Commit 9cd95fa1 authored by gejun's avatar gejun

Add almost all images

parent 023371e6
......@@ -18,9 +18,9 @@
# Cacheline
没有任何竞争或只被一个线程访问的原子操作是比较快的,“竞争”指的是多个线程同时访问同一个[cacheline](https://en.wikipedia.org/wiki/CPU_cache#Cache_entries)。现代CPU为了以低价格获得高性能,大量使用了cache,并把cache分了多级。百度内常见的Intel E5-2620拥有32K的L1 dcache和icache,256K的L2 cache和15M的L3 cache。其中L1和L2cache为每个核心独有,L3则所有核心共享。一个核心写入自己的L1 cache是极快的(4 cycles, 2 ns),但当另一个核心读或写同一处内存时,它得确认看到其他核心中对应的cacheline。对于软件来说,这个过程是原子的,不能在中间穿插其他代码,只能等待CPU完成[一致性同步](https://en.wikipedia.org/wiki/Cache_coherence),这个复杂的算法相比其他操作耗时会很长,在E5-2620上大约在700ns左右。所以访问被多个线程频繁共享的内存是比较慢的。
没有任何竞争或只被一个线程访问的原子操作是比较快的,“竞争”指的是多个线程同时访问同一个[cacheline](https://en.wikipedia.org/wiki/CPU_cache#Cache_entries)。现代CPU为了以低价格获得高性能,大量使用了cache,并把cache分了多级。百度内常见的Intel E5-2620拥有32K的L1 dcache和icache,256K的L2 cache和15M的L3 cache。其中L1和L2cache为每个核心独有,L3则所有核心共享。一个核心写入自己的L1 cache是极快的(4 cycles, 2 ns),但当另一个核心读或写同一处内存时,它得确认看到其他核心中对应的cacheline。对于软件来说,这个过程是原子的,不能在中间穿插其他代码,只能等待CPU完成[一致性同步](https://en.wikipedia.org/wiki/Cache_coherence),这个复杂的算法相比其他操作耗时会很长,在E5-2620上竞争激烈时大约在700ns左右。所以访问被多个线程频繁共享的内存是比较慢的。
要提高性能,就要避免让CPU同步cacheline。这不仅仅和原子指令的性能有关,而是会影响到程序的整体性能。比如像一些临界区很小的场景,使用spinlock效果仍然不佳,问题就在于实现spinlock使用的exchange,fetch_add等指令必须在CPU同步好最新的cacheline后才能完成,看上去只有几条指令,花费若干微秒不奇怪。最有效的解决方法非常直白:**尽量避免共享**。从源头规避掉竞争是最好的,有竞争就要协调,而协调总是很难的。
要提高性能,就要避免让CPU同步cacheline。这不单和原子指令本身的性能有关,还会影响到程序的整体性能。比如像一些临界区很小的场景,使用spinlock效果仍然不佳,问题就在于实现spinlock使用的exchange,fetch_add等指令必须在CPU同步好最新的cacheline后才能完成,看上去只有几条指令,花费若干微秒却不奇怪。最有效的解决方法很直白:**尽量避免共享**。从源头规避掉竞争是最好的,有竞争就要协调,而协调总是很难的。
- 一个依赖全局多生产者多消费者队列(MPMC)的程序难有很好的多核扩展性,因为这个队列的极限吞吐取决于同步cache的延时,而不是核心的个数。最好是用多个SPMC或多个MPSC队列,甚至多个SPSC队列代替,在源头就规避掉竞争。
- 另一个例子是全局计数器,如果所有线程都频繁修改一个全局变量,性能就会很差,原因同样在于不同的核心在不停地同步同一个cacheline。如果这个计数器只是用作打打日志之类的,那我们完全可以让每个线程修改thread-local变量,在需要时再合并所有线程中的值,性能可能有几十倍的差别。
......@@ -29,7 +29,7 @@
# Memory fence
仅靠原子累加实现不了对资源的访问控制,即使简单如[spinlock](https://en.wikipedia.org/wiki/Spinlock)[引用计数](https://en.wikipedia.org/wiki/Reference_counting),看上去正确的代码也可能会crash。这里的关键在于**重排指令**导致了读写一致性的变化。只要没有依赖,代码中在后面的指令(包括访存)就可能跑到前面去,[编译器](http://preshing.com/20120625/memory-ordering-at-compile-time/)[CPU](https://en.wikipedia.org/wiki/Out-of-order_execution)都会这么做。这么做的动机非常自然,CPU要尽量塞满每个cycle,在单位时间内运行尽量多的指令。一个核心访问自己独有的cache是很快的,所以它能很好地管理好一致性问题。当软件依次写入a,b,c后,它能以a,b,c的顺序依次读到,哪怕在CPU层面是完全并发运行的。当代码只运行于单线程中时,重排对软件是透明的。但在多核环境中,这就不成立了。如上节中提到的,访存在等待cacheline同步时要花费数百纳秒,最高效地自然是同时同步多个cacheline,而不是一个个做。一个线程在代码中对多个变量的依次修改,可能会以不同的次序同步到另一个线程所在的核心上,CPU也许永远无法保证这个顺序如同TCP那样,有序修改有序读取,因为不同线程对数据的需求顺序是不同的,按需访问是合理的(从而导致同步cacheline的序和写序不同)。如果其中第一个变量扮演了开关的作用,控制对后续变量对应资源的访问。那么当这些变量被一起同步到其他核心时,更新顺序可能变了,第一个变量未必是第一个更新的,其他线程可能还认为它代表着其他变量有效,而去访问了已经被删除的资源,从而导致未定义的行为。比如下面的代码片段:
仅靠原子累加实现不了对资源的访问控制,即使简单如[spinlock](https://en.wikipedia.org/wiki/Spinlock)[引用计数](https://en.wikipedia.org/wiki/Reference_counting),看上去正确的代码也可能会crash。这里的关键在于**重排指令**导致了读写一致性的变化。只要没有依赖,代码中在后面的指令(包括访存)就可能跑到前面去,[编译器](http://preshing.com/20120625/memory-ordering-at-compile-time/)[CPU](https://en.wikipedia.org/wiki/Out-of-order_execution)都会这么做。这么做的动机非常自然,CPU要尽量塞满每个cycle,在单位时间内运行尽量多的指令。一个核心访问自己独有的cache是很快的,所以它能很好地管理好一致性问题。当软件依次写入a,b,c后,它能以a,b,c的顺序依次读到,哪怕在CPU层面是完全并发运行的。当代码只运行于单线程中时,重排对软件是透明的。但在多核环境中,这就不成立了。如上节中提到的,访存在等待cacheline同步时要花费数百纳秒,最高效地自然是同时同步多个cacheline,而不是一个个做。一个线程在代码中对多个变量的依次修改,可能会以不同的次序同步到另一个线程所在的核心上,CPU也许永远无法保证这个顺序如同TCP那样,有序修改有序读取,因为不同线程对数据的需求顺序是不同的,按需访问更合理(从而导致同步cacheline的序和写序不同)。如果其中第一个变量扮演了开关的作用,控制对后续变量对应资源的访问。那么当这些变量被一起同步到其他核心时,更新顺序可能变了,第一个变量未必是第一个更新的,其他线程可能还认为它代表着其他变量有效,而去访问了已经被删除的资源,从而导致未定义的行为。比如下面的代码片段:
```c++
// Thread 1
......@@ -44,11 +44,13 @@ if (ready) {
p.bar();
}
```
从人的角度,这是对的,因为线程2在ready为true时才会访问p,按线程1的逻辑,此时p应该初始化好了。但对多核机器而言,这段代码难以正常运行:
从人的角度,这是对的,因为线程2在ready为true时才会访问p,按线程1的逻辑,此时p应该初始化好了。但对多核机器而言,这段代码可能难以正常运行:
- 线程1中的ready = true可能会被编译器或cpu重排到p.init()之前,从而使线程2看到ready为true时,p仍然未初始化。
- 即使没有重排,ready和p的值也会独立地同步到线程2所在核心的cache,线程2仍然可能在看到ready为true时看到未初始化的p。这种情况同样也会在线程2中发生,比如p.bar()中的一些代码被重排到检查ready之前。
注:x86的load带acquire语意,store带release语意,上面的代码刨除编译器因素可以正确运行。
通过这个简单例子,你可以窥见原子指令编程的复杂性了吧。为了解决这个问题,CPU提供了[memory fence](http://en.wikipedia.org/wiki/Memory_barrier),让用户可以声明访存指令间的可见性(visibility)关系,boost和C++11对memory fencing做了抽象,总结为如下几种[memory order](http://en.cppreference.com/w/cpp/atomic/memory_order).
| memory order | 作用 |
......@@ -60,8 +62,6 @@ if (ready) {
| memory_order_acq_rel | acquire + release语意 |
| memory_order_seq_cst | acq_rel语意外加所有使用seq_cst的指令有严格地全序关系 |
有了memory order,上面的例子可以这么更正:
```c++
......@@ -91,11 +91,11 @@ if (ready.load(std::memory_order_acquire)) {
# wait-free & lock-free
原子指令能为我们的服务赋予两个重要属性:[wait-free](http://en.wikipedia.org/wiki/Non-blocking_algorithm#Wait-freedom)[lock-free](http://en.wikipedia.org/wiki/Non-blocking_algorithm#Lock-freedom)。前者指不管OS如何调度线程,每个线程都始终在做有用的事;后者比前者弱一些,指不管OS如何调度线程,至少有一个线程在做有用的事。如果我们的服务中使用了锁,那么OS可能把一个刚获得锁的线程切换出去,这时候所有依赖这个锁的线程都在等待,而没有做有用的事,所以用了锁就不是lock-free,更不会是wait-free。为了确保一件事情总在确定时间内完成,实时操作系统(RTOS)的关键代码至少是lock-free的。在我们广泛又多样的在线服务中,对时效性也有着严苛的要求,如果RPC中最关键的部分满足wait-free或lock-free,就可以提供更稳定的服务质量
原子指令能为我们的服务赋予两个重要属性:[wait-free](http://en.wikipedia.org/wiki/Non-blocking_algorithm#Wait-freedom)[lock-free](http://en.wikipedia.org/wiki/Non-blocking_algorithm#Lock-freedom)。前者指不管OS如何调度线程,每个线程都始终在做有用的事;后者比前者弱一些,指不管OS如何调度线程,至少有一个线程在做有用的事。如果我们的服务中使用了锁,那么OS可能把一个刚获得锁的线程切换出去,这时候所有依赖这个锁的线程都在等待,而没有做有用的事,所以用了锁就不是lock-free,更不会是wait-free。为了确保一件事情总在确定时间内完成,实时系统的关键代码至少是lock-free的。在我们广泛又多样的在线服务中,对时效性也有着严苛的要求,如果RPC中最关键的部分满足wait-free或lock-free,就可以提供更稳定的服务质量。比如,由于[fd](https://en.wikipedia.org/wiki/File_descriptor)只适合被单个线程操作,baidu-rpc中使用原子指令最大化了fd的读写的并发度,具体见[IO](io.md)
值得提醒的是,常见想法是lock-free或wait-free的算法会更快,但事实可能相反,因为:
- lock-free和wait-free必须处理复杂的race condition和ABA problem,完成相同目的的代码比用锁更复杂。
- 使用mutex的算法变相带“后退”效果。后退(backoff)指出现竞争时尝试另一个途径以避免激烈的竞争,mutex出现竞争时会使调用者睡眠,在高度竞争时规避了激烈的cacheline同步,使拿到锁的那个线程可以很快地完成一系列流程,总体吞吐可能反而高了。
- 使用mutex的算法变相带“后退”效果。后退(backoff)指出现竞争时尝试另一个途径以避免激烈的竞争,mutex出现竞争时会使调用者睡眠,在高度竞争时规避了激烈的cacheline同步,使拿到锁的那个线程可以很快地独占完成一系列流程,总体吞吐可能反而高了。
mutex导致低性能往往是因为临界区过大(限制了并发度),或临界区过小(上下文切换开销变得突出,应考虑用adaptive mutex)。lock-free和wait-free算法的价值在于其避免了deadlock/livelock,在各种情况下的稳定表现,而不是绝对的高性能。但在一种情况下lock-free和wait-free算法的性能多半更高:就是算法本身可以用少量原子指令实现。实现锁也是要用原子指令的,当算法本身用一两条指令就能完成的时候,相比额外用锁肯定是更快了。
mutex导致低性能往往是因为临界区过大(限制了并发度),或临界区过小(上下文切换开销变得突出,应考虑用adaptive mutex)。lock-free/wait-free算法的价值在于其保证了一个或所有线程始终在做有用的事,而不是绝对的高性能。但在一种情况下lock-free和wait-free算法的性能多半更高:就是算法本身可以用少量原子指令实现。实现锁也是要用原子指令的,当算法本身用一两条指令就能完成的时候,相比额外用锁肯定是更快了。
......@@ -8,19 +8,19 @@ Channel开启backup request。这个Channel会先向其中一个server发送请
运行后,client端和server端的日志分别如下,“index”是请求的编号。可以看到server端在收到第一个请求后会故意sleep 20ms,client端之后发送另一个同样index的请求,最终的延时并没有受到故意sleep的影响。
![img](http://wiki.baidu.com/download/attachments/160281427/image2015-12-28%2019%3A48%3A54.png?version=1&modificationDate=1451303334000&api=v2)
![img](../images/backup_request_1.png)
![img](http://wiki.baidu.com/download/attachments/160281427/image2015-12-28%2019%3A48%3A2.png?version=1&modificationDate=1451303282000&api=v2)
![img](../images/backup_request_2.png)
/rpcz也显示client在2ms后触发了backup超时并发出了第二个请求。
![img](http://wiki.baidu.com/download/attachments/160281427/image2015-12-28%2019%3A54%3A22.png?version=1&modificationDate=1451303662000&api=v2)
![img](../images/backup_request_3.png)
## 选择合理的backup_request_ms
可以观察baidu-rpc默认提供的latency_cdf图,或自行添加。cdf图的y轴是延时(默认微秒),x轴是小于y轴延时的请求的比例。在下图中,选择backup_request_ms=2ms可以大约覆盖95.5%的请求,选择backup_request_ms=10ms则可以覆盖99.99%的请求。
![img](http://wiki.baidu.com/download/attachments/160281427/image2015-12-28%2021%3A23%3A48.png?version=1&modificationDate=1451309036000&api=v2)
![img](../images/backup_request_4.png)
自行添加的方法:
......@@ -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++](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/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/tree/example/selective_echo_c++),具体做法请参考上面的过程。
【不推荐】发起两个异步RPC后Join它们,它们的done内是相互取消的逻辑。示例代码见[example/cancel_c++](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/example/cancel_c++)。这种方法的问题是总会发两个请求,对后端服务有两倍压力,这个方法怎么算都是不经济的,你应该尽量避免用这个方法。
【不推荐】发起两个异步RPC后Join它们,它们的done内是相互取消的逻辑。示例代码见[example/cancel_c++](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/tree/example/cancel_c++)。这种方法的问题是总会发两个请求,对后端服务有两倍压力,这个方法怎么算都是不经济的,你应该尽量避免用这个方法。
# 序言
在多核的前提下,性能和线程是紧密联系在一起的。线程间的跳转对高频IO操作的性能有决定性作用:一次跳转意味着至少3-20微秒的延时,由于每个核心的L1
cache独立(我们的cpu L2 cache也是独立的),随之而来是大量的cache
miss,一些变量的读取、写入延时会从纳秒级上升几百倍至微秒级:等待cpu把对应的cacheline同步过来。有时这带来了一个出乎意料的结果,当每次的处理都很简短时,一个多线程程序未必比一个单线程程序更快。因为前者可能在每次付出了大的切换代价后只做了一点点“正事”,而后者在不停地做“正事”。不过单线程也是有代价的,它工作良好的前提是“正事”都很快,否则一旦某次变慢就使后续的所有“正事”都被延迟了。在一些处理时间普遍较短的程序中,使用(多个不相交的)单线程能最大程度地”做正事“,由于每个请求的处理时间确定,延时表现也很稳定,各种http
server正是这样。但我们的检索服务要做的事情可就复杂多了,有大量的后端服务需要访问,广泛存在的长尾请求使每次处理的时间无法确定,排序策略也越来越复杂。如果还是使用(多个不相交的)单线程的话,一次难以预计的性能抖动,或是一个大请求可能导致后续一堆请求被延迟。
在多核的前提下,性能和线程是紧密联系在一起的。线程间的跳转对高频IO操作的性能有决定性作用:一次跳转意味着至少3-20微秒的延时,由于每个核心的L1 cache独立(我们的cpu L2 cache也是独立的),随之而来是大量的cache miss,一些变量的读取、写入延时会从纳秒级上升几百倍至微秒级:等待cpu把对应的cacheline同步过来。有时这带来了一个出乎意料的结果,当每次的处理都很简短时,一个多线程程序未必比一个单线程程序更快。因为前者可能在每次付出了大的切换代价后只做了一点点“正事”,而后者在不停地做“正事”。不过单线程也是有代价的,它工作良好的前提是“正事”都很快,否则一旦某次变慢就使后续的所有“正事”都被延迟了。在一些处理时间普遍较短的程序中,使用(多个不相交的)单线程能最大程度地”做正事“,由于每个请求的处理时间确定,延时表现也很稳定,各种http server正是这样。但我们的检索服务要做的事情可就复杂多了,有大量的后端服务需要访问,广泛存在的长尾请求使每次处理的时间无法确定,排序策略也越来越复杂。如果还是使用(多个不相交的)单线程的话,一次难以预计的性能抖动,或是一个大请求可能导致后续一堆请求被延迟。
为了避免请求之间相互影响,请求级的线程跳转是baidu-rpc必须付出的代价,我们能做的是使[线程跳转最优化](http://wiki.baidu.com/display/RPC/IO#IO-Thefullpicture)。不过,对服务的性能测试还不能很好地体现这点。测试中的处理往往极为简单,使得线程切换的影响空前巨大,通过控制多线程和单线程处理的比例,我们可以把一个测试服务的qps从100万到500万操纵自如(同机),这损伤了性能测试结果的可信度。要知道,我们真实的服务并不是在累加一个数字,或者echo一个字符串,一个qps几百万的echo程序没有指导意义。鉴于此,在发起性能测试一年后(15年底),在baidu-rpc又经历了1200多次改动后,我们需要review所有的测试,加强其中的线程因素,以获得对真实场景有明确意义的结果。具体来说:
为了避免请求之间相互影响,请求级的线程跳转是baidu-rpc必须付出的代价,我们能做的是使[线程跳转最优化](io.md#the-full-picture)。不过,对服务的性能测试还不能很好地体现这点。测试中的处理往往极为简单,使得线程切换的影响空前巨大,通过控制多线程和单线程处理的比例,我们可以把一个测试服务的qps从100万到500万操纵自如(同机),这损伤了性能测试结果的可信度。要知道,真实的服务并不是在累加一个数字,或者echo一个字符串,一个qps几百万的echo程序没有指导意义。鉴于此,在发起性能测试一年后(15年底),在baidu-rpc又经历了1200多次改动后,我们需要review所有的测试,加强其中的线程因素,以获得对真实场景有明确意义的结果。具体来说:
- 请求不应等长,要有长尾。这能考察RPC能否让请求并发,否则一个慢请求会影响大量后续请求。
- 要有多级server的场景。server内用client访问下游server,这能考察server和client的综合表现。
- 要有一个client访问多个server的场景。这能考察负载均衡是否足够并发,真实场景中很少一个client只访问一个server。
我们希望这套测试场景对以后其他服务的性能测试有借鉴意义。
我们希望这套测试场景对其他服务的性能测试有借鉴意义。
# 测试目标
## UB
com组(INF前身)在08年开发的RPC框架,目前仍在百度广泛使用,已被baidu-rpc代替。UB本身不支持protobuf,因此我们以基于UB的nova
pb-rpc为代表,这个框架在网盟广泛使用。测试时其代码为:<https://svn.baidu.com/app/ecom/cm/trunk/pb-rpc>(r10500)。早期的UB支持CPOOL和XPOOL,分别使用[select](http://linux.die.net/man/2/select)[leader-follower模型](http://kircher-schwanninger.de/michael/publications/lf.pdf),后来提供了EPOOL,使用[epoll](http://man7.org/linux/man-pages/man7/epoll.7.html)处理多路连接。鉴于产品线大都是用EPOOL模型,我们的UB配置也使用EPOOL。UB只支持[连接池](http://wiki.baidu.com/display/RPC/Client#Client-连接方式),结果用“**ubrpc_mc**"指代(mc代表"multiple
connection")。虽然这个名称不太准确(ubrpc是INF在10年使用UB和idlcompiler开发的一个框架),但在本文的语境下,请默认ubrpc
= UB。
com组(INF前身)在08年开发的RPC框架,目前仍在百度广泛使用,已被baidu-rpc代替。UB本身不支持protobuf,因此我们以基于UB的nova pb-rpc为代表,这个框架在网盟广泛使用。测试时其代码为:<https://svn.baidu.com/app/ecom/cm/trunk/pb-rpc>(r10500)。早期的UB支持CPOOL和XPOOL,分别使用[select](http://linux.die.net/man/2/select)[leader-follower模型](http://kircher-schwanninger.de/michael/publications/lf.pdf),后来提供了EPOOL,使用[epoll](http://man7.org/linux/man-pages/man7/epoll.7.html)处理多路连接。鉴于产品线大都是用EPOOL模型,我们的UB配置也使用EPOOL。UB只支持[连接池](client.md#连接方式),结果用“**ubrpc_mc**"指代(mc代表"multiple
connection")。虽然这个名称不太准确(ubrpc是INF在10年使用UB和idlcompiler开发的一个框架),但在本文的语境下,请默认ubrpc = UB。
## hulu-pbrpc
......@@ -37,26 +32,21 @@ PS内部广泛使用的pbrpc产品,在public/sofa-pbrpc和github.com上还有
## apache thrift
thrift是由fb开发的序列化方法和rpc框架,开源后改名apache
thrift,fb自己有一个[fbthrift分支](https://github.com/facebook/fbthrift),我们使用的是apache
thrift。测试时其代码为:<https://svn.baidu.com/third-64/tags/thrift/thrift_0-9-1-400_PD_BL/>。thrift没有线程安全的client,所以每个线程中都得建立一个client,使用独立的连接。在测试中thrift其实是占了其他实现的便宜:它的client不需要处理多线程问题。但在真实环境中,thrift的client不方便。thrift的结果用"**thrift_mc**"指代。
thrift是由fb开发的序列化方法和rpc框架,开源后改名apache thrift,fb自己有一个[fbthrift分支](https://github.com/facebook/fbthrift),我们使用的是apache thrift。测试时其代码为:<https://svn.baidu.com/third-64/tags/thrift/thrift_0-9-1-400_PD_BL/>。thrift没有线程安全的client,所以每个线程中都得建立一个client,使用独立的连接。在测试中thrift其实是占了其他实现的便宜:它的client不需要处理多线程问题。但在真实环境中,thrift的client不方便。thrift的结果用"**thrift_mc**"指代。
## grpc
由google开发的rpc框架,使用http/2和protobuf
3.0,测试时其代码为:<https://github.com/grpc/grpc/tree/release-0_11>。grpc并不是stubby,定位更像是为了推广http/2和protobuf
3.0,但鉴于很多人对它的表现很感兴趣,我们也(很麻烦地)把它加了进来。grpc的结果用"**grpc**"指代。
由google开发的rpc框架,使用http/2和protobuf 3.0,测试时其代码为:<https://github.com/grpc/grpc/tree/release-0_11>。grpc并不是stubby,定位更像是为了推广http/2和protobuf 3.0,但鉴于很多人对它的表现很感兴趣,我们也(很麻烦地)把它加了进来。grpc的结果用"**grpc**"指代。
# 测试方法
如序言中解释的那样,性能数字有巨大的调整空间。这里的关键在于,我们对RPC的底线要求是什么,脱离了这个底线,测试中的表现就严重偏离真实环境中的了。
这个底线我们认为是“RPC必须能处理长尾”
这个底线我们认为是**RPC必须能处理长尾**
在百度的环境中,这几乎是句大白话,哪个产品线,哪个系统没有长尾呢?作为承载大部分服务的RPC框架自然得处理好长尾,减少长尾对正常请求的影响。但在实现层面,这个问题对设计的影响太大了。如果测试中没有长尾,那么RPC实现就可以假设每个请求都差不多快,这时候最优的方法是用多个线程独立地处理请求。由于没有上下文切换和cache一致性同步,程序的性能会显著高于多个线程协作时的表现。
在百度的环境中,这是句大白话,哪个产品线,哪个系统没有长尾呢?作为承载大部分服务的RPC框架自然得处理好长尾,减少长尾对正常请求的影响。但在实现层面,这个问题对设计的影响太大了。如果测试中没有长尾,那么RPC实现就可以假设每个请求都差不多快,这时候最优的方法是用多个线程独立地处理请求。由于没有上下文切换和cache一致性同步,程序的性能会显著高于多个线程协作时的表现。
比如简单的echo程序,处理一个请求只需要200-300纳秒,单个线程可以达到300-500万的吞吐。但如果多个线程协作,即使在及其流畅的系统中,也要付出3-5微秒的上下文切换代价和1微秒的cache同步代价,这还没有考虑多个线程间的其他互斥逻辑,一般来说单个线程的吞吐很难超过10万,即使24核全部用满,吞吐也只有240万,不及一个线程。这正是以http
server为典型的服务选用[单线程模型](http://wiki.baidu.com/display/RPC/Threading+Overview#ThreadingOverview-单线程reactor)的原因(多个线程独立运行eventloop):大部分http请求的处理时间是可预测的,对下游的访问也不会有任何阻塞代码。这个模型可以最大化cpu利用率,同时提供可接受的延时。
比如简单的echo程序,处理一个请求只需要200-300纳秒,单个线程可以达到300-500万的吞吐。但如果多个线程协作,即使在及其流畅的系统中,也要付出3-5微秒的上下文切换代价和1微秒的cache同步代价,这还没有考虑多个线程间的其他互斥逻辑,一般来说单个线程的吞吐很难超过10万,即使24核全部用满,吞吐也只有240万,不及一个线程。这正是以http server为典型的服务选用[单线程模型](threading_overview.md#单线程reactor)的原因(多个线程独立运行eventloop):大部分http请求的处理时间是可预测的,对下游的访问也不会有任何阻塞代码。这个模型可以最大化cpu利用率,同时提供可接受的延时。
多线程付出这么大的代价是为了**隔离请求间的影响**。一个计算复杂或索性阻塞的过程不会影响到其他请求,1%的长尾最终只会影响到1%的性能。而多个独立的线程是保证不了这点的,一个请求进入了一个线程就等于“定了终生”,如果前面的请求慢了一下,那也只能跟着慢了。1%的长尾会影响远超1%的请求,最终表现不佳。换句话说,乍看上去多线程模型“慢”了,但在真实应用中反而会获得更好的综合性能。
......@@ -69,51 +59,42 @@ server为典型的服务选用[单线程模型](http://wiki.baidu.com/display/RP
性能测试使用的机器配置为:
- 单机1:CPU开超线程24核,E5-2620 @ 2.00GHz;64GB内存;OS linux 2.6.32_1-15-0-0
- 多机1(15台+8台):CPU均未开超线程12核,其中15台的CPU为E5-2420 @
1.90GHz.,64GB内存,千兆网卡,无法开启多队列。其余8台为E5-2620
2.0GHz,千兆网卡,绑定多队列到前8个核。这些长期测试机器比较杂,跨了多个机房,测试中延时在1ms以上的就是这批机器。
- 多机2(30台):CPU未开超线程12核,E5-2620 v3 @ 2.40GHz.;96GB内存;OS linux
2.6.32_1-17-0-0;万兆网卡,绑定多队列到前8个核。这是临时借用的新机器,配置非常好,都在广州机房,延时非常短,测试中延时在几百微秒的就是这批机器。
- 多机1(15台+8台):CPU均未开超线程12核,其中15台的CPU为E5-2420 @ 1.90GHz.,64GB内存,千兆网卡,无法开启多队列。其余8台为E5-2620 2.0GHz,千兆网卡,绑定多队列到前8个核。这些长期测试机器比较杂,跨了多个机房,测试中延时在1ms以上的就是这批机器。
- 多机2(30台):CPU未开超线程12核,E5-2620 v3 @ 2.40GHz.;96GB内存;OS linux 2.6.32_1-17-0-0;万兆网卡,绑定多队列到前8个核。这是临时借用的新机器,配置非常好,都在广州机房,延时非常短,测试中延时在几百微秒的就是这批机器。
测试代码:<https://svn.baidu.com/com-test/trunk/public/rpc-perf/>
下面所有的曲线图是使用baidu-rpc开发的dashboard程序绘制的,去掉路径后可以看到和所有baidu-rpc
server一样的[内置服务]()。
server一样的[内置服务](builtin_service.md)
## 配置
> 如无特殊说明,所有测试中的配置只是数量差异(线程数,请求大小,client个数etc),而不是模型差异。我们确保用户看到的qps和延时是同一个场景的不同维度,而不是无法统一的两个场景。
>
如无特殊说明,所有测试中的配置只是数量差异(线程数,请求大小,client个数etc),而不是模型差异。我们确保用户看到的qps和延时是同一个场景的不同维度,而不是无法统一的两个场景。
所有RPC server都配置了24个工作线程,这些线程一般运行用户的处理逻辑。关于每种RPC的特殊说明:
所有RPC server都配置了24个工作线程,这些线程一般运行用户的处理逻辑。关于每种RPC的特殊说明:
- UB:配置了12个reactor线程,使用EPOOL模型。连接池限制数配置为线程个数(24)
- hulu-pbrpc:
额外配置了12个IO线程。这些线程会处理fd读取,请求解析等任务。hulu有个“共享队列“的配置项,默认不打开,作用是把fd静态散列到多个线程中,由于线程间不再争抢,hulu的qps会显著提高,但会明显被长尾的影响(原因见“测试方法”)。考虑到大部分使用者并不会去改配置,我们也选择不打开。
- thrift:
额外配置了12个IO线程。这些线程会处理fd读取,请求解析等任务。thrift的client不支持多线程,每个线程得使用独立的client,连接也都是分开的。
- sofa-pbrpc:按照sofa同学的要求,把io_service_pool_size配置为24,work_thread_num配置为1。大概含义是使用独立的24组线程池,每组1个worker
thread。和hulu不打开“共享队列”时类似,这个配置会显著提高sofa-pbrpc的QPS,但同时使它失去了处理长尾的能力。如果你在真实产品中使用,我们不建议这个配置。(而应该用io_service_pool_size=1,
work_thread_num=24)
- baidu-rpc:尽管baidu-rpc的client运行在bthread中时会获得10%~20%的QPS提升和更低的延时(因为可以节省一次上下文切换,见[图中的bthread
swap](http://wiki.baidu.com/display/RPC/IO#IO-Thefullpicture)),但测试中的client都运行统一的pthread中。
所有的RPC
client都以多个线程同步方式发送,这种方法最接近于真实系统中的情况,在考察QPS时也兼顾了延时因素。一种流行的方案是client不停地往连接中写入数据看server表现,这个方法的弊端在于:server一下子能读出大量请求,不同RPC的比拼变成了“for循环执行用户代码”的比拼,而不是分发请求的效率。在真实系统中server很少能同时读到超过4个请求。这个方法也完全放弃了延时,client其实是让server陷入了雪崩时才会进入的状态,所有请求都因大量排队而超时了。
- hulu-pbrpc: 额外配置了12个IO线程。这些线程会处理fd读取,请求解析等任务。hulu有个“共享队列“的配置项,默认不打开,作用是把fd静态散列到多个线程中,由于线程间不再争抢,hulu的qps会显著提高,但会明显地被长尾影响(原因见[测试方法](#测试方法))。考虑到大部分使用者并不会去改配置,我们也选择不打开。
- thrift: 额外配置了12个IO线程。这些线程会处理fd读取,请求解析等任务。thrift的client不支持多线程,每个线程得使用独立的client,连接也都是分开的。
- sofa-pbrpc:按照sofa同学的要求,把io_service_pool_size配置为24,work_thread_num配置为1。大概含义是使用独立的24组线程池,每组1个worker thread。和hulu不打开“共享队列”时类似,这个配置会显著提高sofa-pbrpc的QPS,但同时使它失去了处理长尾的能力。如果你在真实产品中使用,我们不建议这个配置。(而应该用io_service_pool_size=1, work_thread_num=24)
- baidu-rpc:尽管baidu-rpc的client运行在bthread中时会获得10%~20%的QPS提升和更低的延时,但测试中的client都运行统一的pthread中。
所有的RPC client都以多个线程同步方式发送,这种方法最接近于真实系统中的情况,在考察QPS时也兼顾了延时因素。
一种流行的方案是client不停地往连接中写入数据看server表现,这个方法的弊端在于:server一下子能读出大量请求,不同RPC的比拼变成了“for循环执行用户代码”的比拼,而不是分发请求的效率。在真实系统中server很少能同时读到超过4个请求。这个方法也完全放弃了延时,client其实是让server陷入了雪崩时才会进入的状态,所有请求都因大量排队而超时了。
## 同机单client→单server在不同请求下的QPS(越高越好)
本测试运行在[单机1](http://wiki.baidu.com/display/RPC/Benchmark#Benchmark-环境)上。图中的数值均为用户数据的字节数,实际的请求尺寸还要包括协议头,一般会增加40字节左右。
本测试运行在[单机1](#环境)上。图中的数值均为用户数据的字节数,实际的请求尺寸还要包括协议头,一般会增加40字节左右。
结果链接:<http://brpc.baidu.com:8789/benchmark/qps_vs_reqsize.20160122.184445>
(X轴是用户数据的字节数,Y轴是对应的QPS)
![img](http://wiki.baidu.com/download/attachments/48480483/image2016-1-24%2014%3A10%3A56.png?version=1&modificationDate=1453615862000&api=v2)
![img](../images/qps_vs_reqsize.png)
### 分析
**分析**
* 以_mc结尾的曲线代表client和server保持多个连接(线程数个),在本测试中会有更好的表现。
* baidu-rpc:当请求包小于16KB时,单连接下的吞吐超过了多连接的ubrpc_mc和thrift_mc,随着请求包变大,内核对单个连接的写入速度成为瓶颈。而多连接下的baidu-rpc则一骑绝尘,达到了测试中最高的2.3GB/s。注意:虽然使用连接池的baidu-rpc在发送大包时吞吐更高,但也会耗费更多的CPU(UB和thrift也是这样)。下图中的单连接baidu-rpc已经可以提供800多兆的吞吐,足以打满万兆网卡,而使用的CPU可能只有多链接下的1/2
* (写出过程是[wait-free的](http://wiki.baidu.com/display/RPC/IO#IO-发消息)),真实系统中请优先使用单链接。
* (写出过程是[wait-free的](io.md#发消息)),真实系统中请优先使用单链接。
* thrift: 初期明显低于baidu-rpc,随着包变大超过了单连接的baidu-rpc。
* UB:
* 和thrift类似的曲线,但平均要低4-5万QPS,在32K包时超过了单连接的baidu-rpc。整个过程中QPS几乎没变过。
......@@ -123,36 +104,30 @@ client都以多个线程同步方式发送,这种方法最接近于真实系
## 同机单client→单server在不同线程数下的QPS(越高越好)
本测试运行在[单机1](http://wiki.baidu.com/display/RPC/Benchmark#Benchmark-环境)上。
本测试运行在[单机1](#环境)上。
结果链接:<http://brpc.baidu.com:8789/benchmark/qps_vs_threadnum.20160122.224920>
(X轴是线程数,Y轴是对应的QPS)
![img](http://wiki.baidu.com/download/attachments/48480483/image2016-1-24%2014%3A8%3A29.png?version=1&modificationDate=1453615715000&api=v2)
![img](../images/qps_vs_threadnum.png)
分析
**分析**
> baidu-rpc: 随着发送线程增加,QPS在快速增加,有很好的多线程扩展性。
>
baidu-rpc: 随着发送线程增加,QPS在快速增加,有很好的多线程扩展性。
UB和thrift:8个线程下高于baidu-rpc,但超过8个线程后被baidu-rpc迅速超过,thrift继续“平移”,UB出现了明显下降。
grpc,hulu-pbrpc,sofa-pbrpc: 几乎重合,256个线程时相比1个线程时只有1倍的提升,多线程扩展性不佳。
##
同机单client→单server在固定QPS下的延时[CDF](http://wiki.baidu.com/display/RPC/vars#vars-统计和查看分位值)(越左越好,越直越好)
本测试运行在[单机1](http://wiki.baidu.com/display/RPC/Benchmark#Benchmark-环境)上。考虑到不同RPC的处理能力,我们选择了一个较低、在不少系统中会达到的的QPS:1万。
## 同机单client→单server在固定QPS下的延时[CDF](vars.md#统计和查看分位值)(越左越好,越直越好)
本测试运行在[单机1](#环境)上。考虑到不同RPC的处理能力,我们选择了一个较低、在不少系统中会达到的的QPS:1万。
本测试中有1%的长尾请求耗时5毫秒,长尾请求的延时不计入结果,因为我们考察的是普通请求是否被及时处理了。
结果链接:<http://brpc.baidu.com:8789/benchmark/latency_cdf.20160122.184324>
(X轴是延时(微秒),Y轴是小于X轴延时的请求比例)
![img](http://wiki.baidu.com/download/attachments/48480483/image2016-1-22%2022%3A40%3A33.png?version=1&modificationDate=1453473636000&api=v2)
### 分析
![img](../images/latency_cdf.png)
**分析**
- baidu-rpc:平均延时短,几乎没有被长尾影响。
- UB和thrift:平均延时比baidu-rpc高1毫秒,受长尾影响不大。
- hulu-pbrpc:走向和UB和thrift类似,但平均延时进一步增加了1毫秒。
......@@ -161,75 +136,64 @@ grpc,hulu-pbrpc,sofa-pbrpc: 几乎重合,256个线程时相比1个线程
## 跨机多client→单server的QPS(越高越好)
本测试运行在[多机1](http://wiki.baidu.com/display/RPC/Benchmark#Benchmark-环境)上。
本测试运行在[多机1](#环境)上。
结果链接:<http://brpc.baidu.com:8789/benchmark/qps_vs_multi_client.20160124.151737>
(X轴是client数,Y轴是对应的QPS)
![img](http://wiki.baidu.com/download/attachments/48480483/image2016-1-24%2016%3A2%3A40.png?version=1&modificationDate=1453622566000&api=v2)
![img](../images/qps_vs_multi_client.png)
###分析
**分析**
* baidu-rpc: 随着cilent增加,server的QPS在快速增加,有不错的client扩展性。
* sofa-pbrpc:
* 随着client增加,server的QPS也在快速增加,但幅度不如baidu-rpc,client扩展性也不错。从16个client到32个client时的提升较小。
* sofa-pbrpc: 随着client增加,server的QPS也在快速增加,但幅度不如baidu-rpc,client扩展性也不错。从16个client到32个client时的提升较小。
* hulu-pbrpc: 随着client增加,server的QPS在增加,但幅度进一步小于sofa-pbrpc。
* UB:增加client几乎不能增加server的QPS。
* thrift:平均QPS低于UB,增加client几乎不能增加server的QPS。
* grpc:垫底、增加client几乎不能增加server的QPS。
##
跨机多client→单server在固定QPS下的延时[CDF](http://wiki.baidu.com/display/RPC/vars#vars-统计和查看分位值)(越左越好,越直越好)
## 跨机多client→单server在固定QPS下的延时[CDF](vars.md#统计和查看分位值)(越左越好,越直越好)
本测试运行在[多机1](http://wiki.baidu.com/display/RPC/Benchmark#Benchmark-环境)上。负载均衡算法为round-robin或RPC默认提供的。由于有32个client且一些RPC的单client能力不佳,我们为每个client仅设定了2500QPS,这是一个真实业务系统能达到的数字。
本测试运行在[多机1](#环境)上。负载均衡算法为round-robin或RPC默认提供的。由于有32个client且一些RPC的单client能力不佳,我们为每个client仅设定了2500QPS,这是一个真实业务系统能达到的数字。
本测试中有1%的长尾请求耗时15毫秒,长尾请求的延时不计入结果,因为我们考察的是普通请求是否被及时处理了。
结果链接:<http://brpc.baidu.com:8789/benchmark/multi_client_latency_cdf.20160122.215356>
(X轴是延时(微秒),Y轴是小于X轴延时的请求比例)
![img](http://wiki.baidu.com/download/attachments/48480483/image2016-1-22%2023%3A12%3A10.png?version=1&modificationDate=1453475533000&api=v2)
###分析
![img](../images/multi_client_latency_cdf.png)
**分析**
- baidu-rpc:平均延时短,几乎没有被长尾影响。
- UB和thrift:平均延时短,受长尾影响小,平均延时高于baidu-rpc
- sofa-pbrpc:14%的普通请求被长尾严重干扰。
- hulu-pbrpc:15%的普通请求被长尾严重干扰。
- grpc : 已经完全失控,非常糟糕。
##
跨机多client→多server在固定QPS下的延时[CDF](http://wiki.baidu.com/display/RPC/vars#vars-统计和查看分位值)(越左越好,越直越好)
## 跨机多client→多server在固定QPS下的延时[CDF](vars.md#统计和查看分位值)(越左越好,越直越好)
本测试运行在[多机2](http://wiki.baidu.com/display/RPC/Benchmark#Benchmark-环境)上。20台每台运行4个client,多线程同步访问10台server。负载均衡算法为round-robin或RPC默认提供的。由于grpc访问多server较麻烦且有很大概率仍表现不佳,这个测试不包含grpc。
本测试运行在[多机2](#环境)上。20台每台运行4个client,多线程同步访问10台server。负载均衡算法为round-robin或RPC默认提供的。由于grpc访问多server较麻烦且有很大概率仍表现不佳,这个测试不包含grpc。
本测试中有1%的长尾请求耗时10毫秒,长尾请求的延时不计入结果,因为我们考察的是普通请求是否被及时处理了。
结果链接:<http://brpc.baidu.com:8789/benchmark/multi_server_latency_cdf.20160127.113554>
(X轴是延时(微秒),Y轴是小于X轴延时的请求比例)
[![img](http://wiki.baidu.com/download/attachments/48480483/image2016-1-27%2011%3A47%3A3.png?version=1&modificationDate=1453866423000&api=v2)](http://db-rpc-dev00.db01.baidu.com:8789/benchmark/multi_server_latency_cdf.20160127.113554)
### 分析
![img](../images/multi_server_latency_cdf.png)
**分析**
- baidu-rpc和UB:平均延时短,几乎没有被长尾影响。
- thrift: 平均延时显著高于baidu-rpc和UB。
- sofa-pbrpc:2.5%的普通请求被长尾严重干扰。
- hulu-pbrpc:22%的普通请求被长尾严重干扰。
##
跨机多client→多server→多server在固定QPS下的延时[CDF](http://wiki.baidu.com/display/RPC/vars#vars-统计和查看分位值)(越左越好,越直越好)
## 跨机多client→多server→多server在固定QPS下的延时[CDF](vars.md#统计和查看分位值)(越左越好,越直越好)
本测试运行在[多机2](http://wiki.baidu.com/display/RPC/Benchmark#Benchmark-环境)上。14台每台运行4个client,多线程同步访问8台server,这些server还会同步访问另外8台server。负载均衡算法为round-robin或RPC默认提供的。由于grpc访问多server较麻烦且有很大概率仍表现不佳,这个测试不包含grpc。
本测试运行在[多机2](#环境)上。14台每台运行4个client,多线程同步访问8台server,这些server还会同步访问另外8台server。负载均衡算法为round-robin或RPC默认提供的。由于grpc访问多server较麻烦且有很大概率仍表现不佳,这个测试不包含grpc。
本测试中有1%的长尾请求耗时10毫秒,长尾请求的延时不计入结果,因为我们考察的是普通请求是否被及时处理了。
结果链接:<http://brpc.baidu.com:8789/benchmark/twolevel_server_latency_cdf.20160127.082357>
(X轴是延时(微秒),Y轴是小于X轴延时的请求比例)
![img](http://wiki.baidu.com/download/attachments/48480483/image2016-1-27%208%3A44%3A30.png?version=1&modificationDate=1453855481000&api=v2)
### 分析
![img](../images/twolevel_server_latency_cdf.png)
**分析**
- baidu-rpc:平均延时短,几乎没有被长尾影响。
- UB:平均延时短,长尾区域略差于baidu-rpc。
- thrift: 平均延时显著高于baidu-rpc和UB。
......@@ -238,7 +202,7 @@ grpc,hulu-pbrpc,sofa-pbrpc: 几乎重合,256个线程时相比1个线程
# 结论
baidu-rpc:在吞吐,平均延时,长尾处理上都拔得头筹
baidu-rpc:在吞吐,平均延时,长尾处理上都表现优秀
UB:平均延时和长尾处理的表现都不错,吞吐的扩展性较差,提高线程数和client数几乎不能提升吞吐。
......@@ -248,6 +212,5 @@ sofa-pbrpc:处理小包的吞吐尚可,大包的吞吐显著低于其他RPC
hulu-pbrpc:单机表现和sofa-pbrpc类似,但多机的延时表现极差。
grpc:几乎在所有参与的测试中垫底,可能它的定位是给google cloud
platform的用户提供一个多语言,对网络友好的实现,性能还不是要务。尽管它对自己的第一个形容词也是![img](http://wiki.baidu.com/download/thumbnails/48480483/image2016-1-30%2019%3A5%3A33.png?version=1&modificationDate=1454151950000&api=v2)
grpc:几乎在所有参与的测试中垫底,可能它的定位是给google cloud platform的用户提供一个多语言,对网络友好的实现,性能还不是要务。
......@@ -2,4 +2,4 @@
使用方法:
首先你得[下载和编译](getting_started.md)了baidu-rpc源码,然后去example/http_c++目录,comake2 -P && make -sj4,成功后应该能看到benchmark_http。
首先你得[下载和编译](getting_started.md)了baidu-rpc源码,然后去example/http_c++目录编译,成功后应该能看到benchmark_http。
......@@ -16,9 +16,9 @@ bthread([代码](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/ma
##### Q:bthread是协程(coroutine)吗?
不是。我们常说的协程是N:1线程库,即所有的协程都运行于一个系统线程中,计算能力和各种eventloop等价。由于不跨线程,协程之间的切换不需要系统调用,可以非常快(100ns-200ns),受cache miss的影响也小。但相应的代价是协程无法高效地利用多核,代码必须非阻塞,否则所有的协程都被卡住,对开发者要求苛刻。协程的这个特点使其特别适合写运行时间确定的IO服务器,典型如http server,在一些精心调试的场景中,可以达到非常高的吞吐。但百度内大部分在线服务的运行时间可不确定,一个缓慢的函数很容易卡住所有的协程。在这点上eventloop是类似的,一个回调卡住整个loop就卡住了,比如ub**a**server(注意那个a,不是ubserver)是公司内对异步框架的尝试,由多个并行的eventloop组成,真实表现糟糕:回调里打日志慢一些,访问下redis,计算重一点,等待中的其他请求就会大量超时。所以这个框架从未流行起来。
不是。我们常说的协程是N:1线程库,即所有的协程都运行于一个系统线程中,计算能力和各种eventloop等价。由于不跨线程,协程之间的切换不需要系统调用,可以非常快(100ns-200ns),受cache同步的影响也小。但相应的代价是协程无法高效地利用多核,代码必须非阻塞,否则所有的协程都被卡住,对开发者要求苛刻。协程的这个特点使其适合写运行时间确定的IO服务器,典型如http server,在一些精心调试的场景中,可以达到非常高的吞吐。但百度内大部分在线服务的运行时间并不确定,一个缓慢的函数很容易卡住所有的协程。在这点上eventloop是类似的,一个回调卡住整个loop就卡住了,比如ub**a**server(注意那个a,不是ubserver)是公司内对异步框架的尝试,由多个并行的eventloop组成,真实表现糟糕:回调里打日志慢一些,访问下redis,计算重一点,等待中的其他请求就会大量超时。所以这个框架从未流行起来。
bthread是一个M:N线程库,一个bthread被卡住不会影响其他bthread。关键技术两点:work stealing调度和butex,前者让bthread更快地被调度到更多的核心上,后者让bthread和pthread可以相互等待和唤醒。这两点协程都不需要。更多线程的知识查看[这里](http://wiki.baidu.com/display/RPC/Threading+Overview)
bthread是一个M:N线程库,一个bthread被卡住不会影响其他bthread。关键技术两点:work stealing调度和butex,前者让bthread更快地被调度到更多的核心上,后者让bthread和pthread可以相互等待和唤醒。这两点协程都不需要。更多线程的知识查看[这里](threading_overview.md)
##### Q: 我应该在程序中多使用bthread吗?
......@@ -26,7 +26,7 @@ bthread是一个M:N线程库,一个bthread被卡住不会影响其他bthread
##### Q:bthread和pthread worker如何对应?
pthread worker在任何时间只会运行一个bthread,当前bthread挂起时,pthread worker先尝试从本地(runqueue)弹出一个待运行的bthread,若没有,则随机偷另一个worker的待运行bthread,仍然没有就睡眠在一个futex上,该futex会在任一worker有新的待运行bthread时被唤醒。
pthread worker在任何时间只会运行一个bthread,当前bthread挂起时,pthread worker先尝试从本地(runqueue)弹出一个待运行的bthread,若没有,则随机偷另一个worker的待运行bthread,仍然没有才睡眠并会在有新的待运行bthread时被唤醒。
##### Q:bthread中能调用阻塞的pthread或系统函数吗?
......@@ -43,9 +43,7 @@ pthread worker在任何时间只会运行一个bthread,当前bthread挂起时
##### Q:若有大量的bthread调用了阻塞的pthread或系统函数,会影响RPC运行么?
会。比如有8个pthread worker,当有8个bthread都调用了系统usleep()后,处理网络收发的RPC代码就暂时无法运行了。只要阻塞时间不太长, 这一般没什么影响, 毕竟worker都用完了, 除了排队也没有什么好方法.
在baidu-rpc中用户可以选择调大worker数来缓解问题,
在server端可设置[ServerOptions.num_threads](server.md#id-创建和设置Server-worker线程数)[-bthread_concurrency](http://brpc.baidu.com:8765/flags/bthread_concurrency),
在client端可设置[-bthread_concurrency](http://brpc.baidu.com:8765/flags/bthread_concurrency).
在baidu-rpc中用户可以选择调大worker数来缓解问题, 在server端可设置[ServerOptions.num_threads](server.md#id-创建和设置Server-worker线程数)[-bthread_concurrency](http://brpc.baidu.com:8765/flags/bthread_concurrency), 在client端可设置[-bthread_concurrency](http://brpc.baidu.com:8765/flags/bthread_concurrency).
那有没有完全规避的方法呢?
......@@ -57,12 +55,11 @@ pthread worker在任何时间只会运行一个bthread,当前bthread挂起时
另一个问题是增加一层处理环节(io线程)并不能缓解拥塞, 如果worker线程全部卡住, 程序仍然会卡住,
只是卡的地方从socket缓冲转移到了io线程和worker线程之间的消息队列. 换句话说, 在worker卡住时,
还在运行的io线程做的可能是无用功. 事实上, 这正是上面提到的"没什么影响"真正的含义.
- 一个实际的解决方法是[限制最大并发](server.md#id-创建和设置Server-限制最大并发), 只要同时被处理的请求数低于worker数, 自然可以规避掉"所有worker被阻塞"的情况.
- 另一个解决方法当被阻塞的worker超过阈值时(比如8个中的6个), 就不在原地调用用户代码了, 而是扔到一个独立的线程池中运行. 这样即使用户代码全部阻塞, 也总能保留几个worker处理rpc的收发. 不过目前bthread模式并没有这个机制, 但类似的机制在[打开pthread模式](server.md#id-创建和设置Server-pthread模式)时已经被实现了. 那像上面提到的, 这个机制是不是在用户代码都阻塞时也在做"无用功"呢? 可能是的. 但这个机制更多是为了规避在一些极端情况下的死锁, 比如所有的用户代码都lock在一个pthread mutex上, 并且这个mutex需要在某个RPC回调中unlock, 如果所有的worker都被阻塞, 那么就没有线程来处理RPC回调了, 整个程序就死锁了. 虽然绝大部分的RPC实现都有这个潜在问题, 但实际出现频率似乎很低, 只要养成不在锁内做RPC的好习惯, 这是完全可以规避的.
- 一个实际的解决方法是[限制最大并发](server.md#限制最大并发), 只要同时被处理的请求数低于worker数, 自然可以规避掉"所有worker被阻塞"的情况.
- 另一个解决方法当被阻塞的worker超过阈值时(比如8个中的6个), 就不在原地调用用户代码了, 而是扔到一个独立的线程池中运行. 这样即使用户代码全部阻塞, 也总能保留几个worker处理rpc的收发. 不过目前bthread模式并没有这个机制, 但类似的机制在[打开pthread模式](server.md#pthread模式)时已经被实现了. 那像上面提到的, 这个机制是不是在用户代码都阻塞时也在做"无用功"呢? 可能是的. 但这个机制更多是为了规避在一些极端情况下的死锁, 比如所有的用户代码都lock在一个pthread mutex上, 并且这个mutex需要在某个RPC回调中unlock, 如果所有的worker都被阻塞, 那么就没有线程来处理RPC回调了, 整个程序就死锁了. 虽然绝大部分的RPC实现都有这个潜在问题, 但实际出现频率似乎很低, 只要养成不在锁内做RPC的好习惯, 这是完全可以规避的.
##### Q:bthread会有[Channel](https://gobyexample.com/channels)吗?
不会。channel代表的是两点间的关系,而很多现实问题是多点的,这个时候使用channel最自然的解决方案就是:有一个角色负责操作某件事情或某个资源,其他线程都通过channel向这个角色发号施令。如果我们在程序中设置N个角色,让它们各司其职,那么程序就能分类有序地运转下去。所以使用channel的潜台词就是把程序划分为不同的角色。channel固然直观,但是有代价:额外的上下文切换。做成任何事情都得等到被调用处被调度,处理,回复,调用处才能继续。这个再怎么优化,再怎么尊重cache
locality,也是有明显开销的。另外一个现实是:用channel的代码也不好写。由于业务一致性的限制,一些资源往往被绑定在一起,所以一个角色很可能身兼数职,但它做一件事情时便无法做另一件事情,而事情又有优先级。各种打断、跳出、继续形成的最终代码异常复杂。
不会。channel代表的是两点间的关系,而很多现实问题是多点的,这个时候使用channel最自然的解决方案就是:有一个角色负责操作某件事情或某个资源,其他线程都通过channel向这个角色发号施令。如果我们在程序中设置N个角色,让它们各司其职,那么程序就能分类有序地运转下去。所以使用channel的潜台词就是把程序划分为不同的角色。channel固然直观,但是有代价:额外的上下文切换。做成任何事情都得等到被调用处被调度,处理,回复,调用处才能继续。这个再怎么优化,再怎么尊重cache locality,也是有明显开销的。另外一个现实是:用channel的代码也不好写。由于业务一致性的限制,一些资源往往被绑定在一起,所以一个角色很可能身兼数职,但它做一件事情时便无法做另一件事情,而事情又有优先级。各种打断、跳出、继续形成的最终代码异常复杂。
我们需要的往往是buffered channel,扮演的是队列和有序执行的作用,bthread在r31345之后提供[ExecutionQueue](execution_queue.md),可以完成这个目的。
......@@ -10,7 +10,7 @@ bthread_id是一个特殊的同步结构,它可以互斥RPC过程中的不同
上文提到的bug在其他rpc框架中广泛存在,下面我们来看下baidu-rpc是如何通过bthread_id解决这些问题的。
bthread_id包括两部分,一个是用户可见的64位id,另一个是对应的不可见的bthread::Id结构体。用户接口都是操作id的。从id映射到结构体的方式和baidu-rpc中的[其他结构](http://wiki.baidu.com/display/RPC/Memory+Management)类似:32位是内存池的位移,32位是version。前者O(1)时间定位,后者防止ABA问题。
bthread_id包括两部分,一个是用户可见的64位id,另一个是对应的不可见的bthread::Id结构体。用户接口都是操作id的。从id映射到结构体的方式和baidu-rpc中的[其他结构](memory_management.md)类似:32位是内存池的位移,32位是version。前者O(1)时间定位,后者防止ABA问题。
bthread_id的接口不太简洁,有不少API:
......
......@@ -4,12 +4,11 @@ baidu-rpc提供了[异步接口](client.md#异步访问),所以一个常见的
# 同步或异步
异步即用回调代替阻塞,有阻塞的地方就有回调。虽然在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性能不符合预期。如果你说,”我想把现在的同步代码改造为大量的回调,除了我其他人都看不太懂,并且性能可能更差了”,我猜大部分人不会同意。别被那些鼓吹异步的人迷惑了,他们写的是从头到尾从下到上全异步且不考虑多线程的代码,和你要写的完全是两码事。
异步即用回调代替阻塞,有阻塞的地方就有回调。虽然在javascript这种语言中回调工作的很好,接受度也非常高,但只要用过,就会发现这和我们需要的回调是两码事,这个区别不是[lambda](https://en.wikipedia.org/wiki/Anonymous_function),也不是[future](https://en.wikipedia.org/wiki/Futures_and_promises),而是javascript是单线程的。javascript的回调放到多线程下可能没有一个能跑过,竞争太多,单线程的同步方法和多线程的同步方法是完全不同的。那是不是服务能搞成类似的形式呢?多个线程,每个都是独立的eventloop。可以,ub**a**server就是(注意带a),但实际效果糟糕,因为阻塞改回调可不简单,当阻塞发生在循环,条件分支,深层子函数中时,改造特别困难,况且很多老代码、第三方代码根本不可能去改造。结果是代码中会出现不可避免的阻塞,导致那个线程中其他回调都被延迟,流量超时,server性能不符合预期。如果你说,”我想把现在的同步代码改造为大量的回调,除了我其他人都看不太懂,并且性能可能更差了”,我猜大部分人不会同意。别被那些鼓吹异步的人迷惑了,他们写的是从头到尾从下到上全异步且不考虑多线程的代码,和你要写的完全是两码事。
baidu-rpc中的异步和单线程的异步是完全不同的,异步回调会运行在与调用处不同的线程中,你会获得多核扩展性,但代价是你得意识到多线程问题。你可以在回调中阻塞,只要线程够用,对server整体的性能并不会有什么影响。不过异步代码还是很难写的,所以我们提供了[组合访问](combo_channel.md)来简化问题,通过组合不同的channel,你可以声明式地执行复杂的访问,而不用太关心其中的细节。
当然,延时不长,qps不高时,我们更建议使用同步接口,这也是我们创建bthread的动机:维持同步代码也能提升交互性能。
当然,延时不长,qps不高时,我们更建议使用同步接口,这也是创建bthread的动机:维持同步代码也能提升交互性能。
**判断使用同步或异步**:计算qps * latency(in seconds),如果和cpu核数是同一数量级,就用同步,否则用异步。
......@@ -17,8 +16,7 @@ baidu-rpc中的异步和单线程的异步是完全不同的,异步回调会
- qps = 2000,latency = 10ms,计算结果 = 2000 * 0.01s = 20。和常见的32核在同一个数量级,用同步。
- qps = 100, latency = 5s, 计算结果 = 100 * 5s = 500。和核数不在同一个数量级,用异步。
- qps = 500, latency = 100ms,计算结果 = 500 * 0.1s =
50。基本在同一个数量级,可用同步。如果未来延时继续增长,考虑异步。
- qps = 500, latency = 100ms,计算结果 = 500 * 0.1s = 50。基本在同一个数量级,可用同步。如果未来延时继续增长,考虑异步。
这个公式计算的是同时进行的平均请求数(你可以尝试证明一下),和线程数,cpu核数是可比的。当这个值远大于cpu核数时,说明大部分操作并不耗费cpu,而是让大量线程阻塞着,使用异步可以明显节省线程资源(栈占用的内存)。当这个值小于或和cpu核数差不多时,异步能节省的线程资源就很有限了,这时候简单易懂的同步代码更重要。
......@@ -34,7 +32,7 @@ baidu-rpc中的异步和单线程的异步是完全不同的,异步回调会
**如果仅仅是为了并发RPC,别用bthread。**
不过当你需要并行计算时,问题就不同了。使用bthread可以简单地构建树形的并行计算,充分利用多核资源。比如检索过程中有三个环节可以并行处理,你可以建立两个bthread运行两个环节,在原地运行剩下的环节,最后join那两个bthread。过程大致如下:
```
```c++
bool search() {
...
bthread th1, th2;
......
# 什么是内置服务?
内置服务以多种形式展现服务器内部状态,提高你开发和调试服务的效率。baidu-rpc通过HTTP协议提供内置服务,可通过浏览器或curl访问,服务器会根据User-Agent返回纯文本或html,你也可以添加?console=1要求返回纯文本。我们在自己的开发机上启动了[一个长期运行的例子](http://brpc.baidu.com:8765/),你可以点击后随便看看。服务端口只有在8000-8999内才能被笔记本访问到,对于范围之外的端口可以使用[rpc_view](http://wiki.baidu.com/display/RPC/rpc_view)或在命令行中使用curl <SERVER-URL>
内置服务以多种形式展现服务器内部状态,提高你开发和调试服务的效率。baidu-rpc通过HTTP协议提供内置服务,可通过浏览器或curl访问,服务器会根据User-Agent返回纯文本或html,你也可以添加?console=1要求返回纯文本。我们在自己的开发机上启动了[一个长期运行的例子](http://brpc.baidu.com:8765/),你可以点击后随便看看。服务端口只有在8000-8999内才能被笔记本访问到,对于范围之外的端口可以使用[rpc_view](rpc_view.md)或在命令行中使用curl <SERVER-URL>
从浏览器访问:
![img](http://wiki.baidu.com/download/attachments/71337189/image2015-9-27%2023%3A45%3A26.png?version=1&modificationDate=1443368728000&api=v2)
![img](../images/builtin_service_more.png)
从命令行访问:
![img](http://wiki.baidu.com/download/attachments/71337189/image2015-9-27%2023%3A46%3A48.png?version=1&modificationDate=1443368810000&api=v2)
![img](../images/builtin_service_from_console.png)
# 安全模式
出于安全考虑,直接对外服务需要关闭内置服务(包括经过nginx或其他http server转发流量的),具体方法请阅读[这里](server.md#id-创建和设置Server-安全模式)
出于安全考虑,直接对外服务需要关闭内置服务(包括经过nginx或其他http server转发流量的),具体方法请阅读[这里](server.md#安全模式)
# 主要服务
......@@ -26,7 +26,7 @@
[protobufs服务](http://brpc.baidu.com:8765/protobufs)可以查看程序中所有的protobuf结构体。
[vlog服务](http://brpc.baidu.com:8765/vlog)可以查看程序中当前可开启的[VLOG](http://wiki.baidu.com/display/RPC/streaming_log#streaming_log-VLOG)
[vlog服务](http://brpc.baidu.com:8765/vlog)可以查看程序中当前可开启的[VLOG](streaming_log.md#VLOG)
dir服务可以浏览服务器上的所有文件,这个服务比较敏感,默认关闭。
......
......@@ -10,11 +10,11 @@ cache bouncing使访问频繁修改的变量的开销陡增,甚至还会使访
下图是bvar和原子变量,静态UbMonitor,动态UbMonitor的性能对比。可以看到bvar的耗时基本和线程数无关,一直保持在极低的水平(~20纳秒)。而动态UbMonitor在24核时每次累加的耗时达7微秒,这意味着使用300次bvar的开销才抵得上使用一次动态UbMonitor变量。
![img](http://wiki.baidu.com/download/attachments/53020182/image2015-8-9%200%3A6%3A9.png?version=1&modificationDate=1439049969000&api=v2)
![img](../images/bvar_perf.png)
# 3.用noah监控bvar
![img](http://wiki.baidu.com/download/attachments/53020182/image2015-8-8%2018%3A30%3A48.png?version=1&modificationDate=1439029848000&api=v2)
![img](../images/bvar_flow.png)
- bvar 将被监控的项目定期打入文件:monitor/bvar.<app>.data。
- noah 自动收集文件并生成图像。
......@@ -32,8 +32,6 @@ cache bouncing使访问频繁修改的变量的开销陡增,甚至还会使访
## 1.定义bvar
在COMAKE中增加CONFIGS('public/bvar@ci-base')来依赖bvar。
```c++
#include <bvar/bvar.h>
......@@ -212,16 +210,16 @@ process_username : "gejun"
搜索监控节点:
![img](http://wiki.baidu.com/download/attachments/53020182/image2015-8-8%2019%3A11%3A30.png?version=1&modificationDate=1439032291000&api=v2)
![img](../images/bvar_noah1.png)
点击“文件”tab,勾选要查看的统计量,bvar已经统计了进程级的很多参数,大都以process开头。
![img](http://wiki.baidu.com/download/attachments/53020182/image2015-8-8%2019%3A13%3A8.png?version=1&modificationDate=1439032388000&api=v2)
![img](../images/bvar_noah2.png)
查看趋势图:
![img](http://wiki.baidu.com/download/attachments/53020182/image2015-8-8%2019%3A16%3A9.png?version=1&modificationDate=1439032569000&api=v2)
![img](../images/bvar_noah3.png)
......@@ -11,7 +11,7 @@ bvar分为多个具体的类,常用的有:
- bvar::LatencyRecorder : 专用于记录延时和qps的变量。输入延时,平均延时/最大延时/qps/总次数 都有了。
例子:
```
```c++
// 构造时不带名字,则无法被查询到。并不是所有的bvar都会显示在/vars
bvar::Adder<int> request_count1;
......@@ -56,7 +56,7 @@ About thread-safety:
Variable是所有bvar的基类,主要提供全局注册,列举,查询等功能。
用户以默认参数建立一个bvar时,这个bvar并未注册到任何全局结构中,在这种情况下,bvar纯粹是一个更快的计数器。我们称把一个bvar注册到全局表中的行为为”曝光“,可通过**expose**函数曝光:
```
```c++
// Expose this variable globally so that it's counted in following functions:
// list_exposed
// count_exposed
......@@ -71,7 +71,7 @@ int expose(const base::StringPiece& prefix, const base::StringPiece& name);
当相同名字的bvar已存在时,expose会打印FATAL日志并返回-1。如果选项**--bvar_abort_on_same_name**设为true (默认是false),程序会直接abort。
下面是一些曝光bvar的例子:
```
```c++
bvar::Adder<int> count1;
count1 << 10 << 20 << 30; // values add up to 60.
......@@ -89,7 +89,7 @@ bvar::Status<std::string> status1("count2", "hello"); // the name conflicts. if
```
为避免重名,bvar的名字应加上前缀,建议为<namespace>_<module>_<name>。为了方便使用,我们提供了**expose_as**函数,接收一个前缀。
```
```c++
// Expose this variable with a prefix.
// Example:
// namespace foo {
......@@ -110,7 +110,7 @@ int expose_as(const base::StringPiece& prefix, const base::StringPiece& name);
# Export all variables
我们提供dump_exposed函数导出进程中的所有已曝光的bvar:
```
```c++
// Implement this class to write variables into different places.
// If dump() returns false, Variable::dump_exposed() stops and returns -1.
class Dumper {
......@@ -147,15 +147,15 @@ class Variable {
```
最常见的导出需求是通过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)
![img](../images/bvar_dump_flags.png)
用户可在程序启动前加上对应的gflags,在baidu-rpc中也可通过[/flags](http://wiki.baidu.com/display/RPC/flags)服务在启动后动态修改某个gflag。
用户可在程序启动前加上对应的gflags,在baidu-rpc中也可通过[/flags](flags.md)服务在启动后动态修改某个gflag。
当bvar_dump_file不为空时,程序会启动一个后台导出线程以bvar_dump_interval指定的间隔更新bvar_dump_file,其中包含了被bvar_dump_include匹配且不被bvar_dump_exclude匹配的所有bvar。
比如我们把所有的gflags修改为下图:
![img](http://wiki.baidu.com/download/attachments/133624370/image2015-8-9%2014%3A38%3A1.png?version=1&modificationDate=1439102281000&api=v2)
![img](../images/bvar_dump_flags_2.png)
导出文件为:
......@@ -172,7 +172,7 @@ rpc_server_8002_uptime_ms : 14740954
像”`iobuf_block_count : 8`”被bvar_dump_include过滤了,“`rpc_server_8002_error : 0`”则被bvar_dump_exclude排除了。
如果你的程序没有使用baidu-rpc,仍需要动态修改gflag(一般不需要),可以调用google::SetCommandLineOption(),如下所示:
```
```c++
#include <gflags/gflags.h>
...
if (google::SetCommandLineOption("bvar_dump_include", "*service*").empty()) {
......@@ -187,7 +187,7 @@ LOG(INFO) << "Successfully set bvar_dump_include to *service*";
# bvar::Reducer
Reducer用二元运算符把多个值合并为一个值,运算符需满足结合律,交换律,没有副作用。只有满足这三点,我们才能确保合并的结果不受线程私有数据如何分布的影响。像减法就不满足结合律和交换律,它无法作为此处的运算符。
```
```c++
// Reduce multiple values into one with `Op': e1 Op e2 Op e3 ...
// `Op' shall satisfy:
// - associative: a Op (b Op c) == (a Op b) Op c
......@@ -204,7 +204,7 @@ reducer << e1 << e2 << e3的作用等价于reducer = e1 op e2 op e3。
## bvar::Adder
顾名思义,用于累加,Op为+。
```
```c++
bvar::Adder<int> value;
value<< 1 << 2 << 3 << -4;
CHECK_EQ(2, value.get_value());
......@@ -214,7 +214,7 @@ fp_value << 1.0 << 2.0 << 3.0 << -4.0;
CHECK_DOUBLE_EQ(2.0, fp_value.get_value());
```
Adder<>可用于非基本类型,对应的类型至少要重载`T operator+(T, T)`。一个已经存在的例子是std::string,下面的代码会把string拼接起来:
```
```c++
// This is just proof-of-concept, don't use it for production code because it makes a
// bunch of temporary strings which is not efficient, use std::ostringstream instead.
bvar::Adder<std::string> concater;
......@@ -225,7 +225,7 @@ CHECK_EQ("hello world", concater.get_value());
## bvar::Maxer
用于取最大值,运算符为std::max。
```
```c++
bvar::Maxer<int> value;
value<< 1 << 2 << 3 << -4;
CHECK_EQ(3, value.get_value());
......@@ -235,7 +235,7 @@ Since Maxer<> use std::numeric_limits<T>::min() as the identity, it cannot be ap
## bvar::Miner
用于取最小值,运算符为std::min。
```
```c++
bvar::Maxer<int> value;
value<< 1 << 2 << 3 << -4;
CHECK_EQ(-4, value.get_value());
......@@ -245,7 +245,7 @@ Since Miner<> use std::numeric_limits<T>::max() as the identity, it cannot be ap
# bvar::IntRecorder
用于计算平均值。
```
```c++
// For calculating average of numbers.
// Example:
// IntRecorder latency;
......@@ -259,7 +259,7 @@ class IntRecorder : public Variable;
专用于计算latency和qps的计数器。只需填入latency数据,就能获得latency / max_latency / qps / count。统计窗口是最后一个参数,不填为bvar_dump_interval(这里没填)。
注意:LatencyRecorder没有继承Variable,而是多个bvar的组合。
```
```c++
LatencyRecorder write_latency("table2_my_table_write"); // produces 4 variables:
// table2_my_table_write_latency
// table2_my_table_write_max_latency
......@@ -272,7 +272,7 @@ write_latency << the_latency_of_write;
# bvar::Window
获得之前一段时间内的统计值。Window不能独立存在,必须依赖于一个已有的计数器。Window会自动更新,不用给它发送数据。出于性能考虑,Window的数据来自于每秒一次对原计数器的采样,在最差情况下,Window的返回值有1秒的延时。
```
```c++
// Get data within a time window.
// The time unit is 1 second fixed.
// Window relies on other bvar which should be constructed before this window and destructs after this window.
......@@ -286,7 +286,7 @@ class Window : public Variable;
# bvar::PerSecond
获得之前一段时间内平均每秒的统计值。它和Window基本相同,除了返回值会除以时间窗口之外。
```
```c++
bvar::Adder<int> sum;
// sum_per_second.get_value()是sum在之前60秒内*平均每秒*的累加值,省略最后一个时间窗口的话默认为bvar_dump_interval。
......@@ -295,7 +295,7 @@ bvar::PerSecond<bvar::Adder<int> > sum_per_second(&sum, 60);
**PerSecond并不总是有意义**
上面的代码中没有Maxer,因为一段时间内的最大值除以时间窗口是没有意义的。
```
```c++
bvar::Maxer<int> max_value;
// 错误!最大值除以时间是没有意义的
......@@ -314,7 +314,7 @@ Window的优点是精确值,适合一些比较小的量,比如“上一分
# bvar::Status
记录和显示一个值,拥有额外的set_value函数。
```
```c++
// Display a rarely or periodically updated value.
// Usage:
......@@ -334,7 +334,7 @@ class Status : public Variable;
# bvar::PassiveStatus
按需显示值。在一些场合中,我们无法set_value或不知道以何种频率set_value,更适合的方式也许是当需要显示时才打印。用户传入打印回调函数实现这个目的。
```
```c++
// Display a updated-by-need value. This is done by passing in an user callback
// which is called to produce the value.
// Example:
......@@ -352,7 +352,7 @@ template <typename Tp>
class PassiveStatus : public Variable;
```
虽然很简单,但PassiveStatus是最有用的bvar之一,因为很多统计量已经存在,我们不需要再次存储它们,而只要按需获取。比如下面的代码声明了一个在linux下显示进程用户名的bvar:
```
```c++
static void get_username(std::ostream& os, void*) {
char buf[32];
......@@ -369,7 +369,7 @@ PassiveStatus<std::string> g_username("process_username", get_username, NULL);
# bvar::GFlag
Expose important gflags as bvar so that they're monitored (in noah).
```
```c++
DEFINE_int32(my_flag_that_matters, 8, "...");
// Expose the gflag as *same-named* bvar so that it's monitored (in noah).
......
......@@ -47,4 +47,4 @@ QA测试结论:通过
![img](../images/apicontrol_compare_4.png)
**总线程数(个)**(红色为升级前,蓝色为升级后)
![img](../images/apicontrol_compare_5.png)
\ No newline at end of file
![img](../images/apicontrol_compare_5.png)
......@@ -20,4 +20,4 @@ baidu-dsp是联盟基于Ad Exchange和RTB模式的需求方平台,服务大客
| bs成功率 | ![img](../images/baidu_dsp_compare_3.png) | ![img](../images/baidu_dsp_compare_4.png) |
| cpu_idle | ![img](../images/baidu_dsp_compare_5.png) | ![img](../images/baidu_dsp_compare_6.png) |
| ctr成功率 | ![img](../images/baidu_dsp_compare_7.png) | ![img](../images/baidu_dsp_compare_8.png) |
| cvr成功率 | ![img](../images/baidu_dsp_compare_9.png) | ![img](../images/baidu_dsp_compare_10.png) |
\ No newline at end of file
| cvr成功率 | ![img](../images/baidu_dsp_compare_9.png) | ![img](../images/baidu_dsp_compare_10.png) |
......@@ -84,4 +84,4 @@ Total: 8664 samples
34 0.4% 88.3% 34 0.4% madvise
34 0.4% 88.7% 38 0.4% tcmalloc::CentralFreeList::FetchFromOneSpans
32 0.4% 89.0% 32 0.4% std::__insertion_sort
```
\ No newline at end of file
```
......@@ -44,4 +44,4 @@ qps固定为6500,观察延时。
在线上缩容 不断增大压力过程中:
* ubrpc cpu idle分布在35%~60%,在55%最集中,最低30%;
* baidu-rpc cpu idle分布在60%~85%,在75%最集中,最低50%; baidu-rpc比ubrpc对cpu的消耗低。
\ No newline at end of file
* baidu-rpc cpu idle分布在60%~85%,在75%最集中,最低50%; baidu-rpc比ubrpc对cpu的消耗低。
......@@ -355,7 +355,7 @@ brpc::StartCancel(CallId)可取消任意RPC,CallId必须**在发起RPC前**通
remote_side()方法可知道request被送向了哪个server,返回值类型是[base::EndPoint](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/src/base/endpoint.h),包含一个ip4地址和端口。在RPC结束前调用这个方法都是没有意义的。
打印方式:
```
```c++
LOG(INFO) << "remote_side=" << cntl->remote_side();
printf("remote_side=%s\n", base::endpoint2str(cntl->remote_side()).c_str());
```
......@@ -457,7 +457,7 @@ r32009后用户可以通过继承[brpc::RetryPolicy](http://icode.baidu.com/repo
比如baidu-rpc默认不重试HTTP相关的错误,而你的程序中希望在碰到HTTP_STATUS_FORBIDDEN (403)时重试,可以这么做:
```c++
#include brpc/retry_policy.h>
#include <brpc/retry_policy.h>
class MyRetryPolicy : public brpc::RetryPolicy {
public:
......@@ -592,7 +592,8 @@ brpc::policy::GianoAuthenticator auth(&generator, NULL);
brpc::ChannelOptions option;
option.auth = &auth;
```
首先通过调用Giano API生成验证器baas::CredentialGenerator,具体可参看[Giano快速上手手册.pdf](http://wiki.baidu.com/download/attachments/37774685/Giano%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B%E6%89%8B%E5%86%8C.pdf?version=1&modificationDate=1421990746000&api=v2)。然后按照如上代码一步步将其设置到brpc::ChannelOptions里去。
首先通过调用Giano API生成验证器baas::CredentialGenerator,具体可参看[Giano快速上手手册.pdf](http://wiki.baidu.com/download/attachments/37774685/Giano%E5%BF%A
B%E9%80%9F%E4%B8%8A%E6%89%8B%E6%89%8B%E5%86%8C.pdf?version=1&modificationDate=1421990746000&api=v2)。然后按照如上代码一步步将其设置到brpc::ChannelOptions里去。
当client设置认证后,任何一个新连接建立后都必须首先发送一段验证信息(通过Giano认证器生成),才能发送后续请求。认证成功后,该连接上的后续请求不会再带有验证消息。
......@@ -702,7 +703,7 @@ FATAL 04-07 20:00:03 7778 public/baidu-rpc/src/brpc/channel.cpp:123] Invalid add
### Q: 两个产品线都使用protobuf,为什么不能互相访问
协议 !=protobuf。protobuf负责打包,协议负责定字段。打包格式相同不意味着字段可以互通。协议中可能会包含多个protobuf包,以及额外的长度、校验码、magic number等等。协议的互通是通过在RPC框架内转化为统一的编程接口完成的,而不是在protobuf层面。从广义上来说,protobuf也可以作为打包框架使用,生成其他序列化格式的包,像[idl<=>protobuf](http://wiki.baidu.com/pages/viewpage.action?pageId=144820547)就是通过protobuf生成了解析idl的代码。
协议 !=protobuf。protobuf负责打包,协议负责定字段。打包格式相同不意味着字段可以互通。协议中可能会包含多个protobuf包,以及额外的长度、校验码、magic number等等。协议的互通是通过在RPC框架内转化为统一的编程接口完成的,而不是在protobuf层面。从广义上来说,protobuf也可以作为打包框架使用,生成其他序列化格式的包,像[idl<=>protobuf](idl_protobuf.md)就是通过protobuf生成了解析idl的代码。
### Q: 为什么C++ client/server 能够互相通信, 和其他语言的client/server 通信会报序列化失败的错误
......
......@@ -15,7 +15,7 @@ ParallelChannel (“pchan”)同时访问其包含的sub channel,并合并它
- 可以取消。
- 支持超时。
示例代码见[example/parallel_echo_c++](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/example/parallel_echo_c++/)
示例代码见[example/parallel_echo_c++](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/tree/example/parallel_echo_c++/)
任何brpc::ChannelBase的子类都可以加入ParallelChannel,包括ParallelChannel和其他组合Channel。用户可以设置ParallelChannelOptions.fail_limit来控制访问的最大失败次数(r31803前是ParallelChannel::set_fail_limit),当失败的访问达到这个数目时,RPC call会立刻结束而不等待超时。
......@@ -23,7 +23,7 @@ ParallelChannel (“pchan”)同时访问其包含的sub channel,并合并它
ParallelChannel的内部结构大致如下:
![img](http://wiki.baidu.com/download/attachments/71337222/image2015-7-6%2016%3A1%3A59.png?version=1&modificationDate=1436169719000&api=v2)
![img](../images/pchan.png)
## 插入sub channel
......@@ -155,7 +155,7 @@ const Controller* sub(int index) const;
- 可以取消。
- 支持超时。
示例代码见[example/selective_echo_c++](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/example/selective_echo_c++/)
示例代码见[example/selective_echo_c++](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/tree/example/selective_echo_c++/)
任何brpc::ChannelBase的子类都可加入SelectiveChannel,包括SelectiveChannel和其他组合Channel。
......@@ -168,7 +168,7 @@ SelectiveChannel的重试独立于其中的sub channel,当SelectiveChannel访
SelectiveChannel的初始化和普通Channel基本一样,但Init不需要指定名字服务,因为SelectiveChannel面向sub channel并通过AddChannel动态添加,而普通Channel面向的server才记录在名字服务中。
```c++
#include brpc/selective_channel.h>
#include <brpc/selective_channel.h>
...
brpc::SelectiveChannel schan;
brpc::ChannelOptions schan_options;
......@@ -240,9 +240,9 @@ stub.FooMethod(&cntl, &request, &response, NULL);
# PartitionChannel
[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++/)
[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/tree/example/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++/)
ParititonChannel只能处理一种分库方法,当用户需要多种分库方法共存,或从一个分库方法平滑地切换为另一种分库方法时,可以使用DynamicPartitionChannel,它会根据不同的分库方式动态地建立对应的sub PartitionChannel,并根据容量把请求分配给不同的分库。示例代码见[example/dynamic_partition_echo_c++](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/tree/example/dynamic_partition_echo_c++/)
如果分库在不同的名字服务内,那么用户得自行用ParallelChannel组装,即每个sub channel对应一个分库(使用不同的名字服务)。ParellelChannel的使用方法请见上一节。
......@@ -251,7 +251,7 @@ ParititonChannel只能处理一种分库方法,当用户需要多种分库方
首先定制PartitionParser。这个例子中tag的形式是N/M,N代表分库的index,M是分库的个数。比如0/3代表一共3个分库,这是第一个。
```c++
#include brpc/partition_channel.h>
#include <brpc/partition_channel.h>
...
class MyPartitionParser : public brpc::PartitionParser {
public:
......@@ -281,7 +281,7 @@ public:
然后初始化PartitionChannel。
```c++
#include brpc/partition_channel.h>
#include <brpc/partition_channel.h>
...
brpc::PartitionChannel channel;
......
......@@ -18,8 +18,6 @@ channel_socket_count: 1
channel_short_socket_count: 0
` `
上述信息分为三段:
- 第一段是server接受(accept)的连接。
......@@ -42,13 +40,13 @@ channel_short_socket_count: 0
- Out/m: 上一分钟写出的消息数
- SocketId :内部id,用于debug,用户不用关心。
典型截图分别如下所示:
单连接:![img](http://wiki.baidu.com/download/attachments/165876298/image2016-7-15%2016%3A7%3A41.png?version=1&modificationDate=1468570062000&api=v2)
单连接:![img](../images/single_conn.png)
连接池:![img](http://wiki.baidu.com/download/attachments/165876298/image2016-7-15%2016%3A8%3A40.png?version=1&modificationDate=1468570121000&api=v2)
连接池:![img](../images/pooled_conn.png)
短连接:![img](http://wiki.baidu.com/download/attachments/165876298/image2016-7-15%2016%3A9%3A1.png?version=1&modificationDate=1468570141000&api=v2)
短连接:![img](../images/short_conn.png)
# 概述
一些场景希望同样的请求尽量落到一台机器上,比如访问缓存集群时,我们往往希望同一种请求能落到同一个后端上,以充分利用其上已有的缓存,不同的机器承载不同的稳定working set。而不是随机地散落到所有机器上,那样的话会迫使所有机器缓存所有的内容,最终由于存不下形成颠簸而表现糟糕。 我们都知道hash能满足这个要求,比如当有n台服务器时,输入x总是会发送到第hash(x) % n台服务器上。但当服务器变为m台时,hash(x) % n和hash(x) % m很可能都不相等,这会使得几乎所有请求的发送目的地都发生变化,如果目的地是缓存服务,所有缓存将失效,继而对原本被缓存遮挡的数据库或计算服务造成请求风暴,触发雪崩。一致性哈希是一种特殊的哈希算法,在增加服务器时,发向每个老节点的请求中只会有一部分转向新节点,从而实现平滑的迁移。[这篇论文](http://wiki.baidu.com/download/attachments/105311464/ConsistenHashingandRandomTreesDistributedCachingprotocolsforrelievingHotSpotsontheworldwideweb.pdf?version=1&modificationDate=1437363370000&api=v2)中提出了一致性hash的概念。
一些场景希望同样的请求尽量落到一台机器上,比如访问缓存集群时,我们往往希望同一种请求能落到同一个后端上,以充分利用其上已有的缓存,不同的机器承载不同的稳定working set。而不是随机地散落到所有机器上,那样的话会迫使所有机器缓存所有的内容,最终由于存不下形成颠簸而表现糟糕。 我们都知道hash能满足这个要求,比如当有n台服务器时,输入x总是会发送到第hash(x) % n台服务器上。但当服务器变为m台时,hash(x) % n和hash(x) % m很可能都不相等,这会使得几乎所有请求的发送目的地都发生变化,如果目的地是缓存服务,所有缓存将失效,继而对原本被缓存遮挡的数据库或计算服务造成请求风暴,触发雪崩。一致性哈希是一种特殊的哈希算法,在增加服务器时,发向每个老节点的请求中只会有一部分转向新节点,从而实现平滑的迁移。[这篇论文](http://blog.phpdr.net/wp-content/uploads/2012/08/Consistent-Hashing-and-Random-Trees.pdf)中提出了一致性hash的概念。
一致性hash满足以下四个性质:
......@@ -15,7 +15,7 @@
所有server的32位hash值在32位整数值域上构成一个环(Hash Ring),环上的每个区间和一个server唯一对应,如果一个key落在某个区间内, 它就被分流到对应的server上。
![img](http://wiki.baidu.com/download/attachments/105311464/image2015-7-31%2017%3A35%3A13.png?version=1&modificationDate=1438335312000&api=v2)
![img](../images/chash.png)
当删除一个server的, 它对应的区间会归属于相邻的server,所有的请求都会跑过去。当增加一个server时,它会分割某个server的区间并承载落在这个区间上的所有请求。单纯使用Hash Ring很难满足我们上节提到的属性,主要两个问题:
......@@ -24,7 +24,7 @@
为了解决这个问题,我们为每个server计算m个hash值,从而把32位整数值域划分为n*m个区间,当key落到某个区间时,分流到对应的server上。那些额外的hash值使得区间划分更加均匀,被称为Virtual Node。当删除一个server时,它对应的m个区间会分别合入相邻的区间中,那个server上的请求会较为平均地转移到其他server上。当增加server时,它会分割m个现有区间,从对应server上分别转移一些请求过来。
由于节点故障和变化不常发生, 我们选择了修改复杂度为O(n)的有序数组来存储hash ring,每次分流使用二分查找来选择对应的机器, 由于存储是连续的,查找效率比基于平衡二叉树的实现高。 线程安全性请参照[Double Buffered Data](http://wiki.baidu.com/display/RPC/Locality-aware+load+balancing#Locality-awareloadbalancing-DoublyBufferedData)章节.
由于节点故障和变化不常发生, 我们选择了修改复杂度为O(n)的有序数组来存储hash ring,每次分流使用二分查找来选择对应的机器, 由于存储是连续的,查找效率比基于平衡二叉树的实现高。 线程安全性请参照[Double Buffered Data](lalb.md#doublybuffereddata)章节.
# 使用方式
......
......@@ -8,24 +8,24 @@ baidu-rpc可以分析花在等待锁上的时间及发生等待的函数。
当很多线程争抢同一把锁时,一些线程无法立刻获得锁,而必须睡眠直到某个线程退出临界区。这个争抢过程我们称之为**contention**。在多核机器上,当多个线程需要操作同一个资源却被一把锁挡住时,便无法充分发挥多个核心的并发能力。现代OS通过提供比锁更底层的同步原语,使得无竞争锁完全不需要系统调用,只是一两条wait-free,耗时10-20ns的原子操作,非常快。而锁一旦发生竞争,一些线程就要陷入睡眠,再次醒来触发了OS的调度代码,代价至少为3-5us。所以让锁尽量无竞争,让所有线程“一起飞”是需要高性能的server的永恒话题。
r31906后baidu-rpc支持contention profiler,可以分析在等待锁上花费了多少时间。等待过程中线程是睡着的不会占用CPU,所以contention profiler中的时间并不是cpu时间,也不会出现在[cpu profiler](http://wiki.baidu.com/display/RPC/cpu+profiler)中。cpu profiler可以抓到特别频繁的锁(以至于花费了很多cpu),但耗时真正巨大的临界区往往不是那么频繁,而无法被cpu profiler发现。**contention profiler和cpu profiler好似互补关系,前者分析等待时间(被动),后者分析忙碌时间。**还有一类由用户基于condition或sleep发起的主动等待时间,无需分析。
r31906后baidu-rpc支持contention profiler,可以分析在等待锁上花费了多少时间。等待过程中线程是睡着的不会占用CPU,所以contention profiler中的时间并不是cpu时间,也不会出现在[cpu profiler](cpu_profiler.md)中。cpu profiler可以抓到特别频繁的锁(以至于花费了很多cpu),但耗时真正巨大的临界区往往不是那么频繁,而无法被cpu profiler发现。**contention profiler和cpu profiler好似互补关系,前者分析等待时间(被动),后者分析忙碌时间。**还有一类由用户基于condition或sleep发起的主动等待时间,无需分析。
目前contention profiler支持pthread_mutex_t(非递归)和bthread_mutex_t,开启后每秒最多采集1000个竞争锁,这个数字由[参数bvar_collector_expected_per_second](http://brpc.baidu.com:8765/flags/bvar_collector_expected_per_second)控制(同时影响rpc_dump)。如果一秒内竞争锁的次数N超过了1000,那么每把锁会有1000/N的概率被采集。在我们的各类测试场景中(qps在10万-60万不等)没有观察到被采集程序的性能有明显变化
目前contention profiler支持pthread_mutex_t(非递归)和bthread_mutex_t,开启后每秒最多采集1000个竞争锁,这个数字由参数-bvar_collector_expected_per_second控制(同时影响rpc_dump)
我们通过实际例子来看下如何使用contention profiler,点击“contention”按钮(more左侧)后就会开启默认10秒的分析过程。下图是[libraft](http://wiki.baidu.com/display/RAFT/RAFT)中的一个示例程序的锁状况,这个程序是3个节点复制组的leader,qps在10-12万左右。左上角的**Total seconds: 2.449**是采集时间内(10秒)在锁上花费的所有等待时间。注意是“等待”,无竞争的锁不会被采集也不会出现在下图中。顺着箭头往下走能看到每份时间来自哪些函数。
| Name | Value | Description | Defined At |
| ---------------------------------- | ----- | ---------------------------------------- | ------------------ |
| bvar_collector_expected_per_second | 1000 | Expected number of samples to be collected per second | bvar/collector.cpp |
![img](http://wiki.baidu.com/download/attachments/165876314/screencapture-10-81-3-185-8101-hotspots-contention-1453209162877.png?version=1&modificationDate=1453212153000&api=v2)
如果一秒内竞争锁的次数N超过了1000,那么每把锁会有1000/N的概率被采集。在我们的各类测试场景中(qps在10万-60万不等)没有观察到被采集程序的性能有明显变化。
我们通过实际例子来看下如何使用contention profiler,点击“contention”按钮(more左侧)后就会开启默认10秒的分析过程。下图是libraft中的一个示例程序的锁状况,这个程序是3个节点复制组的leader,qps在10-12万左右。左上角的**Total seconds: 2.449**是采集时间内(10秒)在锁上花费的所有等待时间。注意是“等待”,无竞争的锁不会被采集也不会出现在下图中。顺着箭头往下走能看到每份时间来自哪些函数。
上图有点大,让我们放大一个局部看看。下图红框中的0.768是这个局部中最大的数字,它代表raft::LogManager::get_entry在等待涉及到bvar::detail::UniqueLockBase的函数上共等待了0.768秒(10秒内)。我们如果觉得这个时间不符合预期,就可以去排查代码。
![img](../images/raft_contention_1.png)
上图有点大,让我们放大一个局部看看。下图红框中的0.768是这个局部中最大的数字,它代表raft::LogManager::get_entry在等待涉及到bvar::detail::UniqueLockBase的函数上共等待了0.768秒(10秒内)。我们如果觉得这个时间不符合预期,就可以去排查代码。
![img](http://wiki.baidu.com/download/attachments/165876314/image2016-1-19%2022%3A13%3A26.png?version=1&modificationDate=1453212828000&api=v2)
![img](../images/raft_contention_2.png)
点击上方的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)
![img](../images/raft_contention_3.png)
......@@ -2,10 +2,10 @@ baidu-rpc可以分析程序中的热点函数。
# 开启方法
1. 在COMAKE中增加`CONFIGS('``thirdsrc/tcmalloc@2.5.0.5977``', Libraries('libtcmalloc_and_profiler.a'))`
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')`
1. 链接`libtcmalloc_and_profiler.a`
1. 这么写也开启了tcmalloc,不建议单独链接cpu profiler而不链接tcmalloc,可能越界访问导致[crash](https://github.com/gperftools/gperftools/blob/master/README#L226)**。**可能由于tcmalloc不及时归还内存,越界访问不会crash。
2. 如果tcmalloc使用frame pointer而不是libunwind回溯栈,请确保在CXXFLAGS或CFLAGS中加上`-fno-omit-frame-pointer`,否则函数间的调用关系会丢失,最后产生的图片中都是彼此独立的函数方框。
2. 定义宏BRPC_ENABLE_CPU_PROFILER。在COMAKE中加入`CXXFLAGS('-DBRPC_ENABLE_CPU_PROFILER')`
3. 如果只是baidu-rpc client或没有使用baidu-rpc,看[这里](dummy_server.md)
注意要关闭Server端的认证,否则可能会看到这个:
......@@ -36,9 +36,9 @@ WARNING: 12-26 10:01:25: * 0 [src/brpc/input_messenger.cpp:132][4294969345] Au
cpu profiler的原理是在定期被调用的SIGPROF handler中采样所在线程的栈,由于handler(在linux 2.6后)会被随机地摆放于活跃线程的栈上运行,cpu profiler在运行一段时间后能以很大的概率采集到所有活跃线程中的活跃函数,最后根据栈代表的函数调用关系汇总为调用图,并把地址转换成符号,这就是我们看到的结果图了。采集频率由环境变量CPUPROFILE_FREQUENCY控制,默认100,即每秒钟100次或每10ms一次。。在实践中cpu profiler对原程序的影响不明显。
![img](http://wiki.baidu.com/download/attachments/165876310/image2016-1-19%2023%3A28%3A21.png?version=1&modificationDate=1453217323000&api=v2)
![img](../images/echo_cpu_profiling.png)
你也可以使用[public/baidu-rpc/tools/pprof](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/tools/pprof)或gperftools中的pprof进行profiling。
你也可以使用[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秒。一次运行的例子如下:
......
......@@ -3,16 +3,16 @@
# 使用了baidu-rpc的client
只要在程序运行目录建立dummy_server.port文件,填入一个端口号(比如8888),程序会马上在这个端口上启动一个dummy server。在浏览器中访问它的内置服务,便可看到同进程内的所有bvar。
![img](http://wiki.baidu.com/download/attachments/71337189/image2015-12-25%2017%3A46%3A20.png?version=1&modificationDate=1451036781000&api=v2)
![img](http://wiki.baidu.com/download/attachments/71337189/image2015-12-25%2017%3A47%3A30.png?version=1&modificationDate=1451036850000&api=v2)
![img](http://wiki.baidu.com/download/attachments/71337189/image2015-12-25%2017%3A48%3A24.png?version=1&modificationDate=1451036904000&api=v2)
![img](../images/dummy_server_1.png) ![img](../images/dummy_server_2.png)
![img](../images/dummy_server_3.png)
# 没有使用baidu-rpc
你必须手动加入dummy server。你得先查看[Getting Started](getting_started.md)如何下载和编译baidu-rpc,然后在程序入口处加入如下代码片段:
```c++
#include brpc/server.h>
#include <brpc/server.h>
...
......@@ -32,7 +32,7 @@ int main() {
r31803之后加入dummy server更容易了,只要一行:
```c++
#include brpc/server.h>
#include <brpc/server.h>
...
......
......@@ -13,7 +13,7 @@ server端Controller的SetFailed()常由用户在服务回调中调用。当处
# baidu-rpc的错误码
baidu-rpc使用的所有ErrorCode都定义在[errno.proto](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/protocol/brpc/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/src/brpc/errno.proto)中,*SYS_*开头的来自linux系统,与/usr/include/errno.h中定义的精确一致,定义在proto中是为了跨语言。其余的是baidu-rpc自有的。
[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,你可以直接使用。
......@@ -43,18 +43,18 @@ baidu-rpc中常见错误的打印内容列表如下:
# 自定义错误码
在C++/C中你可以通过宏、常量、protobuf enum等方式定义ErrorCode:
```
```c++
#define ESTOP -114 // C/C++
static const int EMYERROR = 30; // C/C++
const int EMYERROR2 = -31; // C++ only
```
如果你需要用berror返回这些新错误码的描述,你可以在.cpp或.c文件的全局域中调用BAIDU_REGISTER_ERRNO(error_code, description)进行注册,比如:
```
```c++
BAIDU_REGISTER_ERRNO(ESTOP, "the thread is stopping")
BAIDU_REGISTER_ERRNO(EMYERROR, "my error")
```
strerror/strerror_r不认识使用BAIDU_REGISTER_ERRNO定义的错误码,自然地,printf类的函数中的%m也不能转化为对应的描述,你必须使用%s并配以berror()。
```
```c++
errno = ESTOP;
printf("Describe errno: %m\n"); // [Wrong] Describe errno: Unknown error -114
printf("Describe errno: %s\n", strerror_r(errno, NULL, 0)); // [Wrong] Describe errno: Unknown error -114
......
# 概述
类似于kylin的[ExecMan](http://websvn.work.baidu.com/repos/list#from=/repos/inf_common/show/trunk/kylin/ExecMan.h?revision=HEAD!!handler=loadframe), [ExecutionQueue](http://websvn.work.baidu.com/repos/list#from=/repos/public/show/trunk/bthread/bthread/execution_queue.h?revision=HEAD!!handler=loadframe)提供了异步串行执行的功能。ExecutionQueue的相关技术最早使用在RPC中实现[多线程向同一个fd写数据](http://wiki.baidu.com/display/RPC/IO#IO-%E5%8F%91%E6%B6%88%E6%81%AF). 在r31345之后加入到bthread。 ExecutionQueue 提供了如下基本功能:
类似于kylin的ExecMan, [ExecutionQueue](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/src/bthread/execution_queue.h)提供了异步串行执行的功能。ExecutionQueue的相关技术最早使用在RPC中实现[多线程向同一个fd写数据](io.md#发消息). 在r31345之后加入到bthread。 ExecutionQueue 提供了如下基本功能:
- 异步有序执行: 任务在另外一个单独的线程中执行, 并且执行顺序严格和提交顺序一致.
- Multi Producer: 多个线程可以同时向一个ExecutionQueue提交任务
......@@ -42,7 +42,7 @@ ExecutionQueue和mutex都可以用来在多线程场景中消除竞争. 相比
不考虑性能和复杂度,理论上任何系统都可以只使用mutex或者ExecutionQueue来消除竞争.
但是复杂系统的设计上,建议根据不同的场景灵活决定如何使用这两个工具:
- 如果临界区非常小,竞争又不是很激烈,优先选择使用mutex, 之后可以结合[contention profiler](http://wiki.baidu.com/display/RPC/contention+profiler)来判断mutex是否成为瓶颈。
- 如果临界区非常小,竞争又不是很激烈,优先选择使用mutex, 之后可以结合[contention profiler](contention_profiler.md)来判断mutex是否成为瓶颈。
- 需要有序执行,或者无法消除的激烈竞争但是可以通过批量执行来提高吞吐, 可以选择使用ExecutionQueue。
总之,多线程编程没有万能的模型,需要根据具体的场景,结合丰富的profliling工具,最终在复杂度和性能之间找到合适的平衡。
......
......@@ -79,13 +79,13 @@ Comparing to ptmalloc embedded in glibc, tcmalloc often improves performance. Ho
Code compiled with gcc 4.8.2 when linking to a tcmalloc compiled with earlier GCC may crash or deadlock before main(), E.g:
![img](http://wiki.baidu.com/download/attachments/71337200/image2017-8-22%2017%3A32%3A28.png?version=1&modificationDate=1503394348000&api=v2)
![img](../images/tcmalloc_stuck.png)
When you meet the issue, compile tcmalloc with the same GCC as the RPC code.
Another common issue with tcmalloc is that it does not return memory to system as early as ptmalloc. So when there's an invalid memory access, the program may not crash directly, instead it crashes at a unrelated place, or even not crash. When you program has weird memory issues, try removing tcmalloc.
If you want to use [cpu profiler](docs/cpu_profiler.md) or [heap profiler](docs/heap_profiler.md), do link `libtcmalloc_and_profiler.a`. These two profilers are based on tcmalloc.[contention profiler](contention_profiler.md) does not require tcmalloc.
If you want to use [cpu profiler](cpu_profiler.md) or [heap profiler](heap_profiler.md), do link `libtcmalloc_and_profiler.a`. These two profilers are based on tcmalloc.[contention profiler](contention_profiler.md) does not require tcmalloc.
When you remove tcmalloc, not only remove the linking with tcmalloc but also the macros: `-DBRPC_ENABLE_CPU_PROFILER` and `-DBRPC_ENABLE_HEAP_PROFILER`.
......@@ -95,4 +95,4 @@ baidu-rpc detects valgrind automatically (and registers stacks of bthread). Olde
# Track instances
We provide a program to help you to track and monitor all baidu-rpc instances. Just run [trackme_server](tools/trackme_server/trackme_server.cpp) somewhere and launch need-to-be-tracked instances with -trackme_server=SERVER. The trackme_server will receive pings from instances periodically and print logs when it does. You can aggregate instance addresses from the log and call builtin services of the instances for further information.
We provide a program to help you to track and monitor all baidu-rpc instances. Just run [trackme_server](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/tree/tools/trackme_server/) somewhere and launch need-to-be-tracked instances with -trackme_server=SERVER. The trackme_server will receive pings from instances periodically and print logs when it does. You can aggregate instance addresses from the log and call builtin services of the instances for further information.
......@@ -2,11 +2,11 @@ baidu-rpc可以分析内存是被哪些函数占据的。heap profiler的原理
# 开启方法
1. 在COMAKE中增加`CONFIGS('thirdsrc/tcmalloc@2.5.0.5977')`。如果要同时使用cpu profiler,加上`Libraries('libtcmalloc_and_profiler.a')`
1. 链接`libtcmalloc_and_profiler.a`
1. 这个版本的tcmalloc使用frame pointer而不是libunwind回溯栈,请确保在CXXFLAGS或CFLAGS中加上`-fno-omit-frame-pointer`,否则函数间的调用关系会丢失,最后产生的图片中都是彼此独立的函数方框。
1. 如果tcmalloc使用frame pointer而不是libunwind回溯栈,请确保在CXXFLAGS或CFLAGS中加上`-fno-omit-frame-pointer`,否则函数间的调用关系会丢失,最后产生的图片中都是彼此独立的函数方框。
2. 在COMAKE的CPPFLAGS中增加`-DBAIDU_RPC_ENABLE_HEAP_PROFILER`
2. 在COMAKE的CPPFLAGS中增加`-DBRPC_ENABLE_HEAP_PROFILER`
3. 在shell中`export TCMALLOC_SAMPLE_PARAMETER=524288`。该变量指每分配这么多字节内存时做一次统计,默认为0,代表不开启内存统计。[官方文档](http://gperftools.googlecode.com/svn/trunk/doc/tcmalloc.html)建议设置为524288。这个变量也可在运行前临时设置,如`TCMALLOC_SAMPLE_PARAMETER=524288 ./server`。如果没有这个环境变量,可能会看到这样的结果:
......@@ -37,13 +37,13 @@ WARNING: 12-26 10:01:25: * 0 [src/brpc/input_messenger.cpp:132][4294969345] Au
# 图示
![img](http://wiki.baidu.com/download/attachments/165876312/image2016-1-19%2023%3A8%3A50.png?version=1&modificationDate=1453216152000&api=v2)
![img](../images/heap_profiler_1.png)
左上角是当前程序通过malloc分配的内存总量,顺着箭头上的数字可以看到内存来自哪些函数。
点击左上角的text选择框可以查看文本格式的结果,有时候这种按分配量排序的形式更方便。
![img](http://wiki.baidu.com/download/attachments/165876312/image2016-1-19%2023%3A12%3A44.png?version=1&modificationDate=1453216386000&api=v2)
![img](../images/heap_profiler_2.png)
左上角的两个选择框作用分别是:
......@@ -52,7 +52,7 @@ WARNING: 12-26 10:01:25: * 0 [src/brpc/input_messenger.cpp:132][4294969345] Au
下图演示了勾选Diff和Text的效果。
![img](http://wiki.baidu.com/download/attachments/37774685/prof.gif?version=1&modificationDate=1494403248000&api=v2)
![img](../images/heap_profiler_3.gif)
你也可以使用pprof脚本(public/baidu-rpc/tools/pprof)在命令行中查看文本格式结果:
......@@ -103,5 +103,5 @@ Total: 38.9 MB
baidu-rpc还提供一个类似的growth profiler分析内存的分配去向(不考虑释放)。
![img](http://wiki.baidu.com/download/attachments/71337189/image2015-10-1%209%3A55%3A9.png?version=1&modificationDate=1443664514000&api=v2)
![img](../images/growth_profiler.png)
......@@ -156,7 +156,7 @@ Notes on http header:
出于通用性考虑且解压代码不复杂,baidu-rpc不会自动解压response body,用户可以自己做,方法如下:
```c++
#include brpc/policy/gzip_compress.h>
#include <brpc/policy/gzip_compress.h>
...
const std::string* encoding = cntl->http_response().GetHeader("Content-Encoding");
if (encoding != NULL && *encoding == "gzip") {
......@@ -181,7 +181,7 @@ r33796后baidu-rpc client支持在读取完body前就结束RPC,让用户在RPC
1. 首先实现ProgressiveReader,接口如下:
```c++
#include brpc/progressive_reader.h>
#include <brpc/progressive_reader.h>
...
class ProgressiveReader {
public:
......
......@@ -4,7 +4,7 @@
所有pb service默认都能通过/ServiceName/MethodName来访问,其中ServiceName不包括package。对于公司内的纯HTTP服务,一般来说这种形式的URL也够用了。实现步骤如下:
\1. 填写proto文件。
1. 填写proto文件。
下面代码里的HttpRequest和HttpResponse都是空的,因为http数据在Controller中。http request的头在Controller.http_request()中,body在Controller.request_attachment()中。类似的,http response的头在Controller.http_response(),body在Controller.response_attachment()。
......@@ -19,7 +19,7 @@ service HttpService {
};
```
\2.实现Service。和其他pb service一样,也是继承定义在.pb.h中的service基类。
2. 实现Service。和其他pb service一样,也是继承定义在.pb.h中的service基类。
```c++
class HttpServiceImpl : public HttpService {
......@@ -48,7 +48,7 @@ public:
};
```
实现完毕插入Server后可通过如下URL访问,/HttpService/Echo后的部分在cntl->http_request().unresolved_path()中,unresolved_path总是normalized。
实现完毕插入Server后可通过如下URL访问,/HttpService/Echo后的部分在 cntl->http_request().unresolved_path()中,unresolved_path总是normalized。
| URL | 访问方法 | cntl->http_request().uri().path() | cntl->http_request().unresolved_path() |
| -------------------------- | ---------------- | --------------------------------- | -------------------------------------- |
......@@ -64,9 +64,9 @@ public:
实现方法:
\1. proto文件中应以FileService为服务名,以default_method为方法名。
1. proto文件中应以FileService为服务名,以default_method为方法名。
```protbuf
```protobuf
option cc_generic_services = true;
message HttpRequest { };
......@@ -77,7 +77,7 @@ service FileService {
}
```
\2.实现Service。
2. 实现Service。
```c++
class FileServiceImpl: public FileService {
......@@ -168,11 +168,11 @@ if (server.AddService(&queue_svc,
`cntl.http_request().unresolved_path()` 对应星号(*)匹配的部分,保证normalized:开头结尾都不包含斜杠(/),中间斜杠不重复。比如:
![img](http://wiki.baidu.com/download/attachments/71337204/image2016-3-1%2012%3A26%3A51.png?version=1&modificationDate=1456806412000&api=v2)
![img](../images/restful_1.png)
![img](http://wiki.baidu.com/download/attachments/71337204/image2016-3-1%2012%3A27%3A47.png?version=1&modificationDate=1456806467000&api=v2)
![img](../images/restful_2.png)
unresolved_path都是`"foo/bar"`,左右、中间多余的斜杠被移除了。
......@@ -182,7 +182,7 @@ unresolved_path都是`"foo/bar"`,左右、中间多余的斜杠被移除了。
/status页面上的方法名后会加上所有相关的URL,形式是:@URL1 @URL2 ...
![img](http://wiki.baidu.com/download/attachments/71337204/image2016-3-1%200%3A12%3A36.png?version=1&modificationDate=1456762356000&api=v2)
![img](../images/restful_3.png)
# HTTP参数
......@@ -246,7 +246,7 @@ cntl->http_response().set_status_code(brpc::HTTP_STATUS_FOUND);
cntl->http_response().SetHeader("Location", "http://bj.bs.bae.baidu.com/family/image001(4979).jpg");
```
![img](http://wiki.baidu.com/download/attachments/71337204/image2015-12-9%2015%3A1%3A47.png?version=1&modificationDate=1449644507000&api=v2)
![img](../images/302.png)
## Query String
......@@ -277,7 +277,7 @@ http服务常对http body进行压缩,对于文本网页可以有效减少传
出于通用性考虑且解压代码不复杂,baidu-rpc不会自动解压request body,用户可以自己做,方法如下:
```c++
#include brpc/policy/gzip_compress.h>
#include <brpc/policy/gzip_compress.h>
...
const std::string* encoding = cntl->http_request().GetHeader("Content-Encoding");
if (encoding != NULL && *encoding == "gzip") {
......@@ -340,24 +340,21 @@ bool Controller::is_ssl() const;
没有极端性能要求的产品线都有使用HTTP协议的倾向,特别是移动端产品线,所以我们很重视HTTP的实现质量,具体来说:
- 使用了node.js的[http parser](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/src/brpc/http_parser.h)(部分来自nginx)解析http消息,这是一个轻量、优秀的实现。
- 使用了node.js的[http parser](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/src/brpc/details/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往往难以做到这一点。
- 来自不同client的http消息是高度并发的,即使相当复杂的http消息也不会影响对其他客户端的响应。其他rpc和[基于单线程reactor](threading_overview.md#单线程reactor)的各类http server往往难以做到这一点。
# 持续发送
r33796前baidu-rpc server不适合发送超大或无限长的body。r33796后baidu-rpc server支持。方法如下:
\1. 调用Controller::CreateProgressiveAttachment()创建可持续发送的body。
`boost::intrusive_ptr<brpc::ProgressiveAttachment> pa(cntl->CreateProgressiveAttachment());`
返回的ProgressiveAttachment对象需要用boost::intrusive_ptr<>管理,定义在brpc/progressive_attachment.h>中。
\2. 调用ProgressiveAttachment::Write()发送数据。如果写入发生在server回调结束前,发送的数据将会被缓存直到回调结束发送了header部分后才会开始发送数据。如果写入发生在server回调结束后,发送的数据将立刻以chunked mode写出。
1. 调用Controller::CreateProgressiveAttachment()创建可持续发送的body。
`boost::intrusive_ptr<brpc::ProgressiveAttachment> pa(cntl->CreateProgressiveAttachment());`
返回的ProgressiveAttachment对象需要用boost::intrusive_ptr<>管理,定义在brpc/progressive_attachment.h>中。
\3. 发送完毕后确保所有的boost::intrusive_ptr<brpc::ProgressiveAttachment>都析构了。
2. 调用ProgressiveAttachment::Write()发送数据。如果写入发生在server回调结束前,发送的数据将会被缓存直到回调结束发送了header部分后才会开始发送数据。如果写入发生在server回调结束后,发送的数据将立刻以chunked mode写出。
3. 发送完毕后确保所有的boost::intrusive_ptr<brpc::ProgressiveAttachment>都析构了。
# 持续接收
......@@ -379,14 +376,14 @@ baidu-rpc server同端口支持多种协议,当它遇到非法HTTP请求并解
根据[HTTP协议](http://tools.ietf.org/html/rfc3986#section-2.2)中的要求,以下字符应该使用%编码
> ```
> reserved = gen-delims / sub-delims
>
> gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@"
>
> sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
> / "*" / "+" / "," / ";" / "="
> ```
```
reserved = gen-delims / sub-delims
gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@"
sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
/ "*" / "+" / "," / ";" / "="
```
Base64 编码后的字符串中,会以"="或者"=="作为结尾(比如: ?wi=NDgwMDB8dGVzdA==&anothorkey=anothervalue), 这个字段可能会被正确解析,也可能不会,取决于具体实现,用户不应该做任何假设.
......
......@@ -20,7 +20,7 @@ linux一般使用non-blocking IO提高IO并发度。当IO并发度很低时,no
"消息”指向连接写出的有边界的二进制串,可能是发向上游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)
![img](../images/write.png)
由于baidu-rpc的写出总能很快地返回,调用线程可以更快地处理新任务,后台写线程也能每次拿到一批任务批量写出,在大吞吐时容易形成流水线效应而提高IO效率。
......@@ -40,4 +40,4 @@ linux一般使用non-blocking IO提高IO并发度。当IO并发度很低时,no
# The full picture
![img](http://wiki.baidu.com/download/attachments/48480438/image2015-12-26%2017%3A31%3A6.png?version=1&modificationDate=1451122271000&api=v2)
![img](../images/rpc_flow.png)
......@@ -40,21 +40,21 @@ LALB可以解决的问题:
注意这个算法并不是按照延时的比例来分流,不是说一个下游30ms,另一个60ms,它们的流量比例就是60 / 30。而是30ms的节点会一直获得流量直到它的延时高于60ms,或者没有更多流量了。以下图为例,曲线1和曲线2分别是节点1和节点2的延时与吞吐关系图,随着吞吐增大延时会逐渐升高,接近极限吞吐时,延时会飙升。左下的虚线标记了QPS=400时的延时,此时虽然节点1的延时有所上升,但还未高于节点2的基本延时(QPS=0时的延时),所以所有流量都会分给节点1,而不是按它们基本延时的比例(图中大约2:1)。当QPS继续上升达到1600时,分流比例会在两个节点延时相等时平衡,图中为9 : 7。很明显这个比例是高度非线性的,取决于不同曲线的组合,和单一指标的比例关系没有直接关联。在真实系统中,延时和吞吐的曲线也在动态变化着,分流比例更加动态。
![img](http://wiki.baidu.com/download/attachments/38012521/image2015-5-20%2023%3A5%3A34.png?version=1&modificationDate=1432134338000&api=v2)
![img](../images/lalb_1.png)
我们用一个例子来看一下具体的分流过程。启动3台server,逻辑分别是sleep 1ms,2ms,3ms,对于client来说这些值就是延时。启动client(50个同步访问线程)后每秒打印的分流结果如下:
![img](http://wiki.baidu.com/download/attachments/38012521/image2015-5-29%208%3A57%3A44.png?version=1&modificationDate=1432861065000&api=v2)
![img](../images/lalb_2.png)
S[n]代表第n台server。由于S[1]和S[2]的平均延时大于1ms,LALB会发现这点并降低它们的权值。它们的权值会继续下降,直到被算法设定的最低值拦住。这时停掉server,反转延时并重新启动,即逻辑分别为sleep 3ms,2ms,1ms,运行一段时候后分流效果如下:
![img](http://wiki.baidu.com/download/attachments/38012521/image2015-5-29%209%3A2%3A53.png?version=1&modificationDate=1432861373000&api=v2)
![img](../images/lalb_3.png)
刚重连上server时,client还是按之前的权值把大部分流量都分给了S[0],但由于S[0]的延时从1ms上升到了3ms,client的qps也降到了原来的1/3。随着数据积累,LALB逐渐发现S[2]才是最快的,而把大部分流量切换了过去。同样的服务如果用rr或random访问,则qps会显著下降:
"rr" or "random": ![img](http://wiki.baidu.com/download/attachments/38012521/image2015-5-31%2020%3A57%3A24.png?version=1&modificationDate=1433077045000&api=v2)
"rr" or "random": ![img](../images/lalb_4.png)
"la" : ![img](http://wiki.baidu.com/download/attachments/38012521/image2015-5-31%2020%3A58%3A21.png?version=1&modificationDate=1433077102000&api=v2)
"la" : ![img](../images/lalb_5.png)
真实的场景中不会有这么显著的差异,但你应该能看到差别了。
......
......@@ -5,7 +5,7 @@
在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](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告诉框架。
- 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/files/file_watcher.h)关注文件的修改时间,当文件修改后,读取并调用NamingServiceActions::ResetServers告诉框架。
- list:列表就在服务名里(逗号分隔)。在读取完一次并调用NamingServiceActions::ResetServers后就退出了,因为列表再不会改变了。
如果用户需要建立这些对象仍然是不够方便的,因为总是需要一些工厂代码根据配置项建立不同的对象,鉴于此,我们把工厂类做进了框架,并且是非常方便的形式:
......@@ -22,7 +22,7 @@ http://<url> # Domain Naming Service, aka DNS.
这套方式是可扩展的,实现了新的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)
![img](../images/register_ns.png)
看到这些熟悉的字符串格式,容易联想到ftp:// zk:// galileo://等等都是可以支持的。用户在新建Channel时传入这类NamingService描述,并能把这些描述写在各类配置文件中。
......@@ -34,7 +34,7 @@ Load balancer最重要的是如何让不同线程中的负载均衡不互斥,
和NamingService类似,我们使用字符串来指代一个load balancer,在global.cpp中注册:
![img](http://wiki.baidu.com/download/attachments/158717014/image2015-12-19%2017%3A15%3A14.png?version=1&modificationDate=1450516515000&api=v2)
![img](../images/register_lb.png)
# 健康检查
......
[memcached](http://memcached.org/)是常用的缓存服务,为了使用户更快捷地访问memcached并充分利用bthread的并发能力,baidu-rpc直接支持memcache协议。示例程序:<http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/example/memcache_c++/>
[memcached](http://memcached.org/)是常用的缓存服务,为了使用户更快捷地访问memcached并充分利用bthread的并发能力,baidu-rpc直接支持memcache协议。示例程序:[example/memcache_c++](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/tree/example/memcache_c++/)
> 注意:baidu-rpc只支持memcache的二进制协议。memcached在1.3前只有文本协议,但在当前看来支持的意义甚微。如果你的memcached早于1.3,升级版本。
**注意**:baidu-rpc只支持memcache的二进制协议。memcached在1.3前只有文本协议,但在当前看来支持的意义甚微。如果你的memcached早于1.3,升级版本。
相比使用[libmemcached](http://libmemcached.org/libMemcached.html)(官方client)的优势有:
......@@ -9,15 +9,15 @@
- 有明确的request和response。而libmemcached是没有的,收到的消息不能直接和发出的消息对应上,用户需要自己做维护工作。
- 支持多种[连接方式](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的性能。
当前实现充分利用了RPC的并发机制并尽量避免了拷贝。一个client可以轻松地把一个同机memcached实例(版本1.4.15)压到极限:单连接9万,多连接33万。在大部分情况下,baidu-rpc应该能充分发挥memcached的性能。
# 访问单台memcached
创建一个访问memcached的Channel:
```c++
#include brpc/memcache.h>
#include brpc/channel.h>
#include <brpc/memcache.h>
#include <brpc/channel.h>
ChannelOptions options;
options.protocol = brpc::PROTOCOL_MEMCACHE;
......@@ -25,11 +25,9 @@ if (channel.Init("0.0.0.0:11211", &options) != 0) { // 11211是memcached的默
LOG(FATAL) << "Fail to init channel to memcached";
return -1;
}
...
...
```
往memcached中设置一份数据。
```c++
......
......@@ -33,7 +33,7 @@
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函数中处理方法都是不同的,有些函数会加锁,有些则能忍受版本不相等。
![img](http://wiki.baidu.com/download/attachments/99588634/image2015-7-6%2016%3A5%3A53.png?version=1&modificationDate=1436169953000&api=v2)
![img](../images/resource_pool.png)
这种id生成方式在baidu-rpc中应用广泛,baidu-rpc中的SocketId,bthread_id_t也是用类似的方法分配的。
......
......@@ -20,7 +20,7 @@ baidu-rpc就是设计为可随时扩展新协议的,步骤如下:
## 增加ProtocolType
[options.proto](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/protocol/brpc/options.proto)的ProtocolType中增加新协议类型,如果你需要的话可以联系我们增加,以确保不会和其他人的需求重合。
[options.proto](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/src/brpc/options.proto)的ProtocolType中增加新协议类型,如果你需要的话可以联系我们增加,以确保不会和其他人的需求重合。
目前的ProtocolType(16年底):
```c++
......
......@@ -118,7 +118,7 @@ public:
## 设置ServerOptions.nshead_service
```c++
#include brpc/ubrpc2pb_protocol.h>
#include <brpc/ubrpc2pb_protocol.h>
...
brpc::ServerOptions option;
option.nshead_service = new brpc::policy::UbrpcCompackAdaptor; // mcpack2用UbrpcMcpack2Adaptor
......@@ -164,7 +164,7 @@ public:
};
```
完整的example在[example/nshead_extension_c++](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/example/nshead_extension_c++/)
完整的example在[example/nshead_extension_c++](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/tree/example/nshead_extension_c++/)
# 使用nshead+mcpack/compack/idl的服务
......@@ -174,7 +174,7 @@ idl是mcpack/compack的前端,用户只要在idl文件中描述schema,就可
> **这个服务在继续使用mcpack/compack作为序列化格式,相比protobuf占用成倍的带宽和打包时间。**
为了解决这个问题,我们提供了[public/mcpack2pb](http://wiki.baidu.com/display/RPC/idl+%3C%3D%3E+protobuf),允许把protobuf作为mcpack/compack的前端。你只要写一份proto文件,就可以同时解析mcpack/compack和protobuf格式的请求。使用这个方法,使用idl描述的服务的可以平滑地改造为使用proto文件描述,而不用修改上游client(仍然使用mcpack/compack)。你产品线的服务可以逐个地从mcpack/compack/idl切换为protobuf,从而享受到性能提升,带宽节省,全新开发体验等好处。你可以自行在NsheadService使用public/mcpack2pb,也可以联系我们,提供更高质量的协议支持。
为了解决这个问题,我们提供了[mcpack2pb](idl_protobuf.md),允许把protobuf作为mcpack/compack的前端。你只要写一份proto文件,就可以同时解析mcpack/compack和protobuf格式的请求。使用这个方法,使用idl描述的服务的可以平滑地改造为使用proto文件描述,而不用修改上游client(仍然使用mcpack/compack)。你产品线的服务可以逐个地从mcpack/compack/idl切换为protobuf,从而享受到性能提升,带宽节省,全新开发体验等好处。你可以自行在NsheadService使用public/mcpack2pb,也可以联系我们,提供更高质量的协议支持。
# 使用nshead+protobuf的服务
......@@ -186,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在[这里](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/example/nshead_pb_extension_c++/)
这样做的好处是,这个服务还可以被其他使用protobuf的协议访问,比如标准协议,hulu协议,sofa协议等等。NsheadPbServiceAdaptor的主要接口如下。完整的example在[这里](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/tree/example/nshead_pb_extension_c++/)
```c++
class NsheadPbServiceAdaptor : public NsheadService {
......
[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++/)
[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/tree/example/redis_c++/)
相比使用[hiredis](https://github.com/redis/hiredis)(官方client)的优势有:
......@@ -9,15 +9,15 @@
像http一样,baidu-rpc保证在最差情况下解析redis reply的时间复杂度也是O(N),N是reply的字节数,而不是O(N^2)。当reply是个较大的数组时,这是比较重要的。
r32037后加上[-redis_verbose](http://brpc.baidu.com:8765/flags/redis_verbose;redis_verbose_crlf2space)后会在stderr上打印出所有的redis request和response供调试。
r32037后加上[-redis_verbose](#查看发出的请求和收到的回复)后会在stderr上打印出所有的redis request和response供调试。
# 访问单台redis
创建一个访问redis的Channel:
```c++
#include brpc/redis.h>
#include brpc/channel.h>
#include <brpc/redis.h>
#include <brpc/channel.h>
brpc::ChannelOptions options;
options.protocol = brpc::PROTOCOL_REDIS;
......@@ -136,13 +136,13 @@ command_size()可获得(成功)加入的命令个数。
比如response包含三个reply,类型分别是integer,string和array (size=2)。那么可以分别这么获得值:response.reply(0).integer(),response.reply(1).c_str(), repsonse.reply(2)[0]和repsonse.reply(2)[1]。如果类型对不上,调用处的栈会被打印出来,并返回一个undefined的值。
> response中的所有reply的ownership属于response。当response析构时,reply也析构了。相应地,RedisReply被禁止拷贝。
response中的所有reply的ownership属于response。当response析构时,reply也析构了。相应地,RedisReply被禁止拷贝。
调用Clear()后RedisResponse可以重用。
# 访问redis集群
暂时请沿用常见的[twemproxy](https://github.com/twitter/twemproxy)方案,像访问单点一样访问proxy。如果你之前用hiredis访问[BDRP](http://wiki.baidu.com/pages/viewpage.action?pageId=40197196)(使用了twemproxy),那把client更换成baidu-rpc就行了。通过client(一致性哈希)直接访问redis集群虽然能降低延时,但同时也(可能)意味着无法直接利用BDRP的托管服务,这一块还不是很确定。
暂时请沿用常见的[twemproxy](https://github.com/twitter/twemproxy)方案,像访问单点一样访问proxy。如果你之前用hiredis访问BDRP(使用了twemproxy),那把client更换成baidu-rpc就行了。通过client(一致性哈希)直接访问redis集群虽然能降低延时,但同时也(可能)意味着无法直接利用BDRP的托管服务,这一块还不是很确定。
如果你自己维护了redis集群,和memcache类似,应该是可以用一致性哈希访问的。但每个RedisRequest应只包含一个command或确保所有的command始终落在同一台server。如果request包含了多个command,在当前实现下总会送向同一个server。比方说一个request中包含了多个Get,而对应的key分布在多个server上,那么结果就肯定不对了,这个情况下你必须把一个request分开为多个。
......@@ -152,9 +152,14 @@ command_size()可获得(成功)加入的命令个数。
打开[-redis_verbose_crlf2space](http://brpc.baidu.com:8765/flags/redis_verbose_crlf2space)可让打印内容中的CRLF (\r\n)变为空格,方便阅读。
| Name | Value | Description | Defined At |
| ------------------------ | ----- | ---------------------------------------- | ---------------------------------- |
| redis_verbose | false | [DEBUG] Print EVERY redis request/response to stderr | src/brpc/policy/redis_protocol.cpp |
| redis_verbose_crlf2space | false | [DEBUG] Show \r\n as a space | src/brpc/redis.cpp |
# 性能
redis版本:<https://svn.baidu.com/third-64/tags/redis/redis_2-6-14-100_PD_BL/>不是最新的3.0)
redis版本:2.6.14 (不是最新的3.0)
分别使用1,50,200个bthread同步压测同机redis-server,延时单位均为微秒。
......@@ -175,9 +180,7 @@ TRACE: 02-13 19:43:49: * 0 client.cpp:180] Accessing redis server at qps=41167
TRACE: 02-13 19:43:50: * 0 client.cpp:180] Accessing redis server at qps=412583 latency=482
```
200个线程后qps基本到极限了。这里的极限qps比hiredis高很多,原因在于baidu-rpc默认以单链接访问redis-server,多个线程在写出时会[以wait-free的方式合并](http://wiki.baidu.com/display/RPC/IO#IO-发消息),从而让redis-server就像被批量访问一样,每次都能从那个连接中读出一批请求,从而获得远高于非批量时的qps。下面通过连接池访问redis-server时qps的大幅回落是另外一个证明。
200个线程后qps基本到极限了。这里的极限qps比hiredis高很多,原因在于baidu-rpc默认以单链接访问redis-server,多个线程在写出时会[以wait-free的方式合并](io.md#发消息),从而让redis-server就像被批量访问一样,每次都能从那个连接中读出一批请求,从而获得远高于非批量时的qps。下面通过连接池访问redis-server时qps的大幅回落是另外一个证明。
分别使用1,50,200个bthread一次发送10个同步压测同机redis-server,延时单位均为微秒。
......@@ -204,9 +207,7 @@ TRACE: 02-13 19:49:11: * 0 client.cpp:180] Accessing redis server at qps=29271
16878 gejun 20 0 48136 2508 1004 R 99.9 0.0 13:36.59 redis-server // thread_num=200
```
注意redis-server实际处理的qps要乘10。乘10后也差不多在40万左右。另外在thread_num为50或200时,redis-server的CPU已打满。注意redis-server是[单线程reactor](http://wiki.baidu.com/display/RPC/Threading+Overview#ThreadingOverview-单线程reactor),一个核心打满就意味server到极限了。
注意redis-server实际处理的qps要乘10。乘10后也差不多在40万左右。另外在thread_num为50或200时,redis-server的CPU已打满。注意redis-server是[单线程reactor](threading_overview.md#单线程reactor),一个核心打满就意味server到极限了。
使用50个bthread通过连接池方式同步压测同机redis-server。
......
......@@ -8,17 +8,7 @@ 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 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](getting_started.md)
也可以从[agile](http://agile.baidu.com/#/release/public/baidu-rpc)上获取产出,下面是获取版本r34466中的rpc_press的命令:
`wget -r -nH --level=0 --cut-dirs=8 getprod@buildprod.scm.baidu.com:/temp/data/prod-64/public/baidu-rpc/d92a9fac91892a5f4784fc105e493933/r34466/output/bin/rpc_press --user getprod --password getprod --preserve-permissions`
先按照[Getting Started](getting_started.md)编译好baidu-rpc,再去tools/rpc_press编译。
在CentOS 6.3上如果出现找不到libssl.so.4的错误,可执行`ln -s /usr/lib64/libssl.so.6 libssl.so.4临时解决`
......@@ -77,11 +67,11 @@ TRACE: 01-30 16:10:04: * 0 src/brpc/server.cpp:742] Check out http://db-rpc-de
dummy_server启动时会在终端打印日志,一般按住ctrl点击那个链接可以直接打开对应的内置服务页面,就像这样:
![img](http://wiki.baidu.com/download/attachments/97645422/image2016-1-30%2016%3A16%3A39.png?version=1&modificationDate=1454141816000&api=v2)
![img](../images/rpc_press_1.png)
切换到vars页面,在Search框中输入rpc_press可以看到当前压力的延时分布情况:
![img](http://wiki.baidu.com/download/attachments/97645422/image2016-1-30%2016%3A14%3A59.png?version=1&modificationDate=1454141716000&api=v2)
![img](../images/rpc_press_2.png)
你可以通过-dummy_port参数修改dummy_server的端口,但请确保端口在8000到8999范围内,否则总是无法在浏览器中访问。
......
......@@ -2,17 +2,7 @@ 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 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](getting_started.md)
也可以从[agile](http://agile.baidu.com/#/release/public/baidu-rpc)上获取产出,下面是获取版本r34466中的rpc_replay的命令:
`wget -r -nH --level=0 --cut-dirs=8 getprod@buildprod.scm.baidu.com:/temp/data/prod-64/public/baidu-rpc/d92a9fac91892a5f4784fc105e493933/r34466/output/bin/rpc_replay --user getprod --password getprod --preserve-permissions`
先按照[Getting Started](getting_started.md)编译好baidu-rpc,再去tools/rpc_replay编译。
在CentOS 6.3上如果出现找不到libssl.so.4的错误,可执行`ln -s /usr/lib64/libssl.so.6 libssl.so.4临时解决`
......@@ -20,9 +10,9 @@ r31658后,baidu-rpc能随机地把一部分请求写入一些文件中,并
baidu-rpc通过如下flags打开和控制如何保存请求,包含(R)后缀的flag都可以动态设置。
![img](http://wiki.baidu.com/download/attachments/158707916/image2016-2-3%2017%3A39%3A47.png?version=1&modificationDate=1454492387000&api=v2)
![img](../images/rpc_replay_1.png)
![img](http://wiki.baidu.com/download/attachments/158707916/image2016-2-3%2017%3A40%3A26.png?version=1&modificationDate=1454492426000&api=v2)
![img](../images/rpc_replay_2.png)
参数说明:
......@@ -35,7 +25,7 @@ baidu-rpc通过一个[bvar::Collector](http://icode.baidu.com/repo/baidu/opensou
写出的内容依次存放在rpc_dump_dir目录下的多个文件内,这个目录默认在./rpc_dump_<app>,其中<app>是程序名。不同程序在同一个目录下同时采样时会写入不同的目录。如果程序启动时rpc_dump_dir已经存在了,目录将被清空。目录中的每个文件以requests.yyyymmdd_hhmmss_uuuuus命名,以保证按时间有序方便查找,比如:
![img](http://wiki.baidu.com/download/attachments/158707916/image2015-12-19%200%3A11%3A6.png?version=1&modificationDate=1450455081000&api=v2)
![img](../images/rpc_replay_3.png)
目录下的文件数不超过rpc_dump_max_files,超过后最老的文件被删除从而给新文件腾出位置。
......@@ -55,8 +45,8 @@ serialized request (body_size - meta_size bytes, including attachment)
baidu-rpc提供了[SampleIterator](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/src/brpc/rpc_dump.h)从一个采样目录下的所有文件中依次读取所有的被采样请求,用户可根据需求把serialized request反序列化为protobuf请求,做一些二次开发。
```
#include brpc/rpc_dump.h>
```c++
#include <brpc/rpc_dump.h>
...
brpc::SampleIterator it("./rpc_data/rpc_dump/echo_server");
for (SampleRequest* req = it->Next(); req != NULL; req = it->Next()) {
......@@ -69,9 +59,9 @@ for (SampleRequest* req = it->Next(); req != NULL; req = it->Next()) {
# 回放
baidu-rpc在[tools/rpc_replay](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/tools/rpc_replay/)提供了默认的回放工具。运行方式如下:
baidu-rpc在[tools/rpc_replay](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/tree/tools/rpc_replay/)提供了默认的回放工具。运行方式如下:
![img](http://wiki.baidu.com/download/attachments/158707916/image2015-12-19%200%3A40%3A56.png?version=1&modificationDate=1450456871000&api=v2)
![img](../images/rpc_replay_4.png)
主要参数说明:
......@@ -88,7 +78,7 @@ baidu-rpc在[tools/rpc_replay](http://icode.baidu.com/repo/baidu/opensource/baid
rpc_replay会默认启动一个仅监控用的dummy server。打开后可查看回放的状况。其中rpc_replay_error是回放失败的次数。
![img](http://wiki.baidu.com/download/attachments/158707916/image2015-12-19%200%3A44%3A30.png?version=1&modificationDate=1450457085000&api=v2)
![img](../images/rpc_replay_5.png)
如果你无法打开浏览器,命令行中也会定期打印信息:
......
......@@ -2,21 +2,7 @@ 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 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](getting_started.md)
也可以从[agile](http://agile.baidu.com/#/release/public/baidu-rpc)上获取产出,下面是获取版本r34466中的rpc_view的命令:
```bash
wget -r -nH --level=0 --cut-dirs=8 getprod@buildprod.scm.baidu.com:/temp/data/prod-64/public/baidu-rpc/d92a9fac91892a5f4784fc105e493933/r34466/output/bin/rpc_view --user getprod --password getprod --preserve-permissions
```
先按照[Getting Started](getting_started.md)编译好baidu-rpc,再去tools/rpc_view编译。
在CentOS 6.3上如果出现找不到libssl.so.4的错误,可执行`ln -s /usr/lib64/libssl.so.6 libssl.so.4临时解决`
......@@ -34,7 +20,7 @@ TRACE: 02-14 12:12:20: * 0 src/brpc/server.cpp:771] Check out http://db-rpc-de
打开rpc_view在8888端口提供的页面(在secureCRT中按住ctrl点url):
![img](http://wiki.baidu.com/download/attachments/167651918/image2016-2-14%2012%3A15%3A42.png?version=1&modificationDate=1455423342000&api=v2)
![img](../images/rpc_view_1.png)
这个页面正是目标server的内置服务,右下角的提示告诉我们这是rpc_view提供的。这个页面和真实的内置服务基本是一样的,你可以做任何操作。
......@@ -44,8 +30,8 @@ TRACE: 02-14 12:12:20: * 0 src/brpc/server.cpp:771] Check out http://db-rpc-de
假如我们之前停留在原目标server的/connections页面:
![img](http://wiki.baidu.com/download/attachments/167651918/image2016-2-14%2012%3A22%3A32.png?version=1&modificationDate=1455423752000&api=v2)
![img](../images/rpc_view_2.png)
加上?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)
![img](../images/rpc_view_3.png)
......@@ -8,9 +8,9 @@
若启动时未加-enable_rpcz,则可在启动后访问SERVER_URL/rpcz/enable动态开启rpcz,访问SERVER_URL/rpcz/disable则关闭,这两个链接等价于访问SERVER_URL/flags/enable_rpcz?setvalue=true和SERVER_URL/flags/enable_rpcz?setvalue=false。在r31010之后,rpc在html版本中增加了一个按钮可视化地开启和关闭。
![img](http://wiki.baidu.com/download/attachments/71337189/image2015-12-24%2013%3A20%3A38.png?version=1&modificationDate=1450934438000&api=v2)
![img](../images/rpcz_4.png)
![img](http://wiki.baidu.com/download/attachments/71337189/image2015-12-24%2013%3A21%3A34.png?version=1&modificationDate=1450934494000&api=v2)
![img](../images/rpcz_5.png)
如果只是baidu-rpc client或没有使用baidu-rpc,看[这里](dummy_server.md)
......@@ -22,13 +22,13 @@
看到最新请求的概况,点击链接进入第二层。
![img](http://wiki.baidu.com/download/attachments/37774685/image2015-1-21%2020%3A20%3A53.png?version=1&modificationDate=1421842854000&api=v2)
![img](../images/rpcz_6.png)
### 第二层
看到某系列(trace)或某个请求(span)的详细信息。一般通过点击链接进入,也可以把trace=和span=作为query-string拼出链接
![img](http://wiki.baidu.com/download/attachments/37774685/image2015-1-21%2020%3A23%3A35.png?version=1&modificationDate=1421843015000&api=v2)
![img](../images/rpcz_7.png)
内容说明:
......
......@@ -270,7 +270,7 @@ $ curl -d '{"message":"hello"}' http://brpc.baidu.com:8765/EchoService/Echo
## json<=>pb
json通过名字与pb字段一一对应,结构层次也应匹配。json中一定要包含pb的required字段,否则转化会失败,对应请求会被拒绝。json中可以包含pb中没有定义的字段,但不会作为pb的unknown字段被继续传递。转化规则详见[json <=> protobuf](http://wiki.baidu.com/display/RPC/json+%3C%3D%3E+protobuf)
json通过名字与pb字段一一对应,结构层次也应匹配。json中一定要包含pb的required字段,否则转化会失败,对应请求会被拒绝。json中可以包含pb中没有定义的字段,但不会作为pb的unknown字段被继续传递。转化规则详见[json <=> protobuf](idl_protobuf.md)
r34532后增加选项-pb_enum_as_number,开启后pb中的enum会转化为它的数值而不是名字,比如在`enum MyEnum { Foo = 1; Bar = 2; };`中不开启此选项时MyEnum类型的字段会转化为"Foo"或"Bar",开启后为1或2。此选项同时影响client发出的请求和server返回的回复。由于转化为名字相比数值有更好的前后兼容性,此选项只应用于兼容无法处理enum为名字的场景。
......@@ -294,7 +294,7 @@ server.AddService(service, svc_opt);
server端会自动尝试其支持的协议,无需用户指定。`cntl->protocol()`可获得当前协议。server能从一个listen端口建立不同协议的连接,不需要为不同的协议使用不同的listen端口,一个连接上也可以传输多种协议的数据包(但一般不会这么做),支持的协议有:
- [标准协议](http://wiki.baidu.com/display/pp/Protobuf+RPC),显示为"baidu_std",默认启用。
- 标准协议,显示为"baidu_std",默认启用。
- hulu协议,显示为"hulu",默认启动。
......@@ -334,7 +334,7 @@ server端会自动尝试其支持的协议,无需用户指定。`cntl->protoco
顾名思义,这个协议的数据包由nshead+mcpack构成,mcpack中不包含特殊字段。不同于用户基于NsheadService的实现,这个协议使用了mcpack2pb:任何protobuf service都可以接受这个协议的请求。由于没有传递ErrorText的字段,当发生错误时server只能关闭连接。
- ITP协议,显示为"itp",默认不启用,使用方式见[ITP](http://wiki.baidu.com/display/RPC/ITP)。
- ITP协议,显示为"itp",默认不启用,使用方式见[ITP](itp.md)
- UB相关的协议请阅读[实现NsheadService](nshead_service.md)
......
[/status](http://brpc.baidu.com:8765/status)可以访问服务的主要统计信息。这些信息和/vars是同源的,但按服务重新组织方便查看。
![img](http://wiki.baidu.com/download/attachments/165876293/image2016-12-22%2011%3A19%3A56.png?version=1&modificationDate=1482376797000&api=v2)
![img](../images/status.png)
上图中字段的含义分别是:
......@@ -32,4 +32,4 @@ public:
比如:
![img](http://wiki.baidu.com/download/attachments/165876293/image2017-1-14%2023%3A58%3A20.png?version=1&modificationDate=1484409504000&api=v2)
![img](../images/status_2.png)
......@@ -4,8 +4,6 @@ streaming_log - Print log to std::ostreams
# SYNOPSIS
你得依赖[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 +321,4 @@ TEST_F(StreamingLogTest, log_at) {
定义在base/comlog_sink.h中,把日志打印入comlog,主要用于线上系统,用法见[SYNOPSIS](#SYNOPSIS)一段。
> [使用]()ComlogSink的streaming log可以和com_writelog, ul_writelog混用。你并不需要把程序中所有日志都换成streaming log。
使用ComlogSink的streaming log可以和com_writelog, ul_writelog混用。你并不需要把程序中所有日志都换成streaming log。
\ No newline at end of file
......@@ -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++](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/example/streaming_echo_c++/)
例子见[example/streaming_echo_c++](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/tree/example/streaming_echo_c++/)
# 建立Stream
......
......@@ -2,8 +2,7 @@
# thread-local问题
调用阻塞的bthread函数后,所在的pthread很可能改变,这使[pthread_getspecific](http://linux.die.net/man/3/pthread_getspecific)[gcc
__thread](https://gcc.gnu.org/onlinedocs/gcc-4.2.4/gcc/Thread_002dLocal.html)和c++11
调用阻塞的bthread函数后,所在的pthread很可能改变,这使[pthread_getspecific](http://linux.die.net/man/3/pthread_getspecific)[gcc __thread](https://gcc.gnu.org/onlinedocs/gcc-4.2.4/gcc/Thread_002dLocal.html)和c++11
thread_local变量,pthread_self()等的值变化了,如下代码的行为是不可预计的:
```
......@@ -29,7 +28,7 @@ gcc4会优化[标记为__attribute__((__const__))](https://gcc.gnu.org/onlinedoc
extern int *__errno_location (void) __THROW __attribute__ ((__const__));
```
由于此函数被标记为__const__,且没有参数,当你在一个函数中调用多次errno时,可能只有第一次才调用__errno_location(),而之后只是访问其返回的int*。在pthread中这没有问题,因为返回的int*是thread-local的,一个给定的pthread中是不会变化的。但是在bthread中,这是不成立的,因为一个bthread很可能在调用一些函数后跑到另一个pthread去,如果gcc4做了类似的优化,即一个函数内所有的errno都替换为第一次调用返回的int*,这中间bthread又切换了pthread,那么可能会访问之前pthread的errno,从而造成未定义行为。
由于此函数被标记为`__const__`,且没有参数,当你在一个函数中调用多次errno时,可能只有第一次才调用__errno_location(),而之后只是访问其返回的`int*`。在pthread中这没有问题,因为返回的`int*`是thread-local的,一个给定的pthread中是不会变化的。但是在bthread中,这是不成立的,因为一个bthread很可能在调用一些函数后跑到另一个pthread去,如果gcc4做了类似的优化,即一个函数内所有的errno都替换为第一次调用返回的int*,这中间bthread又切换了pthread,那么可能会访问之前pthread的errno,从而造成未定义行为。
比如下文是一种errno的使用场景:
......@@ -58,9 +57,9 @@ Use *p ... - still the errno of original pthread, undefined b
严格地说这个问题不是gcc4导致的,而是glibc给__errno_location的签名不够准确,一个返回thread-local指针的函数依赖于段寄存器(TLS的一般实现方式),这怎么能算const呢?由于我们还未找到覆盖__errno_location的方法,所以这个问题目前实际的解决方法是:
**务必在直接或间接使用bthread的项目的gcc编译选项中添加-D__const__=,即把__const__定义为空,避免gcc4做相关优化。**
**务必在直接或间接使用bthread的项目的gcc编译选项中添加`-D__const__=`,即把`__const__`定义为空,避免gcc4做相关优化。**
\_\_const\_\_定义为空对程序其他部分的影响几乎为0。另外如果你没有**直接**使用errno(即你的项目中没有出现errno),或使用的是gcc
3.4,即使没有定义-D\_\_const\_\_=,程序的正确性也不会受影响,但为了防止未来可能的问题,我们强烈建议加上。
`__const__`定义为空对程序其他部分的影响几乎为0。另外如果你没有**直接**使用errno(即你的项目中没有出现errno),或使用的是gcc
3.4,即使没有定义`-D__const__=`,程序的正确性也不会受影响,但为了防止未来可能的问题,我们强烈建议加上。
需要说明的是,和errno类似,pthread_self也有类似的问题,不过一般pthread_self除了打日志没有其他用途,影响面较小,在-D\_\_const\_\_=后pthread_self也会正常。
需要说明的是,和errno类似,pthread_self也有类似的问题,不过一般pthread_self除了打日志没有其他用途,影响面较小,在`-D__const__=`后pthread_self也会正常。
\ No newline at end of file
......@@ -10,7 +10,7 @@
单线程reactor的运行方式如下图所示:
![img](http://wiki.baidu.com/download/attachments/99588643/image2015-7-6%2016%3A3%3A42.png?version=1&modificationDate=1436169822000&api=v2)
![img](../images/threading_overview_1.png)
## N:1线程库
......@@ -22,7 +22,7 @@
多线程reactor的运行方式如下:
![img](http://wiki.baidu.com/download/attachments/99588643/image2015-7-6%2016%3A4%3A28.png?version=1&modificationDate=1436169869000&api=v2)
![img](../images/threading_overview_2.png)
# 那我们还能改进什么呢?
......
......@@ -30,7 +30,7 @@
- 一个TimerThread而不是多个。
- 创建的timer散列到多个Bucket以降低线程间的竞争,默认12个Bucket。
- Bucket内不使用小顶堆管理时间,而是链表 + nearest_run_time字段,当插入的时间早于nearest_run_time时覆盖这个字段,之后去和全局nearest_run_time(和Bucket的nearest_run_time不同)比较,如果也早于这个时间,修改并唤醒TimerThread。链表节点在锁外使用[ResourcePool](http://wiki.baidu.com/display/RPC/Memory+Management)分配。
- Bucket内不使用小顶堆管理时间,而是链表 + nearest_run_time字段,当插入的时间早于nearest_run_time时覆盖这个字段,之后去和全局nearest_run_time(和Bucket的nearest_run_time不同)比较,如果也早于这个时间,修改并唤醒TimerThread。链表节点在锁外使用[ResourcePool](memory_management.md)分配。
- 删除时通过id直接定位到timer内存结构,修改一个标志,timer结构总是由TimerThread释放。
- TimerThread被唤醒后首先把全局nearest_run_time设置为几乎无限大(max of int64),然后取出所有Bucket内的链表,并把Bucket的nearest_run_time设置为几乎无限大(max of int64)。TimerThread把未删除的timer插入小顶堆中维护,这个堆就它一个线程用。在每次运行回调或准备睡眠前都会检查全局nearest_run_time, 如果全局更早,说明有更早的时间加入了,重复这个过程。
......
......@@ -2,7 +2,7 @@ baidu-rpc可通过多种方式访问用ub搭建的服务。
# ubrpc (by protobuf)
r31687后,baidu-rpc支持通过protobuf访问ubrpc,不需要baidu-rpc-ub,也不依赖idl-compiler。(也可以让protobuf服务被ubrpc client访问,方法见[使用ubrpc的服务](nshead_service.md#id-实现NsheadService-使用ubrpc的服务))。
r31687后,baidu-rpc支持通过protobuf访问ubrpc,不需要baidu-rpc-ub,也不依赖idl-compiler。(也可以让protobuf服务被ubrpc client访问,方法见[使用ubrpc的服务](nshead_service.md#使用ubrpc的服务))。
**步骤:**
......@@ -75,7 +75,7 @@ r31687后,baidu-rpc支持通过protobuf访问ubrpc,不需要baidu-rpc-ub,
idl不同于pb,允许有多个请求,我们先看只有一个请求的情况,和普通的pb访问基本上是一样的。
```c++
#include brpc/channel.h>
#include <brpc/channel.h>
#include "echo.pb.h"
...
......@@ -110,7 +110,7 @@ r31687后,baidu-rpc支持通过protobuf访问ubrpc,不需要baidu-rpc-ub,
多个请求要设置一下set_idl_names
```c++
#include brpc/channel.h>
#include <brpc/channel.h>
#include "echo.pb.h"
...
......@@ -178,7 +178,7 @@ server端由public/ubrpc搭建,request/response使用idl文件描述字段,
4. 用法和访问其他协议类似:创建ChannelChannelOptions.protocol为**brpc::PROTOCOL_NSHEAD_CLIENT**或**"nshead_client"**。requestresponse对象必须是baidu-rpc-ub提供的类型
```c++
#include brpc/ub_call.h>
#include <brpc/ub_call.h>
...
brpc::Channel channel;
......@@ -219,7 +219,7 @@ server端由public/ubrpc搭建,request/response使用idl文件描述字段,
...
```
具体example代码可以参考[echo_c++_compack_ubrpc](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/example/echo_c++_compack_ubrpc/),类似的还有[echo_c++_mcpack_ubrpc](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/blob/example/echo_c++_mcpack_ubrpc/)。
具体example代码可以参考[echo_c++_compack_ubrpc](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/tree/example/echo_c++_compack_ubrpc/),类似的还有[echo_c++_mcpack_ubrpc](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/tree/example/echo_c++_mcpack_ubrpc/)。
# nshead+idl
......@@ -228,7 +228,7 @@ server端是由public/ub搭建,通讯包组成为nshead+idl::compack/idl::mcpa
由于不需要指定servicemethod,无需编写proto文件,直接使用Channel.CallMethod方法发起RPC即可。请求包中的nshead可以填也可以不填,框架会补上正确的magic_numbody_len字段:
```c++
#include brpc/ub_call.h>
#include <brpc/ub_call.h>
...
brpc::Channel channel;
......@@ -265,7 +265,7 @@ response.message();
server端是由public/ub搭建,通讯包组成为nshead+mcpack包,但不是idl编译器生成的,RPC前需要先构造RawBuffer将其传入,然后获取mc_pack_t并按之前手工填写mcpack的方式操作:
```c++
#include brpc/ub_call.h>
#include <brpc/ub_call.h>
...
brpc::Channel channel;
......@@ -312,7 +312,7 @@ mc_pack_get_str(res_pack, "mystr");
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 brpc/nshead_message.h>
#include <brpc/nshead_message.h>
...
brpc::Channel;
......
[public/bvar](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/tree/src/bvar/)是多线程环境下的计数器类库,方便记录和查看用户程序中的各类数值,它利用了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。
[public/bvar](http://icode.baidu.com/repo/baidu/opensource/baidu-rpc/files/master/tree/src/bvar/)是多线程环境下的计数器类库,方便记录和查看用户程序中的各类数值,它利用了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](bvar.md)。baidu-rpc大量使用了bvar提供统计数值,当你需要在多线程环境中计数并展现时,应该第一时间想到bvar。但bvar不能代替所有的计数器,它的本质是把写时的竞争转移到了读:读得合并所有写过的线程中的数据,而不可避免地变慢了。当你读写都很频繁并得基于数值做一些逻辑判断时,你不应该用bvar。
## 查询方法
......@@ -12,11 +12,11 @@
以下动画演示了如何使用过滤功能。你可以把包含过滤表达式的url复制粘贴给他人,他们点开后将看到你看到的内容。
![img](http://wiki.baidu.com/download/attachments/37774685/filter_bvar.gif?version=1&modificationDate=1494403262000&api=v2)
![img](../images/vars_1.gif)
1.0.123.31011及之后的版本加入了一个搜索框加快了寻找特定bvar的速度,在这个搜索框你只需键入bvar名称的一部分,框架将补上*进行模糊查找。不同的名称间可以逗号、分号或空格分隔。
![img](http://wiki.baidu.com/download/attachments/37774685/search_var.gif?version=1&modificationDate=1494403229000&api=v2)
![img](../images/vars_2.gif)
你也可以在命令行中访问vars:
......@@ -42,7 +42,7 @@ 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)
![img](../images/vars_3.gif)
## 统计和查看分位值
......@@ -50,7 +50,7 @@ x%分位值(percentile)是指把一段时间内的N个统计值排序,排
分位值可以绘制为CDF曲线和按时间变化时间。
![img](http://wiki.baidu.com/download/attachments/71337189/image2015-9-21%2022%3A34%3A14.png?version=1&modificationDate=1442846055000&api=v2)
![img](../images/vars_4.png)
上图是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曲线。
......@@ -61,13 +61,13 @@ x%分位值(percentile)是指把一段时间内的N个统计值排序,排
一条真实的好CDF曲线的特征是”斜率很小,尾部很窄“。
![img](http://wiki.baidu.com/download/attachments/71337189/image2015-9-21%2022%3A57%3A1.png?version=1&modificationDate=1442847422000&api=v2)
![img](../images/vars_5.png)
上图是按时间变化曲线。包含了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)
![img](../images/vars_6.png)
你可以用bvar::LatencyRecorder统计非baidu-rpc服务的延时,这么做(更具体的使用方法请查看[bvar-c++](bvar_c++.md)):
......@@ -86,7 +86,7 @@ void foo() {
如果这个程序使用了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)
![img](../images/vars_7.png)
## 非baidu-rpc server
......
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