服务注册与发现---Eureka
Eureka是美国Netflix公司出品的服务注册与发现组件,Spring Cloud集成了该组件,然而时代发展,技术革新,Eureka已经停止更新了,但部分老的Spring Cloud的服务注册与发现组件还是用的Eureka。
学学了解了解,也有裨益,毕竟思想没有变化。
1. Eureka系统架构
Eureka采用了CS的设计架构,Eureka Server 作为服务的注册功能的服务器,它是服务注册中心。而系统的其他服务,使用Eureka的客户端连接到Eureka Server 并维持心跳链接。这样系统的维护人员可以通过Eureka Server来监控系统中各个微服务是否正常运行。
服务注册与发现中,有一个注册中心(Eureka Server)。当服务器启动的时候,会把当前自己的服务器的信息,例如服务IP地址端口等以别名的方式注册到注册中心上(Eureka Server)。另一方服务以别名的方式去注册中心上获取到的实际通信地址(IP+Port),然后在实现本地的远程调用,调用方式可以是Restful风格的接口(例如Spring boot 的 Controller)或者 RPC(例如Alibaba 的 Dubbo)。
核心设计思想是使用服务中心管理每个服务之间的依赖关系(服务治理概念),这样可以在部署时方便的扩容或迁移服务,而不用去维护服务之间的依赖关系,说的直白一点就是,服务部署的IP和端口发生变化,注册中心的数据同时发生变化(服务启动的时候会向中心自动注册),服务的消费者,可以从注册中心通过约定的别名(一般是服务名)获取变更后服务的IP和端口,然后去调用这个服务。
Eureka包含两个组件:
Eureka Server提供服务来注册服务
各个微服务节点通过配置启动后,会在Eureka Server中进行注册,这样EurekaServer中的服务注册表中将会存储所有的可用节点的信息,服务节点的信息可以在界面中直观看到。
Eureka Client通过注册中心进行访问
是一个java客户端,用于简化Eureka Server的交互,客户端同时也具备一个内置的、使用轮询(round-robin)负载算法的负载均衡器。在应用启动后,将会向Eureka Server发送心跳(默认周期30秒)。如果Eureka Server在多个心跳周期没有接收到某个节点的心跳,Eureka Server将会从服务注册表中把这个服务节点移除(默认90秒)
2. Eureka编码举例
2.1 先创建Eureka Server
创建一个Spring boot应用,导入一下依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</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>
application.yml:
server:
port: 7001
# eureka 配置
eureka:
instance:
# eureka 的服务器实例名称
hostname: localhost
client:
# false 表示不向注册中心注册自己
register-with-eureka: false
# false 表示本服务就是注册中心,本服务的职责就是维护服务实例,并不需要去检索服务
fetch-registry: false
service-url:
# 设置于Eureka Server交互的地址,注册服务和查询服务都需要依赖这个地址
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
在主类上加@EnableEurekaServer
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
启动服务,访问localhost:7001
2.2 编写一个Provider
pom中添加如下依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
配置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
url: jdbc:mysql://localhost:3306/db2020?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: 123
http:
encoding:
charset: UTF-8
force: true
# eureka client配置
eureka:
client:
# 表示是否将自己注册进 EurekaServer, 默认true
register-with-eureka: true
# 是否从Eureka Server抓取自己的注册信息,默认true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetch-registry: true
service-url:
# 注册中心地址
defaultZone: http://localhost:7001/eureka
mybatis:
mapper-locations: classpath:mapper/*.xml
# 所有Entity别名类所在包
type-aliases-package: com.yp.entity
在主类上添加@EnableEurekaClient
@SpringBootApplication
@EnableEurekaClient
public class PaymentApplication {
public static void main(String[] args) {
SpringApplication.run(PaymentApplication.class, args);
}
}
启动服务,访问localhost:7001
会发现有一个服务cloud-payment-service(大写)已经注册上来了
2.2 编写一个Consumer
pom中添加如下依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
配置application.yml
server:
port: 80
spring:
application:
name: cloud-order-service
http:
encoding:
charset: UTF-8
force: true
eureka:
client:
# 表示是否将自己注册进 EurekaServer, 默认true
register-with-eureka: true
# 是否从Eureka Server抓取自己的注册信息,默认true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetch-registry: true
service-url:
# 注册中心地址
defaultZone: http://localhost:7001/eureka
在主类上添加@EnableEurekaClient
@SpringBootApplication
@EnableEurekaClient
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}
启动服务,访问localhost:7001
2.3 测试Consumer访问Provider
2.31 Consumer编写
在Consumer中写一个RestTemplete配置,主要是实现负载均衡,和Restful接口访问
@Configuration
public class ApplicationContextConfig {
@Bean
@LoadBalanced
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}
Consumer写Controller
@RestController
@Slf4j
public class OrderController {
// 直接访问地址
private static final String PAYMENT_URL = "http://localhost:8001";
// eureka上注册的微服务
private static final String SERVICE_NAME = "http://CLOUD-PAYMENT-SERVICE";
@Autowired
private RestTemplate restTemplate;
@GetMapping("consumer/payment/get/{id}")
public CommonResult<Payment> getPayment(@PathVariable("id") Long id) {
return restTemplate.getForObject(SERVICE_NAME + "/payment/" + id, CommonResult.class);
}
}
2.3.2 编写Provider
在Provider中写一个Controller,为了简化,后续service、dao不再列出
@RestController
@Slf4j
public class PaymentController {
// 仅仅是为了验证负载均衡,因为本地验证只能由端口区分
@Value("${server.port}")
private int servicePort;
@Autowired
private PaymentService paymentService;
@GetMapping("/payment/{id}")
public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id) {
Payment payment = paymentService.getPaymentById(id);
if (payment != null) {
return new CommonResult<>(200, "获取数据成功! servicePort: " + servicePort, payment);
} else {
return new CommonResult<>(500, "获取数据失败! servicePort: " + servicePort, null);
}
}
}
2.3.3 运行测试
3. Eureka集群
目的是提高容错,原理就是互相注册
3.1 配置hosts
因为本地实验,要区分多个Eureka Server,同一IP映射多个域名
127.0.0.1 eureka7001.com
127.0.0.1 eureka7002.com
3.2 创建Eureka Server的application.yml
创建application7001.yml
server:
port: 7001
# eureka 配置
eureka:
instance:
# eureka 的服务器实例名称
hostname: eureka7001.com
client:
# false 表示不向注册中心注册自己
register-with-eureka: false
# false 表示本服务就是注册中心,本服务的职责就是维护服务实例,并不需要去检索服务
fetch-registry: false
service-url:
# 设置于Eureka Server交互的地址,注册服务和查询服务都需要依赖这个地址
defaultZone: http://eureka7002.com:7002/eureka
创建application7002.yml
server:
port: 7002
# eureka 配置
eureka:
instance:
# eureka 的服务器实例名称
hostname: eureka7002.com
client:
# false 表示不向注册中心注册自己
register-with-eureka: false
# false 表示本服务就是注册中心,本服务的职责就是维护服务实例,并不需要去检索服务
fetch-registry: false
service-url:
# 设置于Eureka Server交互的地址,注册服务和查询服务都需要依赖这个地址
defaultZone: http://eureka7001.com:7001/eureka
3.3 打Eureka Server的jar包
在pom中加入plugin
<build>
<finalName>eureka-server</finalName>
<resources>
<resource>
<directory>src/main/java</directory>
<filtering>true</filtering>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**.*</include>
<include>**/*.*</include><!-- i18n能读取到 -->
<include>**/*/*.*</include>
</includes>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<fork>true</fork> <!-- 如果没有该配置,devtools不会生效 -->
<!-- 指定该Main Class为全局的唯一入口 -->
<mainClass>com.yp.EurekaServerApplication</mainClass>
<layout>ZIP</layout>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal><!--可以把依赖的包都打包到生成的Jar包中-->
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
3.4 运行 eureka-server.jar
在两个cmd窗口独立运行
java -jar eureka-server.jar --spring.config.location=D:\spring-cloud-demo\application7001.yml
java -jar eureka-server.jar --spring.config.location=D:\spring-cloud-demo\application7002.yml
3.5 修改Provider和Consumer的配置
Provider和Consumer的application.yml defaultZone作同样的修改
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
eureka:
client:
# 表示是否将自己注册进 EurekaServer, 默认true
register-with-eureka: true
# 是否从Eureka Server抓取自己的注册信息,默认true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetch-registry: true
service-url:
# 注册中心地址
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
3.5 测试结果:
4. Provider集群验证
启动两个cloud-payment-service进程,一个端口8001,另一个8002,打包与Eureka Server一样
java -jar payment-server.jar --spring.config.location=.\application8001.yml
java -jar payment-server.jar --spring.config.location=.\application8002.yml
端口轮询:
5. Eureka Server注册信息优化
instance:
# 主机名
instance-id: payment_8001
# 访问路径显示IP
prefer-ip-address: true
eureka:
client:
# 表示是否将自己注册进 EurekaServer, 默认true
register-with-eureka: true
# 是否从Eureka Server抓取自己的注册信息,默认true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetch-registry: true
service-url:
# 注册中心地址
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
instance:
# 主机名
instance-id: payment_8001
# 访问路径显示IP
prefer-ip-address: true
6. Eureka Server注册表微服务信息
写一个Controller用于相应注册中心注册表服务实例
@RestController
@RequestMapping("/consumer")
@Slf4j
public class ServiceInstancesController {
@Autowired
private DiscoveryClient discoveryClient;
@GetMapping("/payment/serviceInstances")
public List<ServiceVo> discovery() {
List<ServiceVo> serviceVoList = new ArrayList<>();
List<String> services = discoveryClient.getServices();
for (String serviceName : services) {
ServiceVo serviceVo = new ServiceVo();
List<ServiceInstanceVo> instanceVoList = new ArrayList<>();
serviceVo.setServiceName(serviceName);
List<ServiceInstance> instances = discoveryClient.getInstances(serviceName);
for (ServiceInstance serviceInstance : instances) {
ServiceInstanceVo instanceVo = new ServiceInstanceVo();
instanceVo.setServiceId(serviceInstance.getServiceId());
instanceVo.setHost(serviceInstance.getHost());
instanceVo.setPort(serviceInstance.getPort());
instanceVo.setUri(serviceInstance.getUri().toString());
instanceVoList.add(instanceVo);
}
serviceVo.setInstances(instanceVoList);
serviceVoList.add(serviceVo);
}
return serviceVoList;
}
}
在主类上加 @EnableDiscoveryClient
请求:
http://localhost/consumer/payment/serviceInstances
格式化:
[
{
"serviceName":"cloud-payment-service",
"instances":[
{
"host":"192.168.110.1",
"port":8001,
"uri":"http://192.168.110.1:8001",
"serviceId":"CLOUD-PAYMENT-SERVICE"
},
{
"host":"192.168.110.1",
"port":8002,
"uri":"http://192.168.110.1:8002",
"serviceId":"CLOUD-PAYMENT-SERVICE"
}
]
},
{
"serviceName":"cloud-order-service",
"instances":[
{
"host":"192.168.110.1",
"port":80,
"uri":"http://192.168.110.1:80",
"serviceId":"CLOUD-ORDER-SERVICE"
}
]
}
]
7. Eureka的自我保护
某时刻某一服务不可用了,Eureka不会立刻清理,依旧会对该微服务的信息进行保存,提高系统可用性,这主要是考虑网络阻塞,拥堵对健康检查的影响。
Eureka Service配置
server:
# 关闭自我保护
enable-self-preservation: false
# 时间间隔,单位毫秒
eviction-interval-timer-in-ms: 3000
server:
port: 7001
# eureka 配置
eureka:
instance:
# eureka 的服务器实例名称
hostname: eureka7001.com
client:
# false 表示不向注册中心注册自己
register-with-eureka: false
# false 表示本服务就是注册中心,本服务的职责就是维护服务实例,并不需要去检索服务
fetch-registry: false
service-url:
# 设置于Eureka Server交互的地址,注册服务和查询服务都需要依赖这个地址
defaultZone: http://eureka7002.com:7002/eureka
server:
# 关闭自我保护
enable-self-preservation: false
# 时间间隔,单位毫秒
eviction-interval-timer-in-ms: 3000
注册的服务配置
lease-renewal-interval-in-seconds: 2
# Eureka服务端在接收最后一次心跳等待时间上限,单位秒(默认90秒),超时将剔除服务
lease-expiration-duration-in-seconds: 3
eureka:
client:
# 表示是否将自己注册进 EurekaServer, 默认true
register-with-eureka: true
# 是否从Eureka Server抓取自己的注册信息,默认true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetch-registry: true
service-url:
# 注册中心地址
defaultZone: http://eureka7001.com:7001/eureka
instance:
# 主机名
instance-id: payment_8001
# 访问路径显示IP
prefer-ip-address: true
# Eureka客户端向服务端发送心跳的时间间隔,单位秒(默认30秒)
lease-renewal-interval-in-seconds: 2
# Eureka服务端在接收最后一次心跳等待时间上限,单位秒(默认90秒),超时将剔除服务
lease-expiration-duration-in-seconds: 3
测试: