前言

这段时间一直在思考实现一个针对HTTP协议的LB该考虑哪些要素。其中一个特性令我深思,那就是会话持久(session persistence)该如何保证?
之所以关注该特性,那必然是因为缓存的信息多是和用户、业务直接相关的,一旦缓存失效,那么就会导致用户体验下滑,比如莫名其妙地回到了登录界面;另外,再次缓存也会导致对性能等因素造成负面影响。
总之,各种意义上来说,不能说是个可以忽视的特性。

一致性哈希

要实现均衡负载中会话持久,单纯地依赖于任何特定均衡算法(bancing algorithm),我觉得是不可能的。
即使是一致性哈希(consistent hashing),在集群增加或移除节点时,也终究会导致部分节点被重定向,这样下一次的请求会发送到不同于原来节点的新节点上去,这样原来的节点上的缓存信息也就失效了。
假设我们硬是要用一致性哈希的话,那要应对缓存失效可能就需要一个app server将session缓存数据转移到其他app server上去,而这对app server又做出了新要求,即耦合了app server的代码。显然,对于不同的app server来说这就是个折磨,相当于自己做类似分片管理的工作。要运用一致性哈希的系统,我觉得应该是分布式web缓存(即最初的用意)或是内存型数据库(比如amazon的dynamo)。

当然,由于我对web不是太懂,以上的对于永不过期的session来说确实会造成一定的影响。但是实际上是,session信息作为一种缓存,通常也会设置其有效期,因此即使出现部分缓存失效,是可以接受的。

后端缓存

除此之外,我们还能考虑在后端数据库的“后端”(套娃)弄一个内存缓存(e.g. redis,memcached),这样使app server成为无状态的(stateless)。除此之外,由于缓存与服务器分离,因此服务器即使崩溃重启(如果有replication,往往是比较少见的)或者集群变化等均不会引起session丢失。从这点来说,该方案可能会适合做横向扩展。

session_persistence_cache.png

路由缓存

如果对于会话持久十分严格,还可以在LB层缓存路由信息,这样即使集群变化,路由表不变的话,亦然可以路由到原来的服务器。
如果空间足够的话,考虑本机缓存即可;不然,还是弄个分离的缓存服务器比较好。
那么路由表中的数据是不变的吗?这个我认为是需要进行清理的,比如很久之前的路由信息很明显可以清除。换言之,需要支持限制缓存大小和设置过期时间,那么采用支持这类功能的内存缓存比较好,比如redis。
另外,还有一种可能的方案,就是采用Session Cookie,感兴趣可以看下[1]

总结

  • 一致性哈希(session存在有效期且部分重定向是可接受的)

    • Pros:
      • 不需要调整app server代码
      • 重定向的节点只有两个节点(新节点和指针方向的下一个节点)
    • Cons:
      • 弱会话持久性,不能保证重定向的请求已过期
      • 实现一致性哈希,比如增设虚拟节点,是比较tricky的工作
  • 后端缓存

    • Pros:
      • app server不需要缓存和维护状态
      • 将缓存与app server分离,使app server不受集群影响
      • 缓存系统可能支持集群扩容等功能,某种程度上增强了灵活性
    • Cons:
      • app server代码需要进行一定的调整
      • 多一次中间层开销(包括网络IO开销和缓存本身处理请求的开销)
  • 路由缓存

    • Pros:
      • 强会话持久性,即使采用不确定性均衡算法
      • 原来的节点路由不受集群影响
      • 不需要调整app server代码
    • Cons:
      • 在LB层付出开销,可能影响路由性能。具体来说,如果是本地存储元数据,那么可能占用过多的空间;反之,如果是采用分离的内存缓存,那么需要多一次中间层开销。

reference

load-balancing-affinity-persistence-sticky-sessions-what-you-need-to-know
分布式session解决方案与一致性hash