本文一共分析了三个案例, 分别介绍并发系统中的共享资源并发访问, 计算型密集型任务缓存访问 , 单一热点资源峰值流量问题和解决方案.
Q1: 订票系统, 某车次只有一张火车票, 假定有 1w 个人同时打开 12306 网站来订票, 如何解决并发问题?
A1: 首先介绍数据库层面的并发访问, 解决的办法主要是乐观锁和悲观锁.
乐观锁
假设不会发生并发冲突, 只在提交操作时检查是否违反数据完整性.
乐观锁使用一个自增的字段表示数据的版本号 (或者 timestamp), 更新的时候检查版本号是否一致, 比如数据库中版本号为 4, 更新时版本号使用版本号 version=5, 与数据库中的版本号 version+1=(5) 做比较, 如果相等, 则可以更新, 如果不相等, 其他程序已更新该记录, 返回错误.
悲观锁
假定会发生并发冲突, 屏蔽一切可能违反数据完整行的操作.
一般需要使用数据库的锁机制, 比如 MysqlInnoDB 引擎的行级锁.
结论: 在实际生产环境中, 如果并发量不大且不允许脏读(原始数据为 5,AB 两个事务, B 其他事务更新数据为 2, 事务未提交时, A 读取到的仍然为 5), 可以使用悲观锁. 并发访问量大时, 使用悲观锁有非常大的性能问题, 可以选择乐观锁.
其次, 介绍一下 Memcached 的 CAS 机制
CAS, 又称 Compare-and-Swap, 代表一种原子操作.
Memcached 的 CAS 机制解决的问题及其原理:
1. 实现了 Check-and-Set 原子操作功能;
2. 其使用方式为: 首先使用 gets 指令一个 key-value 及 key 对应 value 的版本号; 其次操作产生新的 value 值; 最后使用 cas 指令重新提交 key-value, 并附带刚刚获得到的版本号;
3. 当服务端判断 cas 操作中的版本号不是最新的时, 则认为改 key 的值已经被修改, 本次 cas 操作失败. 程序设计人员通过 CAS 机制可实现自增和自减的原子操作;
可以看到 MemCache 的 CAS 机制和数据库的乐观锁实现原理非常类似.
Q2: 假设系统中图片存储在 TFS(Taobao File System)中, 接口提供缩略图服务, 首先在缓存中查找是否有缩略图, 如果没有, 则从 TFS 加载原图片, 然后请求缩略图服务, 缩略图计算完成后, 设置回缓存服务中.
遇到的问题: 当一张图片分享给 100w 个人以后, 同一时间有 1w 个并发请求, 由于缩略图计算耗时较长(假设 1s), 在这 1s 内, 每个请求查询缓存都没有找到然后申请计算缩略图, 导致重复的缩略图计算量和资源消耗.
A2: 对于缩略图这种耗时的服务, 非常适合使用缓存, 不过在使用的时候, 对于同一个图片, 原则上只需要计算一次缩略图, 在缩略图未计算完成时, 可以对每张图片做额外的标记表示其正在 Processing, 并发请求遇到缩略图 Processing 时, 可以等待缩略图计算完成 (这是建议的方式) 后从缓存直接读取, 也可以是直接返回错误, 通过客户端重试来解决.
本案例中, 如果缩略图请求在上传图片 1 分钟后才发生, 则可以在后台预先计算缩略图并存储到缓存. 另外就是在上传图片的时候计算缩略图, 不过会增加上传图片的时间.
Q3: 单点峰值流量, 在并发系统中, 除了请求整体的并发量高, 还常见单一热点资源的并发请求量很高. 例如, 1 万个人每人分享了一张图片, 其中 9999 张图片的缩略图请求在 10 QPS 以内, 剩下的一张图片为新闻热点图片, 峰值请求在 10 万 QPS 左右, 系统会遇到的容量问题包括: 1)接口前端机容量不够; 2)缓存资源单实例遇到瓶颈.
A3: 针对单点峰值流量可能遇到的性能瓶颈, 解决方案如下.
1)接口层容量不够: 这个问题比较简单, 只要接口层设计是无状态的, 当容量达到预警线, 可以通过快速水平扩容解决.
2)缓存资源单实例遇到性能瓶颈: 如果使用的是分布式缓存, 当希望突破单一 key 的访问瓶颈时 (这个瓶颈既有可能是 CPU 资源紧张, 也有可能是单机网络带宽跑满, 还有可能是磁盘 IO 吞吐不够), 一个办法是分布式缓存做多副本(x3) 冗余设计, 这样系统的吞吐量 (x3) 可以提高 3 倍, 不过成本也提高 3 倍. 另外一个办法是针对极热点数据, 除了分布式缓存, 同时在前端机上打开 localCache, 依靠数量众多的前端机来抗极热点请求.
来源: http://stor.51cto.com/art/201810/585489.htm