learn.lianglianglee.com/专栏/左耳听风/049 弹力设计篇之“限流设计”.md.html
2022-05-11 18:57:05 +08:00

1825 lines
46 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>049 弹力设计篇之“限流设计”.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="/专栏/左耳听风/000 开篇词 洞悉技术的本质,享受科技的乐趣.md.html">000 开篇词 洞悉技术的本质,享受科技的乐趣.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/001 程序员如何用技术变现(上).md.html">001 程序员如何用技术变现(上).md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/002 程序员如何用技术变现(下).md.html">002 程序员如何用技术变现(下).md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/003 Equifax信息泄露始末.md.html">003 Equifax信息泄露始末.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/004 从Equifax信息泄露看数据安全.md.html">004 从Equifax信息泄露看数据安全.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/005 何为技术领导力.md.html">005 何为技术领导力.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/006 如何拥有技术领导力.md.html">006 如何拥有技术领导力.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/007 推荐阅读:每个程序员都该知道的事.md.html">007 推荐阅读:每个程序员都该知道的事.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/008 Go语言Docker和新技术.md.html">008 Go语言Docker和新技术.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/009 答疑解惑:渴望、热情和选择.md.html">009 答疑解惑:渴望、热情和选择.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/010 如何成为一个大家愿意追随的Leader.md.html">010 如何成为一个大家愿意追随的Leader.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/011 程序中的错误处理:错误返回码和异常捕捉.md.html">011 程序中的错误处理:错误返回码和异常捕捉.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/012 程序中的错误处理:异步编程和最佳实践.md.html">012 程序中的错误处理:异步编程和最佳实践.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/013 魔数 0x5f3759df.md.html">013 魔数 0x5f3759df.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/014 推荐阅读机器学习101.md.html">014 推荐阅读机器学习101.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/015 时间管理:同扭曲时间的事儿抗争.md.html">015 时间管理:同扭曲时间的事儿抗争.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/016 时间管理:投资赚取时间.md.html">016 时间管理:投资赚取时间.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/017 故障处理最佳实践:应对故障.md.html">017 故障处理最佳实践:应对故障.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/018 故障处理最佳实践:故障改进.md.html">018 故障处理最佳实践:故障改进.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/019 答疑解惑:我们应该能够识别的表象和本质.md.html">019 答疑解惑:我们应该能够识别的表象和本质.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/020 分布式系统架构的冰与火.md.html">020 分布式系统架构的冰与火.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/021 从亚马逊的实践,谈分布式系统的难点.md.html">021 从亚马逊的实践,谈分布式系统的难点.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/022 分布式系统的技术栈.md.html">022 分布式系统的技术栈.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/023 分布式系统关键技术:全栈监控.md.html">023 分布式系统关键技术:全栈监控.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/024 分布式系统关键技术:服务调度.md.html">024 分布式系统关键技术:服务调度.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/025 分布式系统关键技术:流量与数据调度.md.html">025 分布式系统关键技术:流量与数据调度.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/026 洞悉PaaS平台的本质.md.html">026 洞悉PaaS平台的本质.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/027 推荐阅读:分布式系统架构经典资料.md.html">027 推荐阅读:分布式系统架构经典资料.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/028 编程范式游记1- 起源.md.html">028 编程范式游记1- 起源.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/029 编程范式游记2- 泛型编程.md.html">029 编程范式游记2- 泛型编程.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/030 编程范式游记3 - 类型系统和泛型的本质.md.html">030 编程范式游记3 - 类型系统和泛型的本质.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/031 Git协同工作流你该怎样选.md.html">031 Git协同工作流你该怎样选.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/032 推荐阅读:分布式数据调度相关论文.md.html">032 推荐阅读:分布式数据调度相关论文.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/033 编程范式游记4- 函数式编程.md.html">033 编程范式游记4- 函数式编程.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/034 编程范式游记5- 修饰器模式.md.html">034 编程范式游记5- 修饰器模式.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/035 编程范式游记6- 面向对象编程.md.html">035 编程范式游记6- 面向对象编程.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/036 编程范式游记7- 基于原型的编程范式.md.html">036 编程范式游记7- 基于原型的编程范式.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/037 编程范式游记8- Go 语言的委托模式.md.html">037 编程范式游记8- Go 语言的委托模式.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/038 编程范式游记9- 编程的本质.md.html">038 编程范式游记9- 编程的本质.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/039 编程范式游记10- 逻辑编程范式.md.html">039 编程范式游记10- 逻辑编程范式.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/040 编程范式游记11- 程序世界里的编程范式.md.html">040 编程范式游记11- 程序世界里的编程范式.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/041 弹力设计篇之“认识故障和弹力设计”.md.html">041 弹力设计篇之“认识故障和弹力设计”.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/042 弹力设计篇之“隔离设计”.md.html">042 弹力设计篇之“隔离设计”.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/043 弹力设计篇之“异步通讯设计”.md.html">043 弹力设计篇之“异步通讯设计”.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/044 弹力设计篇之“幂等性设计”.md.html">044 弹力设计篇之“幂等性设计”.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/045 弹力设计篇之“服务的状态”.md.html">045 弹力设计篇之“服务的状态”.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/046 弹力设计篇之“补偿事务”.md.html">046 弹力设计篇之“补偿事务”.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/047 弹力设计篇之“重试设计”.md.html">047 弹力设计篇之“重试设计”.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/048 弹力设计篇之“熔断设计”.md.html">048 弹力设计篇之“熔断设计”.md.html</a>
</li>
<li>
<a class="current-tab" href="/专栏/左耳听风/049 弹力设计篇之“限流设计”.md.html">049 弹力设计篇之“限流设计”.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/050 弹力设计篇之“降级设计”.md.html">050 弹力设计篇之“降级设计”.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/051 弹力设计篇之“弹力设计总结”.md.html">051 弹力设计篇之“弹力设计总结”.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/052 区块链技术 - 区块链的革命性及技术概要.md.html">052 区块链技术 - 区块链的革命性及技术概要.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/053 区块链技术 - 区块链技术细节 - 哈希算法.md.html">053 区块链技术 - 区块链技术细节 - 哈希算法.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/054 区块链技术 - 区块链技术细节 - 加密和挖矿.md.html">054 区块链技术 - 区块链技术细节 - 加密和挖矿.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/055 区块链技术 - 去中心化的共识机制.md.html">055 区块链技术 - 去中心化的共识机制.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/056 区块链技术 - 智能合约.md.html">056 区块链技术 - 智能合约.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/057 区块链技术 - 传统金融和虚拟货币.md.html">057 区块链技术 - 传统金融和虚拟货币.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/058 管理设计篇之分布式锁.md.html">058 管理设计篇之分布式锁.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/059 管理设计篇之配置中心.md.html">059 管理设计篇之配置中心.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/060 管理设计篇之边车模式.md.html">060 管理设计篇之边车模式.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/061 管理设计篇之服务网格.md.html">061 管理设计篇之服务网格.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/062 管理设计篇之网关模式.md.html">062 管理设计篇之网关模式.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/063 管理设计篇之部署升级策略.md.html">063 管理设计篇之部署升级策略.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/064 性能设计篇之缓存.md.html">064 性能设计篇之缓存.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/065 性能设计篇之异步处理.md.html">065 性能设计篇之异步处理.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/066 性能设计篇之数据库扩展.md.html">066 性能设计篇之数据库扩展.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/067 性能设计篇之秒杀.md.html">067 性能设计篇之秒杀.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/068 性能设计篇之边缘计算.md.html">068 性能设计篇之边缘计算.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/069 程序员练级攻略2018开篇词.md.html">069 程序员练级攻略2018开篇词.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/070 程序员练级攻略2018零基础启蒙.md.html">070 程序员练级攻略2018零基础启蒙.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/071 程序员练级攻略2018正式入门.md.html">071 程序员练级攻略2018正式入门.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/072 程序员练级攻略2018程序员修养.md.html">072 程序员练级攻略2018程序员修养.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/073 程序员练级攻略2018编程语言.md.html">073 程序员练级攻略2018编程语言.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/074 程序员练级攻略:理论学科.md.html">074 程序员练级攻略:理论学科.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/075 程序员练级攻略2018系统知识.md.html">075 程序员练级攻略2018系统知识.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/076 程序员练级攻略2018软件设计.md.html">076 程序员练级攻略2018软件设计.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/077 程序员练级攻略2018Linux系统、内存和网络.md.html">077 程序员练级攻略2018Linux系统、内存和网络.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/078 程序员练级攻略2018异步IO模型和Lock-Free编程.md.html">078 程序员练级攻略2018异步IO模型和Lock-Free编程.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/079 程序员练级攻略2018Java底层知识.md.html">079 程序员练级攻略2018Java底层知识.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/080 程序员练级攻略2018数据库.md.html">080 程序员练级攻略2018数据库.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/081 程序员练级攻略2018分布式架构入门.md.html">081 程序员练级攻略2018分布式架构入门.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/082 程序员练级攻略2018分布式架构经典图书和论文.md.html">082 程序员练级攻略2018分布式架构经典图书和论文.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/083 程序员练级攻略2018分布式架构工程设计.md.html">083 程序员练级攻略2018分布式架构工程设计.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/084 程序员练级攻略2018微服务.md.html">084 程序员练级攻略2018微服务.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/085 程序员练级攻略2018容器化和自动化运维.md.html">085 程序员练级攻略2018容器化和自动化运维.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/086 程序员练级攻略2018机器学习和人工智能.md.html">086 程序员练级攻略2018机器学习和人工智能.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/087 程序员练级攻略2018前端基础和底层原理.md.html">087 程序员练级攻略2018前端基础和底层原理.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/088 程序员练级攻略2018前端性能优化和框架.md.html">088 程序员练级攻略2018前端性能优化和框架.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/089 程序员练级攻略2018UIUX设计.md.html">089 程序员练级攻略2018UIUX设计.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/090 程序员练级攻略2018技术资源集散地.md.html">090 程序员练级攻略2018技术资源集散地.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/091 程序员面试攻略:面试前的准备.md.html">091 程序员面试攻略:面试前的准备.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/092 程序员面试攻略:面试中的技巧.md.html">092 程序员面试攻略:面试中的技巧.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/093 程序员面试攻略:面试风格.md.html">093 程序员面试攻略:面试风格.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/094 程序员面试攻略:实力才是王中王.md.html">094 程序员面试攻略:实力才是王中王.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/095 高效学习:端正学习态度.md.html">095 高效学习:端正学习态度.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/096 高效学习:源头、原理和知识地图.md.html">096 高效学习:源头、原理和知识地图.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/097 高效学习:深度,归纳和坚持实践.md.html">097 高效学习:深度,归纳和坚持实践.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/098 高效学习:如何学习和阅读代码.md.html">098 高效学习:如何学习和阅读代码.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/099 高效学习:面对枯燥和量大的知识.md.html">099 高效学习:面对枯燥和量大的知识.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/100 高效沟通Talk和Code同等重要.md.html">100 高效沟通Talk和Code同等重要.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/101 高效沟通:沟通阻碍和应对方法.md.html">101 高效沟通:沟通阻碍和应对方法.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/102 高效沟通:沟通方式及技巧.md.html">102 高效沟通:沟通方式及技巧.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/103 高效沟通:沟通技术.md.html">103 高效沟通:沟通技术.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/104 高效沟通:好老板要善于提问.md.html">104 高效沟通:好老板要善于提问.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/105 高效沟通:好好说话的艺术.md.html">105 高效沟通:好好说话的艺术.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/106 加餐 谈谈我的“三观”.md.html">106 加餐 谈谈我的“三观”.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/107 结束语 业精于勤,行成于思.md.html">107 结束语 业精于勤,行成于思.md.html</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>049 弹力设计篇之“限流设计”</h1>
<p>保护系统不会在过载的情况下导致问题,那么,我们就需要限流。</p>
<p>我们在一些系统中都可以看到这样的设计,比如,我们的数据库访问的连接池,还有我们的线程池,还有 nginx 下的用于限制瞬时并发连接数的 limit_conn 模块,限制每秒平均速率的 limit_req 模块,还有限制 MQ 的生产速,等等。</p>
<h1>限流的策略</h1>
<p>限流的目的是通过对并发访问进行限速,相关的策略一般是,一旦达到限制的速率,那么就会触发相应的限流行为。一般来说,触发的限流行为如下。</p>
<ul>
<li><strong>拒绝服务</strong>。把多出来的请求拒绝掉。一般来说,好的限流系统在受到流量暴增时,会统计当前哪个客户端来的请求最多,直接拒掉这个客户端,这种行为可以把一些不正常的或者是带有恶意的高并发访问抵挡掉。</li>
<li><strong>服务降级</strong>。关闭或是把后端服务做降级处理。这样可以让服务有足够的资源来处理更多的请求。降级有很多方式,一种是把一些不重要的服务给停掉,把 CPU、内存或是数据的资源让给更重要的功能一种是不再返回全量数据只返回部分数据。</li>
</ul>
<p>因为全量数据需要做 SQL Join 操作,部分的数据则不需要,所以可以让 SQL 执行更快,还有最快的一种是直接返回预设的缓存,以牺牲一致性的方式来获得更大的性能吞吐。</p>
<ul>
<li><strong>特权请求</strong>。所谓特权请求的意思是,资源不够了,我只能把有限的资源分给重要的用户,比如:分给权利更高的 VIP 用户。在多租户系统下,限流的时候应该保大客户的,所以大客户有特权可以优先处理,而其它的非特权用户就得让路了。</li>
<li><strong>延时处理</strong>。在这种情况下,一般会有一个队列来缓冲大量的请求,这个队列如果满了,那么就只能拒绝用户了,如果这个队列中的任务超时了,也要返回系统繁忙的错误了。使用缓冲队列只是为了减缓压力,一般用于应对短暂的峰刺请求。</li>
<li><strong>弹性伸缩</strong>。动用自动化运维的方式对相应的服务做自动化的伸缩。这个需要一个应用性能的监控系统,能够感知到目前最繁忙的 TOP 5 的服务是哪几个。</li>
</ul>
<p>然后去伸缩它们,还需要一个自动化的发布、部署和服务注册的运维系统,而且还要快,越快越好。否则,系统会被压死掉了。当然,如果是数据库的压力过大,弹性伸缩应用是没什么用的,这个时候还是应该限流。</p>
<h1>限流的实现方式</h1>
<h2>计数器方式</h2>
<p>最简单的限流算法就是维护一个计数器 Counter当一个请求来时就做加一操作当一个请求处理完后就做减一操作。如果这个 Counter 大于某个数了(我们设定的限流阈值),那么就开始拒绝请求以保护系统的负载了。</p>
<p>这个算法足够的简单粗暴了。</p>
<h2>队列算法</h2>
<p>在这个算法下,请求的速度可以是波动的,而处理的速度则是非常均速的。这个算法其实有点像一个 FIFO 的算法。</p>
<p><img src="assets/c8f774f88ab8a4b72378971263c0393d.png" alt="img" /></p>
<p>在上面这个 FIFO 的队列上,我们可以扩展出一些别的玩法。</p>
<p>一个是有优先级的队列,处理时先处理高优先级的队列,然后再处理低优先级的队列。 如下图所示,只有高优先级的队列被处理完成后,才会处理低优先级的队列。</p>
<p><img src="assets/de51d6fc68df3d8c808b84e4bc455580.png" alt="img" /></p>
<p>有优先级的队列可能会导致低优先级队列长时间得不到处理。为了避免低优先级的队列被饿死,一般来说是分配不同的比例的处理时间到不同的队列上,于是我们有了带权重的队列。</p>
<p>如下图所示。有三个队列的权重分布是 3:2:1这意味着我们需要在权重为 3 的这个队列上处理 3 个请求后,再去权重为 2 的队列上处理 2 个请求,最后再去权重为 1 的队列上处理 1 个请求,如此返复。</p>
<p><img src="assets/c775345e3b8f599e26a4d7f64941cd54.png" alt="img" /></p>
<p>队列流控是以队列的的方式来处理请求。如果处理过慢,那么就会导致队列满,而开始触发限流。</p>
<p>但是,这样的算法需要用队列长度来控制流量,在配置上比较难以操作。如果队列过长,导致后端服务在队列没有满时就挂掉了。一般来说,这样的模型不能做 push 的,而是要做 pull 方式的会好一些。</p>
<h2>漏斗算法 Leaky Bucket</h2>
<p>漏斗算法可以参看 Wikipedia 的相关词条 <a href="https://en.wikipedia.org/wiki/Leaky_bucket">Leaky Bucket</a></p>
<p>下图是一个<a href="https://www.slideshare.net/vimal25792/leaky-bucket-tocken-buckettraffic-shaping">漏斗算法的示意图</a></p>
<p><img src="assets/95326ea1624d4206a26ff275b39efc00.png" alt="img" /></p>
<p>我们可以看到,就像一个漏斗一样,进来的水量就好像访问流量一样,而出去的水量就像是我们的系统处理请求一样。当访问流量过大时这个漏斗中就会积水,如果水太多了就会溢出。</p>
<p>一般来说,这个“漏斗”是用一个队列来实现的,当请求过多时,队列就会开始积压请求,如果队列满了,就会开拒绝请求。很多系统都有这样的设计,比如 TCP。当请求的数量过多时就会有一个 sync backlog 的队列来缓冲请求,或是 TCP 的滑动窗口也是用于流控的队列。</p>
<p><img src="assets/d4b8b6ceb8de4400dfc97f3ff0feeaa0.png" alt="img" /></p>
<p>我们可以看到,漏斗算法其实就是在队列请求中加上一个限流器,来让 Processor 以一个均匀的速度处理请求。</p>
<h2>令牌桶算法 Token Bucket</h2>
<p>关于令牌桶算法,主要是有一个中间人。在一个桶内按照一定的速率放入一些 token然后处理程序要处理请求时需要拿到 token才能处理如果拿不到则不处理。</p>
<p>下面这个图很清楚地说明了这个算法。</p>
<p><img src="assets/996b8d60ed90c470ce839f8826e375f0.png" alt="img" /></p>
<p>从理论上来说,令牌桶的算法和漏斗算法不一样的是,漏斗算法中,处理请求是以一个常量和恒定的速度处理的,而令牌桶算法则是在流量小的时候“攒钱”,流量大的时候,可以快速处理。</p>
<p>然而我们可能会问Processor 的处理速度因为有队列的存在,所以其总是能以最大处理能力来处理请求的,这也是我们所希望的方式。因此,令牌桶和漏斗都是受制于 Processor 的最大处理能力的。无论令牌桶里有多少令牌也无论队列中还有多少请求。总之Processor 在大流量来临时总是按照自己最大的处理能力来处理的。</p>
<p>但是,试想一下,如果我们的 Processor 只是一个非常简单的任务分配器,比如像 Nginx 这样的基本没有什么业务逻辑的网关,那么它的处理速度一定很快,不会有什么瓶颈,而其用来把请求转发给后端服务,那么在这种情况下,这两个算法就有不一样的情况了。</p>
<p>漏斗算法会以一个稳定的速度转发,而令牌桶算法平时流量不大时在“攒钱”,流量大时,可以一次发出队列里有的请求,而后就受到令牌桶的流控限制。</p>
<p>另外,令牌桶还可能做成第三方的一个服务,这样可以在分布式的系统中对全局进行流控,这也是一个很好的方式。</p>
<h2>基于响应时间的动态限流</h2>
<p>上面的算法有个不好的地方,就是需要设置一个确定的限流值。这就要求我们每次发布服务时都做相应的性能测试,找到系统最大的性能值。</p>
<p>当然,性能测试并不是很容易做的。有关性能测试的方法请参看我在 CoolShell 上的这篇文章《<a href="https://coolshell.cn/articles/17381.html">性能测试应该怎么做</a>》。虽然性能测试比较不容易,但是还是应该要做的。</p>
<p>然而,在很多时候,我们却并不知道这个限流值,或是很难给出一个合适的值。其基本会有如下的一些因素:</p>
<ul>
<li>实际情况下,很多服务会依赖于数据库。所以,不同的用户请求,会对不同的数据集进行操作。就算是相同的请求,可能数据集也不一样(比如,现在很多应用都会有一个时间线 Feed 流,不同的用户关心的主题人人不一样,数据也不一样)。</li>
</ul>
<p>而且数据库的数据是在不断地变化的,可能前两天性能还行,因为数据量增加导致性能变差。在这种情况下,我们很难给出一个确定的一成不变的值,因为关系型数据库对于同一条 SQL 语句的执行时间其实是不可预测的NoSQL 的就比 RDBMS 的可预测性要好)。</p>
<ul>
<li>不同的 API 有不同的性能。我们要在线上为每一个 API 配置不同的限流值,这点太难配置,也很难管理。</li>
<li>而且,现在的服务都是能自动化伸缩的,不同大小的集群的性能也不一样,所以,在自动化伸缩的情况下,我们要动态地调整限流的阈值,这点太难做到了。</li>
</ul>
<p>基于上述这些原因,我们的限流的值是很难被静态地设置成恒定的一个值。</p>
<p>我们想使用一种动态限流的方式。这种方式,不再设定一个特定的流控值,而是能够动态地感知系统的压力来自动化地限流。</p>
<p>这方面设计的典范是 TCP 协议的拥塞控制的算法。TCP 使用 RTT - Round Trip Time 来探测网络的延时和性能,从而设定相应的“滑动窗口”的大小,以让发送的速率和网络的性能相匹配。这个算法是非常精妙的,我们完全可以借鉴在我们的流控技术中。</p>
<p>我们记录下每次调用后端请求的响应时间,然后在一个时间区间内(比如,过去 10 秒)的请求计算一个响应时间的 P90 或 P99 值,也就是把过去 10 秒内的请求的响应时间排个序,然后看 90% 或 99% 的位置是多少。</p>
<p>这样,我们就知道有多少请求大于某个响应时间。如果这个 P90 或 P99 超过我们设定的阈值,那么我们就自动限流。</p>
<p>这个设计中有几个要点。</p>
<ul>
<li>你需要计算的一定时间内的 P90 或 P99。在有大量请求的情况下这个非常地耗内存也非常地耗 CPU因为需要对大量的数据进行排序。</li>
</ul>
<p>解决方案有两种,一种是不记录所有的请求,采样就好了,另一种是使用一个叫蓄水池的近似算法。关于这个算法这里我不就多说了,《编程珠玑》里讲过这个算法,你也可以自行 Google英文叫 <a href="https://en.wikipedia.org/wiki/Reservoir_sampling">Reservoir Sampling</a></p>
<ul>
<li>这种动态流控需要像 TCP 那样,你需要记录一个当前的 QPS. 如果发现后端的 P90/P99 响应太慢,那么就可以把这个 QPS 减半,然后像 TCP 一样走慢启动的方式,直接到又开始变慢,然后减去 1/4 的 QPS再慢启动然后再减去 1/8 的 QPS……</li>
</ul>
<p>这个过程有点像个阻尼运行的过程,然后整个限流的流量会在一个值上下做小幅振动。这么做的目的是,如果后端扩容伸缩后性能变好,系统会自动适应后端的最大性能。</p>
<ul>
<li>这种动态限流的方式实现起来并不容易。大家可以看一下 TCP 的算法。TCP 相关的一些算法,我写在了 CoolShell 上的《<a href="https://coolshell.cn/articles/11609.html">TCP 的那些事(下)</a>》这篇文章中。你可以用来做参考来实现。</li>
</ul>
<p>我在现在创业中的 Ease Gateway 的产品中实现了这个算法。</p>
<h1>限流的设计要点</h1>
<p>限流主要是有四个目的。</p>
<ol>
<li>为了向用户承诺 SLA。我们保证我们的系统在某个速度下的响应时间以及可用性。</li>
<li>同时,也可以用来阻止在多租户的情况下,某一用户把资源耗尽而让所有的用户都无法访问的问题。</li>
<li>为了应对突发的流量。</li>
<li>节约成本。我们不会为了一个不常见的尖峰来把我们的系统扩容到最大的尺寸。而是在有限的资源下能够承受比较高的流量。</li>
</ol>
<p>在设计上,我们还要有以下的考量。</p>
<ul>
<li>限流应该是在架构的早期考虑。当架构形成后,限流不是很容易加入。</li>
<li>限流模块必需是非常好的性能,而且对流量的变化也是非常灵敏的,否则太过迟钝的限流,系统早因为过载而挂掉了。</li>
<li>限流应该有个手动的开关,这样在应急的时候,可以手动操作。</li>
<li>当限流发生时,应该有个监控事件通知。让我们知道有限流事件发生,这样,运维人员可以及时跟进。而且还可以自动化触发扩容或降级,以缓解系统压力。</li>
<li>当限流发生时,对于拒掉的请求,我们应该返回一个特定的限流错误码。这样,可以和其它错误区分开来。而客户端看到限流,可以调整发送速度,或是走重试机制。</li>
<li>限流应该让后端的服务感知到。限流发生时,我们应该在协议头中塞进一个标识,比如 HTTP Header 中,放入一个限流的级别,告诉后端服务目前正在限流中。这样,后端服务可以根据这个标识决定是否做降级。</li>
</ul>
<h1>小结</h1>
<p>好了,我们来总结一下今天分享的主要内容。首先,限流的目的是为了保护系统不在过载的情况下导致问题。接着讲了几种限流的策略。然后讲了,限流的算法,包括计数器、队列、漏斗和令牌桶。然后讨论了如何基于响应时间来限流。最后,我总结了限流设计的要点。下篇文章中,我们讲述降级设计。希望对你有帮助。</p>
</div>
</div>
<div>
<div style="float: left">
<a href="/专栏/左耳听风/048 弹力设计篇之“熔断设计”.md.html">上一页</a>
</div>
<div style="float: right">
<a href="/专栏/左耳听风/050 弹力设计篇之“降级设计”.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":"7099783d486c3cfa","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>