背景
需求主要是做系统资源的使用扣减。用户在使用过程中均会触发资源扣减,系统需要维护资源总量、资源余量,并且在资源余量为0时限制用户使用,系统所有用户共享同一资源总量(To B 业务,平台会给企业授予一定的资源总量,企业自己用户使用平台时触发扣减)。
老项目在做性能压测及优化过程中(只改了五行代码接口吞吐量提升了10多倍 ),发现用户量大的时候,资源使用这里存在严重的性能瓶颈,主要是因为存在大量请求同时修改数据库表一条记录, 大概SQL如下:
update resource set count = count - 1 where type = 1 and count > 1;
以前由于服务客户的用户量较小,该问题并未暴露,现在发现了就不能忽略了。那么如何修改呢?下边就说一下方案的设计。
缓存
首先想到的就是缓存,提前将数据加载到Redis缓存中,利用缓存持久化机制来实现(容忍一秒内的数据丢失),同时也确保数据库和缓存的数据一致性。
这个实现起来是简单,Redis本身就能支持(RDB+AOF数据持久化),而且还提供了原子操作命令(incrby、decryby)。当时毕竟资源与费用挂钩,这么玩太不严谨,拿这个方案肯定要被喷的。
缓存 + DB
目前的情况,使用DB,持久性保证了,性能完全不能满足要求。使用缓存,性能可以了,数据持久性又无法保证。如果能够取长补短,发挥出各自的优势那不就完美了么?Redis用于提升性能,DB用于持久化保证。 那么如何做呢?
先分析一下,我们目前的根本问题:
- 资源扣减,需要确保不出现负值情况,所以要求极高的实时性,需要有准确的实时余量数据。这就要求资源扣减要从统一的地方来做。
- 从统一的地方做扣减导致,所有用户的资源操作都是操作一条数据记录。
从最根本的地方下手解决:
所有用户的资源操作都是操作一条数据记录
这里的串行化操作是必须要解决的,解决的方式就是让用户操作各自的数据,这样就能够完美的支持并发操作。
资源扣减要从统一的地方来做
这里其实就是前边提到的缓存方案。
上边其实是想说明一下思考过程的,奈何基本功有限... 还是直接上最终方案吧
- 充分利用Redis操作性能优点,所有的资源操作在Redis中扣减。
- 充分利用DB持久性保证优点,操作Redis的同时需要在资源记录表写操作记录(每次操作都是一条记录,避免操作同一条数据)。
- 是否操作成功以DB是否存储成功为准。(Redis 成功,DB失败,Redis要做回滚)
- 提供资源更新任务,定时将扣减记录汇总,更新资源信息表数据。(Redis有准确的实时数据,DB中原资源记录就不需要实时性保证了)
- 为了避免Redis与DB数据的不一致,增加资源对账功能,用DB更新Redis。
有没有感觉方案有点熟悉,哈哈,其实就是参考了Mysql的数据持久化方案。
Mysql所有的更新都是以操作缓存(Buffer Pool)和文件(Redo Log)为准的。