宋顺 配置中心,让微服务更『智能』

1. 配置中⼼心,让微服务更更『智能』 宋顺 携程框架架构研发部技术专家
3. 携程框架架构研发部技术专家 2016年年初加⼊入携程,负责中间件产品的研发⼯工作 开源配置中⼼心Apollo主要作者 https://github.com/ctripcorp/apollo 毕业于复旦⼤大学软件⼯工程系,曾就职于⼤大众点评, 担任后台系统技术负责⼈人
4. 1. 为什什么需要配置中⼼心? 2. 配置中⼼心的⼀一般模样 3. 如何让微服务更更『智能』? 4. 配置中⼼心的最佳实践
5. 1. 为什什么需要配置中⼼心? 2. 配置中⼼心的⼀一般模样 3. 如何让微服务更更『智能』? 4. 配置中⼼心的最佳实践
6. 配置即『控制』
7. 程序的发布其实和卫星的发射有⼀一些相似之处 卫星发射升天后 • 处于⾃自主驾驶状态,按照预设的轨道运⾏行行 • 间歇可收到地⾯面的『控制』信号对运⾏行行姿态进⾏行行调整 程序发布到⽣生产环境后 • 按照预设的逻辑运⾏行行 • 通过调整配置参数来动态调整程序的⾏行行为 • 这些配置参数就代表着我们对程序的『控制』信号 图⽚片来源:https://www.popularmechanics.com/space/a7194/how-it-works-nasas-experimental-laser-communication-system/
8. 配置需要治理理
9. 权限控制、审计⽇日志 灰度发布、配置回滚 不不同环境、集群管理理
10. 微服务的复杂性
11. 单体应⽤用时代 • 应⽤用数量量少 • 配置简单 • 运维可以登机器器修改配置⽂文件 微服务时代 • 应⽤用数量量多 • 配置数量量也急剧增⻓长 • ⼈人⼯工登机器器修改不不仅效率低,还容易易出错 图⽚片来源:https://www.nginx.com/blog/introduction-to-microservices/
12. 需要⼀一个统⼀一的配置中⼼心来管理理微服务的配置!
13. 1. 为什什么需要配置中⼼心? 2. 配置中⼼心的⼀一般模样 3. 如何让微服务更更『智能』? 4. 配置中⼼心的最佳实践
14. 1. 为什什么需要配置中⼼心? 2. 配置中⼼心的⼀一般模样(以开源配置中⼼心Apollo为例例) 3. 如何让微服务更更『智能』? 4. 配置中⼼心的最佳实践
15. 治理理能⼒力力
16. 01 统⼀一管理理不不同环境、不不同集群的配置 ⽀支持灰度发布 02 03 ⽀支持已发布的配置回滚 完善的权限管理理、操作审计⽇日志 04
18. 可⽤用性
19. 配置即『控制』 • 所以在⼀一定程度上,配置中⼼心已经成为了了微服务的⼤大脑 • 作为⼤大脑,可⽤用性要求显然是⾮非常⾼高的
20. Apollo at a glance
21. 服务端⾼高可⽤用
22. 客户端⾼高可⽤用
23. 可⽤用性场景举例例
24. 实时性
25. 配置即『控制』,所以我们希望我们的控制指令能迅速、准确地传达到应⽤用程序 图⽚片来源:http://toysoxo.com/toysoxo-shipping-delivery-informaton/
26. 配置发布的过程
27. 发送ReleaseMessage的实现⽅方式
28. 1. 为什什么需要配置中⼼心? 2. 配置中⼼心的⼀一般模样 3. 如何让微服务更更『智能』? 4. 配置中⼼心的最佳实践
29. 开关
30. 发布开关 • 发布开关⼀一般⽤用于发布过程中,⽐比如: 1. 有些新功能依赖于其它系统的新接⼝口,⽽而其它系统的发布周期未必和⾃自⼰己的系统⼀一致, 可以加个发布开关,默认把该功能关闭,等依赖系统上线后再打开。 2. 有些新功能有较⼤大⻛风险,可以加个发布开关,上线后⼀一旦有问题可以迅速关闭 • 需要注意的是,发布开关应该是短暂存在的(1-2周),⼀一旦功能稳定后需要及时清除开关代 码。
31. 实验开关 • A/B测试 • 针对特定⽤用户应⽤用新的推荐算法 • 针对特定百分⽐比的⽤用户使⽤用新的下单流程 • 功能验证 • 有些重⼤大功能已经对外宣称在某年年某⽇日发布 • 可以事先发到⽣生产环境,只对内部⽤用户打开,测试没问题后按时对 全部⽤用户开放 • 实验开关应该也是短暂存在的,⼀一旦实验结束了了需要及时清除实验开关代码。 图⽚片来源:https://www.search-factory.cn/ab-testing/, http://www.businesses.com.au/marketing/418276-top-4-reasons-to-hire-promo-staff-for-your-product-launch-
32. 运维开关 • 运维开关通常⽤用于提升系统稳定性,⽐比如: 1. ⼤大促前可以把⼀一些⾮非关键功能关闭来提升系统容量量 2. 当系统出现问题时可以关闭⾮非关键功能来保证核⼼心功能正常⼯工作 • 运维开关可能会⻓长期存在,⽽而且⼀一般会涉及多个系统,所以需要提前规划。
33. 服务治理理
34. 限流 正常的⾼高速公路路 图⽚片来源:https://uspirg.org/sites/pirg/files/cpn/USN-041817-A3-REPORT/highway-boondoggles-3.html
35. 限流 超出容量量的⾼高速公路路 图⽚片来源:https://abcnews.go.com/International/thousands-cars-stuck-beijing-traffic-jam-50-lane/story?id=34350370
36. 限流 • 服务就像⾼高速公路路⼀一样 • 在正常情况下⾮非常通畅 • 不不过⼀一旦流量量突增(⽐比如⼤大促、遭受DDOS攻击)时,如果没有做好限流,就会导致系统整个 被冲垮,所有⽤用户都⽆无法访问。 • 所以我们需要限流机制来应对此类问题 • ⼀一般的做法是在⽹网关或RPC框架层添加限流逻辑,结合配置中⼼心的动态推送能⼒力力实现动态调 整限流规则配置。
37. ⿊黑⽩白名单 • 对于⼀一些关键服务,哪怕是在内⽹网环境中⼀一般也会对调⽤用⽅方有所限制,⽐比如: 1. 有敏敏感信息的服务可以通过配置⽩白名单来限制只有某些应⽤用或IP才能调⽤用 2. 某个调⽤用⽅方代码有问题导致超⼤大量量调⽤用,对服务稳定性产⽣生了了影响,可以通过配置⿊黑名单来暂 时屏蔽这个调⽤用⽅方或IP • 所以我们需要⿊黑⽩白名单机制来应对此类问题 • ⼀一般的做法是在RPC框架层添加⿊黑⽩白名单逻辑,结合配置中⼼心的动态推送能⼒力力来实现动态调整⿊黑 ⽩白名单配置。
38. 数据库迁移 • ⽐比如:原来使⽤用的SQL Server,现在需要 迁移到MySQL,这种情况就可以结合配置 中⼼心来实现平滑迁移 • 右图中的读写开关和⽐比例例配置都可以通过 配置中⼼心实现动态调整
39. 动态⽇日志级别 • 服务运⾏行行过程中,经常会遇到需要通过⽇日志来排查定位问题的情况,然⽽而这⾥里里 却有个两难: 1. 如果⽇日志级别很⾼高(如:ERROR),可能对排查问题也不不会有太⼤大帮助 2. 如果⽇日志级别很低(如:DEBUG),⽇日常运⾏行行会带来⾮非常⼤大的⽇日志量量,造成系统性能下降 • 为了了兼顾性能和排查问题,我们可以借助于⽇日志组件和配置中⼼心实现动态调整 ⽇日志级别。
40. 动态⽇日志级别 以Spring Boot和Apollo结合为例例: @ApolloConfigChangeListener private void onChange(ConfigChangeEvent changeEvent) { refreshLoggingLevels(changeEvent.changedKeys()); } private void refreshLoggingLevels(Set<String> changedKeys) { boolean loggingLevelChanged = false; for (String changedKey : changedKeys) { if (changedKey.startsWith("logging.level.")) { loggingLevelChanged = true; break; } } if (loggingLevelChanged) { // refresh logging levels this.applicationContext.publishEvent(new EnvironmentChangeEvent(changedKeys)); } } 详细样例例代码可以参考:https://github.com/ctripcorp/apollo-use-cases/tree/master/spring-cloud-logger
41. 动态⽹网关路路由 • ⽹网关的核⼼心功能之⼀一就是路路由转发 • 其中的路路由信息也是经常会需要变化的 • 可以结合配置中⼼心实现动态更更新路路由信息 图⽚片来源:https://www.lunchbadger.com/vs-amazon-aws-api-gateway-express-pricing/
42. 动态⽹网关路路由 以Spring Cloud Zuul和Apollo结合为例例: @ApolloConfigChangeListener public void onChange(ConfigChangeEvent changeEvent) { boolean zuulPropertiesChanged = false; for (String changedKey : changeEvent.changedKeys()) { if (changedKey.startsWith("zuul.")) { zuulPropertiesChanged = true; break; } } if (zuulPropertiesChanged) { refreshZuulProperties(changeEvent); } } private void refreshZuulProperties(ConfigChangeEvent changeEvent) { // rebind configuration beans, e.g. ZuulProperties this.applicationContext.publishEvent(new EnvironmentChangeEvent(changeEvent.changedKeys())); // refresh routes this.applicationContext.publishEvent(new RoutesRefreshedEvent(routeLocator)); } 详细样例例代码可以参考:https://github.com/ctripcorp/apollo-use-cases/tree/master/spring-cloud-zuul
43. 动态数据源 • 数据库是应⽤用运⾏行行过程中的⼀一个⾮非常重要的资源,承担了了⾮非常重要的⻆角⾊色。 • 在运⾏行行过程中,我们会遇到各种不不同的场景需要让应⽤用程序切换数据库连接,⽐比如:数据库维护、 数据库宕机主从切换等。 • 切换过程如下图所示:
44. 动态数据源 以Spring Boot和Apollo结合为例例: @Configuration public class RefreshableDataSourceConfiguration { @Bean public DynamicDataSource dataSource(DataSourceManager dataSourceManager) { DataSource actualDataSource = dataSourceManager.createDataSource(); return new DynamicDataSource(actualDataSource); } } public class DynamicDataSource implements DataSource { private final AtomicReference<DataSource> dataSourceAtomicReference; public DynamicDataSource(DataSource dataSource) { dataSourceAtomicReference = new AtomicReference<>(dataSource); } // set the new data source and return the previous one public DataSource setDataSource(DataSource newDataSource){ return dataSourceAtomicReference.getAndSet(newDataSource); } @Override public Connection getConnection() throws SQLException { return dataSourceAtomicReference.get().getConnection(); } …… }
45. 动态数据源 @ApolloConfigChangeListener public void onChange(ConfigChangeEvent changeEvent) { boolean dataSourceConfigChanged = false; for (String changedKey : changeEvent.changedKeys()) { if (changedKey.startsWith("spring.datasource.")) { dataSourceConfigChanged = true; break; } } if (dataSourceConfigChanged) { refreshDataSource(changeEvent.changedKeys()); } } private synchronized void refreshDataSource(Set<String> changedKeys) { try { // rebind configuration beans, e.g. DataSourceProperties this.applicationContext.publishEvent(new EnvironmentChangeEvent(changedKeys)); DataSource newDataSource = dataSourceManager.createAndTestDataSource(); DataSource oldDataSource = dynamicDataSource.setDataSource(newDataSource); asyncTerminate(oldDataSource); } catch (Throwable ex) { logger.error("Refreshing data source failed", ex); } } 详细样例例代码可以参考:https://github.com/ctripcorp/apollo-use-cases/tree/master/dynamic-datasource
46. 1. 为什什么需要配置中⼼心? 2. 配置中⼼心的⼀一般模样 3. 如何让微服务更更『智能』? 4. 配置中⼼心的最佳实践
47. 公共组件的配置 • 公共组件是指那些发布给其它应⽤用使⽤用的客户端代码,⽐比如RPC客户端、DAL客户端等。 • 这类组件⼀一般是由单独的团队(如中间件团队)开发、维护,但是运⾏行行时是在业务实际应⽤用 内的,所以本质上可以认为是应⽤用的⼀一部分。 • 这类组件的特殊之处在于⼤大部分的应⽤用都会直接使⽤用中间件团队提供的默认值,少部分的应 ⽤用需要根据⾃自⼰己的实际情况对默认值进⾏行行调整。
48. 公共组件的配置 • ⽐比如数据库连接池的最⼩小空闲连接数量量(minimumIdle),出于对数据库资源的保护,DBA要求将全 公司默认的minimumIdle设为1,对⼤大部分的应⽤用可能都适⽤用,不不过有些核⼼心/⾼高流量量应⽤用可能觉得太 ⼩小,需要设为10。 dal.properties (公共Namespace) DAL组件在A应⽤用中读取到的dal.properties配置 minimumIdle = 1 maximumPoolSize = 20 minimumIdle = 1 maximumPoolSize = 20 minimumIdle = 10 应⽤用B的dal.properties (继承⾃自公共Namespace) minimumIdle = 10 maximumPoolSize = 20 DAL组件在B应⽤用中读取到的dal.properties配置 • 通过这种⽅方式的好处是不不管是中间件团队,还是应⽤用开发,都可以灵活地动态调整公共组件的配置。
49. 灰度发布 • 对于重要的配置⼀一定要做灰度发布,先在⼀一台或多台机器器上⽣生效后观察效果,如果没有问题再推给 所有的机器器。 • 对于公共组件的配置,建议先在⼀一个或多个应⽤用上⽣生效后观察效果,没有问题再推给所有的应⽤用。 图⽚片来源:http://www.appadhoc.com/blog/one-page-canary-release-test/
50. 发布审核 • ⽣生产环境建议启⽤用发布审核功能,简单⽽而⾔言就是如果某个⼈人修改了了配置,那么必须由另⼀一个⼈人审核 后才可以发布 • 避免由于头脑不不清醒、⼿手⼀一抖之类的造成⽣生产事故。 图⽚片来源:https://livinator.com/5-tips-to-get-easier-hoa-approval-for-your-home-renovations/
51. 1. 为什什么需要配置中⼼心? 2. 配置中⼼心的⼀一般模样 3. 如何让微服务更更『智能』? 4. 配置中⼼心的最佳实践
52. 在此键⼊入姓名 在此键⼊入Tittle
53. 在此键⼊入姓名 在此键⼊入Tittle
54. 关注『携程技术中⼼心』微信公众号,了了解更更多技术⼲干货!