服务注册中心(Eureka-->Nacos)
一、什么是服务注册中心
作为微服务架构最基础也是最重要的组件之一,服务注册中心本质上是为了解耦服务提供者和服务消费者。对于任何一个微服务,原则上都应存在或者支持多个提供者,这是由微服务的分布式属性决定的。更进一步,为了支持弹性扩缩容特性,一个微服务的提供者的数量和分布往往是动态变化的,也是无法预先确定的。因此,原本在单体应用阶段常用的静态LB机制就不再适用了,需要引入额外的组件来管理微服务提供者的注册与发现,而这个组件就是服务注册中心。
从下面的架构图中,可以看出注册中心要实现的功能就是服务的注册、反注册,服务发现(订阅)、通知,这四个功能。

服务注册 register:注册就是将服务的信息注册到注册中心,这里的信息主要是服务的 name、address、weight 信息。name 是服务的名字,address 是服务提供方的地址信息,包含 ip 和 port 信息,weight 是服务节点的权重信息,用来调节流量的,一般配合负载均衡模块一起使用。
服务反注册 unregister:反注册说的是服务提供方机器下线、进程退出的的时候,能够提前将注册中心的相关节点信息删除或者通过相关状态标识出来,表明该服务节点已经不提供服务,这样调用方 Invoker 在发现服务的时候就可以排除掉这个节点,不会将请求发过来。
服务订阅 subscribe:订阅说的是服务调用方,在调用前需要根据订阅服务的 name 从注册中心获取到所有相关服务的注册信息,比如 address、weight。这样在调用前就可以根据 address 信息进行网络连接,然后发起调用,以及根据 weight 来使用负载均衡来调节流量大小。
服务通知 notify:通知说的是相关服务注册和反注册时,注册中心信息变更,能够主动通知到服务订阅方,这样服务订阅方就可以了解到是新增了服务机器节点,还是下线了服务节点,亦或是服务权重变更,这样方便调用方自动控制服务调用流量的流向以及大小。
解决的问题
①软负载及透明化路由:服务提供者和服务调用者之间相互解耦,服务调用者不需要硬编码服务提供者地址
②服务动态发现及可伸缩能力:服务提供者机器增减能被服务调用者通过注册中心动态感知,而且通过增减机器可以实现服务的弹性伸缩
③通过注册中心可以动态的监控服务运行质量及服务依赖,为服务提供服务治理能力。
二、服务注册中心选型
Eureka作为官方推荐的服务注册中心是我们开始的选择,这帮助我们迅速搭建起第一版的服务注册中心,后来随着架构的演进我们需要一个能够兼容其他语言的方案,同时Eureka2.0的闭源(1.*版本依然是开源的)也是我们替换的一个重要原因,所以我们用Nacos替换掉了Eureka,Nacos完全国产开源,官方计划中会扩展其他原因的官方client,目前的解决方案是自行开发client或者用nacossync来解决不同语言的兼容问题。例如:后面我们启动了一个go语言的微服务,就是注册到consul然后利用nacossync实现服务统一注册到nacos。


因为我们的主力开发语言是Java,所以这里我们对服务注册中心的选择主要是考虑到驾驭性和生态,但是有了这次架构迁移(Nacos替换Eureka)的经验也为未来继续进行架构演进提供案例和经验,这也未必是服务注册中心的最终形态。
Nacos架构图

服务管理:实现服务CRUD,域名CRUD,服务健康状态检查,服务权重管理等功能
配置管理:实现配置管CRUD,版本管理,灰度管理,监听管理,推送轨迹,聚合数据等功能
元数据管理:提供元数据CURD 和打标能力
插件机制:实现三个模块可分可合能力,实现扩展点SPI机制
事件机制:实现异步化事件通知,sdk数据变化异步通知等逻辑
日志模块:管理日志分类,日志级别,日志可移植性(尤其避免冲突),日志格式,异常码+帮助文档
回调机制:sdk通知数据,通过统一的模式回调用户处理。接口和数据结构需要具备可扩展性
寻址模式:解决ip,域名,nameserver、广播等多种寻址模式,需要可扩展
推送通道:解决server与存储、server间、server与sdk间推送性能问题
容量管理:管理每个租户,分组下的容量,防止存储被写爆,影响服务可用性
流量管理:按照租户,分组等多个维度对请求频率,长链接个数,报文大小,请求流控进行控制
缓存机制:容灾目录,本地缓存,server缓存机制。容灾目录使用需要工具
启动模式:按照单机模式,配置模式,服务模式,dns模式,或者all模式,启动不同的程序+UI
一致性协议:解决不同数据,不同一致性要求情况下,不同一致性机制
存储模块:解决数据持久化、非持久化存储,解决数据分片问题
Nameserver:解决namespace到clusterid的路由问题,解决用户环境与nacos物理环境映射问题
CMDB:解决元数据存储,与三方cmdb系统对接问题,解决应用,人,资源关系
Metrics:暴露标准metrics数据,方便与三方监控系统打通
Trace:暴露标准trace,方便与SLA系统打通,日志白平化,推送轨迹等能力,并且可以和计量计费系统打通
接入管理:相当于阿里云开通服务,分配身份、容量、权限过程
用户管理:解决用户管理,登录,sso等问题
权限管理:解决身份识别,访问控制,角色管理等问题
审计系统:扩展接口方便与不同公司审计系统打通
通知系统:核心数据变更,或者操作,方便通过SMS系统打通,通知到对应人数据变更
OpenAPI:暴露标准Rest风格HTTP接口,简单易用,方便多语言集成
Console:易用控制台,做服务管理、配置管理等操作
SDK:多语言sdk
Agent:dns-f类似模式,或者与mesh等方案集成
CLI:命令行对产品进行轻量化管理,像git一样好用
三、实现要点
[链接地址] https://yq.aliyun.com/articles/698930
四、实战
eurekaserver搭建
Eureka分为两部分,Eureka Server和Eureka Client。Eureka Server充当注册中心的角色,Eureka Client相对于Eureka Server来说是客户端,需要将自身信息注册到注册中心。
Eureka Client通过心跳将信息注册到某一个Eureka Server,Eureka Server之间进行信息同步完成一致性。当Eureka Client向Eureka Server发起注册请求的时候(根据defaultZone寻找Eureka Server列表),如果有一次请求注册成功,那么后续就不会在向其他Eureka Server发起注册请求。而且这个注册尝试的次数为3次,超过3次即使下一个Eureka Server是可用的,依然不会进行注册。所以default Zone的Eureka Server地址配置最好顺序是随机的。
解决超过3次依然可以注册的方法是增加配置
eureka.client.transport.retryableClientQuarantineRefreshPercentage=1[地址] http://blog.didispace.com/spring-cloud-eureka-register-detail/
根本解决这个问题请参考文章
1、构建springboot工程并引入依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.sccba</groupId>
<artifactId>himalaya</artifactId>
<version>0.1</version>
</parent>
<artifactId>eurekaserver</artifactId>
<name>${project.artifactId}</name>
<packaging>jar</packaging>
<dependencies>
<!-- eureka server support -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<!-- security support -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- hot deploy support -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.sccba</groupId>
<artifactId>himalaya</artifactId>
<version>0.1</version>
<packaging>pom</packaging>
<name>${project.artifactId}</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-boot.version>2.1.0.RELEASE</spring-boot.version>
<spring-cloud.version>Greenwich.RELEASE</spring-cloud.version>
<spring-cloud-alibaba.version>0.9.0.RELEASE</spring-cloud-alibaba.version>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<minio.version>6.0.8</minio.version>
</properties>
<modules>
<module>eurekaserver</module>
<module>zuul</module>
<module>microservice</module>
</modules>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>0.9.0.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<finalName>${project.name}</finalName>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<configuration>
<fork>true</fork>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<target>${maven.compiler.target}</target>
<source>${maven.compiler.source}</source>
<encoding>${project.build.sourceEncoding}</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
<configuration>
<skipTests>true</skipTests>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.2.2</version>
<configuration>
<packagingExcludes>WEB-INF/lib/spring-boot-starter-tomcat-2.1.0.RELEASE.jar</packagingExcludes>
<packagingExcludes>WEB-INF/lib/tomcat-embed-*.jar</packagingExcludes>
</configuration>
</plugin>
</plugins>
</build>
<distributionManagement>
...
</distributionManagement>
</project>
2、代码
package com.sccba;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration;
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
/**
* @author BaZinGa
* @date
*/
@SpringBootApplication
@EnableEurekaServer // 启动eureka-server
public class EurekaserverApplication extends SpringBootServletInitializer{
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(EurekaserverApplication.class);
}
public static void main(String[] args) {
SpringApplication.run(EurekaserverApplication.class, args);
}
}
package com.sccba;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER);
// 关闭CSRF攻击防御,如不关闭,Eureka Client则会找不到Eureka Server而报异常。
http.csrf().disable();
// 注意:为了可以使用 http://${user}:${password}@${host}:${port}/eureka/ 这种方式登录,所以必须是httpBasic,如果是form方式,不能使用url格式登录
http.authorizeRequests().anyRequest().authenticated().and().httpBasic();
}
}
3、配置
spring:
application:
name: eurekaserver
security:
basic:
enabled: true # 开启基于HTTP basic的认证
user:
name: user #配置登录的账号是user
password: Aa0123 #配置登录的密码是Aa0123
eureka:
instance:
initial-status: STARTING # STARTING 表示在Eureka Server 刚刚启动的时候,默认不主动去注册,等待服务同步数据完成之后再去注册。
client:
register-with-eureka: true #表示是否将自己注册到Eureka Server,默认为true。保证启动的时候将注册信息注册到自己的节点。
fetch-registry: true #表示是否从Eureka Server获取注册信息,默认为true。
serviceUrl:
#设置与Eureka Server交互的地址,查询服务和注册服务都需要依赖这个地址。多个地址可使用 , 分隔。
defaultZone: http://user:Aa0123@peer1:8761/eureka/,http://user:Aa0123@peer2:8762/eureka/,http://user:Aa0123@peer3:8763/eureka/
info:
app:
name: eurekaserver
description: eurekaserver
version: 1.0
spring-boot-version: 2.1.0.RELEASE
owner: sccba
version: 0.0.1-SNAPSHOT
---
spring:
profiles: peer1
server:
port: 8761
eureka:
instance:
hostname: peer1
---
spring:
profiles: peer2
server:
port: 8762
eureka:
instance:
hostname: peer2
---
spring:
profiles: peer3
server:
port: 8763
eureka:
instance:
hostname: peer3
4、启动
java -jar eurekaserver.jar --spring.profiles.active=peer15、使用(EurekaClient)
5.1引入依赖
<!-- eureka client support -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
5.2添加注解
启动类添加@EnableDiscoveryClient或者@EnableEurekaClient1,@EnableDiscoveryClient注解是基于spring-cloud-commons依赖,并且在classpath中实现;2,@EnableEurekaClient注解是基于spring-cloud-netflix依赖,只能为eureka作用;如果你的classpath中添加了eureka,则它们的作用是一样的。
备注:@EnableDiscoveryClient(autoRegister = false) //关闭服务注册
5.3配置
#在eureka中默认显示ip
eureka.instance.prefer-ip-address = true
#手动制定地址
eureka.instance.ip-address = 36.9.8.39
#服务注册中心地址
eureka.client.serviceUrl.defaultZone = http://user:Aa0123@peer1:8761/eureka/,http://user:Aa0123@peer2:8762/eureka/,http://user:Aa0123@peer3:8763/eureka/
Nacos替换Eureka
说明:这里的替换主要是指Nacos的服务发现,Nacos内置配置管理,但是个人认为目前还没有Apollo功能强大,暂时不做变更。
1、修改依赖
注释掉eureka,引入nacos
<!-- eureka client support
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency> -->
<!-- nacos 替换eureka -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
2、配置
#eureka.instance.prefer-ip-address = true
#eureka.instance.ip-address = 36.9.8.39
#eureka.client.serviceUrl.defaultZone = http://user:Aa0123@peer1:8761/eureka/,http://user:Aa0123@peer2:8762/eureka/,http://user:Aa0123@peer3:8763/eureka/
spring.cloud.nacos.discovery.server-addr = 127.0.0.1:8848
3、注解(可选)
// @EnableEurekaClient // 关闭eureka
@EnableDiscoveryClient // 启用nacos
//@EnableDiscoveryClient(autoRegister = false) //关闭服务注册
决多语言问题
目前nacos没有其他语言的原生client,但是可以通过nacossync讲其他服务注册中心的服务同步到nacos,从而实现服务发现。
[实现原理] https://nacos.io/zh-cn/docs/nacos-sync.html
通过源码的方式构建(依赖数据库,详情请参考迁移手册)
通过源码的方式构建(依赖数据库,详情请参考迁移手册)
cd nacosSync/
mvn clean package -U[迁移手册] https://nacos.io/zh-cn/docs/nacos-sync-use.html
或者直接下载Jar包 https://github.com/nacos-group/nacos-sync/releases




