1.什么是服务注册中心?
注册中心可以说是微服务架构中的"通讯录”,它记录了服务和服务地址的映射关系。在分布式架构中,服务会注册到这里,当服务需要调用其它服务时,就到这里找到服务的地址,进行调用,服务注册中心是微服务架构中最基础的设施之—。
服务注册中心的作用:
2.常用的服务注册中心
-
Netflix Eureka
-
Alibaba Nacos
-
HashiCorp Consul
-
Apache ZooKeeper
-
CoreOS Etcd
-
CNCF CoreDNS

3.为什么需要服务注册中心?
在分布式系统中,我们不仅仅是需要在注册中心找到服务和服务地址的映射关系这么简单,我们还需要考虑更多更复杂的问题:
-
服务注册后,如何被及时发现
-
服务宕机后,如何及时下线服务如何有效的水平扩展
-
服务发现时,如何进行路由
-
服务异常时,如何进行降级
-
注册中心如何实现自身的高可用
这些问题的解决都依赖于注册中心。简单看,注册中心的功能有点类似于DNS 服务器或者负载均衡器,而实际上,注册中心作为微服务的基础组件,可能要更加复杂,也需要更多的灵活性和时效性。所以我们还需要学习更多Spring Cloud 微服务组件协同完成应用开发。
4.Eureka服务注册中心三种角色



4.1 Eureka Server
通过 Register、Get、Renew等接口提供服务的注册和发现。
4.2 Application Service (Service Provider)
服务提供方,把自身的服务实例注册到 Eureka Server中。
4.3 Application Client (Service Consumer)
服务调用方,通过Eureka Server获取服务列表,消费服务。
5.项目实现

(1)创建父工程,在父工程的pom文件中配置好匹配SpringCould项目的SpringBoot版本(这里采用的是当前 2021.0.3版本,需要springboot 2.6.8版本)
定义好相关依赖的版本信息
<?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>
<modules>
<module>user-service</module>
<module>order-service</module>
<module>eureka-service-1</module>
<module>eureka-service-2</module>
</modules>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.8</version>
<relativePath/>
</parent>
<packaging>pom</packaging>
<groupId>com.zw</groupId>
<artifactId>cloud-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>2021.0.3</spring-cloud.version>
<mysql.version>8.0.30</mysql.version>
<mybatis-plus>3.5.2</mybatis-plus>
<eureka-client>3.1.3</eureka-client>
<eureka-server>3.1.3</eureka-server>
<lombok>1.18.24</lombok>
</properties>
<dependencyManagement>
<dependencies>
<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>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok}</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>
(2) 在父工程中创建好需要的服务模块,并配置好pom文件,依赖版本由父工程指定
<?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">
<parent>
<artifactId>cloud-demo</artifactId>
<groupId>com.zw</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>order-service</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
//客服端需要的依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<version>${eureka-client}</version>
</dependency>
</dependencies>
<build>
<finalName>app</finalName>
<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">
<parent>
<artifactId>cloud-demo</artifactId>
<groupId>com.zw</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>eureka-service-1</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
//服务端需要的依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
</dependencies>
<build>
<finalName>app</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
(3)编写好服务启动类和配置文件
@EnableEurekaServer注解 用于开启我们的Eureka服务
@EnableEurekaServer
@SpringBootApplication
public class Eureka1Application {
public static void main(String[] args) {
SpringApplication.run(Eureka1Application.class,args);
}
}
服务端简单配置文件
server:
port: 8761
spring:
application:
name: eureka-service
eureka:
instance:
hostname: eureka-8761
prefer-ip-address: true
instance-id: ${spring.cloud.client.ip-address}:${server.port}
client:
fetch-registry: true
register-with-eureka: true
service-url:
defaultZone: http://127.0.0.1:8762/eureka/
@EnableEurekaClient注解 是能够让注册中心发现、扫描到该服务并且只对Eureka的注册中心有效
@EnableDiscoveryClient 对 Eureka、Zookeeper、Consul 等注册中心都有效
从 SpringCloud Edgware 版本开始, @EnableEurekaClient 和 @EnableDiscoveryClient 注解都可以省略了,只需要在 pom.xml 中引入依赖、在application.yml 上进行相关配置,就可以将微服务注册到注册中心上
@LoadBalanced用于在服务集群中实现负载均衡功能
@MapperScan("com.zw.order.mapper")
@SpringBootApplication
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class,args);
}
@LoadBalanced
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
客服端简单配置文件
spring:
application:
name: order-service
eureka:
instance:
prefer-ip-address: true
instance-id: ${spring.cloud.client.ip-address}:${server.port}
client:
service-url:
defaultZone: http://127.0.0.1:8761/eureka/,http://127.0.0.1:8762/eureka/
register-with-eureka: true
fetch-registry: true

(4)服务调用实现
拼接url字符串,通过restTemplate.getForObject()方法,直接进行get请求访问
@Autowired
private OrderMapper orderMapper;
@Autowired
private RestTemplate restTemplate;
public Order queryOrderById(Long orderId) {
Order order = orderMapper.findById(orderId);
String url="http://user-service/user/"+order.getUserId();
User user = restTemplate.getForObject(url, User.class);
order.setUser(user);
return order;
}
方式二,如果在RestTemplate Bean方法上使用@LoadBalanced开启负载均衡后,通过ip进行调用服务会报错 :
java.lang.IllegalStateException: No instances available for XXX
通过DiscoveryClient.getInstances(服务名称),获取服务的详细信息,再通过信息拼接url字符串,使用restTemplate.exchange()这个通用方法方法,直接进行get请求访问
@Autowired
private RestTemplate restTemplate;
@Autowired
private DiscoveryClient discoveryClient;
public Order queryOrderById2(Long orderId) {
Order order = orderMapper.findById(orderId);
StringBuffer sb=null;
List<String> services = discoveryClient.getServices();
if (CollectionUtils.isEmpty(services))
return null;
List<ServiceInstance> serviceInstances = discoveryClient.getInstances("user-service");
if (CollectionUtils.isEmpty(serviceInstances))
return null;
System.out.println("user-service实例对象有:");
serviceInstances.forEach(System.out::println);
ServiceInstance si = serviceInstances.get(1);
log.info("ServiceInstance si====="+si.toString());
sb=new StringBuffer();
sb.append("http://"+si.getHost()+":"+si.getPort()+"/user/"+order.getUserId());
log.info("url==="+sb.toString());
ResponseEntity<User> response = restTemplate.exchange(
sb.toString(),
HttpMethod.GET,
null,
User.class
);
log.info("response.getBody() ====="+response.getBody().toString());
order.setUser(response.getBody());
return order;
}
方式三,如果在RestTemplate Bean方法上使用@LoadBalanced开启负载均衡后,通过ip进行调用服务会报错 :
java.lang.IllegalStateException: No instances available for XXX
ServiceInstance choose = loadBalancerClient.choose("user-service");
sb.append("http://"+choose.getHost()+":"+choose.getPort()+"/user/"+order.getUserId());
6.Eureka架构原理

-
Register(服务注册): 把自己的IP和端口注册给Eureka.
-
Renew(服务续约): 发送心跳包,每30秒发送一次心跳来进行服务续约,告诉Eureka自己还活着。如果90s内都没收到心跳续约,会把Eureka client 从注册表中剔除,在由Eureka-Server 60秒的清除间隔,把Eureka client 给下线
-
Cancel(服务下线):当服务提供者关闭时会向Eureka发送消息,把自己从服务列表中删除。防止服务消费者调用到不存在的服务。
-
Get Registry(获取服务注册列表):服务调用者会在自身注册后先获取一份服务列表,并且缓存在客户端本地。同时,为了性能及安全性考虑,Eureka Server会每隔30秒更新一次缓存中的服务清单。
-
Replicate(集群中数据同步): Eureka集群中的数据复制与同步。
-
Make Remote Call远程调用完成服务的选程调用。
7.CAP原则
C : 一致性,无论微服务的节点有多少,每个节点上的同一份数据都是一致的
A : 可用性,服务快速响应,不宕机,访问不超时并且能快速返回数据,每个请求都能接受到一个响应,无论响应成功或失败
P : 分区容错性,系统中任意信息的丢失或失败不会影响系统的继续运作。



8.Eureka自我保护
8.1 启动自我保护的条件
一般情况下,服务在Eureka 上注册后,会每30秒发送心跳包,Eureka 通过心跳来判断服务是否健康,同时会定期删除超过90秒没有发送心跳的服务。

有两种情况会导致Eureka Server收不到微服务的心跳:
-
微服务自身的原因
-
微服务与Eureka之间的网络故障
自我保护的模式
Eureka Server在运行期间会去统计心跳失败比例在15分钟之内是否低于85%,如果低于85%,Eureka Server会将这些实例保护起来,让这些实例不会过期,同时提示一个警告。这种算法叫做Eureka Server的自我保护模式。
8.2 为什么要启动自我保护
-
因为同时保留"好数据"与"坏数据"总比丢掉任何数据要更好,当网络故障恢复后,这个Eureka节点会退出"自我保护模式”
-
Eureka还有客户端缓存功能(也就是微服务的缓存功能)。即使Eureka服务端集群中所有节点都宕机失效,微服务的Provider和Consumer 都能正常通信。
-
微服务的负载均衡策略会自动剔除死亡的微服务节点。
8.3 如何关闭自我保护
在Eureka服务端的配置文件中设置属性
eureka:
server:
enable-self-preservation: false
eviction-interval-timer-in-ms: 60000
8.4 优雅停服
在开启保护模式的情况下,主动关闭服务,希望在Eureka注册中心里剔除下线的服务,而不是等待90秒后再被删除。
通过acturator实现:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
management:
endpoints:
web:
exposure:
include: health,info,env,shutdown
endpoint:
shutdown:
enabled: true
通过 http://localhost:使用actutor的服务端口/actuator
就可以看到该服务开启的端点信息,再通过具体url查看具体信息


想要实现关闭服务,需要用post请求访问shutdown端点的url,否则服务端提示警告
Request method 'GET' not supported
可以封装成一个工具类,实现功能。这次先用Postman来实现:


9. Eureka安全认证
(1) 为Eureka服务端添加SpringSecurity依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
(2) 设置配置文件
spring:
application:
name: eureka-service
security:
user:
name: root
password: root
eureka:
server:
enable-self-preservation: true
eviction-interval-timer-in-ms: 60000
instance:
hostname: eureka-8761
prefer-ip-address: true
instance-id: ${spring.cloud.client.ip-address}:${server.port}
client:
fetch-registry: true
register-with-eureka: true
service-url:
defaultZone: http://root:root@127.0.0.1:8762/eureka/
添加SpringSecurity依赖后,每次访问该服务都会要求登录(SpringSecurity安全验证),所以需要在配置文件中设置验证账号以及密码,并且注册中心的url需要改变,添加上账号和密码,这样服务端不就需要手动登录。
由于SpringSecurity会自动开启CSRF(跨站请求伪造)功能,任何一次服务请求默认都需要CSRF 的token,而Eureka-client不会生成该token,csrf将微服务的注册也给过滤了,所以这时候Eureka服务器无法相互注册,客服端也不能注册。
解决方法:
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.csrf().ignoringAntMatchers("/eureka/**");
}
}
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.csrf().disable().authorizeHttpRequests()
.anyRequest()
.authenticated()
.and()
.httpBasic();
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· AI Agent开发,如何调用三方的API Function,是通过提示词来发起调用的吗