Eureka 服务注册中心
Eureka架构下将服务分为以下两个角色:
- 注册中心(Eureka Server,服务端)。
- 客户端(Eureka Client)。
Eureka会将客户端(即服务)的信息进行注册,让客户端可以通过Eureka注册中心获取服务信息并进行调用。
Eureka的作用:
- 注册服务信息:服务提供者启动时向Eureka注册自己的信息,Eureka保存这些服务信息到服务列表中。
- 获取服务信息:服务消费者根据服务名称向Eureka拉取服务列表。
- 负载均衡:有多个服务提供者时,服务消费者利用负载均衡算法,从Eureka注册的服务列表中挑选一个服务后发起远程调用。
- 感知服务健康状态:
- 服务提供者会每隔30秒向Eureka Server发送心跳请求,报告自己的健康状态。
- Eureka会更新记录服务信息列表,心跳不正常的服务会被剔除。
在Eureka架构中,角色有以下分工:
- Eureka Server:记录服务信息;进行心跳监控,剔除心跳不正常的服务。
- Eureka Client:
- Provider:注册自己的信息到Eureka Server;每隔30秒向Eureka Server发送心跳。
- Consumer:根据服务名称从Eureka Server拉取服务列表;基于服务列表做负载均衡,选中一个服务后发起远程调用。
Eureka 示例
依照上例,订单服务在获取订单信息时需要从用户服务中获取用户信息并一同发送:
-
首先需要创建一个新的Maven模块来运行Eureka Server。
-
Eureka Server的
pom.xml如:1<?xml version="1.0" encoding="UTF-8"?> 2<project xmlns="http://maven.apache.org/POM/4.0.0" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 5 <modelVersion>4.0.0</modelVersion> 6 <parent> 7 <groupId>asia.linner.demo</groupId> 8 <artifactId>cloud-demo</artifactId> 9 <version>1.0</version> 10 </parent> 11 12 <artifactId>eureka-server</artifactId> 13 14 <properties> 15 <maven.compiler.source>8</maven.compiler.source> 16 <maven.compiler.target>8</maven.compiler.target> 17 <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> 18 </properties> 19 20 <dependencies> 21 <!--Eureka服务端--> 22 <dependency> 23 <groupId>org.springframework.cloud</groupId> 24 <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> 25 </dependency> 26 </dependencies> 27</project>其中最主要的就是导入
eureka-server起步依赖:1<!--Eureka服务端--> 2<dependency> 3 <groupId>org.springframework.cloud</groupId> 4 <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> 5</dependency>在Eureka中,服务端和客户端有不同的依赖,服务端的起步依赖就是
spring-cloud-starter-netflix-eureka-server。
-
-
编写
EurekaApplication启动类,并在启动类上使用@EnableEurekaServer注解装配EurekaServer:1@EnableEurekaServer // EurekaServer开关,自动装配EurekaServer 2@SpringBootApplication 3public class EurekaApplication { 4 public static void main(String[] args) { 5 SpringApplication.run(EurekaApplication.class, args); 6 } 7} -
在
eureka-server中,添加application.yml并加入以下配置信息:1server: 2 # 服务端口(视具体情况进行更改) 3 port: 8180 4spring: 5 application: 6 # 服务名称 7 name: eureka-server 8eureka: 9 client: 10 service-url: 11 # Eureka的地址信息(如果有多个,则用逗号”,“隔开) 12 defaultZone: http://localhost:8180/eureka -
分别在
order-service和user-service中添加Eureka客户端依赖:1<!-- Eureka客户端 --> 2<dependency> 3 <groupId>org.springframework.cloud</groupId> 4 <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> 5</dependency> -
分别在Eureka客户端的
application.yml中添加如下配置信息:1eureka: 2 # 配置eureka-server的信息 3 client: 4 service-url: 5 # defaultZone要与eureka-server中的defaultZone相同 6 defaultZone: http://localhost:8180/eureka分别在Eureka客户端的
application.yml中添加服务名称(spring.application.name),如order-service:1spring: 2 application: 3 # 服务名称 4 name: order-serviceEureka Server利用
spring.application.name来找到对应模块的信息,并返回。利用spring.application.name也可以做负载均衡。由于所有模块都可能作为服务提供者被其他服务调用,所以项目中所有服务模块都应该在Eureka Server中进行注册。
-
在服务消费者中,需要在注册
RestTemplate的方法上使用注解@LoadBalanced来开启Eureka的负载均衡。如
order-service中:1@Bean 2@LoadBalanced // 开启Eureka负载均衡 3public RestTemplate restTemplate() { 4 return new RestTemplate(); 5} -
服务消费者通过Eureka,使用
RestTemplate调用其他模块。如
order-service调用user-service:1@Service 2public class OrderService { 3 4 @Autowired 5 private OrderMapper orderMapper; 6 // 注入RestTemplate 7 @Autowired 8 private RestTemplate restTemplate; 9 10 public Order queryOrderById(Long orderId) { 11 // 1.查询订单 12 Order order = orderMapper.findById(orderId); 13 // 2.利用RestTemplate发起HTTP请求,查询用户信息 14 // 2.1.URL路径(将服务的IP改为服务的名称) 15 String url = "http://user-service/user/" + order.getUserId(); 16 // 2.2.发送HTTP(GET)请求,实现远程调用 17 User user = restTemplate.getForObject(url, User.class); 18 // 3.封装User到Order 19 order.setUser(user); 20 // 4.返回 21 return order; 22 } 23}这里面最主要的是,配置了Eureka后,使用
RestTemplate调用其他模块时,URL中的IP可以改为服务提供者(被调模块)的服务名称,并且可以自动做负载均衡。即,原本是使用localhost:8081这个IP端口来调用user-service,但是现在可以直接使用http://user-service来调用user-service提供的接口。
Eureka 服务注册配置总结
按照角色配置Eureka,至少需要做如下配置:
-
Eureka Server:
依赖:
1<!--Eureka服务端--> 2<dependency> 3 <groupId>org.springframework.cloud</groupId> 4 <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> 5</dependency>配置(
application.yml):1server: 2 # Eureka服务端口(视具体情况进行更改) 3 port: 8180 4spring: 5 application: 6 # 服务名称 7 name: eureka-server 8eureka: 9 client: 10 service-url: 11 # Eureka的地址信息(如果有多个,则用逗号”,“隔开) 12 defaultZone: http://localhost:8180/eureka -
Eureka Client:
依赖:
1<!-- Eureka客户端 --> 2<dependency> 3 <groupId>org.springframework.cloud</groupId> 4 <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> 5</dependency>配置(
application.yml):1spring: 2 application: 3 # 服务名称 4 name: clinet-1-service 5eureka: 6 client: 7 service-url: 8 # defaultZone要与eureka-server中的defaultZone相同 9 defaultZone: http://localhost:8180/eureka -
服务消费者:
开启负载均衡:
1@Bean 2@LoadBalanced // 开启Eureka负载均衡 3public RestTemplate restTemplate() { 4 return new RestTemplate(); 5}使用
RestTemplate调用其他服务时,URL中的IP可以改为服务提供者(被调模块)的服务名称。
Idea 配置模块的多个实例
-
复制现有的配置:
-
使用VM Options修改端口,避免与原有的模块产生端口冲突:
Ribbon 负载均衡
Ribbon是一款负载均衡组件,而Eureka的负载均衡则是基于Ribbon实现的。如上例(order-service)中:
1@Bean
2@LoadBalanced // 开启Eureka负载均衡
3public RestTemplate restTemplate() {
4 return new RestTemplate();
5}
负载均衡流程
Eureka中使用注解@LoadBalanced标识RestTemplate,从而实现拦截RestTemplate发起的请求,并获取相应的服务信息和执行负载均衡策略。
@LoadBalanced注解使用LoadBalancerInterceptor来执行请求的拦截。LoadBalancerInterceptor实现了ClientHttpRequestInterceptor接口。而ClientHttpRequestInterceptor接口主要用于拦截客户端HTTP请求。
ClientHttpRequestInterceptor接口:
1@FunctionalInterface
2public interface ClientHttpRequestInterceptor {
3 ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
4 throws IOException;
5}
拦截流程:
-
使用
RestTemplate发送请求时,Eureka会使用LoadBalancerInterceptor的intercept()来执行拦截:1public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor { 2 3 private LoadBalancerClient loadBalancer; // 负载均衡客户端 4 5 @Override 6 public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, 7 final ClientHttpRequestExecution execution) throws IOException { 8 /* 9 获取请求发出的URL 10 例如order-service中获取订单信息时,会发出[http://user-service/user/{userId}]这个请求 11 这里的request.getURI()会获取到[http://user-service/user/{userId}]这个请求的URI 12 */ 13 final URI originalUri = request.getURI(); 14 /* 15 通过URI获取请求的Host 16 如:user-service 17 */ 18 String serviceName = originalUri.getHost(); 19 Assert.state(serviceName != null, 20 "Request URI does not contain a valid hostname: " + originalUri); 21 /* 22 将Host作为Eureka客户端名称,获取相应的服务信息并执行负载均衡策略 23 */ 24 return this.loadBalancer.execute(serviceName, 25 this.requestFactory.createRequest(request, body, execution)); 26 } 27} -
LoadBalancerClient是一个客户端执行负载均衡的接口,当实际执行时,使用的是RibbonLoadBalancerClient这个实现类:1public class RibbonLoadBalancerClient implements LoadBalancerClient { 2 3 /* ... */ 4 5 /** 6 * 实际执行的是这个方法 7 */ 8 public <T> T execute( 9 String serviceId, // 传入的是serviceName,如 user-service 10 LoadBalancerRequest<T> request, 11 Object hint) throws IOException { 12 13 /* 14 根据serviceId获取负载均衡器 15 loadBalancer中有这么两个属性: 16 - AllServerList 17 - UpServerList 18 它们记录了获取到的Eureka客户端信息 19 实际执行时,loadBalancer的类型是ZoneAwareLoadBalancer 20 ZoneAwareLoadBalancer继承了DynamicServerListLoadBalancer 21 在loadBalancer中,使用IRule定义负载均衡策略 22 */ 23 ILoadBalancer loadBalancer = getLoadBalancer(serviceId); 24 /* 25 根据LoadBalancer,执行负载均衡策略,获取Eureka客户端的真实服务地址 26 如,获取到 localhost:8081 27 */ 28 Server server = getServer(loadBalancer, hint); 29 30 /* ... */ 31 } 32 33 /* ... */ 34}
负载均衡策略
Ribbon的负载均衡策略是使用IRule这个接口来定义。在Eureka中给出了几个负载均衡策略的实现,它们的继承关系图如下:
Eureka中负载均衡默认的实现是ZoneAvoidanceRule,它们的含义如下:
| 内置负载均衡规则类 | 规则描述 |
|---|---|
RoundRobinRule |
简单轮询服务列表来选择服务器。它是Ribbon默认的负载均衡规则。 |
AvailabilityFilteringRule |
对以下两种服务器进行忽略:
|
WeightedResponseTimeRule |
为每一个服务器赋予一个权重值。服务器响应时间越长,这个服务器的权重就越小。 这个规则会随机选择服务器,这个权重值会影响服务器的选择。 |
ZoneAvoidanceRule |
以区域可用的服务器为基础进行服务器的选择。 使用Zone对服务器进行分类(这个Zone可以理解为一个机房、一个机架等)。而后再对Zone内的多个服务做轮询。 在配置服务注册时,Zone的值可以被设置(按照服务器所在的地理位置进行设置)。使用此规则,服务消费者会优先选择跟自己在同一个Zone内的服务,然后再做轮询。 |
BestAvailableRule |
忽略那些短路的服务器,并选择并发数较低的服务器。 |
RandomRule |
随机选择一个可用的服务器。 |
RetryRule |
重试机制的选择逻辑 |
配置负载均衡规则
配置负载均衡规则有两种方式:
-
配置全局默认的负载均衡规则:
在任何一个配置类或新建一个配置类,然后添加一个新的Bean。
1@Bean 2public IRule getLoadBalancerRule() { 3 // 此处以RandomRule为例 4 return new RandomRule(); 5}使用Bean的方式配置负载均衡规则,无论当前服务远程调用哪个服务提供者,都是采用这个Bean里面配置的规则。
-
为指定的服务提供者配置单独的负载均衡规则:
可以在
application.yml中使用serverName.ribbon.NFLoadBalancerRuleClassName配置指定服务的负载均衡规则,它的值是具体规则的实现类的全类名。例如在
order-service中配置user-service的负载均衡规则:1user-service: 2 ribbon: 3 # 负载均衡规则 4 NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
Ribbon 饥饿加载
- 懒加载:第一次访问时才加载。
- 饥饿加载:在项目启动时就开始加载。
Ribbon默认是采用懒加载,即第一次访问时才会去创建LoadBalanceClient,请求时间会很长。而饥饿加载则会在项目启动时创建,降低第一次访问的耗时。
可以在application.yml中,通过下面配置开启Ribbon的饥饿加载:
1ribbon:
2 eager-load: # 饥饿加载
3 enabled: true # 开启饥饿加载
4 clients: user-service # 指定对userservice这个服务饥饿加载
ribbon.eager-load.clients是一个集合类型,如果要对多个服务提供者开启饥饿加载,可以这样配置:
1ribbon:
2 eager-load: # 饥饿加载
3 enabled: true # 开启饥饿加载
4 # 对多个服务提供者开启饥饿加载
5 clients:
6 - clinet-1-service
7 - clinet-2-service
8 - clinet-3-service
评论