# **RPC** 的变革 —— **ARPC** 项目自荐
项目地址: [https://github.com/lesismal/arpc](https://github.com/lesismal/arpc)
## 一、**ARPC** 示例
### **echo server**
```golang
package main
import (
"log"
"github.com/lesismal/arpc"
)
func onEcho(ctx *arpc.Context) {
str := ""
err := ctx.Bind(&str)
if err != nil {
log.Printf("/echo error: %v", err)
return
}
ctx.Write(str)
log.Printf("/echo: %v", str)
}
func main() {
svr := arpc.NewServer()
// register handler
svr.Handler.Handle("/echo", onEcho)
svr.Run(":8888")
}
```
### **echo client**
```golang
package main
import (
"context"
"log"
"net"
"time"
"github.com/lesismal/arpc"
)
func dialer() (net.Conn, error) {
return net.DialTimeout("tcp", "localhost:8888", time.Second*3)
}
func main() {
client, err := arpc.NewClient(dialer)
if err != nil {
log.Fatalf("NewClient failed: %v", err)
}
defer client.Stop()
request := "hello"
response := ""
// err = client.Call("/echo", &request, &response, time.Second*5)
err = client.CallWith(context.Background(), "/echo", &request, &response)
if err != nil {
log.Fatalf("Call /echo failed: %v", err)
} else {
log.Printf("Call /echo Response: \"%v\"", response)
}
}
```
## 二、传统主流的 RPC 框架的局限/不爽
### 1. 网络交互模式单一,无法支持更丰富的场景
> 传统主流 **RPC** 的网络交互主要是 **[客户端到服务端,请求-应答]** 的模式,比较单一。按照这个模式以及顾名思义“远程过程调用”,其实 **HTTP** 也算是 **RPC** 的一种,只是由于其短连接和 **HTTP** 协议的文本编码格式等原因导致性能和资源的浪费,所以很少有直接把 **HTTP** 称为 **RPC** 。
> 网络通信的本质是数据收发,客户端和服务端都可以随时主动发送消息给对方,比如**推送服务**、**IM**、**游戏**等;而且一方发送数据给另一方,有时是不需要回复的,比如**VOIP电话**,其他不要求强一致的消息广播、推送等。
> 这里把需要应答的通信定义为 **Call** ,不需要应答的通信定义为 **Notify**,则网络交互按照发起方、是否需要应答,可以分为以下四种基本模式:
>> | 序号 | 交互方式 | 是否需要应答 | 交互流程 |
>> | - | ------ | ----- | ---------- |
>> | 1 | Call | 需要 | 客户端发起请求,服务端应答 |
>> | 2 | Call | 需要 | 服务端发起请求,客户端应答 |
>> | 3 | Notify | 不需要 | 客户端向服务端发出通知/推送 |
>> | 4 | Notify | 不需要 | 客户端向服务端发出通知/推送 |
> 如此看来,传统主流 **RPC** 就像是个大内男公务员,因为它只支持了第一种基本模式,只覆盖了25%——甚至说它的网络通讯模式有点不完整,都算是一种褒奖,因为25%的模式支持那是相当不完整。
> 而只支持请求-应答的模式也限制了很多业务场景,其他更广泛的业务场景比如**推送服务**、**IM**、**游戏**,我们还需要自定义各种协议。
### 2. Server端函数调用的写法,函数返回即是调用结束,不够灵活
> 传统主流 **RPC** 服务端的写法通常是一个函数,函数返回后框架层把返回值打包发送给客户端作为应答,不支持在该函数中进行异步响应,尤其是golang的 **RPC** ,有的框架为了代码简单,没有写协程,发送数据时直接写到 Conn ,高并发写时竞争比较明显会增加时延;有的框架默认采用读协程收到数据 one-by-one 的方式处理,存在线头阻塞的问题,有的框架采用每个消息新开一个go协程处理,高并发时协程数量可能暴增、比较浪费,不支持按单个 Method/Router 定制同步或者异步处理,也不支持在 Method/Router handler 内由业务层自主选择同步处理、新开go协程异步或者协程池等异步等方式的处理。
## 三、关于 ARPC
### 1. 高性能
> 想说 **ARPC** 比其他流行的 **golang RPC** 性能都好,但是自吹最强好像没有说服力,感谢 [rpcx](https://github.com/smallnest/rpcx) 有做一些主流 **RPC** 框架的性能对比,[老仓库](https://github.com/rpcx-ecosystem/rpcx-benchmark) 已经废弃并且那时 **ARPC** 还没有出生,有兴趣的同学可以到 [新仓库](https://github.com/rpcxio/rpcx-benchmark) 跑下代码进行对比,测试时请注意排除其他程序的干扰。
> 目前最为主流的 **gRPC** 因为官方综合考量使用了 **HTTP2** ,详情参考 [gRPC的动机和设计原则](https://grpc.io/blog/principles),注定了不能很高性能。而很多 **RPC** 的业务场景,是基于内部服务集群, **HTTP2** 的加密流程等显得有些性能浪费。而 **ARPC** 更注重性能和灵活性,通信协议部分交由业务层决定,通常建议使用 **TCP** 作为基础通信协议,如有需要,业务层也可以使用 **TLS** 、**Websocket** 或者 **KCP** 等 。
### 2. 网络交互模式全面
> 上面在 [不爽](#1-网络交互模式单一无法支持更丰富的场景) 的部分提到了传统主流 **RPC** 的不完整, **ARPC** 当然要有比较全面的支持:
>> | 序号 | 交互方式 | 是否需要应答 | 交互流程 | **传统主流RPC** | **ARPC** |
>> | - | ------ | ----- | ---------- | ---------- | ---------- |
>> | 1 | Call | 需要 | 客户端发起请求,服务端应答 | **✔** | **✔** |
>> | 2 | Call | 需要 | 服务端发起请求,客户端应答 | **✘** | **✔** |
>> | 3 | Notify | 不需要 | 客户端向服务端发出通知/推送 | **✘** | **✔** |
>> | 4 | Notify | 不需要 | 客户端向服务端发出通知/推送 | **✘** | **✔** |
### 3. 丰富的业务场景支持
> 由于网络交互模式相对全面,**ARPC** 可以用于处理多种常规业务场景而不受类似 HTTP 短链接、单向请求-应答方式的限制。比如:
>> 1)推送服务
>> 2)游戏服务
>> 3)聊天服务
>> 4)其他需要长连接、双向、广播等灵活交互方式的业务
### 4. 写法简单
> 如[示例](#rpc进化--arpc-项目自荐)所示, Handler 不采用函数返回即调用结束的形式,写法简单、更像 HTTP Handler 。由于也不强制使用编解码器,甚至不必生成结构化消息或者服务如 Protobuf 的 Message、Service 等,这样也带来一些额外的好处,比如热点的结构化数据,业务层可以在数据更新时序列化一次并缓存起来,有需要时直接发送序列化之后的数据给需求方,避免每次发送给每个连接都需要进行一次序列化的浪费,在高在线量的广播类业务中这点尤为明显。
> 其他一些 **RPC** 框架喜欢注册对象的方式,由框架层通过反射去解析符合 Handler 格式的方法进行隐式注册,由于早年被C++的各种语言标准、机制等背后动作玩弄得辛苦,golang项目中希望框架层和业务层都尽量不让用户增加没有必要的心智负担(比如通过对象隐式注册的方式:没有带来性能提升,没有架构设计模块设计的解耦或者其他优化), **ARPC** 的设计遵循简单、透明的原则,所以像 **HTTP** 一样进行显式注册,如果有的同行喜欢玩弄语法糖技巧或者被语法糖技巧玩弄,可以自行定制。
### 5. 更灵活的同步异步
> 支持单个 Method/Router Handler 级别设置同步或者异步处理,也支持Handler 内由业务层自主控制同步或异步回包、从而针对性定制快/慢接口的协程数量控制与线头阻塞问题处理。
### 6. 最少依赖
> 目前如果只使用 **ARPC** 默认参数,则只使用了golang标准库,不需要依赖其他第三方package。
### 7. 易扩展
> 1)网络协议支持:由用户自主决定,服务端实现net.Listener、客户端实现net.Conn即可做为 **ARPC** 的网络载体,[arpc/examples/protocols](https://github.com/lesismal/arpc/tree/master/examples/protocols) 已经提供了 **KCP** 、 **QUIC** 、 **TLS** 、 **UnixSocket** 、 **UTP** 、 **Websocket** 等示例,欢迎参考。
> 2)非结构化的消息体编解码支持:可以直接用 string 、 *string 、 []byte 、 *[]byte 、 error 、 *error 等作为消息体参数。
> 3)结构化的消息体编解码支持:为了最少依赖, **ARPC** 默认使用了 encoding/json 作为结构化消息体的编解码器,性能不够强,但是业务层可以很方便地设置使用 json-iter 、 Protobuf 等作为结构化消息地编解码器。
> 4)消息体编解码中间件支持: **ARPC** 提供了消息体编解码中间件机制, [arpc/middleware/coder](https://github.com/lesismal/arpc/tree/master/middleware/coder) 子包实现了 [Gzip](https://github.com/lesismal/arpc/tree/master/middleware/coder/gzip) 、 [Tracer](https://github.com/lesismal/arpc/tree/master/middleware/coder/tracer) 作为默认示例,有需要的用户可以参考实现自行定制,使用示例在 [arpc/examples/middleware/coder](https://github.com/lesismal/arpc/tree/master/examples/middleware/coder) 。
> 5)Method/Router 的中间件支持: **ARPC** 提供了类似流行的 golang HTTP 框架的中间件,方便业务层自行扩展, [arpc/middleware/router](https://github.com/lesismal/arpc/tree/master/middleware/router) 子包实现了 **Logger** 、 **Recover** 、 **Graceful** 作为默认示例,有需要的用户可以参考并实现自行定制,使用示例在 [arpc/examples/middleware/router](https://github.com/lesismal/arpc/tree/master/examples/middleware/router)
> 6)Web JS Client 支持: **ARPC** 提供了 [JS Client](https://github.com/lesismal/arpc/blob/master/jsclient/arpc.js) 及示例:[API示例](https://github.com/lesismal/arpc/blob/master/examples/protocols/websocket/jsclient/hello.html) 、 [聊天示例](https://github.com/lesismal/arpc/tree/master/examples/webchat)。有了 JS Client , 不需要类似其他 RPC 框架那样部署 HTTP 转换 RPC 的网关,前端可以直接通过 **Websocket** 与 **ARPC** 服务进行交互,而且因为 **ARPC** 已经包括了消息的编解码、Method/Router Handler,比 **melody** 等只封装了收发数据的基础 websocket 框架更方便。
> 其他扩展不一一列举了,欢迎有兴趣的同学查看代码或者 New issue 。
### 8. 更多示例
> [arpc/examples](https://github.com/lesismal/arpc/tree/master/examples)提供了较为丰富的示例,如 **通知** 、 **广播** 、 **优雅退出** 、 **服务注册与发现** 、 **连接池** 、 **kcp/quic/tls/websocket等协议支持** 、 **发布订阅** 、 **JS Web Chat** 等,请见 [arpc/examples](https://github.com/lesismal/arpc/tree/master/examples) 。
### 9. 其他
> 个人精力有限,并且 golang 是世界上第二好的编程语言,所以暂时不考虑对其他语言的支持,欢迎 pr、issue 。
:joy::joy::joy:
in => internal
ex => extension
eg => eg.
哎,看着编辑器里三长两短的总觉得别扭
之前的log util这些内部依赖和micro protocol 这些扩展样例都是散放在 arpc/ 下面的,没分类、有点乱,昨天想想就收进去了,其实再往前的版本,扩展是单独放到其他仓库的,但是又不想浪费别人时间去多依赖一个库,因为也用一些nodejs,nodejs随便几行代码都能搞成一个包然后node_modules磁盘杀手的体验太恶心人了。。
反正代码也不多,就都俩字母了,俩字母再往下的子目录我尽量不展开看。。其实主要是in的简单部分,ex、eg的部分还是希望由业务层自己去定制
#10
更多评论
<a href="/user/soluty" title="@soluty">@soluty</a> 帖子里最少依赖的部分有讲,因为ARPC框架部分package和自己子package本身不依赖标准库之外的其他三方库,所以不需要 go mod ^_^
micro、protocol里的子package实现了服务发现与注册、对quic和websocket的支持是作为样例,需要依赖etcd、gorillia等,但这属于业务层,并不是ARPC框架本身的依赖项
#2