OneProxy :: 优化Slave读流量分配算法,对后端MySQL数据库进行过载保护!

OneProxy的用户和客户中,有一半以上的场景里使用了读写分离功能,较多的场景配置一主两备或一主三备的架构,除了需要自动踢除复制时延较大的节点外,还会涉及到多个读节点之间如何分配流量的问题,即如何找到较合适的读节点来执行查询请求,使得最终整个集群保持较好的响应时间。在负载均衡设备或软件中常见的流量分配算法有以下几种:

  1. 随机算法(Random):采用软件随机算法产生一个整数,然后按后端节点数取模,找到对应的后端结点。这里最主要的问题是随机数并不能真正随机,在极端的时间内,基于时间算法产生的随机数可能完全相同,导致算法变成了按时间片轮询,并且无法考虑到后端的堵塞情况和设备能力差异,现在已经较少采用了。
  2. 轮询算法(Round Robin):维护一个序数,每接到一个请求,将序数加一,然后按后端节点数取模,找到对应的结点。与随机算法而言,此算法较有优势,可以在多个后端节点之间非常公平的分配流量,并且算法的实现成本和执行成本都极低。算法缺点是无法考虑到后端的堵塞情况和设备能力差异,如果后端有一个节点变慢了,则会在慢结点上持续堵塞。如果后端不会堵赛并且设备处理能力一致,则属于极好的负载均衡算法。
  3. 权重算法(Weighted):根据后端结点的处理能力(比如CPU、内存、硬盘等)不同,配置不同的流量比例,可以很好地解决设备能力之间的差异问题。但同样无法考虑到后端的瞬时异常情况
  4. 最少连接(Least Load):找到活跃连接数(也可以是Load值或CPU利用率)最小的后端结点,即找到当下最空闲的后端连接,来分配流量。可以较好地处理后端的堵塞情况和设备能力的差异问题,缺点是实现的算法较为复杂,并且执行成本较高。
  5. 内容算法(Content Based):根据上下文、请求来源(例如IP地址)或请求内容(SQL中的表名),计算Hash值,然后按后端节点数取模,找到对应的后端结点,一致性Hash算法也可归于此类。此算法的缺点是实现的算法较为复杂,并且执行成本较高,在通用的负载均衡场景中,此算法并不多见。

多个MySQL Slave结点之间的流量分配策略,最大的挑距在于SQL处理时间的不固定,有太多的因素会干扰MySQL中的SQL请求的执行效率,比如:

  1. SQL本身的复杂程度,这是排在第一位的因素。
  2. 更新操作或事务引发的锁等待情况。
  3. 数据在内存中的缓存情况。
  4. 硬盘等设备的处理能力,或者瞬时的抖动情况。
  5. 后端MySQL节点上其他程序的影响。
  6. 后端MySQL节点的软硬件差异,或参数配置差异。
  7. 网络距离的影响(例如多IDC分布)
  8. ……(数不胜数的因素)

因此单纯的随机算法或轮询算法特别不适合于数据库的流量分配,单纯的权重算法看起来合理,但也不够适合,因此最少连接数算法和内容算法是比较适合数据库的。OneProxy支持内容算法,比如可以按表名或分片名称进行流量分配,以便让不同的MySQL机器缓存不同部份的数据,达到内存的优化使用,基于内容的算法不在这里讨论。OneProxy也没有单纯地使用最少连接算法,觉得最少连接数每时每刻都在变化,最少并不具有绝对的意义,同时也觉得找到最少连接算法的成本比较高,而是采用了改进了的轮询算法,其逻辑设计如下:

  1. 设置一个安全的后端活跃连接数(high-session),在安全连接数以下无差别对待。
  2. 找到所有在安全活跃连接数以下的后端节点,如果找到则进行轮询算法,否则下一步。
  3. 找到所有可用状态的后端连接,进行轮询算法。

从线上真实客户场景的运行情况来看,达到了较好的效果,从总体上讲不同读节点之间处理的请求数非常公平,同时也能及时响应后端MySQL节点处理能力的瞬时变化,并且算法实现非常简单,执行极其高效,并能发挥一些权重算法的效用(当然OneProxy里还是可以配置后端节点权重的),自认为是一个比较好的适合数据库的流量分配算法。