如何搞定前后端一体的状态管理

背景

最近在写 一个消息系统的进化史 ,写的过程也是不断思考的过程,于是,我自然而然地会去思考: 消息的触发机制往往是一个外部状态的变动,然后在消息系统的分发中枢做分发,那么,是否存在内部的状态变动而触发消息的需求呢?

探索

在前端体系下,这是一个非常常见的需求,例如在浏览器中,维护一个 global 状态,其他各处的组件都可以监听这个状态的变动,由此触发一系列的连锁变动。在现代前端框架中,vue 下的 vuex,react 下的 redux 都是在做这件事。 即使是最原始的 OBM,监听元素变动事件也是这样的机制。

然而在服务端,这个场景不是特别典型。在当前流行的后端架构下,应用的分布式部署 和 无状态化 成为主流,状态 都是由中央数据库(中间件)维护的,即使要实现消息的分发,也几乎是位处中央的中间件去实现,例如 mysql、monogo、redis 等等。

而实际上,确实非常多的中央数据库都实现了 watcher 接口,例如 mongodb、redis、rocketMQ、zookeeper、etcd 等等。

价值

虽然主流的场景下直接使用中央数据库的 watcher 就可以了,但仍然也存在单机下,通过内存维护状态,并且有监听需求的场景,因此,决定写一个简单的可监听 map 变动的工具。

在消息系统的场景中,目前能想到的应用是:

单机上的 room 存在一些公共状态,在这个 room 中的很多成员都有修改这些状态的能力,例如 房间名称房间描述是否允许自由加入 等等,如果把这些变成一个个的接口,则会有不少冗余的开发,如果把这些变成每个连接监听的状态,状态发生变动时,自动推送变动通知,则相当于客户端和服务端存在一个共同的状态管理了。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// init a new responsive map
rmap := NewResponsiveMap()

// create a key listener
cityKeyListener := NewMapListener("city", func(me MapEvent) {
fmt.Printf("city changed, key: %s, option type: %s, oldval: %+v, newval: %+v\n", me.Key, me.Option, me.OldVal, me.NewVal)
})

// watch key
rmap.Watch(cityKeyListener)

// set a city, this will trigger the listener above
rmap.Set("city.name", "beijing")
rmap.Set("city.size", "huge")

// try get, this will trigger too
v, err := rmap.Get("city.name")
if err != nil {
panic(err)
}
fmt.Printf("got city name : %s\n", v)

// try del, this will trigger as well
err = rmap.Del("city")
if err != nil {
panic(err)
}

实现

由于是在写 消息系统进化史-零号机 的过程中想到要实现该能力的,因此代码就先放在同一个仓库中了,详情见 bigmess/pkg/responsivemap

在写完这个项目后,发现了另外两个项目,和我这个实现的理念不谋而合:

实际上,状态的同步,如果以 中心化 的理念去设计,就需要在服务端有一个同样的数据模型,并且所有的触发操作以中心节点的变更为准。 如果是以 去中心化 的理念来设计,服务端只要有一个消息转发机制就行了。

去中心化的理念,在一些协同场景中,就会结合到 CRDT 之类的方案; 而中心化的理念,协同场景中就会有 OT 之类的方案。

甚至你会发现,上面这类 vue-socket.io 的能力,如果把基于 vuex 的部分变成基于 yjs 这类,就成了一个分布式的、拥有 CRDT 能力的协同操作场景了。

后续

  • 看情况考虑实现数组的监听
  • 将库进行分层组装,暴露该暴露的
  • 使用 eventhub 找到更好的实现监听机制的写法
  • 写一些 redis、zk、etcd、rocketMQ、mongodb 的监听机制的 demo #important
  • 在消息系统一号机中,使用该库形成前后一体状态管理
  • 考虑分布式状态管理的实现

True happiness arises, in the first place, from the enjoyment of oneself, and in the next, from the friendship and conversation of a few select companions.
Joseph Addison