mirror of
https://github.com/zhwei820/learn.lianglianglee.com.git
synced 2025-09-26 05:06:42 +08:00
335 lines
21 KiB
HTML
335 lines
21 KiB
HTML
<!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"><dependency>
|
||
<groupId>org.springframework.cloud</groupId>
|
||
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
|
||
</dependency>
|
||
</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 = "card-service", fallback = MemberCardServiceFallback.class)
|
||
public interface MemberCardClient {
|
||
@RequestMapping(value = "/card/addCard", method = RequestMethod.POST)
|
||
public CommonResult<Integer> addCard(@RequestParam(value = "json") String json) throws BusinessException;
|
||
@RequestMapping(value = "/card/updateCard", method = RequestMethod.POST)
|
||
public CommonResult<Integer> updateCard(@RequestParam(value = "json") 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<Integer> addCard(String json) throws BusinessException {
|
||
CommonResult<Integer> result = new CommonResult<>("parking-card service not available! ");
|
||
log.warn("parking-card service not available! ");
|
||
return result;
|
||
}
|
||
@Override
|
||
public CommonResult<Integer> updateCard(String json) throws BusinessException {
|
||
CommonResult<Integer> result = new CommonResult<>("parking-card service not available! ");
|
||
log.warn("parking-card service not available! ");
|
||
return result;
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3>测试 Hystrix</h3>
|
||
<p>上一章节中按正常流程已经将功能完成:会员开通后,积分生成,这里将不启动"积分子服务",看看会是什么效果。(默认服务注册中心已经启动,这里及后续演示过程中不再专门提出)</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"> <dependency>
|
||
<groupId>org.springframework.cloud</groupId>
|
||
<artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
|
||
</dependency>
|
||
<dependency>
|
||
<groupId>org.springframework.boot</groupId>
|
||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||
</dependency>
|
||
</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<HystrixMetricsStreamServlet> getServlet() {
|
||
HystrixMetricsStreamServlet servlet = new HystrixMetricsStreamServlet();
|
||
ServletRegistrationBean<HystrixMetricsStreamServlet> bean = new ServletRegistrationBean<>(servlet);
|
||
bean.addUrlMappings("/hystrix.stream");
|
||
bean.setName("HystrixMetricsStreamServlet");
|
||
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>
|