高性能中间件常用套路

高性能中间件常用套路

高性能分布式中间件是微服务架构下必要的一环,支撑起千亿流量,中间件种类不同,常用分布式中间件 MQ ,缓存等,对于其实现高性能思路做个简单总结

必备基础知识

分析现有的成型中间件,需要补充一下基础知识

  • TCP/IP 网络,三次握手四次挥手,网络拥塞控制,RecQ SendQ , linux内核层对于网络的实现原理
  • CPU工作原理,上下文切换,线程流水线指令并行排序等
  • 硬盘工作原理,读取写入原理(非固态硬盘)
  • 常见IO模型,BIO,NIO
  • java的NIO框架 netty,IO线程与逻辑线程分离,NIO事件多线程处理
  • 基本数据结构

中间件设计架构

中间件系统的架构设计,可以抽象为下图

高性能中间件常用套路_第1张图片

  • 1.分布式架构设计,由高可用的配置中心下发对应客户端的配置信心,配置中心的具体实现可以使 ZK REDIS ETCD 等
  • 2.在分布式场景下,为了实现高性能的调用,多采用客户端与服务端的点对点调用,其IP地址由配置中心下发,或由server端实现网关proxy代理
  • 3.为实现分布式中间件的高性能,此处需要处理两个问题
    • (1) 集群的负载均衡调度
    • (2) 对于服务端每个单点的高性能保证
  • 4.对于每个server节点,实现其高性能即优化单点的网络层,内存逻辑运算层,磁盘操作层

高性能NIO的server端

  • 对于优化单个server节点的网络层,多使用NIO方式,server端与client端在多次通讯的情况下使用TCP长连接维持会话
  • 常见例子为 redis epoll模型,RocketMq的netty模型

高效的Server端逻辑

  • 对于高性能Server节点,在处理好网络请求同时,还要保证server端逻辑可以快速执行完成,这就涉及到合理的数据结构与线程模型
  • 对于内存操作的线程分离,大部分中间件做法是将数据文件缓存与内存中,通过异步线程flush至硬盘
  • 合理的存储引擎对应不同的服务场景 B+树,hash,LSM
  • 对于消息队列,选取顺序读写磁盘的方式,可以高效的提升磁盘IO速度
  • 顺序写磁盘可以带来足够的写入速度,其读取方式为二分查找
    高性能中间件常用套路_第2张图片
    • 对于LSM存储引擎,同样采用顺序写磁盘方式,牺牲一部分读性能从而获得更优越的写性能

原生redis的瓶颈:

  • 单进程单线程, 无法充分发挥服务器多核cpu的性能.
  • 大流量下造成IO阻塞. 同样是由于单进程单线程, cpu在处理业务逻辑的时候,网络IO被阻塞住, 造成无法处理更多的请求.
  • 维护成本高, 如果想要充分发挥服务器的所有资源包括cpu, 网络io等, 就必须建立多个instance, 但此时不可避免会增加维护成本. 拿24核服务器举例来讲, 如果部署24个单机版的instance,理论上可以实现10w*24core= 240wQPS的总体性能.但是每个 instance 有各自独立的数据,占用资源如内存也会同比上升,反过来制约一台服务器又未必能支持这么多的 instance. 如果部署24个Instance来构成单机集群, 虽然可以共享数据,但是因为节点增加, redis的状态通讯更加频繁和费时,性能也下会降很多. 并且两种方式都意味着要维护24个Instance,运维成本都会成倍增加.

  • 持久化. redis提供了两种save方式 1)save触发. 2)bgsave. 当然也可以使用3)aof来实现持久化, 但是这3点都有弊端.

    • 1)save: 由于是单进程单线程, redis会阻塞住所有请求, 来遍历所有redisDB, 把key-val写入dump.rdb. 如果内存数据量过大, 会造成短时间几秒到几十秒甚至更长的时间停止服务, 这种方案对于twitter, taobao等大流量的网站, 显然是不可取的.
    • 2)bgsave: 在触发bgsave时, redis会fork自身, child进程会进入1)的处理方式,这意味着服务器内存要有一半的冗余才可以, 如今内存已变得越来越廉价, 但是对于存储海量数据的情况,内存以及服务器的成本还是不容忽视的.
    • 3)aof: 说到持久化, redis提供的aof算是最完美的方案了, 但是有得必有失, 严重影响性能! 因为redis每接收到一条请求, 就要把命令内容完整的写到磁盘文件, 且不说频繁读写会影响磁盘寿命,写磁盘的时间足以拖垮redis整体性能 . 当然熟悉redis的开发者会想到用appendfsync等参数来调整, 但都不是完美.即使使用 SSD,性能也只是略有提升,并且性价比不高。

针对以上几种情况, 阿里技术保障团队做了如下优化手段, 其实这不仅仅只是优化, 而更是一种对redis的改造.

  • 多线程master + N*work 工作模式.master线程负责监听网络事件, 在接收到一个新的连接后, master会把新的fd注册到worker的epoll事件中, 交由worker处理这个fd的所有读写事件, 这样master线程就可以完全被释放出来接收更多的连接, 同时又不妨碍worker处理业务逻辑和IO读写.

  • 采用这种master + N*worker的网络层事件模型,可以实现redis性能的平行扩展. 真正的让redis在面临高并发请求时可以丛容面对.

  • 抛弃save, bgsave, aof等三种模式.采用redisDB lock模式. AliRedis在数据存储层把多DB存储模式转换成HashDb存储, 将key hash到所有RedisDB上, 这样做有一个弊端就是抛弃了select命令, 但与此同时会带来一个更大的好处, 可以逐个DB持久化而不会影响整个系统, 在做持久化的时候AliRedis只需lock住1/N个redisDb, 占用1/m个线程. 在不需要内存冗余的情况下进行持久化, 相比之前提到的弊端, 这种方式可以带来更大的收益, 更丰厚的回报.
  • 重构hiredis客户端, 支持redis-cluster工作模式, 目前hiredis并不支持redis-cluster模式, 阿里技术保障团队对hiredis进行重构,使之支持redis-cluster.
  • 优化jemalloc, 采用大内存页. Redis在使用内存方面可谓苛刻至极, 压缩, string转number等, 能省就省, 但是在实际生产环境中, 为了追求性能, 对于内存的使用可以适度(不至于如bgsave般浪费)通融处理, 因此AliRedis对jemalloc做了微调, 通过调整pagesize来让一次je_malloc分配更多run空间来储备更多的用户态可用内存, 同时可以减轻换页表的负载, 降低user sys的切换频率, 来提高申请内存的性能, 对jemalloc有兴趣的开发者可以参考jemalloc源码中的bin, run, chunk数据结构进行分析.

你可能感兴趣的:(中间件)