Commit ed52e0b9 authored by gejun's avatar gejun

add overview.md and getting_started.md

parent 72a31f94
# 运行示例程序
在命令行中运行如下命令即可在~/my_baidu_rpc/public/baidu-rpc中下载源代码编译并运行echo示例程序:
> mkdir -p ~/my_baidu_rpc/public && cd ~/my_baidu_rpc/public && svn co
> https://svn.baidu.com/public/trunk/baidu-rpc && cd baidu-rpc && comake2 -UB -J8 -j8 && comake2 -P
> && make -sj8 && cd example/echo_c++ && comake2 -UB -J8 -j8 && comake2 -P && make -sj8 && (
> ./echo_server & ) && ./echo_client && pkill echo_server
# 通过COMAKE依赖baidu-rpc
如果你的模块还没有建立,你可能得去[work.baidu.com](http://work.baidu.com/)上申请新的模块。确保你使用了[comake2](http://wiki.babel.baidu.com/twiki/bin/view/Com/Main/Comake2)
在COMAKE文件中增加:
> CONFIGS('public/baidu-rpc@ci-base')
这依赖了baidu-rpc的最新发布版本。模板可参考[echo的COMAKE文件](https://svn.baidu.com/public/trunk/baidu-rpc/example/echo_c++/COMAKE)。然后运行:
```
$ comake2 -UB # 下载所有的依赖模块
$ comake2 -P # 生成或更新Makefile
$ make -sj8 # 编译
```
你也可以在[agile上](http://agile.baidu.com/#/builds/public/baidu-rpc@trunk)
[scm.baidu.com](http://scm.baidu.com/)上查询baidu-rpc的已发布版本,并在COMAKE中依赖对应的静态版本。当comake2
-UB时baidu-rpc的版本会固定在你选定的版本上。注意:静态版本指的是我们发布的tag,而不是trunk上的revision。请勿依赖trunk的某个revision。
### gcc4下的errno问题
务必在直接或间接使用baidu-rpc的C/C++项目COMAKE中添加**CPPFLAGS('-D__const__=')**,防止[gcc4下的errno问题](http://wiki.baidu.com/display/RPC/thread-local#thread-local-gcc4下的errno问题)
## comake出错
老版本comake2(比如2.1.3.2304)不支持ci-base版本,如果comake2报如下的错误,说明版本太老:
```
... [status:1][err:https://svn.baidu.com/public/tags/xxx/ci-base: (Not a valid URL)
```
你可以运行如下命令更新comake2版本后再试,确保你有修改的权限。
```
$ cd $(dirname $(readlink -f $(which comake2)))/libcomake2
$ ./auto_update.py
```
## 更新依赖后编译失败
一般是baidu-rpc和依赖的模块版本不匹配导致的。确保以下模块没有被指定版本,让baidu-rpc自己选择:
[public/common](http://agile.baidu.com/agile/pipeline#/builds/public/common@trunk)
[public/bthread](http://agile.baidu.com/agile/pipeline#/builds/public/bthread@trunk)
[public/bvar](http://agile.baidu.com/agile/pipeline#/builds/public/bvar@trunk)
[public/murmurhash](http://agile.baidu.com/agile/pipeline#/builds/public/murmurhash@trunk)
[public/mcpack2pb](http://agile.baidu.com/agile/pipeline#/builds/public/mcpack2pb@trunk)
[public/iobuf](http://agile.baidu.com/agile/pipeline#/builds/public/iobuf@trunk)
[public/protobuf-json](http://agile.baidu.com/agile/pipeline#/builds/public/protobuf-json@trunk)
问题的根源在于百度的代码库不是单根的,所以每个模块都有很多独立版本, 产生非常多的组合,
有点类似于以前windows上的dll hell问题, 以baidu-rpc依赖的public/common为例:
- 情况1: 产品线依赖了public/common@某tag 和 public/baidu-rpc@ci-base, 所以baidu-rpc总是会更新到最新,
而public/common则不会, 如果baidu-rpc依赖了public/common的新接口, baidu-rpc的编译就挂了.
- 情况2: 产品线依赖了public/common@ci-base 和 public/baidu-rpc@某tag,
所以public/common总是会更新到最新, 但baidu-rpc不变, 如果public/common中删除了老接口,
或一些接口有调整, baidu-rpc的编译也挂了
如果去掉了public/common的依赖,
每次更新时COMAKE或BCLOUD会自动选择baidu-rpc对应版本被发布时使用的public/common版本,
比如baidu-rpc@某tag会选择该tag发布时对应的public/common版本, 一般总是可编译过的.
当然COMAKE或BCLOUD可能会对依赖打平, 如果产品线依赖的其他模块里依赖了public/common某版本且无法更改,
同时有编译问题, 只是在自己模块中去掉对public/common的依赖是不够的,
因为COMAKE或BCLOUD还是会选到那个指定的版本,
这种情况下可以在自己的COMAKE或BCLOUD中显式地依赖baidu-rpc@ci-base和public/common@ci-base.
基本原则就是都依赖ci-base一般总是对的, 不用过于担心稳定性问题.
# 支持的软件栈
## GCC
| 版本 | 支持程度 |
| ---- | ----------- |
| 3.4 | 一直支持 |
| 4.4 | 15年9月后的版本支持 |
| 4.8 | 一直支持 |
| 5.4 | r34255后支持 |
| 7.1 | r35109后支持 |
使用其他版本的gcc可能会有warning而编译失败 (baidu-rpc把warning也视作error), 请联系我们修复。
r32023后支持C++11编译。
## GDB
如果你使用的是opt/compiler下的gcc(4.8),那么gdb应该也应该用opt/compiler下的。
如果gdb对多线程的支持有问题,可以用这个版本,在目标机器上运行如下命令可获得gdb79可执行文件:
`wget "http://hetu.baidu.com:80/api/tool/getFile?toolId=1357&fileId=1201" -O "gdb79" && chmod +x
./gdb79`
## OS
RHEL4,centos 6.5,centos 4.3,ubuntu 14.04 (linuxmint 17.3)
ubuntu (linuxmint)下找不到openssl/ssl.h的话请安装libssl-dev。
对于自己的机器,安装好svn和[comake2](http://wiki.babel.baidu.com/twiki/bin/view/Com/Main/Comake2#安装升级)(公司环境中才能运行auto_udpate.py)后就可以按照第一节的方法下载和运行baidu-rpc了。
## protobuf
一直支持2.4。公司内请使用[2.4.1.1100](http://scm.baidu.com/fourversion/index.action?threeversion.id=346350&fourversion.id=668155),这个版本中为google::protobuf::NewCallback增加了重载形式,可以支持超过2个参数。
r31361后支持2.6。公司内请使用[2.6.1.400](http://scm.baidu.com/fourversion/index.action?threeversion.id=644886&fourversion.id=1139940),这个版本中为google::protobuf::NewCallback增加了重载形式,可以支持超过2个参数。
r32035后支持3.1.x,server端的arena分配暂不支持。不要求开启C++11,但要求gcc 4.8。
r35000后支持3.2.x。
### 更换protobuf版本
baidu-rpc默认的依赖2.4,你可以在你项目的COMAKE或BCLOUD中指定不同版本的protobuf,依赖会被打平。如果baidu-rpc依赖的一些模块报告protobuf版本不匹配,可以去对应的模块clean后重编。
### 关于NewCallback
由于protobuf
3把NewCallback设置为私有,r32035后baidu-rpc把NewCallback独立于[src/baidu/rpc/callback.h](https://svn.baidu.com/public/trunk/baidu-rpc/src/baidu/rpc/callback.h),如果你的程序出现NewCallback相关的编译错误(不论protobuf的版本),把google::protobuf::NewCallback替换为baidu::rpc::NewCallback就行了。
### 编译.proto
COMAKE[支持proto文件](http://wiki.babel.baidu.com/twiki/bin/view/Com/Main/Comake2#如何编译idl或proto文件)作为源文件,但需要通过**PROTOC**指明protobuf
compiler的位置,在COMAKE文件中加入:
**COMAKE**
你也可以使用用如下命令手动生成代码,详见[公司内的pb文档](http://wiki.babel.baidu.com/twiki/bin/view/Com/Main/Protobuf)
```
$ protoc --cpp_out=DEST_PATH -I=PROTO_PATH your.proto
```
更多protobuf问题请先阅读[google官方文档](https://developers.google.com/protocol-buffers/docs/reference/overview),再阅读[公司内文档](http://wiki.babel.baidu.com/twiki/bin/view/Com/Main/Protobuf),确保你已经了解protobuf的概念和基本使用方式。不确定的接口可以查看生成的.pb.h文件。
### 同时兼容pb 3.0和pb 2.x
勿使用proto3的新类型,proto文件开头要加上syntax="proto2";,[tools/add_syntax_equal_proto2_to_all.sh](https://svn.baidu.com/public/trunk/baidu-rpc/tools/add_syntax_equal_proto2_to_all.sh)可以给目录以下的所有没有加的proto文件加上syntax="proto2"。
## boost
要求1.56以上。r34354后开启C++11时不依赖boost。
**intrusive_ptr**
r34233后改为依赖public/common中的base::intrusive_ptr,不再依赖boost中的。
**context**
r34213后改为依赖public/bthread中的context,不再依赖boost中的。
r34213前如果在编译过程中出现相关的错误,可以尝试在COMAKE中显式地声明依赖:
**COMAKE**
**atomic**
r34354后改为依赖public/common中的base::atomic,不再(直接)依赖boost中的。当开启C++11时,base::atomic基于std::atomic,否则仍基于boost::atomic。换句话说,此版本以后的baidu-rpc当开启C++11时不再依赖boost。
## tcmalloc
baidu-rpc默认**不链接**[tcmalloc](http://goog-perftools.sourceforge.net/doc/tcmalloc.html),如果需要可自行依赖,在COMAKE中增加:
**COMAKE**
tcmalloc相比默认的ptmalloc常可提升整体性能,建议尝试。但不同的tcmalloc版本可能有巨大的性能差异。tcmalloc
2.1.0.100会使baidu-rpc示例程序的性能显著地低于使用tcmalloc 1.7.0.200和2.5.0.5977的版本。甚至使用
1.7.0.100的性能也比1.7.0.200低一些,当你的程序出现性能问题时,去掉tcmalloc或更换版本看看。
使用gcc4.8.2编译的模块, 如果依赖的是third-64/tcmalloc(gcc3.4.5编译的产出),
可能会遇到main函数前malloc线程不安全的情况,具体表现为程序在main函数之前crash或者死锁. 例如
![img](http://wiki.baidu.com/download/attachments/71337200/image2017-8-22%2017%3A32%3A28.png?version=1&modificationDate=1503394348000&api=v2)
如果你的程序在gcc4.8编译的产出中遇到这个问题,建议升级tcmalloc为源码编译.
tcmalloc的另一个常见问题是它不像默认的ptmalloc那样及时的归还系统内存,所以在出现非法内存访问时,可能不会立刻crash,而最终crash在不相关的地方,甚至不crash。当你的程序出现诡异的内存问题时,也记得去掉tcmalloc看看。
如果要使用[cpu profiler](http://wiki.baidu.com/display/RPC/cpu+profiler)[heap
profiler](http://wiki.baidu.com/display/RPC/heap+profiler),请链接tcmalloc,这两个profiler是基于tcmalloc开发的。[contention
profiler](http://wiki.baidu.com/display/RPC/contention+profiler)不要求tcmalloc。
不想链接tcmalloc时请注意:不仅要去掉对tcmalloc模块的依赖,还得检查下是否删除了-DBAIDU_RPC_ENABLE_CPU_PROFILER
或 -DBAIDU_RPC_ENABLE_HEAP_PROFILER等baidu-rpc的宏。
## gflags
经验证支持2.0至[2.21](https://github.com/gflags/gflags/tree/v2.2.1)
## valgrind
在r30539后,在valgrind中运行程序时加上**-has_valgrind**
(确保你的程序使用了[gflags](http://wiki.baidu.com/pages/viewpage.action?pageId=71698818))
> 例如valgrind ./my_program
> -has_valgrind。如果没有开启的话,valgrind无法识别bthread的栈从而可能crash。
>
> 老版本的valgrind(比如3.2)似乎不支持,请使用新一点的版本(比如3.8),可以用[jumbo](http://jumbo.baidu.com/)安装。
在r34944后,-has_valgrind被移除,程序会自动判断是否在valgrind中。
## openssl
r35109后支持1.1
# 新特性
对用户有意义的新特性,以方便用户调研使用。
| 版本 | 功能 | 描述 |
| --------- | --------------------------- | ---------------------------------------- |
| `r33446 ` | 开启-usercode_in_pthread无死锁风险 | 之前有。 |
| `r33424 ` | 增加开关-log_hostname |
开启后会在每条日志后加上本机名。对于汇总的日志查询工具有用。 |
| `r33323 ` | 默认发布工具 | 编译baidu-rpc时rpc_press, rpc_view, rpc_replay,
parallel_http也会一并编译,并能在产品库中获得。要注意的是,产品库默认以gcc
3.4编译,在新机器上可能无法直接运行,需要对一些so做软链。 |
| `r33306 ` | 增加工具parallel_http | 可同时访问数万个http url,
远快于curl(即使批量后台运行) |
| `r32844 ` | 支持http-flv | 另一种广泛用于直播的流媒体协议
|
| `r32803 ` | 支持同时发起大量异步访问 |
重构了bthread_id_list_t,从静态容量变为了动态容量。 |
| `r32668 ` | 支持RTMP | 一种广泛用于直播的流媒体协议(仍在完善中)
|
| `r32560 ` | 支持NamingServiceFilter | 用于过滤名字服务返回的节点列表
|
| `r32536 ` | 初始化bthread |
`ServerOptions增加了bthread_init_fn等参数用于在server启动前初始化一些bthread。` |
| `r32420 ` | 支持nshead_mcpack | 可用protobuf处理nshead+mcpack的协议 |
| `r32401 ` | 受控的日志打印 | `LOG_ONCE:只打印一次 LOG_EVERY_N:每过N次打印一次
LOG_EVERY_SECOND:每秒打印一次` |
| `r32399 ` | 不可修改的flags | 加上-immutable_flags程序的/flags页面就无法被修改了
|
| `r32328 ` | 获取RPC延时 |
Controller.latency_us()会返回对应的RPC延时,同步异步都支持。 |
| `r32301 ` | 显示RTT |
[/connections](http://brpc.baidu.com:8765/connections)页面会显示内核统计的smooth RTT了。 |
| `r32279 ` | 支持凤巢ITP协议 |
详见[ITP](http://wiki.baidu.com/pages/viewpage.action?pageId=184259578) |
| `r32097` | 支持Restful开发 |
用户可定制访问每个方法的URL,详见[RestfulURL](http://wiki.baidu.com/pages/viewpage.action?pageId=213828736#id-实现HTTPService-RestfulURL)
|
| `r32034 ` | 支持protobuf 3.0 | `Server端的Arena分配仍不支持。mcpack2pb,
protobuf-json等周边工具仍待迁移。` |
| `r32015 ` | 访问redis-server |
[访问Redis](http://wiki.baidu.com/pages/viewpage.action?pageId=213828705) |
| `r32009` | RetryPolicy |
可定制重试策略,详见[重试](http://wiki.baidu.com/pages/viewpage.action?pageId=213828685#id-创建和访问Client-错误值得重试)
|
| `r32009 ` | rpc_view |
可在浏览器中查看端口不在[8000-8999]的内置服务,详见[rpc_view](http://wiki.baidu.com/pages/viewpage.action?pageId=167651918)
|
| `r31986 ` | rpc_press |
代替了pbrpcpress,详见[rpc_press](http://wiki.baidu.com/pages/viewpage.action?pageId=97645422) |
| `r31901 ` | contention profiler | 可分析在锁上的等待时间,详见[contention
profiler](http://wiki.baidu.com/pages/viewpage.action?pageId=165876314) |
| `r31658 ` | rpc dump & replay |
详见[rpc_replay](http://wiki.baidu.com/pages/viewpage.action?pageId=158707916) |
# FAQ
### Q: baidu-rpc会不会发布稳定版本
本项目是主干开发,最新的改动在[trunk](https://svn.baidu.com/public/trunk/baidu-rpc/),发布在[agile上](http://agile.baidu.com/#/builds/public/baidu-rpc@trunk)。我们会尽量保持已有接口不变,升级新版本一般不会break代码。由于开发节奏快,我们没有发布Releasing
Branch (RB)的计划。
使用ci-base是更安全的选择。老版本的使用者更稀疏一些,bug会更加隐秘,发现得更晚。这种注意不到的bug会真正影响到策略的判断和迭代。而ci-base你在用,其他产品线也在用,问题很快能被发现和纠正。
如果正在运行的baidu-rpc版本被发现了用户有感的bug,我们会给每个baidu-rpc
server实例推送对应的bug信息,以提示产品线尽快升级,提示内容如下所示:
FATAL: 01-23 11:11:43: adx * 14542 src/baidu/rpc/trackme.cpp:141] Your baidu-rpc (r34140) is
affected by: [r34123-r34140] base::IOBuf::append(Movable) may leak memory;
### Q: SSL相关的链接问题
在1.0.171.31586之**前**的版本中,可能出现如下的core,请在COMAKE的LDFLAGS中去掉-lssl
-lcrypto,加上-ldl -lz。原因在于baidu-rpc链接的third-64/openssl和系统提供的冲突。
在1.0.171.31586之**后**的版本中,为了提高对不同平台的兼容性,baidu-rpc不再静态链接openssl,可能在链接时出现undefined
reference,请在COMAKE的LDFLAGS中加上-lssl -lcrypto。
```
#0 0x000000302af6f950 in strcmp () from /lib64/tls/libc.so.6
#1 0x000000302cc992a3 in OBJ_NAME_new_index () from /lib64/libcrypto.so.4
#2 0x000000302cc959a0 in lh_free () from /lib64/libcrypto.so.4
#3 0x000000302cc95cc2 in lh_insert () from /lib64/libcrypto.so.4
#4 0x000000302cc99469 in OBJ_NAME_add () from /lib64/libcrypto.so.4
#5 0x000000000078a8a5 in SSL_library_init () at webfoot_item.cpp:21
#6 0x0000000000613b0d in RegisterAllExtensionsOrDieImpl () at
src/baidu/rpc/policy/register_all_extensions.cpp:97
#7 0x000000302b809a10 in pthread_once () from /lib64/tls/libpthread.so.0
#8 0x0000000000613a31 in baidu::rpc::policy::RegisterAllExtensionsOrDie () at
src/baidu/rpc/policy/register_all_extensions.cpp:238
#9 0x00000000005ccaac in Channel (this=0xb69c40) at src/baidu/rpc/channel.cpp:68
#10 0x000000000059e0f8 in __static_initialization_and_destruction_0 (__initialize_p=1,
__priority=65535) at test_ei_srv_client.cpp:8
#11 0x000000000059e159 in global constructors keyed to main () at test_ei_srv_client.cpp:19
#12 0x0000000000903696 in __do_global_ctors_aux () at ./src/base/spinlock.h:49
#13 0x000000000059b028 in _init ()
#14 0x0000000000903610 in __libc_csu_init () at ./src/base/spinlock.h:49
#15 0x00000000009035d1 in __libc_csu_init () at ./src/base/spinlock.h:49
#16 0x000000302af1c45f in __libc_start_main () from /lib64/tls/libc.so.6
#17 0x000000000059deaa in _start ()
#18 0x0000007fbffff1a8 in ?? ()
#19 0x000000000000001c in ?? ()
```
### Q: 为什么C++ client/server 能够互相通信, 和其他语言的client/server 通信会报序列化失败的错误
检查一下C++ 版本是否开启了压缩 (Controller::set_compress_type), 目前 python/JAVA
版的rpc框架还没有实现压缩,互相返回会出现问题。
### Q: 两个产品线都使用protobuf,为什么不能互相访问
协议 !=
protobuf。protobuf负责打包,协议负责定字段。打包格式相同不意味着字段可以互通。协议中可能会包含多个protobuf包,以及额外的长度、校验码、magic
number等等。协议的互通是通过在RPC框架内转化为统一的编程接口完成的,而不是在protobuf层面。从广义上来说,protobuf也可以作为打包框架使用,生成其他序列化格式的包,像[idl
<=>
protobuf](http://wiki.baidu.com/pages/viewpage.action?pageId=144820547)就是通过protobuf生成了解析idl的代码。
### Q: protobuf打印了UTF-8相关的错误日志
```
// protobuf 2.4
google/protobuf/wire_format.cc:1059] Encountered string containing invalid UTF-8 data while parsing
protocol buffer. Strings must contain only UTF-8; use the 'bytes' type for raw bytes.
// protobuf 2.6
google/protobuf/wire_format.cc:1091] String field 'key' contains invalid UTF-8 data when serializing
a protocol buffer. Use the 'bytes' type if you intend to send raw bytes
```
原因:pb中的string必须是utf-8编码,非utf-8编码的字符串必须用bytes存储。
解决方式:
1. [推荐]
把proto中对应字段的类型从string改为bytes。string和bytes的二进制格式是一样的,所以这个改动不会造成新老消息的不兼容。这两个类型生成的函数也是一样的,用户代码不需要修改。
2. 定义宏NDEBUG。这个检查会被跳过。
注意:pb 2.4不会打印出问题的字段名,pb 2.6会,如果你需要快速定位出问题的字段,用pb 2.6
P/IP协议](http://en.wikipedia.org/wiki/Internet_protocol_suite)相互访问,但TCP/IP只是往远端发送了一段二进制数据,相比日常需求还有很多问题需要抽象:
- 数据以什么格式传输?不同机器间,网络间可能是不同的字节序,把C++
struct直接作为数据传输显然是不合适的;随着业务变化,数据字段往往要增加或删减,怎么兼容前后不同版本的格式?
- 一个TCP连接只能传输一份数据么?能复用吗?
- 如何连接集群?如何从集群中选出一台机器发送数据?
- 连接断开时应该干什么?万一server不发送回复怎么办?
- ...
[RPC](http://en.wikipedia.org/wiki/Remote_procedure_call)可以解决这些问题,它把网络交互类比为“client访问server上的函数”:client向server发送request后开始等待,直到server收到、处理、回复client后,client又再度恢复并根据response做出反应。
![img](http://wiki.baidu.com/download/thumbnails/71337070/Client-server.jpg?version=1&modificationDate=1427941174000&api=v2)
我们来看看上面的一些问题是如何解决的:
- RPC需要序列化,目前公司内定为protobuf。用户填写protobuf::Message类型的request,RPC结束后,从同为protobuf::Message类型的response中取出结果。protobuf有较好的前后兼容性,方便业务调整字段。我们强烈建议使用protobuf描述业务数据。
- 用户不需要关心连接是如何建立的,但用户可以选择不同的连接方式:短连接,连接池,单连接。
- 用户一般通过名字服务发现一个集群中的所有机器,常用的有[BNS](http://devops.baidu.com/new/bns/index.md),以前有[galileo](http://wiki.babel.baidu.com/twiki/bin/view/Com/Gm/ResourceRouterHome)。用户可以指定负载均衡算法,让RPC每次选出一台机器发送请求。
- 连接断开时按用户的配置进行重试。如果server没有在给定时间内返回response,那么client会返回超时错误。
RPC框架作为最基本的网络通讯组件,需要具备优秀的稳定性、可维护性和扩展性。在开发本框架前,公司内外主要有如下RPC实现:
- UB
:com组(INF前身)在08年开发的RPC框架,目前仍广泛使用,经历了大量的线上考验,稳定性优秀。UB的主要问题是依赖众多、扩展性不佳,以及缺乏调试工具。用户往往只需要UB中的一部分功能而不得不依赖大量模块,由于百度的代码库不是单根,这带来了复杂的依赖打平问题。UB每个请求都要独占一个连接,在大规模服务中每台机器都需要保持大量的连接,限制了其使用场景(比如INF自己的分布式系统都不用UB)。UB只支持nshead+mcpack协议,也没有太多考虑扩展接口,所以增加新协议和新功能往往要调整大段代码,在实践中大部分人都“知难而退”了。UB也缺乏调试和运维接口,服务的运行状态对用户基本是黑盒,只能靠低效的打日志来追踪问题,产品线服务出现问题时总是要拉上UB的维护同学一起排查,效率很低。UB有多个变种:
- ubrpc:INF在10年基于UB开发的RPC框架,用idl文件描述数据的schema,而不是在代码中手动打包。这个RPC有被使用,但不广泛。
- [nova-pbrpc](http://websvn.work.baidu.com/repos/app_ecom_nova/list/trunk/public/pb-rpc):网盟在12年基于UB开发的RPC框架,用protobuf代替mcpack作为序列化方法,协议是nshead
+ user's protobuf。
- [public/pbrpc](http://websvn.work.baidu.com/repos/public/list/trunk/pbrpc):INF在13年初基于UB开发的RPC框架,用protobuf代替mcpack作为序列化方法,但协议与nova-pbrpc不同,大致是nshead
+ meta protobuf。meta protobuf中有个string字段包含user's
protobuf。由于用户数据要序列化两次,这个RPC的性能很差,基本没有被推广开。
- [hulu-pbrpc](https://svn.baidu.com/public/trunk/hulu/pbrpc/):INF在13年基于saber(kylin变种)和protobuf实现的RPC框架。hulu尝试区分了机制(pbrpc)和策略(huluwa),减轻了依赖问题。但在实现上有较多问题:未封装的引用计数,混乱的生命周期,充斥的多线程问题(race
conditions & ABA
problems),运行质量很不可靠,比如hulu的短链接从来没有能正常运行过。其编程接口强依赖saber,很多功能的划分不够清晰,比如增加新协议就需要同时修改saber和pbrpc,扩展新功能仍然很困难。hulu增加了http协议的内置服务,可以提供一些简单的内部运行状态,但对于排查问题还比较简陋。hulu支持和一个server只保持一个连接,相比UB可以节省连接数,但由于多线程实现粗糙,读写不够并发,hulu的性能反而不如UB。
- [sofa-pbrpc](https://svn.baidu.com/public/trunk/sofa-pbrpc/):PS在13年基于boost::asio和protobuf实现的RPC框架,这个库代码工整,接口清晰,支持同步和异步,有非HTTP协议的调试接口(最新版也支持HTTP了)。但sofa-pbrpc也有产品线自研框架的鲜明特点:不支持公司内的其他协议,对名字服务、负载均衡、服务认证、连接方式等多样化的需求的抽象不够一般化。sofa-pbrpc还对外发布了开源版本。
- thrift:facebook最早在07年开发的RPC框架,后转为[apache的开源项目](https://thrift.apache.org/)。包含独特的序列化格式和IDL,支持很多编程语言。thrift让网络上的开发者能完成基本的RPC通讯,但也仅限于此了。thrift的代码看似分层很清楚,client、server选择很多,但没有一个足够通用,每个server实现都只能解决很小一块场景,每个client都线程不安全。实际使用中非常麻烦。thrift的代码质量也比较差,接口和生成的代码都比较乱。
基于这些观察,我们感到有必要开发一套新的RPC框架,即**baidu-rpc**,以方便各类用户开发、调试、运维网络服务,满足产品线之间对平台化、接口化日益高涨的需求,能适应核数越来越多的硬件架构,性能远好于其他实现。请注意,本文档主要指C++版本的baidu-rpc。目前的[pbrpc4j](http://websvn.work.baidu.com/repos/public/list/trunk/javalib/pbrpc4j/?revision=HEAD&bypassEmpty=true)基于netty改造,功能较少,我们可能会也可能不会把它作为baidu-rpc的java版。python有[client实现](https://svn.baidu.com/public/tags/pythonlib/pbrpc/pbrpc_1-0-2-5_PD_BL/)[server实现](http://wiki.baidu.com/display/RPC/Python+Server)。php目前只支持client。
# 目标
- 提供稳定的RPC框架。
- 适用各类业务场景,提供优秀的延时,吞吐,并发度,具备优秀的多核扩展性。
- 接口易懂,用户体验佳。有完备的调试和运维接口(HTTP)。
- 开发习惯好(注释、日志、changelog),有全面的单元、模块、集成测试,能作为编码示例。
# 假设
- 用户对访问延时,吞吐,并发度,分库等指标及功能有广泛而多样的需求。
- 机器以Intel x86_64为主。
- 下游服务规模可能到数万个节点,机器和网络状况多样化。
# 应用场景
几乎所有的网络交互。
RPC不是万能的抽象,否则我们也不需要TCP/IP这一层了。但是在我们绝大部分的网络交互中,RPC既能解决问题,又能隔离更底层的网络问题。对于RPC常见的质疑有:
- 我的数据非常大,用protobuf序列化太慢了。首先这可能是个伪命题,你得用[profiler](http://wiki.baidu.com/display/RPC/cpu+profiler)证明慢了才是真的慢,其次一些协议支持附件,你可以在传递protobuf请求时附带二进制数据。
- 我传输的是流数据,RPC表达不了。baidu-rpc支持[Streaming
RPC](http://wiki.baidu.com/pages/viewpage.action?pageId=152229270),这可以表达。
- 我的场景不需要回复。简单推理可知,你的场景中请求可丢可不丢,可处理也可不处理,因为client总是无法感知,你真的确认这是OK的?即使场景真的不需要,我们仍然建议用最小的结构体回复,因为这不大会是瓶颈,并且在出问题时让你有一些线索,否则真的是盲人摸象。
# 优势
### 使用更简单的接口
我们只有三个用户类:[Server](https://svn.baidu.com/public/trunk/baidu-rpc/src/baidu/rpc/server.h)[Channel](https://svn.baidu.com/public/trunk/baidu-rpc/src/baidu/rpc/channel.h?revision=HEAD)[Controller](https://svn.baidu.com/public/trunk/baidu-rpc/src/baidu/rpc/controller.h?revision=HEAD),分别对应server端,client端,和调整参数集合。你不需要推敲诸如“Client怎么初始化”,“XXXManager有什么用”,“Context和Controller的关系是什么“之类的问题,你要做的很简单:
- 建服务就#include
<[baidu/rpc/server.h](https://svn.baidu.com/public/trunk/baidu-rpc/src/baidu/rpc/server.h)>,并按照注释或例子使用Server对象。
- 访问服务就#include
<[baidu/rpc/channel.h](https://svn.baidu.com/public/trunk/baidu-rpc/src/baidu/rpc/channel.h)>,并按照注释或例子使用Channel对象。
- 想控制一次RPC访问的参数,就看看[baidu/rpc/controller.h](https://svn.baidu.com/public/trunk/baidu-rpc/src/baidu/rpc/controller.h)中到底有些什么。请注意,这个类是Server和Channel共用的,其中分成了三段,分别标记为Client-side,
Server-side和Both-side methods。
我们尝试让事情变得更加简单,以名字服务为例,在其他RPC实现中,你也许需要复制一长段晦涩的代码才可使用,而在baidu-rpc中访问BNS可以这么写"bns://node-name",
本地文件列表可以这么写"file:///home/work/server.list",相信不用我解释,你也能明白这些代表什么,这个字串可以放在配置文件中,方便地载入并使用。
### 访问各种服务,被各种服务访问
baidu-rpc能访问百度内所有基于protobuf的RPC server实现,能[访问ub
server](http://wiki.baidu.com/pages/viewpage.action?pageId=213828700)(idl/mcpack/compack)
baidu-rpc能被百度内所有基于protobuf的RPC
client访问,能被HTTP+json访问,通过一些额外代码可以被UB访问。
### 使服务更加可靠
我们的开发机制能持续地保证高质量:
- 分离机制(mechanism)和策略(policy):baidu-rpc把可扩展的部分都抽象为了策略,并放在了[单独的目录](https://svn.baidu.com/public/trunk/baidu-rpc/src/baidu/rpc/policy/),具体的协议支持,压缩算法,名字服务,负载均衡都是策略,如果你想二次开发,可以很容易找到模板并开始上手。就像在算法问题中O(M*N)问题变为了O(M+N)那样,这有效地降低了baidu-rpc整体的复杂度,使得我们可以把精力集中到最核心的代码上。
- 完整的单元和集成测试:baidu-rpc有完整的单元测试,集成测试,系统测试和性能测试,在我们每次check
in代码后都会运行,这确保我们在开发阶段可以及时规避问题。
- 全面的调试和监控手段:baidu-rpc特别重视开发和运维,你可以使用浏览器或curl[查询服务器内部状态](http://wiki.baidu.com/display/RPC/Builtin+Services),你也可以用pprof[分析在线服务的性能](http://wiki.baidu.com/display/RPC/cpu+profiler)。你可以[用bvar计数](http://wiki.baidu.com/display/RPC/bvar),相比缓慢的ubmonitor几乎没有性能损耗,同时bvar能以/vars访问,这改变了我们监控在线服务的方式
- 新基础库:baidu-rpc依赖的[C++
base](http://wiki.baidu.com/pages/viewpage.action?pageId=38035224)改造自chromium,是百度的新基础库。
### 获得更好的延时和吞吐
虽然大部分RPC实现都号称“高性能”,但仅仅是在测试中有不错的数字,在广泛的场景中确保高性能仍是困难的,为了做到这一点,baidu-rpc有比其他RPC实现更深的技术考量,会确保以最快的速度把请求分发到合适的位置,具体来说:
- **对不同客户端请求的读取和解析是完全并发的,用户也不用区分”IO线程“和”处理线程"**。听上去简单,但几乎没有做到的。大部分实现会区分“IO线程”和“处理线程”,并把[fd](http://en.wikipedia.org/wiki/File_descriptor)(对应一个客户端)散列到IO线程中去。这听上不错,实则很糟糕。首先当一个IO线程在读取其中的fd时,同一个线程中的fd都无法得到处理。有些实现(ubaserver)不扣除关闭的fd,那么在一段时间后,不同IO线程中的fd将很不均匀,一个fd的影响范围将更大。当一些解析变慢时,比如特别大的protobuf
message,同一个IO线程中的其他fd都遭殃了。虽然不同IO线程间的fd是并发的,但由于你不太可能开太多IO线程(这类线程的事情很少,大部分时候都是闲着的),10个就比较多了,所以一个fd能影响到的”其他fd“仍有相当大的比例(10个即10%,在线检索可是要求99.99%的)。将来的RPC框架将广泛地用于多租户(multi-tenacy)环境,一个数据复杂的业务如果严重干扰到其他业务的SLA,将是无法接受的。在baidu-rpc中,对不同fd的读取是完全并发的,对同一个fd中不同消息的解析也是并发的**。**比如一个特别大的protobuf
message在解析时不会影响同一个客户端的其他消息,更不用提其他客户端的消息了。
- **只要cpu和工作线程有剩余,新请求会在O(1)时间内得到处理**。由于大部分业务请求都要花费很多毫秒,即使只有一个任务由于调度的原因而得不到及时的处理,都是无法接受的。以hulu为例,有两种任务分发模式:一种是分发到固定线程,另一种是通过condition。前者没有同步开销,对于大流量效果更好,但无法保证本要求。后者有高强度的同步开销,吞吐不会太高,但可以保证本要求。这让用户很纠结。在baidu-rpc中总是能保证本要求,并且性能好于hulu。
- **对同一fd和不同fd的写出是高度并发的**。当多个线程都要对一个fd写出时(常见于单连接),第一个线程会直接在原线程写出,其他线程会以[wait-free](http://en.wikipedia.org/wiki/Non-blocking_algorithm#Wait-freedom)的方式托付自己的写请求,多个线程在高度竞争下仍可以在1秒内对同一个fd写入500万个16字节的消息,这很可能是目前最高性能的同fd写出实现。对不同fd的写出总是并发的。值得一提的是,我们在未来可能会用类似的技术实现往磁盘写的Channel,以满足一些服务器对超高频率日志的需求。
- **服务器线程数自动调节**。传统的服务器需要根据下游延时的调整自身的线程数,否则吞吐可能会受影响,一般是通过配置。在baidu-rpc中,每个请求均运行在新建立的bthread中,请求结束后线程就结束了,所以天然会根据负载自动调节线程数。
baidu-rpc背后的技术和知识请阅读[深入RPC](http://wiki.baidu.com/display/RPC/RPC+in+Depth),和其他RPC实现的性能对比请见[Benchmark](http://wiki.baidu.com/pages/viewpage.action?pageId=48480483)
### 获得独有的功能
如果你有任何需求,都可以向我们提:这并不意味着我们会全盘接受,我们也许会提议另一种解决方案,或指出一些理解的误区,不过这终究可以使想法得到更多打磨,产生新的火花。灵活的内部结构使我们可以更快地添加新功能,有一些功能是其他RPC实现很难拥有的,举例来说:
- 编写和访问HTTP server:你写的任何protobuf
server都可以在移动端用HTTP+json访问,你也可以写纯的HTTP/HTTPS server或者访问其他HTTP server。
- backup request: 又称tied requests,是一种降低请求延时的技术。
- [Locality-aware load balancer](http://wiki.baidu.com/display/RPC/Locality-aware+load+balancing) :
这种负载均衡算法会优先选择最近的服务器,即同机 > 同rack > 同机房 >
跨机房,并在近节点发生故障时快速收敛,当节点大量混部时,这可以有效地降低延时,并规避网络抖动。
- [内置服务](http://wiki.baidu.com/display/RPC/Builtin+Services) :
彻底改变开发和调试体验的强大工具!配合[bvar](http://wiki.baidu.com/display/RPC/bvar)更贴心。
- [组合访问](http://wiki.baidu.com/pages/viewpage.action?pageId=213828709):并发访问,分库分环,从没有这么简单过。
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment