背景
6月14日晚,2018年俄罗斯世界杯在莫斯科开幕。国内数以千万的观众通过优酷、央视影音或者是咪咕视频观看了此次开幕赛。
阿里云公布的一份数据显示,第一波流量洪峰出现在揭幕战开场后的第44分钟,峰值达到了1.5个2018年春晚的规模。自此,本届世界杯也成为了史上最大规模的一次在线直播。比赛期间,预计全网70%的世界杯直播流量都跑在了阿里云上。(注:上述内容引用自:https://www.leiphone.com/news/201806/Du10JxOxuJ6Ou782.html,所属权归原作者所有。)
细心的网友们肯定已经注意到了,今年的世界杯与以往的世界杯相比,不仅比赛结果出人意料,而且观看比赛的APP客户端中也增加了丰富的互动和红包惊喜,众平台为了引流和激活“僵尸”用户也是使出了浑身解数。下面一起来盘点一下精彩世界杯背后你看得见的Redis身影吧!主要从业务架构、应用场景、高可用建设以及弹性扩缩容等几个方面进行展开。
业务架构
(图来源自网络)
上图是某企业直播解决方案,主要使用了弹性计算、CDN、智能动态编码技术、视频AI、窄带高清2.0、Redis等等产品技术,充分保证了超清画质体验的同时,节省了带宽的消耗,并最大限度优化资源和成本,给观众带来流畅和丰富多彩的互动体验,极大的满足了用户的参与感。当然,本文重点不是谈论视频如何从录制到播出的,而是要探讨一下“数据库服务器”中Redis在丰富观看直播体验中发挥的重要作用和使用场景及实现。
应用场景
Redis之所以能够被广泛的应用于企业的架构中,而且是不可或缺的重要组成部分,也可以说是标配,其中很重要的一点就是得益于它具有丰富的数据结构,这也是它逐渐替代Memcached,备受青睐的重要原因。它的数据结构有:String、List、Hash、set、Sorted set、bitmap、bit field、hyperLogLog、Geospatial Index等。
正是因为有了这些数据结构和Redis技术的不断完善和发展,才被广泛应用于各行各业中,应用场景也是百花齐放。比如:会话缓存(Session cache)、全页缓存(FPC)、手机验证码、访问频率限制/黑白名单、消息队列、发布与订阅、消息通知、排名/排行榜/最新列表、计数器(比如微博的转评赞计数、阅读数(浏览数,视频播放计数)、博文数(发帖数)、粉丝数、关注数(喜欢商品数)、未读数(动态数))、共同好友/喜好/标签、推送、下拉刷新、私信、商品库存管理(限时的优惠活动信息)、证券指标实时计算,发号器/UUID、以及随着LBS(基于位置服务)的发展,加入的GEO(地理信息定位)的功能和基于Lua自定义命令或功能等等。大家在使用过程中,需要结合自己的业务场景,选择正确的数据类型。那么下面以“优酷APP”为例,来看看有哪些看得见的场景用到了Redis技术。
聊天列表(评论列表)
这种评论列表可以用Hash来实现,比如:{userID:contents}
用户ID | 评论内容 |
---|---|
11101000 | 破产了 |
关于评论,你有没有想过一个问题,“垃圾评论” 怎么在评论列表里看不到?没有用户一直在刷评论的?是的,这个肯定是spam系统在默默工作的,这个Redis也是可以做到的,更多策略/机制需要结合drools配合完成。当然通常都是有专门的spam的系统来实现。举个例子,比如要限制某个用户一分钟内的评论次数:
Method1: 用sorted set实现。将最近一天用户评论操作记录起来, score用timestamp替代得分,然后通过Zset的命令RANGEBYSCORE、ZADD、ZRANGEBYSCORE结合实现
RANGEBYSCORE userID:10000:operation:comment 61307510405600 +inf //获得1分钟内的操作记录
redis> ZADD userID:10000:operation:comment 61307510402300 "这是一条评论" //score 为timestamp (integer) 1
redis> ZRANGEBYSCORE userID:10000:operation:comment 61307510405600 +inf //获得1分钟内的操作记录
Method2: 用Redis+Lua实现访问频率控制
# KEYS[1]表示限制次数,由调用程序传入
local key1 ,key2 = 'access:limit' ,'access:expire'
if redis.call('EXISTS' ,key2) > 0 then
return redis.call('DECR' ,key1) ;
else
redis.call('SET' ,key2 ,1)
redis.call('EXPIRE' ,key2 ,60)
redis.call('SET' ,key1 ,KEYS[1])
return KEYS[1]
end
赛况(赛事列表)
最新活动列表,可以用Redis的List数据结构或者sorted set数据结构实现,加上过期时间轻松搞定。同样的方法,也能轻松的实现最新商品列表、各种排行榜等。
消息(未读消息数)
实现思路是:使用hash存储用户上次看过的时间,使用sorted set存储每个模块(评论区、群聊)的每个信息产生的时间,并记录未读消息的数量,实现可以参考:https://yq.aliyun.com/ziliao/92283
点赞(点赞数)
点赞数,实现相对比较简单了,用string数据结构或者Hash数据结构都能实现,这种点赞没有取消的操作,直接Incr即可。假设key为场次编号:active16
string:
INCR active16
GET active16
Hash:
HSET active:active16 zan 0
HINCRBY active:active16 zan 1
HGETALL active:active16
红包雨(传送门、红包雨)、商品库存/红包金额管理
下面介绍一种基于Redis的抢红包方案。
把原始的红包称为大红包,拆分后的红包称为小红包。
1、小红包预先生成,插到数据库里,红包对应的用户ID是null。
2、每个大红包对应两个Redis队列,一个是未消费红包队列,另一个是已消费红包队列。开始时,把未抢的小红包全放到未消费红包队列里。
未消费红包队列里是json字符串,activeID是活动场次,money是红包金额,product是商品个数。如{activeId:'16', money:'300'} 或 {activeID:'16',product:'50'}
3、在Redis中用一个map来过滤已抢到红包的用户。
4、抢红包时,先判断用户是否抢过红包,如果没有,则从未消费红包队列中取出一个小红包,再push到另一个已消费队列中,最后把用户ID放入去重的map中。
5、用一个单线程批量把已消费队列里的红包取出来,再批量update红包的用户ID到数据库里。
上面的流程是很清楚的,但是在第4步时,如果是用户快速点了两次,或者开了两个浏览器来抢红包,会不会有可能用户抢到了两个红包?
为了解决这个问题,采用了lua脚本方式,让第4步整个过程是原子性地执行。
下面是在Redis上执行的Lua脚本:
-- 函数:尝试获得红包,如果成功,则返回json字符串,如果不成功,则返回空
-- 参数:红包队列名, 已消费的队列名,去重的Map名,用户ID
-- 返回值:nil 或者 json字符串,包含用户ID:userId,红包ID:id,红包金额:money
-- 如果用户已抢过红包,则返回nil
if redis.call('hexists', KEYS[3], KEYS[4]) ~= 0 then
return nil
else
-- 先取出一个小红包
local hongBao = redis.call('rpop', KEYS[1]);
if hongBao then
local x = cjson.decode(hongBao);
-- 加入用户ID信息
x['userId'] = KEYS[4];
local re = cjson.encode(x);
-- 把用户ID放到去重的set里
redis.call('hset', KEYS[3], KEYS[4], KEYS[4]);
-- 把红包放到已消费队列里
redis.call('lpush', KEYS[2], re);
return re;
end
end
return nil
参考:https://blog.csdn.net/hengyunabc/article/details/19433779
消息推送
上图中的这种弹出消息,未必是Redis实现或者说肯定不是,哈哈。但是这里想提示的是消息提醒,Redis是可以实现的。
这种用pub/sub机制来实现的消息通知,没有持久化机制,属于即发即弃模式。生产者不需要关心有多少的订阅者,也不用关心订阅者的具体信息,在线的客户端(消费者)正常情况下是都看到的,正如我们关注的某个节目一样,在线的时候总能关注的节目更新通知一样。
高可用建设和弹性扩展
面对世界杯这种全球性的全民赛事,尤其是在大家都比较关注的明星或球队对抗的时候(朋友圈都被刷屏的那种),那压力可想而知,虽然拿不到具体的数据,但是从我在微博时保障的热点事件来看,也能猜着个大概。它跟微博热点有很多的相似点,具有不可预见性和突发性,并且伴随着极短时间内流量的数倍增长,甚至更多,有时持续时间较长。如何快速应对突发流量的冲击,确保线上服务的稳定性,是一个非常巨大的挑战和有意义的事情。为了达到这一目标,首先需要有一个完善的,稳定可靠的,健壮的数据库运维体系来提供支撑和管理。
正如前面一开始提到的,优酷、央视影音、咪咕视频等视频平台,为了保障业务稳定、减少成本(不可能为了短短一个月的赛事准备4年才用到一次的基础设施),选择和公有云结合是最佳选择,这也是阿里云之所以有流量洪峰出现的重要原因。那么对于Redis来说如何去建设高可用服务以及解决弹性扩展问题?主要有两点:
高可用:异地灾备和多活能力
“不要把鸡蛋放到同一个篮子里”,相信很多架构师也肯定会这么去想,也肯定是这么做的。可以自己搭建一套高可用架构,也可以直接采用阿里云Redis服务提供的异地灾备和多活能力和, 实例部署在跨region,自动双向同步。(参考资料:https://help.aliyun.com/document_detail/71881.html?spm=a2c4g.11186623.6.660.4NtWyS)
通过异地灾备和多活的能力,一旦发生故障,还可以通过异地快速接管业务,确保使用体验。另外,很多分布较广的业务,用户需要跨地域远距离访问服务。如果此时访问延迟大,将直接影响用户体验。云数据库Redis提供的云上多活,还可以帮助用户消除跨地域远距离访问时的延迟大问题。
弹性:资源伸缩、读写分离
一个区域出现访问异常后,仍然能通过一些手段,比如降级、切流量、限流等一系列措施来保障服务的稳定性。在云模式下,当业务量上来了,扛不住的时候,可以通过阿里云Redis服务自动具备弹性扩缩容一劳永逸,还可以精打细算使用读写分离功能小成本卸载读压力或者通过混合存储卸载存储成本等等。(参考资料:https://help.aliyun.com/document_detail/65001.html?spm=a2c4g.11186631.6.612.JcCjqf)
总之,Redis是一个非常重要的组件,它能够被广泛的应用于企业的架构中,而且是不可或缺的重要组成部分。
如果之前没有了解过,那么,“Redis,请了解一下”!不足之处,欢迎批评指正。
作者简介:
张冬洪:阿里云MVP,极数云舟对外合作部总监、技术专家,Redis中国用户组主席,中国MySQL用户组主席团成员