当前位置:   article > 正文

记一次shardingsphere自动更新datasource引发的生产故障_abstractroutingdatasource 和shardingsphere 冲突

abstractroutingdatasource 和shardingsphere 冲突

背景

公司的项目大部分通过shardingsphere编排治理的配置中心管理数据分片规则和数据源配置,参见:ShardingSphere编排治理。最近有个需求需要做数据迁移,为了确保新数据源没问题采用了双数据源动态切换的灰度方案,但上线当天却因为shardingsphere的自动更新出现了生产故障。。。

shardingsphere版本:4.1.1

动态切换实现方案

因业务合并,原有数据源A的数据需要迁移到数据源B,大概思路如下:

为了稳定性需要支持动态切换以及按租户切换:

动态数据源实现

首先注册一个动态数据源,根据特定的key访问特定数据源

  1. // 数据源配置类
  2. @Configuration
  3. public class DataSourceConfig {
  4. /**
  5. * 原编排治理的配置中心的名称
  6. */
  7. @Value("${spring.shardingsphere.orchestration-name}")
  8. private String springShardingsphereOrchestrationName;
  9. /**
  10. * 新编排治理的配置中心的名称
  11. */
  12. @Value("${spring.shardingsphere.orchestration-name.new}")
  13. private String newSpringShardingsphereOrchestrationName;
  14. private static final CenterRepositoryConfigurationYamlSwapper CONFIGURATION_YAML_SWAPPER = new CenterRepositoryConfigurationYamlSwapper();
  15. /**
  16. * 创建数据源
  17. * @param conf
  18. * @param name
  19. * @return
  20. */
  21. @SneakyThrows
  22. public static DataSource createDataSource(YamlCenterRepositoryConfiguration conf, String name) {
  23. Map<String, CenterConfiguration> instanceConfigurationMap = Maps.newHashMapWithExpectedSize(1);
  24. instanceConfigurationMap.put(name, CONFIGURATION_YAML_SWAPPER.swap(conf));
  25. OrchestrationConfiguration orchestrationConfiguration = new OrchestrationConfiguration(instanceConfigurationMap);
  26. return OrchestrationShardingDataSourceFactory.createDataSource(orchestrationConfiguration);
  27. }
  28. /**
  29. * 注册动态数据源
  30. * @param properties
  31. * @return
  32. */
  33. @Bean
  34. public DataSource dataSource(SpringBootRootConfigurationProperties properties) {
  35. log.info("=========初始化灰度数据源开始===========");
  36. // 初始化新旧数据源
  37. YamlCenterRepositoryConfiguration conf = properties.getOrchestration().get(this.springShardingsphereOrchestrationName);
  38. final DataSource oldDataSource = createDataSource(conf, this.springShardingsphereOrchestrationName);
  39. final DataSource newDataSource = createDataSource(conf, this.newSpringShardingsphereOrchestrationName);
  40. // 初始化动态数据源
  41. DynamicDataSource dataSource = new DynamicDataSource();
  42. Map<Object, Object> dataSourceMap = Maps.newHashMap();
  43. dataSourceMap.put("OLD_SOURCE", oldDataSource);
  44. dataSourceMap.put("NEW_SOURCE", newDataSource);
  45. dataSource.setTargetDataSources(dataSourceMap);
  46. dataSource.setDefaultTargetDataSource(oldDataSource);
  47. log.info("=========初始化灰度数据源完成===========");
  48. return dataSource;
  49. }
  50. }

自定义的动态数据源类继承AbstractRoutingDataSource,重写获取数据源的方法

  1. public class DynamicDataSource extends AbstractRoutingDataSource {
  2. @Override
  3. protected Object determineCurrentLookupKey() {
  4. String dataSourceKey = DataSourceContextHolder.getDataSourceKey();
  5. log.info("[determineCurrentLookupKey] dataSourceKey:{}", dataSourceKey);
  6. return dataSourceKey;
  7. }
  8. }

而特定的key则利用ThreadLocal实现动态切换数据源,为了支持异步线程也能继承数据源使用了阿里开源的TransmittableThreadLocal

  1. public class DataSourceContextHolder {
  2. private static final ThreadLocal<String> DATASOURCE_KEY_HOLDER = new TransmittableThreadLocal<>();
  3. public static void setDataSourceKey(String dataSourceKey) {
  4. DATASOURCE_KEY_HOLDER.set(dataSourceKey);
  5. }
  6. public static String getDataSourceKey() {
  7. return DATASOURCE_KEY_HOLDER.get();
  8. }
  9. public static void clearDataSourceKey() {
  10. DATASOURCE_KEY_HOLDER.remove();
  11. }
  12. }

动态数据源切换

通过AOP无侵入增加灰度切换,方便随时变更灰度范围

  1. @Aspect
  2. @Component
  3. @Slf4j
  4. public class DataSourceGrayHttpAspect {
  5. @Pointcut("execution(* com.web.*.*(..))")
  6. public void dataSourcePointcut() {
  7. }
  8. @Before("dataSourcePointcut()")
  9. public void beforeMethod(JoinPoint joinPoint) {
  10. Long appId = getAppId();
  11. // 根据租户计算使用的数据源
  12. DataSourceContextHolder.setDataSourceKey("NEW_SOURCE");
  13. }
  14. @After("dataSourcePointcut()")
  15. public void afterMethod() {
  16. DataSourceContextHolder.clearDataSourceKey();
  17. }
  18. }

这里有个关键点(记住,后面会提!):本来AOP切到具体表的Mapper中,方便控制哪些表参加灰度,但由于获取动态数据源(determineCurrentLookupKey)是在事务开始前获取,若代码里使用了显式事务(如@Transactional ),则在切面之前就确定了数据源,之后的计算已没意义

一切准备就绪后,正式进入正题

 故障过程

由于上面提到的关键点,第一次发版后发现需要改到在Controller层就计算数据源,因此进行第二次发版,此时服务已经同时连接两个数据源,但新数据源并没有任何流量

出现故障

在发版之前需要在新的shardingsphere编排治理配置中心增加表配置,想着新数据源没有流量就打算直接修改,结果刚刚改完还没开始发布,告警群突然出现大量告警

原来是新配置写错规则了!但当时一脸懵B,还没发布呀 ,怎么就生效了呢?为了及时止血赶紧回滚配置,结果回滚后出现更多的告警。。。

已经痛不欲生的我只好赶紧重启服务,故障才终于解决!

故障原因分析

从以上经过可以看出,出现故障的直接原因有两个:

  • 配置错误
  • 配置实时生效
  • 数据源被关闭

到底配置是怎么实时生效,数据源又为什么会被关闭了呢?带着问题我翻查了sharding官网,结果发现编排治理的配置中心确实是支持动态生效的,参见:ShardingSphere 配置中心说明,那他又是如何实现呢?果然让我找到一个listener调用了dataSource的renew方法

  1. // org.apache.shardingsphere.shardingjdbc.orchestration.internal.datasource.OrchestrationEncryptDataSource
  2. public class OrchestrationEncryptDataSource extends AbstractOrchestrationDataSource {
  3. /**
  4. * Renew encrypt rule.
  5. *
  6. * @param encryptRuleChangedEvent encrypt configuration changed event
  7. */
  8. @Subscribe
  9. @SneakyThrows
  10. public final synchronized void renew(final EncryptRuleChangedEvent encryptRuleChangedEvent) {
  11. dataSource = new EncryptDataSource(
  12. dataSource.getDataSource(), new EncryptRule(encryptRuleChangedEvent.getEncryptRuleConfiguration()), dataSource.getRuntimeContext().getProperties().getProps());
  13. }
  14. }

通过本次debug,刚修改了sharding配置后,sharding会重建所有应用内的dataSource

因此可以解释出为何会配置实时生效,而出现数据源被关闭的原因是新数据源的配置覆盖了原数据源,导致需要访问原数据源时就提示被关闭

真正的原因

由于特殊需求我们自定义了dataSource,其中包含多个sharding dataSource的实例,当配置变更后sharding是无法区分应该重建哪个实例,只能全部重建(本来使用了sharding编排治理的配置中心就可以自定义对应的数据源,可能他们也不知道有这样的场景);但个人认为如果不是使用默认创建的dataSource实例就应该把实时生效的listener关闭掉,避免修改不属于自己范围的内容

避免故障

清楚了原因后,避免故障的方案有两个:

  • 每次变更配置均新建sharding编排治理的配置中心,更换nacos名称
  • 魔改sharding代码,禁用动态生效功能

最终因配置变更频率低,我们采用了方案二

总结

从本次故障得出经验,在使用开源框架时必须多查阅其官网手册,基本可以避免大部分的坑;对于不确定的事情需要多做测试,多想后果,多准备预案,不要等到出现问题时自乱阵脚!

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/天景科技苑/article/detail/757121
推荐阅读
相关标签
  

闽ICP备14008679号