【可伸缩服务架构】框架与中间件
虽然这是一本老书,但涉及了分布式系统中常见的组件设计思路,其中的架构设计思想,是不会过时的。
更新历史
- 2022.03.17:完成初稿
读后感
这本书其实不是第一次打开了,但是之前对于分布式系统的理解不够深入,导致更多是囫囵吞枣,没有办法很好地领会关键的设计思想。经过几年的历练,再次打开这本老书,读起来轻松了,能去粗取精抓住重点了。比较可惜的是书本里主要用的是 Dubbo 实现,和我的技术栈不太匹配。不过问题不大,重在思想!
读书笔记
永不重复的高性能分布式发号器
- 发号器作为分布式服务化系统不可获取的基础设施之一,在保证系统正确运行和高可用上发挥着不可替代的作用
- UUID 虽然能够保证 ID 的唯一性,但无法满足业务系统需要的很多其他特性,如时间粗略有序性、可反解和可制造性。
- 注:但是对于日志来说,UUID 是足够的
- 分布式系统对发号器的基本需求
- 全局唯一
- 粗略有序(秒级有序、毫秒级有序)
- 可反解,即本身含有一定信息量
- 可制造
- 高性能,单台机器 TPS 能达到 10000
- 高可用
- 可伸缩
- 基于比特设计,可以用 64 个 bit 实现全部的需求,对比 UUID 更省空间
- 运行发号器需要保证时间的正确性,可以定期执行
ntpdate -u pool.utp.orgpool.ntp.org
进行同步
可灵活扩展的消息队列框架
- 消息队列在互联网领域里得到了广泛应用,多应用于异步处理、模块之间的解耦和高并发系统的削峰等场景
- 这部分更多是客户端的具体实现,略过
轻量级的数据库分库分表架构
- 垂直拆分:根据业务的维度,将原本的一个库(表)拆分为多个库(表),每个库(表)与原有的结构不同
- 水平拆分:根据分片(sharding)算法,将一个库(表)拆分为多个库(表),每个库(表)依旧保留原有的架构
- 在 MySQL 的表中达到千万级别,就需要考虑进行分表
- 三种解决方案
- 客户端分片
- 应用层直接实现:通用、简单,但对业务有侵入,不过性能更高,实现简单
- 定制 JDBC 协议实现:不侵入业务,但开发人员需要理解 JDBC 协议
- 定制 ORM 框架
- 代理分片:对业务无侵入,但增加了代理层,有性能损失
- 分布式数据库,如 OceanBase, TiDB 等
- 客户端分片
- 分库分表引起的问题
- 扩容与迁移:一般成倍扩容,方案较复杂
- 查询问题:非主键字段,比较难查询
- 多个分片表查询后合并数据集,效率很低
- 使用搜索引擎,数据冗余,查询和存储分离
- 跨库事务难以实现
- 同组数据跨库问题
缓存的本质和使用
- 我们在使用缓存提高读操作性能的同时,一定会失去部分的一致性
- 适合使用缓存的场景
- 读密集
- 存在热数据
- 对响应时效要求高
- 对一致性要求不严格
- 需要实现分布式锁
- 不适合使用缓存的场景
- 读少
- 更新频繁
- 对一致性要求严格
- 应用层访问缓存的模式
- 双读双写:读操作先读缓存,写操作先写数据库
- 异步更新:全量数据保存在缓存中,并且不设置过期时间,由异步的更新服务将数据库里的变更同步到缓存中;机制复杂,但性能较好
- 串联模式:在微服务中不推荐,要保证高可用成本较高
- 分布式缓存分片的三种模式
- 客户端分片:性能较好,但是对业务有侵入
- 代理分片:增加了代理层,增加损耗和维护成本
- 集群分片:如 Redis 3.0 的 Cluster
- 分布式缓存的迁移方案
- 平滑迁移:双写方案 - 双写、迁移历史数据、切读、下双写
- 停机迁移
- 一致性哈希
- 缓存设计的核心要素
- 容量规划:缓存内容的大小、缓存内容的数量、淘汰策略、缓存的数据结构、每秒的读峰值、每秒的写峰值
- 性能优化:线程模型、预热方法、缓存分片、冷热数据的比例
- 高可用:复制模型、失效转移、持久策略、缓存重建
- 缓存监控:缓存服务监控、缓存容量监控、缓存请求监控、缓存响应时间监控
- 注意事项:缓存穿透、大对象、是否使用缓存实现分布式锁、是否使用缓存支持的脚本、是否避免了竞争条件、缓存雪崩的预防
- 缓存设计的优秀实践
- 对应用需要缓存的数据大小进行评估,包括缓存的数据结构、缓存大小、缓存数量、缓存的失效实践,然后根据业务情况自行推算在未来一定时间内的容量的使用情况
- 将使用缓存的业务进行分离,核心业务和非核心业务使用不同的缓存实例,从物理上进行隔离
- 根据缓存实例提供的内存大小推算应用需要使用的缓存实例数量
- 缓存的超时时间的设置是很重要的
- 所有的缓存实例都需要添加监控,我们需要对慢查询、大对象、内存使用情况做可靠的监控
- 不推荐多个业务共享一个缓存实例,不得不共享时,需要通过规范来限制各个应用使用的 key 有唯一前缀,避免相互覆盖
- 任何缓存的 key 都必须设定缓存失效实践,且失效时间不能集中在某一点
- 低频访问的数据不要放在缓存中
- 缓存数据不易过大
- 对于存储较多 value 的 key,尽量不要使用 HGATALL 等集合操作
- 对性能要求不是非常高,尽量使用分布式缓存,而不要使用本地缓存
- 写缓存时一定要写入完全正确的数据
- 使用缓存是,一定要有降级处理,尤其对关键的业务环节,缓存有问题或者失效时也要能回源到数据库进行处理
RPC 服务
- RPC 协议是一种通过网络向远程计算机程序请求服务,而不需要了解底层网络技术的协议。优势:简单、高效、通用
- RPC 协议以传输层协议(如 TCP, UDP, HTTP)为基础,为两个不同的应用程序间传递数据
- RPC 采用客户端/服务端模式
- 网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端被称为 Socket。Socket 用于描述 IP 地址和端口,是一个通信连接的句柄,可以用来实现不同的计算机之间的通信,是网络编程接口的具体实现
- 实现透明的远程过程调用的重点是创建客户存根(client stub)