Redis 作为最受欢迎的 NoSQL 数据库之一,具备高性能、高可用性、高扩展性等特点,在各互联网业务中使用广泛。目前业界针对 Redis 的性能优化主要针是配置项优化以及使用方式的优化。本文介绍尝试撇开 Redis 本身,而从通用的协议栈层面来做优化,这种优化方式理论上可推广到其他 Socket 类互联网应用,如 Memcached、Ngnix、Envoy 等。
分 析
Redis-server 作为一个标准的 Socket 类应用,会通过监听地址端口接收来自客户端的连接,连接建立后会读取连接上的客户端请求,处理后再返回响应给客户端,这其中的连接建立、请求读取、响应返回都是通过内核的 TCP/IP 协议栈来处理的。可以通过火焰图先看一下 Redis-server 在性能压测下的 CPU 消耗情况。
图中,是在客户端读请求压测的时候抓取的火焰图信息。可见,内核态协议栈所占用的 CPU 消耗较大,其中以 sys_write 为主,占比 40% 左右。所以,如果能对这部分 CPU 占用进行优化,收益还是非常可观的。
那么这部分 CPU 占比如何进行优化呢?最好还能做到 Redis 应用本身完全无感知。
协议栈的处理完全省掉是不现实的,这样底层 TCP 通信就玩不转了。但是我们可以考虑将这部分处理剥离出去,不占用 Redis 的 CPU。
那剥离出去的协议栈实现放在哪儿呢?
可以放到一个单独的进程中实现。那这样是不是和剥离前没有区别?
No!因为一台机器上一般会启动多个 Redis 实例,多个 Redis 实例在这种情况下就可以共享这个协议栈实现的进程。相当于将 Redis 和协议栈的 1:1 绑定部署关系,变为 N:1 的独立部署关系。
那这个协议栈实现进程的性能就非常重要了,绝对不能成为瓶颈,否则会导致最终的性能没有提升,甚至更糟。具体如何实现呢?
下面该轮到用户态协议栈出场了!
优 化
用户态协议栈介绍
顾名思义,用户态协议栈是将原本在内核态实现的 TCP/IP 协议栈移到用户态实现的技术。放到用户态实现可以带来几大好处:
1. 高性能
Redis 本身是一个用户态的应用程序,调用内核态的 TCP/IP 协议栈实现,不可避免地会带来用户态和内核态的上下文切换开销。另外,最重要的一点,内核协议栈和应用绑定在一起,无法做到和应用在资源占用上剥离,也就是前面所述的独立部署。
2. 易调测
做过内核态开发的同学应该都知道,内核下程序的调测还是比较痛苦的,动不动给你来个 Oops 就会导致内核挂死。放到用户态实现调测起来就会方便很多。