learn.lianglianglee.com/专栏/SpringCloud微服务实战(完)/08 服务响应慢或服务不可用怎么办——快速失败与服务降级.md.html
2022-08-14 03:40:33 +08:00

335 lines
21 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<!-- saved from url=(0046)https://kaiiiz.github.io/hexo-theme-book-demo/ -->
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
<link rel="icon" href="/static/favicon.png">
<title>08 服务响应慢或服务不可用怎么办——快速失败与服务降级.md.html</title>
<!-- Spectre.css framework -->
<link rel="stylesheet" href="/static/index.css">
<!-- theme css & js -->
<meta name="generator" content="Hexo 4.2.0">
</head>
<body>
<div class="book-container">
<div class="book-sidebar">
<div class="book-brand">
<a href="/">
<img src="/static/favicon.png">
<span>技术文章摘抄</span>
</a>
</div>
<div class="book-menu uncollapsible">
<ul class="uncollapsible">
<li><a href="/" class="current-tab">首页</a></li>
</ul>
<ul class="uncollapsible">
<li><a href="../">上一级</a></li>
</ul>
<ul class="uncollapsible">
<li>
<a href="/专栏/SpringCloud微服务实战/00 开篇导读.md.html">00 开篇导读</a>
</li>
<li>
<a href="/专栏/SpringCloud微服务实战/01 以真实“商场停车”业务切入——需求分析.md.html">01 以真实“商场停车”业务切入——需求分析</a>
</li>
<li>
<a href="/专栏/SpringCloud微服务实战/02 具象业务需求再抽象分解——系统设计.md.html">02 具象业务需求再抽象分解——系统设计</a>
</li>
<li>
<a href="/专栏/SpringCloud微服务实战/03 第一个 Spring Boot 子服务——会员服务.md.html">03 第一个 Spring Boot 子服务——会员服务</a>
</li>
<li>
<a href="/专栏/SpringCloud微服务实战/04 如何维护接口文档供外部调用——在线接口文档管理.md.html">04 如何维护接口文档供外部调用——在线接口文档管理</a>
</li>
<li>
<a href="/专栏/SpringCloud微服务实战/05 认识 Spring Cloud 与 Spring Cloud Alibaba 项目.md.html">05 认识 Spring Cloud 与 Spring Cloud Alibaba 项目</a>
</li>
<li>
<a href="/专栏/SpringCloud微服务实战/06 服务多不易管理如何破——服务注册与发现.md.html">06 服务多不易管理如何破——服务注册与发现</a>
</li>
<li>
<a href="/专栏/SpringCloud微服务实战/07 如何调用本业务模块外的服务——服务调用.md.html">07 如何调用本业务模块外的服务——服务调用</a>
</li>
<li>
<a class="current-tab" href="/专栏/SpringCloud微服务实战/08 服务响应慢或服务不可用怎么办——快速失败与服务降级.md.html">08 服务响应慢或服务不可用怎么办——快速失败与服务降级</a>
</li>
<li>
<a href="/专栏/SpringCloud微服务实战/09 热更新一样更新服务的参数配置——分布式配置中心.md.html">09 热更新一样更新服务的参数配置——分布式配置中心</a>
</li>
<li>
<a href="/专栏/SpringCloud微服务实战/10 如何高效读取计费规则等热数据——分布式缓存.md.html">10 如何高效读取计费规则等热数据——分布式缓存</a>
</li>
<li>
<a href="/专栏/SpringCloud微服务实战/11 多实例下的定时任务如何避免重复执行——分布式定时任务.md.html">11 多实例下的定时任务如何避免重复执行——分布式定时任务</a>
</li>
<li>
<a href="/专栏/SpringCloud微服务实战/12 同一套服务如何应对不同终端的需求——服务适配.md.html">12 同一套服务如何应对不同终端的需求——服务适配</a>
</li>
<li>
<a href="/专栏/SpringCloud微服务实战/13 采用消息驱动方式处理扣费通知——集成消息中间件.md.html">13 采用消息驱动方式处理扣费通知——集成消息中间件</a>
</li>
<li>
<a href="/专栏/SpringCloud微服务实战/14 Spring Cloud 与 Dubbo 冲突吗——强强联合.md.html">14 Spring Cloud 与 Dubbo 冲突吗——强强联合</a>
</li>
<li>
<a href="/专栏/SpringCloud微服务实战/15 破解服务中共性问题的繁琐处理方式——接入 API 网关.md.html">15 破解服务中共性问题的繁琐处理方式——接入 API 网关</a>
</li>
<li>
<a href="/专栏/SpringCloud微服务实战/16 服务压力大系统响应慢如何破——网关流量控制.md.html">16 服务压力大系统响应慢如何破——网关流量控制</a>
</li>
<li>
<a href="/专栏/SpringCloud微服务实战/17 集成网关后怎么做安全验证——统一鉴权.md.html">17 集成网关后怎么做安全验证——统一鉴权</a>
</li>
<li>
<a href="/专栏/SpringCloud微服务实战/18 多模块下的接口 API 如何统一管理——聚合 API.md.html">18 多模块下的接口 API 如何统一管理——聚合 API</a>
</li>
<li>
<a href="/专栏/SpringCloud微服务实战/19 数据分库后如何确保数据完整性——分布式事务.md.html">19 数据分库后如何确保数据完整性——分布式事务</a>
</li>
<li>
<a href="/专栏/SpringCloud微服务实战/20 优惠券如何避免超兑——引入分布式锁.md.html">20 优惠券如何避免超兑——引入分布式锁</a>
</li>
<li>
<a href="/专栏/SpringCloud微服务实战/21 如何查看各服务的健康状况——系统应用监控.md.html">21 如何查看各服务的健康状况——系统应用监控</a>
</li>
<li>
<a href="/专栏/SpringCloud微服务实战/22 如何确定一次完整的请求过程——服务链路跟踪.md.html">22 如何确定一次完整的请求过程——服务链路跟踪</a>
</li>
<li>
<a href="/专栏/SpringCloud微服务实战/23 结束语.md.html">23 结束语</a>
</li>
</ul>
</div>
</div>
<div class="sidebar-toggle" onclick="sidebar_toggle()" onmouseover="add_inner()" onmouseleave="remove_inner()">
<div class="sidebar-toggle-inner"></div>
</div>
<script>
function add_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.add('show')
}
function remove_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.remove('show')
}
function sidebar_toggle() {
let sidebar_toggle = document.querySelector('.sidebar-toggle')
let sidebar = document.querySelector('.book-sidebar')
let content = document.querySelector('.off-canvas-content')
if (sidebar_toggle.classList.contains('extend')) { // show
sidebar_toggle.classList.remove('extend')
sidebar.classList.remove('hide')
content.classList.remove('extend')
} else { // hide
sidebar_toggle.classList.add('extend')
sidebar.classList.add('hide')
content.classList.add('extend')
}
}
function open_sidebar() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.add('show')
overlay.classList.add('show')
}
function hide_canvas() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.remove('show')
overlay.classList.remove('show')
}
</script>
<div class="off-canvas-content">
<div class="columns">
<div class="column col-12 col-lg-12">
<div class="book-navbar">
<!-- For Responsive Layout -->
<header class="navbar">
<section class="navbar-section">
<a onclick="open_sidebar()">
<i class="icon icon-menu"></i>
</a>
</section>
</header>
</div>
<div class="book-content" style="max-width: 960px; margin: 0 auto;
overflow-x: auto;
overflow-y: hidden;">
<div class="book-post">
<p id="tip" align="center"></p>
<div><h1>08 服务响应慢或服务不可用怎么办——快速失败与服务降级</h1>
<p>上个章节已经基于 OpenFeign 完成了微服务间的调用,并且在多实例集群的情况下,通过调整负载策略很好应对并发调用。网络产品开发时,网络有时可能是不可用的,服务亦有可能是不可用的,当调用服务响应慢或不可用时,大量的请求积压,会成为压倒系统骆驼的最后一根稻草。这种情况下,我们如何应对呢?本章节就带你走近 Hystrix 组件。</p>
<h3>什么是 Hystrix</h3>
<p>它是分布式系统提供的一个低时延容错机制的基础组件提供限流、服务降级、系统熔断保护、快速失败等多个维度来保障微服务的稳定性。Hystrix 也是 Netflix 套件的一部分。</p>
<p>遗憾的是 1.5.18 版本之后进入了维护模式,官方提供了替代方案:<a href="https://github.com/resilience4j/resilience4j">resilience4j</a>,本测试采用的 Hystrix 终极版,需要更高版本的话,建议还是采用 resilience4j 这里不作过多介绍后续将替换成另一个重要组件——Sentinel 来替代 Hystrix。</p>
<h3>引入 Hystrix</h3>
<p>采用 starter 的方式引入</p>
<pre><code class="language-xml">&lt;dependency&gt;
&lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt;
&lt;artifactId&gt;spring-cloud-starter-netflix-hystrix&lt;/artifactId&gt;
&lt;/dependency&gt;
</code></pre>
<p>feignClient 中已经默认集成了断路器的功能,但是需要在配置文件中打开,才能开启。在 application.properties 中打开 hystrix 开关:</p>
<pre><code class="language-properties">#hystrix enable
feign.hystrix.enabled=true
</code></pre>
<p>重新回到之前的 FeignClient 代码,在注解中增加 fallback 属性值,添加相应的 fallback 调用类。</p>
<pre><code class="language-java">@FeignClient(value = &quot;card-service&quot;, fallback = MemberCardServiceFallback.class)
public interface MemberCardClient {
@RequestMapping(value = &quot;/card/addCard&quot;, method = RequestMethod.POST)
public CommonResult&lt;Integer&gt; addCard(@RequestParam(value = &quot;json&quot;) String json) throws BusinessException;
@RequestMapping(value = &quot;/card/updateCard&quot;, method = RequestMethod.POST)
public CommonResult&lt;Integer&gt; updateCard(@RequestParam(value = &quot;json&quot;) String json) throws BusinessException;
}
</code></pre>
<p>编写 MemberCardServiceFallback 方法,就是一个普通的服务实现类,增加了<a href="">@Component </a> 注解。</p>
<pre><code class="language-java">@Component
@Slf4j
public class MemberCardServiceFallback implements MemberCardClient {
@Override
public CommonResult&lt;Integer&gt; addCard(String json) throws BusinessException {
CommonResult&lt;Integer&gt; result = new CommonResult&lt;&gt;(&quot;parking-card service not available! &quot;);
log.warn(&quot;parking-card service not available! &quot;);
return result;
}
@Override
public CommonResult&lt;Integer&gt; updateCard(String json) throws BusinessException {
CommonResult&lt;Integer&gt; result = new CommonResult&lt;&gt;(&quot;parking-card service not available! &quot;);
log.warn(&quot;parking-card service not available! &quot;);
return result;
}
}
</code></pre>
<h3>测试 Hystrix</h3>
<p>上一章节中按正常流程已经将功能完成:会员开通后,积分生成,这里将不启动&quot;积分子服务&quot;,看看会是什么效果。(默认服务注册中心已经启动,这里及后续演示过程中不再专门提出)</p>
<ol>
<li>只启动 parking-member 一个子服务</li>
<li>打开 parking-member 子服务的 swagger-ui 界面,调用会员绑定手机号接口(或采用 PostMan 工具)</li>
</ol>
<p>正常情况下会直接调用 fallback 接口,快速失败,响应调用方。</p>
<p>此时将积分模块服务启动,再次发起调用,正确情况下已不再调用 fallback 方法,而是正常调用积分服务接口,如下图所示:</p>
<p><img src="assets/2020-05-05-021449.jpg" alt="img" /></p>
<p><img src="assets/2020-05-05-021320.jpg" alt="img" /></p>
<h3>图形化监控 Hystrix</h3>
<p>通过上面的应用,我们已经可以将 Hystrix 正常的集成到功能开发过程中,但究竟 Hystrix 实时运行状态是什么样的呢?有没有什么办法可以看到 Hystrix 的各项指标呢?这里我们引入 Hystrix Dashboard (仪表盘),通过 UI 的方式,快速的查看运行状况。</p>
<h4>新增仪盘表项目</h4>
<p>我们在 parking-base-serv 项目下,新建一个名为 parking-hystrix-dashboard Spring Boot 子工程,专门来做 Hystrix 的仪表盘监控。修改 pom.xml 文件,添加相关依赖:</p>
<pre><code class="language-java"> &lt;dependency&gt;
&lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt;
&lt;artifactId&gt;spring-cloud-starter-hystrix-dashboard&lt;/artifactId&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
&lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
&lt;artifactId&gt;spring-boot-starter-actuator&lt;/artifactId&gt;
&lt;/dependency&gt;
</code></pre>
<p>在项目启动类中增加 @EnableHystrixDashboard 注解,开启仪表盘功能</p>
<pre><code class="language-java">@SpringBootApplication
@EnableDiscoveryClient
@EnableHystrixDashboard
public class ParkingHystrixDashboardApplication {
public static void main(String[] args) {
SpringApplication.run(ParkingHystrixDashboardApplication.class, args);
}
}
</code></pre>
<p>启动项目,打开地址:<a href="http://localhost:10093/hystrix">http://localhost:10093/hystrix</a>,出现如下界面表明正常运行。</p>
<p><img src="assets/5a7c3fa0-a0ad-11ea-972e-9972d673b258" alt="img" /></p>
<h4>调整被监控项目</h4>
<p>会员服务中在调用积分服务接口的过程中,采用 Feign 的方式发起远程调用,同时实现了 fallback 服务降级、快速失败功能,本次要监控的主要目标就是此功能。</p>
<p>在 parking-member 项目 config 代码包下,增加 Hystrix 数据流的配置:</p>
<pre><code class="language-java">@Configuration
public class HystrixConfig {
@Bean
public ServletRegistrationBean&lt;HystrixMetricsStreamServlet&gt; getServlet() {
HystrixMetricsStreamServlet servlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean&lt;HystrixMetricsStreamServlet&gt; bean = new ServletRegistrationBean&lt;&gt;(servlet);
bean.addUrlMappings(&quot;/hystrix.stream&quot;);
bean.setName(&quot;HystrixMetricsStreamServlet&quot;);
return bean;
}
}
</code></pre>
<p>启动后,打开本项目的 Hystrix 数据获取地址:<a href="http://localhost:10060/hystrix.stream">http://localhost:10060/hystrix.stream</a>,初始状态,页面会不停的输出 ping 空值的情况,只有采用 Hystrix 当相关功能被请求时,才能正常的输出数据 JSON 格式数据,如截图所示:</p>
<p><img src="assets/2020-05-05-021529.jpg" alt="img" /></p>
<p>上图输出的结果不够友好,没有办法直观的分析 Hystrix 组件的应用情况,此时我们的仪表盘项目就派上用场。</p>
<h4>仪表盘解读</h4>
<p>将地址 <a href="http://localhost:10060/hystrix.stream">http://localhost:10060/hystrix.stream</a> 输入到 dashboard 页面中数据抓取地址栏中Delay 项可采用默认值Titile 项可以新取一个名字,便于我们能够识别。同样的,相关功能只有被执行过,仪表盘中才能正常的显示,下图所示是由于积分服务未启动,会员服务直接调用导致全部失败的情况。</p>
<p><img src="assets/2020-05-05-021531.jpg" alt="img" /></p>
<p>关于图表简单解读下:</p>
<ul>
<li>左上角的圆圈表示服务的健康程度,从绿色、黄色、橙色、红色递减</li>
<li>曲线用来记录 2 分钟内流量的相对变化,观察流量的上升和下降趋势。</li>
<li>左侧框中的数字与右上数字含义是一一对应</li>
<li>Host 与 Cluster 记录的是服务请求频率</li>
<li>再下面的几个 *th 标签表示百分位的延迟情况</li>
</ul>
<p><img src="assets/2020-05-05-021320.jpg" alt="img" /></p>
<p>(恢复积分服务后,高频次重新调用功能,发现请求是正常的,圆圈也变大)</p>
<p>本案例中仅编写了一个 Hystrix 的应用情况,如果服务中多处使用的话,仪表盘的展现会更加丰富,从页面上可以清晰监控到服务的压力情况、运转情况等等,为运维工作提供重要的参照依据。</p>
<p><img src="assets/2020-05-05-021320.jpg" alt="img" /></p>
<p>(图中参数展现略有不同,图片来源于<a href="https://github.com/Netflix-Skunkworks/hystrix-dashboard">https://github.com/Netflix-Skunkworks/hystrix-dashboard</a>)</p>
<p>通过上文的学习实践,相信你对 Hystrix 断路器的应用有了初步的概念,以及如何应用到项目中去,为我们的服务提供保驾护航。</p>
<h3>留一个思考题</h3>
<p>文中仅展示了一个模块服务的断路器的应用,如果是多个服务需要监控怎么办?同时打开多个仪表盘页面吗?你有没有什么更好的办法?</p>
</div>
</div>
<div>
<div style="float: left">
<a href="/专栏/SpringCloud微服务实战/07 如何调用本业务模块外的服务——服务调用.md.html">上一页</a>
</div>
<div style="float: right">
<a href="/专栏/SpringCloud微服务实战/09 热更新一样更新服务的参数配置——分布式配置中心.md.html">下一页</a>
</div>
</div>
</div>
</div>
</div>
</div>
<a class="off-canvas-overlay" onclick="hide_canvas()"></a>
</div>
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v652eace1692a40cfa3763df669d7439c1639079717194" integrity="sha512-Gi7xpJR8tSkrpF7aordPZQlW2DLtzUlZcumS8dMQjwDHEnw9I7ZLyiOj/6tZStRBGtGgN6ceN6cMH8z7etPGlw==" data-cf-beacon='{"rayId":"70997597b9c33d60","version":"2021.12.0","r":1,"token":"1f5d475227ce4f0089a7cff1ab17c0f5","si":100}' crossorigin="anonymous"></script>
</body>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-NPSEEVD756"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', 'G-NPSEEVD756');
var path = window.location.pathname
var cookie = getCookie("lastPath");
console.log(path)
if (path.replace("/", "") === "") {
if (cookie.replace("/", "") !== "") {
console.log(cookie)
document.getElementById("tip").innerHTML = "<a href='" + cookie + "'>跳转到上次进度</a>"
}
} else {
setCookie("lastPath", path)
}
function setCookie(cname, cvalue) {
var d = new Date();
d.setTime(d.getTime() + (180 * 24 * 60 * 60 * 1000));
var expires = "expires=" + d.toGMTString();
document.cookie = cname + "=" + cvalue + "; " + expires + ";path = /";
}
function getCookie(cname) {
var name = cname + "=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i].trim();
if (c.indexOf(name) === 0) return c.substring(name.length, c.length);
}
return "";
}
</script>
</html>