整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

百亿流量系统,是如何从0开始搭建的?

百亿流量系统,是如何从0开始搭建的?
作者:xiaojiaqi
来源:https://github.com/xiaojiaqi/10billionhongbaos

. 前言

前几天,偶然看到了 《扛住100亿次请求——如何做一个“有把握”的春晚红包系统”》一文,看完以后,感慨良多,收益很多。正所谓他山之石,可以攻玉,虽然此文发表于2015年,我看到时已经过去良久,但是其中的思想仍然是可以为很多后端设计借鉴。

同时作为一微信后端工程师,看完以后又会思考,学习了这样的文章以后,是否能给自己的工作带来一些实际的经验呢?所谓纸上得来终觉浅,绝知此事要躬行,能否自己实践一下100亿次红包请求呢?否则读完以后脑子里能剩下的东西 不过就是100亿 1400万QPS整流 这样的字眼,剩下的文章将展示作者是如何以此过程为目标,在本地环境的模拟了此过程。

实现的目标:单机支持100万连接,模拟了摇红包和发红包过程,单机峰值QPS 6万,平稳支持了业务。

注:本文以及作者所有内容,仅代表个人理解和实践,过程和微信团队没有任何关系,真正的线上系统也不同,只是从一些技术点进行了实践,请读者进行区分。


2. 背景知识


  • QPS:Queries per second 每秒的请求数目
  • PPS:Packets per second 每秒数据包数目
  • 摇红包:客户端发出一个摇红包的请求,如果系统有红包就会返回,用户获得红包
  • 发红包:产生一个红包里面含有一定金额,红包指定数个用户,每个用户会收到红包信息,用户可以发送拆红包的请求,获取其中的部分金额。


3. 确定目标

在一切系统开始以前,我们应该搞清楚我们的系统在完成以后,应该有一个什么样的负载能力。

3.1 用户总数

通过文章我们可以了解到接入服务器638台,服务上限大概是14.3亿用户, 所以单机负载的用户上限大概是14.3亿/638台=228万用户/台。但是目前中国肯定不会有14亿用户同时在线,参考 http://qiye.qianzhan.com/show/detail/160818-b8d1c700.html的说法,2016年Q2 微信用户大概是8亿,月活在5.4 亿左右。所以在2015年春节期间,虽然使用的用户会很多,但是同时在线肯定不到5.4亿。

3.2. 服务器数量

一共有638台服务器,按照正常运维设计,我相信所有服务器不会完全上线,会有一定的硬件冗余,来防止突发硬件故障。假设一共有600台接入服务器。

3.3 单机需要支持的负载数

每台服务器支持的用户数:5.4亿/600=90万。也就是平均单机支持90万用户。如果真实情况比90万更多,则模拟的情况可能会有偏差,但是我认为QPS在这个实验中更重要。

3.4. 单机峰值QPS

文章中明确表示为1400万QPS.这个数值是非常高的,但是因为有600台服务器存在,所以机的QPS为 1400万/600=约为2.3万QPS, 文章曾经提及系统可以支持4000万QPS,那么系统的QPS 至少要到4000万/600=约为 6.6万, 这个数值大约是目前的3倍,短期来看并不会被触及。但是我相信应该做过相应的压力测试。

3.5. 发放红包

文中提到系统以5万个每秒的下发速度,那么单机每秒下发速度50000/600 =83个/秒,也就是单机系统应该保证每秒以83个的速度下发即可。

最后考虑到系统的真实性,还至少有用户登录的动作,拿红包这样的业务。真实的系统还会包括聊天这样的服务业务。

最后整体的看一下 100亿次摇红包这个需求,假设它是均匀地发生在春节联欢晚会的4个小时里,那么服务器的QPS 应该是10000000000/600/3600/4.0=1157. 也就是单机每秒1000多次,这个数值其实并不高。如果完全由峰值速度1400万消化 10000000000/(1400*10000)=714秒,也就是说只需要峰值坚持11分钟,就可以完成所有的请求。可见互联网产品的一个特点就是峰值非常高,持续时间并不会很长。


总结

从单台服务器看,它需要满足下面一些条件:


  1. 支持至少100万连接用户
  2. 每秒至少能处理2.3万的QPS,这里我们把目标定得更高一些 分别设定到了3万和6万。
  3. 摇红包:支持每秒83个的速度下发放红包,也就是说每秒有2.3万次摇红包的请求,其中83个请求能摇到红包,其余的2.29万次请求会知道自己没摇到。当然客户端在收到红包以后,也需要确保客户端和服务器两边的红包数目和红包内的金额要一致。因为没有支付模块,所以我们也把要求提高一倍,达到200个红包每秒的分发速度
  4. 支持用户之间发红包业务,确保收发两边的红包数目和红包内金额要一致。同样也设定200个红包每秒的分发速度为我们的目标。


想完整模拟整个系统实在太难了,首先需要海量的服务器,其次需要上亿的模拟客户端。这对我来说是办不到,但是有一点可以确定,整个系统是可以水平扩展的,所以我们可以模拟100万客户端,在模拟一台服务器 那么就完成了1/600的模拟。

和现有系统区别:和大部分高QPS测试的不同,本系统的侧重点有所不同。我对2者做了一些对比。


4. 基础软件和硬件


4.1软件

Golang 1.8r3 , shell, python (开发没有使用c++ 而是使用了golang, 是因为使用golang 的最初原型达到了系统要求。虽然golang 还存在一定的问题,但是和开发效率比,这点损失可以接受)

服务器操作系统:Ubuntu 12.04

客户端操作系统:debian 5.0


4.2硬件环境

服务端:dell R2950。8核物理机,非独占有其他业务在工作,16G内存。这台硬件大概是7年前的产品,性能应该不是很高要求。

服务器硬件版本:


服务器CPU信息:

客户端:esxi 5.0 虚拟机,配置为4核 5G内存。一共17台,每台和服务器建立6万个连接。完成100万客户端模拟


5. 技术分析和实现

5.1) 单机实现100万用户连接

这一点来说相对简单,笔者在几年前就早完成了单机百万用户的开发以及操作。现代的服务器都可以支持百万用户。相关内容可以查看:

github代码以及相关文档:

https://github.com/xiaojiaqi/C1000kPracticeGuide

系统配置以及优化文档:

https://github.com/xiaojiaqi/C1000kPracticeGuide/tree/master/docs/cn

5.2) 3万QPS

这个问题需要分2个部分来看客户端方面和服务器方面。


  • 客户端QPS


因为有100万连接连在服务器上,QPS为3万。这就意味着每个连接每33秒,就需要向服务器发一个摇红包的请求。因为单IP可以建立的连接数为6万左右, 有17台服务器同时模拟客户端行为。我们要做的就保证在每一秒都有这么多的请求发往服务器即可。

其中技术要点就是客户端协同。但是各个客户端的启动时间,建立连接的时间都不一致,还存在网络断开重连这样的情况,各个客户端如何判断何时自己需要发送请求,各自该发送多少请求呢?

我是这样解决的:利用NTP服务,同步所有的服务器时间,客户端利用时间戳来判断自己的此时需要发送多少请求。

算法很容易实现:假设有100万用户,则用户id 为0-999999.要求的QPS为5万, 客户端得知QPS为5万,总用户数为100万,它计算 100万/5万=20,所有的用户应该分为20组,如果 time() % 20==用户id % 20,那么这个id的用户就该在这一秒发出请求,如此实现了多客户端协同工作。每个客户端只需要知道 总用户数和QPS 就能自行准确发出请求了。

(扩展思考:如果QPS是3万 这样不能被整除的数目,该如何办?如何保证每台客户端发出的请求数目尽量的均衡呢?)


  • 服务器QPS


服务器端的QPS相对简单,它只需要处理客户端的请求即可。但是为了客观了解处理情况,我们还需要做2件事情。


  • 第一:需要记录每秒处理的请求数目,这需要在代码里埋入计数器。
  • 第二:我们需要监控网络,因为网络的吞吐情况,可以客观的反映出QPS的真实数据。为此,我利用python脚本 结合ethtool 工具编写了一个简单的工具,通过它我们可以直观的监视到网络的数据包通过情况如何。它可以客观的显示出我们的网络有如此多的数据传输在发生。


工具截图:


5.3) 摇红包业务

摇红包的业务非常简单,首先服务器按照一定的速度生产红包。红包没有被取走的话,就堆积在里面。服务器接收一个客户端的请求,如果服务器里现在有红包就会告诉客户端有,否则就提示没有红包。

因为单机每秒有3万的请求,所以大部分的请求会失败。只需要处理好锁的问题即可。

我为了减少竞争,将所有的用户分在了不同的桶里。这样可以减少对锁的竞争。如果以后还有更高的性能要求,还可以使用 高性能队列——Disruptor来进一步提高性能。

注意,在我的测试环境里是缺少支付这个核心服务的,所以实现的难度是大大的减轻了。另外提供一组数字:2016年淘宝的双11的交易峰值仅仅为12万/秒,微信红包分发速度是5万/秒,要做到这点是非常困难的。(http://mt.sohu.com/20161111/n472951708.shtml)


5.4) 发红包业务

发红包的业务很简单,系统随机产生一些红包,并且随机选择一些用户,系统向这些用户提示有红包。这些用户只需要发出拆红包的请求,系统就可以随机从红包中拆分出部分金额,分给用户,完成这个业务。同样这里也没有支付这个核心服务。


5.5)监控

最后 我们需要一套监控系统来了解系统的状况,我借用了我另一个项目(https://github.com/xiaojiaqi/fakewechat) 里的部分代码完成了这个监控模块,利用这个监控,服务器和客户端会把当前的计数器内容发往监控,监控需要把各个客户端的数据做一个整合和展示。同时还会把日志记录下来,给以后的分析提供原始数据。线上系统更多使用opentsdb这样的时序数据库,这里资源有限,所以用了一个原始的方案。

监控显示日志大概这样:


6. 代码实现及分析

在代码方面,使用到的技巧实在不多,主要是设计思想和golang本身的一些问题需要考虑。

首先golang的goroutine 的数目控制,因为至少有100万以上的连接,所以按照普通的设计方案,至少需要200万或者300万的goroutine在工作。这会造成系统本身的负担很重。

其次就是100万个连接的管理,无论是连接还是业务都会造成一些心智的负担。

我的设计是这样的:

首先将100万连接分成多个不同的SET,每个SET是一个独立,平行的对象。每个SET 只管理几千个连接,如果单个SET 工作正常,我只需要添加SET就能提高系统处理能力。

其次谨慎的设计了每个SET里数据结构的大小,保证每个SET的压力不会太大,不会出现消息的堆积。

再次减少了gcroutine的数目,每个连接只使用一个goroutine,发送消息在一个SET里只有一个gcroutine负责,这样节省了100万个goroutine。这样整个系统只需要保留 100万零几百个gcroutine就能完成业务。大量的节省了cpu 和内存

系统的工作流程大概是:每个客户端连接成功后,系统会分配一个goroutine读取客户端的消息,当消息读取完成,将它转化为消息对象放至在SET的接收消息队列,然后返回获取下一个消息。

在SET内部,有一个工作goroutine,它只做非常简单而高效的事情,它做的事情如下,检查SET的接受消息,它会收到3类消息


  1. 客户端的摇红包请求消息
  2. 客户端的其他消息 比如聊天 好友这一类
  3. 服务器端对客户端消息的回应


对于第1种消息客户端的摇红包请求消息 是这样处理的,从客户端拿到摇红包请求消息,试图从SET的红包队列里 获取一个红包,如果拿到了就把红包信息 返回给客户端,否则构造一个没有摇到的消息,返回给对应的客户端。

对于第2种消息客户端的其他消息 比如聊天 好友这一类,只需简单地从队列里拿走消息,转发给后端的聊天服务队列即可,其他服务会把消息转发出去。

对于第3种消息服务器端对客户端消息的回应。SET 只需要根据消息里的用户id,找到SET里保留的用户连接对象,发回去就可以了。

对于红包产生服务,它的工作很简单,只需要按照顺序在轮流在每个SET的红包产生对列里放至红包对象就可以了。这样可以保证每个SET里都是公平的,其次它的工作强度很低,可以保证业务稳定。

见代码:

https://github.com/xiaojiaqi/10billionhongbaos


7. 实践

实践的过程分为3个阶段

阶段1

分别启动服务器端和监控端,然后逐一启动17台客户端,让它们建立起100万的链接。在服务器端,利用ss 命令 统计出每个客户端和服务器建立了多少连接。

命令如下:


 Alias ss2=Ss –ant | grep 1025 | grep EST | awk –F: “{print \$8}” | sort | uniq –c’

结果如下:


阶段2

利用客户端的http接口,将所有的客户端QPS 调整到3万,让客户端发出3W QPS强度的请求。

运行如下命令:

观察网络监控和监控端反馈,发现QPS 达到预期数据,网络监控截图:

在服务器端启动一个产生红包的服务,这个服务会以200个每秒的速度下发红包,总共4万个。此时观察客户端在监控上的日志,会发现基本上以200个每秒的速度获取到红包。

等到所有红包下发完成后,再启动一个发红包的服务,这个服务系统会生成2万个红包,每秒也是200个,每个红包随机指定3位用户,并向这3个用户发出消息,客户端会自动来拿红包,最后所有的红包都被拿走。


阶段3

利用客户端的http接口,将所有的客户端QPS 调整到6万,让客户端发出6W QPS强度的请求。

如法炮制,在服务器端,启动一个产生红包的服务,这个服务会以200个每秒的速度下发红包。总共4万个。此时观察客户端在监控上的日志,会发现基本上以200个每秒的速度获取到红包。

等到所有红包下发完成后,再启动一个发红包的服务,这个服务系统会生成2万个红包,每秒也是200个,每个红包随机指定3位用户,并向这3个用户发出消息,客户端会自动来拿红包,最后所有的红包都被拿走。

最后,实践完成。

8. 分析数据

在实践过程中,服务器和客户端都将自己内部的计数器记录发往监控端,成为了日志。我们利用简单python 脚本和gnuplt 绘图工具,将实践的过程可视化,由此来验证运行过程。

第一张是客户端的QPS发送数据:

这张图的横坐标是时间,单位是秒,纵坐标是QPS,表示这时刻所有客户端发送的请求的QPS。

图的第一区间,几个小的峰值,是100万客户端建立连接的, 图的第二区间是3万QPS 区间,我们可以看到数据 比较稳定的保持在3万这个区间。最后是6万QPS区间。但是从整张图可以看到QPS不是完美地保持在我们希望的直线上。这主要是以下几个原因造成的


  1. 当非常多goroutine 同时运行的时候,依靠sleep 定时并不准确,发生了偏移。我觉得这是golang本身调度导致的。当然如果cpu比较强劲,这个现象会消失。
  2. 因为网络的影响,客户端在发起连接时,可能发生延迟,导致在前1秒没有完成连接。
  3. 服务器负载较大时,1000M网络已经出现了丢包现象,可以通过ifconfig 命令观察到这个现象,所以会有QPS的波动。


第二张是 服务器处理的QPS图:

和客户端的向对应的,服务器也存在3个区间,和客户端的情况很接近。但是我们看到了在大概22:57分,系统的处理能力就有一个明显的下降,随后又提高的尖状。这说明代码还需要优化。

整体观察在3万QPS区间,服务器的QPS比较稳定,在6万QSP时候,服务器的处理就不稳定了。我相信这和我的代码有关,如果继续优化的话,还应该能有更好的效果。

将2张图合并起来 :

基本是吻合的,这也证明系统是符合预期设计的。

这是红包生成数量的状态变化图:

非常的稳定。

这是客户端每秒获取的摇红包状态:

可以发现3万QPS区间,客户端每秒获取的红包数基本在200左右,在6万QPS的时候,以及出现剧烈的抖动,不能保证在200这个数值了。我觉得主要是6万QPS时候,网络的抖动加剧了,造成了红包数目也在抖动。

最后是golang 自带的pprof 信息,其中有gc 时间超过了10ms, 考虑到这是一个7年前的硬件,而且非独占模式,所以还是可以接受。


总结

按照设计目标,我们模拟和设计了一个支持100万用户,并且每秒至少可以支持3万QPS,最多6万QPS的系统,简单模拟了微信的摇红包和发红包的过程。可以说达到了预期的目的。

如果600台主机每台主机可以支持6万QPS,只需要7分钟就可以完成 100亿次摇红包请求。

虽然这个原型简单地完成了预设的业务,但是它和真正的服务会有哪些差别呢?我罗列了一下


Refers:


  • 单机百万的实践
  • https://github.com/xiaojiaqi/C1000kPracticeGuide
  • 如何在AWS上进行100万用户压力测试
  • https://github.com/xiaojiaqi/fakewechat/wiki/Stress-Testing-in-the-Cloud
  • 构建一个你自己的类微信系统
  • https://github.com/xiaojiaqi/fakewechat/wiki/Design
  • http://djt.qq.com/article/view/1356
  • http://techblog.cloudperf.net/2016/05/2-million-packets-per-second-on-public.html
  • http://datacratic.com/site/blog/1m-qps-nginx-and-ubuntu-1204-ec2
  • @火丁笔记
  • http://huoding.com/2013/10/30/296
  • https://gobyexample.com/non-blocking-channel-operations

项目地址:https://github.com/xiaojiaqi/10billionhongbaos

时音视频的开发学习有很多可以参考的开源项目。

音视频流媒体在现在的生活中已经无处不在,拥有一大批顶级的音频/视频工具确实派得上用场。修剪文件、编辑视频、最大化音频――我们需要满足社交媒体流的传播需求,而公司总是需要音频/视频内容,以便与用户进行最有效的沟通。

一个实时音视频应用共包括几个环节:采集、编码、前后处理、传输、解码、缓冲、渲染等很多环节。每一个细分环节,还有更细分的技术模块。比如,前后处理环节有美颜、滤镜、回声消除、噪声抑制等,采集有麦克风阵列等,编解码有VP8、VP9、H.264、H.265等。

我们今天汇总了一些能帮助到正在学习或进行音视频开发的实时音视频开发者们的开源项目与几个也在为开源社区贡献力量的商业服务。这些项目分为几类:音视频编解码类、视频前后处理、服务端类等。

音视频编解码类开源项目

视频编解码的作用,就是在设备的摄像头采集画面和前处理后,将图像进行压缩,进行数字编码,用于传输。编解码器的优劣基本在于:压缩效率的高低,速度和功耗。

目前,主流的视频编码器分为3个系列:VPx(VP8,VP9),H.26x(H.264,H.265),AVS(AVS1.0,AVS2.0)。VPx系列是由Google开源的视频编解码标准。在保证相同质量情况下,VP9相比VP8码率减少约50%。H.26x系列在硬件支持上比较广泛,H.265的编码效率能比上一代提高了30-50%,但是复杂度和功耗会比上一代大很多,所以纯软件编码实现的话有一定瓶颈,现有的技术下,还是需要依靠硬件编解码为主。AVS是我国具备自主知识产权的第二代信源编码标准,目前已经发展到第二代。

WebRTC

首先会用到的肯定是WebRTC,是一个支持网页浏览器进行实时语音对话或视频对话的开源项目。它提供了包括音视频的采集、编解码、网络传输、显示等功能。如果你想基于WebRTC开发实时音视频应用,需要注意,由于WebRTC缺少服务端设计和部署方案,你还需要将WebRTC与Janus等服务端类开源项目结合即可。

官网地址: ?webrtc.org/?

x264

H.264是目前应用最广的码流标准。x264则是能够产生符合H.264标准的码流的编码器,它可以将视频流编码为H.264、MPEG-4 AVC格式。它提供了命令行接口与API,前者被用于一些图形用户接口例如Straxrip、MeGUI,后者则被FFmpeg、Handbrake等调用。当然,既然有x264,就有对应HEVC/H.265的x265。

官网地址:? ?https://www.videolan.org/developers/x264.html?

FFmpeg

FFmpeg大家应该不陌生,提供了编码、解码、转换、封装等功能,以及剪裁、缩放、色域等后期处理,支持几乎目前所有音视频编码标准(由于格式众多,我们就不一一列列举了,可以在Wikipedia中找到)。

同时,FFmpeg还衍生出了libav项目,从中诞生了视频解码器LAV,许多播放软件都可调用LAV进行解码,并且LAV本身也支持利用显卡进行视频硬解。很多主流视频播放器中都以FFmpeg作为内核播放器。不仅仅是视频播放器,就连Chrome这类可以播放网页视频的浏览器也受益于FFmpeg。很多开发者也基于FFmpeg做过很多开发并开源出来,比如大神雷霄骅(代码可见他的sourceforge)。

官网地址:? ?ffmpeg.org/?

ijkplayer

在介绍ijkplayer之前,要先提到ffplay。ffplay是一个使用了FFmpeg和sdl库的可移植的媒体播放器。ijkplay是Bilibili开源的基于ffplay.c实现的轻量级iOS/Android视频播放器,API易于集成,且编译配置可裁剪,利于控制安装包大小。

在编解码方面,ijkplayer支持视频软解和硬解,可以在播放前配置,但在播放过程中则不能切换。iOS和Android上视频硬解可分别使用大家熟悉的VideoToolbox和MediaCodec。但ijkplayer对音频仅支持软解。

Github地址:? ?https://github.com/Bilibili/ijkplayer?

JSMpeg

JSMpeg是一个基于JavaScript的MPEG1视频的解码器。如果要做H5端的视频直播,可以考虑使用JSMpeg在移动端进行解码。在H5端做音视频直播,可以使用JSMpeg进行视频解码,这也是最近比较火的H5抓娃娃的主流策略。

Github地址:? ?https://github.com/phoboslab/jsmpeg?

Opus

Opus是用C语言开发的一个高灵活度的音频编码器,针对ARM、x86有特殊优化,fix-point实现。Opus在各方面都有着明显优势。它同时支持语音与音乐的编码,比特率为6k-510k。它融合了SILK编码方法和CELT编码方法。SILK原本被用于Skype中,基于语音信号的线性预测分析(LPC),对音乐支持并不好。而CELT尽管适用于全带宽音频,但对低比特率语音的编码效率不高,所以两者在Opus中形成了互补。

Opus是“取代”了Speex。但是Speex中有的功能,Opus却没有,比如回声消除。这个功能已经从编码器中独立出来。所以如果想实现好的回声消除,可以配合WebRTC的AEC和AECM模块做二次开发。

官网地址:? ?opus-codec.org/?

live555

live555是一个C++流媒体开源项目,其中不仅包括了传输协议(SIP、RTP)、音视频编码器(H.264、MPEG4)等,还包括流媒体服务器的例子,是流媒体项目的首选,里面的传输模块是非常值得视频会议开发作为参考的。

官网地址:? ?www.live555.com/?

本文福利, 免费领取C++音视频学习资料包+学习路线大纲、技术视频/代码,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,编解码,推拉流,srs),有需要的可以进企鹅裙927239107领取哦~

音视频前后处理开源项目

前后处理包含很多细分技术,应用正确的话,对视频质量或多或少都有提升。不过每增加一个处理环节,必然会增加运算量与延时,所以如何取舍,还要大家各自斟酌。

Seetaface

Seetaface是由中科院山世光老师开源的一套完整的人脸检测,人脸对齐和人脸验证方案。代码基于C++实现,开源协议为BSD-2,可供学术界和工业界免费使用。且不依赖于任何第三方的库函数,在使用对齐好的LFW图片上,检测对齐全部使用该开源软件的情况下可达到97.1%。

Github地址:? ?https://github.com/seetaface/SeetaFaceEngine?

GPUImage

现在在iOS端做美颜效果、加水印,基本都会采用GPUImage,它内置了125种渲染效果, 还支持脚本自定义。该项目实现了图片滤镜、摄像头实时滤镜。它优势在于处理效果是基于GPU实现,相对于CPU处理性能更高。

Github地址:? ?https://github.com/BradLarson/GPUImage?

Open nsfw model

Open nsfw model是雅虎开源项目,全名是Open Not suitable for work model,专门鉴别不适合工作时间浏览的图片(言而言之就是小黄图)。它是基于Caffe框架训练的模型,用于音视频后处理。不过,它还不能鉴别恐怖、血腥图片。

Github地址:? ?https://github.com/yahoo/open_nsfw??

Soundtouch

Soundtouch是一个开源的音频处理框架,主要功能对音频变速、变调,实现变声的效果。同时,它也能对媒体流实时处理。采用32位浮点或者16位定点,支持单声道或者双声道,采样率范围为8k - 48k。

官网地址:? ?www.surina.net/soundtouch/??

服务端类开源项目

正如开始时我们所说,WebRTC缺少服务端的设计与部署,利用MCU、SFU实现多人聊天,提高传输质量,都需要开发者自己动手。而下面这些开源项目能够帮到你。

Jitsi

Jitsi是开源的视频会议系统,可以实现在线视频会议,文档共享和即时消息的分享。它支持网络视频会议,使用SFU模式实现视频路由器功能。开发语言是Java。它支持SIP帐号注册电话呼叫。不仅支持单机本地安装方式,还支持云平台安装。

官网地址:? ?jitsi.org/??

JsSIP

JsSIP是基于WebRTC的JavaScript SIP协议实现的库,可以在浏览器和Node.js中运行。它可以与 OverSIP、Kamailio、Asterisk、OfficeSIP等SIP Server一起运行。

Github地址:?https://github.com/versatica/JsSIP

SRS

SRS是一个采用MIT协议授权的国产的简单的RTMP/HLS 直播服务器。最新版还支持FLV模式,同时具备了RTMP的实时性,以及HLS中属于HTTP协议对各种网络环境高度适应性,并且支持更多播放器。它的功能与nginx-rtmp-module类似, 可以实现RTMP/HLS的分发。

Github地址:? ?https://github.com/ossrs/srs

JRTPLIB

JRTPLIB 是一个开源的 RTP协议实现库,支持Windows和unix平台。它支持多线程,处理性能较好。它还支持RFC3550、UDP IPV6,支持自定义扩展传输协议。但它不支持TCP传输,这需要开发者自己来实现。同时,它也不支持音视频的分包,代码要你自己来实现。

Github地址:? ?https://github.com/j0r1/JRTPLIB

OPAL

OPAL是OpenH323的下一个版本,继承了Openh323协议,其新包含了SIP协议栈,是实现SIP协议的首选,缺点是参考例子较少。

代码地址:? ?https://link.zhihu.com/?target=http://sourceforge.net/projects/opalvoip/files/

Kurento

Kurento是一个基于WebRTC的媒体服务端,并包含了一系列API,可以简化web与移动端实时视频应用的开发。

Github地址:? ?https://github.com/Kurento

Janus

Janus是一个WebRTC媒体网关。不论是做流媒体、视频会议、录制、网关,都可以基于Janus来实现。

Github地址:github.com/Kurento

? ?Callstats.io??

实时通信过程中的,延时、丢包、接通率、掉线率等质量问题,都影响用户体验。商用项目尤其需要关注。Callstats是一家通过对WebRTC呼叫进行专业监测,来帮助用户搜集通讯数据,提升通话质量的服务商。

Callstats也通过Github开放很多案例,可供使用Jitsi-videobridge,、turn-server、JsSIP的开发者参考。

Github地址:?https://github.com/callstats-io

Meetecho

Meetecho是著名的开源WebRTC网关项目Janus的开发者。他们还提供基于Janus开发的技术咨询与部署服务、建立视频会议直播与录制服务等。

Github地址:? ?https://github.com/carlhuda/janus

声网Agora

声网提供了从编解码到端到端传输的全套服务,开发者可以接入上文所述的音视频前后处理的开源项目,配合使用声网SDK可以建立高质量的实时音视频应用。在Web端,Agora Web SDK可以帮助WebRTC开发者解决服务端传输中会遇到的卡顿、延时、回声、多人视频不稳定等问题。同时,声网SDK还对多个系统平台的应用提供实时音视频通讯服务。

声网在Github上有许多可供开发者参考、实践的demo源码,覆盖了从网页端、iOS到Android平台,以及音视频直播、游戏连麦、企业会议、AR、直播答题、小程序等多种实时互动应用场景。

Github地址:? ?https://github.com/AgoraIO-Community

我们在这里列出了18个开源项目,以及3个能有效保证实时音视频传输质量的服务。不过篇幅有限,还有很多开源项目我们没有详细列出,比如在音视频方面,http://Xiph.org的Speex、FLAC,还有Xvid、libvpx、Lagarith、Daala、Thor等。欢迎大家继续补充。

片来自包图网

知乎存储平台团队基于开源 Redis 组件打造的 Redis 平台管理系统,经过不断的研发迭代,目前已经形成了一整套完整自动化运维服务体系,提供一键部署集群,一键自动扩缩容,Redis 超细粒度监控,旁路流量分析等辅助功能。

目前,Redis 在知乎的规模如下:

  • 机器内存总量约 70TB,实际使用内存约 40TB。
  • 平均每秒处理约 1500 万次请求,峰值每秒约 2000 万次请求。
  • 每天处理约 1 万亿余次请求。
  • 单集群每秒处理最高每秒约 400 万次请求。
  • 集群实例与单机实例总共约 800 个。
  • 实际运行约 16000 个 Redis 实例。
  • Redis 使用官方 3.0.7 版本,少部分实例采用 4.0.11 版本。

知乎 Redis 平台演进历程

根据业务的需求,我们将实例区分为如下两种类型:

  • 单机(Standalone),单机实例通常用于容量与性能要求不高的小型存储。
  • 集群(Cluster),集群则用来应对对性能和容量要求较高的场景。

单机(Standalone)

对于单机实例,我们采用原生主从(Master-Slave)模式实现高可用,常规模式下对外仅暴露 Master 节点。由于使用原生 Redis,所以单机实例支持所有 Redis 指令。

对于单机实例,我们使用 Redis 自带的哨兵(Sentinel)集群对实例进行状态监控与 Failover。

Sentinel 是 Redis 自带的高可用组件,将 Redis 注册到由多个 Sentinel 组成的 Sentinel 集群后,Sentinel 会对 Redis 实例进行健康检查。

当 Redis 发生故障后,Sentinel 会通过 Gossip 协议进行故障检测,确认宕机后会通过一个简化的 Raft 协议来提升 Slave 成为新的 Master。

通常情况我们仅使用 1 个 Slave 节点进行冷备,如果有读写分离请求,可以建立多个 Read only slave 来进行读写分离。



如上图所示,通过向 Sentinel 集群注册 Master 节点实现实例的高可用,当提交 Master 实例的连接信息后,Sentinel 会主动探测所有的 Slave 实例并建立连接,定期检查健康状态。

客户端通过多种资源发现策略如简单的 DNS 发现 Master 节点,将来有计划迁移到如 Consul 或 etcd 等资源发现组件 。

当 Master 节点发生宕机时,Sentinel 集群会提升 Slave 节点为新的 Master,同时在自身的 pubsub channel +switch-master 广播切换的消息,具体消息格式为:

switch-master <master name> <oldip> <oldport> <newip> <newport> 

Watcher 监听到消息后,会去主动更新资源发现策略,将客户端连接指向新的 Master 节点,完成 Failover。

实际使用中需要注意以下几点:

  • 只读 Slave 节点可以按照需求设置 slave-priority 参数为 0,防止故障切换时选择了只读节点而不是热备 Slave 节点。
  • Sentinel 进行故障切换后会执行 CONFIG REWRITE 命令将 SLAVEOF 配置落地,如果 Redis 配置中禁用了 CONFIG 命令,切换时会发生错误,可以通过修改 Sentinel 代码来替换 CONFIG 命令。
  • Sentinel Group 监控的节点不宜过多,实测超过 500 个切换过程偶尔会进入 TILT 模式,导致 Sentinel 工作不正常,推荐部署多个 Sentinel 集群并保证每个集群监控的实例数量小于 300 个。
  • Master 节点应与 Slave 节点跨机器部署,有能力的使用方可以跨机架部署,不推荐跨机房部署 Redis 主从实例。
  • Sentinel 切换功能主要依赖 down-after-milliseconds 和failover-timeout 两个参数,down-after-milliseconds 决定了Sentinel 判断 Redis 节点宕机的超时,知乎使用 30000 作为阈值。

而 failover-timeout 则决定了两次切换之间的最短等待时间,如果对于切换成功率要求较高,可以适当缩短 failover-timeout 到秒级保证切换成功。

  • 单机网络故障等同于机器宕机,但如果机房全网发生大规模故障会造成主从多次切换,此时资源发现服务可能更新不够及时,需要人工介入。

集群(Cluster)

当实例需要的容量超过 20G 或要求的吞吐量超过 20 万请求每秒时,我们会使用集群(Cluster)实例来承担流量。

集群是通过中间件(客户端或中间代理等)将流量分散到多个 Redis 实例上的解决方案。

知乎的 Redis 集群方案经历了两个阶段:

  • 客户端分片
  • Twemproxy 代理

客户端分片(before 2015)

早期知乎使用 redis-shard 进行客户端分片,redis-shard 库内部实现了 CRC32、MD5、SHA1 三种哈希算法,支持绝大部分Redis 命令。使用者只需把 redis-shard 当成原生客户端使用即可,无需关注底层分片。



基于客户端的分片模式具有如下优点:

  • 基于客户端分片的方案是集群方案中最快的,没有中间件,仅需要客户端进行一次哈希计算,不需要经过代理,没有官方集群方案的 MOVED/ASK 转向。
  • 不需要多余的 Proxy 机器,不用考虑 Proxy 部署与维护。
  • 可以自定义更适合生产环境的哈希算法。

但是也存在如下问题:

  • 需要每种语言都实现一遍客户端逻辑,早期知乎全站使用 Python 进行开发,但是后来业务线增多,使用的语言增加至 Python,Golang,Lua,C/C++,JVM 系(Java,Scala,Kotlin)等,维护成本过高。
  • 无法正常使用 MSET、MGET 等多种同时操作多个 Key 的命令,需要使用 Hash tag 来保证多个 Key 在同一个分片上。
  • 升级麻烦,升级客户端需要所有业务升级更新重启,业务规模变大后无法推动。
  • 扩容困难,存储需要停机使用脚本 Scan 所有的 Key 进行迁移,缓存只能通过传统的翻倍取模方式进行扩容。
  • 由于每个客户端都要与所有的分片建立池化连接,客户端基数过大时会造成 Redis 端连接数过多,Redis 分片过多时会造成 Python 客户端负载升高。
  • 早期知乎大部分业务由 Python 构建,Redis 使用的容量波动较小,redis-shard 很好地应对了这个时期的业务需求,在当时是一个较为不错的解决方案。

Twemproxy 集群 (2015 - Now)

2015 年开始,业务上涨迅猛,Redis 需求暴增,原有的 redis-shard 模式已经无法满足日益增长的扩容需求,我们开始调研多种集群方案,最终选择了简单高效的 Twemproxy 作为我们的集群方案。

由 Twitter 开源的 Twemproxy 具有如下优点:

  • 性能很好且足够稳定,自建内存池实现 Buffer 复用,代码质量很高。
  • 支持 fnv1a_64、murmur、md5 等多种哈希算法。
  • 支持一致性哈希(ketama),取模哈希(modula)和随机(random)三种分布式算法。

但是缺点也很明显:

  • 单核模型造成性能瓶颈。
  • 传统扩容模式仅支持停机扩容。

对此,我们将集群实例分成两种模式:

  • 缓存(Cache)
  • 存储(Storage)

如果使用方可以接受通过损失一部分少量数据来保证可用性,或使用方可以从其余存储恢复实例中的数据,这种实例即为缓存,其余情况均为存储。我们对缓存和存储采用了不同的策略。

存储



对于存储我们使用 fnv1a_64 算法结合 modula 模式即取模哈希对 Key 进行分片。

底层 Redis 使用单机模式结合 Sentinel 集群实现高可用,默认使用 1 个 Master 节点和 1 个 Slave 节点提供服务,如果业务有更高的可用性要求,可以拓展 Slave 节点。

当集群中 Master 节点宕机,按照单机模式下的高可用流程进行切换,Twemproxy 在连接断开后会进行重连。

对于存储模式下的集群,我们不会设置 auto_eject_hosts,不会剔除节点。

同时,对于存储实例,我们默认使用 noeviction 策略,在内存使用超过规定的额度时直接返回 OOM 错误,不会主动进行 Key 的删除,保证数据的完整性。

由于 Twemproxy 仅进行高性能的命令转发,不进行读写分离,所以默认没有读写分离功能。

而在实际使用过程中,我们也没有遇到集群读写分离的需求,如果要进行读写分离,可以使用资源发现策略在 Slave 节点上架设 Twemproxy 集群,由客户端进行读写分离的路由。

缓存

考虑到对于后端(MySQL/HBase/RPC 等)的压力,知乎绝大部分业务都没有针对缓存进行降级,这种情况下对缓存的可用性要求较数据的一致性要求更高。

但是如果按照存储的主从模式实现高可用,1 个 Slave 节点的部署策略在线上环境只能容忍 1 台物理节点宕机,N 台物理节点宕机高可用就需要至少 N 个 Slave 节点,这无疑是种资源的浪费。



所以我们采用了 Twemproxy 一致性哈希(Consistent Hashing)策略来配合 auto_eject_hosts 自动弹出策略组建 Redis 缓存集群。

对于缓存我们仍然使用 fnv1a_64 算法进行哈希计算,但是分布算法我们使用了 ketama 即一致性哈希进行 Key 分布。缓存节点没有主从,每个分片仅有 1 个 Master 节点承载流量。

Twemproxy 配置 auto_eject_hosts 会在实例连接失败超过server_failure_limit 次的情况下剔除节点。

并在 server_retry_timeout 超时之后进行重试,剔除后配合 ketama 一致性哈希算法重新计算哈希环,恢复正常使用,这样即使一次宕机多个物理节点仍然能保持服务。



在实际的生产环境中需要注意以下几点:

  • 剔除节点后,会造成短时间的命中率下降,后端存储如 MySQL、HBase 等需要做好流量监测。
  • 线上环境缓存后端分片不宜过大,建议维持在 20G 以内,同时分片调度应尽可能分散,这样即使宕机一部分节点,对后端造成的额外的压力也不会太多。
  • 机器宕机重启后,缓存实例需要清空数据之后启动,否则原有的缓存数据和新建立的缓存数据会冲突导致脏缓存。

直接不启动缓存也是一种方法,但是在分片宕机期间会导致周期性 server_failure_limit 次数的连接失败。

  • server_retry_timeout 和 server_failure_limit 需要仔细敲定确认,知乎使用 10min 和 3 次作为配置,即连接失败 3 次后剔除节点,10 分钟后重新进行连接。

Twemproxy 部署

在方案早期我们使用数量固定的物理机部署 Twemproxy,通过物理机上的 Agent 启动实例,Agent 在运行期间会对 Twemproxy 进行健康检查与故障恢复。

由于 Twemproxy 仅提供全量的使用计数,所以 Agent 运行时还会进行定时的差值计算来计算 Twemproxy 的 requests_per_second 等指标。

后来为了更好地故障检测和资源调度,我们引入了 Kubernetes,将 Twemproxy 和 Agent 放入同一个 Pod 的两个容器内,底层 Docker 网段的配置使每个 Pod 都能获得独立的 IP,方便管理。

最开始,本着简单易用的原则,我们使用 DNS A Record 来进行客户端的资源发现,每个 Twemproxy 采用相同的端口号,一个 DNS A Record 后面挂接多个 IP 地址对应多个 Twemproxy 实例。

初期,这种方案简单易用,但是到了后期流量日益上涨,单集群 Twemproxy 实例个数很快就超过了 20 个。

由于 DNS 采用的 UDP 协议有 512 字节的包大小限制,单个 A Record 只能挂接 20 个左右的 IP 地址,超过这个数字就会转换为 TCP 协议,客户端不做处理就会报错,导致客户端启动失败。

当时由于情况紧急,只能建立多个 Twemproxy Group,提供多个 DNS A Record 给客户端,客户端进行轮询或者随机选择,该方案可用,但是不够优雅。

如何解决 Twemproxy 单 CPU 计算能力的限制?

之后我们修改了 Twemproxy 源码, 加入 SO_REUSEPORT 支持。



Twemproxy with SO_REUSEPORT on Kubernetes

同一个容器内由 Starter 启动多个 Twemproxy 实例并绑定到同一个端口,由操作系统进行负载均衡,对外仍然暴露一个端口,但是内部已经由系统均摊到了多个 Twemproxy 上。

同时 Starter 会定时去每个 Twemproxy 的 stats 端口获取 Twemproxy 运行状态进行聚合,此外 Starter 还承载了信号转发的职责。

原有的Agent 不需要用来启动 Twemproxy 实例,所以 Monitor 调用 Starter 获取聚合后的 stats 信息进行差值计算,最终对外界暴露出实时的运行状态信息。

为什么没有使用官方 Redis 集群方案?

我们在 2015 年调研过多种集群方案,综合评估多种方案后,最终选择了看起来较为陈旧的 Twemproxy 而不是官方 Redis 集群方案与 Codis,具体原因如下:

MIGRATE 造成的阻塞问题:Redis 官方集群方案使用 CRC16 算法计算哈希值并将 Key 分散到 16384 个 Slot 中,由使用方自行分配 Slot 对应到每个分片中。

扩容时由使用方自行选择 Slot 并对其进行遍历,对 Slot 中每一个 Key 执行 MIGRATE 命令进行迁移。

调研后发现,MIGRATE 命令实现分为三个阶段:

  • DUMP 阶段:由源实例遍历对应 Key 的内存空间,将 Key 对应的 Redis Object 序列化,序列化协议跟 Redis RDB 过程一致。
  • RESTORE 阶段:由源实例建立 TCP 连接到对端实例,并将 DUMP 出来的内容使用 RESTORE 命令到对端进行重建,新版本的 Redis 会缓存对端实例的连接。
  • DEL 阶段(可选):如果发生迁移失败,可能会造成同名的 Key 同时存在于两个节点。

此时 MIGRATE 的 REPLACE 参数决定是否覆盖对端的同名 Key,如果覆盖,对端的 Key 会进行一次删除操作,4.0 版本之后删除可以异步进行,不会阻塞主进程。

经过调研,我们认为这种模式并不适合知乎的生产环境。Redis 为了保证迁移的一致性, MIGRATE 所有操作都是同步操作,执行 MIGRATE 时,两端的 Redis 均会进入时长不等的 BLOCK 状态。

对于小 Key,该时间可以忽略不计,但如果一旦 Key 的内存使用过大,一个 MIGRATE 命令轻则导致 P95 尖刺,重则直接触发集群内的 Failover,造成不必要的切换。

同时,迁移过程中访问到处于迁移中间状态的Slot 的 Key 时,根据进度可能会产生 ASK 转向,此时需要客户端发送 ASKING 命令到 Slot 所在的另一个分片重新请求,请求时延则会变为原来的两倍。

同样,方案初期时的 Codis 采用的是相同的 MIGRATE 方案,但是使用 Proxy 控制 Redis 进行迁移操作而非第三方脚本(如 redis-trib.rb),基于同步的类似 MIGRATE 的命令,实际跟 Redis 官方集群方案存在同样的问题。

对于这种 Huge Key 问题决定权完全在于业务方,有时业务需要不得不产生 Huge Key 时会十分尴尬,如关注列表。

一旦业务使用不当出现超过 1MB 以上的大 Key 便会导致数十毫秒的延迟,远高于平时 Redis 亚毫秒级的延迟。

有时,在 Slot 迁移过程中业务不慎同时写入了多个巨大的 Key 到 Slot 迁移的源节点和目标节点,除非写脚本删除这些 Key ,否则迁移会进入进退两难的地步。

对此,Redis 作者在 Redis 4.2 的 roadmap[5] 中提到了 Non blocking MIGRATE。

但是截至目前,Redis 5.0 即将正式发布,仍未看到有关改动,社区中已经有相关的 Pull Request [6],该功能可能会在 5.2 或者 6.0 之后并入 Master 分支,对此我们将持续观望。

缓存模式下高可用方案不够灵活:还有,官方集群方案的高可用策略仅有主从一种,高可用级别跟 Slave 的数量成正相关。

如果只有一个 Slave,则只能允许一台物理机器宕机,Redis 4.2 roadmap 提到了 cache-only mode,提供类似于 Twemproxy 的自动剔除后重分片策略,但是截至目前仍未实现。

内置 Sentinel 造成额外流量负载:另外,官方 Redis 集群方案将 Sentinel 功能内置到 Redis 内,这导致在节点数较多(大于 100)时在 Gossip 阶段会产生大量的 PING/INFO/CLUSTER INFO 流量。

根据 issue 中提到的情况,200 个使用 3.2.8 版本节点搭建的 Redis 集群,在没有任何客户端请求的情况下,每个节点仍然会产生 40Mb/s 的流量。

虽然到后期 Redis 官方尝试对其进行压缩修复,但按照 Redis 集群机制,节点较多的情况下无论如何都会产生这部分流量,对于使用大内存机器但是使用千兆网卡的用户这是一个值得注意的地方。

Slot 存储开销:最后,每个Key 对应的 Slot 的存储开销,在规模较大的时候会占用较多内存,4.x 版本以前甚至会达到实际使用内存的数倍。

虽然 4.x 版本使用 rax 结构进行存储,但是仍然占据了大量内存,从非官方集群方案迁移到官方集群方案时,需要注意这部分多出来的内存。

总之,官方 Redis 集群方案与 Codis 方案对于绝大多数场景来说都是非常优秀的解决方案。

但是我们仔细调研发现并不是很适合集群数量较多且使用方式多样化的我们,场景不同侧重点也会不一样,但在此仍然要感谢开发这些组件的开发者们,感谢你们对 Redis 社区的贡献。

扩容

静态扩容

对于单机实例,如果通过调度器观察到对应的机器仍然有空闲的内存,我们仅需直接调整实例的 maxmemory 配置与报警即可。

同样,对于集群实例,我们通过调度器观察每个节点所在的机器,如果所有节点所在机器均有空闲内存,我们会像扩容单机实例一样直接更新 maxmemory 与报警。

动态扩容

但是当机器空闲内存不够,或单机实例与集群的后端实例过大时,无法直接扩容,需要进行动态扩容:

  • 对于单机实例,如果单实例超过 30GB 且没有如 sinterstore 之类的多 Key 操作,我们会将其扩容为集群实例。
  • 对于集群实例,我们会进行横向的重分片,我们称之为 Resharding 过程。



Resharding 过程

原生 Twemproxy 集群方案并不支持扩容,我们开发了数据迁移工具来进行 Twemproxy 的扩容,迁移工具本质上是一个上下游之间的代理,将数据从上游按照新的分片方式搬运到下游。

原生 Redis 主从同步使用 SYNC/PSYNC 命令建立主从连接,收到 SYNC 命令的 Master 会 fork 出一个进程遍历内存空间生成 RDB 文件并发送给 Slave。

期间所有发送至 Master 的写命令在执行的同时都会被缓存到内存的缓冲区内,当 RDB 发送完成后,Master 会将缓冲区内的命令及之后的写命令转发给 Slave 节点。

我们开发的迁移代理会向上游发送 SYNC 命令模拟上游实例的 Slave,代理收到 RDB 后进行解析。

由于 RDB 中每个 Key 的格式与 RESTORE 命令的格式相同,所以我们使用生成 RESTORE 命令按照下游的 Key 重新计算哈希并使用 Pipeline 批量发送给下游。

等待 RDB 转发完成后,我们按照新的后端生成新的 Twemproxy 配置,并按照新的 Twemproxy 配置建立 Canary 实例。

从上游的 Redis 后端中取 Key 进行测试,测试 Resharding 过程是否正确,测试过程中的 Key 按照大小,类型,TTL 进行比较。

测试通过后,对于集群实例,我们使用生成好的配置替代原有 Twemproxy 配置并 restart/reload Twemproxy 代理。

我们修改了 Twemproxy 代码,加入了 config reload 功能,但是实际使用中发现直接重启实例更加可控。

而对于单机实例,由于单机实例和集群实例对于命令的支持不同,通常需要和业务方确定后手动重启切换。

由于 Twemproxy 部署于 Kubernetes ,我们可以实现细粒度的灰度,如果客户端接入了读写分离,我们可以先将读流量接入新集群,最终接入全部流量。

这样相对于 Redis 官方集群方案,除在上游进行 BGSAVE 时的 fork 复制页表时造成的尖刺以及重启时造成的连接闪断,其余对于 Redis 上游造成的影响微乎其微。

这样扩容存在的问题:

对上游发送 SYNC 后,上游fork 时会造成尖刺:对于存储实例,我们使用Slave 进行数据同步,不会影响到接收请求的 Master 节点。

对于缓存实例,由于没有 Slave 实例,该尖刺无法避免,如果对于尖刺过于敏感,我们可以跳过 RDB 阶段,直接通过 PSYNC 使用最新的 SET 消息建立下游的缓存。

切换过程中有可能写到下游,而读在上游:对于接入了读写分离的客户端,我们会先切换读流量到下游实例,再切换写流量。

一致性问题:两条具有先后顺序的写同一个 Key 命令在切换代理后端时会通过 1)写上游同步到下游 2)直接写到下游两种方式写到下游。

此时,可能存在应先执行的命令却通过 1)执行落后于通过 2)执行,导致命令先后顺序倒置。

这个问题在切换过程中无法避免,好在绝大部分应用没有这种问题,如果无法接受,只能通过上游停写排空 Resharding 代理保证先后顺序。

官方 Redis 集群方案和 Codis 会通过 blocking 的 MIGRATE 命令来保证一致性,不存在这种问题。

实际使用过程中,如果上游分片安排合理,可实现数千万次每秒的迁移速度,1TB 的实例 Resharding 只需要半小时左右。

另外,对于实际生产环境来说,提前做好预期规划比遇到问题紧急扩容要快且安全得多。

旁路分析

由于生产环境调试需要,有时会需要监控线上 Redis 实例的访问情况,Redis 提供了多种监控手段,如 MONITOR 命令。

但由于 Redis 单线程的限制,导致自带的 MONITOR 命令在负载过高的情况下会再次跑高 CPU,对于生产环境来说过于危险。

而其余方式如 Keyspace Notify 只有写事件,没有读事件,无法做到细致的观察。

对此我们开发了基于 libpcap 的旁路分析工具,系统层面复制流量,对应用层流量进行协议分析,实现旁路 MONITOR,实测对于运行中的实例影响微乎其微。

同时对于没有 MONITOR 命令的 Twemproxy,旁路分析工具仍能进行分析。

由于生产环境中绝大部分业务都使用 Kubernetes 部署于 Docker 内 ,每个容器都有对应的独立 IP。

所以可以使用旁路分析工具反向解析找出客户端所在的应用,分析业务方的使用模式,防止不正常的使用。

将来的工作

由于 Redis 5.0 发布在即,4.0 版本趋于稳定,我们将逐步升级实例到 4.0 版本,由此带来的如 MEMORY 命令、Redis Module 、新的 LFU 算法等特性无论对运维方还是业务方都有极大的帮助。

最后

知乎架构平台团队是支撑整个知乎业务的基础技术团队,开发和维护着知乎几乎全量的核心基础组件。

包括容器、Redis、MySQL、Kafka、LB、HBase 等核心基础设施,团队小而精,每个同学都独当一面负责上面提到的某个核心系统。

随着知乎业务规模的快速增长,以及业务复杂度的持续增加,团队面临的技术挑战也越来越大。

参考资料:

  • Redis Official site

https://redis.io/

  • Twemproxy Github Page

https://github.com/twitter/twemproxy

  • Codis Github Page

https://github.com/CodisLabs/codis

  • SO_REUSEPORT Man Page

http://man7.org/linux/man-pages/man7/socket.7.html

  • Kubernetes

https://kubernetes.io/

作者:陈鹏

简介:现知乎存储平台组 Redis 平台技术负责人,2014 年加入知乎技术平台组从事基础架构相关系统的开发与运维,从无到有建立了知乎 Redis 平台,承载了知乎高速增长的业务流量。