面向状态机编程:复杂业务逻辑应对之道

面向,状态机,编程,复杂,业务,逻辑,应对 · 浏览次数 : 57

小编点评

**状态机应用场景** 状态机应用场景对于那种复杂流程也同样适合,在实际项目中根据状态机核心要素梳理出隐性的状态转换关系,从而将一个复杂流程问题转换为状态机的模式问题,再利用状态机模式来实现。 **状态机核心要素** * 状态机核心要素包括状态、事件、状态转换关系等。 * 状态:表示某一状态的定义。 * 事件:表示状态发生的变化。 * 状态转换关系:表示状态之间转换的定义。 **状态机的应用场景** * 状态机用于表示复杂业务逻辑的状态。 * 状态机用于实现复杂业务逻辑的模式转换。 * 状态机用于描述库存状态的变化。 * 状态机用于实现事件触发的处理。 **状态机的应用场景** * 状态机用于表示库存状态的变化。 * 状态机用于实现事件触发的处理。 * 状态机用于描述库存状态的变化。 * 状态机用于实现事件触发的处理。

正文

作者:京东零售 樊思国

一、背景

在研发项目中,经常能遇到复杂的状态流转类的业务场景,比如游戏编程中NPC的跳跃、前进、转向等状态变化,电商领域订单的状态变化等。这类情况其实可以有一种优雅的实现方法:状态机。如下图,是操作系统对进程调度的状态机:

图 操作系统进程调度状态机

二、实现方式

面对以上场景,通常情况下的实现有以下几种,下面分别比较它们适用范围和优缺点:

2.1 if/else

优点:实现简单、直观。

缺点:状态多了代码可读性,业务与状态判断深度耦合,维护和扩展困难。

2.2 状态模式

状态模式类图及使用方式如下:

public class StatePatternDemo {
   public static void main(String[] args) {
      Context context = new Context();
 
      StartState startState = new StartState();
      startState.doAction(context);
 
      System.out.println(context.getState().toString());
 
      StopState stopState = new StopState();
      stopState.doAction(context);
 
      System.out.println(context.getState().toString());
   }
}

优点:状态单独实现,可读性比if/else好。

缺点:扩展状态需增加状态类,状态多了会出现很多状态类;并没有完全实现状态与业务解耦,不利于维护和了解整个系统状态全貌。

2.3 有限状态机

优点:严谨的数学模型,状态转移和业务逻辑基于事件完全解耦,能看到整个系统状态全貌便于维护和扩展。

缺点:需引入状态机实现方式,具备一定理解成本。

三、有限状态机

3.1 定义

有限状态机(英语:finite-state machine,缩写:FSM)又称有限状态自动机(英语:finite-state automaton,缩写:FSA),简称状态机,是表示有限个状态以及在这些状态之间的转移和动作等行为的数学计算模型。

3.2 关键概念

  • 状态State:一般在状态转移图中用圆圈表示。
  • 事件Event:表示从一种状态迁移到另一种状态的触发机制。对应状态转换图中的箭头部分。
  • 动作Action: 表示状态转换后,执行的动作,但不是必须的,也可以状态转换后不执行任何动作。
  • 转移Transition:表示状态转换,从原始状态迁移到目的状态的一个过程。
  • 条件Guard:表示发生状态转移需满足的条件。

3.3 技术选型

在Java项目中,比较常用的有Spring Statemachine和Squirrel-foundation。

框架 优点 缺点
Spring Statemachine 基于Spring生态,社区强大。功能完备,支持多种状态机配置和持久化方式。 较为重量级,额外功能多。单例模式状态机不保证线程安全,只能通过工厂模式创建新的状态机实例实现,对性能有一定影响。
Squirrel-foundation 轻量级实现,状态机的创建开销小。便于二次改造,实现定制业务。 社区没有spring活跃。特殊约定较多。

综上,在下面的项目中,由于团队使用SpringBoot作为开发框架,并且项目不涉及高并发场景,故选择Spring Statemachine。

四、项目实战

在现实项目中,碰到多种状态转换的复杂业务流程,可以通过以下几个步骤进行分级,逐步将产品需求清晰的实现出来:

4.1 需求背景

零售采销在维护SKU物流属性(长、宽、高和重量)时,会因为和物流仓库侧实际属性存在不一致的情况,导致带来物流成本的偏差。为解决这个问题,需设计一个系统供采销通过操作SKU的方式,将属性下发给物流侧审核,得到最终的准确物流属性。在对SKU进行审核操作的过程中,分别存在未操作、任务下发中、下发失败、已审核、自行联系供应商和挂起6种状态(状态转换详见4.2),考虑到这些状态转换的条件分布在不同的场景下,处于对可维护性和扩展性的考虑,采用状态机实现该需求。

4.2 状态转换图

通过梳理状态转换关系,画出状态转换图如下:


SKU属性审核状态转换图

4.3 配置状态机

4.3.1 定义状态枚举

public enum SkuStateEnum {

    /**
     * 未操作
     */
    INIT(0, "未操作"),
    /**
     * 任务下发中
     */
    TASK_DELIVERY(1, "任务下发中"),
    /**
     * 下发失败
     */
    DELIVERY_FAIL(2, "下发失败"),
    /**
     * 复核中
     */
    RECHECKING(3, "复核中"),
    /**
     * 已复核
     */
    RECHECKED(4, "已复核"),
    /**
     * 自行联系供应商
     */
    CONCAT_SUPPLIER(5, "自行联系供应商"),
    /**
     * 挂起
     */
    SUSPEND(6, "挂起");

    /**
     * 状态代码
     */
    private Integer state;
    /**
     * 描述信息
     */
    private String desc;

    SkuStateEnum(Integer state, String desc) {
        this.state = state;
        this.desc = desc;
    }

    public static SkuStateEnum getByState(Integer state) {
        for (SkuStateEnum skuStateEnum : SkuStateEnum.values()) {
            if (skuStateEnum.getState().equals(state)) {
                return skuStateEnum;
            }
        }
        return null;
    }

    public Integer getState() {
        return state;
    }

    public String getDesc() {
        return desc;
    }
}

4.3.2 定义事件枚举

public enum SkuAttrEventEnum {
    /**
     * 调用OMC属性采集接口成功
     */
    INVOKE_OMC_ATTR_COLLECT_API_SUCCESS,
    /**
     * 调用OMC属性采集接口失败
     */
    INVOKE_OMC_ATTR_COLLECT_API_FAIL,
    /**
     * 调用OMC下发查询接口并已经生成采集单
     */
    INVOKE_OMC_SKU_DELIVERY_API_GATHER_FINISH,
    /**
     * 调用OMC下发查询接口失败
     */
    INVOKE_OMC_SKU_DELIVERY_API_FAIL,
    /**
     * OMC的MQ返回SKU属性已变更
     */
    MQ_OMC_SKU_ATTR_CHANGED,
    /**
     * 调用商品中台jsf接口,返回SKU属性已变更
     */
    INVOKE_SKU_ATTR_API_CHANGED,
    /**
     * 京东有库存
     */
    HAS_JD_STOCK,
    /**
     * 京东无库存,VMI有库存
     */
    NO_JD_STOCK_HAS_VMI_STOCK,
    /**
     * 京东和VMI均无库存
     */
    NO_JD_STOCK_NO_VMI_STOCK,
    /**
     * 上传并复核
     */
    UPLOAD_AND_RECHECK;
}

4.3.3 配置状态机

@Configuration
@EnableStateMachineFactory
@Slf4j
public class SkuAttrStateMachineConfig extends StateMachineConfigurerAdapter<SkuStateEnum, SkuAttrEventEnum> {

    /**
     * 配置状态
     *
     * @param states
     * @throws Exception
     */
    @Override
    public void configure(StateMachineStateConfigurer<SkuStateEnum, SkuAttrEventEnum> states) throws Exception {
        states.withStates().initial(SkuStateEnum.INIT)
            .states(EnumSet.allOf(SkuStateEnum.class));
    }

    @Override
    public void configure(StateMachineConfigurationConfigurer<SkuStateEnum, SkuAttrEventEnum> config) throws Exception {
        config.withConfiguration().listener(listener()).autoStartup(false);
    }

    /**
     * 配置状态转换和事件的关系
     *
     * @param transitions
     * @throws Exception
     */
    @Override
    public void configure(StateMachineTransitionConfigurer<SkuStateEnum, SkuAttrEventEnum> transitions)
        throws Exception {
        transitions.withExternal().source(SkuStateEnum.INIT).target(SkuStateEnum.TASK_DELIVERY)
            .event(SkuAttrEventEnum.INVOKE_OMC_ATTR_COLLECT_API_SUCCESS)
            .action(ctx -> {
                log.info("[调用OMC属性采集接口成功],状态变更:{} -> {}.", SkuStateEnum.INIT.getDesc(),
                    SkuStateEnum.TASK_DELIVERY.getDesc());
            })
            .and()
            .withExternal().source(SkuStateEnum.INIT).target(SkuStateEnum.DELIVERY_FAIL)
            .event(SkuAttrEventEnum.INVOKE_OMC_ATTR_COLLECT_API_FAIL)
            .action(ctx -> {
                log.info("[调用OMC属性采集接口失败],状态变更:{} -> {}.", SkuStateEnum.INIT.getDesc(),
                    SkuStateEnum.DELIVERY_FAIL.getDesc());
            })
            .and()
            .withExternal().source(SkuStateEnum.INIT).target(SkuStateEnum.CONCAT_SUPPLIER)
            .event(SkuAttrEventEnum.NO_JD_STOCK_HAS_VMI_STOCK)
            .action(ctx -> {
                log.info("[京东无库存,VMI有库存],状态变更:{} -> {}.", SkuStateEnum.INIT.getDesc(),
                    SkuStateEnum.CONCAT_SUPPLIER.getDesc());
            })
            .and()
            .withExternal().source(SkuStateEnum.INIT).target(SkuStateEnum.SUSPEND)
            .event(SkuAttrEventEnum.NO_JD_STOCK_NO_VMI_STOCK)
            .action(ctx -> {
                log.info("[京东和VMI均无库存],状态变更:{} -> {}.", SkuStateEnum.INIT.getDesc(),
                    SkuStateEnum.SUSPEND.getDesc());
            })
            .and()
            .withExternal().source(SkuStateEnum.SUSPEND).target(SkuStateEnum.TASK_DELIVERY)
            .event(SkuAttrEventEnum.HAS_JD_STOCK)
            .action(ctx -> {
                log.info("[京东有库存],状态变更:{} -> {}.", SkuStateEnum.SUSPEND.getDesc(),
                    SkuStateEnum.TASK_DELIVERY.getDesc());
            })
            .and()
            .withExternal().source(SkuStateEnum.SUSPEND).target(SkuStateEnum.RECHECKING)
            .event(SkuAttrEventEnum.INVOKE_OMC_ATTR_COLLECT_API_SUCCESS)
            .action(ctx -> {
                log.info("[调用OMC属性采集接口成功],状态变更:{} -> {}.", SkuStateEnum.SUSPEND.getDesc(),
                    SkuStateEnum.RECHECKING.getDesc());
            })
            .and()
            .withExternal().source(SkuStateEnum.SUSPEND).target(SkuStateEnum.CONCAT_SUPPLIER)
            .event(SkuAttrEventEnum.NO_JD_STOCK_HAS_VMI_STOCK)
            .action(ctx -> {
                log.info("[京东无库存,VMI有库存]:{} -> {}.", SkuStateEnum.SUSPEND.getDesc(),
                    SkuStateEnum.CONCAT_SUPPLIER.getDesc());
            })
            .and()
            .withExternal().source(SkuStateEnum.TASK_DELIVERY).target(SkuStateEnum.RECHECKING)
            .event(SkuAttrEventEnum.INVOKE_OMC_SKU_DELIVERY_API_GATHER_FINISH)
            .action(ctx -> {
                log.info("[调用OMC下发查询接口并已经生成采集单]:{} -> {}.", SkuStateEnum.TASK_DELIVERY.getDesc(),
                    SkuStateEnum.RECHECKING.getDesc());
            })
            .and()
            .withExternal().source(SkuStateEnum.TASK_DELIVERY).target(SkuStateEnum.RECHECKED)
            .event(SkuAttrEventEnum.MQ_OMC_SKU_ATTR_CHANGED)
            .action(ctx -> {
                log.info("[OMC的MQ返回SKU属性已变更]:{} -> {}.", SkuStateEnum.TASK_DELIVERY.getDesc(),
                    SkuStateEnum.RECHECKED.getDesc());
            })
            .and()
            .withExternal().source(SkuStateEnum.DELIVERY_FAIL).target(SkuStateEnum.TASK_DELIVERY)
            .event(SkuAttrEventEnum.INVOKE_OMC_ATTR_COLLECT_API_SUCCESS)
            .action(ctx -> {
                log.info("[调用OMC属性采集接口成功]:{} -> {}.", SkuStateEnum.DELIVERY_FAIL.getDesc(),
                    SkuStateEnum.TASK_DELIVERY.getDesc());
            })
            .and()
            .withExternal().source(SkuStateEnum.CONCAT_SUPPLIER).target(SkuStateEnum.RECHECKED)
            .event(SkuAttrEventEnum.INVOKE_SKU_ATTR_API_CHANGED)
            .action(ctx -> {
                log.info("[调用商品中台jsf接口,返回SKU属性已变更]:{} -> {}.", SkuStateEnum.CONCAT_SUPPLIER.getDesc(),
                    SkuStateEnum.RECHECKED.getDesc());
            });
    }

    /**
     * 全局监听器
     *
     * @return
     */
    private StateMachineListener<SkuStateEnum, SkuAttrEventEnum> listener() {
        return new StateMachineListenerAdapter<SkuStateEnum, SkuAttrEventEnum>() {
            @Override
            public void transition(Transition<SkuStateEnum, SkuAttrEventEnum> transition) {
                //当状态的转移在configure方法配置中时,会走到该方法。
                log.info("[{}]状态变更:{} -> {}", transition.getKind().name(),
                    transition.getSource() == null ? "NULL" : ofNullableState(transition.getSource().getId()),
                    transition.getTarget() == null ? "NULL" : ofNullableState(transition.getTarget().getId()));
            }

            @Override
            public void eventNotAccepted(Message<SkuAttrEventEnum> event) {
                //当发生的状态转移不在configure方法配置中时,会走到该方法,此处打印error日志,方便排查状态转移问题
                log.error("事件未收到: {}", event);
            }

            private Object ofNullableState(SkuStateEnum s) {
                return Optional.ofNullable(s)
                    .map(SkuStateEnum::getDesc)
                    .orElse(null);
            }
        };
    }
}

4.4 业务逻辑处理

4.4.1 构建状态机

对每个sku的操作,通过状态机工厂stateMachineFactory.getStateMachine

//注入状态机工厂实例
@Autowired
private StateMachineFactory<SkuStateEnum, SkuAttrEventEnum> stateMachineFactory;
//构建状态机
public StateMachine<SkuStateEnum, SkuAttrEventEnum> buildStateMachine(String skuId) throws BusinessException {
        if (StringUtils.isEmpty(skuId)) {
            return null;
        }
        StateMachine<SkuStateEnum, SkuAttrEventEnum> stateMachine = null;
        try {
            //从DB中获取当前skuId对应的状态
            LambdaQueryWrapper<SkuAttrRecheckState> query = Wrappers.lambdaQuery(SkuAttrRecheckState.class);
            query.eq(SkuAttrRecheckState::getSkuId, skuId);
            SkuAttrRecheckState skuAttrRecheckState = this.baseMapper.selectOne(query);
            SkuStateEnum skuStateEnum = SkuStateEnum.getByState(
                skuAttrRecheckState == null ? SkuStateEnum.INIT.getState() : skuAttrRecheckState.getState());
            //从状态机工厂获取一个状态机
            stateMachine = stateMachineFactory.getStateMachine(skuId);
            stateMachine.stop();
            //配置状态机参数
            stateMachine.getStateMachineAccessor().doWithAllRegions(sma -> {
                //配置状态机拦截器,当状态发生转移时,会走到该拦截器中
                sma.addStateMachineInterceptor(new StateMachineInterceptorAdapter<SkuStateEnum, SkuAttrEventEnum>() {
                    @Override
                    public void preStateChange(State<SkuStateEnum, SkuAttrEventEnum> state,
                        Message<SkuAttrEventEnum> message,
                        Transition<SkuStateEnum, SkuAttrEventEnum> transition,
                        StateMachine<SkuStateEnum, SkuAttrEventEnum> stateMachine,
                        StateMachine<SkuStateEnum, SkuAttrEventEnum> rootStateMachine) {
                        //获取状态转移时,对应的SKU详细信息
                        SkuAttrRecheckState result = JSON.parseObject(
                            String.class.cast(message.getHeaders().get(JSON_STR)), SkuAttrRecheckState.class);
                        //更新状态机转移后的状态(来自于4.3.3中的配置)
                        result.setState(state.getId().getState());
                        //将状态机转移后的状态写入DB
                        LambdaQueryWrapper<SkuAttrRecheckState> query = Wrappers.lambdaQuery(SkuAttrRecheckState.class);
                        query.eq(SkuAttrRecheckState::getSkuId, result.getSkuId());
                        if (baseMapper.exists(query)) {
                            UpdateWrapper<SkuAttrRecheckState> updateQuery = new UpdateWrapper<>();
                            updateQuery.eq("sku_id",result.getSkuId());
                            log.info("更新状态信息:{}", JSON.toJSONString(result));
                            baseMapper.update(result, updateQuery);
                        } else {
                            log.info("写入状态信息:{}", JSON.toJSONString(result));
                            baseMapper.insert(result);
                        }
                    }
                });
                //将状态机的初始状态配置为DB中的skuId对应状态
                sma.resetStateMachine(new DefaultStateMachineContext<SkuStateEnum, SkuAttrEventEnum>(
                    skuStateEnum, null, null, null));
            });
            //启动状态机
            stateMachine.start();
        } catch (Exception e) {
            log.error("skuId={},构建状态机失败.", skuId, e);
            throw new BusinessException("状态机构建失败", e);
        }
        return stateMachine;
    }

4.4.2 封装事件

public synchronized Boolean sendEvent(StateMachine<SkuStateEnum, SkuAttrEventEnum> stateMachine,
        SkuAttrEventEnum skuAttrEventEnum, SkuAttrRecheckState skuAttrRecheckState) throws BusinessException {
        try {
            //发送事件,并将需要传递的信息写入header
            stateMachine.sendEvent(MessageBuilder.withPayload(skuAttrEventEnum)
                .setHeader(SKU_ID, skuAttrRecheckState.getSkuId())
                .setHeader(STATE, skuAttrRecheckState.getState())
                .setHeader(JSON_STR, JSON.toJSONString(skuAttrRecheckState))
                .build());
        } catch (Exception e) {
            log.error("发送事件失败", e);
            throw new BusinessException("发送事件失败", e);
        }
        return true;
    }

4.4.3 业务逻辑应用

当用户在界面上对“未操作”状态的SKU点击审核按钮时,会调用物流OMC接口将SKU属性下发到物流侧,当下发成功时,状态会转换为“任务下发中”,当调用接口失败,则会将状态转换为"下发失败",核心代码如下:

public Boolean recheck(List<String> skuIds) throws BusinessException {
        if (CollectionUtils.isEmpty(skuIds)) {
            log.error("参数错误,sku列表为空");
            throw new BusinessException("参数错误,sku列表为空");
        }
        List<SkuAttrExceptionDetail> skuDetails = skuAttrExceptionDetailMapper.queryBySkuIdList(skuIds);
        if (CollectionUtils.isEmpty(skuDetails)) {
            log.error("查询sku异常明细结果集为空,skuIds={}", JSON.toJSONString(skuIds));
            return false;
        }
        for (SkuAttrExceptionDetail detail : skuDetails) {
            if (detail.getState() != SkuStateEnum.INIT.getState()) {
                log.info("{}不是未操作状态sku不进行复核", detail.getSkuId());
                continue;
            }
            //构建SKU对应的状态机
            StateMachine<SkuStateEnum, SkuAttrEventEnum> stateMachine = buildStateMachine(detail.getSkuId());
            SkuAttrRecheckState skuAttrRecheckState = DomainBuilderUtil.buildSkuAttrRecheckState(detail);
            //判定库存并发送事件
            adjustAndSendEvents(detail, stateMachine, skuAttrRecheckState);
        }
        return true;
    }

public void adjustAndSendEvents(SkuAttrExceptionDetail detail,
        StateMachine<SkuStateEnum, SkuAttrEventEnum> stateMachine,
        SkuAttrRecheckState skuAttrRecheckState) throws BusinessException {
        //1、京东有库存,调用物流属性接口,下发SKU属性
        if (detail.getSpotInventoryQtty() > 0) {
            invokeOmcSkuAttrCollectApiAndSendEvent(detail, stateMachine, skuAttrRecheckState);
            return;
        }
        //2、京东无库存,有VMI库存
        if (detail.getSpotInventoryQtty() <= 0 && detail.getVmiInventoryQtty() > 0) {
            sendEvent(stateMachine, SkuAttrEventEnum.NO_JD_STOCK_HAS_VMI_STOCK, skuAttrRecheckState);
            return;
        }
        //3、京东和VMI均无库存
        if (detail.getSpotInventoryQtty() <= 0 && detail.getVmiInventoryQtty() <= 0) {
            sendEvent(stateMachine, SkuAttrEventEnum.NO_JD_STOCK_NO_VMI_STOCK, skuAttrRecheckState);
            return;
        }
    }

private void invokeOmcSkuAttrCollectApiAndSendEvent(SkuAttrExceptionDetail detail,
        StateMachine<SkuStateEnum, SkuAttrEventEnum> stateMachine,
        SkuAttrRecheckState skuAttrRecheckState) throws BusinessException {
        DistrustAttributeGatherRequest request = RequestUtil.buildOmcAttrCollectRequest(detail, reqSource);
        try {
            if (jsfInvokeService.invokeSkuAttrCollectApi(request)) {
                sendEvent(stateMachine, SkuAttrEventEnum.INVOKE_OMC_ATTR_COLLECT_API_SUCCESS,
                    skuAttrRecheckState);
            } else {
                sendEvent(stateMachine, SkuAttrEventEnum.INVOKE_OMC_ATTR_COLLECT_API_FAIL, skuAttrRecheckState);
            }
        } catch (Exception e) {
            log.error("调用物流Sku属性采集接口错误,request={}", JSON.toJSONString(request), e);
            sendEvent(stateMachine, SkuAttrEventEnum.INVOKE_OMC_ATTR_COLLECT_API_FAIL, skuAttrRecheckState);
        }
    }

五、总结

本文通过介绍有限状态机,并结合具体项目,通过状态机的应用将状态和业务逻辑解耦,便于简化复杂业务逻辑,降低理解成本。另外,状态机的应用场景对于那种复杂的流程也同样适合,可以在实际项目中根据状态机核心要素梳理出隐性的状态转换关系,从而将一个复杂的流程问题转换为状态机的模式问题,再利用状态机模式来实现,可以有助于我们优雅的解决更广泛的复杂业务问题。

与面向状态机编程:复杂业务逻辑应对之道相似的内容:

面向状态机编程:复杂业务逻辑应对之道

在研发项目中,经常能遇到复杂的状态流转类的业务场景,比如游戏编程中NPC的跳跃、前进、转向等状态变化,电商领域订单的状态变化等。这类情况其实可以有一种优雅的实现方法:状态机。本文重点介绍有限状态机,并结合具体项目,通过状态机的应用将状态和业务逻辑解耦,便于简化复杂业务逻辑,降低理解成本。另外,重点讲解如何优雅的解决更广泛的复杂业务问题。

5.2 汇编语言:标志位测试指令

汇编语言是一种面向机器的低级语言,用于编写计算机程序。汇编语言与计算机机器语言非常接近,汇编语言程序可以使用符号、助记符等来代替机器语言的二进制码,但最终会被汇编器编译成计算机可执行的机器码。标志位测试指令是汇编语言中用于测试处理器标志位状态的指令。标志位是位于处理器状态寄存器中的一组特殊标志,用于指示上一个运算的结果是否为零、是否进位/借位、是否溢出等等。可以使用标志位测试指令来检查标志位的状态

Java并发篇:6个必备的Java并发面试种子题目

免费体验AI绘画:https://www.topgpt.one;文章涉及了几个常见的并发编程相关的主题。首先,线程的创建和生命周期是面试中常被问及的话题,面试官可能会询问如何创建线程、线程的状态转换以及如何控制线程的执行顺序等。其次,synchronized关键字是用于实现线程同步的重要工具,面试中可能会涉及到它的使用场景以及与其他同步机制的比较。此外,抽象队列同步器(AQS)是Java并发编程中

手机穿戴设备能力共享,提升丰富交互体验

HUAWEI Wear Engine面向手机和穿戴设备的应用与服务开发者,提供华为穿戴设备开放能力。 开发者通过调用Wear Engine开放能力,可以实现手机上的生态应用与服务给华为穿戴设备发消息、发通知、传输数据,并获取穿戴设备状态、读取传感器数据等,也可以实现华为穿戴设备上的生态应用与服务给手

Axure 创建轮播图

拖一个动态面板,设置名称 双击动态面板,添加3个状态 给3个状态,分别添加3张图片 设置交互 新建交互 -> 载入时 -> 设置面板状态 双击进去,界面看得直观些 下一项、向后循环,循环间隔 2S

Axure 公告通知

1、一个浅黄色的底图 (710X30) ; 2、一个喇叭小图标(Volume up) ; 3、一个动态面板。 动态面板中设置statel、state2和state3三种面板状态,这三种状态中分别放三个文本标签均为14号字,红色字体。 设置动态面板的选择状态为Next,向后循环,循环间隔为3000毫秒

Axure 列表左右滑动交互-删除、置顶

Axure 列表左右滑动交互 左滑:删除、标记 右滑:置顶、回复 拖一个动态面板,命名为【滑动面板】,添加三个状态,并分别命名为:正常状态、向左状态、向右状态 添加元件 正常状态 向左状态 将【正常状态】里的元件复制到 【向左状态】 中,并进行组合,方便调整 并将组合元件向左移动,右边留出两个按钮空

实时数据的处理一致性如何保证?

实时数据一致性的定义以及面临的挑战 数据一致性通常指的是数据在整个系统或多个系统中保持准确、可靠和同步的状态。在实时数据处理中,一致性包括但不限于数据的准确性、完整性、时效性和顺序性。 下图是典型的实时/流式数据处理的流程: 流式数据以各种方式推送到kafka中 flink流式数据处理引擎将数据处理

5种GaussDB ETCD服务异常实例分析处理

摘要:一文带你细数几种ETCD服务异常实例状态。 本文分享自华为云社区《【实例状态】GaussDB ETCD服务异常》,作者:酷哥 。 首先确认是否是虚拟机、网络故障 虚拟机故障导致ETCD服务异常告警 问题现象 管控面上报etcd服务异常告警,虚拟机发生重启,热迁移、冷迁移,HA等动作。 问题分析

[转帖]深入理解同步机制---内核自旋锁

https://switch-router.gitee.io/blog/spinlock/ 进程(线程)间的同步机制是面试时的常见问题,所以准备用一个系列来好好整理下用户态与内核态的各种同步机制。本文就以内核空间的一种基础同步机制—自旋锁开始好了 自旋锁是什么 自旋锁就是一个二状态的原子(atomi