brpc服务器项目,brpc 摘要

Cacheline

1.背景:cpu的L1和L2级cache为每个核独有,cpu的L3cache为所有核心共享

2.原因:核写入自己的L1级cache是极快的,但当另一核读写同一处内存时,由于每个核有local cache,需要进行一致性同步,确保内存和所有local cache的数据是一致的,这个复杂的硬件算法使得原子操作变得很慢

3.例子:所有线程频繁修改一个计数器,性能就会很差,让每个线程修改独立的thread_local变量,在需要时再同步

4.解决方法:避免共享,变量按访问规律排列,频繁修改的线程要放在独立的cacheline中

Memory fence

1.背景:cpu和编译器重排指令导致读写顺序的变化

Non-blocking wait-free& lock-free

1.Non-blocking:一个线程的失败或挂起不会导致其他线程的失败或挂起。 通过Non-blocking实现高并发

2.Non-blocking算法如果保证每个线程始终在做有用的事,那么就是wait-free,如果保证至少有一个线程在做有用的事,那就是lock-free

3.wait-free&lock-free并不保证高性能,因为需要处理更多更复杂的race condition和ABA problem

4.mutex:锁导致低性能的原因是临界区过大,或者上下文切换频繁

5.使用链表记录 处理多线程写的问题,通过swap(head)的方式,竞争写权限,如果返回null,则成功,否则swap(head)->next 。如果数据量较大,会启动keep Write写线程批量写出

Socket

1.Create/Address/SetFailed

2.Socket类似shared_ptr,SocketId类似于weak_ptr,SetFailed可以再需要时确保Socket不能被继续Address而最终引用计数归0.

3.存储SocketUniquePtr还是SocketId取决于是否需要强引用。如果需要大量数据交互,存放的是SocketUniquePtr。epoll主要提醒对应fd上发生了事件,如果socket回收了,那这个事件可有可无,所以存放SocketId

自适应限流

1.背景:服务能力有上限,请求速度超过处理速度,服务会过载。通过设置最大并发能直接拒绝一部分请求。

2.原因:当服务数量多,拓扑复杂,且处理能力会逐渐变化的局面下,固定最大并发数带了了巨大测试工作量

3.Little' Law: 服务稳定时:concurrency = qps*latency, noload_latency 单纯任务处理的延时,peak_qps是处理或回复的qps

3.解决方案:besk_max_concurrrency = noload_latency * peak_qps。 自适应限流就是要找到服务的noload_latency 和 peak_qps,并将最大并发设置为靠近两者乘积的一个值

max_concurrency = max_qps * ((2+alpha) * min_latency - lantency)。 不断对请求进行采样,通过窗口采样获得样本的平均延迟和当前qps, 计算出下一个窗口的max_concurrency。 通过定期降低max_concurrency

为避免流量损失太多的情况 1.请求数量足够多时,直接提交当前采样窗口 2.计算公式方面,当current_qps > max_qps时,直接进行更新,不进行平滑处理。 2秒左右将qps打满

雪崩

1.解决方法:1)设置合理的max_concurrency,最大qps*非拥塞时的延时来评估最大并发。2)注意重试设置。只有当连接错误时重试,而不是连接超时时重试。如果需要连接时重试,可以设置backup request,这种重试只有一次,放大程度降低到最低。3)brpc默认在bthread中处理请求,个数是软件限制

backup request

1.设置backup_request_ms,先发送请求,经过backup_request_ms后再发送一次请求,之后谁先返回取哪一个

bthread

单核reator,libevent等为典型,实质是把多段逻辑按事件触发顺序交织在一个系统线程中

N:1 线程库,所有协程运行于一个系统线程中,计算能力与各种eventloop库等价。 由于不跨线程,可以高效的利用 local cache, 无上下文切换代价,但不能高效的利用多核,代码必须非阻塞。 更适合写运行时间确定的IO服务器,典型如http server

多核reator,以boost::asio为典型,一般由一个或者多个线程分别运行event dispatcher,待事情发生后把event_handler交给一个worker线程。由于cache一致性,未必能获取线性与核心数的性能。

M:N 线程库,用户态的实现以go为主。一个bthread被卡住不会影响其他bthread,关键技术两点: work stealing调度和butex

lambda是一种语法糖,可以让异步编程没那么麻烦,但无法降低多线程编码难度

bvar

多线程环境下的计数器类库,方便记录和查看用户程序中各类数值,利用local cache减少了cache bouncing。本质是将写时的竞争转移到了读,读得合并所有写过的线程数据,不可避免的慢了

适合: 写多读少

不适合:基于最新值做逻辑判断,读多写多

方式:定期把监控项目打入固定目录下的文件

内存管理

内存管理都存在如下两点权衡

1.线程间竞争小,resource pool通过批量发送和归还避免全局竞争,并降低单次的开销

2.浪费的空间少,需要共享内存

多线程框架广泛地通过传递对象的ownership来让问题异步化,如何让分配这些小对象的开销变得更小是值得研究的问题:大多数结构是等长的

对象可以被归还,但归还后并没有被删除,也没有被析构,而是仅仅进入freelist

一个pool中所有的bthread的栈必须一样大

ABA问题

A->B->A 通过加入版本号解决

A1->B2->A3

id通过 (32位偏移量 + 32位版本)决定 ,偏移量相当于指针

DoublyBufferData

背景:读远多于写的数据结构。一个方法是双缓冲,实现无锁查找1)数据分前台和后台2)检索线程只读前台,不用加锁3)另一个写线程:修改后台数据,切换前后台,睡眠一段时间,以确保老前台不再被检索线程访问

解决方案:读拿一把thread-local锁,写需要拿所有的thread-local锁。

1.数据分前台和后台

2.读拿到自己所在线程的thread-local锁,执行查询逻辑后释放

3.同时只有一个写,修改后台数据,切换前后台,挨个获得所有thread-local锁并立刻释放,结束后再改一遍新后台(保证之前的读线程所在的thread-local已结束,即所有读线程都看到了新前台)

健康检查

只要一个节点不从NamingService删除,它要么是正常的,要么在做健康检查

cpu profile

在定期被调用的SIGPROF handler中采用所在线程的栈,cpu profiler在运行一段时间后能以很大概率采集到所有活跃线程中的活跃函数,最后根据栈代表的函数调用关系汇总为调用图,并把地址转为符号

heap profiler

每分配满一些内存就采样被调用处的栈

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值