Commit 6854081c authored by gejun's avatar gejun

fix links between docs

parent 85d8fd80
This diff is collapsed.
......@@ -12,10 +12,10 @@
了解这些因素后可以更好的理解baidu-rpc中相关的设计。
1. 拥塞时A服务最大qps的跳变是因为线程个数是**硬限**,单个请求的处理时间很大程度上决定了最大qps。而baidu-rpc server端默认在bthread中处理请求,个数是软限,单个请求超时只是阻塞所在的bthread,并不会影响为新请求建立新的bthread。baidu-rpc也提供了完整的异步接口,让用户可以进一步提高io-bound服务的并发度,降低服务被打满的可能性。
2. baidu-rpc中[重试](http://wiki.baidu.com/pages/viewpage.action?pageId=213828685#id-创建和访问Client-重试)默认只在连接出错时发起,避免了流量放大,这是比较有效率的重试方式。如果需要基于超时重试,可以设置[backup request](http://wiki.baidu.com/pages/viewpage.action?pageId=213828685#id-创建和访问Client-backuprequest),这类重试最多只有一次,放大程度降到了最低。baidu-rpc中的RPC超时是deadline,超过后RPC一定会结束,这让用户对服务的行为有更好的预判。在之前的一些实现中,RPC超时是单次超时*重试次数,在实践中容易误判。
3. baidu-rpc server端的[max_concurrency选项](http://wiki.baidu.com/pages/viewpage.action?pageId=213828715#id-创建和设置Server-限制最大并发)控制了server的最大并发:当同时处理的请求数超过max_concurrency时,server会回复client错误,而不是继续积压。这一方面在服务开始的源头控制住了积压的请求数,尽量避免延生到用户缓冲或队列中,另一方面也让client尽快地去重试其他server,对集群来说是个更好的策略。
2. baidu-rpc中[重试](client.md#重试)默认只在连接出错时发起,避免了流量放大,这是比较有效率的重试方式。如果需要基于超时重试,可以设置[backup request](client.md#backuprequest),这类重试最多只有一次,放大程度降到了最低。baidu-rpc中的RPC超时是deadline,超过后RPC一定会结束,这让用户对服务的行为有更好的预判。在之前的一些实现中,RPC超时是单次超时*重试次数,在实践中容易误判。
3. baidu-rpc server端的[max_concurrency选项](server.md#id-创建和设置Server-限制最大并发)控制了server的最大并发:当同时处理的请求数超过max_concurrency时,server会回复client错误,而不是继续积压。这一方面在服务开始的源头控制住了积压的请求数,尽量避免延生到用户缓冲或队列中,另一方面也让client尽快地去重试其他server,对集群来说是个更好的策略。
对于baidu-rpc的用户来说,要防止雪崩,主要注意两点:
1. 评估server的最大并发,设置合理的max_concurrency值。这个默认是不设的,也就是不限制。无论程序是同步还是异步,用户都可以通过 **最大qps \* 非拥塞时的延时**(秒)来评估最大并发,原理见[little's law](https://en.wikipedia.org/wiki/Little),这两个量都可以在baidu-rpc中的内置服务中看到。max_concurrency与最大并发相等或大一些就行了。
2. 注意考察重试发生时的行为,特别是在定制RetryPolicy时。如果你只是用默认的baidu-rpc重试,一般是安全的。但用户程序也常会自己做重试,比如通过一个Channel访问失败后,去访问另外一个Channel,这种情况下要想清楚重试发生时最差情况下请求量会放大几倍,服务是否可承受。
\ No newline at end of file
2. 注意考察重试发生时的行为,特别是在定制RetryPolicy时。如果你只是用默认的baidu-rpc重试,一般是安全的。但用户程序也常会自己做重试,比如通过一个Channel访问失败后,去访问另外一个Channel,这种情况下要想清楚重试发生时最差情况下请求量会放大几倍,服务是否可承受。
......@@ -4,7 +4,7 @@
Channel开启backup request。这个Channel会先向其中一个server发送请求,如果在ChannelOptions.backup_request_ms后还没回来,再向另一个server发送。之后哪个先回来就取哪个。在设置了合理的backup_request_ms后,大部分时候只会发一个请求,对后端服务只有一倍压力。
示例代码见[example/backup_request_c++](https://svn.baidu.com/public/trunk/baidu-rpc/example/backup_request_c++)。这个例子中,client设定了在2ms后发送backup request,server在碰到偶数位的请求后会故意睡眠20ms以触发backup request。
示例代码见[example/backup_request_c++](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/example/backup_request_c++)。这个例子中,client设定了在2ms后发送backup request,server在碰到偶数位的请求后会故意睡眠20ms以触发backup request。
运行后,client端和server端的日志分别如下,“index”是请求的编号。可以看到server端在收到第一个请求后会故意sleep 20ms,client端之后发送另一个同样index的请求,最终的延时并没有受到故意sleep的影响。
......@@ -41,6 +41,6 @@ my_func_latency << tm.u_elapsed(); // u代表微秒,还有s_elapsed(), m_elap
# 当后端server不能挂在一个名字服务内时
【推荐】建立一个开启backup request的SelectiveChannel,其中包含两个sub channel。访问这个SelectiveChannel和上面的情况类似,会先访问一个sub channel,如果在ChannelOptions.backup_request_ms后没返回,再访问另一个sub channel。如果一个sub channel对应一个集群,这个方法就是在两个集群间做互备。SelectiveChannel的例子见[example/selective_echo_c++](https://svn.baidu.com/public/trunk/baidu-rpc/example/selective_echo_c++),具体做法请参考上面的过程。
【推荐】建立一个开启backup request的SelectiveChannel,其中包含两个sub channel。访问这个SelectiveChannel和上面的情况类似,会先访问一个sub channel,如果在ChannelOptions.backup_request_ms后没返回,再访问另一个sub channel。如果一个sub channel对应一个集群,这个方法就是在两个集群间做互备。SelectiveChannel的例子见[example/selective_echo_c++](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/example/selective_echo_c++),具体做法请参考上面的过程。
【不推荐】发起两个异步RPC后Join它们,它们的done内是相互取消的逻辑。示例代码见[example/cancel_c++](https://svn.baidu.com/public/trunk/baidu-rpc/example/cancel_c++)。这种方法的问题是总会发两个请求,对后端服务有两倍压力,这个方法怎么算都是不经济的,你应该尽量避免用这个方法。
\ No newline at end of file
【不推荐】发起两个异步RPC后Join它们,它们的done内是相互取消的逻辑。示例代码见[example/cancel_c++](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/example/cancel_c++)。这种方法的问题是总会发两个请求,对后端服务有两倍压力,这个方法怎么算都是不经济的,你应该尽量避免用这个方法。
......@@ -2,4 +2,4 @@
使用方法:
首先你得[下载和编译](http://wiki.baidu.com/display/RPC/Getting+Started)了baidu-rpc源码,然后去example/http_c++目录,comake2 -P && make -sj4,成功后应该能看到benchmark_http。
\ No newline at end of file
首先你得[下载和编译](getting_started.md)了baidu-rpc源码,然后去example/http_c++目录,comake2 -P && make -sj4,成功后应该能看到benchmark_http。
This diff is collapsed.
......@@ -24,4 +24,4 @@ bthread_id的接口不太简洁,有不少API:
这么多接口是为了满足不同的使用流程。
- 发送request的流程:create -> lock -> ... register timer and send RPC ... -> unlock
- 接收response的流程:lock -> ..process response -> call done
\ No newline at end of file
- 接收response的流程:lock -> ..process response -> call done
baidu-rpc提供了[异步接口](http://wiki.baidu.com/pages/viewpage.action?pageId=213828685#id-创建和访问Client-异步访问),所以一个常见的问题是:我应该用异步接口还是bthread?
baidu-rpc提供了[异步接口](client.md#异步访问),所以一个常见的问题是:我应该用异步接口还是bthread?
短回答:延时不高时你应该先用简单易懂的同步接口,不行的话用异步接口,只有在需要多核并行计算时才用bthread。
......@@ -7,7 +7,7 @@ baidu-rpc提供了[异步接口](http://wiki.baidu.com/pages/viewpage.action?pag
异步即用回调代替阻塞,有阻塞的地方就有回调。虽然在javascript这种语言中回调工作的很好,接受度也非常高,但只要你用过,就会发现这和我们需要的回调是两码事,这个区别不是[lambda](https://en.wikipedia.org/wiki/Anonymous_function),也不是[future](https://en.wikipedia.org/wiki/Futures_and_promises),而是javascript是单线程的。javascript的回调放到多线程下可能没有一个能跑过,race
condition太多了,单线程的同步方法和多线程的同步方法是两个世界。那是不是服务能搞成类似的形式呢?多个线程,每个都是独立的eventloop。可以,ub**a**server就是(注意带a),但实际效果糟糕,因为阻塞改回调可不简单,当阻塞发生在循环,条件分支,深层子函数中时,改造特别困难,况且很多老代码、第三方代码你根本不可能去改造。结果是代码中会出现不可避免的阻塞,导致那个线程中其他回调都被延迟,流量超时,server性能不符合预期。如果你说,”我想把现在的同步代码改造为大量的回调,除了我其他人都看不太懂,并且性能可能更差了”,我猜大部分人不会同意。别被那些鼓吹异步的人迷惑了,他们写的是从头到尾从下到上全异步且不考虑多线程的代码,和你要写的完全是两码事。
baidu-rpc中的异步和单线程的异步是完全不同的,异步回调会运行在与调用处不同的线程中,你会获得多核扩展性,但代价是你得意识到多线程问题。你可以在回调中阻塞,只要线程够用,对server整体的性能并不会有什么影响。不过异步代码还是很难写的,所以我们提供了[组合访问](http://wiki.baidu.com/pages/viewpage.action?pageId=213828709)来简化问题,通过组合不同的channel,你可以声明式地执行复杂的访问,而不用太关心其中的细节。
baidu-rpc中的异步和单线程的异步是完全不同的,异步回调会运行在与调用处不同的线程中,你会获得多核扩展性,但代价是你得意识到多线程问题。你可以在回调中阻塞,只要线程够用,对server整体的性能并不会有什么影响。不过异步代码还是很难写的,所以我们提供了[组合访问](combo_channel.md)来简化问题,通过组合不同的channel,你可以声明式地执行复杂的访问,而不用太关心其中的细节。
当然,延时不长,qps不高时,我们更建议使用同步接口,这也是我们创建bthread的动机:维持同步代码也能提升交互性能。
......@@ -26,7 +26,7 @@ baidu-rpc中的异步和单线程的异步是完全不同的,异步回调会
有了bthread这个工具,用户甚至可以自己实现异步。以“半同步”为例,在baidu-rpc中用户有多种选择:
- 发起多个异步RPC后挨个Join,这个函数会阻塞直到RPC结束。(这儿是为了和bthread对比,实现中我们建议你使用[ParallelChannel](http://wiki.baidu.com/pages/viewpage.action?pageId=213828709#id-组合访问-ParallelChannel),而不是自己Join)
- 发起多个异步RPC后挨个Join,这个函数会阻塞直到RPC结束。(这儿是为了和bthread对比,实现中我们建议你使用[ParallelChannel](combo_channel.md#parallelchannel),而不是自己Join)
- 启动多个bthread各自执行同步RPC后挨个join bthreads。
哪种效率更高呢?显然是前者。后者不仅要付出创建bthread的代价,在RPC过程中bthread还被阻塞着,不能用于其他用途。
......@@ -58,4 +58,4 @@ bool search() {
- 计算时间超过1ms时收益比较明显。如果计算非常简单,几微秒就结束了,用bthread是没有意义的。
- 尽量让原地运行的部分最慢,那样bthread中的部分即使被延迟了几微秒,最后可能还是会先结束,而消除掉延迟的影响。并且join一个已结束的bthread时会立刻返回,不会有上下文切换开销。
另外当你有类似线程池的需求时,像执行一类job的线程池时,也可以用bthread代替。如果对job的执行顺序有要求,你可以使用基于bthread的[ExecutionQueue](http://wiki.baidu.com/pages/viewpage.action?pageId=160291992)
另外当你有类似线程池的需求时,像执行一类job的线程池时,也可以用bthread代替。如果对job的执行顺序有要求,你可以使用基于bthread的[ExecutionQueue](execution_queue.md)
......@@ -12,7 +12,7 @@
# 安全模式
出于安全考虑,直接对外服务需要关闭内置服务(包括经过nginx或其他http server转发流量的),具体方法请阅读[这里](http://wiki.baidu.com/pages/viewpage.action?pageId=213828715#id-创建和设置Server-安全模式)
出于安全考虑,直接对外服务需要关闭内置服务(包括经过nginx或其他http server转发流量的),具体方法请阅读[这里](server.md#id-创建和设置Server-安全模式)
# 主要服务
......@@ -32,4 +32,4 @@ dir服务可以浏览服务器上的所有文件,这个服务比较敏感,
threads服务可以查看进程内所有线程的运行状况,调用时对程序性能较大,默认关闭。
其他还有一些调试服务,如有需求请联系我们。
\ No newline at end of file
其他还有一些调试服务,如有需求请联系我们。
This diff is collapsed.
......@@ -145,7 +145,7 @@ class Variable {
static int dump_exposed(Dumper* dumper, const DumpOptions* options);
};
```
最常见的导出需求是通过HTTP接口查询和写入本地文件。前者在baidu-rpc中通过[/vars](http://wiki.baidu.com/display/RPC/vars)服务提供,后者则已实现在bvar中,由用户选择开启。该功能由5个gflags控制,你的程序需要使用[gflags](http://wiki.baidu.com/pages/viewpage.action?pageId=71698818)
最常见的导出需求是通过HTTP接口查询和写入本地文件。前者在baidu-rpc中通过[/vars](vars.md)服务提供,后者则已实现在bvar中,由用户选择开启。该功能由5个gflags控制,你的程序需要使用[gflags](flags.md)
![img](http://wiki.baidu.com/download/attachments/133624370/image2015-8-8%2023%3A18%3A21.png?version=1&modificationDate=1439047101000&api=v2)
......@@ -379,4 +379,4 @@ static bvar::GFlag s_gflag_my_flag_that_matters("my_flag_that_matters");
// Expose the gflag as a bvar named "foo_bar_my_flag_that_matters".
static bvar::GFlag s_gflag_my_flag_that_matters_with_prefix("foo_bar", "my_flag_that_matters");
```
\ No newline at end of file
```
This diff is collapsed.
......@@ -15,7 +15,7 @@ ParallelChannel (“pchan”)同时访问其包含的sub channel,并合并它
- 可以取消。
- 支持超时。
示例代码见[example/parallel_echo_c++](https://svn.baidu.com/public/trunk/baidu-rpc/example/parallel_echo_c++/)
示例代码见[example/parallel_echo_c++](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/example/parallel_echo_c++/)
任何baidu::rpc::ChannelBase的子类都可以加入ParallelChannel,包括ParallelChannel和其他组合Channel。用户可以设置ParallelChannelOptions.fail_limit来控制访问的最大失败次数(r31803前是ParallelChannel::set_fail_limit),当失败的访问达到这个数目时,RPC call会立刻结束而不等待超时。
......@@ -148,14 +148,14 @@ const Controller* sub(int index) const;
# SelectiveChannel
[SelectiveChannel](https://svn.baidu.com/public/trunk/baidu-rpc/src/baidu/rpc/selective_channel.h) (“schan”)按负载均衡算法访问其包含的一个Channel,相比普通Channel它更加高层:把流量分给sub channel,而不是具体的Server。SelectiveChannel主要用来支持机器组之间的负载均衡,它具备Channel的主要属性:
[SelectiveChannel](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/src/brpc/selective_channel.h) (“schan”)按负载均衡算法访问其包含的一个Channel,相比普通Channel它更加高层:把流量分给sub channel,而不是具体的Server。SelectiveChannel主要用来支持机器组之间的负载均衡,它具备Channel的主要属性:
- 支持同步和异步访问。
- 发起异步操作后可以立刻删除。
- 可以取消。
- 支持超时。
示例代码见[example/selective_echo_c++](https://svn.baidu.com/public/trunk/baidu-rpc/example/selective_echo_c++/)
示例代码见[example/selective_echo_c++](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/example/selective_echo_c++/)
任何baidu::rpc::ChannelBase的子类都可加入SelectiveChannel,包括SelectiveChannel和其他组合Channel。
......@@ -240,9 +240,9 @@ stub.FooMethod(&cntl, &request, &response, NULL);
# PartitionChannel
[PartitionChannel](https://svn.baidu.com/public/trunk/baidu-rpc/src/baidu/rpc/partition_channel.h)是特殊的ParallelChannel,它会根据名字服务中的tag自动建立对应分库的sub channel。这样用户就可以把所有的分库机器挂在一个名字服务内,通过tag来指定哪台机器对应哪个分库。示例代码见[example/partition_echo_c++](https://svn.baidu.com/public/trunk/baidu-rpc/example/partition_echo_c++/)
[PartitionChannel](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/src/brpc/partition_channel.h)是特殊的ParallelChannel,它会根据名字服务中的tag自动建立对应分库的sub channel。这样用户就可以把所有的分库机器挂在一个名字服务内,通过tag来指定哪台机器对应哪个分库。示例代码见[example/partition_echo_c++](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/example/partition_echo_c++/)
ParititonChannel只能处理一种分库方法,当用户需要多种分库方法共存,或从一个分库方法平滑地切换为另一种分库方法时,可以使用DynamicPartitionChannel,它会根据不同的分库方式动态地建立对应的sub PartitionChannel,并根据容量把请求分配给不同的分库。示例代码见[example/dynamic_partition_echo_c++](https://svn.baidu.com/public/trunk/baidu-rpc/example/dynamic_partition_echo_c++/)
ParititonChannel只能处理一种分库方法,当用户需要多种分库方法共存,或从一个分库方法平滑地切换为另一种分库方法时,可以使用DynamicPartitionChannel,它会根据不同的分库方式动态地建立对应的sub PartitionChannel,并根据容量把请求分配给不同的分库。示例代码见[example/dynamic_partition_echo_c++](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/example/dynamic_partition_echo_c++/)
如果分库在不同的名字服务内,那么用户得自行用ParallelChannel组装,即每个sub channel对应一个分库(使用不同的名字服务)。ParellelChannel的使用方法请见上一节。
......@@ -478,4 +478,4 @@ TRACE: 09-06 11:17:49: * 0 server.cpp:192] S[0]=0 S[1]=245961 S[2]=245888 [tot
TRACE: 09-06 11:17:50: * 0 server.cpp:192] S[0]=0 S[1]=250198 S[2]=250150 [total=500348]
```
在真实的线上环境中,我们会逐渐地增加4分库的server,同时下掉3分库中的server。DynamicParititonChannel会按照每种分库方式的容量动态切分流量。当某个时刻3分库的容量变为0时,我们便平滑地把Server从3分库变为了4分库,同时并没有修改Client的代码。
\ No newline at end of file
在真实的线上环境中,我们会逐渐地增加4分库的server,同时下掉3分库中的server。DynamicParititonChannel会按照每种分库方式的容量动态切分流量。当某个时刻3分库的容量变为0时,我们便平滑地把Server从3分库变为了4分库,同时并没有修改Client的代码。
......@@ -34,4 +34,4 @@
- 发起rpc时通过Controller::set_request_code()填入请求的hash code。
> request的hash算法并不需要和lb的hash算法保持一致,只需要hash的值域是32位无符号整数。由于memcache默认使用md5,访问memcached集群时请选择c_md5保证兼容性, 其他场景可以选择c_murmurhash以获得更高的性能和更均匀的分布。
\ No newline at end of file
> request的hash算法并不需要和lb的hash算法保持一致,只需要hash的值域是32位无符号整数。由于memcache默认使用md5,访问memcached集群时请选择c_md5保证兼容性, 其他场景可以选择c_murmurhash以获得更高的性能和更均匀的分布。
......@@ -2,7 +2,7 @@ baidu-rpc可以分析花在等待锁上的时间及发生等待的函数。
# 开启方法
按需开启。无需配置,不依赖tcmalloc,不需要链接frame pointer或libunwind。如果只是baidu-rpc client或没有使用baidu-rpc,看[这里](http://wiki.baidu.com/pages/viewpage.action?pageId=213843633)
按需开启。无需配置,不依赖tcmalloc,不需要链接frame pointer或libunwind。如果只是baidu-rpc client或没有使用baidu-rpc,看[这里](dummy_server.md)
# 图示
......@@ -28,4 +28,4 @@ r31906后baidu-rpc支持contention profiler,可以分析在等待锁上花费
点击上方的count选择框,可以查看锁的竞争次数。选择后左上角变为了**Total samples: 439026**,代表采集时间内总共的锁竞争次数(估算)。图中箭头上的数字也相应地变为了次数,而不是时间。对比同一份结果的时间和次数,可以更深入地理解竞争状况。
![img](http://wiki.baidu.com/download/attachments/165876314/image2016-1-19%2022%3A17%3A40.png?version=1&modificationDate=1453213082000&api=v2)
\ No newline at end of file
![img](http://wiki.baidu.com/download/attachments/165876314/image2016-1-19%2022%3A17%3A40.png?version=1&modificationDate=1453213082000&api=v2)
......@@ -6,7 +6,7 @@ baidu-rpc可以分析程序中的热点函数。
1. 这么写也开启了tcmalloc,不建议单独链接cpu profiler而不链接tcmalloc,可能越界访问导致[crash](https://code.google.com/p/gperftools/source/browse/README#sl_svn1035d5c18f64d114ac790b92a96f3b3a1a301eb9_207)**。**可能由于tcmalloc不及时归还内存,越界访问不会crash。
2. 这个版本的tcmalloc使用frame pointer而不是libunwind回溯栈,请确保在CXXFLAGS或CFLAGS中加上`-fno-omit-frame-pointer`,否则函数间的调用关系会丢失,最后产生的图片中都是彼此独立的函数方框。
2. 定义宏BAIDU_RPC_ENABLE_CPU_PROFILER。在COMAKE中加入`CXXFLAGS('-DBAIDU_RPC_ENABLE_CPU_PROFILER')`
3. 如果只是baidu-rpc client或没有使用baidu-rpc,看[这里](http://wiki.baidu.com/pages/viewpage.action?pageId=213843633)
3. 如果只是baidu-rpc client或没有使用baidu-rpc,看[这里](dummy_server.md)
注意要关闭Server端的认证,否则可能会看到这个:
......@@ -38,7 +38,7 @@ cpu profiler的原理是在定期被调用的SIGPROF handler中采样所在线
![img](http://wiki.baidu.com/download/attachments/165876310/image2016-1-19%2023%3A28%3A21.png?version=1&modificationDate=1453217323000&api=v2)
你也可以使用[public/baidu-rpc/tools/pprof](https://svn.baidu.com/public/trunk/baidu-rpc/tools/pprof)或gperftools中的pprof进行profiling。
你也可以使用[public/baidu-rpc/tools/pprof](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/tools/pprof)或gperftools中的pprof进行profiling。
比如`pprof --text localhost:9002 --seconds=5`的意思是统计运行在本机9002端口的server的cpu情况,时长5秒。一次运行的例子如下:
......@@ -90,4 +90,4 @@ Total: 2954 samples
38 1.3% 64.9% 38 1.3% epoll_ctl
37 1.3% 66.1% 37 1.3% memcpy
35 1.2% 67.3% 35 1.2% baidu::rpc::Socket::Address
```
\ No newline at end of file
```
......@@ -9,7 +9,7 @@
# 没有使用baidu-rpc
你必须手动加入dummy server。你得先查看[Getting Started](http://wiki.baidu.com/display/RPC/Getting+Started)如何下载和编译baidu-rpc,然后在程序入口处加入如下代码片段:
你必须手动加入dummy server。你得先查看[Getting Started](getting_started.md)如何下载和编译baidu-rpc,然后在程序入口处加入如下代码片段:
```c++
#include <baidu/rpc/server.h>
......@@ -41,4 +41,4 @@ int main() {
baidu::rpc::StartDummyServerAt(8888/*port*/);
...
}
```
\ No newline at end of file
```
baidu-rpc使用[baidu::rpc::Controller](https://svn.baidu.com/public/trunk/baidu-rpc/src/baidu/rpc/controller.h)设置一次RPC的参数和获取一次RPC的结果,ErrorCode()和ErrorText()是Controller的两个方法,分别是该次RPC的错误码和错误描述,只在RPC结束后才能访问,否则结果未定义。ErrorText()由Controller的基类google::protobuf::RpcController定义,ErrorCode()则是baidu::rpc::Controller定义的。Controller还有个Failed()方法告知该次RPC是否失败,这三者的关系是:
baidu-rpc使用[baidu::rpc::Controller](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/src/brpc/controller.h)设置一次RPC的参数和获取一次RPC的结果,ErrorCode()和ErrorText()是Controller的两个方法,分别是该次RPC的错误码和错误描述,只在RPC结束后才能访问,否则结果未定义。ErrorText()由Controller的基类google::protobuf::RpcController定义,ErrorCode()则是baidu::rpc::Controller定义的。Controller还有个Failed()方法告知该次RPC是否失败,这三者的关系是:
- 当Failed()为true时,ErrorCode()一定不为0,ErrorText()是非空的错误描述
- 当Failed()为false时,ErrorCode()一定为0,ErrorText()是未定义的(目前在baidu-rpc中会为空,但你最好不要依赖这个事实)
......@@ -13,9 +13,9 @@ server端Controller的SetFailed()常由用户在服务回调中调用。当处
# baidu-rpc的错误码
baidu-rpc使用的所有ErrorCode都定义在[errno.proto](https://svn.baidu.com/public/trunk/baidu-rpc/protocol/baidu/rpc/errno.proto)中,*SYS_*开头的来自linux系统,与/usr/include/errno.h中定义的精确一致,定义在proto中是为了跨语言。其余的是baidu-rpc自有的。
baidu-rpc使用的所有ErrorCode都定义在[errno.proto](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/protocol/baidu/rpc/errno.proto)中,*SYS_*开头的来自linux系统,与/usr/include/errno.h中定义的精确一致,定义在proto中是为了跨语言。其余的是baidu-rpc自有的。
[berror(error_code)](https://svn.baidu.com/public/trunk/common/base/errno.h)可获得error_code的描述,berror()可获得[system errno](http://www.cplusplus.com/reference/cerrno/errno/)的描述。**ErrorText() != berror(****ErrorCode())**,ErrorText()会包含更具体的错误信息。baidu-rpc默认包含berror所属的[public/common模块](http://wiki.baidu.com/pages/viewpage.action?pageId=38035224),你可以直接使用。
[berror(error_code)](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/src/base/errno.h)可获得error_code的描述,berror()可获得[system errno](http://www.cplusplus.com/reference/cerrno/errno/)的描述。**ErrorText() != berror(****ErrorCode())**,ErrorText()会包含更具体的错误信息。baidu-rpc默认包含berror,你可以直接使用。
baidu-rpc中常见错误的打印内容列表如下:
......@@ -74,4 +74,4 @@ Fail to define EMYERROR(30) which is already defined as `Read-only file system',
总的来说这和RPC框架没什么关系,直到你希望通过RPC框架传递ErrorCode。这个需求很自然,不过你得确保不同的模块对ErrorCode的理解是相同的,否则当两个模块把一个错误码理解为不同的错误时,它们之间的交互将出现无法预计的行为。为了防止这种情况出现,你最好这么做:
- 优先使用系统错误码,它们的值和含义是固定不变的。
- 多个交互的模块使用同一份错误码定义,防止后续修改时产生不一致。
- 使用BAIDU_REGISTER_ERRNO描述新错误码,以确保同一个进程内错误码是互斥的。
\ No newline at end of file
- 使用BAIDU_REGISTER_ERRNO描述新错误码,以确保同一个进程内错误码是互斥的。
......@@ -184,4 +184,4 @@ high_priority的task之间的执行顺序也会**严格按照提交顺序**, 这
int execution_queue_cancel(const TaskHandle& h);
```
返回非0仅仅意味着ExecutionQueue已经将对应的task递给过execute, 真实的逻辑中可能将这个task缓存在另外的容器中,所以这并不意味着逻辑上的task已经结束,你需要在自己的业务上保证这一点.
\ No newline at end of file
返回非0仅仅意味着ExecutionQueue已经将对应的task递给过execute, 真实的逻辑中可能将这个task缓存在另外的容器中,所以这并不意味着逻辑上的task已经结束,你需要在自己的业务上保证这一点.
......@@ -138,4 +138,4 @@ Name | Value | Description | Defined At
health_check_interval (R) | 3 | seconds between consecutive health-checkings | src/baidu/rpc/socket_map.cpp
```
1.0.251.32399后增加了-immutable_flags,打开后所有的gflags将不能被动态修改。当一个服务对某个gflag值比较敏感且不希望在线上被误改,可打开这个开关。打开这个开关的同时也意味着你无法动态修改线上的配置,每次修改都要重启程序,对于还在调试阶段或待收敛阶段的程序不建议打开。
\ No newline at end of file
1.0.251.32399后增加了-immutable_flags,打开后所有的gflags将不能被动态修改。当一个服务对某个gflag值比较敏感且不希望在线上被误改,可打开这个开关。打开这个开关的同时也意味着你无法动态修改线上的配置,每次修改都要重启程序,对于还在调试阶段或待收敛阶段的程序不建议打开。
......@@ -3,7 +3,7 @@
在命令行中运行如下命令即可在~/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
http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob && 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
```
......@@ -15,7 +15,7 @@ https://svn.baidu.com/public/trunk/baidu-rpc && cd baidu-rpc && comake2 -UB -J8
```
CONFIGS('public/baidu-rpc@ci-base')
```
这依赖了baidu-rpc的最新发布版本。模板可参考[echo的COMAKE文件](https://svn.baidu.com/public/trunk/baidu-rpc/example/echo_c++/COMAKE)。然后运行:
这依赖了baidu-rpc的最新发布版本。模板可参考[echo的COMAKE文件](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/example/echo_c++/COMAKE)。然后运行:
```
$ comake2 -UB # 下载所有的依赖模块
......@@ -132,7 +132,7 @@ baidu-rpc默认的依赖2.4,你可以在你项目的COMAKE或BCLOUD中指定
### 关于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就行了。
3把NewCallback设置为私有,r32035后baidu-rpc把NewCallback独立于[src/baidu/rpc/callback.h](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/src/brpc/callback.h),如果你的程序出现NewCallback相关的编译错误(不论protobuf的版本),把google::protobuf::NewCallback替换为baidu::rpc::NewCallback就行了。
### 编译.proto
......@@ -151,7 +151,7 @@ $ protoc --cpp_out=DEST_PATH -I=PROTO_PATH your.proto
### 同时兼容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"。
勿使用proto3的新类型,proto文件开头要加上syntax="proto2";,[tools/add_syntax_equal_proto2_to_all.sh](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/tools/add_syntax_equal_proto2_to_all.sh)可以给目录以下的所有没有加的proto文件加上syntax="proto2"。
## boost
......@@ -241,7 +241,7 @@ r35109后支持1.1
| 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 | RetryPolicy | 可定制重试策略,详见[重试](client.md#错误值得重试) |
| 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) |
......@@ -251,7 +251,7 @@ r35109后支持1.1
### Q: baidu-rpc会不会发布稳定版本
本项目是主干开发,最新的改动在[trunk](https://svn.baidu.com/public/trunk/baidu-rpc/),发布在[agile上](http://agile.baidu.com/#/builds/public/baidu-rpc@trunk)。我们会尽量保持已有接口不变,升级新版本一般不会break代码。由于开发节奏快,我们没有发布Releasing
本项目是主干开发,最新的改动在[trunk](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/),发布在[agile上](http://agile.baidu.com/#/builds/public/baidu-rpc@trunk)。我们会尽量保持已有接口不变,升级新版本一般不会break代码。由于开发节奏快,我们没有发布Releasing
Branch (RB)的计划。
使用ci-base是更安全的选择。老版本的使用者更稀疏一些,bug会更加隐秘,发现得更晚。这种注意不到的bug会真正影响到策略的判断和迭代。而ci-base你在用,其他产品线也在用,问题很快能被发现和纠正。
......
......@@ -18,7 +18,7 @@ baidu-rpc可以分析内存是被哪些函数占据的。heap profiler的原理
/home/gejun/pprof/echo_server.1419559063.localhost.pprof.heap: header size >= 2**16
```
4. 如果只是baidu-rpc client或没有使用baidu-rpc,看[这里](http://wiki.baidu.com/pages/viewpage.action?pageId=213843633)。
4. 如果只是baidu-rpc client或没有使用baidu-rpc,看[这里](dummy_server.md)。
注意要关闭Server端的认证,否则可能会看到这个:
......
http client的例子见[example/http_c++](https://svn.baidu.com/public/trunk/baidu-rpc/example/http_c++/http_client.cpp)
http client的例子见[example/http_c++](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/example/http_c++/http_client.cpp)
# 创建Channel
......@@ -31,7 +31,7 @@ HTTP和protobuf无关,所以除了Controller和done,CallMethod的其他参
# POST
默认的HTTP Method为GET,如果需要做POST,则需要设置。待POST的数据应置入request_attachment(),它([base::IOBuf](https://svn.baidu.com/public/trunk/iobuf/base/iobuf.h))可以直接append std::string或char*
默认的HTTP Method为GET,如果需要做POST,则需要设置。待POST的数据应置入request_attachment(),它([base::IOBuf](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/src/base/iobuf.h))可以直接append std::string或char*
```c++
baidu::rpc::Controller cntl;
......@@ -57,7 +57,7 @@ channel.CallMethod(NULL, &cntl, NULL, NULL, NULL/*done*/);
URL的一般形式如下图:
```c++
```
// URI scheme : http://en.wikipedia.org/wiki/URI_scheme
//
// foo://username:password@example.com:8042/over/there/index.dtb?type=animal&name=narwhal#nose
......@@ -88,24 +88,47 @@ URL的一般形式如下图:
# 常见设置
**以http request为例(对response的操作自行替换)**,常见操作方式如下表所示:
| 操作 | 方法 |
| -------------- | ---------------------------------------- |
| 访问名为Foo的header | `const std::string* value = cntl->http_request().GetHeader("Foo");` |
| | // 不存在为NULL |
| 设置名为Foo的header | `cntl->http_request().SetHeader("Foo", "value")` |
| 访问名为Foo的query | `const std::string* value = cntl->http_request().uri().GetQuery("Foo");` |
| | // 不存在为NULL |
| 设置名为Foo的query | `cntl->http_request().uri().SetQuery("Foo", "value")` |
| 设置HTTP方法 | `cntl->http_request().set_method(baidu::rpc::HTTP_METHOD_POST);` |
| 设置url | `cntl->http_request().uri() = "http://www.baidu.com";` |
| 设置content-type | `cntl->http_request().set_content_type("text/plain");` |
| 访问body | `base::IOBuf& buf = cntl->request_attachment();` |
| | `std::string str = cntl->request_attachment().to_string();` // 有拷贝 |
| 设置body | `cntl->request_attachment().append("....");` |
| | `base::IOBufBuilder os;``os << "....";` |
| | `os.move_to(cntl->request_attachment());` |
以http request为例 (对response的操作自行替换), 常见操作方式如下所示:
访问名为Foo的header
```c++
const std::string* value = cntl->http_request().GetHeader("Foo"); //不存在为NULL
```
设置名为Foo的header
```c++
cntl->http_request().SetHeader("Foo", "value");
```
访问名为Foo的query
```c++
const std::string* value = cntl->http_request().uri().GetQuery("Foo"); // 不存在为NULL
```
设置名为Foo的query
```c++
cntl->http_request().uri().SetQuery("Foo", "value");
```
设置HTTP方法
```c++
cntl->http_request().set_method(baidu::rpc::HTTP_METHOD_POST);
```
设置url
```c++
cntl->http_request().uri() = "http://www.baidu.com";
```
设置content-type
```c++
cntl->http_request().set_content_type("text/plain");
```
访问body
```c++
base::IOBuf& buf = cntl->request_attachment();
std::string str = cntl->request_attachment().to_string(); // 有拷贝
```
设置body
```c++
cntl->request_attachment().append("....");
base::IOBufBuilder os; os << "....";
os.move_to(cntl->request_attachment());
```
Notes on http header:
......@@ -126,7 +149,7 @@ Notes on http header:
# 压缩request body
在r33877后,调用`Controller::set_request_compress_type(baidu::rpc::COMPRESS_TYPE_GZIP)`可将http body用gzip压缩,并设置"Content-Encoding"为"gzip"。
在r33877后,调用Controller::set_request_compress_type(baidu::rpc::COMPRESS_TYPE_GZIP)可将http body用gzip压缩,并设置"Content-Encoding"为"gzip"。
# 解压response body
......@@ -178,15 +201,12 @@ r33796后baidu-rpc client支持在读取完body前就结束RPC,让用户在RPC
virtual void OnEndOfMessage(const base::Status& status) = 0;
};
```
OnReadOnePart在每读到一段数据时被调用,OnEndOfMessage在数据结束或连接断开时调用,实现前仔细阅读注释。
2. 发起RPC前设置`cntl.response_will_be_read_progressively();`
这告诉baidu-rpc在读取http response时只要读完header部分RPC就可以结束了。
3. RPC结束后调用`cntl.ReadProgressiveAttachmentBy(new MyProgressiveReader);`
MyProgressiveReader就是用户实现ProgressiveReader的实例。用户可以在这个实例的OnEndOfMessage接口中删除这个实例。
# 持续上传
......@@ -195,4 +215,4 @@ r33796后baidu-rpc client支持在读取完body前就结束RPC,让用户在RPC
# 访问带认证的Server
根据Server的认证方式生成对应的auth_data,并设置为http header "Authorization"的值。比如用的是curl,那就加上选项`-H "Authorization : <auth_data>"。`查询[giano文档](http://doc.noah.baidu.com/new/baas/base_tool.md)了解如何在Shell中生成auth_data。
\ No newline at end of file
根据Server的认证方式生成对应的auth_data,并设置为http header "Authorization"的值。比如用的是curl,那就加上选项`-H "Authorization : <auth_data>"。`查询[giano文档](http://doc.noah.baidu.com/new/baas/base_tool.md)了解如何在Shell中生成auth_data。
这里特指“纯粹"的HTTP service,而不是可通过HTTP访问的pb服务。虽然用不到pb消息,但“纯粹”的HTTP Service也必须定义在.proto文件中,只是request和response都是空的结构体。这么做是确保所有的服务声明集中在proto文件中,而不是散列在.proto、程序、配置等多个地方。示例代码见[http_server.cpp](https://svn.baidu.com/public/trunk/baidu-rpc/example/http_c++/http_server.cpp)
这里特指“纯粹"的HTTP service,而不是可通过HTTP访问的pb服务。虽然用不到pb消息,但“纯粹”的HTTP Service也必须定义在.proto文件中,只是request和response都是空的结构体。这么做是确保所有的服务声明集中在proto文件中,而不是散列在.proto、程序、配置等多个地方。示例代码见[http_server.cpp](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/example/http_c++/http_server.cpp)
# URL前缀为/ServiceName/MethodName
......@@ -226,7 +226,7 @@ cntl->http_response().set_content_type("text/html");
## Status Code
status code是http response特有的字段,标记http请求的完成情况。请使用定义在[http_status_code.h](https://svn.baidu.com/public/trunk/baidu-rpc/src/baidu/rpc/http_status_code.h)中的enum,遵守HTTP协议。
status code是http response特有的字段,标记http请求的完成情况。请使用定义在[http_status_code.h](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/src/brpc/http_status_code.h)中的enum,遵守HTTP协议。
```c++
// Get Status Code
......@@ -250,7 +250,7 @@ cntl->http_response().SetHeader("Location", "http://bj.bs.bae.baidu.com/family/i
## Query String
如上面的[HTTP headers](http://wiki.baidu.com/pages/viewpage.action?pageId=213828736#id-实现HTTPService-HTTPheaders)中提到的那样,我们按约定成俗的方式来理解query string,即key1=value1&key2=value2&...。只有key而没有value也是可以的,仍然会被GetQuery查询到,只是值为空字符串,这常被用做bool型的开关。接口定义在[uri.h](https://svn.baidu.com/public/trunk/baidu-rpc/src/baidu/rpc/uri.h)
如上面的[HTTP headers](http_service.md#http-headers)中提到的那样,我们按约定成俗的方式来理解query string,即key1=value1&key2=value2&...。只有key而没有value也是可以的,仍然会被GetQuery查询到,只是值为空字符串,这常被用做bool型的开关。接口定义在[uri.h](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/src/brpc/uri.h)
```c++
const std::string* time_value = cntl->http_request().uri().GetQuery("time");
......@@ -340,7 +340,7 @@ bool Controller::is_ssl() const;
没有极端性能要求的产品线都有使用HTTP协议的倾向,特别是移动端产品线,所以我们很重视HTTP的实现质量,具体来说:
- 使用了node.js的[http parser](https://svn.baidu.com/public/trunk/baidu-rpc/src/baidu/rpc/http_parser.h)(部分来自nginx)解析http消息,这是一个轻量、优秀的实现。
- 使用了node.js的[http parser](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/src/brpc/http_parser.h)(部分来自nginx)解析http消息,这是一个轻量、优秀的实现。
- 使用[rapidjson](https://github.com/miloyip/rapidjson)解析json,这是一个主打性能的json库,由一位腾讯专家开发。
- 在最差情况下解析http请求的时间复杂度也是O(N),其中N是请求的字节数。反过来说,如果解析代码要求http请求是完整的,那么它可能会花费O(N^2)的时间。HTTP请求普遍较大,这一点意义还是比较大的。
- 来自不同client的http消息是高度并发的,即使相当复杂的http消息也不会影响对其他客户端的响应。其他rpc和[基于单线程reactor](http://wiki.baidu.com/display/RPC/Threading+Overview#ThreadingOverview-单线程reactor)的各类http server往往难以做到这一点。
......@@ -390,4 +390,4 @@ baidu-rpc server同端口支持多种协议,当它遇到非法HTTP请求并解
Base64 编码后的字符串中,会以"="或者"=="作为结尾(比如: ?wi=NDgwMDB8dGVzdA==&anothorkey=anothervalue), 这个字段可能会被正确解析,也可能不会,取决于具体实现,用户不应该做任何假设.
一个解决方法是删除末尾的"=", 不影响Base64的[正常解码](http://en.wikipedia.org/wiki/Base64#Padding); 第二个方法是在这个URI在base64之后在使用%编码,使用的地方先进行%解码,然后再用base64解码.
\ No newline at end of file
一个解决方法是删除末尾的"=", 不影响Base64的[正常解码](http://en.wikipedia.org/wiki/Base64#Padding); 第二个方法是在这个URI在base64之后在使用%编码,使用的地方先进行%解码,然后再用base64解码.
......@@ -8,17 +8,17 @@ linux一般使用non-blocking IO提高IO并发度。当IO并发度很低时,no
# 收消息
“消息”指从连接读入的有边界的二进制串,可能是来自上游client的request或来自下游server的response。baidu-rpc使用一个或多个[EventDispatcher](https://svn.baidu.com/public/trunk/baidu-rpc/src/baidu/rpc/event_dispatcher.h)(简称为EDISP)等待任一fd发生事件。和常见的“IO线程”不同,EDISP不负责读取。IO线程的问题在于一个线程同时只能读一个fd,当多个繁忙的fd聚集在一个IO线程中时,一些读取就被延迟了。多租户、复杂分流算法,[Streaming RPC](http://wiki.baidu.com/pages/viewpage.action?pageId=152229270)等功能会加重这个问题。高负载下偶尔的长延时read也会拖慢一个IO线程中所有fd的读取,对可用性的影响幅度较大。
“消息”指从连接读入的有边界的二进制串,可能是来自上游client的request或来自下游server的response。baidu-rpc使用一个或多个[EventDispatcher](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/src/brpc/event_dispatcher.h)(简称为EDISP)等待任一fd发生事件。和常见的“IO线程”不同,EDISP不负责读取。IO线程的问题在于一个线程同时只能读一个fd,当多个繁忙的fd聚集在一个IO线程中时,一些读取就被延迟了。多租户、复杂分流算法,[Streaming RPC](streaming_rpc.md)等功能会加重这个问题。高负载下偶尔的长延时read也会拖慢一个IO线程中所有fd的读取,对可用性的影响幅度较大。
由于epoll的[一个bug](https://patchwork.kernel.org/patch/1970231/)及epoll_ctl较大的开销,EDISP使用Edge triggered模式。当收到事件时,EDISP给一个原子变量加1,只有当加1前的值是0时启动一个bthread处理对应fd上的数据。在背后,EDISP把所在的pthread让给了新建的bthread,使其有更好的cache locality,可以尽快地读取fd上的数据。而EDISP所在的bthread会被偷到另外一个pthread继续执行,这个过程即是bthread的work stealing调度。要准确理解那个原子变量的工作方式可以先阅读[atomic instructions](http://wiki.baidu.com/pages/viewpage.action?pageId=36886832),再看[Socket::StartInputEvent](https://svn.baidu.com/public/trunk/baidu-rpc/src/baidu/rpc/socket.cpp)。这些方法使得baidu-rpc读取同一个fd时产生的竞争是[wait-free](http://en.wikipedia.org/wiki/Non-blocking_algorithm#Wait-freedom)的。
由于epoll的[一个bug](https://patchwork.kernel.org/patch/1970231/)及epoll_ctl较大的开销,EDISP使用Edge triggered模式。当收到事件时,EDISP给一个原子变量加1,只有当加1前的值是0时启动一个bthread处理对应fd上的数据。在背后,EDISP把所在的pthread让给了新建的bthread,使其有更好的cache locality,可以尽快地读取fd上的数据。而EDISP所在的bthread会被偷到另外一个pthread继续执行,这个过程即是bthread的work stealing调度。要准确理解那个原子变量的工作方式可以先阅读[atomic instructions](atomic_instructions.md),再看[Socket::StartInputEvent](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/src/brpc/socket.cpp)。这些方法使得baidu-rpc读取同一个fd时产生的竞争是[wait-free](http://en.wikipedia.org/wiki/Non-blocking_algorithm#Wait-freedom)的。
[InputMessenger](https://svn.baidu.com/public/trunk/baidu-rpc/src/baidu/rpc/input_messenger.h)负责从fd上切割和处理消息,它通过用户回调函数理解不同的格式。Parse一般是把消息从二进制流上切割下来,运行时间较固定;Process则是进一步解析消息(比如反序列化为protobuf)后调用用户回调,时间不确定。InputMessenger会逐一尝试用户指定的多套回调,当某一个Parse成功切割下一个消息后,调用对应的Process。由于一个连接上往往只有一种消息格式,InputMessenger会记录下上次的选择,而避免每次都重复尝试。若一次从某个fd读取出n个消息(n > 1),InputMessenger会启动n-1个bthread分别处理前n-1个消息,最后一个消息则会在原地被Process。
[InputMessenger](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/src/brpc/input_messenger.h)负责从fd上切割和处理消息,它通过用户回调函数理解不同的格式。Parse一般是把消息从二进制流上切割下来,运行时间较固定;Process则是进一步解析消息(比如反序列化为protobuf)后调用用户回调,时间不确定。InputMessenger会逐一尝试用户指定的多套回调,当某一个Parse成功切割下一个消息后,调用对应的Process。由于一个连接上往往只有一种消息格式,InputMessenger会记录下上次的选择,而避免每次都重复尝试。若一次从某个fd读取出n个消息(n > 1),InputMessenger会启动n-1个bthread分别处理前n-1个消息,最后一个消息则会在原地被Process。
可以看到,fd间和fd内的消息都会在baidu-rpc中获得并发,这使baidu-rpc非常擅长大消息的读取,在高负载时仍能及时处理不同来源的消息,减少长尾的存在。
# 发消息
"消息”指向连接写出的有边界的二进制串,可能是发向上游client的response或下游server的request。多个线程可能会同时向一个fd发送消息,而写fd又是非原子的,所以如何高效率地排队不同线程写出的数据包是这里的关键。baidu-rpc使用一种wait-free MPSC链表来实现这个功能。所有待写出的数据都放在一个单链表节点中,next指针初始化为一个特殊值(Socket::WriteRequest::UNCONNECTED)。当一个线程想写出数据前,它先尝试和对应的链表头(Socket::_write_head)做原子交换,返回值是交换前的链表头。如果返回值为空,说明它获得了写出的权利,它会在原地写一次数据。否则说明有另一个线程在写,它把next指针指向返回的头,那样正在写的线程之后会看到并写出这块数据。这套方法可以让写竞争是wait-free的,而获得写权利的线程虽然在原理上不是wait-free也不是lock-free,可能会被一个值仍为UNCONNECTED的节点锁定(这需要发起写的线程正好在原子交换后,在设置next指针前,仅仅一条指令的时间内被OS换出),但在实践中很少出现。在当前的实现中,如果获得写权利的线程一下子无法写出所有的数据,会启动一个KeepWrite线程继续写,直到所有的数据都被写出。这套逻辑非常复杂,大致原理如下图,细节请阅读[socket.cpp](https://svn.baidu.com/public/trunk/baidu-rpc/src/baidu/rpc/socket.cpp)
"消息”指向连接写出的有边界的二进制串,可能是发向上游client的response或下游server的request。多个线程可能会同时向一个fd发送消息,而写fd又是非原子的,所以如何高效率地排队不同线程写出的数据包是这里的关键。baidu-rpc使用一种wait-free MPSC链表来实现这个功能。所有待写出的数据都放在一个单链表节点中,next指针初始化为一个特殊值(Socket::WriteRequest::UNCONNECTED)。当一个线程想写出数据前,它先尝试和对应的链表头(Socket::_write_head)做原子交换,返回值是交换前的链表头。如果返回值为空,说明它获得了写出的权利,它会在原地写一次数据。否则说明有另一个线程在写,它把next指针指向返回的头,那样正在写的线程之后会看到并写出这块数据。这套方法可以让写竞争是wait-free的,而获得写权利的线程虽然在原理上不是wait-free也不是lock-free,可能会被一个值仍为UNCONNECTED的节点锁定(这需要发起写的线程正好在原子交换后,在设置next指针前,仅仅一条指令的时间内被OS换出),但在实践中很少出现。在当前的实现中,如果获得写权利的线程一下子无法写出所有的数据,会启动一个KeepWrite线程继续写,直到所有的数据都被写出。这套逻辑非常复杂,大致原理如下图,细节请阅读[socket.cpp](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/src/brpc/socket.cpp)
![img](http://wiki.baidu.com/download/attachments/48480438/image2015-12-20%2019%3A0%3A23.png?version=1&modificationDate=1450609228000&api=v2)
......@@ -26,7 +26,7 @@ linux一般使用non-blocking IO提高IO并发度。当IO并发度很低时,no
# Socket
和fd相关的数据均在[Socket](https://svn.baidu.com/public/trunk/baidu-rpc/src/baidu/rpc/socket.h)中,是rpc最复杂的结构之一,这个结构的独特之处在于用64位的SocketId指代Socket对象以方便在多线程环境下使用fd。常用的三个方法:
和fd相关的数据均在[Socket](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/src/brpc/socket.h)中,是rpc最复杂的结构之一,这个结构的独特之处在于用64位的SocketId指代Socket对象以方便在多线程环境下使用fd。常用的三个方法:
- Create:创建Socket,并返回其SocketId。
- Address:取得id对应的Socket,包装在一个会自动释放的unique_ptr中(SocketUniquePtr),当Socket被SetFailed后,返回指针为空。只要Address返回了非空指针,其内容保证不会变化,直到指针自动析构。这个函数是wait-free的。
......
baidu-rpc使用[base::IOBuf](https://svn.baidu.com/public/trunk/iobuf/base/iobuf.h)作为存储附件或http body的数据结构,它是一种非连续零拷贝缓冲,在其他项目中得到了验证并有出色的性能。IOBuf的接口和std::string类似,但不相同。
baidu-rpc使用[base::IOBuf](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/src/base/iobuf.h)作为存储附件或http body的数据结构,它是一种非连续零拷贝缓冲,在其他项目中得到了验证并有出色的性能。IOBuf的接口和std::string类似,但不相同。
如果你之前使用Kylin中的BufHandle,你将更能感受到IOBuf的便利性:前者几乎没有实现完整,直接暴露了内部结构,用户得小心翼翼地处理引用计数,极易出错。BufHandle是很多bug的诱因。
......@@ -92,4 +92,4 @@ IOBuf有很高的综合性能:
- 从文件读入->切割12+16字节->拷贝->合并到另一个缓冲->写出到/dev/null这一流程的吞吐是240.423MB/s或8586535次/s
- 从文件读入->切割12+128字节->拷贝->合并到另一个缓冲->写出到/dev/null这一流程的吞吐是790.022MB/s或5643014次/s
- 从文件读入->切割12+1024字节->拷贝->合并到另一个缓冲->写出到/dev/null这一流程的吞吐是1519.99MB/s或1467171次/s
\ No newline at end of file
- 从文件读入->切割12+1024字节->拷贝->合并到另一个缓冲->写出到/dev/null这一流程的吞吐是1519.99MB/s或1467171次/s
......@@ -101,7 +101,7 @@ LoadBalancer是一个读远多于写的数据结构:大部分时候,所有
- 不同的读之间没有竞争,高度并发。
- 如果没有写,读总是能无竞争地获取和释放thread-local锁,一般小于25ns,对延时基本无影响。如果有写,由于其临界区极小(拿到立刻释放),读在大部分时候仍能快速地获得锁,少数时候释放锁时可能有唤醒写线程的代价。由于写本身就是少数情况,读整体上几乎不会碰到竞争锁。
完成这些功能的数据结构是[DoublyBufferedData<>](https://svn.baidu.com/public/trunk/common/base/containers/doubly_buffered_data.h),我们常简称为DBD。baidu-rpc中的所有load balancer都使用了这个数据结构,使不同线程在分流时几乎不会互斥。而其他rpc实现往往使用了全局锁,这使得它们无法写出复杂的分流算法:否则分流代码将会成为竞争热点。
完成这些功能的数据结构是[DoublyBufferedData<>](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/src/base/containers/doubly_buffered_data.h),我们常简称为DBD。baidu-rpc中的所有load balancer都使用了这个数据结构,使不同线程在分流时几乎不会互斥。而其他rpc实现往往使用了全局锁,这使得它们无法写出复杂的分流算法:否则分流代码将会成为竞争热点。
这个结构有广泛的应用场景:
......@@ -150,4 +150,4 @@ Icon
- 反馈时扣除发出时间和未结束次数。
- 框架保证每个选择总对应一次反馈。
这样“当前时间 - 发出时间之和 / 未结束次数”便是未结束RPC的平均耗时,我们称之为inflight delay。当inflight delay大于平均延时时,我们就线性地惩罚节点权值,即weight = base_weight * avg_latency / inflight_delay。当发向一个节点的请求没有在平均延时内回来时,它的权值就会很快下降,从而纠正我们的行为,这比等待超时快多了。不过这没有考虑延时的正常抖动,我们还得有方差,方差可以来自统计,也可简单线性于平均延时。不管怎样,有了方差bound后,当inflight delay > avg_latency + max(bound * 3, MIN_BOUND)时才会惩罚权值。3是正态分布中的经验数值。
\ No newline at end of file
这样“当前时间 - 发出时间之和 / 未结束次数”便是未结束RPC的平均耗时,我们称之为inflight delay。当inflight delay大于平均延时时,我们就线性地惩罚节点权值,即weight = base_weight * avg_latency / inflight_delay。当发向一个节点的请求没有在平均延时内回来时,它的权值就会很快下降,从而纠正我们的行为,这比等待超时快多了。不过这没有考虑延时的正常抖动,我们还得有方差,方差可以来自统计,也可简单线性于平均延时。不管怎样,有了方差bound后,当inflight delay > avg_latency + max(bound * 3, MIN_BOUND)时才会惩罚权值。3是正态分布中的经验数值。
......@@ -2,10 +2,10 @@
# 名字服务
在baidu-rpc中,[NamingService](https://svn.baidu.com/public/trunk/baidu-rpc/src/baidu/rpc/naming_service.h)用于获得服务名对应的所有节点。一个直观的做法是定期调用一个函数以获取最新的节点列表。但这会带来一定的延时(定期调用的周期一般在若干秒左右),作为通用接口不太合适。特别当名字服务提供事件通知时(比如zk),这个特性没有被利用。所以我们反转了控制权:不是我们调用用户函数,而是用户在获得列表后调用我们的接口,对应[NamingServiceActions](https://svn.baidu.com/public/trunk/baidu-rpc/src/baidu/rpc/naming_service.h)。当然我们还是得启动进行这一过程的函数,对应NamingService::RunNamingService。下面以三个实现解释这套方式:
在baidu-rpc中,[NamingService](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/src/brpc/naming_service.h)用于获得服务名对应的所有节点。一个直观的做法是定期调用一个函数以获取最新的节点列表。但这会带来一定的延时(定期调用的周期一般在若干秒左右),作为通用接口不太合适。特别当名字服务提供事件通知时(比如zk),这个特性没有被利用。所以我们反转了控制权:不是我们调用用户函数,而是用户在获得列表后调用我们的接口,对应[NamingServiceActions](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/src/brpc/naming_service.h)。当然我们还是得启动进行这一过程的函数,对应NamingService::RunNamingService。下面以三个实现解释这套方式:
- bns:没有事件通知,所以我们只能定期去获得最新列表,默认间隔是[5秒](http://brpc.baidu.com:8765/flags/ns_access_interval)。为了简化这类定期获取的逻辑,baidu-rpc提供了[PeriodicNamingService](https://svn.baidu.com/public/trunk/baidu-rpc/src/baidu/rpc/periodic_naming_service.h) 供用户继承,用户只需要实现单次如何获取(GetServers)。获取后调用NamingServiceActions::ResetServers告诉框架。框架会对列表去重,和之前的列表比较,通知对列表有兴趣的观察者(NamingServiceWatcher)。这套逻辑会运行在独立的bthread中,即NamingServiceThread。一个NamingServiceThread可能被多个Channel共享,通过intrusive_ptr管理ownership。
- file:列表即文件。合理的方式是在文件更新后重新读取。[该实现](https://svn.baidu.com/public/trunk/baidu-rpc/src/baidu/rpc/policy/file_naming_service.cpp)使用[FileWatcher](https://svn.baidu.com/public/trunk/common/base/file_watcher.h)关注文件的修改时间,当文件修改后,读取并调用NamingServiceActions::ResetServers告诉框架。
- bns:没有事件通知,所以我们只能定期去获得最新列表,默认间隔是[5秒](http://brpc.baidu.com:8765/flags/ns_access_interval)。为了简化这类定期获取的逻辑,baidu-rpc提供了[PeriodicNamingService](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/src/brpc/periodic_naming_service.h) 供用户继承,用户只需要实现单次如何获取(GetServers)。获取后调用NamingServiceActions::ResetServers告诉框架。框架会对列表去重,和之前的列表比较,通知对列表有兴趣的观察者(NamingServiceWatcher)。这套逻辑会运行在独立的bthread中,即NamingServiceThread。一个NamingServiceThread可能被多个Channel共享,通过intrusive_ptr管理ownership。
- file:列表即文件。合理的方式是在文件更新后重新读取。[该实现](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/src/brpc/policy/file_naming_service.cpp)使用[FileWatcher](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/src/base/file_watcher.h)关注文件的修改时间,当文件修改后,读取并调用NamingServiceActions::ResetServers告诉框架。
- list:列表就在服务名里(逗号分隔)。在读取完一次并调用NamingServiceActions::ResetServers后就退出了,因为列表再不会改变了。
如果用户需要建立这些对象仍然是不够方便的,因为总是需要一些工厂代码根据配置项建立不同的对象,鉴于此,我们把工厂类做进了框架,并且是非常方便的形式:
......@@ -20,7 +20,7 @@ list://addr1,addr2,... # use the addresses separated by comma
http://<url> # Domain Naming Service, aka DNS.
```
这套方式是可扩展的,实现了新的NamingService后在[global.cpp](https://svn.baidu.com/public/trunk/baidu-rpc/src/baidu/rpc/global.cpp)中依葫芦画瓢注册下就行了,如下图所示:
这套方式是可扩展的,实现了新的NamingService后在[global.cpp](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/src/brpc/global.cpp)中依葫芦画瓢注册下就行了,如下图所示:
![img](http://wiki.baidu.com/download/attachments/158717014/image2015-12-19%2016%3A59%3A8.png?version=1&modificationDate=1450515550000&api=v2)
......@@ -28,9 +28,9 @@ http://<url> # Domain Naming Service, aka DNS.
# 负载均衡
baidu-rpc中[LoadBalancer](https://svn.baidu.com/public/trunk/baidu-rpc/src/baidu/rpc/load_balancer.h)从多个服务节点中选择一个节点,目前的实现见[负载均衡](http://wiki.baidu.com/pages/viewpage.action?pageId=213828685#id-创建和访问Client-负载均衡)
baidu-rpc中[LoadBalancer](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/src/brpc/load_balancer.h)从多个服务节点中选择一个节点,目前的实现见[负载均衡](client.md#负载均衡)
Load balancer最重要的是如何让不同线程中的负载均衡不互斥,解决这个问题的技术是[DoublyBufferedData](http://wiki.baidu.com/pages/viewpage.action?pageId=38012521#Locality-awareloadbalancing-DoublyBufferedData)
Load balancer最重要的是如何让不同线程中的负载均衡不互斥,解决这个问题的技术是[DoublyBufferedData](lalb.md#doublybuffereddata)
和NamingService类似,我们使用字符串来指代一个load balancer,在global.cpp中注册:
......@@ -44,4 +44,4 @@ Load balancer最重要的是如何让不同线程中的负载均衡不互斥,
- 健康检查线程先在确保没有其他人在使用Socket了后关闭连接。目前是通过对Socket的引用计数判断的。这个方法之所以有效在于Socket被SetFailed后就不能被Address了,所以引用计数只减不增。
- 定期连接直到远端机器被连接上,在这个过程中,如果Socket析构了,那么该线程也就随之退出了。
- 连上后复活Socket(Socket::Revive),这样Socket就又能被其他地方,包括LoadBalancer访问到了(通过Socket::Address)。
\ No newline at end of file
- 连上后复活Socket(Socket::Revive),这样Socket就又能被其他地方,包括LoadBalancer访问到了(通过Socket::Address)。
[memcached](http://memcached.org/)是常用的缓存服务,为了使用户更快捷地访问memcached并充分利用bthread的并发能力,baidu-rpc直接支持memcache协议。示例程序:<https://svn.baidu.com/public/trunk/baidu-rpc/example/memcache_c++/>
[memcached](http://memcached.org/)是常用的缓存服务,为了使用户更快捷地访问memcached并充分利用bthread的并发能力,baidu-rpc直接支持memcache协议。示例程序:<http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/example/memcache_c++/>
> 注意:baidu-rpc只支持memcache的二进制协议。memcached在1.3前只有文本协议,但在当前看来支持的意义甚微。如果你的memcached早于1.3,升级版本。
......@@ -7,7 +7,7 @@
- 线程安全。用户不需要为每个线程建立独立的client。
- 支持同步、异步、批量同步、批量异步等访问方式,能使用ParallelChannel等组合访问方式。
- 有明确的request和response。而libmemcached是没有的,收到的消息不能直接和发出的消息对应上,用户需要自己做维护工作。
- 支持多种[连接方式](http://wiki.baidu.com/pages/viewpage.action?pageId=213828702#id-访问Memcached-连接方式)。支持超时、backup request、取消、tracing、内置服务等一系列RPC基本福利。
- 支持多种[连接方式](client.md#连接方式)。支持超时、backup request、取消、tracing、内置服务等一系列RPC基本福利。
当前实现充分利用了RPC的并发机制并尽量避免了拷贝。一个client可以轻松地把一个同机memcached实例([版本1.4.15](https://svn.baidu.com/third-64/tags/memcached/memcached_1-4-15-100_PD_BL/))压到极限:单连接9万,多连接33万。在大部分情况下,baidu-rpc应该能充分发挥memcached的性能。
......@@ -99,4 +99,4 @@ bool PopVersion(std::string* version);
建立一个使用c_md5负载均衡算法的channel,每个MemcacheRequest只包含一个操作或确保所有的操作始终落在同一台server,就能访问挂载在对应名字服务下的memcached集群了。如果request包含了多个操作,在当前实现下这些操作总会送向同一个server。比方说一个request中包含了多个Get操作,而对应的key分布在多个server上,那么结果就肯定不对了,这个情况下你必须把一个request分开为多个。
或者你可以沿用常见的[twemproxy](https://github.com/twitter/twemproxy)方案。这个方案虽然需要额外部署proxy,还增加了延时,但client端仍可以像访问单点一样的访问它。
\ No newline at end of file
或者你可以沿用常见的[twemproxy](https://github.com/twitter/twemproxy)方案。这个方案虽然需要额外部署proxy,还增加了延时,但client端仍可以像访问单点一样的访问它。
内存管理总是程序中的重要一环,在多线程时代,一个好的内存分配大都在如下两点间权衡:
- 线程间竞争少。内存分配的粒度大都比较小,对性能敏感,如果不同的线程在大多数分配时会竞争同一份资源或同一把锁,性能将会非常糟糕,原因无外乎和cache一致性有关,已被大量的malloc方案证明。
- 浪费的空间少。如果每个线程各申请各的,速度也许不错,但万一一个线程总是申请,另一个线程总是释放,这个方案就显然不靠谱了。所以线程之间总是要共享全局数据的,如何共享就是方案的关键了。
- 浪费的空间少。如果每个线程各申请各的,速度也许不错,但万一一个线程总是申请,另一个线程总是释放,内存就爆炸了。线程之间总是要共享内存的,如何共享就是方案的关键了。
一般用户可以使用[tcmalloc](http://goog-perftools.sourceforge.net/doc/tcmalloc.html)[jemalloc](https://github.com/jemalloc/jemalloc)等成熟的内存分配方案,但这对于较为底层,关注性能长尾的应用是不够的。因为我们注意到
一般的应用可以使用[tcmalloc](http://goog-perftools.sourceforge.net/doc/tcmalloc.html)[jemalloc](https://github.com/jemalloc/jemalloc)等成熟的内存分配方案,但这对于较为底层,关注性能长尾的应用是不够的。多线程框架广泛地通过传递对象的ownership来让问题异步化,如何让分配这些小对象的开销变的更小是值得研究的。其中的一个特点较为显著
- 大多数结构是等长的。
这个属性可以大幅简化内存分配的过程,获得比通用malloc更稳定、快速的性能。baidu-rpc中的ResourcePool<T>和ObjectPool<T>即提供这类分配。
这个属性可以大幅简化内存分配的过程,获得比通用malloc更稳定、快速的性能。baidu-rpc中的ResourcePool<T>和ObjectPool<T>即提供这类分配。
> 这篇文章不是鼓励用户使用ResourcePool<T>或ObjectPool<T>,事实上我们反对用户在程序中使用这两个类。因为”等长“的副作用是某个类型独占了一部分内存,这些内存无法再被其他类型使用,如果不加控制的滥用,反而会在程序中产生大量彼此隔离的内存分配体系,即浪费内存也不见得会有更好的性能。
> 这篇文章不鼓励用户使用ResourcePool<T>或ObjectPool<T>,事实上我们反对用户在程序中使用这两个类。因为”等长“的副作用是某个类型独占了一部分内存,这些内存无法再被其他类型使用,如果不加控制的滥用,反而会在程序中产生大量彼此隔离的内存分配体系,既浪费内存也不见得会有更好的性能。
# ResourcePool<T>
......@@ -25,11 +25,11 @@
# ObjectPool<T>
这是ResourcePool<T>的变种,不在返回偏移量,而是直接返回对象指针。内部结构和ResourcePool类似,一些代码更加简单。对于用户来说,这就是一个多线程下的对象池,baidu-rpc里也是这么用的。比如Socket::Write中得把每个待写出的请求包装为WriteRequest,这个对象就是用ObjectPool<WriteRequest>分配的,相比通用的malloc更快、更稳定一些
这是ResourcePool<T>的变种,不返回偏移量,而直接返回对象指针。内部结构和ResourcePool类似,一些代码更加简单。对于用户来说,这就是一个多线程下的对象池,baidu-rpc里也是这么用的。比如Socket::Write中把每个待写出的请求包装为WriteRequest,这个对象就是用ObjectPool<WriteRequest>分配的
# 生成bthread_t
用户期望通过创建bthread获得更高的并发度,所以创建bthread必须很快。 在目前的实现中创建一个bthread的平均耗时小于200ns。如果每次都要从头创建,是不可能这么快的。创建过程更像是从一个bthread池子中取一个实例,我们又同时需要一个id来指代一个bthread,所以这儿正是ResourcePool的用武之地。bthread在代码中被称作Task,其结构被称为TaskMeta,定义在[task_meta.h](https://svn.baidu.com/public/trunk/bthread/bthread/task_meta.h)中,所有的TaskMeta由ResourcePool<TaskMeta>分配。
用户期望通过创建bthread获得更高的并发度,所以创建bthread必须很快。 在目前的实现中创建一个bthread的平均耗时小于200ns。如果每次都要从头创建,是不可能这么快的。创建过程更像是从一个bthread池子中取一个实例,我们又同时需要一个id来指代一个bthread,所以这儿正是ResourcePool的用武之地。bthread在代码中被称作Task,其结构被称为TaskMeta,定义在[task_meta.h](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/src/bthread/task_meta.h)中,所有的TaskMeta由ResourcePool<TaskMeta>分配。
bthread的大部分函数都需要在O(1)时间内通过bthread_t访问到TaskMeta,并且当bthread_t失效后,访问应返回NULL以让函数做出返回错误。解决方法是:bthread_t由32位的版本和32位的偏移量组成。版本解决[ABA问题](http://en.wikipedia.org/wiki/ABA_problem),偏移量由ResourcePool<TaskMeta>分配。查找时先通过偏移量获得TaskMeta,再检查版本,如果版本不匹配,说明bthread失效了。注意:这只是大概的说法,在多线程环境下,即使版本相等,bthread仍可能随时失效,在不同的bthread函数中处理方法都是不同的,有些函数会加锁,有些则能忍受版本不相等。
......@@ -39,8 +39,8 @@ bthread的大部分函数都需要在O(1)时间内通过bthread_t访问到TaskMe
# 栈
使用ResourcePool加快创建的副作用是:一个pool中所有bthread的栈必须是一样大的。这似乎限制了用户的选择,不过基于我们的观察,大部分用户并不关心栈的具体大小,而只需要两种大小的栈:尺寸普通但数量较少,尺寸小但数量众多。所以我们用不同的pool管理不同大小的栈,用户可以根据场景选择。两种栈分别对应属性BTHREAD_ATTR_NORMAL(栈默认为1M)和BTHREAD_ATTR_SMALL(栈默认为32K)。用户还可以指定BTHREAD_ATTR_LARGE,这个属性的栈大小和pthread一样,由于尺寸较大,bthread不会对其做任何caching,创建速度较慢。server默认使用BTHREAD_ATTR_NORMAL运行用户代码。
使用ResourcePool加快创建的副作用是:一个pool中所有bthread的栈必须是一样大的。这似乎限制了用户的选择,不过基于我们的观察,大部分用户并不关心栈的具体大小,而只需要两种大小的栈:尺寸普通但数量较少,尺寸小但数量众多。所以我们用不同的pool管理不同大小的栈,用户可以根据场景选择。两种栈分别对应属性BTHREAD_ATTR_NORMAL(栈默认为1M)和BTHREAD_ATTR_SMALL(栈默认为32K)。用户还可以指定BTHREAD_ATTR_LARGE,这个属性的栈大小和pthread一样,由于尺寸较大,bthread不会对其做caching,创建速度较慢。server默认使用BTHREAD_ATTR_NORMAL运行用户代码。
栈使用[mmap](http://linux.die.net/man/2/mmap)分配,bthread还会用mprotect分配4K的guard page以检测栈溢出。由于mmap和mprotect不能超过max_map_count,此值为内核参数,默认为65536,当bthread非常多后可能要调整此参数。另外当有很多bthread时,内存问题可能不仅仅是栈,也包括各类用户和系统buffer。
栈使用[mmap](http://linux.die.net/man/2/mmap)分配,bthread还会用mprotect分配4K的guard page以检测栈溢出。由于mmap+mprotect不能超过max_map_count(默认为65536),当bthread非常多后可能要调整此参数。另外当有很多bthread时,内存问题可能不仅仅是栈,也包括各类用户和系统buffer。
(go语言中的)goroutine在1.3前通过[segmented stacks](https://gcc.gnu.org/wiki/SplitStacks)动态地调整栈大小,发现有[hot split](https://docs.google.com/document/d/1wAaf1rYoM4S4gtnPh0zOlGzWtrZFQ5suE8qr2sD8uWQ/pub)问题后换成了变长连续栈(类似于vector resizing,只适合内存托管的语言)。由于bthread基本只会在64位平台上使用,虚存空间庞大,对变长栈需求不明确。加上segmented stacks的性能有影响,bthread暂时没有变长栈的计划。
\ No newline at end of file
goroutine在1.3前通过[segmented stacks](https://gcc.gnu.org/wiki/SplitStacks)动态地调整栈大小,发现有[hot split](https://docs.google.com/document/d/1wAaf1rYoM4S4gtnPh0zOlGzWtrZFQ5suE8qr2sD8uWQ/pub)问题后换成了变长连续栈(类似于vector resizing,只适合内存托管的语言)。由于bthread基本只会在64位平台上使用,虚存空间庞大,对变长栈需求不明确。加上segmented stacks的性能有影响,bthread暂时没有变长栈的计划。
......@@ -16,11 +16,11 @@ baidu-rpc server在同端口支持所有的协议,大部分时候这对部署
baidu-rpc就是设计为可随时扩展新协议的,步骤如下:
> 以nshead开头的协议有统一支持,看[这里](http://wiki.baidu.com/pages/viewpage.action?pageId=213828733)。
> 以nshead开头的协议有统一支持,看[这里](nshead_service.md)。
## 增加ProtocolType
[options.proto](https://svn.baidu.com/public/trunk/baidu-rpc/protocol/baidu/rpc/options.proto)的ProtocolType中增加新协议类型,如果你需要的话可以联系我们增加,以确保不会和其他人的需求重合。
[options.proto](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/protocol/baidu/rpc/options.proto)的ProtocolType中增加新协议类型,如果你需要的话可以联系我们增加,以确保不会和其他人的需求重合。
目前的ProtocolType(16年底):
```c++
......@@ -52,14 +52,15 @@ enum ProtocolType {
```
## 实现回调
均定义在struct Protocol中,该结构定义在[protocol.h](https://svn.baidu.com/public/trunk/baidu-rpc/src/baidu/rpc/protocol.h)。其中的parse必须实现,除此之外server端至少要实现process_request,client端至少要实现serialize_request,pack_request,process_response;
均定义在struct Protocol中,该结构定义在[protocol.h](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/src/brpc/protocol.h)。其中的parse必须实现,除此之外server端至少要实现process_request,client端至少要实现serialize_request,pack_request,process_response;
实现协议回调还是比较困难的,这块的代码不会像供普通用户使用的那样,有较好的提示和保护,你得先靠自己搞清楚其他协议中的类似代码,然后再动手,最后发给我们做code review。
### parse
定义:`typedef ParseResult (*Parse)(base::IOBuf* source, Socket *socket, bool read_eof, const void *arg);`
```c++
typedef ParseResult (*Parse)(base::IOBuf* source, Socket *socket, bool read_eof, const void *arg);
```
用于把消息从source上切割下来,client端和server端使用同一个parse函数。返回的消息会被递给process_request(server端)或process_response(client端)。
参数:source是读取到的二进制内容,socket是对应的连接,read_eof为true表示连接已被对端关闭,arg在server端是对应server的指针,在client端是NULL。
......@@ -73,49 +74,57 @@ ParseResult可能是错误,也可能包含一个切割下来的message,可
- PARSE_ERROR_ABSOLUTELY_WRONG : 应该是这个协议(比如magic number匹配了),但是格式不符合预期。连接会被关闭。
### serialize_request
定义:`typedef bool (*SerializeRequest)( base::IOBuf* request_buf, Controller* cntl, const google::protobuf::Message* request); `
```c++
typedef bool (*SerializeRequest)(base::IOBuf* request_buf,
Controller* cntl,
const google::protobuf::Message* request);
```
把request序列化进request_buf,client端必须实现。发生在pack_request之前,一次RPC中只会调用一次。cntl包含某些协议(比如http)需要的信息。成功返回true,否则false。
### pack_request
定义:`typedef int (*PackRequest)( base::IOBuf* msg, uint64_t correlation_id, const google::protobuf::MethodDescriptor* method, Controller* controller, const base::IOBuf& request_buf, const Authenticator* auth);`
```c++
typedef int (*PackRequest)(base::IOBuf* msg,
uint64_t correlation_id,
const google::protobuf::MethodDescriptor* method,
Controller* controller,
const base::IOBuf& request_buf,
const Authenticator* auth);
```
把request_buf打包入msg,每次向server发送消息前(包括重试)都会调用。当auth不为空时,需要打包认证信息。成功返回0,否则-1。
### process_request
定义:`typedef void (*ProcessRequest)(InputMessageBase* msg_base);`
```c++
typedef void (*ProcessRequest)(InputMessageBase* msg_base);
```
处理server端parse返回的消息,server端必须实现。可能会在和parse()不同的线程中运行。多个process_request可能同时运行。
在r34386后必须在处理结束时调用msg_base->Destroy(),为了防止漏调,考虑使用DestroyingPtr<>
### process_response
定义:`typedef void (*ProcessResponse)(InputMessageBase* msg);`
```c++
typedef void (*ProcessResponse)(InputMessageBase* msg);
```
处理client端parse返回的消息,client端必须实现。可能会在和parse()不同的线程中运行。多个process_response可能同时运行。
在r34386后必须在处理结束时调用msg_base->Destroy(),为了防止漏调,考虑使用DestroyingPtr<>
### verify
定义:`typedef bool (*Verify)(const InputMessageBase* msg);`
```c++
typedef bool (*Verify)(const InputMessageBase* msg);
```
处理连接的认证,只会对连接上的第一个消息调用,需要支持认证的server端必须实现,不需要认证或仅支持client端的协议可填NULL。成功返回true,否则false。
### parse_server_address
定义:`typedef bool (*ParseServerAddress)(base::EndPoint* out, const char* server_addr_and_port);`
```c++
typedef bool (*ParseServerAddress)(base::EndPoint* out, const char* server_addr_and_port);
```
把server_addr_and_port(Channel.Init的一个参数)转化为base::EndPoint,可选。一些协议对server地址的表达和理解可能是不同的。
### get_method_name
定义:`typedef const std::string& (*GetMethodName)(const google::protobuf::MethodDescriptor* method, const Controller*);`
```c++
typedef const std::string& (*GetMethodName)(const google::protobuf::MethodDescriptor* method,
const Controller*);
```
定制method name,可选。
### supported_connection_type
......@@ -128,7 +137,7 @@ ParseResult可能是错误,也可能包含一个切割下来的message,可
## 注册到全局
实现好的协议要调用RegisterProtocol[注册到全局](https://svn.baidu.com/public/trunk/baidu-rpc/src/baidu/rpc/global.cpp),以便baidu-rpc发现。就像这样:
实现好的协议要调用RegisterProtocol[注册到全局](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/src/brpc/global.cpp),以便baidu-rpc发现。就像这样:
```c++
Protocol http_protocol = { ParseHttpMessage,
SerializeHttpRequest, PackHttpRequest,
......@@ -146,16 +155,15 @@ if (RegisterProtocol(PROTOCOL_HTTP, http_protocol) != 0) {
为了进一步简化protocol的实现逻辑,r34386是一个不兼容改动,主要集中在下面几点:
- ProcessXXX必须在处理结束时调用msg_base->Destroy()。在之前的版本中,这是由框架完成的。这个改动帮助我们隐藏处理EOF的代码(很晦涩),还可以在未来支持更异步的处理(退出ProcessXXX不意味着处理结束)。为了确保所有的退出分支都会调用msg_base->Destroy(),可以使用定义在[destroying_ptr.h](https://svn.baidu.com/public/trunk/baidu-rpc/src/baidu/rpc/destroying_ptr.h)中的DestroyingPtr<>,可能像这样:
- ProcessXXX必须在处理结束时调用msg_base->Destroy()。在之前的版本中,这是由框架完成的。这个改动帮助我们隐藏处理EOF的代码(很晦涩),还可以在未来支持更异步的处理(退出ProcessXXX不意味着处理结束)。为了确保所有的退出分支都会调用msg_base->Destroy(),可以使用定义在[destroying_ptr.h](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/src/brpc/destroying_ptr.h)中的DestroyingPtr<>,可能像这样:
```c++
void ProcessXXXRequest(InputMessageBase* msg_base) {
DestroyingPtr<MostCommonMessage> msg(static_cast<MostCommonMessage*>(msg_base));
...
}
```
- 具体请参考[其他协议](https://svn.baidu.com/public/trunk/baidu-rpc/src/baidu/rpc/policy/baidu_rpc_protocol.cpp)的实现。
- 具体请参考[其他协议](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/src/brpc/policy/baidu_rpc_protocol.cpp)的实现。
- InputMessageBase::socket_id()被移除,而通过socket()可以直接访问到对应Socket的指针。ProcessXXX函数中Address Socket的代码可以移除。
ProcessXXXRequest开头的修改一般是这样:
```c++
......@@ -186,7 +194,6 @@ void ProcessRpcResponse(InputMessageBase* msg_base) {
- CheckEOFGuard eof_guard(msg->socket_id());
+ DestroyingPtr<MostCommonMessage> msg(static_cast<MostCommonMessage*>(msg_base));
...
- // After a successful fight, EOF will no longer interrupt the
......@@ -204,4 +211,4 @@ check_eof_guard.h被移除,所以对这个文件的include也得移除:
```
- if (AddClientSideHandler(handler) != 0) {
+ if (get_or_new_client_side_messenger()->AddHandler(handler) != 0) {
```
\ No newline at end of file
```
ub是百度内广泛使用的老RPC框架,在迁移ub服务时不可避免地需要[访问ub-server](http://wiki.baidu.com/pages/viewpage.action?pageId=213828700)或被ub-client访问。ub使用的协议种类很多,但都以nshead作为二进制包的头部,这类服务在baidu-rpc中统称为**“nshead service”**
ub是百度内广泛使用的老RPC框架,在迁移ub服务时不可避免地需要[访问ub-server](ub_client.md)或被ub-client访问。ub使用的协议种类很多,但都以nshead作为二进制包的头部,这类服务在baidu-rpc中统称为**“nshead service”**
nshead后大都使用mcpack/compack作为序列化格式,注意这不是“协议”。"协议"除了序列化格式,还涉及到各种特殊字段的定义,一种序列化格式可能会衍生出很多协议。ub没有定义标准协议,所以即使都使用mcpack/compack,产品线的通信协议也是五花八门,无法互通。鉴于此,我们提供了一套接口,让用户能够灵活的处理自己产品线的协议,同时享受baidu-rpc提供的builtin services等一系列框架福利。
......@@ -10,9 +10,7 @@ ubrpc协议的基本形式是nshead+compack或mcpack2,但compack或mcpack2中
## 把idl文件转化为proto文件
使用脚本[public/mcpack2pb/idl2proto](https://svn.baidu.com/public/trunk/mcpack2pb/idl2proto)把idl文件自动转化为proto文件,下面是转化后的proto文件。
使用脚本[idl2proto](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/tools/idl2proto)把idl文件自动转化为proto文件,下面是转化后的proto文件。
```protobuf
// Converted from echo.idl by public/mcpack2pb/idl2proto
......@@ -126,11 +124,11 @@ baidu::rpc::ServerOptions option;
option.nshead_service = new baidu::rpc::policy::UbrpcCompackAdaptor; // mcpack2用UbrpcMcpack2Adaptor
```
例子见[example/echo_c++_ubrpc_compack](https://svn.baidu.com/public/trunk/baidu-rpc/example/echo_c++_ubrpc_compack/)
例子见[example/echo_c++_ubrpc_compack](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/example/echo_c++_ubrpc_compack/)
# 使用nshead+blob的服务
[NsheadService](https://svn.baidu.com/public/trunk/baidu-rpc/src/baidu/rpc/nshead_service.h)是baidu-rpc中所有处理nshead打头协议的基类,实现好的NsheadService实例得赋值给ServerOptions.nshead_service才能发挥作用。不赋值的话,默认是NULL,代表不支持任何nshead开头的协议,这个server被nshead开头的数据包访问时会报错。明显地,**一个Server只能处理一种以nshead开头的协议。**
[NsheadService](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/src/brpc/nshead_service.h)是baidu-rpc中所有处理nshead打头协议的基类,实现好的NsheadService实例得赋值给ServerOptions.nshead_service才能发挥作用。不赋值的话,默认是NULL,代表不支持任何nshead开头的协议,这个server被nshead开头的数据包访问时会报错。明显地,**一个Server只能处理一种以nshead开头的协议。**
NsheadService的接口如下,基本上用户只需要实现`ProcessNsheadRequest`这个函数。
......@@ -166,11 +164,11 @@ public:
};
```
完整的example在[example/nshead_extension_c++](https://svn.baidu.com/public/trunk/baidu-rpc/example/nshead_extension_c++/)
完整的example在[example/nshead_extension_c++](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/example/nshead_extension_c++/)
# 使用nshead+mcpack/compack/idl的服务
idl是mcpack/compack的前端,用户只要在idl文件中描述schema,就可以生成一些C++结构体,这些结构体可以打包为mcpack/compack。如果你的服务仍在大量地使用idl生成的结构体,且短期内难以修改,同时想要使用baidu-rpc提升性能和开发效率的话,可以实现[NsheadService](https://svn.baidu.com/public/trunk/baidu-rpc/src/baidu/rpc/nshead_service.h),其接口接受nshead + 二进制包为request,用户填写自己的处理逻辑,最后的response也是nshead+二进制包。流程与protobuf方法保持一致,但过程中不涉及任何protobuf的序列化和反序列化,用户可以自由地理解nshead后的二进制包,包括用idl加载mcpack/compack数据包。
idl是mcpack/compack的前端,用户只要在idl文件中描述schema,就可以生成一些C++结构体,这些结构体可以打包为mcpack/compack。如果你的服务仍在大量地使用idl生成的结构体,且短期内难以修改,同时想要使用baidu-rpc提升性能和开发效率的话,可以实现[NsheadService](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/src/brpc/nshead_service.h),其接口接受nshead + 二进制包为request,用户填写自己的处理逻辑,最后的response也是nshead+二进制包。流程与protobuf方法保持一致,但过程中不涉及任何protobuf的序列化和反序列化,用户可以自由地理解nshead后的二进制包,包括用idl加载mcpack/compack数据包。
不过,你应当充分意识到这么改造的坏处:
......@@ -180,7 +178,7 @@ idl是mcpack/compack的前端,用户只要在idl文件中描述schema,就可
# 使用nshead+protobuf的服务
如果你的协议已经使用了nshead + protobuf,或者你想把你的协议适配为protobuf格式,那可以使用另一种模式:实现[NsheadPbServiceAdaptor](https://svn.baidu.com/public/trunk/baidu-rpc/src/baidu/rpc/nshead_pb_service_adaptor.h)(NsheadService的子类)。
如果你的协议已经使用了nshead + protobuf,或者你想把你的协议适配为protobuf格式,那可以使用另一种模式:实现[NsheadPbServiceAdaptor](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/src/brpc/nshead_pb_service_adaptor.h)(NsheadService的子类)。
工作步骤:
......@@ -188,7 +186,7 @@ idl是mcpack/compack的前端,用户只要在idl文件中描述schema,就可
- Call ParseRequestFromIOBuf() to convert the body after nshead header to pb request, then call the pb method.
- When user calls server's done to end the RPC, SerializeResponseToIOBuf() is called to convert pb response to binary data that will be appended after nshead header and sent back to client.
这样做的好处是,这个服务还可以被其他使用protobuf的协议访问,比如标准协议,hulu协议,sofa协议等等。NsheadPbServiceAdaptor的主要接口如下。完整的example在[这里](https://svn.baidu.com/public/trunk/baidu-rpc/example/nshead_pb_extension_c++/)
这样做的好处是,这个服务还可以被其他使用protobuf的协议访问,比如标准协议,hulu协议,sofa协议等等。NsheadPbServiceAdaptor的主要接口如下。完整的example在[这里](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/example/nshead_pb_extension_c++/)
```c++
class NsheadPbServiceAdaptor : public NsheadService {
......
......@@ -30,7 +30,7 @@ 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还对外发布了开源版本。
- [sofa-pbrpc](https://github.com/baidu/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的代码质量也比较差,接口和生成的代码都比较乱。
......@@ -56,7 +56,7 @@ RPC框架作为最基本的网络通讯组件,需要具备优秀的稳定性
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),这可以表达。
- 我传输的是流数据,RPC表达不了。baidu-rpc支持[Streaming RPC](streaming_rpc.md),这可以表达。
- 我的场景不需要回复。简单推理可知,你的场景中请求可丢可不丢,可处理也可不处理,因为client总是无法感知,你真的确认这是OK的?即使场景真的不需要,我们仍然建议用最小的结构体回复,因为这不大会是瓶颈,并且在出问题时让你有一些线索,否则真的是盲人摸象。
# 优势
......@@ -73,7 +73,7 @@ RPC不是万能的抽象,否则我们也不需要TCP/IP这一层了。但是
### 访问各种服务,被各种服务访问
baidu-rpc能访问百度内所有基于protobuf的RPC server实现,能[访问ub server](http://wiki.baidu.com/pages/viewpage.action?pageId=213828700)(idl/mcpack/compack)
baidu-rpc能访问百度内所有基于protobuf的RPC server实现,能[访问ub server](ub_client.md)(idl/mcpack/compack)
baidu-rpc能被百度内所有基于protobuf的RPC client访问,能被HTTP+json访问,通过一些额外代码可以被UB访问。
......@@ -81,7 +81,7 @@ baidu-rpc能被百度内所有基于protobuf的RPC client访问,能被HTTP+jso
我们的开发机制能持续地保证高质量:
- 分离机制(mechanism)和策略(policy):baidu-rpc把可扩展的部分都抽象为了策略,并放在了[单独的目录](https://svn.baidu.com/public/trunk/baidu-rpc/src/baidu/rpc/policy/),具体的协议支持,压缩算法,名字服务,负载均衡都是策略,如果你想二次开发,可以很容易找到模板并开始上手。就像在算法问题中O(M*N)问题变为了O(M+N)那样,这有效地降低了baidu-rpc整体的复杂度,使得我们可以把精力集中到最核心的代码上。
- 分离机制(mechanism)和策略(policy):baidu-rpc把可扩展的部分都抽象为了策略,并放在了[单独的目录](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/src/brpc/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访问,这改变了我们监控在线服务的方式
......@@ -109,4 +109,4 @@ baidu-rpc背后的技术和知识请阅读[深入RPC](http://wiki.baidu.com/disp
- [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):并发访问,分库分环,从没有这么简单过。
- [组合访问](combo_channel.md):并发访问,分库分环,从没有这么简单过。
parallel_http能同时访问大量的http服务(几万个),适合在命令行中查询线上所有server的内置信息,供其他工具进一步过滤和聚合。curl很难做到这点,即使多个curl以后台的方式运行,并行度一般也只有百左右,访问几万台机器需要等待极长的时间。
\ No newline at end of file
parallel_http能同时访问大量的http服务(几万个),适合在命令行中查询线上所有server的内置信息,供其他工具进一步过滤和聚合。curl很难做到这点,即使多个curl以后台的方式运行,并行度一般也只有百左右,访问几万台机器需要等待极长的时间。
[redis](http://redis.io/)是最近几年比较火的缓存服务,相比memcached在server端提供了更多的数据结构和操作方法,简化了用户的开发工作,在百度内有比较广泛的应用。为了使用户更快捷地访问redis并充分利用bthread的并发能力,baidu-rpc直接支持redis协议。示例程序:<https://svn.baidu.com/public/trunk/baidu-rpc/example/redis_c++/>
[redis](http://redis.io/)是最近几年比较火的缓存服务,相比memcached在server端提供了更多的数据结构和操作方法,简化了用户的开发工作,在百度内有比较广泛的应用。为了使用户更快捷地访问redis并充分利用bthread的并发能力,baidu-rpc直接支持redis协议。示例程序:[example/redis_c++](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/example/redis_c++/)
相比使用[hiredis](https://github.com/redis/hiredis)(官方client)的优势有:
- 线程安全。用户不需要为每个线程建立独立的client。
- 支持同步、异步、批量同步、批量异步等访问方式,能使用ParallelChannel等组合访问方式。
- 支持多种[连接方式](http://wiki.baidu.com/pages/viewpage.action?pageId=213828685#id-创建和访问Client-连接方式)。支持超时、backup request、取消、tracing、内置服务等一系列RPC基本福利。
- 一个进程和一个redis-server只有一个连接。多个线程同时访问一个redis-server时更高效(见[性能](http://wiki.baidu.com/pages/viewpage.action?pageId=213828705#id-访问Redis-性能))。无论reply的组成多复杂,内存都会连续成块地分配,并支持短串优化(SSO)。
- 支持多种[连接方式](client.md#连接方式)。支持超时、backup request、取消、tracing、内置服务等一系列RPC基本福利。
- 一个进程和一个redis-server只有一个连接。多个线程同时访问一个redis-server时更高效(见[性能](#性能))。无论reply的组成多复杂,内存都会连续成块地分配,并支持短串优化(SSO)。
像http一样,baidu-rpc保证在最差情况下解析redis reply的时间复杂度也是O(N),N是reply的字节数,而不是O(N^2)。当reply是个较大的数组时,这是比较重要的。
......@@ -103,7 +103,7 @@ CHECK_EQ(-10, response.reply(3).integer());
# RedisRequest
一个[RedisRequest](https://svn.baidu.com/public/trunk/baidu-rpc/src/baidu/rpc/redis.h)可包含多个Command,调用AddCommand*增加命令,成功返回true,失败返回false并会打印调用处的栈。
一个[RedisRequest](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/src/brpc/redis.h)可包含多个Command,调用AddCommand*增加命令,成功返回true,失败返回false并会打印调用处的栈。
```c++
bool AddCommand(const char* fmt, ...);
......@@ -123,7 +123,7 @@ command_size()可获得(成功)加入的命令个数。
# RedisResponse
[RedisResponse](https://svn.baidu.com/public/trunk/baidu-rpc/src/baidu/rpc/redis.h)可能包含一个或多个[RedisReply](https://svn.baidu.com/public/trunk/baidu-rpc/src/baidu/rpc/redis_reply.h),reply_size()可获得reply的个数,reply(i)可获得第i个reply的引用(从0计数)。注意在hiredis中,如果请求包含了N个command,获取结果也要调用N次redisGetReply。但在baidu-rpc中这是不必要的,RedisResponse已经包含了N个reply,通过reply(i)获取就行了。只要RPC成功,response.reply_size()应与request.command_size()相等,除非redis-server有bug(redis-server工作的基本前提就是response和request按序一一对应)
[RedisResponse](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/src/brpc/redis.h)可能包含一个或多个[RedisReply](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/src/brpc/redis_reply.h),reply_size()可获得reply的个数,reply(i)可获得第i个reply的引用(从0计数)。注意在hiredis中,如果请求包含了N个command,获取结果也要调用N次redisGetReply。但在baidu-rpc中这是不必要的,RedisResponse已经包含了N个reply,通过reply(i)获取就行了。只要RPC成功,response.reply_size()应与request.command_size()相等,除非redis-server有bug(redis-server工作的基本前提就是response和request按序一一对应)
每个reply可能是:
......@@ -250,4 +250,4 @@ redis 127.0.0.1:6379> client getname
"baidu-rpc-cli"
```
和官方CLI类似,redis_cli <command>也可以直接运行命令,-server参数可以指定redis-server的地址。
\ No newline at end of file
和官方CLI类似,redis_cli <command>也可以直接运行命令,-server参数可以指定redis-server的地址。
类似[futex](http://man7.org/linux/man-pages/man2/futex.2.html),用于同步bthread和pthread的原语。bthread等待butex时不会阻塞所在的pthread,这就是butex和futex的关键区别。理解[butex代码](http://websvn.work.baidu.com/repos/public/show/trunk/bthread/bthread/butex.cpp)的正确性比较困难,但在bthread的其他代码中只需要关心[其接口](http://websvn.work.baidu.com/repos/public/show/trunk/bthread/bthread/butex.h),和futex一样,这是一个非常强大的积木。除了bthread_usleep基于独立线程[TimerThread](http://websvn.work.baidu.com/repos/public/show/trunk/bthread/bthread/timer_thread.h?revision=HEAD),大部分阻塞函数都依赖butex,比如bthread_mutex_t, bthread_cond_t, bthread_join等。butex中的超时事件也依赖[TimerThread](http://websvn.work.baidu.com/repos/public/show/trunk/bthread/bthread/timer_thread.h?revision=HEAD)
\ No newline at end of file
1. 每次CI前运行tools/switch_trunk确保baidu-rpc依赖的主要模块使用主干版本。依赖的模块有:public/common public/bvar public/iobuf public/bthread public/protobuf-json public/murmurhash public/mcpack2pb2
2. 每次CI前检查所有修改的文件。在svn下可用如下命令:`svn st | grep "^?.*\.\(cpp\|c\|cc\|h\|hpp\|sh\|py\|pl\|proto\|thrift\|java\)$"`。如有预期之外的文件改动,请咨询后再CI。
3. 每次CI前确认comake2和bcloud都可以无warning编译通过。
1. comake2编译方法:comake2 -P && make -sj8
2. bcloud编译方法:bcloud build
4. 每次CI前务必确认在gcc 4.8和3.4下都编译通过,最好也检查gcc 4.4下的编译情况。
1. 用gcc 4.8编译:在~/.bashrc中加入`export PATH=/opt/compiler/gcc-4.8.2/bin:$PATH`,重新登陆后有效。
2. 用gcc 3.4编译:一些老机器的/usr/bin/gcc默认是3.4。BCLOUD默认也是3.4(在2016/6/17是的)。
3. 用gcc 4.4编译:centos 6.3的/usr/bin/gcc默认是4.4。
5. 每次CI前运行单测通过,运行方式:进入src/baidu/rpc/test目录comake2 -P,如果报错则运行comake2 -UB拉依赖(拉完依赖要运行tools/switch_trunk把依赖模块再切回主干),make -sj8,运行所有形如test_*的可执行程序。
\ No newline at end of file
### 切换方法
bthread通过[boost.context](http://www.boost.org/doc/libs/1_56_0/libs/context/doc/html/index.html)切换上下文。[setjmp](http://en.wikipedia.org/wiki/Setjmp.h), [signalstack](http://linux.die.net/man/2/sigaltstack), [ucontext](http://en.wikipedia.org/wiki/Setcontext)也可以切换上下文,但boost.context是[最快的](http://www.boost.org/doc/libs/1_56_0/libs/context/doc/html/context/performance.html)
### 基本方法
调度bthread需要比上下文切换更多的东西。每个线程都有自己的[TaskGroup](http://websvn.work.baidu.com/repos/public/show/trunk/bthread/baidu/bthread/task_group.h),包含独立的[runqueue](http://websvn.work.baidu.com/repos/public/show/trunk/bthread/baidu/bthread/work_stealing_queue.h),它允许一个线程在一端push/pop,多个线程在另一端steal,这种结构被称作work stealing queue。bthread通过调用TaskGroup::sched或TaskGroup::sched_to放弃CPU,这些函数会先尝试pop自己的runqueue,若没有就偷另一个TaskGroup的runqueue(所有的TaskGroup由[TaskControl](http://websvn.work.baidu.com/repos/public/show/trunk/bthread/baidu/bthread/task_control.h?revision=HEAD)管理),还是没有就调用TaskControl::wait_task(),它在任一runqueue有新bthread时都会被唤醒。获得待运行bthread后TaskGroup会跳至其上下文。
新建bthread的模式和pthread有所不同:可以直接跳至新bthread的上下文,再调度原bthread,这相当于原bthread把自己的时间片让给了新bthread。当新bthread要做的工作比原bthread更紧急时,这可以让新bthread免去可能的调度排队,并保持cache locality。请注意,“调度原bthread"不意味着原bthread必须等到新bthread放弃CPU才能运行,通过调用TaskControl::signal_task(),它很可能在若干微秒后就会被偷至其他pthread worker而继续运行。当然,bthread也允许调度新bthread而让原bthread保持运行。这两种新建方式分别对应bthread_start_urgent()和bthread_start_background()函数。
### 相关工作
标准work stealing调度的过程是每个worker pthread都有独立的runqueue,新task入本地runqueue,worker pthread没事干后先运行本地的task,没有就随机偷另一个worker的task,还是没事就不停地sched_yield... 随机偷... sched_yield... 随机偷... 直到偷到任务。典型代表是intel的[Cilk](http://en.wikipedia.org/wiki/Cilk),近期改进有[BWS](http://jason.cse.ohio-state.edu/bws/)。实现较简单,但硬伤在于[sched_yield](http://man7.org/linux/man-pages/man2/sched_yield.2.html):当一个线程调用sched_yield后,OS对何时唤醒它没任何信息,一般便假定这个线程不急着用CPU,把它排到很低的优先级,比如在linux 2.6后的[CFS](http://en.wikipedia.org/wiki/Completely_Fair_Scheduler)中,OS把调用sched_yield的线程视作用光了时间配额,直到所有没有用完的线程运行完毕后才会调度它。由于在线系统中的低延时(及吞吐)来自线程之间的快速唤醒和数据传递,而这种算法中单个task往往无法获得很好的延时,更适合追求总体完成时间的离线科学计算。这种算法的另一个现实困扰是,在cpu profiler结果中总是有大量的sched_yield,不仅影响结果分析,也让用户觉得这类程序简单粗暴,占CPU特别多。
goroutine自1.1后使用的是另一种work stealing调度,大致过程是新goroutine入本地runqueue,worker先运行本地runqueue中的goroutine,如果没有没有就随机偷另一个worker的runqueue,和标准work stealing只偷一个不同,go会偷掉一半。如果也没有,则会睡眠,所以在产生新goroutine时可能要唤醒worker。由于在调用syscall时会临时放弃go-context,goroutine还有一个全局runqueue,大概每个context调度61次会去取一次全局runqueue,以防止starvation。虽然[设计文档](https://docs.google.com/document/d/1TTj4T2JO42uD5ID9e89oa0sLKhJYD0Y_kqxDv3I3XMw/edit#heading=h.mmq8lm48qfcw)中称其为scalable,但很难称之为scalable,最多“相比之前只有一个全局runqueue更scalable了”,其中有诸多全局竞争点,其对待starvation的态度也相当山寨,考虑到go的gc也不给力,基本上你不能期待goroutine的延时有什么保证,很可能会出现10ms甚至更长的延时,完全不能用于在线服务。
根据[这份材料](http://www.erlang.org/euc/08/euc_smp.pdf),erlang中的lightweight process也有一部分算法也是work stealing,另外每250毫秒对所有scheduler的负载做个统计,然后根据统计结果调整每个scheduler的负载,不过我觉得效果不会太好,因为这时间也实在太长了。另外他们看上去无法避免在还有待运行的任务时,一些scheduler却闲着。不过对于erlange这类本身[比较慢](http://benchmarksgame.alioth.debian.org/u64q/benchmark.php?test=all&lang=hipe&lang2=gpp&data=u64q)的语言,scheduler要求可能也不高,和我们对在线服务的要求不是一个数量级。
### 调度bthread
之所以对调度如此苛刻,在于我们希望**为每个请求建立bthread**。这意味着如下要求:
- 建立bthread必须非常快。但这不属于调度,在“创建和回收bthread”一节中已有阐述。
- 只要有worker pthread闲着并且可获得CPU,待运行的bthread应该在O(1)内开始运行。这看上去严苛,但却不可或缺,对于一个延时几十毫秒的检索,如果明明有worker pthread可用,但却因为调度的原因导致这个检索任务只能等着本线程的其他任务完成,对可用性(4个9)会有直接的影响。
- 延时不应高于内核的调度延时:大约是5微秒。否则又让用户陷入了两难:一些场景要用pthread,一些用bthread。我们希望用户轻松一点。
- 增加worker pthread数量可以线性增加调度能力。
- 建立后还未运行的bthread应尽量减少内存占用。请求很可能会大于worker pthread数,不可避免地在队列中积累,还没运行却需要占用大量资源,比如栈,是没道理的。
bthread的调度算法能满足这些要求。为了说明白这个过程,我们先解释[futex](http://man7.org/linux/man-pages/man2/futex.2.html)是如何工作的。futex有一系列接口,最主要的是两个:
- futex_wait(void* futex, int expected_value);
- futex_wake(void* futex, int num_wakeup);
*(int*)futex和expected_value相等时,futex_wait会阻塞,**判断相等并阻塞是原子的**。futex_wake则是唤醒阻塞于futex上的线程,最多num_wakeup个。(对齐的)有效地址都可以作为futex。一种配合方式如下:
```c++
int g_futex = 0; // shared by all threads
// Consumer thread
while (1) {
const int expected_val = g_futex;
while (there's sth to consume) {
consume(...);
}
futex_wait(&g_futex, expected_val);
}
// Producer thread
produce(...);
atomically_add1(g_futex); /*note*/
futex_wake(&g_futex, 1);
```
由于futex_wait的原子性,在Producer thread中原子地给g_futex加1后,至少有一个Consumer thread要么看到g_futex != expected_val而不阻塞,或从阻塞状态被唤醒,然后去消费掉所有的东西。比如Consumer thread在消费掉所有的东西后到重新futex_wait中这段时间,Producer thread可能产生了更多东西。futex把这个race condition简化成了两种可能:
- 这段时间内Producer thread执行了原子加(note那行)
- 这段时间内Producer thread还没有执行原子加。
第一种情况,Consumer thread中的futex_wait会看到g_futex != expected_val,立刻返回-1 (errno=EWOULDBLOCK)。第二种情况,Consumer会先阻塞,然后被原子加之后的futex_wake唤醒。总之Consumer总会看到这个新东西,而不会漏掉。我们不再说明更多细节,需要提醒的是,这个例子只是为了说明后续算法,如果你对[原子操作](http://wiki.baidu.com/pages/viewpage.action?pageId=36886832)还没有概念,**绝对**不要在工作项目中使用futex。
bthread的基本调度结构是每个worker pthread都有一个runqueue,新建的bthread压入本地runqueue,调度就是告诉一些闲着的worker,“我这有bthread待运行了,赶紧来偷”。如果我们没有提醒其他worker,在本地runqueue中的bthread必须等到本线程挨个执行排在它之前的bthread,直到把它弹出为止。这种只在一个worker pthread中轮换的方式就是fiber或N:1 threading。显而易见,这限制了并行度。为了充分发挥SMP的潜力,我们要唤醒其他worker来偷走bthread一起并行执行。把上面的代码中的consume换成"偷bthread”,produce换成“压入本地runqueue",就是最简单的bthread调度算法。
TODO:原理
下图中每个线程都在不停地建立(很快结束的)bthread,最多有24个这样的线程(24核机器)。使用三种不同方法的吞吐如下:
- futex(绿色) - 用一个全局futex管理所有闲置的worker pthread,在需要时signal这个futex。这个方法完全不能扩展,在2个线程时已经饱和(120万左右)。
- futex optimized with atomics(红色)- 仍然是一个全局futex,但是用原子变量做了缓冲,可以合并掉连续的futex_wake。这个方法已经可以扩展,最终能达到1300万左右的吞吐。这种方法的延时会高一些。
- new algorithm(蓝色)- 新算法有非常好的扩展性,不仅是斜的,而且斜率还在变大。这是由于新算法在激烈的全局竞争后会只修改thread local变量,所以越来越快,最终可以达到4500万吞吐。由于类似的原因,新算法的延时越忙反而越低,在不忙时和内核调度延时(5us)接近,在很忙时会降到0.2us。
![img](http://wiki.baidu.com/download/attachments/35959040/image2014-12-7%2021%3A37%3A22.png?version=1&modificationDate=1417959443000&api=v2)
\ No newline at end of file
......@@ -10,9 +10,9 @@ rpc_press无需写代码就压测各种rpc server,目前支持的协议有:
在终端中运行如下命令即可编译出最新版baidu-rpc包含的rpc_press工具.
`PREVDIR=`pwd` && TEMPDIR=`mktemp -d -t build_rpc_press.XXXXXXXXXX` && mkdir $TEMPDIR/public && cd $TEMPDIR/public && svn co https://svn.baidu.com/public/trunk/baidu-rpc && cd baidu-rpc && comake2 -UB -J8 -j8 && comake2 -P && make -sj8 && cd tools/rpc_press && comake2 -P && make -sj8 && cp -f ./rpc_press $PREVDIR && cd $PREVDIR; rm -rf $TEMPDIR`
`PREVDIR=`pwd` && TEMPDIR=`mktemp -d -t build_rpc_press.XXXXXXXXXX` && mkdir $TEMPDIR/public && cd $TEMPDIR/public && svn co http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob && cd baidu-rpc && comake2 -UB -J8 -j8 && comake2 -P && make -sj8 && cd tools/rpc_press && comake2 -P && make -sj8 && cp -f ./rpc_press $PREVDIR && cd $PREVDIR; rm -rf $TEMPDIR`
编译完成后,rpc_press就会出现在当前目录下。如果编译出错,看[Getting Started](http://wiki.baidu.com/pages/viewpage.action?pageId=71337200)
编译完成后,rpc_press就会出现在当前目录下。如果编译出错,看[Getting Started](getting_started.md)
......@@ -36,17 +36,17 @@ json也可以写在文件中,假如./input.json包含了上述两个请求,-
- -proto:指定相关的proto文件名。
- -method:指定方法名,形式必须是package.service.method。
- -server:当-lb_policy为空时,是服务器的ip:port;当-lb_policy不为空时,是集群地址,比如bns://node-name, file://server_list等等。具体见[名字服务](http://wiki.baidu.com/pages/viewpage.action?pageId=213828685#id-创建和访问Client-名字服务)
- -server:当-lb_policy为空时,是服务器的ip:port;当-lb_policy不为空时,是集群地址,比如bns://node-name, file://server_list等等。具体见[名字服务](client.md#名字服务)
- -input: 指定json请求或包含json请求的文件。r32157后json间不需要分隔符,r32157前json间用分号分隔。
可选参数:
- -inc: 包含被import的proto文件的路径。rpc_press默认支持import目录下的其他proto文件,但如果proto文件在其他目录,就要通过这个参数指定,多个路径用分号(;)分隔。
- -lb_policy: 指定负载均衡算法,默认为空,可选项为: rr random la c_murmurhash c_md5,具体见[负载均衡](http://wiki.baidu.com/pages/viewpage.action?pageId=213828685#id-创建和访问Client-负载均衡)
- -lb_policy: 指定负载均衡算法,默认为空,可选项为: rr random la c_murmurhash c_md5,具体见[负载均衡](client.md#负载均衡)
- -timeout_ms: 设定超时,单位是毫秒(milliseconds),默认是1000(1秒)
- -max_retry: 最大的重试次数,默认是3, 一般无需修改. baidu-rpc的重试行为具体请见[这里](http://wiki.baidu.com/pages/viewpage.action?pageId=213828685#id-创建和访问Client-重试).
- -protocol: 连接server使用的协议,可选项见[协议](http://wiki.baidu.com/pages/viewpage.action?pageId=213828685#id-创建和访问Client-协议), 默认是baidu_std(标准协议)
- -connection_type: 连接方式,可选项为: single pooled short,具体见[连接方式](http://wiki.baidu.com/pages/viewpage.action?pageId=213828685#id-创建和访问Client-连接方式)。默认会根据协议自动选择,无需指定.
- -max_retry: 最大的重试次数,默认是3, 一般无需修改. baidu-rpc的重试行为具体请见[这里](client.md#重试).
- -protocol: 连接server使用的协议,可选项见[协议](client.md#协议), 默认是baidu_std(标准协议)
- -connection_type: 连接方式,可选项为: single pooled short,具体见[连接方式](client.md#连接方式)。默认会根据协议自动选择,无需指定.
- -output: 如果不为空,response会转为json并写入这个文件,默认为空。
- -duration:大于0时表示发送这么多秒的压力后退出,否则一直发直到按ctrl-c或进程被杀死。默认是0(一直发送)。
- -qps:大于0时表示以这个压力发送,否则以最大速度(自适应)发送。默认是100。
......@@ -117,4 +117,4 @@ dummy_server启动时会在终端打印日志,一般按住ctrl点击那个链
**Q: 如果下游是基于j-protobuf框架的服务模块,压力工具该如何配置?**
A:因为协议兼容性问题,启动rpc_press的时候需要带上-baidu_protocol_use_fullname=false
\ No newline at end of file
A:因为协议兼容性问题,启动rpc_press的时候需要带上-baidu_protocol_use_fullname=false
......@@ -4,9 +4,9 @@ r31658后,baidu-rpc能随机地把一部分请求写入一些文件中,并
在终端中运行如下命令即可编译出最新版baidu-rpc包含的rpc_replay工具.
`PREVDIR=`pwd` && TEMPDIR=`mktemp -d -t build_rpc_replay.XXXXXXXXXX` && mkdir $TEMPDIR/public && cd $TEMPDIR/public && svn co https://svn.baidu.com/public/trunk/baidu-rpc && cd baidu-rpc && comake2 -UB -J8 -j8 && comake2 -P && make -sj8 && cd tools/rpc_replay && comake2 -P && make -sj8 && cp -f ./rpc_replay $PREVDIR && cd $PREVDIR; rm -rf $TEMPDIR`
`PREVDIR=`pwd` && TEMPDIR=`mktemp -d -t build_rpc_replay.XXXXXXXXXX` && mkdir $TEMPDIR/public && cd $TEMPDIR/public && svn co http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob && cd baidu-rpc && comake2 -UB -J8 -j8 && comake2 -P && make -sj8 && cd tools/rpc_replay && comake2 -P && make -sj8 && cp -f ./rpc_replay $PREVDIR && cd $PREVDIR; rm -rf $TEMPDIR`
编译完成后,rpc_press就会出现在当前目录下。如果编译出错,看[Getting Started](http://wiki.baidu.com/pages/viewpage.action?pageId=71337200)
编译完成后,rpc_press就会出现在当前目录下。如果编译出错,看[Getting Started](getting_started.md)
......@@ -31,7 +31,7 @@ baidu-rpc通过如下flags打开和控制如何保存请求,包含(R)后缀的
- -rpc_dump_max_files: 设置目录下的最大文件数,当超过限制时,老文件会被删除以腾出空间。
- -rpc_dump_max_requests_in_one_file:一个文件内的最大请求数,超过后写新文件。
baidu-rpc通过一个[bvar::Collector](https://svn.baidu.com/public/trunk/bvar/bvar/collector.h)来汇总来自不同线程的被采样请求,不同线程之间没有竞争,开销很小。
baidu-rpc通过一个[bvar::Collector](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/src/bvar/collector.h)来汇总来自不同线程的被采样请求,不同线程之间没有竞争,开销很小。
写出的内容依次存放在rpc_dump_dir目录下的多个文件内,这个目录默认在./rpc_dump_<app>,其中<app>是程序名。不同程序在同一个目录下同时采样时会写入不同的目录。如果程序启动时rpc_dump_dir已经存在了,目录将被清空。目录中的每个文件以requests.yyyymmdd_hhmmss_uuuuus命名,以保证按时间有序方便查找,比如:
......@@ -53,7 +53,7 @@ serialized request (body_size - meta_size bytes, including attachment)
> 一个文件可能包含多种协议的请求,如果server被多种协议访问的话。回放时被请求的server也将收到不同协议的请求。
baidu-rpc提供了[SampleIterator](https://svn.baidu.com/public/trunk/baidu-rpc/src/baidu/rpc/rpc_dump.h)从一个采样目录下的所有文件中依次读取所有的被采样请求,用户可根据需求把serialized request反序列化为protobuf请求,做一些二次开发。
baidu-rpc提供了[SampleIterator](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/src/brpc/rpc_dump.h)从一个采样目录下的所有文件中依次读取所有的被采样请求,用户可根据需求把serialized request反序列化为protobuf请求,做一些二次开发。
```
#include <baidu/rpc/rpc_dump.h>
......@@ -69,7 +69,7 @@ for (SampleRequest* req = it->Next(); req != NULL; req = it->Next()) {
# 回放
baidu-rpc在[tools/rpc_replay](https://svn.baidu.com/public/trunk/baidu-rpc/tools/rpc_replay/)提供了默认的回放工具。运行方式如下:
baidu-rpc在[tools/rpc_replay](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/tools/rpc_replay/)提供了默认的回放工具。运行方式如下:
![img](http://wiki.baidu.com/download/attachments/158707916/image2015-12-19%200%3A40%3A56.png?version=1&modificationDate=1450456871000&api=v2)
......
......@@ -5,10 +5,10 @@ rpc_view可以查看端口不在8000-8999的server的内置服务。之前如果
在终端中运行如下命令即可编译出最新版baidu-rpc包含的rpc_view工具.
```bash
PREVDIR=`pwd` && TEMPDIR=`mktemp -d -t build_rpc_press.XXXXXXXXXX` && mkdir $TEMPDIR/public && cd $TEMPDIR/public && svn co https://svn.baidu.com/public/trunk/baidu-rpc && cd baidu-rpc && comake2 -UB -J8 -j8 && comake2 -P && make -sj8 && cd tools/rpc_view && comake2 -P && make -sj8 && cp -f ./rpc_view $PREVDIR && cd $PREVDIR; rm -rf $TEMPDIR
PREVDIR=`pwd` && TEMPDIR=`mktemp -d -t build_rpc_press.XXXXXXXXXX` && mkdir $TEMPDIR/public && cd $TEMPDIR/public && svn co http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob && cd baidu-rpc && comake2 -UB -J8 -j8 && comake2 -P && make -sj8 && cd tools/rpc_view && comake2 -P && make -sj8 && cp -f ./rpc_view $PREVDIR && cd $PREVDIR; rm -rf $TEMPDIR
```
编译完成后,rpc_press就会出现在当前目录下。如果编译出错,看[Getting Started](http://wiki.baidu.com/pages/viewpage.action?pageId=71337200)
编译完成后,rpc_press就会出现在当前目录下。如果编译出错,看[Getting Started](getting_started.md)
......@@ -48,4 +48,4 @@ TRACE: 02-14 12:12:20: * 0 src/baidu/rpc/server.cpp:771] Check out http://db-r
加上?changetarge后就跳到新目标server的/connections页面了。接下来点击其他tab都会显示新目标server的。
![img](http://wiki.baidu.com/download/attachments/167651918/image2016-2-14%2012%3A23%3A10.png?version=1&modificationDate=1455423790000&api=v2)
\ No newline at end of file
![img](http://wiki.baidu.com/download/attachments/167651918/image2016-2-14%2012%3A23%3A10.png?version=1&modificationDate=1455423790000&api=v2)
......@@ -12,7 +12,7 @@
![img](http://wiki.baidu.com/download/attachments/71337189/image2015-12-24%2013%3A21%3A34.png?version=1&modificationDate=1450934494000&api=v2)
如果只是baidu-rpc client或没有使用baidu-rpc,看[这里](http://wiki.baidu.com/pages/viewpage.action?pageId=213843633)
如果只是baidu-rpc client或没有使用baidu-rpc,看[这里](dummy_server.md)
## 数据展现
......@@ -40,10 +40,10 @@
## Annotation
只要你使用了baidu-rpc,就可以使用[TRACEPRINTF](https://svn.baidu.com/public/trunk/baidu-rpc/src/baidu/rpc/traceprintf.h)打印内容到事件流中,比如:
只要你使用了baidu-rpc,就可以使用[TRACEPRINTF](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/src/brpc/traceprintf.h)打印内容到事件流中,比如:
```c++
TRACEPRINTF("Hello rpcz %d", 123);
```
这条annotation会按其发生时间插入到对应请求的rpcz中。从这个角度看,rpcz是请求级的日志。如果你用TRACEPRINTF打印了沿路的上下文,便可看到请求在每个阶段停留的时间,牵涉到的数据集和参数。这是个很有用的功能。
\ No newline at end of file
这条annotation会按其发生时间插入到对应请求的rpcz中。从这个角度看,rpcz是请求级的日志。如果你用TRACEPRINTF打印了沿路的上下文,便可看到请求在每个阶段停留的时间,牵涉到的数据集和参数。这是个很有用的功能。
This diff is collapsed.
......@@ -58,11 +58,11 @@ bthread_creation_qps : 95
rpc_server_8765_example_echo_service_echo_qps : 57
```
如果不同机器的分流确实不均,且难以解决,可以考虑[限制最大并发](http://wiki.baidu.com/pages/viewpage.action?pageId=213828715#id-创建和设置Server-限制最大并发)
如果不同机器的分流确实不均,且难以解决,可以考虑[限制最大并发](server.md#id-创建和设置Server-限制最大并发)
### 优化单机性能
请使用[CPU profiler](http://wiki.baidu.com/display/RPC/cpu+profiler)分析程序的热点,用数据驱动优化。一般来说一个卡顿的cpu-bound程序一般能看到显著的热点。
请使用[CPU profiler](cpu_profiler.md)分析程序的热点,用数据驱动优化。一般来说一个卡顿的cpu-bound程序一般能看到显著的热点。
## 3.2 定位io-bound问题
......@@ -98,7 +98,7 @@ rpc_server_8765_example_echo_service_echo_qps : 57
### 排除锁的嫌疑
如果程序被某把锁挡住了,也可能呈现出“io-bound”的特征。先用[contention profiler](http://wiki.baidu.com/display/RPC/contention+profiler)排查锁的竞争状况。
如果程序被某把锁挡住了,也可能呈现出“io-bound”的特征。先用[contention profiler](contention_profiler.md)排查锁的竞争状况。
### 使用rpcz
......@@ -128,7 +128,7 @@ rpcz可以帮助你看到最近的所有请求,和处理它们时在每个阶
TRACEPRINTF主要适合若干次的函数调用,如果一个函数调用了很多次,或者函数本身开销很小,每次都往rpcz打印日志是不合适的。这时候你可以使用bvar。
[bvar](http://wiki.baidu.com/display/RPC/bvar)是一个多线程下的计数库,可以以极低的开销统计用户递来的数值,相比“打日志大法”几乎不影响程序行为。你不用完全了解bvar的完整用法,只要使用bvar::LatencyRecorder即可。
[bvar](bvar.md)是一个多线程下的计数库,可以以极低的开销统计用户递来的数值,相比“打日志大法”几乎不影响程序行为。你不用完全了解bvar的完整用法,只要使用bvar::LatencyRecorder即可。
仿照如下代码对foobar的运行时间进行监控。
......@@ -164,4 +164,4 @@ void search() {
### 只使用了baidu-rpc client
得打开dummy server提供内置服务,方法见[这里](http://wiki.baidu.com/pages/viewpage.action?pageId=213843633)
\ No newline at end of file
得打开dummy server提供内置服务,方法见[这里](dummy_server.md)
This diff is collapsed.
......@@ -4,7 +4,7 @@ streaming_log - Print log to std::ostreams
# SYNOPSIS
你得依赖[public/common](https://svn.baidu.com/public/trunk/common/)模块,在COMAKE中插入CONFIGS('public/common@ci-base')即可。
你得依赖[public/common](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/src/)模块,在COMAKE中插入CONFIGS('public/common@ci-base')即可。
```c++
#include <base/logging.h>
......@@ -323,4 +323,4 @@ TEST_F(StreamingLogTest, log_at) {
定义在base/comlog_sink.h中,把日志打印入comlog,主要用于线上系统,用法见[SYNOPSIS](#SYNOPSIS)一段。
> [使用]()ComlogSink的streaming log可以和com_writelog, ul_writelog混用。你并不需要把程序中所有日志都换成streaming log。
\ No newline at end of file
> [使用]()ComlogSink的streaming log可以和com_writelog, ul_writelog混用。你并不需要把程序中所有日志都换成streaming log。
......@@ -17,7 +17,7 @@ Streaming RPC保证:
目前的实现还没有自动切割过大的消息,同一个tcp连接上的多个Stream之间可能有[Head-of-line blocking](https://en.wikipedia.org/wiki/Head-of-line_blocking)问题,请尽量避免过大的单个消息,实现自动切割后我们会告知并更新文档。
例子见[example/streaming_echo_c++](https://svn.baidu.com/public/trunk/baidu-rpc/example/streaming_echo_c++/)
例子见[example/streaming_echo_c++](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/example/streaming_echo_c++/)
# 建立Stream
......
本页说明bthread下使用pthread-local可能会导致的问题。bthread-local的使用方法见[这里](http://wiki.baidu.com/pages/viewpage.action?pageId=213828715#id-创建和设置Server-bthread-local)
本页说明bthread下使用pthread-local可能会导致的问题。bthread-local的使用方法见[这里](server.md#bthread-local)
# thread-local问题
......
......@@ -51,4 +51,4 @@
- 不用把一个函数拆成若干个回调,可以使用RAII。
- 从用户线程A切换为用户线程B时,也许我们可以让B在A所在的核上运行,而让A去其他核运行,从而使更高优先级的B更少受到cache miss的干扰。
实现全功能的M:N线程库是极其困难的,所以M:N线程库一直是个活跃的研究话题。我们这里说的M:N线程库特别针对编写网络服务,在这一前提下一些需求可以简化,比如没有时间片抢占,没有优先级等,即使有也以简单粗暴为主,无法和操作系统级别的实现相比。M:N线程库可以在用户态也可以在内核中实现,用户态的实现以新语言为主,比如GHC threads和goroutine,这些语言可以围绕线程库设计全新的API。而在主流语言中的实现往往得修改内核,比如[Windows UMS](https://msdn.microsoft.com/en-us/library/windows/desktop/dd627187(v=vs.85).aspx)。google SwicthTo虽然是1:1,但基于它可以实现M:N的效果。在使用上M:N线程库更类似于系统线程,需要用锁或消息传递保证代码的线程安全。
\ No newline at end of file
实现全功能的M:N线程库是极其困难的,所以M:N线程库一直是个活跃的研究话题。我们这里说的M:N线程库特别针对编写网络服务,在这一前提下一些需求可以简化,比如没有时间片抢占,没有优先级等,即使有也以简单粗暴为主,无法和操作系统级别的实现相比。M:N线程库可以在用户态也可以在内核中实现,用户态的实现以新语言为主,比如GHC threads和goroutine,这些语言可以围绕线程库设计全新的API。而在主流语言中的实现往往得修改内核,比如[Windows UMS](https://msdn.microsoft.com/en-us/library/windows/desktop/dd627187(v=vs.85).aspx)。google SwicthTo虽然是1:1,但基于它可以实现M:N的效果。在使用上M:N线程库更类似于系统线程,需要用锁或消息传递保证代码的线程安全。
......@@ -9,7 +9,7 @@
你注意到了么,在RPC中timer更像是”保险机制”,在大部分情况下都不会发挥作用,自然地我们希望它的开销越小越好。一个几乎不触发的功能需要两次系统调用似乎并不理想。那在应用框架中一般是如何实现timer的呢?谈论这个问题需要区分“单线程”和“多线程”:
- 在单线程框架中,比如以[libevent](http://libevent.org/)[, ](http://en.wikipedia.org/wiki/Reactor_pattern)[libev](http://software.schmorp.de/pkg/libev.html)为代表的eventloop类库,或以[GNU Pth](http://www.gnu.org/software/pth/pth-manual.html), [StateThreads](http://state-threads.sourceforge.net/index.html)为代表的coroutine / fiber类库中,一般是以[小顶堆](https://en.wikipedia.org/wiki/Heap_(data_structure))记录触发时间。[epoll_wait](http://man7.org/linux/man-pages/man2/epoll_wait.2.html)前以堆顶的时间计算出参数timeout的值,如果在该时间内没有其他事件,epoll_wait也会醒来,从堆中弹出已超时的元素,调用相应的回调函数。整个框架周而复始地这么运转,timer的建立,等待,删除都发生在一个线程中。只要所有的回调都是非阻塞的,且逻辑不复杂,这套机制就能提供基本准确的timer。不过就像[Threading Overview](http://wiki.baidu.com/pages/viewpage.action?pageId=99588643)中说的那样,这不是RPC的场景。
- 在单线程框架中,比如以[libevent](http://libevent.org/)[, ](http://en.wikipedia.org/wiki/Reactor_pattern)[libev](http://software.schmorp.de/pkg/libev.html)为代表的eventloop类库,或以[GNU Pth](http://www.gnu.org/software/pth/pth-manual.html), [StateThreads](http://state-threads.sourceforge.net/index.html)为代表的coroutine / fiber类库中,一般是以[小顶堆](https://en.wikipedia.org/wiki/Heap_(data_structure))记录触发时间。[epoll_wait](http://man7.org/linux/man-pages/man2/epoll_wait.2.html)前以堆顶的时间计算出参数timeout的值,如果在该时间内没有其他事件,epoll_wait也会醒来,从堆中弹出已超时的元素,调用相应的回调函数。整个框架周而复始地这么运转,timer的建立,等待,删除都发生在一个线程中。只要所有的回调都是非阻塞的,且逻辑不复杂,这套机制就能提供基本准确的timer。不过就像[Threading Overview](threading_overview.md)中说的那样,这不是RPC的场景。
- 在多线程框架中,任何线程都可能被用户逻辑阻塞较长的时间,我们需要独立的线程实现timer,这种线程我们叫它TimerThread。一个非常自然的做法,就是使用用锁保护的小顶堆。当一个线程需要创建timer时,它先获得锁,然后把对应的时间插入堆,如果插入的元素成为了最早的,唤醒TimerThread。TimerThread中的逻辑和单线程类似,就是等着堆顶的元素超时,如果在等待过程中有更早的时间插入了,自己会被插入线程唤醒,而不会睡过头。这个方法的问题在于每个timer都需要竞争一把全局锁,操作一个全局小顶堆,就像在其他文章中反复谈到的那样,这会触发cache bouncing。同样数量的timer操作比单线程下的慢10倍是非常正常的,尴尬的是这些timer基本不触发。
我们重点谈怎么解决多线程下的问题。
......@@ -34,7 +34,7 @@
- 删除时通过id直接定位到timer内存结构,修改一个标志,timer结构总是由TimerThread释放。
- TimerThread被唤醒后首先把全局nearest_run_time设置为几乎无限大(max of int64),然后取出所有Bucket内的链表,并把Bucket的nearest_run_time设置为几乎无限大(max of int64)。TimerThread把未删除的timer插入小顶堆中维护,这个堆就它一个线程用。在每次运行回调或准备睡眠前都会检查全局nearest_run_time, 如果全局更早,说明有更早的时间加入了,重复这个过程。
这里勾勒了TimerThread的大致工作原理,工程实现中还有不少细节问题,具体请阅读[timer_thread.h](https://svn.baidu.com/public/trunk/bthread/bthread/timer_thread.h)[timer_thread.cpp](https://svn.baidu.com/public/trunk/bthread/bthread/timer_thread.cpp)
这里勾勒了TimerThread的大致工作原理,工程实现中还有不少细节问题,具体请阅读[timer_thread.h](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/src/bthread/timer_thread.h)[timer_thread.cpp](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/src/bthread/timer_thread.cpp)
这个方法之所以有效:
......@@ -48,10 +48,8 @@
至此baidu-rpc在默认配置下不再有全局竞争点,在400个线程同时运行时,profiling也显示几乎没有对锁的等待。
下面是一些和linux下时间管理相关的知识:
- epoll_wait的超时精度是毫秒,较差。pthread_cond_timedwait的超时使用timespec,精度到纳秒,一般是60微秒左右的延时。
- 出于性能考虑,TimerThread使用wall-time,而不是单调时间,可能受到系统时间调整的影响。具体来说,如果在测试中把系统时间往前或往后调一个小时,程序行为将完全undefined。未来可能会让用户选择单调时间。
- 在cpu支持nonstop_tsc和constant_tsc的机器上,baidu-rpc和bthread会优先使用基于rdtsc的cpuwide_time_us。那两个flag表示rdtsc可作为wall-time使用,不支持的机器上会转而使用较慢的内核时间。我们的机器(Intel Xeon系列)大都有那两个flag。rdtsc作为wall-time使用时是否会受到系统调整时间的影响,未测试不清楚。
\ No newline at end of file
- 在cpu支持nonstop_tsc和constant_tsc的机器上,baidu-rpc和bthread会优先使用基于rdtsc的cpuwide_time_us。那两个flag表示rdtsc可作为wall-time使用,不支持的机器上会转而使用较慢的内核时间。我们的机器(Intel Xeon系列)大都有那两个flag。rdtsc作为wall-time使用时是否会受到系统调整时间的影响,未测试不清楚。
# AddressSanitizer介绍
​ AddressSanitizer最初由google研发,简称asan, 用于运行时检测C/C++程序中的内存错误,相比较传统工具如valgind,运行速度快,检测到错误之后,输出信息非常详细,可以通过add2line符号化输出,从而直接定位到代码行,方便快速的定位问题;
官方doc: <http://clang.llvm.org/docs/AddressSanitizer.html>
# AddressSanitizer可以检测的错误类型
- 堆、栈及全局变量的越界访问;
- use-after-free;
- use-after-return;
- double-free, invalid free;
- 内存泄露;
# AddressSanitizer支持的平台
- Linux i386/x86_64
- OS X 10.7 - 10.11 (i386/x86_64)
- iOS Simulator
- Android ARM
- FreeBSD i386/x86_64
# AddressSanitizer如何使用
## **4.1环境配置**
- gcc4.8及以上版本 (gcc4.8已经集成了asan功能);
- asan不支持tcmalloc,所以代码中请确保关闭该功能;
## **4.2 使用方法(针对C++)**
- 在COMAKE文件中添加asan编译参数: -fPIC -fsanitize=address -fno-omit-frame-pointer
- 在COMAKE文件中添加asan链接参数:-lasan
# 使用示例
1. ** **为了验证asan的功能是否生效,我们手动在测试代码client.cpp的中添加了内存越界访问的错误代码,该代码中依赖baidu-rpc生成的静态链接库libbdrpc.a;
2. 预期:启动client及server之后,输出内存越界错误信息,表明asan的配置已经生效;
## **5.1 在测试代码client.cpp中添加内存越界代码**
** **在测试代码client.cpp中添加以下内存越界代码:
​ ![img](http://wiki.baidu.com/download/attachments/120898866/mc.png?version=1&modificationDate=1436440081000&api=v2)
## **5.2 使用asan检测测试代码及baidu-rpc内存错误**
- 在baidu-rpc源码及测试代码client.cpp中,修改COMAKE文件,添加asan编译及链接的参数:
​ ![img](http://wiki.baidu.com/download/attachments/120898866/22.png?version=1&modificationDate=1436440186000&api=v2)
- 保存之后,执行comake2 -UB && comake2 && make -j10,生成静态链接库 libbdrpc.a;
- 运行测试代码的可执行文件echo_client及echo_server,输出内存越界的错误提示,表明环境设置成功;
![img](http://wiki.baidu.com/download/attachments/120898866/cc.png?version=1&modificationDate=1436440721000&api=v2)
- 使用addr2line符号化输出,直接定位到代码行,根据个人需求,可以直接使用addr2line,也可以写脚本实现:
![img](http://wiki.baidu.com/download/attachments/120898866/tt.png?version=1&modificationDate=1436440921000&api=v2)
- 以上则表明环境配置成功,如果代码中有内存越界等问题的话,asan会检测出来,并直接输出到标准错误;
\ No newline at end of file
## 获取脚本
你需要[public/baidu-rpc/tools](http://websvn.work.baidu.com/repos/public/list/trunk/baidu-rpc/tools)目录下的makeproj, makeproj_template和shflags。它们可以被拷贝其他地方。
## 使用makeproj
```
$ ./makeproj -h
USAGE: ./makeproj [flags] args
flags:
--path: Repository <path> of your project. e.g. app/ecom/fcr/my_project (default: '.')
--workroot: Where to put your code tree (default: '')
--namespace: namespace of your code (default: 'example')
--port: default port of your server (default: 9000)
-h,--[no]help: show this help (default: false)
```
以~/jpaas_test为根目录建立代码路径为app/ecom/fcr/fancy_project的项目。(目录会自动建立)
```
$ makeproj --path app/ecom/fcr/fancy_project --workroot ~/jpaas_test
```
进入新建立的模块目录:
```
$ cd ~/jpaas_test/app/ecom/fcr/fancy_project/
$ ls
build.sh COMAKE fancy_project_client.cpp fancy_project.proto fancy_project_server.cpp jpaas_control Release
```
运行build.sh会下载依赖模块、编译、并生成output/bin/<server>.tar.gz,它可以部署至jpaas。如下图所示:
```
$ ls output/bin
fancy_project_client fancy_project_server fancy_project_server.tar.gz
```
\ No newline at end of file
# 1 背景介绍
目 前公司的rpc框架有baidu-rpc、hulu、sofa、nova等,很多的业务都是基于这些框架搭建服务的,但是如果依赖的下游未ready的情 况下,很难开展联调测试,目前在进行功能验证时,只能人工的写一些mock代码来模拟下游,这种方式的代码复用率低,不同产品测试之间无法共享使用。此 外,pbrpc的模块的异常测试往往也是需要修改接口代码,改变接口行为,模拟异常,测试很难自动化;pbrpc mockserver就是解决在这些问题,具体的适用场景:
使用场景:
1. **模块功能验证**: 涉及上下游模块联调测试,但依赖的下游未ready情况下,可以快速的用pbprc_mockserver,模拟下游,测试模块自身功能;
2. **异常测试**:下游server的异常难构造,可以通过pbrpc_mockserver 来定制预期的response,构造各种异常场景、异常数据等;
# 2 使用示例
1. 获取工具:svn co <https://svn.baidu.com/com-test/trunk/public/baidu-rpc/pbrpc_service_tools/pbrpc_mockserver/>
2. 生成一个简单的echo服务的mockserver,执行如下指令
```python
./gen_mock_server.py -s example.EchoService -m Echo -p .proto/echo.proto
```
- 当前data目录生成,json格式的输入和输出文件,**用户可以修改json 来自定义 response**
```bash
ll data/
total 12
-rw-rw-r-- 1 work work 26 Dec 23 20:26 example.EchoService.Echo.input.json
-rw-rw-r-- 1 work work 26 Dec 23 20:26 example.EchoService.Echo.output.json
-rw-rw-r-- 1 work work 202 Dec 23 20:26 example.EchoService.info
```
- 在src/mockserver/ 目录下生成mock 代码
3. 执行sh bulid.sh 编译生成mockserver
4. 启动mockserver ./mockserver --port=9999 &
# 3 实现介绍
## 3.1 内部框架
![img](http://wiki.baidu.com/download/attachments/105293828/image2014-12-22%2019%3A53%3A41.png?version=1&modificationDate=1433828648000&api=v2)
## 3.2 功能详解
- ### 接收用户注册proto & service,生成response模版,支持用户自定义
用 户提供rpc的proto文件,并且提供需要mock的service name,二者缺一不可。 使用protobuf自带的protoc工具 基于proto文件生成c语言文件;根据service name来获取service的method,method的输入输出类型,根据输出类型获取响应的结构,生成用户可读的json格式的响应文件。 用户根据响应json文件的模板,自定义响应各个字段的内容;
模块工作流程如下:
1. Proto文件使用protoc生成c语言描述文件,编译进mockserver proto解析系统;
2. Mockserver proto解析系统根据service name,解析service中的各个method;
3. 根据method解析method的响应message类型;
4. 根据响应message类型,使用递归的方式遍历message的各个字段,包括嵌套的message
5. 生成key-value对形式的json格式的响应类型
- ### mockserver 源码的自动生成:
根据用户提供的proto,和用户需要mock的service,基于baidu-rpc的通用server的模版,自动生成指定service的mockserver源码。
- ### response自动填充功能:
Mockserver模块负责解析用户请求,根据用户请求的rpc协议,解析出用户请求的service及对应的method,根据proto及method的得出需要响应的message类型,工作流程如下:
1. Mockserver接收用户请求;
2. 解析用户请求,根据用户请求类型,分别使用不同的协议处理handler处理;
3. 解析出请求的service,及对应的method;
4. 根据method解析出request,并得出response的类型;
5. 根据response的类型,结合用户自定义的response类型的json串,填充response结构并转换为pb格式;
6. 组装响应,发送给客户端,完成server mock。
\ No newline at end of file
......@@ -2,11 +2,11 @@ baidu-rpc可通过多种方式访问用ub搭建的服务。
# ubrpc (by protobuf)
r31687后,baidu-rpc支持通过protobuf访问ubrpc,不需要baidu-rpc-ub,也不依赖idl-compiler。(也可以让protobuf服务被ubrpc client访问,方法见[使用ubrpc的服务](http://wiki.baidu.com/pages/viewpage.action?pageId=213828733#id-实现NsheadService-使用ubrpc的服务))。
r31687后,baidu-rpc支持通过protobuf访问ubrpc,不需要baidu-rpc-ub,也不依赖idl-compiler。(也可以让protobuf服务被ubrpc client访问,方法见[使用ubrpc的服务](nshead_service.md#id-实现NsheadService-使用ubrpc的服务))。
**步骤:**
1.[public/mcpack2pb/idl2proto](https://svn.baidu.com/public/trunk/mcpack2pb/idl2proto)把idl文件转化为proto文件,老版本idl2proto不会转化idl中的service,需要手动转化。
1.[idl2proto](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/tools/idl2proto)把idl文件转化为proto文件,老版本idl2proto不会转化idl中的service,需要手动转化。
```protobuf
// Converted from echo.idl by public/mcpack2pb/idl2proto
......@@ -146,7 +146,7 @@ r31687后,baidu-rpc支持通过protobuf访问ubrpc,不需要baidu-rpc-ub,
// cntl.idl_result();
```
例子详见[example/echo_c++_ubrpc_compack](https://svn.baidu.com/public/trunk/baidu-rpc/example/echo_c++_ubrpc_compack/)。
例子详见[example/echo_c++_ubrpc_compack](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/example/echo_c++_ubrpc_compack/)。
# ubrpc (by baidu-rpc-ub)
......@@ -154,7 +154,7 @@ server端由public/ubrpc搭建,request/response使用idl文件描述字段,
**步骤:**
1. 依赖[public/baidu-rpc-ub](https://svn.baidu.com/public/trunk/baidu-rpc-ub)模块,在COMAKE中增加依赖:`CONFIGS('public/baidu-rpc-ub@ci-base')。`这个模块是baidu-rpc的扩展,不需要的用户不会依赖idl/mcpack/compack等模块。baidu-rpc-ub只包含扩展代码,baidu-rpc中的新特性会自动体现在这个模块中。
1. 依赖[public/baidu-rpc-ub](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob-ub)模块,在COMAKE中增加依赖:`CONFIGS('public/baidu-rpc-ub@ci-base')。`这个模块是baidu-rpc的扩展,不需要的用户不会依赖idl/mcpack/compack等模块。baidu-rpc-ub只包含扩展代码,baidu-rpc中的新特性会自动体现在这个模块中。
2. 编写一个proto文件,其中定义了service,名字和idl中的相同,但请求类型必须是baidu.rpc.UBRequest,回复类型必须是baidu.rpc.UBResponse。这两个类型定义在baidu/rpc/ub.proto中,使用时得import
......@@ -219,7 +219,7 @@ server端由public/ubrpc搭建,request/response使用idl文件描述字段,
...
```
具体example代码可以参考[echo_c++_compack_ubrpc](https://svn.baidu.com/public/trunk/baidu-rpc-ub/example/echo_c++_compack_ubrpc/),类似的还有[echo_c++_mcpack_ubrpc](https://svn.baidu.com/public/trunk/baidu-rpc-ub/example/echo_c++_mcpack_ubrpc/)。
具体example代码可以参考[echo_c++_compack_ubrpc](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob-ub/example/echo_c++_compack_ubrpc/),类似的还有[echo_c++_mcpack_ubrpc](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob-ub/example/echo_c++_mcpack_ubrpc/)。
# nshead+idl
......@@ -258,7 +258,7 @@ channel.CallMethod(NULL, &cntl, &request, &response, NULL); // 假设channel
response.message();
```
具体example代码可以参考[echo_c++_mcpack_ub](https://svn.baidu.com/public/trunk/baidu-rpc-ub/example/echo_c++_mcpack_ub/),compack情况类似,不再赘述
具体example代码可以参考[echo_c++_mcpack_ub](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob-ub/example/echo_c++_mcpack_ub/),compack情况类似,不再赘述
# nshead+mcpack(非idl产生的)
......@@ -305,11 +305,11 @@ const mc_pack_t* res_pack = response.McpackHandle();
mc_pack_get_str(res_pack, "mystr");
```
具体example代码可以参考[echo_c++_raw_mcpack](https://svn.baidu.com/public/trunk/baidu-rpc-ub/example/echo_c++_raw_mcpack/)。
具体example代码可以参考[echo_c++_raw_mcpack](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob-ub/example/echo_c++_raw_mcpack/)。
# nshead+blob
r32897后baidu-rpc直接支持用nshead+blob访问老server(而不用依赖baidu-rpc-ub)。example代码可以参考[nshead_extension_c++](https://svn.baidu.com/public/trunk/baidu-rpc/example/nshead_extension_c++/client.cpp)。
r32897后baidu-rpc直接支持用nshead+blob访问老server(而不用依赖baidu-rpc-ub)。example代码可以参考[nshead_extension_c++](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/example/nshead_extension_c++/client.cpp)。
```c++
#include <baidu/rpc/nshead_message.h>
......@@ -342,7 +342,7 @@ if (cntl.Failed()) {
// response.head and response.body contains nshead_t and blob respectively.
```
或者用户也可以使用baidu-rpc-ub中的UBRawBufferRequest和UBRawBufferResponse来访问。example代码可以参考[echo_c++_raw_buffer](https://svn.baidu.com/public/trunk/baidu-rpc-ub/example/echo_c++_raw_buffer/)。
或者用户也可以使用baidu-rpc-ub中的UBRawBufferRequest和UBRawBufferResponse来访问。example代码可以参考[echo_c++_raw_buffer](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob-ub/example/echo_c++_raw_buffer/)。
```c++
baidu::rpc::Channel channel;
......
[public/bvar](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/src/)是多线程环境下的计数器类库,方便记录和查看用户程序中的各类数值,它利用了thread local存储避免了cache bouncing,相比UbMonitor几乎不会给程序增加性能开销,也快于竞争频繁的原子操作。baidu-rpc集成了bvar,[/vars](http://brpc.baidu.com:8765/vars)可查看所有曝光的bvar,[/vars/VARNAME](http://brpc.baidu.com:8765/vars/rpc_socket_count)可查阅某个bvar,增加计数器的方法请查看[bvar](http://wiki.baidu.com/display/RPC/bvar)。baidu-rpc大量使用了bvar提供统计数值,当你需要在多线程环境中计数并展现时,应该第一时间想到bvar。但bvar不能代替所有的计数器,它的本质是把写时的竞争转移到了读:读得合并所有写过的线程中的数据,而不可避免地变慢了。当你读写都很频繁并得基于数值做一些逻辑判断时,你不应该用bvar。
## 查询方法
[/vars](http://brpc.baidu.com:8765/vars) : 列出所有的bvar
[/vars/NAME](http://brpc.baidu.com:8765/vars/rpc_socket_count):查询名字为NAME的bvar
[/vars/NAME1,NAME2,NAME3](http://brpc.baidu.com:8765/vars/pid;process_cpu_usage;rpc_controller_count):查询名字为NAME1或NAME2或NAME3的bvar
[/vars/foo*,b$r](http://brpc.baidu.com:8765/vars/rpc_server*_count;iobuf_blo$k_*):查询名字与某一统配符匹配的bvar,注意用$代替?匹配单个字符,因为?在url中有特殊含义。
以下动画演示了如何使用过滤功能。你可以把包含过滤表达式的url复制粘贴给他人,他们点开后将看到你看到的内容。
![img](http://wiki.baidu.com/download/attachments/37774685/filter_bvar.gif?version=1&modificationDate=1494403262000&api=v2)
1.0.123.31011及之后的版本加入了一个搜索框加快了寻找特定bvar的速度,在这个搜索框你只需键入bvar名称的一部分,框架将补上*进行模糊查找。不同的名称间可以逗号、分号或空格分隔。
![img](http://wiki.baidu.com/download/attachments/37774685/search_var.gif?version=1&modificationDate=1494403229000&api=v2)
你也可以在命令行中访问vars:
```
$ curl brpc.baidu.com:8765/vars/bthread*
bthread_creation_count : 125134
bthread_creation_latency : 3
bthread_creation_latency_50 : 3
bthread_creation_latency_90 : 5
bthread_creation_latency_99 : 7
bthread_creation_latency_999 : 12
bthread_creation_latency_9999 : 12
bthread_creation_latency_cdf : "click to view"
bthread_creation_latency_percentiles : "[3,5,7,12]"
bthread_creation_max_latency : 7
bthread_creation_qps : 100
bthread_group_status : "0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 "
bthread_num_workers : 24
bthread_worker_usage : 1.01056
```
## 查看历史趋势
点击大部分数值型的bvar会显示其历史趋势。每个可点击的bvar记录了过去60秒,60分钟,24小时,30天总计174个值。当有1000个可点击bvar时大约会占1M内存。
![img](http://wiki.baidu.com/download/attachments/37774685/plot_bvar.gif?version=1&modificationDate=1494403276000&api=v2)
## 统计和查看分位值
x%分位值(percentile)是指把一段时间内的N个统计值排序,排在第N * x%位的值就是x%分位值。比如一段时间内有1000个值,排在第500位(1000 * 50%)的值是50%分位值(即中位数),排在第990位的是99%分位值(1000 * 99%),排在第999位的是99.9%分位值。分位值能比平均值更准确的刻画数值分布,对衡量系统SLA有重要意义。对于最常见的延时统计,平均值很难反映出实质性的内容,99.9%分位值往往更加关键,它决定了系统能做什么。
分位值可以绘制为CDF曲线和按时间变化时间。
![img](http://wiki.baidu.com/download/attachments/71337189/image2015-9-21%2022%3A34%3A14.png?version=1&modificationDate=1442846055000&api=v2)
上图是CDF曲线。纵轴是延时。横轴是小于纵轴数值的数据比例。很明显地,这个图就是由从10%到99.99%的所有分位值组成。比如横轴=50%处对应的纵轴值便是50%分位值。那为什么要叫它CDF?CDF是[Cumulative Distribution Function](https://en.wikipedia.org/wiki/Cumulative_distribution_function)的缩写。当我们选定一个纵轴值x时,对应横轴的含义是"数值 <= x的比例”,如果数值是来自随机采样,那么含义即为“数值 <= x的概率”,这不就是概率的定义么?CDF的导数是[概率密度函数](https://en.wikipedia.org/wiki/Probability_density_function),换句话说如果我们把CDF的纵轴分为很多小段,对每个小段计算两端对应的横轴值之差,并把这个差作为新的横轴,那么我们便绘制了PDF曲线,就像(横着的)正态分布,泊松分布那样。但密度会放大差距,中位数的密度往往很高,在PDF中很醒目,这使得边上的长尾相对很扁而不易查看,所以大部分系统测试结果选择CDF曲线而不是PDF曲线。
可以用一些简单规则衡量CDF曲线好坏:
- 越平越好。一条水平线是最理想的,这意味着所有的数值都相等,没有任何等待,拥塞,停顿。当然这是不可能的。
- 99%之后越窄越好:99%之后是长尾的聚集地,对大部分系统的SLA有重要影响,越少越好。如果存储系统给出的性能指标是"99.9%的读请求在xx毫秒内完成“,那么你就得看下99.9%那儿的值;如果检索系统给出的性能指标是”99.99%的请求在xx毫秒内返回“,那么你得关注99.99%分位值。
一条真实的好CDF曲线的特征是”斜率很小,尾部很窄“。
![img](http://wiki.baidu.com/download/attachments/71337189/image2015-9-21%2022%3A57%3A1.png?version=1&modificationDate=1442847422000&api=v2)
上图是按时间变化曲线。包含了4条曲线,横轴是时间,纵轴从上到下分别对应99.9%,99%,90%,50%分位值。颜色从上到下也越来越浅(从橘红到土黄)。滑动鼠标可以阅读对应数据点的值,上图中显示是”39秒种前的99%分位值是330微秒”。这幅图中不包含99.99%的曲线,因为99.99%分位值常明显大于99.9%及以下的分位值,画在一起的话会使得其他曲线变得很”矮“,难以辨认。你可以点击以"_latency_9999"结尾的bvar独立查看99.99%曲线,当然,你也可以独立查看50%,90%,99%,99.9%等曲线。按时间变化曲线可以看到分位值的变化趋势,对分析系统的性能变化很实用。
baidu-rpc的服务都会自动统计延时分布,用户不用自己加了。如下图所示:
![img](http://wiki.baidu.com/download/attachments/71337189/image2015-9-21%2023%3A5%3A41.png?version=1&modificationDate=1442847942000&api=v2)
你可以用bvar::LatencyRecorder统计非baidu-rpc服务的延时,这么做(更具体的使用方法请查看[bvar-c++](bvar_c++.md)):
```c++
#include <bvar/bvar.h>
...
bvar::LatencyRecorder g_latency_recorder("client"); // expose this recorder
...
void foo() {
...
g_latency_recorder << my_latency;
...
}
```
如果这个程序使用了baidu-rpc server,那么你应该已经可以在/vars看到client_latency, client_latency_cdf等变量,点击便可查看动态曲线。如下图所示:
![img](http://wiki.baidu.com/download/thumbnails/71337189/image2015-9-21%2023%3A33%3A16.png?version=1&modificationDate=1442849597000&api=v2)
## 非baidu-rpc server
如果这个程序只是一个baidu-rpc client或根本没有使用baidu-rpc,并且你也想看到动态曲线,看[这里](dummy_server.md)
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