广交会线上举办,在第三方服务不能保证稳定性的情况下,为保证官网稳定性,新增数据聚合服务,用于缓存数据,并保护第三方服务,且在第三方服务失败的情况下,能够返回缓存的数据,保证前台能够拿到返回数据。
数据聚合服务使用Redis集群来做数据缓存服务,但是用户可以通过恶意构造数据的方式,让请求越过Redis层,每次都打到第三方请求(缓存穿透);同时缓存的数据有生存期,在数据失效的那一刻,可能有大量请求打到第三方服务(缓存击穿)。
品牌橱窗、CF奖展品 这两个接口,其中品牌橱窗是个性化数据,当用户未登录时(游客),看到的是相同的数据,用户登录之后,看到的是个性化的数据(每个人看到的不同);CF奖展品无论是否登录,每个人看到的都是相同的。
接口名:DescribeBrandGoodList
参数名称 | 必选 | 类型 | 描述 |
---|---|---|---|
eventId | 是 | int64 | 请求id |
interfaceName | 是 | string | 接口名 |
para | struct | accessToken, page, size, lang | |
accessToken | 否 | string | 用户信息 |
uid | 否 | string | 用户id |
pageIndex | 是 | int64 | 页号(第0页开始) |
pageSize | 是 | int64 | 页容量 |
lang | 否 | string | 语言(可选zh和en,默认为zh) |
接口名:DescribeCfGoodList
参数名称 | 必选 | 类型 | 描述 |
---|---|---|---|
eventId | 是 | int64 | 请求id |
interfaceName | 是 | string | 接口名 |
para | struct | access_token, page, size, lang | |
pageIndex | 是 | int64 | 页号(第0页开始) |
pageSize | 是 | int64 | 页容量 |
lang | 否 | string | 语言(可选zh和en,默认为zh) |
数据请求过程是,前端发起请求到数据聚合服务,处理的流程图如下:
Redis key的设计为:
未登录的场景:
用户登录场景:
用户登录后会有一个uid,这个uid可以认为是无法伪造的,所以可以等同于未登录的场景来考虑。但是带uid的redis key需要设置真实的过期时间(可以较长,如6小时),避免百万用户请求数据始终缓存。
针对pageSize和language的问题,可以和前台约定好,后端固定pageSize,不依赖前端传入;language固定两种入参校验。pageNum这个字段不太好控制,理论上我们不应该限制用户选择查看的页数,但是针对当前场景,用户无法直接选择查看的页号,每次"换一批"只能向前翻动一页,在合理的情况的下,我们限制最大的查看页号为500,如果超过500,认为为非法请求,返回第1页内容。
如果第三方服务请求失败,要在redis中set一个空值,如"null",并设置一个较短的生存期(2秒),防止短期内大量请求打到服务端,设置较短的生存期避免请求长时间拿不到数据。这里还要考虑缓存击穿的问题。
每个redisKey的逻辑过期时间为5min,针对redisKey失效,大量请求同时并发打到后台服务的问题,这里使用redis实现一个分布式锁来解决。
采用setnx命令来实现redis分布式锁,若setnx成功,表示获取到了锁,则请求第三方服务,并将数据更新到redis,释放锁;没有获取到锁的进程,可以直接返回默认数据。这样可以保证当redisKey失效的那一刻,只有一个进程请求第三方服务并更新redis。
redis中的数据是一个定时任务(3min执行一次,缓存前20页)异步请求第三方服务,更新到redis的,未登录的情况下,理论上请求不会到第三方服务,都会命中redis且没有逻辑过期。我们对redis热key拼接上0~19的数字,将一个热key转化为20个key,每次请求的时候,生成一个0~19的随机数,这样就能将热key打散,避免redis热key的问题。定时更新redis的时候,更新20个key,请求时redisKey重新设计为apiName_page_size_language_randomNum
虽然redis的性能已经比较优秀了,但是为了保证在大规模并发请求时(压测)服务稳定性,我们考虑在redis缓存之前,加上一层内存缓存,将前20页的数据缓存在本地内存,请求若命中内存则直接返回,这样使得请求响应更快更稳定。
使用ristretto来做内存缓存,可以控制缓存的时间、数量、大小、淘汰策略等。
经过内存缓存的优化之后,压测接口的响应时间从ms级提升到了µs级,且压测锯齿明显减少。
下表是一轮压测的情况:
处理耗时 | 数量 | 占比 |
---|---|---|
< 1ms | 7236615 | 99.972% |
< 5ms | 1141 | |
< 10ms | 301 | |
< 15ms | 106 | |
< 30ms | 151 | |
< 50ms | 183 | |
< 100ms | 128 | |
> 100ms | 0 | |
总计 | 7238625 |
这里有一个现象是,虽然请求都是命中内存就直接返回了,但是在并发请求量太大的情况下,仍然会存在100ms左右的响应时间。
本文介绍了广交会项目后台用到的两种缓存和相关的优化方法。使用两级缓存还有一个问题就是缓存数据的实时性的问题,这里缓存的过期时间和更新时间需要设置好,不然会出现一致性的问题。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。