• 状态机在分布式系统中的应用


    前言


    在大型的分布式系统中,经常会涉及到状态的改变,这里的状态变化可以分很多种,最极端的情况是,任何状态之间都可以互相切换。这种状态之间的切换,转变,更加官方一点的称为叫状态机。这个词可能很多人会感到比较陌生,英文就是State Machine。所以如果大家在学习开源项目中,看到这个单词,指的就是状态机的意思。那么状态机有什么用途呢,为什么我们要定义这样一个概念呢?本文笔者就来简单聊聊状态机的管理。

    状态机有何用


    我们老是说状态机,状态机,那么它到底有何用呢?答案很简单,5个字:状态机管理。很明显,它的用途在于状态的管理。可能有些人会觉得奇怪,为什么需要引入状态机的概率来管理状态呢,一种简单的做法是在特定的条件下特定状态的更新,不就OK了吗。比如说下面这种方式:

        switch(State s)
            case s1:
                s.setState(NewState1)
            case s2:
                s.setState(NewState2)
            case s3:
                s.setState(NewState3)
            ...

    以上这种通过if语句或switch语句在条件判断,然后进行状态改变的逻辑,从原理上来说并没有错。但是大家想一想,如果系统够复杂,中间涉及的状态足够多,而且每个不同状态切换所需要的判定条件(这里可理解为触发的event事件)又不同,那么这种方式会带来很大的弊端,

    状态多的情况下容易出错,而且维护成本很高,如果未来要更改状态规则的时候,无法全局修改。无法清晰了解整个状态变化过程。

    所以在拥有复杂状态变化的情况下,我们一般的做法都是引入状态机管理对象,来专门做状态的更新。那么我们如何定义状态机管理对象呢?接着往下看。

    状态机对象如何定义


    在定义这个类对象之前,一般我们要先设计好系统的状态变化图,里面应该包含2大元素信息:

    • 1.系统所有可能出现的状态
    • 2.不同状态间进行切换的触发事件(event)。

    这里笔者以Ozone中的Container状态机管理为例,如下所示:

      // Client-driven Create State Machine
      // States: <ALLOCATED>------------->CREATING----------------->[OPEN]
      // Events:            (BEGIN_CREATE)    |    (COMPLETE_CREATE)
      //                                      |
      //                                      |(TIMEOUT)
      //                                      V
      //                                  DELETING----------------->[DELETED]
      //                                           (CLEANUP)
    

    我们可以看到上面总共有5个状态,4个event事件,对应着4种状态关系的切换。我们摘取其中一段:

     <ALLOCATED>------------->CREATING----------------->[OPEN]
               (BEGIN_CREATE)    |    (COMPLETE_CREATE)

    这里包含了2种切换。1.ALLOCATED分配状态经过BEGIN_CREATE事件后,状态变为CREATING状态。2.然后收到创建完成的事件COMPLETE_CREATE,状态又从CREATING变为OPEN。

    但是单纯看图形的描述,当然很好理解,下面我们来想想如何在代码层面进行实现呢?

    首先第一步,定义好所有的状态和相应的事件,这个我们一般会用枚举来做。OK,这一步很简单,大家肯定都会。继续下一步。

    定义状态机管理类,继续以Ozone的Container状态机管理为例,代码如下:

    /**
     * Template class that wraps simple event driven state machine.
     * @param <STATE> states allowed
     * @param <EVENT> events allowed
     */
    public class StateMachine<STATE extends Enum<?>, EVENT extends Enum<?>> {
      // 系统初始状态
      private STATE initialState;
      // 系统潜在的最后状态
      private Set<STATE> finalStates;
    
      // Event事件 --><初始状态,结束状态> 的映射图
      private final LoadingCache<EVENT, Map<STATE, STATE>> transitions =
          CacheBuilder.newBuilder().build(
              CacheLoader.from((Supplier<Map<STATE, STATE>>) () -> new HashMap()));
    
      public StateMachine(STATE initState, Set<STATE> finalStates) {
        this.initialState = initState;
        this.finalStates = finalStates;
      }
      ...
    
      /**
       * 添加状态变化规则
       */
      public void addTransition(STATE from, STATE to, EVENT e) {
        // 在e对应的event事件内增加新<from, to>状态变化对
        transitions.getUnchecked(e).put(from, to);
      }
    
      /**
       * 获取下一状态,根据当前状态和触发event事件
       */
      public STATE getNextState(STATE from, EVENT e)
          throws InvalidStateTransitionException {
        // 获取到下一状态
        STATE target = transitions.getUnchecked(e).get(from);
        if (target == null) {
          throw new InvalidStateTransitionException(from, e);
        }
        return target;
      }
    }

    这个代码也不是绝对的,只要我们能够表达上图中的event和状态间的关系即可,没有强制性的结构定义规则。

    类结构定义完毕之后,我们第一步要做的操作就是初始化状态机对象,通过addTransition方法构造出之前设计好的关系结构。相当于一次add操作就是加了一个箭头。那么总共就是4次添加操作。

    private void initializeStateMachine() {   
        stateMachine.addTransition(OzoneProtos.LifeCycleState.ALLOCATED,
            OzoneProtos.LifeCycleState.CREATING,
            OzoneProtos.LifeCycleEvent.BEGIN_CREATE);
    
        stateMachine.addTransition(OzoneProtos.LifeCycleState.CREATING,
            OzoneProtos.LifeCycleState.OPEN,
            OzoneProtos.LifeCycleEvent.COMPLETE_CREATE);
    
        stateMachine.addTransition(OzoneProtos.LifeCycleState.OPEN,
            OzoneProtos.LifeCycleState.CLOSED,
            OzoneProtos.LifeCycleEvent.CLOSE);
    
        stateMachine.addTransition(OzoneProtos.LifeCycleState.OPEN,
            OzoneProtos.LifeCycleState.DELETING,
            OzoneProtos.LifeCycleEvent.DELETE);
    }

    状态机在系统中如何应用


    状态机类定义好了,那么我们如何在系统中进行实际应用呢?此时我们只需要明白一点,其它系统代码无须关注对象状态的变换规则,它只负责传入档期状态以及event事件,后面的状态结果交给状态机对象来做。下面是一个简单的例子:

      /**
       * {@inheritDoc}
       * Used by client to update container state on SCM.
       */
      @Override
      public OzoneProtos.LifeCycleState updateContainerState(String containerName,
          OzoneProtos.LifeCycleEvent event) throws IOException {
        ContainerInfo containerInfo;
        lock.lock();
        try {
          ...
          containerInfo = ContainerInfo.fromProtobuf(
              OzoneProtos.SCMContainerInfo.PARSER.parseFrom(containerBytes));
    
          OzoneProtos.LifeCycleState newState;
          try {
             // 传入当前container状态,和event事件对象,通过状态机获取到下一个新的状态
             newState = stateMachine.getNextState(containerInfo.getState(), event);
          } catch (InvalidStateTransitionException ex) {
            throw new SCMException("Failed to update container state"
                + containerName + ", reason : invalid state transition from state: "
                + containerInfo.getState() + " upon event: " + event + ".",
                SCMException.ResultCodes.FAILED_TO_CHANGE_CONTAINER_STATE);
          }
          // 设置新状态
          containerInfo.setState(newState);
          containerStore.put(dbKey, containerInfo.getProtobuf().toByteArray());
          return newState;
        } finally {
          lock.unlock();
        }
      }

    其实我们可以看到,这样就可以避免了很多很重的判定逻辑分散在程序各地。

    希望通过简单的分析能够给读者朋友带来收获。

  • 相关阅读:
    写在学期将要结尾处
    程序有感
    Python字典操作小汇总
    mysql 乱码解决方案
    公告 百度安家
    本周工作安排
    bind1st/bind2nd
    OpenGL放大缩小实现
    Eclipse安装SVN插件
    QQ也玩命令行
  • 原文地址:https://www.cnblogs.com/bianqi/p/12183659.html
Copyright © 2020-2023  润新知