learn.lianglianglee.com/专栏/300分钟吃透分布式缓存-完/13 如何完整学习MC协议及优化client访问?.md.html
2022-05-11 18:52:13 +08:00

877 lines
24 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>13 如何完整学习MC协议及优化client访问.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="/专栏/300分钟吃透分布式缓存-完/00 开篇寄语:缓存,你真的用对了吗?.md">00 开篇寄语:缓存,你真的用对了吗?.md.html</a>
</li>
<li>
<a href="/专栏/300分钟吃透分布式缓存-完/01 业务数据访问性能太低怎么办?.md">01 业务数据访问性能太低怎么办?.md.html</a>
</li>
<li>
<a href="/专栏/300分钟吃透分布式缓存-完/02 如何根据业务来选择缓存模式和组件?.md">02 如何根据业务来选择缓存模式和组件?.md.html</a>
</li>
<li>
<a href="/专栏/300分钟吃透分布式缓存-完/03 设计缓存架构时需要考量哪些因素?.md">03 设计缓存架构时需要考量哪些因素?.md.html</a>
</li>
<li>
<a href="/专栏/300分钟吃透分布式缓存-完/04 缓存失效、穿透和雪崩问题怎么处理?.md">04 缓存失效、穿透和雪崩问题怎么处理?.md.html</a>
</li>
<li>
<a href="/专栏/300分钟吃透分布式缓存-完/05 缓存数据不一致和并发竞争怎么处理?.md">05 缓存数据不一致和并发竞争怎么处理?.md.html</a>
</li>
<li>
<a href="/专栏/300分钟吃透分布式缓存-完/06 Hot Key和Big Key引发的问题怎么应对.md">06 Hot Key和Big Key引发的问题怎么应对.md.html</a>
</li>
<li>
<a href="/专栏/300分钟吃透分布式缓存-完/07 MC为何是应用最广泛的缓存组件.md">07 MC为何是应用最广泛的缓存组件.md.html</a>
</li>
<li>
<a href="/专栏/300分钟吃透分布式缓存-完/08 MC系统架构是如何布局的.md">08 MC系统架构是如何布局的.md.html</a>
</li>
<li>
<a href="/专栏/300分钟吃透分布式缓存-完/09 MC是如何使用多线程和状态机来处理请求命令的.md">09 MC是如何使用多线程和状态机来处理请求命令的.md.html</a>
</li>
<li>
<a href="/专栏/300分钟吃透分布式缓存-完/10 MC是怎么定位key的.md">10 MC是怎么定位key的.md.html</a>
</li>
<li>
<a href="/专栏/300分钟吃透分布式缓存-完/11 MC如何淘汰冷key和失效key.md">11 MC如何淘汰冷key和失效key.md.html</a>
</li>
<li>
<a href="/专栏/300分钟吃透分布式缓存-完/12 为何MC能长期维持高性能读写.md">12 为何MC能长期维持高性能读写.md.html</a>
</li>
<li>
<a class="current-tab" href="/专栏/300分钟吃透分布式缓存-完/13 如何完整学习MC协议及优化client访问.md">13 如何完整学习MC协议及优化client访问.md.html</a>
</li>
<li>
<a href="/专栏/300分钟吃透分布式缓存-完/14 大数据时代MC如何应对新的常见问题.md">14 大数据时代MC如何应对新的常见问题.md.html</a>
</li>
<li>
<a href="/专栏/300分钟吃透分布式缓存-完/15 如何深入理解、应用及扩展 Twemproxy.md">15 如何深入理解、应用及扩展 Twemproxy.md.html</a>
</li>
<li>
<a href="/专栏/300分钟吃透分布式缓存-完/16 常用的缓存组件Redis是如何运行的.md">16 常用的缓存组件Redis是如何运行的.md.html</a>
</li>
<li>
<a href="/专栏/300分钟吃透分布式缓存-完/17 如何理解、选择并使用Redis的核心数据类型.md">17 如何理解、选择并使用Redis的核心数据类型.md.html</a>
</li>
<li>
<a href="/专栏/300分钟吃透分布式缓存-完/18 Redis协议的请求和响应有哪些“套路”可循.md">18 Redis协议的请求和响应有哪些“套路”可循.md.html</a>
</li>
<li>
<a href="/专栏/300分钟吃透分布式缓存-完/19 Redis系统架构中各个处理模块是干什么的.md">19 Redis系统架构中各个处理模块是干什么的.md.html</a>
</li>
<li>
<a href="/专栏/300分钟吃透分布式缓存-完/20 Redis如何处理文件事件和时间事件.md">20 Redis如何处理文件事件和时间事件.md.html</a>
</li>
<li>
<a href="/专栏/300分钟吃透分布式缓存-完/21 Redis读取请求数据后如何进行协议解析和处理.md">21 Redis读取请求数据后如何进行协议解析和处理.md.html</a>
</li>
<li>
<a href="/专栏/300分钟吃透分布式缓存-完/22 怎么认识和应用Redis内部数据结构.md">22 怎么认识和应用Redis内部数据结构.md.html</a>
</li>
<li>
<a href="/专栏/300分钟吃透分布式缓存-完/23 Redis是如何淘汰key的.md">23 Redis是如何淘汰key的.md.html</a>
</li>
<li>
<a href="/专栏/300分钟吃透分布式缓存-完/24 Redis崩溃后如何进行数据恢复的.md">24 Redis崩溃后如何进行数据恢复的.md.html</a>
</li>
<li>
<a href="/专栏/300分钟吃透分布式缓存-完/25 Redis是如何处理容易超时的系统调用的.md">25 Redis是如何处理容易超时的系统调用的.md.html</a>
</li>
<li>
<a href="/专栏/300分钟吃透分布式缓存-完/26 如何大幅成倍提升Redis处理性能.md">26 如何大幅成倍提升Redis处理性能.md.html</a>
</li>
<li>
<a href="/专栏/300分钟吃透分布式缓存-完/27 Redis是如何进行主从复制的.md">27 Redis是如何进行主从复制的.md.html</a>
</li>
<li>
<a href="/专栏/300分钟吃透分布式缓存-完/28 如何构建一个高性能、易扩展的Redis集群.md">28 如何构建一个高性能、易扩展的Redis集群.md.html</a>
</li>
<li>
<a href="/专栏/300分钟吃透分布式缓存-完/29 从容应对亿级QPS访问Redis还缺少什么.md">29 从容应对亿级QPS访问Redis还缺少什么.md.html</a>
</li>
<li>
<a href="/专栏/300分钟吃透分布式缓存-完/30 面对海量数据,为什么无法设计出完美的分布式缓存体系?.md">30 面对海量数据,为什么无法设计出完美的分布式缓存体系?.md.html</a>
</li>
<li>
<a href="/专栏/300分钟吃透分布式缓存-完/31 如何设计足够可靠的分布式缓存体系,以满足大中型移动互联网系统的需要?.md">31 如何设计足够可靠的分布式缓存体系,以满足大中型移动互联网系统的需要?.md.html</a>
</li>
<li>
<a href="/专栏/300分钟吃透分布式缓存-完/32 一个典型的分布式缓存系统是什么样的?.md">32 一个典型的分布式缓存系统是什么样的?.md.html</a>
</li>
<li>
<a href="/专栏/300分钟吃透分布式缓存-完/33 如何为秒杀系统设计缓存体系?.md">33 如何为秒杀系统设计缓存体系?.md.html</a>
</li>
<li>
<a href="/专栏/300分钟吃透分布式缓存-完/34 如何为海量计数场景设计缓存体系?.md">34 如何为海量计数场景设计缓存体系?.md.html</a>
</li>
<li>
<a href="/专栏/300分钟吃透分布式缓存-完/35 如何为社交feed场景设计缓存体系.md">35 如何为社交feed场景设计缓存体系.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>13 如何完整学习MC协议及优化client访问</h1>
<p>你好,我是你的缓存课老师陈波,欢迎进入第 13 课时“Memcached 协议分析”的学习。</p>
<h6>协议分析</h6>
<h6>异常错误响应</h6>
<p>接下来,我们来完整学习 Mc 协议。在学习 Mc 协议之前,首先来看看 Mc 处理协议指令如果发现异常如何进行异常错误响应的。Mc 在处理所有 client 端指令时,如果遇到错误,就会返回 3 种错误信息中的一种。</p>
<ul>
<li>第一种错误是协议错误,一个&quot;ERROR\r\n&quot;的字符串。表明 client 发送了一个非法命令。</li>
<li>第二种错误是 client 错误,格式为&quot;CLIENT_ERROR &lt;error-描述信息&gt;\r\n&quot;。这个错误信息表明 client 发送的协议命令格式有误,比如少了字段、多了非法字段等。</li>
<li>第三种错误是&quot;SERVER_ERROR &lt;error-描述信息&gt;\r\n&quot;。这个错误信息表明 Mc server 端,在处理命令时出现的错误。比如在给 key/value 分配 Item 空间失败后,会返回&quot;SERVER_ERROR out of memory storing object&quot; 错误信息。</li>
</ul>
<h6>存储协议命令</h6>
<p>现在再来看看 Mc 的存储协议。Mc 的存储协议命令不多,只有 6 个。</p>
<p>Mc 存储指令分 2 行。第一行是报文首部,第二行是 value 的 data block 块。这两部分用 \r\n 来进行分割和收尾。</p>
<p>存储类指令的报文首行分 2 种格式,其中一种是在 cmd 存储指令,后面跟 key、flags、expiretime、value 字节数,以及一个可选的 noreply。</p>
<p>其中 flags 是用户自己设计的一个特殊含义数字Mc 对 flag 只存储而不进行任何额外解析处理expiretime 是 key 的过期时间value 字节数是 value block 块的字节长度,而带上 noreply 是指 Mc 处理完后静默处理,不返回任何响应给 client。</p>
<p>这种 cmd 指令包括我们最常用的 set 指令,另外还包括 add、replace、append、reppend ,总共 5 个指令:</p>
<ul>
<li>Set 命令用于存储一个 key/value</li>
<li>Add 命令是在当 key 不存在时,才存储这个 key/value</li>
<li>Replace 命令,是当 key 存在时,才存储这个 key/value</li>
<li>Append 命令,是当 key 存在时,追加 data 到 value 的尾部;</li>
<li>Prepend 命令,是当 key 存在时,将 data 加到 value 的头部。</li>
</ul>
<p>另外一种存储协议指令,主要格式和字段与前一种基本相同,只是多了一个 cas unique id这种格式只有 cas 指令使用。cas 指令是指只有当这个 key 存在,且从本 client 获取以来没有其他任何人修改过时才进行修改。cas 的英文含义是 compare and set即比较成功后设置的意思。</p>
<h6>存储命令响应</h6>
<p>Mc 在响应存储协议时如果遇到错误就返回前面说的3种错误信息中的一种。否则就会返回如下 4 种正常的响应,&quot;STORED\r\n”、&quot;EXISTS\r\n”、&quot;NOT_STORED\r\n&quot;&quot;NOT_FOUND\r\n“。</p>
<p>其中stored 表明存储修改成功。NOT_STORED 表明数据没有存储成功,但并不是遇到错误或异常。这个响应一般表明 add 或 replace 等指令,前置条件不满足时,比如 add这个 key 已经存在 Mc就会 add 新 key 失败。replace 时, key 不存在,也无法 replace 成功。EXISTS 表明待 cas 的key 已经被修改过了,而 NOT_FOUND 是指待 cas 的 key 在 Mc 中不存在。</p>
<p>Mc 对存储命令的请求及响应协议,可以参考下面的思维导图来有一个完整的印象。</p>
<p><img src="assets/CgotOV2lNqOABAkvAAC0oNkHCok972.png" alt="img" /></p>
<h6>获取命令</h6>
<p>Mc 的获取协议,只有 get、gets 两种指令,如下图所示。格式为 get/gets 后,跟随若干个 key然后 \r\n 结束请求命令。get 指令只获取 key 的 flag 及 valuegets 会额外多获取一个 cas unique id值。gets 主要是为 cas 指令服务的。</p>
<p>获取命令的响应,就是 value 字串,后面跟上 key、flag、value 字节数,以及 value 的 data block 块。最后跟一个 END\r\n 表明所有存在的 key/value 已经返回,如果没有返回的 key则表明这个 key 在 Mc 中不存在。</p>
<p><img src="assets/CgoB5l2lNqOAd_DsAADGwh4XXXo008.png" alt="img" /></p>
<h6>其他指令</h6>
<p>Mc 的其他协议指令包括 delete、incr、decr、touch、gat、gats、slabs、lru、stats 这 9 种指令。</p>
<p>其中 delete 用于删除一个 key。</p>
<p>incr/decr 用于对一个无符号长整型数字进行加或减。</p>
<p>touch、gat、gats 是 Mc 后来增加的指令,都可以用来修改 key 的过期时间。不同点是 touch 只修改 key 的过期时间,不获取 key对应的value。</p>
<p>而 gat、gats 指令,不仅会修改 key 的过期时间,还会获取 key 对应的 flag 和 value 数据。gats 同 gets还会额外获取 cas 唯一 id 值。</p>
<p>Slabs reassign 用于在 Mc 内存达到设定上限后,将 slab 重新在不同的 slabclass 之间分配。这样可以规避 Mc 启动后自动分配而产生随机性,使特殊 size 的数据也得到较好的命中率。Slabs automove 是一个开关指令,当打开时,就允许 Mc 后台线程自行决定何时将 slab 在slabclass 之间重新分配。</p>
<p>lru 指令用于 Mc LRU 的设置和调优。比如 LRU tune 用于设置 HOT、WARM LRU 的内存占比。LRU mode 用来设置 Mc 只使用 COLD LRU还是使用新版的 4 个 LRU 的新策略。LRU TEMP_TTL 用来设置 Mc 的 TEMP LRU 的TTL值默认是 61s小于这个 TMEP_TTL 的 key会被插入到 TEMP LRU。</p>
<p>Stats 用于获取 Mc 的各种统计数据。Stats 后面可以跟 statistics、slabs、size 等参数,来进一步获取更多不同的详细统计。</p>
<h6>Client 使用</h6>
<p>Mc 在互联网企业应用广泛,热门语言基本都有 Mc client 的实现。以 Java 语言为例,互联网业界广泛使用的有 Memcached-Java-Client、SpyMemcached、Xmemcached 等。</p>
<p>Memcached-Java-Client 推出时间早10 年前就被广泛使用,这个 client 性能一般,但足够稳定,很多互联网企业至今仍在使用。不过这个 client 几年前就停止了更新。</p>
<p>SpyMemcached 出现的比较晚,性能较好,但高并发访问场景,稳定性欠缺。近几年</p>
<p>变更很少,基本停止了更新。</p>
<p>Xmemcached 性能较好综合表现最佳。而且社区活跃度高近些年也一直在持续更新中。Java 新项目启动,推荐使用 Xmemcached。</p>
<p>在使用 Mc client 时,有一些通用性的调优及改进方案。比如,如果读写的 key/value 较大,需要设置更大的缓冲 buf以提高性能。在一些业务场景中需要启用 TCP_NODELAY避免 40ms 的延迟问题。同时,如果存取的 key/value size 较大可以设置一个压缩阀值超过阀值就对value 进行压缩算法,减少读写及存储的空间。</p>
<p>为了避免缓存雪崩,并更好地应对极热 key 及洪水流量的问题,还可以对 Mc client 进行封装,加入多副本、多层级策略,使 Mc 缓存系统在任何场景下,都可做到高可用、高性能。</p>
<p>讲到这里Mc 的核心知识点就基本讲完了,知识点结构图如下所示。</p>
<p><img src="assets/CgotOV2lNqOAbxgDAAFIRc23upk013.png" alt="img" /></p>
<p>回顾一下最近几节课的内容。首先,学习了 Mc 的系统架构,学习了 Mc 基于 libevent 的网络模型,学习了 Mc 的多线程处理,包括主线程、工作线程如何进行网络 IO 协调及处理,学习了 Mc 的状态机。然后,继续学习了 Mc 用于定位 key 的哈希表,学习了用于数据生命周期管理的 LRU还学习 slab 分配机制,以及 Mc 数据的存储机理。最后,还完整学习了 Mc的协议了解了以 Java 语言为例的 3 种 Mc client以及 Mc client 在线上使用过程中,如何进行调优及改进。</p>
<p>根据下面 Mc 协议的思维导图,查看自己是否对所有指令都有理解,可以结合 Mc 的协议文档,启动一个 Mc 实例,进行各个命令的实际操练。</p>
<p><img src="assets/CgoB5l2lNqOASrF2AADzbaPtWVI518.png" alt="img" /></p>
</div>
</div>
<div>
<div style="float: left">
<a href="/专栏/300分钟吃透分布式缓存-完/12 为何MC能长期维持高性能读写.md">上一页</a>
</div>
<div style="float: right">
<a href="/专栏/300分钟吃透分布式缓存-完/14 大数据时代MC如何应对新的常见问题.md">下一页</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":"70996e4a3c323d60","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>