Feb14

【转载】斗鱼直播平台后端RPC架构浅析

Author: 陈厚道  Click: 7607   Comments: 0 Category: 架构  Tag: douyu,斗鱼,rpc

# 背景

# 关键设计点

## 模块化

## 资源隔离

## 权限控制

### RPC框架的需求分析和概要设计

#### 发展与现状

- RPC框架指的是能够完成RPC调用的解决方案,除了点对点的RPC协议的具体实现之外,还可以包含服务的发现与注销,提供服务的多台Server的负载均衡、服务的高可用等更多的功能,目前的RPC框架大致有两种不同的侧重方向,一种偏重于服务治理,另一种偏重于跨语言调用。

- 开源的RPC框架介绍:Dubbo、DubboX、Thrift、Motan。其中Dubbo,Dubbox,motan是java生态下的偏向服务治理的RPC框架,Thrift是偏重于跨语言的调用的RPC。

#### RPC框架提供的主要功能

- 服务发现:服务发布、订阅、通知

- 负载均衡:支持一致性Hash、随机请求、轮询、最少连接数优先、低并发优先等分发原则

- 高可用策略:失败重试(FailOver)、快速失败(FailFast)、异常隔离(Server连续失败超过指定次数置为不可用,然后定期进行心跳探测)

- 其他: 调用统计、权限控制、安全、调用链追踪、日志

#### DY RPC框架交互流程1

DY RPC框架中有服务提供方 RPC Server,服务调用方RPC Client,服务注册中心MessageServer三个角色。该框架的RPCServer主要现在用c++写的服务,RPC Client包括php或者RPCServer。

1. RPC Server向MessageServer集群的某个节点B注册服务,并保持长连接。该MessageServe r B节点会通知集群的所有节点。

同时MessageServer B节点也会定时把注册到该节点的RPCServer的服务配置信息同步到 MessageServer集群。

2. RPCClient会连接到MessageServer集群的某个节点A,发起RPC调用。MessageServer A节点会根据RPC调用的参数(服务提供方的ID,GroupID、负载均衡策略等)选择一条合适的

RPC调用链路,比如RPCClient->MessageServerA->MessageServerB->RPCServer,最终到达某个RPCServer,进行函数调用。其中一个RPC调用最多会经过2个MessageServer节点,最少会经过1个MessageServer节点。

3. 当某个RPC Server发生变更时,通过广播的方式,MessageServer集群的所有节点也能比较实时的感知到某个RPCServer发生变更。

TODO RPC流程交互图

#### DY RPC功能模块划分

1. MessageServer在RPC框架这个功能上应该提供的功能,包括服务的注册和发现模块、协议序列化模块、心跳检测模块、负载均衡算法模块,RPC路由模块、失败重试策略模块、超时丢弃策略模块、消息持久化模块。

2. RPCServer要包含RPC治理的组件,主要功能包括RPC的统计、RPC的频率控制、RPC的安全性控制。

##### RPCServer可用性检测模块

每个服务默认都要实现一个类似Ping Pong的 Request和Response,用来给直连RPCServer的MessageServer探测RPCServer是否可用提供依据。不能简单的依赖心跳消息来探测RPCServer是否可用。

##### 负载均衡模块

MessageServer把RPC请求转发给RPCServer Group时,需要支持的负载均衡算法:

1. 随机法(已实现)

2. 轮询法(已实现)目前在生产环境用的这种算法,负载较不均衡。

3. 组内Hash法(已实现)

4. TODO 最少连接法 (最靠谱的负载均衡做法)

斗鱼采用的这些负载均衡算法可以参考这篇微信文章的介绍:http://mp.weixin.qq.com/s/PAOvmzraVlAMECL-PZs2Pg

看服务器响应自己请求的速度就可以判断应该把下一个请求发到哪个服务器端。

具体说是选择活动请求(已经发出去的请求收到响应)数目最少的那个服务端。 只要根据自己以往的调用情况就能做出判断。

5. TODO: 目前的消息系统只支持点到点、点到组。目前还暂不支持点到组内的某个节点的负载均衡算法。

##### 失败重试策略模块

在RPCClient直连的MessageServer上实现RPC失败重试的策略。

- 只有幂等的RPC调用才能重试。

##### 超时丢弃策略模块

在RPCServer的业务层实现超时丢弃的策略,应用场景:发送火箭超时时,客户端提示发送失败,其实是在鱼翅交易服务器出现性能抖动导致。最后的结果就是鱼翅服务器扣除了鱼翅,但是客户端提示发送火箭失败,比较严重的情况是,用户以为提示失败时不会消耗鱼翅,所以不断重新发送火箭。

针对这种类型的RPC,RPCServer的业务层可以根据RPC的配置规则+RPC发起时间来决定是否直接丢弃该RPC。

##### 消息持久化模块

- 在调用RPC时如果指定可达时,才触发消息持久化的机制。

- 因为RPC的调用链最多需要经过4个节点(RPCClient->MessageServerA->MessageServerB->RPCServer),导致RPC不可达到的情况较为复杂,如果采用自研的方案做消息持久化的话,我们可以假设MessageServer的集群比较稳定,RPCServer较不稳定,所以我们持久化的方案是在和RPCServer直连的MessageServer上实现。

- MessageServer上做持久化具体设计要点:

- 正常流程:

- MessageServer将RPC请求转化为消息,以RPCServer的模块id为Key,将消息存入Redis的队列,我们将这个消息称为MessageData;

- 将RPC请求的MessageID作为Key,Value作为保留字段设计,存入Redis的String,我们将这个数据称为MetaData,同时设置这个Key的过期时间为10分钟(暂定),这个操作和上面的操作作为Redis的一个事务来执行;

- 执行完上面的事务后,直接调用RPC的Response,返回给RPCClient;

- RPCServer集群的某个节点从Redis队列取出MessageData,执行RPCHandler。

- 异常流程1:

- 如果在执行RPCHandler的过程中,RPCServer异常,就只会影响一条MessageData。可以通过一些辅助脚本来做补单,考虑一种策略来实现自动化的补单。

- 异常流程2:

- MessageServerA->MessageServerB网络抖动 或者 MessageServerB->Redis的网络抖动都会导致MessageData不能进入队列;

- 在和RPCClient直连的MessageServerA一段时间(先暂定10s)没有收到RPCResponse,就会触发重试机制,重试的上限次数暂定20次,确保整体重试的时间小于MetaData过期的时间就可以,重试流程进入到MessageServerB节点时,如果是重试RPC,查找Redis队列是否有这个MessageData,如果不存在,则执行正常流程。如果存在,则丢弃本次重试,说明上一次重试已经成功了。

##### 增加RPC追踪链日志

- 在RPCClient直连的个MessageServer上给RPC请求赋予一个Global的RPCID;

- RPCID可以从IDMakerServer集群获得,通过一次获得一批ID来获得良好的性能;

- 在RPC经过的每个节点,都需要有规划统一的格式,并上报给大数据平台;

- 在大数据后台,可以根据RPCID查找整个RPC调用链上的信息。

##### RPC治理组件

- RPC调用统计:每个RPC入口增加统计信息,当rpc进入内部业务函数后也有一次统计,统计信息汇入大数据实时统一日志

- RPC频率控制:某个时间单位内,RPC调用不得超过某个数量;如果有超过,则报警。在频率控制粒度方面,采取如下控制策略和监控策略。

- 每个服务的所有RPC在单位时间内的调用频率控制,超出则报警;

- 某个RPC在单位时间内的调用频率控制,超出则报警;

- 定时统计每个Client来源在每个RPC的调用次数,并按照统一格式上传给大数据平台,大数据平台提供按照Client来源、时间查找RPC调用次数的Top 10的类似功能;

- 大数据平台定时对比RPC的历史调用次数和当前调用次数,超过一定的比例就报警。

- RPC安全策略:

- 可以随时关闭某个RPC、某个服务的所有RPC的安全策略;

- ip验证:给一个ip白名单,这个白名单才能发起RPC调用,不建议按照每个RPC调用单独设置ip白名单

- 口令验证:针对某个RPC、某个服务单独设置密码,对大都数服务都设置成统一的密码,不建议针对每个RPC或者每个服务都单独设置密码。因为除了密码,还有一个摘要认证加密算法才能破解RPC的协议。现在密码是运维维护的,摘要认证加密算法是开发维护的。所以不建议对密码的粒度控制得过细

- RPC手动降级:可以随时关闭某个RPC;也可以根据Client来源关闭某个RPC,但对其他Client来源是开启的。

- RPC自动降级: TODO

- 配置文件格式:参考详细设计文档 by 李明

#### 关键数据结构

1. 服务注册协议

```

struct MC_MsgLoginReqNew : public MessageRoot

{

uint8_t  _version;

DWORD    _uid;

char      _user_name[33];

char      _password[33];      //之前的口令字段依旧不使用

int      _module_id          //模块id

};

```

2.RPCClient Request基本结构,同样包括GroupRPCReq(组内随机调用),GroupRPCReq2(组内hash调用)的

```

struct MC_RPC_Req_New : public MessageRoot< MCT_RPC_Req_New, MC_RPC_Req_New >

{

uint8_t _version;        // 版本号

int64 _rpc_global_id;    // 每次调用需要从idMakderServer获得唯一id,RPC追踪链需要依赖该id来识别

int _rpc_option;          // 包括RPC可达,重试,超时丢失等标志,不可叠加

int32 _user_data;          // 自定义用户数据

int _rpc_retry_count;      // 0表示第一次请求,每重试一次+1

int _invoker_id;          // 调用者的ID

int _invoker_moudule_id;  // 调用者的模块id

uint32_t _invoker_ip;      // 调用者的ip

int _call_token;        //调用标识,由调用者设置,返回结果时必须携带此token,否则调用者无法区分是哪一次调用

int _recvier_id;      // 接收者的serverID

uint32_t _req_time    // 请求时间戳

int64_t _random_num;  // 随机数 没有口令配置此项可以不填

uchar _password[32];  // 口令,由 随机数+ 模块id+ 函数名+ 配置文件的口令+ 时间戳 的字符串 一次md5获得,服务器使用同字段md5对比校验,没有口令配置此项留空即可

char _func_name[128]; //函数名

char _text_data[1];  //SttEncoding存储函数体,包括函数名、参数名/参数值

};

```

> _rpc_global_id,_invoker_moudule_id,_invoker_ip这个由调用方直连的第一个MessageServer直接赋值

> _version,_req_time,_random_num,_password,由RPCClient生成,RPCServer校验

> _rpc_retry_count,表示重试的次数,这个由调用方直连的第一个MessageServer发起重试策略时+1

> _rpc_option,包括RPC可达,重试,超时丢弃等标志,现在不可叠加,以后可支持叠加,常见的场景是:

1. RPCClient不太关注返回结果的、关键数据更新类的RPC,建议指定RPC可达。

2. RPCClient非常关注返回结果提示,但又不支持重试的(非幂等RPC),建议指定超时丢弃标志

3. RPCClient非常关注返回结果提示,该RPC又支持幂等,建议指定重试标志

4. _user_data:根据不同的RPC标志,可以指定特定的含义.比如指定最大重试次数或者超时丢弃的时间

> GroupRPCReq(组内随机调用),GroupRPCReq2(组内hash调用)的数据结构也需要同时更新。

3. RPCServer Response基本结构

```

struct RPC_RespNew

{

uint8_t _version;      // 版本号

int64 _rpc_global_id;  // RPC全局唯一id

int recvier_id;        // 接收者的ID;如果是按组接收,此值由MessageServer修改为具体的接收者ID

int invoker_id;        // 调用者的ID

int call_token;        //调用标识,由调用者设置,返回结果时必须携带此token,否则调用者无法区分是哪一次调用

char text_data[1];      //SttEncoding存储调用结果

}

```

#### 以送火箭场景为场景描述架构重构的思路

1. php调用发送火箭RPC接口给鱼翅交易服务器,鱼翅服务器完成RPC调用,并且是把这个消息发送给所有的ChatRoom。

2. 鱼翅交易服务器把发送火箭这个RPC封装成NetMessage通过ChatRoom发送给RoomMaster,RoomMaster找到人气值前50的房间,并向人气值前50的房间的ChatRoom发送火箭广播的NetMessag,ChatRoom再把广播消息发送给MessageRepeater

3. ChatRoom通过NetMessage把发送火箭这个消息事件发送给排行榜服务器

4. ChatRoom通过NetMessage把发送火箭这个消息事件发送给经验服务器

5. ChatRoom发送创建红包RPC给红包服务器

目前的业务流程的主要弊端如下:

1. ChatRoom和大都数服务耦合非常紧密,据我了解,c++的各个小组经常存在同时维护ChatRoom的情况。

2. 同样,新增一个和送火箭相关的服务,ChatRoom也需要增加相关接口。

3. ChatRoom通过RPC、NetMessage和其他业务交互时,如果网络出现抖动或者其他业务在维护或者不稳定时,都会导致数据的丢失,比较影响用户的体验。

针对送火箭这个业务流程,我个人认为比较优雅的架构(新架构)如下:

1. php调用发送火箭RPC接口给鱼翅交易服务器,鱼翅服务器完成RPC调用

2. 鱼翅交易服务器把发送火箭这个RPC封装成消息事件,发送给消息队列服务器。

3. 红包服务器、经验服务器、排行榜服务器、RoomMaster都通过订阅的方式订阅到了发送火箭这个消息。这些服务器按照自己的业务规则处理该消息事件!

4. RoomMaster找到人气值前50的房间,并向人气值前50的房间的ChatRoom发送火箭广播的NetMessag,ChatRoom再把广播消息发送给MessageRepeater。

新架构的优点如下:

1. ChatRoom和其他服务已经完全解耦。

2. 如果新增一个和送火箭相关的服务,鱼翅服务器的逻辑也不用调整。新增的服务只需要订阅送火箭的消息队列就可以了。

3. 消息队列服务器是一个稳定的第三方服务,基本是不用维护的。其他比如红包服务器、经验服务器、排行榜服务器的不稳定,都不会导致数据的丢失。

老框架迁移到新框架下的推进计划:

1. 先挑选送火箭这个业务进行重构,其他业务流程仍然兼容老的RPC的通信方式;

2. 逐步梳理c++组的业务流程,挑选业务流程逐一进行重构;

3. 第一个业务流程的重构预估时间大概在3周左右,后面的每个业务流程重构预估在1周左右,在3-4个月的时间内梳理完所有流程。

## 当前底层框架可以优化的点

1. MessageServer集群可以优雅的增加、删除、修改(同时删除、增加来实现)节点,现在修改某个节点的ip需要重启整个集群?

2. 把弹幕的MessageServer集群和RPC的MessageServer集群分离

3. 协议序列化框架改成ProtoBuffer,可以逐个协议升级

4. MessageServer的通信链路自动检测,防止出现某个节点异常之后很久才发现

## 统一日志

**TODO:本周5和c++组讨论之后再确定**

## 近期之内主要的工作项

WorkItem | 优先级 | 备注

---|---|---

Feb10

【原创】采用xhgui及tideways搭建PHP性能监控平台

Author: leeon  Click: 5157   Comments: 0 Category: php  Tag: xhgui,xhprof,tideways,php

      xhprof扩展已经三年多没有更新了,PHP7是没法直接使用xhprof来进行性能分析了。好在tideways出品了一款xhprof的进化版php性能分析插件,可以完美支持php7程序。网上关于如何使用tideways和xhgui搭建性能分析平台的教程已经有了很多,部署的话可以参考借鉴这篇文章https://segmentfault.com/a/1190000007580819,但是在搭建过程中还是遇到了一些问题,需要说明一下。

   1. 当git clone https://github.com/perftools/xhgui.git xhguid的源后,注意你的php代码中必须安装mogondb的扩展,否则在php install的时候会出现调用composer安装第三方库失败,composer下载的vendoer资源也是需要依赖pecl的mogondb库的。这点在官方的配置文档中并没有提及。

  2. xhgui在配置nginx的时候官方文档也有些问题,需要注意这里

这里需要修改成:

        location / {

                 try_files $uri $uri/ /index.php?$query_string;

        }

如果不修改这个配置,xhgui里面集成的slim框架无法正常解析到路由数据。
 3.xhgui里面集成的slim框架有一个轻微语法出错,会导致php7下warning级别错误,请修复vendor\slim\views\Slim\Views\Twig.php文件的render方法,父类定义了第二个参数,但继承的类中没有指定导致warning报错,将第二个参数添加上即可:
public function render($template, $data = NULL)

xhgui属于无侵入式的代码性能监控方案,对于现有的代码不需要原先xhprof那样在代码中埋点指定xprof的文件路径,我们仅仅需要在对应的项目工程nginx配置中加入如下的代码:

[code="plain"]
fastcgi_param TIDEWAYS_SAMPLERATE "25";
fastcgi_param PHP_VALUE "auto_prepend_file=/usr/local/nginx/html/xhgui/external/header.php";
[/code]

第二行配置请根据自己的xhgui存放路径进行填写。

Feb5

【原创】Redis与Memcached性能测试对比

Author: leeon  Click: 5620   Comments: 0 Category: 优化  Tag: redis,memcached

      最近一哥们面试一家在线视频直播的公司的PHP岗位,面试官问一题目“redis一定比memcached读写性能更好吗?”,哥们回答是不一定,但是这面试官一口咬定redis性能就是比memcached好,我对这个答案不置可否,我认为应该是看应用场景来判断性能优劣,而不能一口认定redis就比memcached的读写性能要好。

为了真实的反应最新版本的memcached和redis的单实例进程性能,我们做一个比较客观的测试。

测试环境:树莓派3,1G内存,4核 ARMv7

memcached:1.4.34版本,设置内存256M,参数设置为-r 100000 -c 10240 -m 256M -t 4 -b 20480

redis:3.2.7版本,关闭掉数据落地的相关设置,纯粹的测试内存的读写访问性能。

        为了保证对redis和memcached的请求设置一致化,我们采用memtier_benchmark工具来进行性能压力测试。同时保证公平性采用memtier_benchmark默认的压力测试配置参数。

memcached压力测试命令:

./memtier_benchmark -p 11211 -P memcache_binary --hide-histogram

redis压力测试命令:

./memtier_benchmark -p 6379 --hide-histogram

测试结果如下:

redis:

[code="plain"]
root@home2:~/memtier_benchmark# ./memtier_benchmark -p 6379 --hide-histogram
[RUN #1] Preparing benchmark client...
[RUN #1] Launching threads now...
[RUN #1 100%, 76 secs] 0 threads: 2000000 ops, 26604 (avg: 26231) ops/sec, 1010.67KB/sec (avg: 996.38KB/sec), 7.52 (avg: 1.17) msec latencyy

4 Threads
50 Connections per thread
10000 Requests per thread
Type Ops/sec Hits/sec Misses/sec Latency KB/sec
------------------------------------------------------------------------
Sets 2367.43 --- --- 7.63500 182.35
Gets 23648.29 2.60 23645.69 7.61300 805.85
Waits 0.00 --- --- 0.00000 ---
Totals 26015.72 2.60 23645.69 7.61500 988.20

[/code]

memcached:

[code="plain"]
root@home2:~/memtier_benchmark# ./memtier_benchmark -p 11211 -P memcache_binary --hide-histogram
[RUN #1] Preparing benchmark client...
[RUN #1] Launching threads now...
[RUN #1 100%, 54 secs] 0 threads: 2000000 ops, 44100 (avg: 36452) ops/sec, 1.79MB/sec (avg: 1.48MB/sec), 4.52 (avg: 1.18) msec latencyy

4 Threads
50 Connections per thread
10000 Requests per thread
Type Ops/sec Hits/sec Misses/sec Latency KB/sec
------------------------------------------------------------------------
Sets 3325.54 --- --- 5.50400 256.14
Gets 33218.86 3.65 33215.20 5.47500 1261.61
Waits 0.00 --- --- 0.00000 ---
Totals 36544.40 3.65 33215.20 5.47800 1517.75

[/code]

从结果可以看出memcached的随机set get性能并不比redis差,因此我们抱有对memcached因为年久性能不行的固有思维印象是不可取的。memcached的多线程机制在读写交叉的高并发请求下性能或许会比redis要好一些,当然redis的主从机制是memcached未有的。

Feb4

【原创】在树莓派3上给Nginx部署免费HTTPS证书

Author: leeon  Click: 5173   Comments: 0 Category: 网络  Tag: https,nginx,certbot,ssl

    自从家里的电信ADSL好几年前封锁80端口后,http服务已经不能用来作为公网服务直接访问了,但是电信对443端口并没有屏蔽,我们可以通过配置https服务来提供公网服务。最近炒的火热的Let's Encrypt免费CA服务提供商,刚好趁着苹果强制ATS的东风横空出世,得益于Mozilla的支持将免费SSL证书全民普及的概念广而告之,自从去年沃通的免费证书关停等等一系列免费的ssl证书被停止服务,这证书刚好拿来耍耍,体验一下。

   使用Let's Encrypt的免费ssl需要安装官方提供的certbot脚本,这个脚本需要安装树莓派的backports源,我们需要增加对backports源的支持。

修改/et/apt/sources.list文件,在末尾添加

deb http://ftp.debian.org/debian/ jessie-backports main contrib non-free

然后还需要安装debian-keyring,debian-archive-keyring 两个包才能正常,否则在添加backports源后会提示

W: GPG error: http://ftp.debian.org jessie-backports InRelease: The following signatures couldn't be verified because the public key is not available: NO_PUBKEY 8B48AD6246925553 NO_PUBKEY 7638D0442B90D010


执行如上步骤后,最后执行一次apt update来更新源。

接着我们就可以按照官方提供的文档进行操作了。

[code="bash"]
$ sudo apt-get install certbot -t jessie-backports
[/code]

安装certbot工具包成功后,如果想单独生成ssl证书,使用standalone模式即可。certbot也提供了几种不同的生成模式:

Plugin Auth Inst Notes Challenge types (and port)
apache Y Y
Automates obtaining and installing a cert with Apache 2.4 on
Debian-based distributions with libaugeas0 1.0+.
tls-sni-01 (443)
webroot Y N
Obtains a cert by writing to the webroot directory of an
already running webserver.
http-01 (80)
nginx Y Y
Automates obtaining and installing a cert with Nginx. Alpha
release shipped with Certbot 0.9.0.
tls-sni-01 (443)
standalone Y N
Uses a “standalone” webserver to obtain a cert. Requires
port 80 or 443 to be available. This is useful on systems
with no webserver, or when direct integration with the local
webserver is not supported or not desired.
http-01 (80) or tls-sni-01 (443)
manual Y N
Helps you obtain a cert by giving you instructions to perform
domain validation yourself.
http-01 (80) or dns-01 (53)
certbot生成的证书会存在在/etc/letsencrypt/live/(设置证书时绑定的域名目录)下。Let’s Encrypt CA证书仅提供90天有效期,故在此过期时间之前需要定期重新生成一次证书。

在执行certbot renew命令时候程序会自动判断是否过期,但我们可以通过添加--dry-run命令来模拟生成新证书。

如果需要强制重新生成证书则添加 --force-renewal参数执行,但请不要频繁的重新生成证书,这样会受到请求频率限制。

certbot成功生成的文件有四个:

cert.pem 主要针对Apache<2.4.8版本的证书文件

chain.pem 主要针对Apache<2.4.8版本的证书文件

fullchain.pem 证书文件,对应nginx中的ssl_certificate 参数配置,对应Apache >= 2.4.8配置中的SSLCertificateFile参数

privkey.pem 证书的私钥文件,对应nginx配置中的ssl_certificate_key 参数配置 ,对应apache配置中SSLCertificateKeyFile参数

分类

标签

归档

最新评论

Abyss在00:04:28评论了
Linux中ramdisk,tmpfs,ramfs的介绍与性能测试
shallwe99在10:21:17评论了
【原创】如何在微信小程序开发中正确的使用vant ui组件
默一在09:04:53评论了
Berkeley DB 由浅入深【转自架构师杨建】
Memory在14:09:22评论了
【原创】最佳PHP框架选择(phalcon,yaf,laravel,thinkphp,yii)
leo在17:57:04评论了
shell中使用while循环ssh的注意事项

我看过的书

链接

其他

访问本站种子 本站平均热度:8823 c° 本站链接数:1 个 本站标签数:464 个 本站被评论次数:94 次