上面讲的是我们编程模型的选择,下面我想分享一下怎么达到高性能。昨天有些演讲嘉宾也说了,现在英特尔已经发明 搞不定单 CPU 去赫兹一直 往上升,摩尔定律终不终结其实年夜 家心里都是有一个小小的问号了。就单 CPU 来看,它的性能已经很难再往上提了,但现在厂商怎么做呢,一个 CPU 尽可能 pack 更多的 CPU 进去,比如早些年我看这个24核、32核、64核感到 挺多了,现在 AMD 直接有两三百核来提升单机办事 器的性能,然则 我们在实践的时候却发明 我们在用这么高端、这么年夜 量 CPU 的机器的时候,性能却是没有呈线性提升的,就是在办事 器 Scale Up 之后,性能不是平行扩展的。
后来我们发明 一个比较年夜 的问题,之前也有很多年夜 牛说的一些Cache 问题,就是这个内存和 CPU 之间的高速通路其实是异常 拥堵的,特别在 CPU 变多了之后这条路就更堵了,所以在写高性能法度模范 的时候,Cache 异常 的重要。另外就是锁,当 CPU 变多了之后,这些锁之间的竞争开销会导致机器没办法完全施展 出硬件的性能。那现在业界算是公认比较好的能够解决现在单机性能瓶颈的一个模型就是 Shared-Nothing。每个 CPU 核上尽可能只干自力 完整的一件事,当然这个事情说起来比较简单。年夜 家写法度模范 都邑 发明 有很多需要共享,就是全局的一些变量,比如我的用户信息肯定是全局共享,弗成 能每一个 CPU 存一份,因为这样会导致很多不一致的情况。比如最简单的金融付费,一个用户付完费了,其他核心肯定也要看到这个结果,那么这就有一个问题:全局的器械 怎么办?
这个是 Shared-Nothing 简单的框架图,就是每个进程和线程之间都尽可能规避这个器械 。每个器械 做一个事情怎么做呢?其实我们现在网络层就是通过 Reuse Port 去做,异常 简单,直接这么去导就行了,通过这样我们网络收发就可以线性了,但全局变量就不可 ,那我们怎么做?就是有一些异步的进程去处理这一部分的能力,通过 Reuse Port,网络部分有自力 的线程去收发,但异常 重、异常 庞杂 的器械 就通过自力 的 worker 去处理这些全局的内容,但在这个进程 中是没有锁的,中间会通过无锁队列去传递我想要的信息。比如我需要获取一个用户的信息,通过无锁队列这边网络收发完之后,再向这边发送一个消息,之后全局的数据处理完之后再通过无锁队列返回来,这样中间是没有任何的锁和增强的开销的,这样就达到全局数据的共享,这就是我们解决全局数据的办法 理念。
但如果读取异常 频繁的情况下怎么去解决呢?我们在每一个收发线程上还会做一些 ThreadLocal 的快照,发曩昔 之后拿回来的器械 在本地做缓存,每个线程上去做缓存,这样就解决了需要频繁读写的问题。熟悉 Kernel Reuse Port 的同学可能知道 Reuse Port在处理很多 case 的时候是没办法完整地连续 分发的,它只会依据IP 和 Port 去做。但这有一个问题是,比如现在在4G和wifi之间做切换,IP 和 Port 会变对吧,那变了之后再去做分发的时候,做Shared-Nothing 很重要的一个事情是用户请求一定要转发到同一个线程进程里面。那如果网络一变,它通过 Reuse Port 的方法 就不会转发到同一个里面去了,那这种问题在正常 TCP 链接的部分不会涌现 ,因为 TCP 就是依据IP 和 Port去界说 链接的,但在新的一些业务场景比如 Quic、UDP,会涌现 一个问题:它不是通过 IP 和 Port 来界说 一个链接,而是通过协议自带的信息去做,所以在原有的内核的 Reuse Port 方法 就完全失效了,没办法做 Shared-Nothing,于是我们在中间内核做了一些模块,依据 一些 UDP 协议信息做转发,选择一些 ID 去做。这也是我们在做 Shared-Nothing 时遇到的链接转发的一个问题。