【Alibaba Cola 状态机】重点解析以及实践案例
【Alibaba Cola 状态机】重点解析以及实践案例
1. 状态模式
状态模式是一种行为型设计模式,允许对象在内部状态改变时改变其行为,简单地讲就是,一个拥有状态的context对象,在不同状态下,其行为会发生改变。看起来是改变了对象各个接口方法的实现一样。
模式中包含角色:
- 上下文
- 抽象状态
- 具体状态
优点包括解耦客户端和状态对象,可扩展性强,避免大量条件语句。
缺点是可能增加系统类的数量和复杂性。
适用场景如自动售货机的状态转换、线程状态管理等。代码示例展示了自动售卖机如何利用状态模式实现不同状态的切换。
推荐文章:23种设计模式之状态模式(State Pattern)_状态模式哪种状态切换好-CSDN博客
2. 状态机
状态机(Finite State Machine,简称FSM)是一个数学模型,用于描述对象在其生命周期中可能的状态以及状态之间的转换。
它由一组状态、一组事件、一组转换规则和一组动作组成,能够清晰地表示和管理对象的行为和状态变化。
在状态机中,“有限”指的是状态和事件都是有限的,即存在一个有限的状态集和事件集。这使得状态机具有明确的、可控的行为模式,便于分析和实现。
状态机的组成元素:状态、事件、转换、动作
状态(State): 表示对象的当前情况或条件。状态机中的状态是有限的,例如:待机、运行、暂停、停止等。
事件(Event): 触发状态转换的输入或信号。事件可以是外部输入、内部触发或其他系统生成的信号。
转换(Transition): 定义了状态之间的切换规则。当特定的事件发生时,状态机会根据转换规则从一个状态转换到另一个状态。
动作(Action): 在状态转换过程中执行的操作或任务。动作可以是状态进入前执行的预处理、状态转换时执行的中间操作或状态退出后执行的清理工作。
状态机的优缺点
优点:
- 逻辑清晰:状态机将复杂的系统行为分解为简单的状态和状态之间的转换,使系统逻辑更加清晰和易于理解。
- 可维护性高:由于状态机的逻辑是模块化的,任何状态的修改或扩展只需在该状态的实现部分进行,不会影响其他部分,增强了代码的可维护性。
- 可扩展性强:新的状态和转换可以方便地添加到现有的状态机中,使系统具有良好的扩展性。
缺点:状态爆炸,对于复杂系统,状态和转换的数量可能非常庞大,导致状态机图变得复杂难以管理,这被称为“状态爆炸”问题。
3. Alibaba Cola 状态机
实现一个状态机引擎,教你看清DSL的本质_状态机 dsl-CSDN博客
相比Spring statemachine状态机等的复杂,功能多;
但是我们 实际业务员 需要常用的功能,简单使用,所以这类就显得不简洁;再看cola-statemachine相比就是小巧、无状态、简单、轻量、性能极高的状态机DSL实现,解决业务中的状态流转问题。
如果是实现业务的话,阿里状态机是不二之选,更加适合我们日常开发!KISS(Keep It Simple and Stupid)
而如果是比较复杂的业务或者组件开发,这个状态机可能也能 妙用进行应对,面对不了可能就得用 Spring statemachine 有状态的状态机,或者对阿里状态机进行 二次开发
开源者的初心就是,让开发更舒适,结合实际需求抽离出必要部分,而不是严格按照传统状态模式,传统状态机
无状态状态机–cola stateMachine-CSDN博客
状态机本身没有状态,而是提供一个状态机单例,通过输入参数 “起始状态”、“事件”、“上下文”,这些参数足以确定一次状态轮转,期间通过 condition 和 action 后,返回“最终状态”,若轮转失败,则返回原状态;
由于没有状态,所以调用者使用这个状态机都是互不干扰的,通过输入决定输出(一次轮转),做到一个实例服务多个调用者
- 这里的上下文,就是过程中的通行数据罢了
- 你也可以在上下文里面去设置状态,像状态模式那样,当其实在使用状态机的时候,状态机就相当于状态模式的上下文,这样看,发挥状态机的优势,就没必要设置了。
采用了无状态设计之后,我们就可以使用一个状态机 Instance 来响应所有的请求了,性能会大大的提升
原本的状态机是有状态的,状态机当前的状态决定了其行为,这样导致每次请求,都是申请一个新的状态机去维护状态的轮转,轮转的过程,状态机的状态也在时刻发生变化;
当阿里状态机理念是状态机每次进行一次轮转就行了,哪怕需要连续的过程性的轮转,多次请求状态机即可(一个轮转的 action 嵌套执行外部轮转的事件,返回的最终状态可能与实际不符,最好嵌套执行内部轮转而不是外部轮转;或者等轮转结束,再按照这次请求的响应进行下次轮转)
有状态的状态机就相当于可以本地运行的应用,不同客户之间是隔离的,状态在应用内部轮转
无状态的状态机就相当于 web 应用,所有客户访问同一个 web,请求一次,执行一次特定的轮转,响应结果
4. Alibaba Cola 状态机使用
源码其实不难,大部分还是容易看懂的,如果出现问题按照思路可以调试去查查,这里就是我总结的用法。
/*** Alibaba Cola 状态机关键词* State:状态* Event:事件,状态由事件触发,引起变化* Transition:流转,表示从一个状态到另一个状态* External Transition:外部流转,两个不同状态之间的流转* Internal Transition:内部流转,同一个状态之间的流转* Condition:条件,表示是否允许到达某个状态* Action:动作,到达某个状态之后,可以做什么* StateMachine:状态机*/
4.1 StateMachineUtil(核心方法封装)
@Slf4j
public class StateMachineUtil {public static <S, E, C> StateMachine<S, E, C> getMachine(String machineId) {return StateMachineFactory.get(machineId);}public static void showMachine(String machineId) {getMachine(machineId).showStateMachine();}public static String generatePlantUML(String machineId) {return getMachine(machineId).generatePlantUML();}public static <S, E, C> void printMachine(String machineId) {StateMachine<S, E, C> stateMachine = getMachine(machineId);stateMachine.showStateMachine();System.out.println(stateMachine.generatePlantUML());}public static <S, E, C> S fireEvent(String machineId, S state, E event, C context) {return (S) getMachine(machineId).fireEvent(state, event, context);}}
- 通过 machineId 获得状态机实例:
StateMachineFactory.get(machineId)
- 打印状态机(注意重写 toString 方法到想要的效果)
- 执行状态机(machineId,起始状态,事件,上下文)(返回最终状态)
4.2 状态机准备信息
根据状态机轮转所需的信息,也就是准备信息,在状态机构建过程中需要用到;
我将其抽象成接口,有两个:外部流转助手、内部流转助手
- 状态的事件会让状态变成别的状态,还是不变就是这里外和内
public interface StateExternalTransitionHelper<S, E, C> {List<S> getFromState();S getToState(S from) throws GlobalServiceException;E getOnEvent();Condition<C> getWhenCondition();Action<S, E, C> getPerformAction();}
from 为 list 是因为有时候同一事件,多个状态都可以执行,其中 getToState 通过 from 得知 to,并允许抛出异常;
public interface StateInternalTransitionHelper<S, E, C> {List<S> getWithinList();E getOnEvent();Condition<C> getWhenCondition();Action<S, E, C> getPerformAction();
}
4.3 通过准备信息构造状态机
外部流转助手、内部流转助手 的实现类,就蕴含了准备信息,将这些在状态机构造过程中应用:
private static <S, E, C> void builderAssign(StateMachineBuilder<S, E, C> builder,StateExternalTransitionHelper<S, E, C> helper) {List<S> fromStateList = helper.getFromState();if(!CollectionUtil.isEmpty(fromStateList)) {E onEvent = helper.getOnEvent();Condition<C> whenCondition = helper.getWhenCondition();Action<S, E, C> performAction = helper.getPerformAction();fromStateList.forEach(fromState -> {try {S toState = helper.getToState(fromState); // 若抛异常就忽略这一个,构造下一个状态轮转// 不保证每个 toState 相等的情况下,不用 externalTransitions 与 fromAmongbuilder.externalTransition().from(fromState).to(toState).on(onEvent).when(whenCondition).perform(performAction);} catch (GlobalServiceException e) {log.warn(e.getMessage());}});}
}private static <S, E, C> void builderAssign(StateMachineBuilder<S, E, C> builder,StateInternalTransitionHelper<S, E, C> helper) {List<S> withinList = helper.getWithinList();if(!CollectionUtil.isEmpty(withinList)) {E onEvent = helper.getOnEvent();Condition<C> whenCondition = helper.getWhenCondition();Action<S, E, C> performAction = helper.getPerformAction();withinList.forEach(within -> {builder.internalTransition().within(within).on(onEvent).when(whenCondition).perform(performAction);});}
}public static <S, E, C> void buildMachine(String machineId,List<? extends StateExternalTransitionHelper<S, E, C>> externalHelpers,List<? extends StateInternalTransitionHelper<S, E, C>> internalHelpers) {// 创建一个 builderStateMachineBuilder<S, E, C> builder = StateMachineBuilderFactory.create();// 添加轮转externalHelpers.forEach(helper -> {builderAssign(builder, helper);});internalHelpers.forEach(helper -> {builderAssign(builder, helper);});// 创建状态机builder.build(machineId);
}
4.4 示例(简历状态轮转)
不要直接实现那两个接口,要先用模块内部的接口继承,这样注入 Spring 容器中的 bean,获取的时候指定我们自己的接口类型,防止获得别的模块的 helper bean
public interface ResumeStateExternalTransitionHelper extends StateExternalTransitionHelper<ResumeStatus, ResumeEvent, ResumeContext> {}
public interface ResumeStateInternalTransitionHelper extends StateInternalTransitionHelper<ResumeStatus, ResumeEvent, ResumeContext> {}
上下文:
@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Slf4j
public class ResumeContext {private Long managerId;private StuResume resume;ResumeExecuteDTO executeDTO;public void log(ResumeStatus from, ResumeStatus to, ResumeEvent event) {log.info("resume state from {} to {} run {} currentResume {} managerId {} executeDTO {}",from, to, event, resume.getId(), managerId, executeDTO);}}
简历状态:
@Getter
public enum ResumeStatus {DRAFT("草稿", 0),PENDING_SELECTION("待筛选", 1),REJECTED("筛选不通过", 2),SCHEDULE_INITIAL_INTERVIEW("待安排初试", 3),PENDING_INITIAL_INTERVIEW("待初试", 4),INITIAL_INTERVIEW_PASSED("初试通过", 5), // 仅当初试为最后一个流程时显示INITIAL_INTERVIEW_FAILED("初试不通过", 6), // 仅当初试为最后一个流程时显示SCHEDULE_SECOND_INTERVIEW("待安排复试", 7),PENDING_SECOND_INTERVIEW("待复试", 8),SECOND_INTERVIEW_PASSED("复试通过", 9), // 仅当复试为最后一个流程时显示SECOND_INTERVIEW_FAILED("复试不通过", 10), // 仅当复试为最后一个流程时显示SCHEDULE_FINAL_INTERVIEW("待安排终试", 11),PENDING_FINAL_INTERVIEW("待终试", 12),FINAL_INTERVIEW_PASSED("终试通过", 13), // 仅当复试为最后一个流程时显示FINAL_INTERVIEW_FAILED("终试不通过", 14), // 仅当复试为最后一个流程时显示PENDING_HANDLING("待处理", 15),SUSPENDED("挂起", 16),;ResumeStatus(String message, Integer code) {this.message = message;this.code = code;}@Overridepublic String toString() {return message;}private final String message;@EnumValue@JsonValueprivate final Integer code;public static ResumeStatus get(Integer code) {for (ResumeStatus resumeStatus : ResumeStatus.values()) {if(resumeStatus.getCode().equals(code)) {return resumeStatus;}}throw new GlobalServiceException(GlobalServiceStatusCode.USER_RESUME_STATUS_EXCEPTION);}
}
简历事件:
@Getter
public enum ResumeEvent {NEXT(1, "推进"),APPROVE(2, "通过"),ELIMINATE(3, "淘汰"),RESET(4, "重置"),PENDING(5, "待处理"),SUSPEND(6, "挂起"),CONFIRM(7, "转正"),;@Overridepublic String toString() {return description;}private final Integer event;private final String description;ResumeEvent(Integer event, String description) {this.event = event;this.description = description;}public static ResumeEvent get(Integer event) {for (ResumeEvent resumeEvent : ResumeEvent.values()) {if(resumeEvent.getEvent().equals(event)) {return resumeEvent;}}throw new GlobalServiceException(GlobalServiceStatusCode.USER_RESUME_STATUS_TRANS_EVENT_ERROR);}}
你也可以不是枚举类,但是我们要提供有限的固定的实例,并且代表状态和事件,枚举太适用了!
常量类:
public interface ResumeStateMachineConstants {String RESUME_STATE_MACHINE_ID = "resumeStateMachineId";}
配置类:
@Configuration
@RequiredArgsConstructor
public class ResumeStateMachineBuildConfig {private final List<ResumeStateExternalTransitionHelper> externalHelpers;private final List<ResumeStateInternalTransitionHelper> internalHelpers;@PostConstructpublic void buildInterviewMachine() {StateMachineUtil.buildMachine(ResumeStateMachineConstants.RESUME_STATE_MACHINE_ID,externalHelpers,internalHelpers);StateMachineUtil.printMachine(ResumeStateMachineConstants.RESUME_STATE_MACHINE_ID);}}
一个 helper 实现展示:
@Component
@RequiredArgsConstructor
public class ResumeNextStateHelper implements ResumeStateExternalTransitionHelper {private final Condition<ResumeContext> defaultResumeCondition;private final Action<ResumeStatus, ResumeEvent, ResumeContext> defaultResumeAction;@Overridepublic List<ResumeStatus> getFromState() {return List.of(DRAFT,PENDING_SELECTION,SCHEDULE_INITIAL_INTERVIEW,PENDING_INITIAL_INTERVIEW,SCHEDULE_SECOND_INTERVIEW,PENDING_SECOND_INTERVIEW,SCHEDULE_FINAL_INTERVIEW);}@Overridepublic ResumeStatus getToState(ResumeStatus from) throws GlobalServiceException {return switch (from) {case DRAFT -> PENDING_SELECTION;case PENDING_SELECTION -> SCHEDULE_INITIAL_INTERVIEW;case SCHEDULE_INITIAL_INTERVIEW -> PENDING_INITIAL_INTERVIEW;case PENDING_INITIAL_INTERVIEW -> SCHEDULE_SECOND_INTERVIEW;case SCHEDULE_SECOND_INTERVIEW -> PENDING_SECOND_INTERVIEW;case PENDING_SECOND_INTERVIEW -> SCHEDULE_FINAL_INTERVIEW;case SCHEDULE_FINAL_INTERVIEW -> PENDING_FINAL_INTERVIEW;default -> throw new GlobalServiceException(GlobalServiceStatusCode.USER_RESUME_STATUS_EXCEPTION);};}@Overridepublic ResumeEvent getOnEvent() {return ResumeEvent.NEXT;}@Overridepublic Condition<ResumeContext> getWhenCondition() {return defaultResumeCondition;}@Overridepublic Action<ResumeStatus, ResumeEvent, ResumeContext> getPerformAction() {return defaultResumeAction;}
}
这里这两个是我写的默认值,并注入了容器,这样的写法,需要注意 bean 的名称不要冲突了
通过以上代码,在项目启动的时候即可构造出状态机:
执行状态机(代码片段):
我个人觉得没必要在状态机内部将状态落库,根据请求状态机的响应,进行落库
5. 小思考 · 状态机设计的合理性
状态机并不是通过 from 和 to 确定 event,而是 from 和 event 确定轮转
通过 from 到 to 的变化,是确定不了事件的,实现通过 from 到 to 的变化触发某一事件,事件可能不止一个,而且并不保证触发我们想要的那一个
比如一次请求,对象的当前属性就是 from
,请求是以 event
作为参数,还是以 to
作为参数
① 若是 event
作为参数,那么根据 from
和 event
即可进行状态轮转,并返回最终的状态
② 若是 to
作为参数,from
到 to
的这个状态变化会触发什么特定行为
状态机更注重的是状态的轮转,也就是 ①,而 ② 是状态变化的后置行为
以“一场面试”为例,一场面试的状态可以是: 未开始 、 进行中 、 已结束
如果是 ①,传入的参数可以是 “开始面试” 这一具体事件,当前面试状态是 ”未开始“,就可以轮转成”进行中“,也可以是 “通知用户” 这一事件,状态内部流转的过程中对用户进行面试通知;
如果是 ②,传入的参数可以是 ”进行中“,面试状态将直接更新成”进行中“,状态变化触发对应的事件,但做不了内部轮转
① 这种方式靠的是状态机,让请求行为更加具体,如进行“开始面试”,“通知用户”这种具体的行为
而 ② 更加狭隘,如果我们设计一份代码来管理 from
到 to
触发的事件,如果要符合我们的接口预期,这往往可能 并不能做到 ① 的可读性高、灵活性高、通用性高、可扩展性高
用 ② 来进行状态轮转不太合适,但是也不是完全没用,更适合用其管理一些状态变化固定的“副作用”,作为状态轮转的后置行为,还是不错的!可以结合责任链模式去实现
但是又说回来,
from
到to
的转变的副作用,在编写代码的时候应该也就是规定对应事件的副作用吧,比如 “未开始” 到 “进行中”,就是“面试开始” 这一具体行为的副作用,那其实在状态机就能实现啊;也就是说 ② 这种方式适合,有多个事件可以进行
from
到to
的轮转,并且这些事件都有共同的副作用,这个副作用就可以用 ② 来管理,出现这种情况也很极端了~在比较简单的系统,
from
和to
能够 可以确定唯一的事件,没有内部轮转的需求 ,用这个也无所谓
如果非要 ② 这种请求方式,不想指定 event
,出现特殊的状态变化,就得自动触发对应事件,这种就属于特殊需求了, from
到 to
触发的事件的映射关系,也只能在 to
已知的情况下可以复用,甚至只用于这一特殊请求,代码写得可能比较死,不太优雅🙁,太为难了(;′⌒`),反正我不是很喜欢,我个人比较喜欢优雅直观的设计;
我个人认为要是有个接口可以任意改变状态,那么应该不需要触发什么特定事件了,毕竟都允许可以任意更改了,或者说有一个统一的事件
可以定义一个”任意更改状态“的事件,上下文携带 toState,所有状态都可以触发,在状态机的内部/外部轮转(状态的行为)即可
但这不符合状态机明确的设计理念
综上所述,
用 ① 更加合适,可以让请求行为更加优雅具体直观和灵活,开发低耦合更具有扩展性,能实现的功能更多;② 的这种请求方式适合那种状态变化没有任何事件触发的场景,或触发的都是同一事件的场景,等不具有状态轮转的过程概念的场景。
BUT,
规矩是死的,视具体需求而论,切勿只纸上谈兵!
以上也只是我的想法,不同人有不同的想法和理念!
状态机还可以结合很多其他的设计模式,更多妙用等你开发!
状态机你爱咋用咋用,只要合理都 OK 啦!
开发是灵活的过程,开发者的理念为准,没有固定的强限定,但是我们要满足其基本的优秀写法!
相关文章:

【Alibaba Cola 状态机】重点解析以及实践案例
【Alibaba Cola 状态机】重点解析以及实践案例 1. 状态模式 状态模式是一种行为型设计模式,允许对象在内部状态改变时改变其行为,简单地讲就是,一个拥有状态的context对象,在不同状态下,其行为会发生改变。看起来是改…...

购买商城源码前需要考虑哪些方面?
前言 购买商城源码前需要考虑的方面包括功能满足、技术兼容性、可扩展性、公司实力、客户评价、安全性与稳定性等。 购买商城源码是一项重要决策,需要综合考虑多个因素。以下是详细的考虑方面: 1.功能满足: 确保所选的源码能够支持企业所…...

MongoDB快速入门CRUD
1. 数据库管理 1.1 切换数据库 切换到名为 myDatabase 的数据库。如果该数据库不存在,MongoDB 会在第一次写入数据时自动创建它。 use myDatabase;1.2 查看当前数据库 显示当前使用的数据库的名称。 db; 1.3 显示所有数据库 列出当前 MongoDB 实例中的所有数…...

【python基础】—利用pandas读取或写入mysql表数据
文章目录 一、read_sql()二、to_sql()三、连接数据库方式—MySQL1、用sqlalchemy包构建数据库链接2、用DBAPI构建数据库链接 四、容易遇到的问题 一、read_sql() 功能 将 SQL 查询/数据库表读入 DataFrame。 语法 读取数据库(通过SQL语句或表名) pand…...

C/C++信号量
文章目录 一、信号量介绍1.1 什么是信号量1.2 信号量的原子性1.3 信号量的使用 二、C语言使用2.1 函数接口2.2 信号量代码 三、C20使用3.1 函数接口 四、C11模拟信号量 一、信号量介绍 1.1 什么是信号量 信号量是一种特殊的变量,是操作系统层面的,可以…...

SSL Pining 问题解决方案
实战案例 为了能够更好的复现 SSL Pining 场景,我们对一个 App(https:app4.scrape.center)进行抓包,这个 App 包含了 SSL Pining 的相关设置,如果我们将手机的代理设置为抓包软件提供的代理服务,那么这个 …...

【Spring Boot】全局异常处理
目录 背景 前言 设计步骤 1.定义异常信息类: 2.自定义异常: 3.创建全局异常处理类 4.在控制器中抛出异常 5.输出 捕获 Valid 校验异常 背景 去面试的时候被问到SpringBoot项目中,如何处理全局异常的,也就是如何捕获全局异…...

安全基础学习-SM3加密算法
SM3是一种广泛使用在中国国家标准中的哈希算法,全称为“中国国家密码算法SM3”。它由中国国家密码管理局制定,主要用于数字签名和消息完整性验证。SM3算法与SHA-256在结构上类似,但其设计具有特定的改进以增强安全性。 SM3算法生成256位的哈希值,使用了32轮的迭代运算,并…...

MySQL中处理JSON数据:大数据分析的新方向
1. 简介 1.1. 概述 在MySQL中处理JSON数据的能力是在MySQL 5.7版本中引入的,并在后续的版本中不断得到增强。这使得MySQL能够直接操作和查询JSON格式的数据,极大地扩展了其处理复杂数据结构的能力。 1.2. 主要特点 灵活性与可扩展性 :JSON允许开发者存储不规则和嵌套的数…...

K8S 容器调度
在Kubernetes中,容器调度是一个自动化的过程,负责将容器(在Kubernetes中称为Pod)分配到集群中的合适节点上运行。这一过程由Kubernetes的调度器(kube-scheduler)控制,它通过一系列算法和策略来确…...

C++ //练习 17.2 定义一个tuple,保存一个string、一个vector<string>和一个pair<string, int>。
C Primer(第5版) 练习 17.2 练习 17.2 定义一个tuple,保存一个string、一个vector和一个pair<string, int>。 环境:Linux Ubuntu(云服务器) 工具:vim 代码块 /**********************…...

外观检测设备真的能提高生产效率吗?
零部件外观检测设备是一种专业的设备,用于对各类零部件的外观进行检测和评估。现代制造业中扮演着重要的角色,能够有效提升产品质量,确保产品符合国家标准和客户需求。 首先,零部件外观检测设备具备高精度和高效率的特点。通过采用…...

ant design pro 中用户的表单如何控制多个角色
ant design pro 如何去保存颜色ant design pro v6 如何做好角色管理ant design 的 tree 如何作为角色中的权限选择之一ant design 的 tree 如何作为角色中的权限选择之二ant design pro access.ts 是如何控制多角色的权限的 看上面的图片 当创建或编辑一个用户时,…...

Prometheus监控系统
目录 1.Prometheus概述 1.1 TSDB时序数据库 1.2 Prometheus 的特点 1.3 Prometheus 的生态组件 1.4 Prometheus 的工作模式: 1.5 Prometheus 的工作流程 1.6 Prometheus 的局限性 2.部署Prometheus 2.1 Prometheust Server 端安装和相关配置 2.2 部署 Expo…...

mq-fanout交换机
交换机 交换机是什么?步骤 交换机本身具备路由功能 消息先发到交换机,交换机在路由到队列,消费者监听队列拿到消息 广播模式是什么 是什么 例如:每个微服务创建队列,订单服务只启动1台,1个消费者,订单 怎么创建 创建一个队列 -交换机里type-选择模式(广播模式) 在交换…...

android13禁用打开wifi ap 热点
总纲 android13 rom 开发总纲说明 目录 1.前言 2.情况分析 3.代码分析 4.代码修改 5.彩蛋 1.前言 这个文章介绍的是如何禁止用户打开wifi热点,禁止用户安装app后,打开wifi热点。 2.情况分析 android13 应用层打开wifi AP public void setWifiApEnabled(boolean isEn…...

前端宝典之六:React源码解析之lane模型
本文主要内容: 介绍lane模型 一、 lane模型 lane模型就是react优先级的机制,可以用来 可以表示优先级的不同可能同时存在几个同优先级的更新,所以还得能表示批的概念方便进行优先级相关计算 1、表示优先级不同 lane模型使用31位的二进制…...

邦德咖啡线下门店盛大开业,引领国产健康咖啡新风尚
近日,国内咖啡市场迎来了一股清新的绿色风潮,邦德咖啡线下门店正式拉开帷幕,以其独特的健康理念和创新的产品,誓要成为国产咖啡界的一股强劲力量。 邦德咖啡线下门店以阿卡迪亚绿色为品牌主色调,立志打造国产健康咖啡…...

Elasticsearch + Search UI 构建一个文件搜索引擎
目录 Elasticsearch使用优势App Search Search UI配置engine集中管理配置和提供实用工具函数配置和初始化一个基于Elasticsearch的搜索界面应用程序Search UI 基础用法 好书推荐 Elasticsearch 使用优势 使用ElasticSearch的主要好处在于其强大的全文搜索和实时分析能力。Elas…...

机械学习—零基础学习日志(如何理解概率论2)
全概率公式与贝叶斯公式 上面所提到的公式,可以使用上一篇文章的基本公式推导。 使用到了概率的基本运算公式。 完整的公式展示: 习题练习: 剩余的练习: 第二题解析: 第三题: 第四题: 注意&…...

鸿蒙关于手机全局本地文件读取,写入
一.背景 需求是需要操作用户手机中的文件,不是应用沙箱 二.解决方案 这里要注意的一点拿到fsOpen.path的路径再去进行open文件,因为这里还不知道本地文件路径在哪里,需要选择一下路径再拿到路径去请求 1.这里就是进行两个fs.open…...

嵌入式企业面试真题
1.C语言中指针数组和数组指针的区别是什么? 答:指针数组是指数组的元素都是指针类型的数组。数组指针是指一个指向数组的指针。指向的是数组第一个元素的地址,每次偏移一个数组的大小。 2.讲一下什么是结构体字节对齐? 答:结构体字节对齐是指当结构体中元素的物理内存大…...

开源一款H5自适应留言表白墙php源码下载
开源一款H5自适应留言表白墙php源码下载,优点就是安装简单,功能实用[滑稽][滑稽] 缺点就是UI简陋,功能稀少 第一张是首页,第二张是查看留言 第三张是留言列表(10秒自动刷新),第四张是表白墙界面...

jmeter引入jar包的三种方式
示例 实现对登录密码进行MD5加密 pom文件依赖 <!-- https://mvnrepository.com/artifact/commons-codec/commons-codec --><dependency><groupId>commons-codec</groupId><artifactId>commons-codec</artifactId><version>1.12&l…...

零基础学习Redis(5) -- redis单线程模型介绍
前面我们提到过,redis是单线程的,这期我们详细介绍一下redis的单线程模型 1. redis单线程模型 redis只使用一个线程处理所有的请求,并不是redis服务器进程内部只有一个线程,其实也存在多个线程,只不过多个线程是在处…...

Android Audio
audio概述: Android Audio知识梳理 看完这一篇就够了!-CSDN博客 Android audio篇章(1)------Audio架构_android audio(1)-CSDN博客 android audio google: 音频 | Android Open Source Project (google.cn) 音频…...

远程MySQL数据库:定义、优势及cPanel的数据库工具
在现代网站和应用程序开发中,数据库是必不可少的核心部分。通常情况下,数据库与网站托管在同一台服务器上,但为了提升性能和安全性,越来越多的用户选择使用远程MySQL数据库。那么,什么是远程MySQL数据库呢?…...

【docker】Dockerfile
Dockerfile是用于构建Docker镜像的文本文件,其中包含一组用于定义镜像构建过程的指令。下面是常见的Dockerfile指令及其解释: FROM:指定基础镜像,用于构建新镜像。COPY:将文件或目录从构建上下文复制到镜像中。ADD&am…...

Redis 的 List 结构非常适合用于实现消息队列php
1. Redis List 结构消息队列简介 Redis 的 List 结构非常适合用于实现消息队列。你可以通过 LPUSH 或 RPUSH 命令将消息推入队列,通过 BLPOP 或 BRPOP 命令从队列中弹出消息。BLPOP 和 BRPOP 命令支持阻塞操作,适合在消费者端等待消息的到来。 2. 实现…...

极速闪存启动:SD与SPI模式的智能初始化指南
最近很多客户朋友在询问我们 CS 创世 SD NAND 能不能使用 SPI 接口,两者使用起来有何区别,下面为大家详细解答。 SD MODE: CS 创世 SD NAND 支持 SD 模式和 SPI 模式,SD NAND 默认为 SD 模式,上电后,其初始化过程如下…...