纯异步事件驱动构建的微服务体系
奇葩的需求
通常我们都只会记录最后一次变更的状态,因为最终状态是真实时间多数的需求。例如修改了用户名,只会记录最后修改的用户名,通常还会加上修改时间和创建时间,就是我们多数常见系统的设计模式。 如果需求增加了:“用户名不能和曾经使用过的用户名一致”,问题就来了。
如果不用常见的设计方案,会怎样
修改用户名,这个操作就是一次事件。事件是关于“发生了什么”的记录,类似日志。但是与日志不同的是,事件还是事实的单一来源。因此,它们必须包含准确描述所发生事件所需的所有信息。
事件驱动的解决方案
存储
那如果我们不进行update,直接存储了原始完整的事件,也就是只记录了日志,会怎样? 数据库里会有多条记录,每次查询都必须使用max(版本)取得最后记录后才能拿到最准确的信息。性能上不够好,且产生了大量的冗余信息。 那如果不考虑存储成本,也不考虑事务,且有一种物化视图,能够全自动的从所有事件中自动取得最新版本,是不是就是最理想的状态? 类似区块链的运行原理,没有删除和修改,只有新增,所有发生的事件都被完整存储,测试、解耦、伸缩变得so easy
调用
如果我们向mq发出一个事件,但等待对此事件的一个回信(可能从另一个topic中),不考虑性能的情况下,本质上来说就是一个普通调用了。 不需要服务发现也不需要负载均衡,每个调用都被完整记录,且请求或返回数据的共享变得so easy。
同步调用的缺陷
解耦。无论是常见的服务发现还是dubbo式能力发现,必须在知道被调用方的前提下才能执行调用。至少需要知道被调用方的名称和请求对象,才能准确执行调用。 这样不够“声明式”,不够解耦。
弹性。今天我们在k8s这样的平台上,似乎很少遇到弹性问题。深入细节,长连接中的高qps依然无法分流,特别是在特殊协议中这种情况可能雪上加霜。
线上故障。没打访问日志?哭死。打了访问日志?一个月激增10倍的日志成本
接口版本。多个 API 定义和服务版本通常需要同时存在。强制让客户端升级到最新的 API 并不总是可行或可取的。
测试。单元测试改动了数据怎么办?基建层要不要做单元测试?
基于纯异步事件驱动构建的微服务体系
事件应该是什么样
准确的描述是:包含唯一id的kv结构数据
最佳实践的格式:使用protobuf或apache avro,保持足够的体积优势、反解析优势,利用现有的基建(例如proto工程化能力)
事件的设计
错误的设计:用户名正在更新
正确的设计:用户名更新为xxx 事件不能是处于中间状态,而且已经完成,成为事实的事情。
怎样定义“类型” 错误的设计:
id: 1 name: xxx admin_name: "" type: user
正确的设计:
id: 1 name: xxx --- id: 2 admin_name: xxx
不要使不同事件“合用”定义,如果是两个类型,就定义2个事件。事件不需要和数据库设计对等 3. 最小化事件 拿修改用户名的事件举例,事件中当然需要包含用户id和用户名,但需要不需要包含用户头像等其他有关联但并不是“有用的信息”?站在最佳实践的角度上,不要包含此类信息。不仅会造成数据量变大,而且实际上也要校验每次的“更新”是否对原始数据有修改,造成了额外的负担
技术和中间件
mq。常见的mq都可以完成。但是kafka这种有各种限制(消费者数目不能大于分区数等),可能会导致整体服务性能或弹性能力不足。
落盘。根据数据类型的不同,有的数据需要实时落盘+事务做聚会,这种情况需要开发。如果不需要实时,比如”用户名不能和曾经使用过的用户名一致“的场景,完全可以使用某些”奇葩“的方案,包括但不限于:
使用kafka connect把数据按照parquet格式写入对象存储,使用presto加载parquest进行查询。
使用带有kafka抽取的数据库(例如singlestore),直接抽取kafka数据,自动落盘
老系统怎么办
触发器。使用触发器拿到最新数据,并发布在流中
数据库日志。订阅binlog并推送在流中,可能需要做相应的转化,因为一般数据库的数据都有一定冗余,比如上面说的头像信息
orm拦截器。代码层面的变更通知
定时扫描update_at。使用定时任务获取变更事件并推送
有了事件流,然后呢
困扰多年的问题突然没有了
mysql如何同步redis
mysql如何同步es
需求变化快?我变的更快。甚至无需修改原有代码,订阅事件结果,并直接使用事件覆盖上一个事件,可以快速满足需求,代码屎山也可以轻松重构。
海量数据+事务。打破传统tp和ap天然的边界(可能是两个老死不相往来的部门),研发的思路可以不用总是局限在加索引这件事上
结尾
对于“传统”研发团队而言,以纯事件驱动架构改造微服务是个过大的调整,会打破现有的多数认知。我个人猜想敢于尝试这只螃蟹的肯定不多。
但是有了这种打破常规的思路,在某些问题上可能就不那么会受到限制。对于某些“恶臭”的传统设计,是否能够在某些程度的突破一下边界。在某些特定的、非核心的系统中,可以尝试下使用新模式解决问题。

更多精彩内容
研发团队新鲜事儿,来公众号「迷路idea」找我一起探讨