问题:
(1)拆分 100 个 key 会出现读扩散的问题,需要申请较多 Redis 资源,存储成本比较高。而且可能存在读取超时问题,不能保证一次读取所有 key 都读取成功,故返回的结果可能会较上一次有减少。
(2)容灾方案方面,如果申请备份 Redis,也需要较多的存储资源,需要的额外存储成本。
4.5.2 方案二设计思路:
在方案一实现的基础上进行优化,并且要考虑数字不断累加、节约成本与实现容灾方案。在写场景,通过本地缓存进行合并写请求进行原子性累加,读场景返回本地缓存的值,减少额外的存储资源占用。使用 Redis 实现中心化存储,最终大家读到的值都是一样的。
具体设计方案:
每个 docker 实例启动时都会执行定时任务,分为读 Redis 任务和写 Redis 任务。
读取流程:
- 本地的定时任务每秒执行一次,读取 Redis 单 key 的值,如果获取到的值大于本地缓存那么更新本地缓存的值。
- 对外暴露的 sdk 直接返回本地缓存的值即可。
- 有个问题需要注意下,每次实例启动第一秒内是没有数据的,所以会阻塞读,等有数据再返回。
写入流程:
- 因为读取都是读取本地缓存(本地缓存不过期),所以处理好并发情况下的写即可。
- 本地缓存写变量使用 go 的 atomic.AddInt64 支持原子性累加本地写缓存的值。
- 每次执行更新 Redis 的定时任务,先将本地写缓存复制到 amount 变量,然后再将本地写缓存原子性减去 amount 的值,最后将 amount 的值 incr 到 Redis 单 key 上,实现 Redis 的单 key 的值一直累加。
- 容灾方案是使用备份 Redis 集群,写入时进行双写,一旦主机群挂掉,设计了一个配置开关支持读取备份 Redis。两个 Redis 集群的数据一致性,通过定时任务兜底实现。
本方案调用 Redis 的流量是跟实例数成正比,经调研读取侧的服务为主会场实例数 2 万个,写入侧服务为资产中台实例数 8 千个,所以实际 Redis 要支持的 QPS 为 2.8 万/定时任务执行间隔(单位为 s),经压测验证 Redis 单实例可以支持单 key2 万 get,8k incr 的操作,所以设置定时任务的执行时间间隔是 1s,如果实例数更多可以考虑延长执行时间间隔。
具体写入流程图如下:

优点 | 缺点 | |
方案一 | 1. 实现成本简单 | 1. 浪费存储资源; |
方案二 | 1. 节约资源; | 1. 实现稍复杂,需要考虑好并发原子性累加问题 |
结论:
从实现效果,资源成本和容灾等方面考虑,最终选择了方案二上线。
4.6 难点六:进行母活动与子活动的平滑切换需求背景:
为了保证本次春节活动的最终上线效果和交付质量,实际上分了三个阶段进行的。
(1)第一阶段是内部人员测试阶段。
(2)第二个阶段是外部演练阶段,圈定部分外部用户进行春节活动功能的验证(灰度放量),也是发现暴露问题以及验证对应解决机制最有效的手段,影响面可控。
(3)第三个阶段是正式春节活动。
而产品的需求是这三个阶段是分别独立的阶段,包含用户获得奖励、展示与使用奖励都是隔离的。

技术挑战:
有多个上游调用钱包发奖励,同时钱包有多个奖励业务下游,所以大家一起改本身沟通成本较高,配置出错的概率就比较大,而且不能同步改,会有较大的技术安全隐患。
设计思路:
作为奖励入账的唯一入口,钱包资产中台收敛了整个活动配置切换的实现。设计出母活动和子活动的分层配置,上游请求参数统一传母活动 ID 代表春节活动,钱包资产中台根据请求时间决定采用哪个子活动配置进行发奖,以此来实现不同时间段不同活动的产品需求。降低了沟通成本,减少了配置出错的概率,并且可以同步切换,较大地提升了研发与测试人效。
示意图:

钱包方向在本次春节活动期间做了三件事情来保障大流量大预算的现金红包发放的资金安全:
- 现金红包发放整体预算控制的拦截
- 单笔现金红包发放金额上限的拦截
- 大流量发红包场景的资金对账
- 小时级别对账:支持红包雨/集卡/烟火红包发放 h 1 小时级对账,并针对部分场景设置兜底 h 2 核对。
- 准实时对账:红包雨已入账的红包数据反查钱包资产中台和活动侧做准实时对账
多维度核对示意图:

