流程图
流程说明
io分离
处理流程分为逻辑处理和数据读取,逻辑层的进程是无状态的,可以水平扩容的。所有关于产品的逻辑都是在这里实现。
io层负责数据的读写,包括数据持久化,一致性事务,数据缓存,缓存又分为redis缓存和进程缓存,基本上所有的系统的性能瓶颈都在这一块。
io层负责提供读写接口给逻辑层,要屏蔽具体的存储细节,比如不应该提供mysql或者redis等相关实现细节的接口
读写分离
io层分为读和写,为什么要分离读写呢?因为读写的场景太不一样了,实现的技术也不一样,qps和sla也不一样。
-
basic_write:写数据
可以分为同步写和异步写。
如果是同步写,需要有限流和超时措施,同步写一般会提供事务属性,所以在设计接口的时候,需要把相关数据都放在一个basic_w里面
如果是异步写,一般是直接把数据写到kafka里面就完事。这里可以再抽象出一个服务basic_sync,异步把数据写到持久化存储里面 -
basic_read: 读数据
可以分为一致性读和非一致性读。
一致性读一般会直接读db,避免写入数据后读不出来
非一致性读一般是读从库或者缓存。如果是缓存,是redis缓存还是进程级缓存?如果是redis缓存,需要考虑缓存击穿以及热点缓存的问题。
通过限制读进程的qps以及限制读进程到db的链接数量(比如一个进程只允许创建一个db connection),可以有效的保护db,避免流量洪峰或者缓存击透搞挂db
对于读请求并且有热点的key,一来是可以把热点key加后缀来避免热点,二是可以通过进程缓存+etcd等同步工具实现一致性的缓存读
逻辑分离
logic_gateway & logic_middle处理都处理业务逻辑,是产品需求的体现。不同的是,gateway处理的逻辑是跟接入端有关系的。比如请求user信息,如果是web的请求,返回一个大图的url,如果是app h5的请求,就需要返回一个缩略图的url。logic_middle则不关心这些,它会返回关于这个user的所有信息,包括大图和小图。
通常来说划分logic_gateway和logic_middle是比较困难的,但是实现的时候一定要记得,相关的接口可能会被不同设备请求,所以设备相关的逻辑不应该和其他逻辑混淆。产品开始阶段不需要logic_middle,等到有需求的时候再提出来。
logic_midlle提取出来就是业务中台
调用限制
logic_gateway只可以调用logic_middle和basic读写服务
logic_middle只可以调用basic服务
basic服务只允许调用db/cache服务
一句话:避免同层调用,实现微服务的层次化和扁平化
为什么要扁平化
扁平化可能导致两个logic服务调用相同的几十个basic服务,但是并不提倡logic直接调用另一个logic,因为同层调用会使得调用链拉长,甚至不可控制。调用多个快速响应的basic服务比调用一个大而全的logic服务要可靠的多。服务与服务之间应该正交化,即同一份数据只能通过一个唯一的链路获取到。这样便于分析和定位重复类的问题。大而全的接口会使得复用变得不可能,并且增加网络开销。微服务的设计应该和代码模块设计一样,小而美。