本文最后更新于 2024-12-13,文章内容可能已经过时。

微服务 Redis 集群迁移中序列化问题分析及解决方案

1. 事情经过

在我们最初使用单节点 Redis 的时候,尽管各微服务采用了不同的序列化方式来处理缓存到 Redis 中的 token,但由于数据流量小、并发度低以及 key 空间较为独立等因素,这些问题并未显现。然而,随着业务的发展和技术架构的优化,我们决定将 Redis 升级为集群模式以提高性能和可用性。在迁移到 Redis 集群模式后,由于不同服务对相同 key 的 token 使用了不同的序列化方式,导致了一系列兼容性和一致性问题。

1.1 序列化方式一(value采用默认方式)

   @Bean
    public RedisTemplate<Object, Object> redisTemplate(
        RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);
        template.setKeySerializer(new StringRedisSerializer());
        return template;
    }

1.2 序列化方式二(value采用Jackson)

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);
        Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper mapper = new ObjectMapper();
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        serializer.setObjectMapper(mapper);
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(serializer);
        template.afterPropertiesSet();
        return template;
    }

2. 产生原因

2.1 缺乏统一标准

  • 在设计初期没有为所有微服务定义统一的序列化格式,这使得每个服务可以根据自己的需求选择最适合它的序列化方法。
    2.2 单一入口变为多点分布
  • 从单节点 Redis 迁移到集群模式后,key 被哈希并分配到了多个节点上,导致相同 key 可能由不同服务写入不同节点,增加了不同序列化方式冲突的可能性。
    2.3 跨服务依赖增加
  • 集群模式下,服务间的交互变得更加复杂,尤其是在共享同一套 key-value 对的情况下,不同的序列化方式会导致数据难以互认,进而产生一致性问题。
    2.4 故障转移和负载均衡Redis
  • 集群支持故障转移和负载均衡,可能导致某个服务突然从一个节点切换到另一个节点获取数据,进一步加剧了不同序列化方式带来的挑战。

3. 处理办法

为了有效解决上述问题,并确保未来类似情况不再发生,我们采取了以下措施:

3.1 制定并实施统一规范

  • 确定一个所有服务都遵循的序列化格式(如 JSONProtocol Buffers MessagePack),并在整个组织内推广。
  • 规范 token 的生成、存储、读取和验证过程,确保所有相关操作都是标准化的。

3.2 加强沟通与协作

  • 建立跨团队的技术评审机制,确保涉及共享资源的操作得到所有相关方的认可。
  • 定期举办内部培训和技术分享会,提升团队成员对最佳实践的理解。

3.3 引入中间件或专用服务

  • 构建一个专门负责管理 token 的服务,它可以作为所有微服务访问 Redis 的代理,统一处理序列化/反序列化逻辑。
  • 使用成熟的开源项目或商业解决方案来简化这一过程,减少自研带来的风险。

3.4 优化缓存策略

  • 对于 token 这样的敏感信息,考虑采用更安全的存储方式,如加密后的本地缓存或者专用的安全令牌服务。
  • 在设计缓存方案时,考虑到分布式环境下的高可用性和容错性,避免单点故障。

3.5 文档记录

  • 在集成测试阶段加入针对不同服务间交互的测试用例,特别是涉及到共享数据的部分。
  • 使用混沌工程方法论定期进行系统稳定性测试,提前发现潜在问题。

3.6 逐步迁移

  • 详细记录所选序列化格式的具体实现细节以及任何特殊注意事项。
  • 维护清晰的服务接口文档,包括输入输出参数的格式说明,帮助其他团队正确使用。

3.7 逐步迁移

  • 如果直接转换到集群模式风险较大,可以考虑分阶段迁移,先将部分服务迁移到集群,观察其行为,并根据实际情况调整序列化方式或其他配置。

3.8 加强监控和日志记录

  • 密切监控系统的运行状态,及时发现并解决由于序列化不一致引起的问题。
  • 详细的日志可以帮助快速定位问题源。

3.9 测试环境模拟

  • ​ 在正式迁移前,构建一个与生产环境相似的测试环境来模拟集群模式下的操作,确保所有服务能够正确处理共享资源。