Feb14

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

Author: 陈厚道  Click: 5412   Date: 2017.02.14 @ 14:20:13 pm Category: 架构

# 背景

# 关键设计点

## 模块化

## 资源隔离

## 权限控制

### 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经过的每个节点,都需要有规划统一的格式,并上报给大数据平台;