现在流行的是 Spring Cloud基于NetFlix解决 方案提供的 解决方案 。那么让我们来演示如何使用它。原文来自tomask79,点击标题见原文!
1. 注册中心
基于Spring Cloud的MicroServices的Hearth是Eureka Server。也称为Discovery Server。因为该服务器保存有关您的系统可以在其运行位置,健康状况和其他方面使用的所有微服务的信息。很明显,在生产中,这个服务器需要具有高可用性。使用Spring Cloud,您可以通过将EnableEurekaServer注释添加到Spring Boot应用程序的启动类来创建此服务器。
@SpringBootApplication @EnableEurekaServer <b>public</b> <b>class</b> SpringMicroserviceRegistryApplication { <b>public</b> <b>static</b> <b>void</b> main(String[] args) { SpringApplication.run(SpringMicroserviceRegistryApplication.<b>class</b>, args); } }
只需要这一行代码就可以启动eureka服务器了,默认在 http://localhost:8761 可访问注册中心,可以在application.properties/yaml中配置端口等特定配置:
server.port=9761 eureka.client.register-with-eureka=false eureka.client.fetch-registry=false logging.level.com.netflix.eureka=OFF logging.level.com.netflix.discovery=OFF
在调试环境可以配置关闭一些参数:
微服务注册到Discovery Server
现在让我们实现我们的微服务。首先,Spring Boot应用程序需要知道在哪里注册您的微服务:
spring.application.name=personsService eureka.client.serviceUrl.defaultZone=http:<font><i>//localhost:9761/eureka</i></font><font> </font>
还要注意微服务名为“ personsService ”。现在编码微服务,是一个简单的REST控制器返回一些JSON:
@RestController <b>public</b> <b>class</b> PersonsController { @RequestMapping(<font>"/persons"</font><font>) <b>public</b> Persons getPersonById() { <b>final</b> Persons persons = <b>new</b> Persons(); <b>final</b> Person person = <b>new</b> Person(); person.setName(</font><font>"Tomas"</font><font>); person.setSurname(</font><font>"Kloucek"</font><font>); person.setDepartment(</font><font>"Programmer"</font><font>); persons.getPersons().add(person); <b>return</b> persons; } } </font>
Spring Cloud MicroService的客户端
现在你可以访问 http://localhost:8080/persons ,也可以使用使用RestTemplate 直接访问这个微服务, 但这样做是愚蠢的。更聪明的是让您的客户端通过MicroService id(在我的情况下是personService)首先访问注册服务器,询问Ribbon loadbalancer负载平衡器来获取微服务的URL,然后调用该服务。查看客户端代码:
首先,我们需要让我们的客户端成为发现服务器客户端。因此EnableDiscoveryClient注释。(Spring Boot 2以后可不需要此注释)
@SpringBootApplication @EnableDiscoveryClient <b>public</b> <b>class</b> SpringMicroserviceClientApplication { <b>public</b> <b>static</b> <b>void</b> main(String[] args) { SpringApplication.run(SpringMicroserviceClientApplication.<b>class</b>, args); } }
然后我们需要让Ribbon loadbalancer通过提供服务ID来选择微服务的一个实例。
@Component <b>public</b> <b>class</b> MicroServiceClient implements CommandLineRunner { @Autowired <b>private</b> LoadBalancerClient loadBalancer; @Override <b>public</b> <b>void</b> run(String... arg0) throws Exception { <b>final</b> RestTemplate restTemplate = <b>new</b> RestTemplate(); <b>final</b> ServiceInstance serviceInstance = loadBalancer.choose(<font>"personsService"</font><font>); <b>if</b> (serviceInstance != <b>null</b>) { System.out.println(</font><font>"Invoking instance at URL: "</font><font>+serviceInstance.getUri()); System.out.println( restTemplate.getForObject(serviceInstance.getUri()+</font><font>"/persons"</font><font>, String.<b>class</b>)); } <b>else</b> { System.out.println(</font><font>"Didn't find any running instance of personsService at DiscoveryServer!"</font><font>); } } } </font>
LoadBalancerClient将在Discovery服务器上选择注册的一个正在运行的微服务实例!(banq注:通常需要通过Feign实现JSON对象转换的方式访问远程微服务)
运行演示
下载 演示
运行发现服务器
运行MicroService
运行客户端
2. 在Spring Cloud 微服务中使用断路器Circuit-Breaker
在编写微服务时,如果无法访问特定微服务,需要告诉微服务要执行什么操作。也就是说当被访问的微服务不可用时,有几个选项:
用于实现此目的的广泛使用的模式是 断路器 模式。在你继续阅读之前,一定要阅读Martin Fowler定义的这个描述。
无论如何,简而言之。断路器的作用是将MicroService调用方法包装在代理监控MicroService调用失败中。如果失败将达到某个阈值,则所有其他调用将以异常结束,或者如果您使用备份计划调用来定义... Spring Cloud具有出色的实现,称为 Hystrix 。
使用Hystrix断路器
使用之前的personsService这个微服务,使用以下规则向调用者添加容错逻辑:
每20秒(metrics.rollingStats.timeInMilliseconds)从6个请求(收集统计数据circuitBreaker.requestVolumeThreshold),如果所有的人都用崩溃(截至circuitBreaker.errorThresholdPercentage)然后重定向调用到后备逻辑。每隔5秒尝试一次这个微服务是否可用(circuitBreaker.sleepWindowInMilliseconds)。
提到的MicroService调用组件将如下所示:
@Component <b>public</b> <b>class</b> MicroServiceInvoker { @Autowired <b>private</b> LoadBalancerClient loadBalancer; @HystrixCommand(fallbackMethod = <font>"invokeMicroServiceFallback"</font><font>, commandProperties = { @HystrixProperty(name = </font><font>"circuitBreaker.errorThresholdPercentage"</font><font>, value = </font><font>"100"</font><font>), @HystrixProperty(name = </font><font>"metrics.rollingStats.timeInMilliseconds"</font><font>, value = </font><font>"20000"</font><font>), @HystrixProperty(name = </font><font>"circuitBreaker.requestVolumeThreshold"</font><font>, value = </font><font>"6"</font><font>), @HystrixProperty(name = </font><font>"circuitBreaker.sleepWindowInMilliseconds"</font><font>, value = </font><font>"5000"</font><font>) } ) <b>public</b> <b>void</b> invokeMicroService() { <b>final</b> RestTemplate restTemplate = <b>new</b> RestTemplate(); <b>final</b> ServiceInstance serviceInstance = loadBalancer.choose(</font><font>"personsService"</font><font>); <b>if</b> (serviceInstance != <b>null</b>) { System.out.println(</font><font>"Invoking instance at URL: "</font><font>+serviceInstance.getUri()); System.out.println(</font><font>"Result :"</font><font>+ restTemplate.getForObject(serviceInstance.getUri()+</font><font>"/persons"</font><font>, String.<b>class</b>)); } <b>else</b> { System.out.println(</font><font>"Service is down..."</font><font>); <b>throw</b> <b>new</b> IllegalStateException(</font><font>"PersonsService is not running!"</font><font>); } } <b>public</b> <b>void</b> invokeMicroServiceFallback() { System.out.println(</font><font>"Waiting for circuit-breaker to close again..."</font><font>); } } </font>
测试断路器
下载 源码
运行发现服务器:
mvn clean install (in the spring-microservice-registry directory with pom.xml) java -jar target/demo-0.0.1-SNAPSHOT.war
运行微服务
mvn clean install (in the spring-microservice-service directory with pom.xml) java -jar target/demo-0.0.1-SNAPSHOT.war
在http://localhost:9761确认微服务已经注册。
运行另外一个微服务,它是上面微服务客户端,调用者:
mvn clean install (in the spring-microservice-client directory with pom.xml) java -jar target/demo-0.0.1-SNAPSHOT.war
启动客户端你会看到:
Invocation number :16 Invoking instance at URL: http:<font><i>//192.168.1.112:8080</i></font><font> Result :{</font><font>"persons"</font><font>:[{</font><font>"name"</font><font>:</font><font>"Tomas"</font><font>,</font><font>"surname"</font><font>:</font><font>"Kloucek"</font><font>,</font><font>"department"</font><font>:</font><font>"Programmer"</font><font>}]} Invocation number :17 Invoking instance at URL: http:</font><font><i>//192.168.1.112:8080</i></font><font> Result :{</font><font>"persons"</font><font>:[{</font><font>"name"</font><font>:</font><font>"Tomas"</font><font>,</font><font>"surname"</font><font>:</font><font>"Kloucek"</font><font>,</font><font>"department"</font><font>:</font><font>"Programmer"</font><font>}]} Invocation number :18 Invoking instance at URL: http:</font><font><i>//192.168.1.112:8080</i></font><font> Result :{</font><font>"persons"</font><font>:[{</font><font>"name"</font><font>:</font><font>"Tomas"</font><font>,</font><font>"surname"</font><font>:</font><font>"Kloucek"</font><font>,</font><font>"department"</font><font>:</font><font>"Programmer"</font><font>}]} Invocation number :19 </font>
关闭personService微服务 20秒, 你会看到输出:
Invocation number :18 Invoking instance at URL: http:<font><i>//192.168.1.112:8080</i></font><font> Waiting <b>for</b> circuit-breaker to close again... Invocation number :19 Invoking instance at URL: http:</font><font><i>//192.168.1.112:8080</i></font><font> Waiting <b>for</b> circuit-breaker to close again... </font>
一会儿看到输出:
Invocation number :78 Waiting <b>for</b> circuit-breaker to close again... Invocation number :79 Waiting <b>for</b> circuit-breaker to close again...
每隔5秒改变到:
Invoking instance at URL: http:<font><i>//192.168.1.112:8080</i></font><font> Waiting <b>for</b> circuit-breaker to close again... </font>
当Hystrix测试微服务实例是否再次正常运行时,在你运行微服务之后,断路器应该是关闭的,微服务客户端在启动运行时就能发现这个情况...总而言之,断路器有以下状态:
3. 使用Netlix Feign作为调用微服务
之前展示了一个微服务客户端如何在 Ribbon的 帮助下使用 RestTemplate 调用另外一个微服务的:
@Component <b>public</b> <b>class</b> MicroServiceClient implements CommandLineRunner { @Autowired <b>private</b> LoadBalancerClient loadBalancer; @Override <b>public</b> <b>void</b> run(String... arg0) throws Exception { <b>final</b> RestTemplate restTemplate = <b>new</b> RestTemplate(); <b>final</b> ServiceInstance serviceInstance = loadBalancer.choose(<font>"personsService"</font><font>); <b>if</b> (serviceInstance != <b>null</b>) { System.out.println(</font><font>"Invoking instance at URL: "</font><font>+serviceInstance.getUri()); System.out.println( restTemplate.getForObject(serviceInstance.getUri()+</font><font>"/persons"</font><font>, String.<b>class</b>)); } <b>else</b> { System.out.println(</font><font>"Didn't find any running instance of personsService at DiscoveryServer!"</font><font>); } } } </font>
老实说,这里有太多的样板代码,在大型系统中会经常重复。这是引入 Feign 的理由:
Feign为您带来以下好处:
使用Feign的先前代码将如下所示:
远程微服务调用返回的是原始JSON,那么大多数时候你都想要的是Java POJO。因此,让我们创建另一个调用相同MicroService的Feign客户端声明:
@Component @FeignClient(<font>"personsService"</font><font>) <b>public</b> <b>interface</b> JacksonMicroServiceFeignClient { @RequestMapping(method = RequestMethod.GET, value = </font><font>"/persons"</font><font>) ClientPersonsTO invokePersonsMicroService(); } </font>
只要调用上面这个接口就可以实现远程微服务的调用,比如:
@Autowired <b>private</b> JacksonMicroServiceFeignClient jacksonMicroServiceFeignClient; . . <b>final</b> ClientPersonsTO clientPersonsTO = jacksonMicroServiceFeignClient. invokePersonsMicroService();
而远程微服务的代码是这个样子:
@RestController <b>public</b> <b>class</b> PersonsController { @RequestMapping(<font>"/persons"</font><font>) <b>public</b> Persons getPersonById() { <b>final</b> Persons persons = <b>new</b> Persons(); <b>final</b> Person person = <b>new</b> Person(); person.setName(</font><font>"Tomas"</font><font>); person.setSurname(</font><font>"Kloucek"</font><font>); person.setDepartment(</font><font>"Programmer"</font><font>); persons.getPersons().add(person); <b>return</b> persons; } } </font>
远程微服务返回的是Persons对象,这个Persons是通过转换成JSON到达你的客户端的,你的Feign客户端将这个JSON字符串又转换为ClientPersonsTO,两者名称可以不同,但是内部数据结构应该相同额。
将Feign与Hystrix结合起来
在具有maven依赖关系的类路径中包含Hystrix:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-hystrix</artifactId> </dependency>
当然你也应该包含进Feign等组件,这些配置可以通过Idea等Spring 导航选择相应组件后自动生成pom.xml配置。
从现在开始,Feign将用Hystrix封装每个MS微服务呼叫。您可以通过以下设置禁用它:feign.hystrix.enabled = false应用程序属性。您的Feign客户端接口方法也可以返回HystrixCommand以允许调用者使用带有Observable Java的反应模式。那么让我们创建Feign Hystrix客户端:
@Component @FeignClient(value = <font>"personsService"</font><font>, fallback = MicroServiceHystrixFallback.<b>class</b>) <b>public</b> <b>interface</b> HystrixMicroServiceFeignClient { @RequestMapping(method = RequestMethod.GET, value = </font><font>"/persons"</font><font>) ClientPersonsTO getPersonsWithHystrix(); } </font>
如果断路器打开,我在这里定义了后备也就是快速失败或回退返回。顺便说一句,当返回HystrixCommand时,还不支持fallback 。现在,使用 archaius 配置Feign Hystrix,它使用方法名作为command键,因此application.properties中的配置将是:
server.port=8888 eureka.client.serviceUrl.defaultZone=http:<font><i>//localhost:9761/eureka</i></font><font> hystrix.command.getPersonsWithHystrix.fallback.enabled=<b>true</b> hystrix.command.getPersonsWithHystrix.metrics.rollingStats.timeInMilliseconds=35000 hystrix.command.getPersonsWithHystrix.circuitBreaker.sleepWindowInMilliseconds=5000 hystrix.command.getPersonsWithHystrix.circuitBreaker.requestVolumeThreshold=6 hystrix.command.getPersonsWithHystrix.circuitBreaker.errorThresholdPercentage=100 hystrix.command.getPersonsWithHystrix.execution.isolation.strategy=THREAD </font>
测试
下载 源码
运行Eureka:
mvn clean install (in the spring-microservice-registry directory with pom.xml) java -jar target/demo-0.0.1-SNAPSHOT.war
运行被调用微服务:
mvn clean install (in the spring-microservice-service directory with pom.xml) java -jar target/demo-0.0.1-SNAPSHOT.war verify with http:<font><i>//localhost:9761 that MicroService is registered.</i></font><font> </font>
运行调用微服务:
mvn clean install (in the spring-microservice-client directory with pom.xml) java -jar target/demo-0.0.1-SNAPSHOT.war [0-2]