欢迎光临散文网 会员登陆 & 注册

纯异步事件驱动构建的微服务体系

2023-08-25 20:13 作者:迷路idea  | 我要投稿

奇葩的需求

通常我们都只会记录最后一次变更的状态,因为最终状态是真实时间多数的需求。例如修改了用户名,只会记录最后修改的用户名,通常还会加上修改时间和创建时间,就是我们多数常见系统的设计模式。 如果需求增加了:“用户名不能和曾经使用过的用户名一致”,问题就来了。

如果不用常见的设计方案,会怎样

修改用户名,这个操作就是一次事件。事件是关于“发生了什么”的记录,类似日志。但是与日志不同的是,事件还是事实的单一来源。因此,它们必须包含准确描述所发生事件所需的所有信息。


事件驱动的解决方案

存储

那如果我们不进行update,直接存储了原始完整的事件,也就是只记录了日志,会怎样? 数据库里会有多条记录,每次查询都必须使用max(版本)取得最后记录后才能拿到最准确的信息。性能上不够好,且产生了大量的冗余信息。 那如果不考虑存储成本,也不考虑事务,且有一种物化视图,能够全自动的从所有事件中自动取得最新版本,是不是就是最理想的状态? 类似区块链的运行原理,没有删除和修改,只有新增,所有发生的事件都被完整存储,测试、解耦、伸缩变得so easy

调用

如果我们向mq发出一个事件,但等待对此事件的一个回信(可能从另一个topic中),不考虑性能的情况下,本质上来说就是一个普通调用了。 不需要服务发现也不需要负载均衡,每个调用都被完整记录,且请求或返回数据的共享变得so easy。


同步调用的缺陷

  1. 解耦。无论是常见的服务发现还是dubbo式能力发现,必须在知道被调用方的前提下才能执行调用。至少需要知道被调用方的名称和请求对象,才能准确执行调用。 这样不够“声明式”,不够解耦。

  2. 弹性。今天我们在k8s这样的平台上,似乎很少遇到弹性问题。深入细节,长连接中的高qps依然无法分流,特别是在特殊协议中这种情况可能雪上加霜。

  3. 线上故障。没打访问日志?哭死。打了访问日志?一个月激增10倍的日志成本

  4. 接口版本。多个 API 定义和服务版本通常需要同时存在。强制让客户端升级到最新的 API 并不总是可行或可取的。

  5. 测试。单元测试改动了数据怎么办?基建层要不要做单元测试?


基于纯异步事件驱动构建的微服务体系

事件应该是什么样

准确的描述是:包含唯一id的kv结构数据

最佳实践的格式:使用protobuf或apache avro,保持足够的体积优势、反解析优势,利用现有的基建(例如proto工程化能力)

事件的设计

  1. 错误的设计:用户名正在更新

  2. 正确的设计:用户名更新为xxx 事件不能是处于中间状态,而且已经完成,成为事实的事情。

  3. 怎样定义“类型” 错误的设计:


id: 1 name: xxx admin_name: "" type: user


正确的设计:


id: 1 name: xxx --- id: 2 admin_name: xxx


不要使不同事件“合用”定义,如果是两个类型,就定义2个事件。事件不需要和数据库设计对等 3. 最小化事件 拿修改用户名的事件举例,事件中当然需要包含用户id和用户名,但需要不需要包含用户头像等其他有关联但并不是“有用的信息”?站在最佳实践的角度上,不要包含此类信息。不仅会造成数据量变大,而且实际上也要校验每次的“更新”是否对原始数据有修改,造成了额外的负担

技术和中间件

  1. mq。常见的mq都可以完成。但是kafka这种有各种限制(消费者数目不能大于分区数等),可能会导致整体服务性能或弹性能力不足。

  2. 落盘。根据数据类型的不同,有的数据需要实时落盘+事务做聚会,这种情况需要开发。如果不需要实时,比如”用户名不能和曾经使用过的用户名一致“的场景,完全可以使用某些”奇葩“的方案,包括但不限于:

  3. 使用kafka connect把数据按照parquet格式写入对象存储,使用presto加载parquest进行查询。

  4. 使用带有kafka抽取的数据库(例如singlestore),直接抽取kafka数据,自动落盘

老系统怎么办

  1. 触发器。使用触发器拿到最新数据,并发布在流中

  2. 数据库日志。订阅binlog并推送在流中,可能需要做相应的转化,因为一般数据库的数据都有一定冗余,比如上面说的头像信息

  3. orm拦截器。代码层面的变更通知

  4. 定时扫描update_at。使用定时任务获取变更事件并推送

有了事件流,然后呢

  1. 困扰多年的问题突然没有了

  2. mysql如何同步redis

  3. mysql如何同步es

  4. 需求变化快?我变的更快。甚至无需修改原有代码,订阅事件结果,并直接使用事件覆盖上一个事件,可以快速满足需求,代码屎山也可以轻松重构。

  5. 海量数据+事务。打破传统tp和ap天然的边界(可能是两个老死不相往来的部门),研发的思路可以不用总是局限在加索引这件事上

结尾

对于“传统”研发团队而言,以纯事件驱动架构改造微服务是个过大的调整,会打破现有的多数认知。我个人猜想敢于尝试这只螃蟹的肯定不多。

但是有了这种打破常规的思路,在某些问题上可能就不那么会受到限制。对于某些“恶臭”的传统设计,是否能够在某些程度的突破一下边界。在某些特定的、非核心的系统中,可以尝试下使用新模式解决问题。



更多精彩内容

我们为什么又又又又需要一个新的mq了

小团队的实用云原生指南

jdk17的gc选择

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


纯异步事件驱动构建的微服务体系的评论 (共 条)

分享到微博请遵守国家法律