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 服务器目前运行平稳,但我们依旧担心此类事故在未来会再次发生,于是痛定思痛,经过深入的调研和比较一段时间后,通过由基础架构部牵头,各大业务线负责人和架构师参与的专项注册中心架构评审会上,CTO 拍板,做出决议:选择落地 Alibaba Nacos 作为掌门教育的新注册中心。
Talk is cheap,show me the solution。基础架构部说干就干,Nacos 部署到 FAT 环境后,打头阵的是测试组的同学,对 Nacos 做全方位的功能和性能测试,毕竟 Nacos 是阿里巴巴拳头开源产品,迭代了2年多,在不少互联网型和传统型公司都已经落地,我们选择了稳定的 1.2.1 版本,得出结论是功能稳定,性能上佳,关于功能和性能方面的相关数据,具体参考后续:《掌门教育微服务体系 Solar | 阿里巴巴 Nacos 企业级落地下篇》。
阿里巴巴中间件部门开发的 Spring Cloud 增强套件,致力于提供微服务开发的一站式解决方案。此项目包含开发分布式应用微服务的必需组件,方便开发者通过 Spring Cloud 编程模型轻松使用这些组件来开发分布式应用服务。依托 Spring Cloud Alibaba ,您只需要添加一些注解和少量配置,就可以将 Spring Cloud 应用接入阿里微服务解决方案,通过阿里中间件来迅速搭建分布式应用系统。
Nacos Server 环境和域名
Nacos Server 环境隔离和调用隔离
Nacos Server 集成 Ldap
Nacos 界面权限
Nacos 界面显示服务概览
标准监控
高级监控
日志合并及 JSON 格式化
Nacos Server 告警
业务服务上下线的告警
业务服务上下线的告警 | 业务服务灰度蓝绿的告警 |
---|---|
Nacos Eureka Sync 告警
待同步的业务服务列表服务增加的告警 | 待同步的业务服务列表服务删除的告警 |
---|---|
服务名大写告警
钉钉机器人上的告警 | 掌控 APP 上的告警 |
---|---|
业务服务同步完毕告警
业务服务同步完毕的告警 |
---|
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 蓝绿灰度发布
Solar 多区域路由
Solar 子环境隔离
Solar 版本号和区域值,子环境号策略
微服务上的 Sentinel 埋点
集成携程 VI Cornerstone 实现服务拉入拉出
Solar Nacos SDK 的服务,在应用发布时需要做服务的拉入拉出,目的是为了发布时流量无损。掌门使用 VI Cornerstone 实现拉入拉出功能。具体实现是在初始化 NacosDiscoveryProperties 对象时设置 instance.enabled 属性值为 false,在服务完全初始化后,通过发布系统调用 Solar Nacos SDK 的 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);
}
}
}
org.springframework.boot.env.EnvironmentPostProcessor=\
com.ctrip.framework.cs.spring.NacosApplicationContextInitializer
Solar 2.3.x & 1.3.x,基于 Nacos SDK
Solar 2.2.x & 1.2.x,基于 Eureka SDK
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 |
设置 Parent
<parent>
<groupId>com.zhangmen</groupId>
<artifactId>solar-parent</artifactId>
<version>${solar.version}</version>
</parent>
添加到 pom.xml
<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
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);
}
}
吴毅挺,掌门技术副总裁,负责技术中台和少儿技术团队。曾就职于百度、eBay 、携程,曾任携程高级研发总监,负责从零打造携程私有云、容器云、桌面云和 PaaS 平台。
任浩军,掌门基础架构部负责人。曾就职于平安银行、万达、惠普,曾负责平安银行平台架构部PaaS 平台 Halo 基础服务框架研发。10 多年开源经历,Github ID:@HaojunRen,Nepxion 开源社区创始人,Nacos Group Member,Spring Cloud Alibaba & Nacos & Sentinel& OpenTracing Committer。