服务注册中心:Eureka
一、Eureka介绍
1.1、什么是Eureka
Eureka 是 Netflix 开发的服务发现组件,本身是一个基于 REST 的服务。Spring Cloud 将它集成在其子项目 Spring Cloud Netflix 中,实现 Spring Cloud 的服务注册与发现,同时还提供了负载均衡、故障转移等能力。
1.2、Eureka的架构图
- Eureka Server:通过 Register、Get、Renew 等接口提供服务的注册和发现。
- Service Provider:服务提供方,把自身的服务实例注册到 Eureka Server 中。
- Service Consumer:服务调用方,通过 Eureka Server 获取服务列表,消费服务。
1.3、Eureka的运行流程
二、Eureka入门案例
2.1、创建注册中心
(1)在spring-cloud2020下创建一个子模块,名称叫:eureka-server7001
(2)在eureka-server7001的pom.xml中,添加以下依赖信息
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>com.atguigu.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.16</version>
</dependency>
</dependencies>
(3)创建一个配置文件application.yml,编写配置
server:
port: 7001
spring:
application:
#该名称在集群模式下应该保持一致
name: eureka-server
eureka:
instance:
#服务注册中心实例的主机名
hostname: localhost
client:
#是否将自己注册到注册中心,默认为 true,单实例模式下需要设置为 false
register-with-eureka: false
#是否从注册中心获取服务注册信息,默认为 true,单实例模式下需要设置为 false
fetch-registry: false
#注册中心对外暴露的注册地址
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
(4)创建主启动类EurekaMain7001,在启动类上标注开启EurekaServer服务
@EnableEurekaServer
@SpringBootApplication
public class EurekaMain7001 {
public static void main(String[] args) {
SpringApplication.run(EurekaMain7001.class);
}
}
(5)启动服务,在浏览器地址栏中输入:http://localhost:7001/
2.2、创建服务提供者
(1)在spring-cloud-study下创建一个子模块,名称叫:cloud-provider-payment8001
(2)在cloud-provider-payment8001的pom.xml中,添加以下依赖信息
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.16</version>
</dependency>
</dependencies>
(3)创建配置文件application.yml
server:
port: 8001
spring:
application:
name: cloud-payment-service
datasource:
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
driver-class-name: org.gjt.mm.mysql.Driver # mysql驱动包
url: jdbc:mysql://localhost:3306/db2020?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: root
mybatis:
mapperLocations: classpath:mapper/*.xml
type-aliases-package: com.atguigu.springcloud.entities # 所有Entity别名类所在包
eureka:
instance:
#是否使用 ip 地址注册
prefer-ip-address: true
#该实例注册到服务中心的唯一ID
instance-id: ${spring.cloud.client.ip-address}:${server.port}
client:
#设置服务注册中心地址
service-url:
defaultZone: http://localhost:7001/eureka/
(4)创建主启动类,在主启动类上开启EurekaClient
@EnableEurekaClient
@SpringBootApplication
public class PaymentMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain8001.class, args);
}
}
(5)启动当前服务提供者,然后打开浏览器,在浏览器地址栏中输入:http://localhost:7001/
2.3、创建服务消费者
(1)在spring-cloud2020下创建一个子模块,名称叫:cloud-consumer-order80
(2)在cloud-consumer-order80的pom.xml中,添加以下依赖信息
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.16</version>
</dependency>
</dependencies>
(3)创建配置文件application.yaml
server:
port: 60
spring:
application:
name: cloud-consumer-order80
eureka:
client:
#是否将自己注册到注册中心,默认为 true
register-with-eureka: false
#表示 Eureka Client 间隔多久去服务器拉取注册信息,默认为 30 秒
registry-fetch-interval-seconds: 10
#设置服务注册中心地址
service-url:
defaultZone: http://localhost:7001/eureka/
(4)创建主启动类,在主启动类上开启EurekaClient
@EnableEurekaClient
@SpringBootApplication
public class OrderMain80
{
public static void main(String[] args) {
SpringApplication.run(OrderMain80.class, args);
}
}
(5)启动当前服务消费者,然后用restTemplate远程调用测试打开浏览器,在浏览器地址栏中输入:http://localhost:60/consumer/payment/get/1测试
注意:我们现在虽然可以正常调用成功,但聪明的你一定要知道,我们现在只是学习了注册中心的搭建和服务的注册,并没有学习服务的远程调用,这个restTemplate本身就是spring提供的一种访问restful风格的模板类,他不是spring cloud的知识点,spring cloud下服务的调用我们会在后续章节或技术中进行介绍,这里特别说明,是怕大家混淆,我们这一章节重点学习注册中心。
三、Eureka集群配置
3.1、配置集群环境
1)在刚才的父工程下再创建一个 cloud-eureka-server7002注册中心的项目,如果是多机器部署不用修改端口,通过 IP 区分服务,如果在一台机器上演示需要修改端口区分服务。
(2)添加域名映射,eureka会把hostname相同的url移除掉,如果我们配置的都是localhost,所以虽然你启动了两个eureka,但是它们不会把自己当成集群
C:\Windows\System32\drivers\etc\hosts
127.0.0.1 eureka-server7001.com
127.0.0.1 eureka-server7002.com
(3)修改cloud-eureka-server7001的application.yml
server:
port: 7001
spring:
application:
#该名称在集群模式下应该保持一致
name: eureka-server
eureka:
instance:
#服务注册中心实例的主机名
hostname: eureka-server7001.com
#是否使用 ip 地址注册
prefer-ip-address: true
#该实例注册到服务中心的唯一ID
instance-id: ${spring.cloud.client.ip-address}:${server.port}
client:
#设置服务注册中心地址,指向另一个注册中心(除自己之外所有,多个使用逗号隔开)
service-url:
defaultZone: http://localhost:7002/eureka/
(4)修改cloud-eureka-server7002的application.yml
server:
port: 7002
spring:
application:
#该名称在集群模式下应该保持一致
name: eureka-server
eureka:
instance:
#服务注册中心实例的主机名
hostname: eureka-server7002.com
#是否使用 ip 地址注册
prefer-ip-address: true
#该实例注册到服务中心的唯一ID
instance-id: ${spring.cloud.client.ip-address}:${server.port}
client:
#设置服务注册中心地址,指向另一个注册中心(除自己之外所有,多个使用逗号隔开)
service-url:
defaultZone: http://localhost:7001/eureka/
(5)启动两个注册中心,他们会自动构成一个集群环境,我们先启动7001的,然后再启动7002的,先启动哪一个无所谓,但是会报错,这属于正常现象,因为你另一台还没有启动,没有办法注册,等两台都启动了,过一会清除一下控制台,就发现不报错了。
3.2、修改服务提供者
application.yml
server:
port: 8001
spring:
application:
name: cloud-payment-service
datasource:
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
driver-class-name: org.gjt.mm.mysql.Driver # mysql驱动包
url: jdbc:mysql://localhost:3306/db2020?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: root
mybatis:
mapperLocations: classpath:mapper/*.xml
type-aliases-package: com.atguigu.springcloud.entities # 所有Entity别名类所在包
eureka:
instance:
#是否使用 ip 地址注册
prefer-ip-address: true
#该实例注册到服务中心的唯一ID
instance-id: ${spring.cloud.client.ip-address}:${server.port}
client:
#设置服务注册中心地址
service-url:
# defaultZone: http://localhost:7001/eureka/
defaultZone: http://eureka-server7001.com:7001/eureka/,http://eureka-server7002.com:7002/eureka/
http://localhost:7001/ , http://localhost:7002/
3.3、修改服务消费者
application.yml
server:
port: 60
spring:
application:
name: cloud-consumer-order80
eureka:
client:
#是否将自己注册到注册中心,默认为 true
register-with-eureka: false
#表示 Eureka Client 间隔多久去服务器拉取注册信息,默认为 30 秒
registry-fetch-interval-seconds: 10
#设置服务注册中心地址
service-url:
#defaultZone: http://localhost:7001/eureka/
defaultZone: http://eureka-server7001.com:7001/eureka/,http://eureka-server7002.com:7002/eureka/
然后依次启动服务,进行测试。
3.4、支付微服务集群配置
(1)新创建一个支付模块cloud-provider-payment8002
(2)将cloud-provider-payment8001的代码和依赖复制过来
(3)修改cloud-provider-payment8001的application.yml的配置文件
server:
port: 8001
spring:
application:
name: cloud-payment-service
datasource:
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
driver-class-name: org.gjt.mm.mysql.Driver # mysql驱动包
url: jdbc:mysql://localhost:3306/db2020?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: root
mybatis:
mapperLocations: classpath:mapper/*.xml
type-aliases-package: com.atguigu.springcloud.entities # 所有Entity别名类所在包
eureka:
instance:
#是否使用 ip 地址注册
prefer-ip-address: true
#该实例注册到服务中心的唯一ID
instance-id: ${spring.cloud.client.ip-address}:${server.port}
client:
#设置服务注册中心地址
service-url:
# defaultZone: http://localhost:7001/eureka/
defaultZone: http://eureka-server7001.com:7001/eureka/,http://eureka-server7002.com:7002/eureka/
(4)修改cloud-provider-payment8002的application.yml的配置文件
server:
port: 8002
spring:
application:
name: cloud-payment-service
datasource:
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
driver-class-name: org.gjt.mm.mysql.Driver # mysql驱动包
url: jdbc:mysql://localhost:3306/db2020?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: root
mybatis:
mapperLocations: classpath:mapper/*.xml
type-aliases-package: com.atguigu.springcloud.entities # 所有Entity别名类所在包
eureka:
instance:
#是否使用 ip 地址注册
prefer-ip-address: true
#该实例注册到服务中心的唯一ID
instance-id: ${spring.cloud.client.ip-address}:${server.port}
client:
#设置服务注册中心地址
service-url:
# defaultZone: http://localhost:7001/eureka/
defaultZone: http://eureka-server7001.com:7001/eureka/,http://eureka-server7002.com:7002/eureka/
(5)修改cloud-consumer-order80服务消费者的ApplicationContextConfig,@LoadBalanced开启RestTemplate的负载均衡功能
@Configuration
public class ApplicationContextConfig {
@Bean
@LoadBalanced
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
(6)因为cloud-consumer-order80服务消费者的远程调用地址是写死的,我们可以更改为服务名,动态获取。
@RestController
@Slf4j
public class OrderController {
public static final String PAYMENT_URL = "http://CLOUD-PAYMENT-SERVICE";
@Resource
private RestTemplate restTemplate;
@GetMapping("/consumer/payment/get/{id}")
public CommonResult<Payment> getPayment(@PathVariable("id") Long id)
{
return restTemplate.getForObject(PAYMENT_URL+"/payment/get/"+id,CommonResult.class);
}
@GetMapping("/consumer/payment/create")
public CommonResult create(Payment payment)
{
return restTemplate.postForObject(PAYMENT_URL+"/payment/create",payment,CommonResult.class);
}
}
(7)修改cloud-provider-payment8001和cloud-provider-payment8002的controller的代码,因为搭建了服务提供者的集群,我们可以添加以下代码显示端口来查看负载均衡的效果。
@RestController
@Slf4j
public class PaymentController {
@Resource
private PaymentService paymentService;
@Value("${server.port}")
private Integer serverPort;
@PostMapping(value = "/payment/create")
public CommonResult create(@RequestBody Payment payment){
int result = paymentService.create(payment);
log.info("*****插入结果:"+result);
if(result > 0)
{
return new CommonResult(200,"插入数据库成功: ");
}else{
return new CommonResult(444,"插入数据库失败",null);
}
}
@GetMapping(value = "/payment/get/{id}")
public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id)
{
Payment payment = paymentService.getPaymentById(id);
if(payment != null)
{
return new CommonResult(200,"查询成功,端口号:"+serverPort,payment);
}else{
return new CommonResult(444,"没有对应记录,查询ID: ");
}
}
}
测试结果如下
3.5、服务发现Discovery
我们可以通过以下操作获取到服务注册中心的服务实例信息。
(1)编写cloud-provider-payment8001的Controller,注入DiscoveryClient然后编写getDiscovery()方法获取服务注册信息。
@RestController
@Slf4j
public class PaymentController {
@Resource
private PaymentService paymentService;
@Resource
private DiscoveryClient discoveryClient;
@Value("${server.port}")
private Integer serverPort;
@PostMapping(value = "/payment/create")
public CommonResult create(@RequestBody Payment payment){
int result = paymentService.create(payment);
log.info("*****插入结果:"+result);
if(result > 0)
{
return new CommonResult(200,"插入数据库成功: ");
}else{
return new CommonResult(444,"插入数据库失败",null);
}
}
@GetMapping(value = "/payment/get/{id}")
public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id)
{
Payment payment = paymentService.getPaymentById(id);
if(payment != null)
{
return new CommonResult(200,"查询成功,端口号:"+serverPort,payment);
}else{
return new CommonResult(444,"没有对应记录,查询ID: ");
}
}
/**
* 获取注册中心的服务信息
* @return
*/
@GetMapping(value = "/payment/getDiscovery")
public Object getDiscovery(){
//获取所有的Service
List<String> services = discoveryClient.getServices();
for (String service : services) {
log.info(service);
}
List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
for (ServiceInstance instance : instances) {
log.info(instance.getHost()+"\t"+instance.getPort()+"\t"+instance.getUri());
}
return this.discoveryClient;
}
}
(2)在主启动类上添加@EnableDiscoveryClient注解
@EnableEurekaClient
@EnableDiscoveryClient
@SpringBootApplication
public class PaymentMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain8001.class, args);
}
}
测试效果如下:
四、Eureka架构原理
- Register(服务注册):把自己的 IP 和端口注册给 Eureka。
- Renew(服务续约):发送心跳包,每 30 秒发送一次,告诉 Eureka 自己还活着。如果 90 秒还未发送心跳,宕机。
- Cancel(服务下线):当 Provider 关闭时会向 Eureka 发送消息,把自己从服务列表中删除,防止 Consumer 调用到不存在的服务。
- Get Registry(获取服务注册列表):获取其他服务列表。
- Replicate(集群中数据同步):Eureka 集群中的数据复制与同步。
- Make Remote Call(远程调用):完成服务的远程调用。
五、Eureka自我保护机制
5.1、什么是自我保护
一般情况下,服务在 Eureka 上注册后,会每 30 秒发送心跳包,Eureka 通过心跳来判断服务是否健康,同时会定期删除超过 90 秒没有发送心跳的服务。Eureka Server 在运行期间会去统计心跳失败比例在 15 分钟之内是否低于 85%,如果低于 85%,Eureka Server 会将这些实例保护起来,让这些实例不会过期,同时提示一个警告,这种算法叫做 Eureka Server 的自我保护模式,这种自我保护模式默认开启。
有两种情况会导致 Eureka Server 收不到微服务的心跳:
()1微服务自身的原因
(2)微服务与 Eureka 之间的网络故障
5.2、为什么要使用自我保护机制
- 因为同时保留"好数据"与"坏数据"总比丢掉任何数据要更好,当网络故障恢复后,这个 Eureka 节点会退出"自我保护模式"。
- Eureka 还有客户端缓存功能(也就是微服务的缓存功能),即使 Eureka 集群中所有节点都宕机失效,微服务的 Provider 和 Consumer 都能正常通信。
- 微服务的负载均衡策略会自动剔除死亡的微服务节点。
5.3、如何关自我保护
我们可以在注册中心配置文件中进行关闭
eureka:
server:
#true:开启自我保护模式,false:关闭自我保护模式
enable-self-preservation: false
#清理间隔(单位:毫秒,默认是 60*1000)
eviction-interval-timer-in-ms: 60000