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>
<!-- springCloud -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- mysql驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus}</version>
</dependency>
<!-- lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok}</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>
(2) 在父工程中创建好需要的服务模块,并配置好pom文件,依赖版本由父工程指定
-
Eureka-Client端
<?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>
-
Eureka-Server端
<?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 #官方文档建议使用8761作为服务端口
spring:
application:
name: eureka-service #在eureka网页中显示的服务名称,如果有多个服务注册中心,
eureka:
instance:
hostname: eureka-8761
prefer-ip-address: true #是否使用ip地址注册
instance-id: ${spring.cloud.client.ip-address}:${server.port}
client:
fetch-registry: true #默认为true,在单机模式下需要关闭
register-with-eureka: true #默认为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")
//@EnableEurekaClient
@SpringBootApplication
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class,args);
}
//如果有多台相同服务,可以开启ribbon负载均衡,否则会报错
@LoadBalanced
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
客服端简单配置文件
spring:
application:
name: order-service #在eureka网页中显示的服务名称
eureka:
instance:
# hostname: localhost
prefer-ip-address: true #是否使用ip地址注册
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 #是否注册到eureka,,客户端需要为true 默认也是true
fetch-registry: true #是否从eureka获取信息,默认true
(4)服务调用实现
-
服务调用方式一:
服务调用方式一:
拼接url字符串,通过restTemplate.getForObject()方法,直接进行get请求访问
@Autowired
private OrderMapper orderMapper;
@Autowired
private RestTemplate restTemplate;
public Order queryOrderById(Long orderId) {
// 1.查询订单
Order order = orderMapper.findById(orderId);
//2.构造url
//在没有加入注册中心前只能通过硬编码来进行http请求服务
//String url="http://localhost:8081/user/"+order.getUserId();
//加入注册中心后使用 服务名称来请求服务,比硬编码更加灵活
String url="http://user-service/user/"+order.getUserId();
//3.通过restTemplate发送http请求,返回类型是User
User user = restTemplate.getForObject(url, User.class);
order.setUser(user);
// 4.返回
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) {
// 1.查询订单
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());
//请求user-service服务
ResponseEntity<User> response = restTemplate.exchange(
sb.toString(),
HttpMethod.GET,
null,
// new ParameterizedTypeReference<Order>() {
// }
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");
//拼接url
sb.append("http://"+choose.getHost()+":"+choose.getPort()+"/user/"+order.getUserId());
6.Eureka架构原理
-
Register(服务注册): 把自己的IP和端口注册给Eureka.
-
Renew(服务续约): 发送心跳包,每30秒发送一 次,告诉Eureka自己还活着。如果90秒还未发送心跳,宕机。
-
Cancel(服务下线):当服务提供者关闭时会向Eureka发送消息,把自己从服务列表中删除。防止服务消费者调用到不存在的服务。
-
Get Registry(获取服务注册列表):获取其他服务列表。
-
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: #开启自我保护功能 false关闭
enable-self-preservation: false
eviction-interval-timer-in-ms: 60000 #清理间隔时间 单位毫秒(默认60*1000)
8.4 优雅停服
在开启保护模式的情况下,主动关闭服务,希望在Eureka注册中心里剔除下线的服务,而不是等待90秒后再被删除。
通过acturator实现:
-
(1)添加服务端依赖
<!-- actuator依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
-
(2)设置服务端配置文件
#度量指标监控与健康检查
management:
endpoints:
web:
exposure:
include: health,info,env,shutdown
#include: "*" #开启所有端点
endpoint:
shutdown:
enabled: true #shutdown端点需要二次强调开启
通过 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 #清理间隔时间 单位毫秒(默认60*1000)
instance:
hostname: eureka-8761
prefer-ip-address: true #是否使用ip地址注册
instance-id: ${spring.cloud.client.ip-address}:${server.port}
client:
fetch-registry: true
register-with-eureka: true
service-url:
#向另一个注册中心注册 配置了安全认证所以每次访问都需要密码
defaultZone: http://root:[email protected]:8762/eureka/
添加SpringSecurity依赖后,每次访问该服务都会要求登录(SpringSecurity安全验证),所以需要在配置文件中设置验证账号以及密码,并且注册中心的url需要改变,添加上账号和密码,这样服务端不就需要手动登录。
由于SpringSecurity会自动开启CSRF(跨站请求伪造)功能,任何一次服务请求默认都需要CSRF 的token,而Eureka-client不会生成该token,csrf将微服务的注册也给过滤了,所以这时候Eureka服务器无法相互注册,客服端也不能注册。
解决方法:
-
第一种方式
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
//方式一:忽略以 /eureka/下的全部请求
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
//忽略eureka的访问请求的csrf认证
http.csrf().ignoringAntMatchers("/eureka/**");
}
}
-
第一种方式
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
//方式二:关闭csrf全部功能
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.csrf().disable().authorizeHttpRequests()
.anyRequest()
.authenticated()
.and()
.httpBasic();
}
}
标签:服务,spring,boot,eureka,---,注册,Eureka
From: https://www.cnblogs.com/ggzs/p/16629797.html