[单排日记][应用无状态]

应用无状态

业务场景:公司自建代理服务进行 IP 轮换。处理业务问题时,发现即使是同一个用户,连续访问时代理 IP 也在持续变化。检查代码后发现了一些问题,主要涉及应用无状态这一设计思想。

为什么需要无状态及如何实现

以当前业务为例,需要维护一个可用的 IP 池,设计上应该尽量满足均衡使用,IP 不可用时更换或等待再次可用。均衡使用与无状态关系不大,这里主要关注 IP 可用性的维护。

一个简单的做法是定义一个数组,数组元素是 IP 对象,为每个 IP 对象添加一个属性标记是否可用。这种做法是有状态的应用,每个 IP 的状态都保留在应用实例内。如果业务量不大且是单实例运行,这种方式是可行的。

实际上,目前应用开发普遍采用集群部署。为了保证服务的高可用,生产环境一般会通过 Kubernetes 或 Swarm 配置多个应用副本,既可以分流,又可以保证在部分副本出现问题时,仍有副本可以正常提供服务(同时等待集群启动新的替换副本)。

因此,如果设计了有状态的应用,一个主要问题是:同一个应用的不同副本之间,状态不方便直接同步。以本例为例,实际上部署了双副本,那么用户访问时:

  1. 第一次访问到实例 A,发现 IP 被封,实例 A 将自己维护的 IP 状态改为不可用
  2. 第二次访问时:
    • 如果再次访问到实例 A,该失效 IP 不会给用户,是正确的
    • 如果访问到实例 B,实例 B 并不知道这个 IP 已失效,仍然分配了这个 IP,导致功能失效

而这个分流是随机的,如果没有提前考虑,后续随机出现问题,排查起来会比较困难。

常用的解决方案是:将需要保存的状态存放在 Redis 中,应用保持无状态,需要时从 Redis 读写。

当然后续也有高并发下状态一致性的问题,解决方案需要根据具体业务需求决定。当前业务只要求大致准确、失败时最终检出,因此没有加锁,直接先读判断,布尔值取反,优先改为 false 即可。其他业务需根据自身需求选择合适的方案。