本文根据vivo使用腾讯云产品的实践总结而来,感谢团队同学 LiGuolin 主笔贡献。
背景
突如起来的新冠疫情让各大公司年底欢聚一堂的年会变得遥遥无期,采用线上直播年会的方式成为一种“刚需”。下面主要分享下vivo公司2020年线上年会直播的技术方案实践和思考,本文主要阐述公司年会直播对比普通的直播,在技术上需要解决的问题以及优化方案,希望能为大家带来一些思路。
1.公司年会直播需求
公司年会直播需求背景描述
- 支持4G/5G手机端观看,公网观看无限制
- 支持公司内网观看,支持公司有线设备无线设备观看,公司内网观看人数需要支持10000人 同时在线观看,公司员工分布在国内各大城市
- 支持三种不同的清晰度观看,4k,2k,1k三种不同的码率,默认使用2k码率观看
- 支持Mac,Windows,手机等终端进行观看,内网观看要求直播画面流畅不卡顿
- 支持海外员工观看
相比于其他场景的直播,公司年会直播有上述几个要求,通过这些业务方的要求,我们很快地识别出,其中有几个技术难点:
- 在公司出口带宽有上限的情况下,数千员工同时通过公司出口网络去拉流,公司出口带宽将很快被打满,势必有部分用户播放卡顿,或者无法正确加载直播画面,甚至导致公司其他业务受网络影响无法正常提供服务
- 同一个观看页面,需要支持内网和外网同时观看
- 需要支持海外员工观看,海外访问国内直播源质量差
- 推流单链路,上行推流异常无法进行容灾切换
- IOS浏览器默认不支持flv格式的播放
2.解决方案
针对上述的问题,我们咨询了公司内部相关的直播专家和腾讯云相关技术专家,我们制定了公司内部直播相关的解决方案
1. 在各个地区分公司,搭建内网直播流代理服务器,拉取公网CDN的直播流数据进行内部分发。公网观看的用户,通过DNS域名解析,解析到腾讯服务器地址,获取到公网CDN服务节点IP地址进行直播观看,内网观看用户通过公司内部的智能DNS解析服务器,解析到公司内部服务器地址,这样就能够做到同一个域名,背后服务的节点却不一样,上层业务无感知。为了较低的时延,采用flv格式拉流播放。
2. 内网直播流代理服务器匹配各大直播厂商标准的直播播放地址格式,生成相同的拉流观看地址,播放端无需正对直播地址做兼容。
3. 开通腾讯云直播海外加速,海外回国内直播服务源站走物理专线,保障链路质量。
4. 直播现场默认通过腾讯推送一主一备两路流数据到腾讯云直播,主备流分别走不同设备和不同网络链路,实现设备、网络、上行直播服务接入点冗余容灾。
5. IOS端默认返回HLS的数据格式进行观看,调查了很多能够播放FLV格式的播放器,对IOS浏览器都不是支持的很好。
整体的架构图如下图所示:
那么上述部署架构说明:
● 多个办公区分散部署直播拉流服务器为直播高可用保障提供操作空间
● 采用直播代理拉流设计解决了内网观看直播年会打满网络出口带宽的问题
● 采用公网和内网拉流两路拉流设计满足了不在办公网环境员工的观看需求
● 现场采用第三方专业三网聚合备保障了推流端的稳定可靠
● 员工观看直播时只从本地区拉流代理服务器拉流。这个是如何做到的呢?可以通过运维侧的智能解析配置实现、即城市A办公区员工电脑观看,直播域名解析到的地址是城市A的直播代理服务器,城市B办公区员工电脑观看,直播域名解析到的地址是城市B的直播代理服务器。这样既解决了性能问题,也解决了直播观看体验问题。因为拉流服务器拉流会从本地区的腾讯CDN节点拉流,其实相当于在公司办公区搭建了一层直播CDN节点。拉流代理服务器默认是DNS解析获取服务端地址,需要注意服务器上配置dns应为所在地 运营商网络出口的localdns,可以正常获取就近的直播cdn节点ip访问。
● 直播代理服务器采用LVS(主备)负载均衡 NGINX 直播拉流服务的部署思路,同时我们也做了域名负载均衡的降级方案,确保4层负载均衡出现问题时直接切换VIP至各个直播代理服务器上
● 当然在正式直播时也对各个办公区和各个直播代理服务器进行了压测、容量评估及必要的监控
● 直播推流端,直播采用的是三方供应商提供的舞台、采集、导播和推流整体方案,供应商将年会现场画面推流至腾讯云,网络出口有电信、移动、联通专线,还有5G移动网络,4线路冗余。
3.详细部署说明
针对刚才的方案,我们给出具体的操作实时步骤,给大家进行参考
3.1 部署LVS主备集群
代码语言:shell复制#安装LVS MASTER和BACKUP节点
yum -y install ipvsadm
yum -y install keepalived
systemctl enable ipvsadm
systemctl enable keepalived
modprobe -r ip_vs_wr
modprobe -r ip_vs_r
modprobe -r ip_vs
modprobe ip_vs
modprobe ip_vs_r
modprobe ip_vs_wr
ethtool -k eth0|grep offload
ethtool -K eth0 lro off
ethtool -K eth0 gro off
ethtool -g eth0
ethtool -G eth0 rx 4096
ethtool -g eth0
service keepalived start
#修改/etc/keepalived/keepalived.conf 配置,假设VIP地址为:112.25.102.239,RS地址为:112.25.102.72和112.25.102.73,则配置如下:
#配置LVS MASTER节点
! Configuration File for keepalived
global_defs {
router_id lvs_112.25.102.239
}
vrrp_instance VI_1 { #vrrp实例定义部分
state MASTER #设置lvs的状态,MASTER和BACKUP两种,必须大写
interface eth0 #设置对外服务的接口
virtual_router_id 109 #设置虚拟路由标示,这个标示是一个数字,同一个vrrp实例使用唯一标示
priority 100 #定义优先级,数字越大优先级越高,在一个vrrp——instance下,master的优先级必须大于backup
advert_int 1 #设定master与backup负载均衡器之间同步检查的时间间隔,单位是秒
authentication { #设置验证类型和密码
auth_type PASS #主要有PASS和AH两种
auth_pass 1111 #验证密码,同一个vrrp_instance下MASTER和BACKUP密码必须相同
}
virtual_ipaddress { #设置虚拟ip地址,可以设置多个,每行一个
112.25.102.239
}
}
virtual_server 112.25.102.239 80 { #设置虚拟服务器,需要指定虚拟ip和服务端口
delay_loop 6 #健康检查时间间隔
lb_algo wrr #负载均衡调度算法
lb_kind DR #负载均衡转发规则
#persistence_timeout 50 #设置会话保持时间,对动态网页非常有用
protocol TCP #指定转发协议类型,有TCP和UDP两种
real_server 112.25.102.72 80 { #配置服务器节点1,需要指定real server的真实IP地址和端口
weight 1 #设置权重,数字越大权重越高
TCP_CHECK { #realserver的状态监测设置部分单位秒
connect_timeout 8 #连接超时为10秒
retry 3 #重连次数
delay_before_retry 3 #重试间隔
connect_port 80 #连接端口为81,要和上面的保持一致
}
}
real_server 112.25.102.73 80 { #配置服务器节点1,需要指定real server的真实IP地址和端口
weight 1 #设置权重,数字越大权重越高
TCP_CHECK { #realserver的状态监测设置部分单位秒
connect_timeout 8 #连接超时为10秒
retry 3 #重连次数
delay_before_retry 3 #重试间隔
connect_port 80 #连接端口为81,要和上面的保持一致
}
}
}
#配置LVS BACKUP节点
! Configuration File for keepalived
global_defs {
router_id lvs_112.25.102.239
}
vrrp_instance VI_1 { #vrrp实例定义部分
state BACKUP #设置lvs的状态,MASTER和BACKUP两种,必须大写
interface eth0 #设置对外服务的接口
virtual_router_id 109 #设置虚拟路由标示,这个标示是一个数字,同一个vrrp实例使用唯一标示
priority 100 #定义优先级,数字越大优先级越高,在一个vrrp——instance下,master的优先级必须大于backup
advert_int 1 #设定master与backup负载均衡器之间同步检查的时间间隔,单位是秒
authentication { #设置验证类型和密码
auth_type PASS #主要有PASS和AH两种
auth_pass 1111 #验证密码,同一个vrrp_instance下MASTER和BACKUP密码必须相同
}
virtual_ipaddress { #设置虚拟ip地址,可以设置多个,每行一个
112.25.102.239
}
}
virtual_server 112.25.102.239 80 { #设置虚拟服务器,需要指定虚拟ip和服务端口
delay_loop 6 #健康检查时间间隔
lb_algo wrr #负载均衡调度算法
lb_kind DR #负载均衡转发规则
#persistence_timeout 50 #设置会话保持时间,对动态网页非常有用
protocol TCP #指定转发协议类型,有TCP和UDP两种
real_server 112.25.102.72 80 { #配置服务器节点1,需要指定real server的真实IP地址和端口
weight 1 #设置权重,数字越大权重越高
TCP_CHECK { #realserver的状态监测设置部分单位秒
connect_timeout 8 #连接超时为10秒
retry 3 #重连次数
delay_before_retry 3 #重试间隔
connect_port 80 #连接端口为81,要和上面的保持一致
}
}
real_server 112.25.102.73 80 { #配置服务器节点1,需要指定real server的真实IP地址和端口
weight 1 #设置权重,数字越大权重越高
TCP_CHECK { #realserver的状态监测设置部分单位秒
connect_timeout 8 #连接超时为10秒
retry 3 #重连次数
delay_before_retry 3 #重试间隔
connect_port 80 #连接端口为81,要和上面的保持一致
}
}
}
#配置生效
service keepalived stop
service keepalived start
3.2 nginx的配置
代码语言:text复制#修改配置,假设内网观看的域名为 live.abc.com 本机地址为:112.25.102.85,则在/etc/nginx/conf.d/config目录下新建文件:live.abc.com.conf , 配置为:
server {
listen 80;
server_name live.abc.com;
charset utf-8;
access_log /data/log/nginx/log/ng/ng.log cookie;
location / {
proxy_pass http://112.25.102.85:8888;
}
}
3.3 内网拉流服务器配置
我们内网拉流节点服务器是使用Docker进行部署,我们首先先拉取一个公有的docker镜像,主要是nginx-http-flv的镜像,并且在此基础上,再进行部分nginx-rtmp模块的配置,统一修改好配置之后,重新生成新的镜像,然后将此进行可以快速地复制到其他分公司的内部服务器节点,这样就可以节省比较多的部署时间
docker部署完整的配置如下,有几个需要注意的点,就是需要指定配置docker的dns解析服务器的地址,例如114.114.114.114,否则就会默认走到内网智能dns解析,这样就会形成一个死循环,无法从公网进行拉流
代码语言:shell复制#拉取一个公有镜像
docker pull mugennsou/nginx-http-flv
#启动docke
docker exec nginx-http-flv bash -c "echo 'domain vivo.xyz' >
/etc/resolv.conf && echo 'nameserver 114.114.114.114' >>
/etc/resolv.conf && echo 'nameserver 8.8.8.8' >>
/etc/resolv.conf && echo 'search vivo.xyz' >>
/etc/resolv.conf && /usr/local/nginx/sbin/nginx"
3.4 docker容器中nginx-rtmp配置
这边需要注意的就是新增nginx-rtmp模块,直播流已经经过腾讯云直播转码,所以默认我们需要拉取三路已经转码之后的三路不同清晰度的流
代码语言:text复制#user nobody;
worker_processes auto;
events {
worker_connections 10240;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
server {
listen 8888;
server_name localhost;
location / {
root html;
index index.html index.htm;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
#支持不同的分辨率
location = /live-nh/nh_1080P.flv {
proxy_pass http://127.0.0.1:8888/live?app=vivo_party_live_1080P&stream=test01;
}
location = /live-nh/nh_720P.flv {
proxy_pass http://127.0.0.1:8888/live?app=vivo_party_live_720P&stream=test02;
}
location = /live-nh/nh_540P.flv {
proxy_pass http://127.0.0.1:8888/live?app=vivo_party_live_540P&stream=test03;
}
location /live {
flv_live on; #open flv live streaming (subscribe)
chunked_transfer_encoding on; #open 'Transfer-Encoding: chunked' response
add_header 'Access-Control-Allow-Origin' '*'; #add additional HTTP heade
add_header 'Access-Control-Allow-Credentials' 'true'; #add additional HTTP heade
}
location /hls {
types {
application/vnd.apple.mpegurl m3u8;
video/mp2t ts;
}
root /tmp;
add_header 'Cache-Control' 'no-cache';
}
location /stat {
#configuration of streaming & recording statistics
rtmp_stat all;
rtmp_stat_stylesheet stat.xsl;
}
location /stat.xsl {
root .; #specify in where stat.xsl located
}
location /control {
rtmp_control all; #configuration of control module of rtmp
}
}
}
rtmp_auto_push on;
rtmp_auto_push_reconnect 1s;
rtmp_socket_dir /tmp;
rtmp {
out_queue 4096;
out_cork 8;
max_streams 2560;
timeout 5s;
drop_idle_publisher 5s;
log_interval 5s; #interval used by log module to log in access.log, it is very useful for debug
log_size 1m; #buffer size used by log module to log in access.log
server {
listen 1935;
application party_live_1080P {
live on;
pull rtmp://live.abc.com/live-nh/nh_1080P;
gop_cache on; #open GOP cache for reducing the wating time for the first picture of video
}
application party_live_720P {
live on;
pull rtmp://live.abc.com/live-nh/nh_720P;
gop_cache on; #open GOP cache for reducing the wating time for the first picture of video
}
application party_live_540P {
live on;
pull rtmp://live.abc.com/live-nh/nh_540P;
gop_cache on; #open GOP cache for reducing the wating time for the first picture of video
}
application hls {
live on;
hls on;
hls_path /tmp/hls;
}
}
}
3.5 rs配置关联VIP
rs配置关联VIP,同时添加路由规则限制本地访问VIP网络包
代码语言:shell复制#保存以下操作为rs.sh 并执行
#!/bin/bash
SNS_VIP=112.25.102.239
/etc/rc.d/init.d/functions
case "$1" in
start)
ifconfig lo:0 $SNS_VIP netmask 255.255.255.255 broadcast $SNS_VIP
/sbin/route add -host $SNS_VIP dev lo:0
echo "1" >/proc/sys/net/ipv4/conf/lo/arp_ignore
echo "2" >/proc/sys/net/ipv4/conf/lo/arp_announce
echo "1" >/proc/sys/net/ipv4/conf/all/arp_ignore
echo "2" >/proc/sys/net/ipv4/conf/all/arp_announce
sysctl -p >/dev/null 2>&1
echo "RealServer Start OK"
;;
stop)
ifconfig lo:0 down
route del $SNS_VIP >/dev/null 2>&1
echo "0" >/proc/sys/net/ipv4/conf/lo/arp_ignore
echo "0" >/proc/sys/net/ipv4/conf/lo/arp_announce
echo "0" >/proc/sys/net/ipv4/conf/all/arp_ignore
echo "0" >/proc/sys/net/ipv4/conf/all/arp_announce
echo "RealServer Stoped"
;;
*)
echo "Usage: $0 {start|stop}"
exit 1
esac
exit 0
参数说明:
arp_ignore:用来配置对arp请求的响应模式,0 表示回应任何网络接口上对任何本地IP地址的arp查询请求
arp_announce:用来配置发送arp请求的模式,0标识使用任何interface上的任何本地地址,在此模式下无论使用哪个接口发送arp请求,arp请求包里的源ip地址在arp层不会做修改,也就是arp请求包里的源ip地址为即将发送ip数据包的源ip地址。
3.6 查看LVS状态命令
代码语言:shell复制#ipvsadm -Ln
IP Virtual Server version 1.2.1 (size=4194304)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
TCP 112.25.102.239:80 wr
-> 112.25.102.72:80 Route 1 0 0
-> 112.25.102.73:80 Route 1 0 0
#查看当前LVS MASTER节点是哪个
#ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft foreve
2: ens192: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
link/ether 00:50:56:be:3b:fa brd ff:ff:ff:ff:ff:ff
inet 112.25.102.84/24 brd 112.25.102.255 scope global eth0
valid_lft forever preferred_lft foreve
inet 112.25.102.239/32 scope global eth0
valid_lft forever preferred_lft forever
4.直播相关的监控
搭建好上述步骤之后,我们进行单个地区的测试,发现系统稳定,方案可行,内网拉流的流畅度和清晰度都符合预期的效果,接下来就是需要做好实时的监控,经过我们梳理,我们确认从四个维度对直播流媒体服务器进行监控,确保出线问题的时候,我们能够及时发现问题
首先,我们要确认需要监控的点,有几个重点的监控点:
- 每一个地区拉流服务器的网络状态,如果主动上网存在问题,或者dns解析有问题,需要提前发现问题,因为腾讯分配CDN节点提供服务的逻辑,是根据dns解析服务器所在的网络运营商和所属地区返回最佳的CDN节点的,所以我们依旧需要查看地区是否正确
- 监控nginx的服务是否正常提供服务,这是最基本的服务确认,我们要确保每一个地区的每一个服务节点,都是正常提供服务
- 监控LVS服务器状态,我们能够监控LVS负载的每一个节点的连接数,是否达到预期的负载均衡,链接数是否达到预期效果
- 服务器心跳监控,确保每一台机器都在正常服役,因为是内网服务器,网络比较复杂,随时会有内网网络调整,确保每一个时刻网络服务和拉流服务都是正常的
4.1监控流媒体服务器网络状态
监控流媒体服务器网络状态是否正常。(包括主动上网)状态是否正常
代码语言:shell复制#!/bin/bash
svr_list=("ngx_tianjin01" "ngx_tianjin02" "ngx_sichuan01" "ngx_sichuan02" "ngx_haerbin01" "ngx_henan02" "ngx_shanghai01" "ngx_hebei02" "ngx_france01" "ngx_german02")
for element in ${svr_list[@]}
do
echo "======> server: "$element
#curl cip.cc
#远程调用服务器上面的执行命令程序,并实时得到结果
done
4.2监控nginx服务状态
监控nginx服务状态,以及服务器上面流的基本状态,确保服务可用。
代码语言:shell复制#!/bin/bash
str1="http://"
srt2=":8888/stat"
str3="HTTP/1.1 200 OK"
let flag=0
while true
do
echo -e "