在springcloud之前我们听过最多的服务注册rpc框架就是 dubbo + zookepper。
今天我们说一下一个新的服务注册组件:eureka
本项目地址:git项目地址
一、核心概念
Eureka 作为 Spring Cloud 体系中最核心、默认的注册中心组件,研究它的运行机制,有助于我们在工作中更好地使用它。
Eureka 主要包括两个模块:Eureka Server 和 Eureka Client。
1. Eureka Server:注册中心服务端
以 REST API 的形式为服务实例提供了注册、管理和查询等操作。同时,Eureka Server 也为我们提供了可视化的监控页面,可以直观地看到各个 Eureka Server 当前的运行状态和所有已注册服务的情况。
注册中心服务端主要对外提供了三个功能:
- 服务注册
服务提供者启动时,会通过 Eureka Client 向 Eureka Server 注册信息,Eureka Server 会存储该服务的信息,Eureka Server 内部有二层缓存机制来维护整个注册表 - 提供注册表
服务消费者在调用服务时,如果 Eureka Client 没有缓存注册表的话,会从 Eureka Server 获取最新的注册表 - 同步状态
Eureka Client 通过注册、心跳机制和 Eureka Server 同步当前客户端的状态。
2. Eureka Client :服务注册客户端
简而言之就是具体提供服务的微服务,将自身的服务接口注册到eureka-Server端,以供调用。
自我保护机制
默认情况下,如果 Eureka Server 在一定的 90s 内没有接收到某个微服务实例的心跳,会注销该实例。但是在微服务架构下服务之间通常都是跨进程调用,网络通信往往会面临着各种问题,比如微服务状态正常,网络分区故障,导致此实例被注销。
固定时间内大量实例被注销,可能会严重威胁整个微服务架构的可用性。为了解决这个问题,Eureka 开发了自我保护机制,那么什么是自我保护机制呢?
Eureka Server 在运行期间会去统计心跳失败比例在 15 分钟之内是否低于 85%,如果低于 85%,Eureka Server 即会进入自我保护机制。
Eureka Server 触发自我保护机制后,页面会出现提示:
通过在 Eureka Server 配置如下参数,开启或者关闭保护机制,生产环境建议打开:
eureka.server.enable-self-preservation=true
二、使用eureka搭建高可用订单支付集群
本次演示一个简易的订单支付系统,架构分为:
- 客户端consumer(port:80)
- eureka-Sever注册中心(port:7001,7002)
- eureka-client支付微服务(port:8001,8002)
采用多注册中心,多服务端实现系统高可用,及负载均衡。
数据库:
CREATE TABLE `payment` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
`serial` varchar(255) DEFAULT NULL COMMENT '名称',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
这里由于我们是一个集群系统,所以我们创建一个父工程:springcloud2020
并将共用的entities以及mapper,抽离到子Module cloud-api-commons里面,贴一下项目结构图:
1.支付模块构建
如上图所示,首先我们看一下支付模块也就是 微服务的提供者,eureka的client端:端口:8001
这里需要引入一个核心的eureka-client包:
<!--eureka client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
server:
port: 8001
spring:
application:
name: cloud-payment-service
datasource:
# 当前数据源操作类型
type: com.alibaba.druid.pool.DruidDataSource
# mysql驱动类
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/yao?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=GMT%2B8
username: root
password: 123456
mybatis:
mapper-locations: classpath*:mapper/*.xml
type-aliases-package: com.cpown.springcloud.entities
eureka:
instance:
hostname: localhost #eureka服务注册地址
client:
#是否从eureka Server 抓取已有的注册信息,默认true,单节点无所谓,集群必须为true才能配合ribbon使用负载均衡
fetch-registry: true
#表示是否将自己加入eureka service true代表加入
register-with-eureka: true
service-url:
# #设置 eureka server 交互的地址查询服务和注册服务需要依赖的地址
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
这里要注意的就是eureka的核心配置:defaultZone里面的是Eureka的注册中心地址,后边会说到,配了两个地址是一个高可用集群的操作。
package com.cpown.springcloud.controller;
import com.cpown.springcloud.dto.CommonResult;
import com.cpown.springcloud.entities.Payment;
import com.cpown.springcloud.service.PaymentServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
@RestController
@Slf4j
public class PaymentController {
@Resource
PaymentServiceImpl paymentService;
@Value("${server.port}")
private String serverPort;
@GetMapping("/get/{id}")
public CommonResult get(@PathVariable("id") Long id){
log.info("8001查询数据:"+id);
Payment payment = paymentService.selectByPrimaryKey(id);
if(null != payment){
return new CommonResult("请求成功,serverPort:"+serverPort,0000,payment);
}else {
return new CommonResult("请求失败,serverPort:"+serverPort,9999);
}
}
@PostMapping("/create")
public CommonResult create(@RequestBody Payment payment){
log.info("8001新增数据"+payment);
int insert = paymentService.insert(payment);
log.info("8001新增数据结果:"+insert);
if(insert>0){
return new CommonResult("插入数据成功,serverPort:"+serverPort,0000,payment);
}else {
return new CommonResult("插入数据失败,serverPort:"+serverPort,9999);
}
}
}
我们的payment服务端游两个接口,我们启动自测一下:
接口是可用的。
这里需要在启动类添加@EnableEurekaClient注解 标明自己是 eureka服务里面的 Client,加了注解才能够将服务注册进eureka-server注册中心。
2.注册中心 eureka-server
注册中心代码不多:要注意的点有三个:
- 需要引入eureka-server
<!--eureka server-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
- 需要使用注解@EnableEurekaServer表明当前服务是server端
- 配置eureka-server
server:
port: 7001
#spring:
# application:
# name: cloud-eureka-service #eureka服务注册名称
eureka:
instance:
hostname: eureka7001 #eureka服务注册地址
client:
#false 表示自己就是注册中心,职责就是维护服务实例,不需要去检索服务
fetch-registry: false
#false 表示不想注册中心注册自己
register-with-eureka: false
service-url:
# #设置 eureka server 交互的地址查询服务和注册服务需要依赖的地址
defaultZone: http://eureka7002.com:7002/eureka/
注意:我们前边在支付模块里面配置的defaultZone,地址就是我们当前 注册中心的地址。
由于我们是集群配置,为了便于区分,我们将本地 localhost转换为相应的域名地址。让上面配置的host形式的serviceUrl能在本地正确访问到
我们启动一下:可以发现我们之前的支付模块服务已经注册进注册中心,也可使用localhost:7001直接访问
三、实现高可用及负载均衡
我们一般实现注册中心高可用及负载均衡,至少应该脱离单机操作,实现多机器协作。
- 在前面支付模块的基础上,再创建一个支付微服务cloud-provider-payment8002,代码一样,功能一样,仅端口不一样。
- 再构建一个注册中心cloud-eureka-sever7002
注意:这里的defaultZone地址配置的是我们 ,另一个注册中心地址7001;同样的在7001的配置文件中defaultZone地址为7002.
注册中心配置讲究一个,互相注册,相互守望。
defaultZone: http://eureka7001.com:7001/eureka/
- 在注册中心集群配置过后,相应的我们需要在 支付端将注册中心地址修改为两个。
.
都配置号以后启动支付服务,以及注册中心:
可以看到,在注册中心7001和7002里面分别有两个CLOUD-PAYMENT-SERVICE(我们的支付项目配置的微服务名称 spring.application.name)
同时我们的7001和7002里面的DS Replicas分别显示了另一台地址,就代表集群构建成功了.
四、客户端调用模块 cloud-consumer-order80
最后我们创建客户端项目,客户端只有两个类:
package com.cpown.springcoloud.config;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
/**
* RestTemplate配置类
*/
@Configuration
public class ApplicationContextConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
配置类:主要为了创建RestTemplate 的bean实例,方便后边调用接口。
@LoadBalanced注解很关键,如果需要实现负载均衡调用多台机器的话 需要使用此注解,可以直接使用微服务名称调用接口,不需要使用接口ip地址。如下:
package com.cpown.springcoloud.controller;
import com.cpown.springcloud.dto.CommonResult;
import com.cpown.springcloud.entities.Payment;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
/**
* 订单调用controller
*/
@RestController
@Slf4j
public class OrderController {
@Resource
RestTemplate restTemplate;
/**
* 单机调用
*/
// private static final String baseUrl = "http://localhost:8001";
/**
* 集群微服务调用
* 可以直接使用微服务名称
* 需要搭配 @LoadBalanced注解
*/
private static final String baseUrl = "http://cloud-payment-service";
@GetMapping("/consumer/payment/get/{id}")
public CommonResult<Payment> get(@PathVariable("id") String id){
return restTemplate.getForObject(baseUrl+"/get/"+id,CommonResult.class);
}
@GetMapping("/consumer/payment/create")
public CommonResult<Payment> create(Payment payment){
return restTemplate.postForObject(baseUrl+"/create",payment,CommonResult.class);
}
}
至此,多有代码已结束,我们启动所有项目测试一下。
五、测试
由于默认采用的负载均衡策略是轮询,所以我们的请求,应该是交替调用 8001和8002服务的。
没问题的。
六、补充
在我们的支付模块 8001和8002配置文件中添加如下配置,可以在监控页面直接看到,规范的服务名称,以及ip地址。