learn.lianglianglee.com/专栏/300分钟吃透分布式缓存-完/07 MC为何是应用最广泛的缓存组件?.md.html
2022-05-11 19:04:14 +08:00

551 lines
21 KiB
HTML
Raw Permalink 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>07 MC为何是应用最广泛的缓存组件.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.html">00 开篇寄语:缓存,你真的用对了吗?.md.html</a>
</li>
<li>
<a href="/专栏/300分钟吃透分布式缓存-完/01 业务数据访问性能太低怎么办?.md.html">01 业务数据访问性能太低怎么办?.md.html</a>
</li>
<li>
<a href="/专栏/300分钟吃透分布式缓存-完/02 如何根据业务来选择缓存模式和组件?.md.html">02 如何根据业务来选择缓存模式和组件?.md.html</a>
</li>
<li>
<a href="/专栏/300分钟吃透分布式缓存-完/03 设计缓存架构时需要考量哪些因素?.md.html">03 设计缓存架构时需要考量哪些因素?.md.html</a>
</li>
<li>
<a href="/专栏/300分钟吃透分布式缓存-完/04 缓存失效、穿透和雪崩问题怎么处理?.md.html">04 缓存失效、穿透和雪崩问题怎么处理?.md.html</a>
</li>
<li>
<a href="/专栏/300分钟吃透分布式缓存-完/05 缓存数据不一致和并发竞争怎么处理?.md.html">05 缓存数据不一致和并发竞争怎么处理?.md.html</a>
</li>
<li>
<a href="/专栏/300分钟吃透分布式缓存-完/06 Hot Key和Big Key引发的问题怎么应对.md.html">06 Hot Key和Big Key引发的问题怎么应对.md.html</a>
</li>
<li>
<a class="current-tab" href="/专栏/300分钟吃透分布式缓存-完/07 MC为何是应用最广泛的缓存组件.md.html">07 MC为何是应用最广泛的缓存组件.md.html</a>
</li>
<li>
<a href="/专栏/300分钟吃透分布式缓存-完/08 MC系统架构是如何布局的.md.html">08 MC系统架构是如何布局的.md.html</a>
</li>
<li>
<a href="/专栏/300分钟吃透分布式缓存-完/09 MC是如何使用多线程和状态机来处理请求命令的.md.html">09 MC是如何使用多线程和状态机来处理请求命令的.md.html</a>
</li>
<li>
<a href="/专栏/300分钟吃透分布式缓存-完/10 MC是怎么定位key的.md.html">10 MC是怎么定位key的.md.html</a>
</li>
<li>
<a href="/专栏/300分钟吃透分布式缓存-完/11 MC如何淘汰冷key和失效key.md.html">11 MC如何淘汰冷key和失效key.md.html</a>
</li>
<li>
<a href="/专栏/300分钟吃透分布式缓存-完/12 为何MC能长期维持高性能读写.md.html">12 为何MC能长期维持高性能读写.md.html</a>
</li>
<li>
<a href="/专栏/300分钟吃透分布式缓存-完/13 如何完整学习MC协议及优化client访问.md.html">13 如何完整学习MC协议及优化client访问.md.html</a>
</li>
<li>
<a href="/专栏/300分钟吃透分布式缓存-完/14 大数据时代MC如何应对新的常见问题.md.html">14 大数据时代MC如何应对新的常见问题.md.html</a>
</li>
<li>
<a href="/专栏/300分钟吃透分布式缓存-完/15 如何深入理解、应用及扩展 Twemproxy.md.html">15 如何深入理解、应用及扩展 Twemproxy.md.html</a>
</li>
<li>
<a href="/专栏/300分钟吃透分布式缓存-完/16 常用的缓存组件Redis是如何运行的.md.html">16 常用的缓存组件Redis是如何运行的.md.html</a>
</li>
<li>
<a href="/专栏/300分钟吃透分布式缓存-完/17 如何理解、选择并使用Redis的核心数据类型.md.html">17 如何理解、选择并使用Redis的核心数据类型.md.html</a>
</li>
<li>
<a href="/专栏/300分钟吃透分布式缓存-完/18 Redis协议的请求和响应有哪些“套路”可循.md.html">18 Redis协议的请求和响应有哪些“套路”可循.md.html</a>
</li>
<li>
<a href="/专栏/300分钟吃透分布式缓存-完/19 Redis系统架构中各个处理模块是干什么的.md.html">19 Redis系统架构中各个处理模块是干什么的.md.html</a>
</li>
<li>
<a href="/专栏/300分钟吃透分布式缓存-完/20 Redis如何处理文件事件和时间事件.md.html">20 Redis如何处理文件事件和时间事件.md.html</a>
</li>
<li>
<a href="/专栏/300分钟吃透分布式缓存-完/21 Redis读取请求数据后如何进行协议解析和处理.md.html">21 Redis读取请求数据后如何进行协议解析和处理.md.html</a>
</li>
<li>
<a href="/专栏/300分钟吃透分布式缓存-完/22 怎么认识和应用Redis内部数据结构.md.html">22 怎么认识和应用Redis内部数据结构.md.html</a>
</li>
<li>
<a href="/专栏/300分钟吃透分布式缓存-完/23 Redis是如何淘汰key的.md.html">23 Redis是如何淘汰key的.md.html</a>
</li>
<li>
<a href="/专栏/300分钟吃透分布式缓存-完/24 Redis崩溃后如何进行数据恢复的.md.html">24 Redis崩溃后如何进行数据恢复的.md.html</a>
</li>
<li>
<a href="/专栏/300分钟吃透分布式缓存-完/25 Redis是如何处理容易超时的系统调用的.md.html">25 Redis是如何处理容易超时的系统调用的.md.html</a>
</li>
<li>
<a href="/专栏/300分钟吃透分布式缓存-完/26 如何大幅成倍提升Redis处理性能.md.html">26 如何大幅成倍提升Redis处理性能.md.html</a>
</li>
<li>
<a href="/专栏/300分钟吃透分布式缓存-完/27 Redis是如何进行主从复制的.md.html">27 Redis是如何进行主从复制的.md.html</a>
</li>
<li>
<a href="/专栏/300分钟吃透分布式缓存-完/28 如何构建一个高性能、易扩展的Redis集群.md.html">28 如何构建一个高性能、易扩展的Redis集群.md.html</a>
</li>
<li>
<a href="/专栏/300分钟吃透分布式缓存-完/29 从容应对亿级QPS访问Redis还缺少什么.md.html">29 从容应对亿级QPS访问Redis还缺少什么.md.html</a>
</li>
<li>
<a href="/专栏/300分钟吃透分布式缓存-完/30 面对海量数据,为什么无法设计出完美的分布式缓存体系?.md.html">30 面对海量数据,为什么无法设计出完美的分布式缓存体系?.md.html</a>
</li>
<li>
<a href="/专栏/300分钟吃透分布式缓存-完/31 如何设计足够可靠的分布式缓存体系,以满足大中型移动互联网系统的需要?.md.html">31 如何设计足够可靠的分布式缓存体系,以满足大中型移动互联网系统的需要?.md.html</a>
</li>
<li>
<a href="/专栏/300分钟吃透分布式缓存-完/32 一个典型的分布式缓存系统是什么样的?.md.html">32 一个典型的分布式缓存系统是什么样的?.md.html</a>
</li>
<li>
<a href="/专栏/300分钟吃透分布式缓存-完/33 如何为秒杀系统设计缓存体系?.md.html">33 如何为秒杀系统设计缓存体系?.md.html</a>
</li>
<li>
<a href="/专栏/300分钟吃透分布式缓存-完/34 如何为海量计数场景设计缓存体系?.md.html">34 如何为海量计数场景设计缓存体系?.md.html</a>
</li>
<li>
<a href="/专栏/300分钟吃透分布式缓存-完/35 如何为社交feed场景设计缓存体系.md.html">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>07 MC为何是应用最广泛的缓存组件</h1>
<p>好,我是你的缓存老师陈波,欢迎你进入第 7 课时“Memcached 原理及特性”的学习。</p>
<p>众所周知用户体验可以说是互联网企业最看重的指标而在用户体验中请求响应速度首当其冲。因此互联网系统对性能的追求是永无止境的。性能争霸缓存为王Memcached作为互联网系统使用最广泛、影响最大的标配缓存组件可以说的上是王中之王了。</p>
<p>本课时将讲解 Memcached 的原理及特性,系统架构,还会重点讲解 Memcached 的网络模型、状态机,最后还会涉及到 Memcached 命令处理的整个流程。</p>
<h6>Memcached 原理及特性</h6>
<p>首先来看 Memcached 的原理及特性。</p>
<h6>原理</h6>
<p>Memcached 是一个开源的、高性能的分布式 key/value 内存缓存系统。它以 key/value 键值对的方式存储数据,是一个键值类型的 NoSQL 组件。</p>
<p>NoSQL 即 Not SQL泛指非关系型数据存储。NoSQL 是通过聚合模型来进行数据处理的。其聚合模型主要分为key/value 键值对、列族、图形等几种方式。其中 key/value 键值类似我们平常使用的 map只能通过 key 来进行查找和变更操作。我们使用的 Memcached、Redis 等都是 key/value 类型的 NoSQL 存储组件。</p>
<p>Memcached 简称 Mc是一个典型的内存型缓存组件这就意味着Mc 一旦重启就会丢失所有的数据。如下图所示Mc 组件之间相互不通信,完全由 client 对 key 进行 Hash 后分布和协同。Mc 采用多线程处理请求,由一个主线程和任意多个工作线程协作,从而充分利用多核,提升 IO 效率。</p>
<p><img src="assets/CgoB5l2kTEyABYmcAAD91NLPzIs234.png" alt="img" /></p>
<h6>slab 机制</h6>
<p>接下来介绍 Mc 的 slab 机制。</p>
<p>Mc 并不是将所有数据放在一起来进行管理的,而是将内存划分为一系列相同大小的 slab 空间后,每个 slab 只管理一定范围内的数据存储。也就是说 Mc 内部采用 slab 机制来管理内存分配。Mc 内的内存分配以 slab 为单位,默认情况下一个 slab 是 1MB可以通过 -I 参数在启动时指定其他数值。</p>
<p>slab 空间内部,会被进一步划分为一系列固定大小的 chunk。每个 chunk 内部存储一个 Item利用 Item 结构存储数据。因为 chunk 大小固定,而 key/value 数据的大小随机。所以Item存储完 key/value 数据后一般还会有多余的空间这个多余的空间就被浪费了。为了提升内存的使用效率chunk size 就不能太大,而要尽量选择与 key/value size 接近的 ,从而减少 chunk 内浪费的空间。</p>
<p>Mc 在分配内存时,先将内存按固定大小划分成 slab然后再将不同 slab 分拆出固定 size 的 chunk。虽然 slab 内的 chunk 大小相同,但不同 slab 的 chunk size 并不同Mc 会按照一个固定比例,使划分的 chunk size 逐步增大,从而满足不同大小 key/value 存储的需要。</p>
<p>如下图,一组具有相同 chunk size 的所有 slab就组成一个 slabclass。不同 slabclass 的 chunk size 按递增因子一次增加。Mc 就通过 slabclass 来管理一组 slab 内的存储空间的。每个 slabclass 内部有一个 freelist ,包含这组 slab 里所有空闲的 chunk当需要存储数据时从这个 freelist 里面快速分配一个 chunk 做存储空间。当 Item 数据淘汰剔除时,这个 Item 所在的 chunk 又被回收至这个 freelist。</p>
<p><img src="assets/CgotOV2kTEyALjAgAADUQE7hy3o476.png" alt="img" /></p>
<p>Mc 在通过 slab 机制管理内存分配时,实际 key/value 是存在 Item 结构中,所以对 key/value 的存储空间分配就转换为对 Item 的分配。而 Item 空间的分配有 2 种方式,如果 Mc 有空闲空间,则从 slabclass 的 freelist 分配;如果没有空闲空间,则从对应 slabclass id 对应的 LRU 中剔除一个 Item来复用这个 Item 的空间。</p>
<p>在查找或变更一个 key 时,首先要定位这个 key 所在的存储位置。Mc 是通过哈希表 Hashtable 来定位 key 的。Hashtable 可以看作是一个内存空间连续的大数组,而这个大数据的每一个槽位对应一个 key 的 Hash 值,这个槽位也称 bucket。由于不同 key 的 Hash 值可能相同,所以 Mc 在 Hashtable 的每个捅内部再用一个单向链表,来解决 Hash 冲突的问题。</p>
<p>Mc 内部是通过 LRU 来管理存储 Item 数据的,当内存不足时,会从 LRU 队尾中剔除一个过期或最不活跃的 key供新的 Item 使用。</p>
<h6>特性</h6>
<p>讲完 slab 机制我们来学习 Mc 的特性。</p>
<ul>
<li>Mc 最大的特性是高性能,单节点压测性能能达到百万级的 QPS。</li>
<li>其次因为 Mc 的访问协议很简单,只有 get/set/cas/touch/gat/stats 等有限的几个命令。Mc 的访问协议简单,跟它的存储结构也有关系。</li>
<li>Mc 存储结构很简单,只存储简单的 key/value 键值对,而且对 value 直接以二进制方式存储,不识别内部存储结构,所以有限几个指令就可以满足操作需要。</li>
<li>Mc 完全基于内存操作,在系统运行期间,在有新 key 写进来时,如果没有空闲内存分配,就会对最不活跃的 key 进行 eviction 剔除操作。</li>
<li>最后Mc 服务节点运行也特别简单,不同 Mc 节点之间互不通信,由 client 自行负责管理数据分布。</li>
</ul>
</div>
</div>
<div>
<div style="float: left">
<a href="/专栏/300分钟吃透分布式缓存-完/06 Hot Key和Big Key引发的问题怎么应对.md.html">上一页</a>
</div>
<div style="float: right">
<a href="/专栏/300分钟吃透分布式缓存-完/08 MC系统架构是如何布局的.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":"70996e3e29853d60","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>