掌门教育自 2014 年正式转型在线教育以来,秉承“让教育共享智能,让学习高效快乐”的宗旨和愿 景,经历云计算、大数据、人工智能、 AR / VR / MR 以及现今最火的 5G ,一直坚持用科技赋能 教育。掌门教育的业务近几年得到了快速发展,特别是今年的疫情,使在线教育成为了新的风口, 也给掌门教育新的机遇。
随着业务规模进一步扩大,流量进一步暴增,微服务数目进一步增长,使老的微服务体系所采用的 注册中心 Eureka 不堪重负,同时 Spring Cloud 体系已经演进到第二代,第一代的 Eureka 注册 中心已经不大适合现在的业务逻辑和规模,同时它目前被 Spring Cloud 官方置于维护模式,将不 再向前发展。如何选择一个更为优秀和适用的注册中心,这个课题就摆在了掌门人的面前。经过对 Alibaba Nacos 、HashiCorp Consul 等开源注册中心做了深入的调研和比较,最终选定 Alibaba Nacos 做微服务体系 Solar 中的新注册中心。
背景故事
掌门教育微服务面临的挑战
第一次生产事故
2020 年疫情爆发后的几个月后,掌门教育的微服务实例数比去年猛增 40% ,基础架构部乐观的认 为注册中心 Eureka 服务器可以抗住该数量级的实例数规模, Eureka 服务器在阿里云 PROD 环 境上执行三台 8C16G 普通型机器三角结构型对等部署,运行了好几年都一直很稳定,但灾难还是 在 2020 年 3 月某天晚上降临,当天晚上大概 9 点 30 分左右,其中两台 Eureka 服务器无征兆的 CPU 占用迅速上升到 100%,同时大量业务服务掉线,告警系统被触发,钉钉机器人告警和邮件告 警铺天盖地而来。基础架构部和运维部紧急重启 Eureka 服务器,但没多久,CPU 依旧没抗住,而 且更加来势凶猛,打开的文件描述符数瞬间达到 8000+ ,TCP 连接达到 1 万+ ,业务服务和 Eureka 服务器的通信产生大面积的 TCP CLOSE_WAIT 事件,且伴有大量 Broken pipe 异常。
org.apache.catalina.connector.ClientAbortException:java.io.IOException:Broken pipe
运维人员尝试把机器升级成增强型 8C16G ,折腾一番后,于 23:00 左右恢复正常。
第二次生产事故
微服务实例数依旧在增长, Eureka 服务器平稳运行了大概半个月后,灾难又一次降临,CPU 再次飙升到100%,过程就不表述了。处理方式,把机器升级成增强型 16C32G,并把 Eureka 服务器的版本升级到 Spring Cloud Hoxton 版,并优化了它的一些配置参数,尔后事件再也没出现。
掌门教育新微服务演进思考
虽然 Eureka 服务器目前运行平稳,但我们依旧担心此类事故在未来会再次发生,于是痛定思痛,经过深入的调研和比较一段时间后,通过由基础架构部牵头,各大业务线负责人和架构师参与的专项注册中心架构评审会上,CTO 拍板,做出决议:选择落地 Alibaba Nacos 作为掌门教育的新注册中心。
Talk is cheap,show me the solution。基础架构部说干就干,Nacos 部署到 FAT 环境后,打头阵的是测试组的同学,对 Nacos 做全方位的功能和性能测试,毕竟 Nacos 是阿里巴巴拳头开源产品,迭代了2年多,在不少互联网型和传统型公司都已经落地,我们选择了稳定的 1.2.1 版本,得出结论是功能稳定,性能上佳,关于功能和性能方面的相关数据,具体参考后续:《掌门教育微服务体系 Solar | 阿里巴巴 Nacos 企业级落地下篇》。
但是,如何迁移 Eureka 上的业务服务到 Nacos 上?业务服务实例数目众多,迁移工作量巨大,需要全公司业务部门配合,同时 Eureka 对注册的业务服务名大小写不敏感,而 Nacos 对注册的业务服务名大小写敏感,那么对于业务服务名不规范的业务部门需要改造。而对于基础架构部来说,Nacos Eureka Sync 方案如同一座大山横亘在我们面前,是首先需要迈过去的坎,纵观整个过程,该方案选型还是折腾了一番,具体参考后续:《掌门教育微服务体系 Solar | 阿里巴巴 Nacos 企业级落地中篇》。
阿里巴巴 Nacos 企业级落地的优化代码,在不久的将来会通过开源的方式回馈给业界。
官方介绍
Nacos 简介
阿里巴巴中间件部门开发的新一代集服务注册发现中心和配置中心为一体的中间件。它是构建以“服务”为中心的现代应用架构 (例如微服务范式、云原生范式) 的服务基础设施,支持几乎所有主流类型的“服务”的发现、配置和管理,更敏捷和容易地构建、交付和管理微服务平台。
- Nacos Landscape
- Nacos Map
摘自官网 What is Nacos:https://nacos.io/en-us/docs/what-is-nacos.html
Spring Cloud Alibaba 简介
阿里巴巴中间件部门开发的 Spring Cloud 增强套件,致力于提供微服务开发的一站式解决方案。此项目包含开发分布式应用微服务的必需组件,方便开发者通过 Spring Cloud 编程模型轻松使用这些组件来开发分布式应用服务。依托Spring Cloud Alibaba ,您只需要添加一些注解和少量配置,就可以将 Spring Cloud应用接入阿里微服务解决方案,通过阿里中间件来迅速搭建分布式应用系统。
摘自官网 Spring Cloud Alibaba Introduction:https://github.com/alibaba/spring-cloud-alibaba/blob/master/spring-cloud-alibaba-docs/src/main/asciidoc-zh/introduction.adoc
关于 Nacos 和 Spring Cloud Alibaba 如何使用,它的技术实现原理怎样等,官方文档或者民间博客、公众号文章等可以提供非常详尽且有价值的材料,这些不在本文的讨论范围内,就不一一赘述。笔者尝试结合掌门教育现有的技术栈以及中间件一体化的战略,并着眼于强大的 Nacos 和Spring Cloud Alibaba 技术生态圈展开阐释。
Nacos 开发篇
Nacos Server 落地
Nacos Server
- Nacos Server 环境和域名
掌门的应用环境分为 4 套,DEV | FAT | UAT | PROD 分别对应开发、测试、准生产环境、生产环境,因此 Nacos Server 也分为 4 套独立环境。除了 DEV 环境是单机部署外,其他是集群方式部署。对外均以域名方式访问,包括 SDK 方式连接 Nacos Server 和访问 Nacos Server Dashboard 控制台页面。
- Nacos Server 环境隔离和调用隔离Nacos Server 可以创建不同的命名空间,做到同一个应用环境的基础上更细粒度的划分,隔离服务注册和发现。在某些场景下,开发本地有需要连接测试环境的 Nacos Server ,但其他测试服务不能调用到开发本地,这时候可以将NacosDiscoveryProperties 的 enabled 属性设置为 false 。
- Nacos Server 集成 Ldap
Nacos Server Dashboard 集成公司的 Ldap 服务,并在用户首次登录时记录用户信息。
Nacos Server 界面
- Nacos 界面权限
Nacos Server Dashboard 用户首次登陆时,默认分配普通用户(即非ROLE_ADMIN )角色,对查询以外的按钮均无操作权限,以免出现误操作导致服务非正常上下线。
- Nacos 界面显示服务概览Nacos Server Dashboard 页面增加服务总数及实例总数的统计,该信息每 5 秒刷新一次。
Nacos 监控
Nacos Server 监控
- 标准监控
基于公司现有的 Prometheus 、 Grafana 、 AlertManager 从系统层监控 Nacos。
- 高级监控
根据 Nacos 监控手册,结合 Prometheus 和 Grafana 监控 Nacos 指标。
- Nacos Eureka Sync Etcd 监控
从如下界面可以监控到,业务服务列表是否在同步服务的集群上呈现一致性Hash 均衡分布。
Nacos 日志
- 日志合并及 JSON 格式化
将 Nacos 多模块的日志统一按 info 、 warn、error 级别合并,定义 schema 字段标记不同模块,按 JSON 格式滚动输出到文件,供 ELK 采集展示。
Nacos 告警
Nacos Server 告警
- 业务服务上下线的告警
业务服务上下线的告警 |
业务服务灰度蓝绿的告警 |
- Nacos Eureka Sync 告警
待同步的业务服务列表服务增加的告警 |
待同步的业务服务列表服务删除的告警 |
- 服务名大写告警
钉钉机器人上的告警 |
掌控 APP 上的告警 |
- 业务服务同步完毕告警
业务服务同步完毕的告警 |
Nacos Client 落地
Solar Nacos SDK 环境初始化
应用接入 Solar Nacos SDK 在启动时需要初始化完成 Nacos Server 的连接配置,即 spring.cloud.nacos.discovery.server-addr 参数的赋值。不同环境下连接的 Nacos Server ,因此需要读取机器所在的 env 环境参数,来选择相对应的 Nacos Server 地址。
初始化逻辑代码如下:public class NacosClientConfigApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {
private static final Logger logger = LoggerFactory.getLogger(NacosClientConfigApplicationContextInitializer.class);
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
try {
Properties props = new Properties();
String path = isOSWindows() ? CommonConstant.SERVER_PROPERTIES_WINDOWS : CommonConstant.SERVER_PROPERTIES_LINUX;
File file = new File(path);
if (file.exists() && file.canRead()) {
FileInputStream fis = new FileInputStream(file);
if (fis != null) {
try {
props.load(new InputStreamReader(fis, Charset.defaultCharset()));
} finally {
fis.close();
}
}
}
String env = System.getProperty("env");
if (!isBlank(env)) {
env = env.trim().toLowerCase();
} else {
env = System.getenv("ENV");
if (!isBlank(env)) {
env = env.trim().toLowerCase();
} else {
env = props.getProperty("env");
if (!isBlank(env)) {
env = env.trim();
} else {
env = NacosEnv.DEV.getCode();
}
}
}
String serverAddr = NacosEnv.getValueByCode(env);
Map<String, Object> nacosClientPropertySource = new HashMap<>();
nacosClientPropertySource.put(CommonConstant.NACOS_DISCOVERY_SERVER_ADDR, serverAddr);
applicationContext.getEnvironment().getPropertySources().addLast(new MapPropertySource("solarNacosClientPropertySource", nacosClientPropertySource));
} catch (Exception e) {
logger.error(e.getMessage());
}
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}
private boolean isOSWindows() {
String osName = System.getProperty("os.name");
return !isBlank(osName) && osName.startsWith("Windows");
}
private boolean isBlank(String str) {
return Strings.nullToEmpty(str).trim().isEmpty();
}
}
Solar Nacos 蓝绿灰度发布和子环境隔离
在 Nacos 和Eureka 双注册中心过渡状态下, Solar SDK 支持跨注册中心调用的蓝绿灰度发布和子环境功能。下面的图片,只以 Eureka 为例:
我们只需要把 Eureka SDK 换到 Nacos SDK 即可,实现如下功能:
- Solar 蓝绿灰度发布
- 版本匹配灰度发布
- 版本权重灰度发布
- Solar 多区域路由
- 区域匹配灰度路由
- 区域权重灰度路由
- Solar 子环境隔离
- 环境隔离
- 环境路由
- Solar 版本号和区域值,子环境号策略
- DEV 环境,Git 插件自动创建灰度版本号
- DevOps 环境设置
Solar 蓝绿灰度发布架构图:
Solar 基于版本维度的蓝绿灰度发布架构图:
Solar 子环境隔离架构图:
更多功能参考:
掌门1对1微服务体系Solar第1弹:全链路灰度蓝绿发布智能化实践,掌门教育已经实现通过灰度蓝绿发布方式,实现对流量的精确制导和调拨。
Nepxion Discovery 开源社区:https://github.com/Nepxion/Discovery
Solar Nacos 集成 Sentinel
Solar Nacos 集成灰度蓝绿埋点到 Skywalking
Solar Nacos 集成 Sentinel埋点到 Skywalking
- 微服务上的 Sentinel 埋点
- 网关上的Sentinel 埋点
Solar Nacos集成 DevOps 发布平台
- 集成携程 VI Cornerstone 实现服务拉入拉出
Solar Nacos SDK 的服务,在应用发布时需要做服务的拉入拉出,目的是为了发布时流量无损。掌门使用 VI Cornerstone 实现拉入拉出功能。具体实现是在初始化 NacosDiscoveryProperties 对象时设置instance.enabled 属性值为 false,在服务完全初始化后,通过发布系统调用 Solar NacosSDK 的 API 接口再修改为 true 来被外部发现并提供服务。
public class NacosApplicationContextInitializer implements EnvironmentPostProcessor {
@Override
public void postProcessEnvironment(ConfigurableEnvironment configurableEnvironment, SpringApplication springApplication) {
Boolean bootstrapEnabled = configurableEnvironment.getProperty("devops.enabled", Boolean.class, false);
if (bootstrapEnabled) {
Properties properties = new Properties();
properties.put("spring.cloud.nacos.discovery.instanceEnabled", "false");
PropertiesPropertySource propertiesPropertySource = new PropertiesPropertySource("devopsEnabledNacosDiscoveryProperties", properties);
MutablePropertySources mutablePropertySources = configurableEnvironment.getPropertySources();
mutablePropertySources.addFirst(propertiesPropertySource);
}
}
}
spring.factories 配置文件:
org.springframework.boot.env.EnvironmentPostProcessor=\
com.ctrip.framework.cs.spring.NacosApplicationContextInitializer
Solar Nacos SDK 接入
- Solar 版本定义
- Solar 2.3.x & 1.3.x,基于 Nacos SDK
- Solar 2.2.x & 1.2.x,基于 Eureka SDK
- Solar 版本关系
- Solar 版本与 Spring Boot 技术栈的关系
框架版本 |
Spring Cloud版本 |
Spring Boot版本 |
Spring Cloud Alibaba版本 |
2.x.x |
Greenwich |
2.1.x.RELEASE |
2.1.x.RELEASE |
1.x.x |
Edgware |
1.5.x.RELEASE |
1.5.x.RELEASE |
- Solar 版本与注册中心的关系
框架版本 |
支持的注册中心 |
支持的Cornerstone(VI)版本 |
1.0.x ~ 1.2.x |
Eureka |
<= 0.2.4 |
>= 1.3.x |
Nacos |
>= 1.0.0 |
2.0.x ~ 2.2.x |
Eureka |
<= 0.2.4 |
>= 2.3.x |
Nacos |
>= 1.0.0 |
Solar SDK 接入:
- 设置 Parent
<parent>
<groupId>com.zhangmen</groupId>
<artifactId>solar-parent</artifactId>
<version>${solar.version}</version>
</parent>
- 添加到 pom.xml只需引入一个 Jar 包,对接成本极低,只做基本组件封装,非常轻量级。
微服务
<dependency>
<groupId>com.zhangmen</groupId>
<artifactId>solar-framework-starter-service</artifactId>
<version>${solar.version}</version>
</dependency>
网关
<dependency>
<groupId>com.zhangmen</groupId>
<artifactId>solar-framework-starter-zuul</artifactId>
<version>${solar.version}</version>
</dependency>
- 入口类添加注解
@EnableSolarService , @EnableSolarZuul 封装了标准 Spring Boot / Spring Cloud /
Apollo 等大量注解,降低业务的使用成本。
微服务
@EnableSolarService
public class DemoApplication {
public static void main(String[] args) {
new SpringApplicationBuilder(DemoApplication.class).run(args);
}
}
网关
@EnableSolarZuul
public class DemoApplication {
public static void main(String[] args) {
new SpringApplicationBuilder(DemoApplication.class).run(args);
}
}
Solar Nacos SDK 和 Solar Eureka SDK 升级和回滚
升级和回滚方案非常简单,此方式同时适用于网关和服务,见下图: