springcloud 总集:https://www.tapme.top/blog/detail/2019-02-28-11-33java
本次用到所有代码见文章最下方。git
全部的系统都会遇到故障,分布式系统单点故障几率更高。如何构建应用程序来应对故障,是每一个软件开发人员工做的关键部分。可是一般在构建系统时,大多数工程师只考虑到基础设施或关键服务完全发生故障,使用诸如集群关键服务器、服务间的负载均衡以及异地部署等技术。尽管这些方法考虑到组件系统的完全故障,但他们之解决了构建弹性系统的一小部分问题。当服务崩溃时,很容易检测到该服务以及失效,所以应用程序能够饶过它。然而,当服务运行缓慢时,检测到这个服务性能愈加低下并绕过它是很是困难的,由于如下几个缘由:github
性能较差的远程服务会致使很大的潜在问题,它们不只难以检测,还会触发连锁反应,从而影响整个应用程序生态系统。若是没有适当的保护措施,一个性能不佳的服务能够迅速拖垮整个应用程序。基于云、基于微服务的应用程序特别容易受到这些类型的终端影响,由于这些应用由大量细粒度的分布式服务组成,这些服务在完成用户的事务时涉及不一样的基础设施。spring
客户端弹性模式是在远程服务发生错误或表现不佳时保护远程资源(另外一个微服务调用或者数据库查询)免于崩溃。这些模式的目标是为了能让客户端“快速失败”,不消耗诸如数据库链接、线程池之类的资源,还能够避免远程服务的问题向客户端的消费者进行传播,引起“雪崩”效应。spring cloud 主要使用的有四种客户端弹性模式:数据库
上一篇已经说过,这里再也不赘述。json
本模式模仿的是电路中的断路器。有了软件断路器,当远程服务被调用时,断路器将监视这个调用,若是调用时间太长,断路器将介入并中断调用。此外,若是对某个远程资源的调用失败次数达到某个阈值,将会采起快速失败策略,阻止未来调用失败的远程资源。服务器
<!-- more -->微信
当远程调用失败时,将执行替代代码路径,并尝试经过其余方式来处理操做,而不是产生一个异常。也就是为远程操做提供一个应急措施,而不是简单的抛出异常。架构
舱壁模式是创建在造船的基础概念上。咱们都知道一艘船会被划分为多个水密舱(舱壁),于是即便少数几个部位被击穿漏水,整艘船并不会被淹没。将这个概念带入到远程调用中,若是全部调用都使用的是同一个线程池来处理,那么颇有可能一个缓慢的远程调用会拖垮整个应用程序。在舱壁模式中能够隔离每一个远程资源,并分配各自的线程池,使之互不影响。app
下图展现了这些模式是如何运用到微服务中的:
使用 Netflix 的 Hystrix 库来实现上述弹性模式。继续使用上一节的项目,给 licensingservice 服务实现弹性模式。
首先修改 POM 文件,添加下面两个依赖:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-hystrix</artifactId> </dependency> <!--本依赖不是必须的,spring-cloud-starter-hystrix已经带了,可是在Camden.SR5发行版本中使用了1.5.6,这个版本有一个不一致的地方,在没有后备的状况下会抛出java.lang.reflect.UndeclaredThrowableException而不是com.netflix.hystrix.exception.HystrixRuntimeException, 在后续版本中修复了这个问题--> <dependency> <groupId>com.netflix.hystrix</groupId> <artifactId>hystrix-javanica</artifactId> <version>1.5.9</version> </dependency>
而后在启动类上加入@EnableCircuitBreaker
启用 Hystrix。
首先修改 organizationservice 项目中的 OrganizationController,模拟延迟,每隔两次让线程 sleep 2 秒
@RestController public class OrganizationController { private static int count=1; @GetMapping(value = "/organization/{orgId}") public Object getOrganizationInfo(@PathVariable("orgId") String orgId) throws Exception{ if(count%2==0){ TimeUnit.SECONDS.sleep(2); } count++; Map<String, String> data = new HashMap<>(2); data.put("id", orgId); data.put("name", orgId + "公司"); return data; } }
只需在方法上添加@HystrixCommand
,便可实现超时短路。若是 Spring 扫描到该注解注释的类,它将动态生成一个代理,来包装这个方法,并经过专门用于处理远程调用的线程池来管理对该方法的全部调用。
修改 licensingservice 服务中的 OrganizationByRibbonService,OrganizationFeignClient,给其中的方法加上@HystrixCommand
的注解。而后再访问接口localhost:10011/licensingByRibbon/11313,localhost:10011/licensingByFeign/11313。屡次访问可发现抛出错误com.netflix.hystrix.exception.HystrixRuntimeException
,断路器生效,默认状况下操时时间为 1s。
{ "timestamp": 1543823192424, "status": 500, "error": "Internal Server Error", "exception": "com.netflix.hystrix.exception.HystrixRuntimeException", "message": "OrganizationFeignClient#getOrganization(String) timed-out and no fallback available.", "path": "/licensingByFeign/11313/" }
可经过设置注解参数来修改操时时间。设置超时时间大于 2s 后便不会报操时错误。(不知道为何在 Feign 中设置失败,ribbon 中正常。)。通常都是将配置写在配置文件中。
@HystrixCommand(commandProperties = { @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "20000") })
因为远程资源的消费者和资源自己之间存在存在一个"中间人",所以开发人员可以拦截服务故障,并选择替代方案。在 Hystrix 中进行后备处理,很是容易实现。
只需在@HystrixCommand
注解中加入属性 fallbackMethod="methodName",那么在执行失败时,便会执行后备方法。注意防备方法必须和被保护方法在同一个类中,而且方法签名必须相同。修改 licensingservice 中 service 包下的 OrganizationByRibbonService 类,改成以下:
@Component public class OrganizationByRibbonService { private RestTemplate restTemplate; @Autowired public OrganizationByRibbonService(RestTemplate restTemplate) { this.restTemplate = restTemplate; } @HystrixCommand(commandProperties = { @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1000") },fallbackMethod = "getOrganizationWithRibbonBackup") public Organization getOrganizationWithRibbon(String id) throws Exception { ResponseEntity<Organization> responseEntity = restTemplate.exchange("http://organizationservice/organization/{id}", HttpMethod.GET, null, Organization.class, id); return responseEntity.getBody(); } public Organization getOrganizationWithRibbonBackup(String id)throws Exception{ Organization organization = new Organization(); organization.setId("0"); organization.setName("组织服务调用失败"); return organization; } }
启动应用,屡次访问localhost:10011/licensingByRibbon/11313/,能够发现调用失败时,会启用后备方法。
在 feign 中实现后备模式,须要编写一个 feign 接口的实现类,而后在 feign 接口中指定该类。以 licensingservice 为例。首先在 client 包中添加一个 OrganizationFeignClientImpl 类,代码以下:
@Component public class OrganizationFeignClientImpl implements OrganizationFeignClient{ @Override public Organization getOrganization(String orgId) { Organization organization=new Organization(); organization.setId("0"); organization.setName("后备模式返回的数据"); return organization; } }
而后修改 OrganizationFeignClient 接口的注解,将@FeignClient("organizationservice")
改成@FeignClient(name="organizationservice",fallback = OrganizationFeignClientImpl.class
。
重启项目,屡次访问localhost:10011/licensingByFeign/11313/,可发现后备服务起做用了。
在确认是否要启用后备服务时,要注意如下两点:
在基于微服务的应用程序中,一般须要调用多个微服务来完成特定的任务,在不适用舱壁的模式下,这些调用默认是使用同一批线程来执行调用的,而这些线程是为了处理整个 Java 容器的请求而预留的。所以在存在大量请求的状况下,一个服务出现性能问题会致使 Java 容器内的全部线程被占用,同时阻塞新请求,最终容器完全崩溃。
Hystrix 使用线程池来委派全部对远程服务的调用,默认状况下这个线程池有 10 个工做线程。可是这样很容易出现一个运行缓慢的服务占用所有的线程,全部 hystrix 提供了一种一种易于使用的机制,在不一样的远程资源调用间建立‘舱壁’,将不一样服务的调用隔离到不一样的线程池中,使之互不影响。
要实现隔离的线程池,只须要在@HystrixCommand
上加入线程池的注解,这里以 ribbon 为例(Feign 相似)。修改 licensingservice 中 service 包下的 OrganizaitonByRibbonService 类,将getOrganizationWithRibbon
方法的注解改成以下:
@HystrixCommand(commandProperties = { @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1000") }, fallbackMethod = "getOrganizationWithRibbonBackup", threadPoolKey = "licenseByOrgThreadPool", threadPoolProperties = { @HystrixProperty(name = "coreSize", value = "30"), @HystrixProperty(name = "maxQueueSize", value = "10") })
若是将maxQueueSize
属性值设为-1,将使用SynchronousQueue
保存全部的传入请求,同步队列会强制要求正在处理中的请求数量永远不能超过线程池的大小。设为大于 1 的值将使用LinkedBlockingQueue
。
注意:示例代码中都是硬编码属性值到 Hystrix 注解中的。在实际应用环境中,通常都是将配置项配置在 Spring Cloud Config 中的,方便统一管理。
本次用到所有代码:点击跳转
本篇原创发布于:FleyX 的我的博客
扫码关注微信公众号:FleyX 学习笔记,获取更多干货