基本概念
1、Registe 一一服务注册
当eureka Client向Eureka Server注册时,Eureka Client提供自身的元数据,比如IP地址、端口、运行状况指标的Uri、主页地址等信息。
2、Renew一一服务续约
Eureka lient在默认的情况下会每隔30秒发送一次心跳来进行服务续约,代表自己依然存活。如果eureka server在90秒内没有收到心跳消息,将eureka client从实例注册列表中删除。
3、Fetch Registries一一获取服务注册列表信息
Eureka Client从Eureka Server获取服务注册表信息,井将其缓存在本地。Eureka Client会使用服务注册列表信息查找其他服务的信息,从而进行远程调用,该注册列表每30秒更新一次。Eureka Client
和Eureka Server 可以使用 JSON XML 数据格式进行通信。在默认的情况下, Eureka Client
使用JSON 格式的方式来获取服务注册列表的信息。
4、Cancel-一服务下线
Eureka Client在程序关闭时可以向Eureka Server发送下线请求。不过下线请求不会自动完成,需要在程序关闭时调用如下代码:
DiscoveryManager.getinstance().shutdownComponent();
5、Eviction一一服务剔除
当Eureka Client90秒没有向Eureka Server发送服务续约(即心跳)时,Eureka Server会将该服务实例从服务注册列表删除,即服务剔除。
Register服务注册
boolean register() throws Throwable { logger.info(PREFIX + appPathidentifier + ”: registering service ... ” ); EurekaHttpResponse<Void> httpResponse; try { httpResponse = eurekaTransport.registrationClient.register(instanceinfo); }catch(Exception e) { logger.warn(”{} - registration failed {} ”, PREFIX + appPathidentifier, e.getMessage(), e}; throw e; } if logger.isInfoEnabled()) { logger.info(”{} - registration status: {} ”, PREFIX + appPathidentifier, httpResponse.getStatusCode()); } return httpResponse.getStatusCode() == 204 ; }
服务续约
boolean renew () { EurekaHttpResponse<Instanceinfo> httpResponse; try { httpResponse = eurekaTransport.registrationClient.sendHeartBeat(InstanceInfo.getAppName(), instanceinfo.getid() ,instanceinfo , null); logger.debug (”{) - Heartbeat status: {) ”, PREFIX + appPathidentifier, httpResponse.getStatusCode()); if {httpResponse. getStatusCode {) == 4 04) { REREGISTER COUNTER.increment() ; logger.info(” {) - Re-registering apps/{) ”, PREFIX + appPathidentifier, instanceinfo.getAppName{)); return register(); } return httpResponse. getStatusCode () == 200; )catch(Throwable e) { logger.error(” {) - was unable to send heartbeat !”, PREFIX + appPathidentifier, e); return false;
为什么获取服务实例特别慢?
1、Eureka Client的注册延迟
Eureka Client启动之后,不是立即向Eureka Server注册的,而是有一个延迟向服务端注册的时间。通过跟踪源码,可以发现默认的延迟时间为 40 秒。
2、Eureka Server的响应缓存
Eureka Server维护每30秒更新一次响应缓存,可通过更改配置eureka.server.responseCacheUpdatelntervalMs来修改。
3、LoadBalancer的缓存
Ribbon的负载平衡器从本地的Eureka Consumer获取服务注册列表信息。Ribbon本身还维护了缓存,以避免每个请求都需要从Eureka Consumer获取服务注册列表。此缓存每30秒刷新一次。
Ribbon
1、RestTemplate
RestTemplate是用来消费REST服务的,所以RestTemplate的主要方法都与REST的http协议的一些方法紧密相连,例如HEAD GET POST PUT DELETE OPTIONS等方法,对应的RestTemplate中的方法为headForHeaders()、getForObject()、postForObject()、put()和delete()等。
示例:获取百度的html
@RestController public class RestTestController { @GetMapping (”/testRest”) public String testRest() { RestTemplate restTemplate=new RestTemplate() ; //代表发送get请求,并且返回结果为String类型 return restTemplate.getForObject(”https://www.baidu.com/”, String.class) ; } }
2、负载均衡
负载均衡是指将负载分摊到多个执行单元上,常见的负载均衡有两种方式。一种是独立进程单元,通过负载均衡策略,将请求转发到不同的执行单元上,例如Ngnix。另一种是将负载均衡逻辑以代码的形式封装到服务消费者的客户端上,服务消费者客户端维护了一份服务提供者的信息列表,有了信息列表,通过负载均衡策略将请求分摊给多个服务提供者,从而达到负载均衡的目的。
简单来说,一个在服务端做负载均衡,一种则提前在客户端做负载均衡,Ribbon则是后面一种。
3、Ribbon
Spring Cloud构建的微服务系统中,Ribbon作为服务消费者的负载均衡器,有两种使用方式,一种是和RestTemplate相结合,另一种是和Feign 相结合。
在本人的上一篇博文中有Ribbon的demo:spring cloud深入学习(三)-----服务消费
@Configuration public class RibbonConfig { @Bean @LoadBalanced RestTemplate restTemplate () { return new RestTemplate(); } }
Ribbon实现方式:RestTemplate + @LoadBalanced
4、LoadBalancerClient
LoadBalancerClient是实现负载均衡的核心类,可以通过如下方式获取到服务实例:
@RestController public class RibbonController { @Autowired private LoadBalancerClient loadBalancer; @GetMapping(”/testRibbon”) public String testRibbon() { Serviceinstance instance = loadBalancer.choose(”eureka-provider"); return instance.getHost() + ”:” + instance.getPort(); } }
总结:RestTemplate加上@LoadBalance注解后,维护了一个被@LoadBalance注解的RestTemplate列表;
RestTemplate列表中的对象通过拦截器将调用交给Ribbon的LoadBalancerClient去处理;
LoadBalancerClient具体交给了ILoadBalancer来处理,ILoadBalancer通过配置IRule、IPing等,获取注册列表的信息,默认10秒向EurekaClient 发送一次“ping”,检查是否要更新服务的注册列表信息;
在得到服务注册列表信息后,ILoadBalancer根据IRule策略进行负载均衡。
Feign
Spring Cloud Feign是一套基于Netflix Feign实现的声明式服务调用客户端。它使得编写Web服务客户端变得更加简单。我们只需要通过创建接口并用注解来配置它既可完成对Web服务接口的绑定。它具备可插拔的注解支持,包括Feign注解、JAX-RS注解。它也支持可插拔的编码器和解码器。Spring Cloud Feign还扩展了对Spring MVC注解的支持,同时还整合了Ribbon和Eureka来提供均衡负载的HTTP客户端实现。
1、pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.4.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.ty</groupId> <artifactId>eureka-feign-consumer</artifactId> <version>0.0.1-SNAPSHOT</version> <name>eureka-feign-consumer</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <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-client</artifactId> <version>2.1.1.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> <version>2.1.1.RELEASE</version> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Finchley.RELEASE</version> <type>pom</type> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
2、application.properties
# 服务名称 spring.application.name=eureka-feign-consumer # 端口号 server.port=4001 # 服务注册中心地址 eureka.client.serviceUrl.defaultZone=http://localhost:1001/eureka/
3、主类
package com.ty.eurekafeignconsumer; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.cloud.openfeign.EnableFeignClients; @SpringBootApplication @EnableEurekaClient //让程序拥有feign的功能 @EnableFeignClients public class EurekaFeignConsumerApplication { public static void main(String[] args) { SpringApplication.run(EurekaFeignConsumerApplication.class, args); } }
4、controller
package com.ty.eurekafeignconsumer.controller; import com.ty.eurekafeignconsumer.service.EurekaClientFeign; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController public class FeignController { @Autowired private EurekaClientFeign eurekaClientFeign; @GetMapping("/hi") public String sayHi(@RequestParam(defaultValue = "马云", required = false) String name) { return eurekaClientFeign.sayHiFromClientEureka(name); } }
5、service
package com.ty.eurekafeignconsumer.service; import com.ty.eurekafeignconsumer.config.FeignConfig; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; @FeignClient(value = "eureka-provider", configuration = FeignConfig.class) public interface EurekaClientFeign { //只需要在接口中定义方法即可。调用eureka-provider服务的firstCall方法,并且feign集成了Ribbon @GetMapping(value = "/firstCall") String sayHiFromClientEureka(@RequestParam("name") String name); }
6、config
package com.ty.eurekafeignconsumer.config; import feign.Retryer; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import static java.util.concurrent.TimeUnit.SECONDS; @Configuration public class FeignConfig { @Bean public Retryer feignRetryer() { //调用远程服务失败后,会进行重试。重试间隔为1s,最大尝试次数为5 return new Retryer.Default(100, SECONDS.toMillis(1), 5); } }
运行结果:
原理:@FeignClient注解用于创建声明式API接口,该接口是RSTful风格的。Feign是个伪Java Http客户端,Feign不做任何的请求处理。Feign通过处理注解生成request模板,从而简化Http API的开发。在发送Http Request 请求之前Feign通过处理注解的方式替换掉Request模板中的参数,生成真正的Request,并交给Java Http客户端去处理。
请求架构图如下:
总结:feign处理过程如下:
- 首先通过@EnableFeignClients注解开启FeignClient的功能。只有这个注解存在,才会在程序启动时开启对@FeignClient注解的包扫描;
- 根据Feign的规则实现接口,井在接口上面加上@FeignClient注解;
- 程序启动后,会进行包扫描,扫描所有的@FeignClient注解的类,并将这些信息注入IoC容器中;
- 当接口的方法被调用时,通过JDK的代理来生成具体的RequestTemplate模板对象;
- 根据RequestTemplate再生成Http请求的Request对象;
- Request对象交给Client去处理,其中Client的网络请求框架可以是HttpURLConnection、HttpClient和OkHttp;
- 最后Client被封装到LoadBalanceClient类,这个类结合类Ribbon做到了负载均衡
代码已上传到github,可自行下载,记得给小心心哦!
eureka-feign-consumer地址:https://github.com/ali-mayun/eureka-feign-consumer