<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>w3xse7en</title>
    <link>https://w3xse7en.github.io/</link>
      <atom:link href="https://w3xse7en.github.io/index.xml" rel="self" type="application/rss+xml" />
    <description>w3xse7en</description>
    <generator>Wowchemy (https://wowchemy.com)</generator><language>zh</language>
    <image>
      <url>https://w3xse7en.github.io/images/icon_hua2ec155b4296a9c9791d015323e16eb5_11927_512x512_fill_lanczos_center_2.png</url>
      <title>w3xse7en</title>
      <link>https://w3xse7en.github.io/</link>
    </image>
    
    <item>
      <title>Go</title>
      <link>https://w3xse7en.github.io/docs/lang/go/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>https://w3xse7en.github.io/docs/lang/go/</guid>
      <description>&lt;h2 id=&#34;select&#34;&gt;select&lt;/h2&gt;
&lt;p&gt;go select思想来源于网络IO模型中的select，本质上也是IO多路复用，只不过这里的IO是基于channel而不是基于网络，同时go select也有一些自己不同的特性。&lt;/p&gt;
&lt;h3 id=&#34;特性&#34;&gt;特性:&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;每个case都必须是一个通信&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;所有channel表达式都会被求值&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;所有被发送的表达式都会被求值&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;如果任意某个通信可以进行，它就执行；其他被忽略。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;如果有多个case都可以运行，select会随机公平地选出一个执行。其他不会执行。否则执行default子句(如果有)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;如果没有default字句，select将阻塞，直到某个通信可以运行；Go不会重新对channel或值进行求值。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;冷知识：select 使用 c语言 的AsyncCall2函数 该函数执行时间为 200ms&lt;/p&gt;
&lt;p&gt;问题：分别指定耗时50ms，200ms，3000ms，select打印最先完成的异步调用耗时，预想：50ms 先 200ms 后，结果：50ms 与 200ms 随机出现&lt;/p&gt;
&lt;h3 id=&#34;用途&#34;&gt;用途&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;结合特性5,6，可以通过带 default 语句的 select 实现非阻塞读写&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;结合特性2，每次 select 都会对所有通信表达式求值，因此可通过 time.After简洁实现定时器功能，并且定时任务可通过 done channel 停止&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;结合特性4，如果多个case满足读写条件，select会随机选择一个语句执行&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;br&gt;
&lt;h2 id=&#34;context&#34;&gt;context&lt;/h2&gt;
&lt;p&gt;context.Context 是用来设置截止日期、同步信号，传递请求相关值的结构体。&lt;/p&gt;
&lt;p&gt;在 Goroutine 构成的树形结构中对信号进行同步以减少计算资源的浪费是 context.Context 的最大作用&lt;/p&gt;
&lt;p&gt;每一个 context.Context 都会从最顶层的 Goroutine 一层一层传递到最下层。context.Context 可以在上层 Goroutine 执行出现错误时，将信号及时同步给下层&lt;/p&gt;
&lt;p&gt;当最上层的 Goroutine 因为某些原因执行失败时，下层的 Goroutine 由于没有接收到这个信号所以会继续工作；但是当我们正确地使用 context.Context 时，就可以在下层及时停掉无用的工作以减少额外资源的消耗&lt;/p&gt;
&lt;h1 id=&#34;defer&#34;&gt;defer&lt;/h1&gt;
&lt;p&gt;defer 是否会在panic后执行&lt;/p&gt;
&lt;h1 id=&#34;interface&#34;&gt;interface&lt;/h1&gt;
&lt;ol&gt;
&lt;li&gt;writing generic algorithm&lt;/li&gt;
&lt;li&gt;hiding implementation detail&lt;/li&gt;
&lt;li&gt;providing interception points&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;interface拥有两个指针
一个指向类型 一个指向具体值&lt;/p&gt;
&lt;h1 id=&#34;slice&#34;&gt;slice&lt;/h1&gt;
&lt;p&gt;分配在连续的内存地址上&lt;/p&gt;
&lt;p&gt;元素类型一致，元素存储宽度一致&lt;/p&gt;
&lt;p&gt;空间大小固定，不能修改&lt;/p&gt;
&lt;p&gt;可以通过索引计算出元素对应存储的位置（只需要知道数组内存的起始位置和数据元素宽度即可）&lt;/p&gt;
&lt;p&gt;会出现数据溢出的问题（下标越界）&lt;/p&gt;
&lt;h2 id=&#34;slice扩容&#34;&gt;slice扩容&lt;/h2&gt;
&lt;p&gt;如果新的slice大小是当前大小2倍以上，则大小增长为新大小&lt;/p&gt;
&lt;p&gt;如果当前slice cap 小于1024，按每次2倍增长，否则每次按当前大小1/4增长。直到增长的大小超过或等于新大小&lt;/p&gt;
&lt;p&gt;append的实现是在内存中将slice的array值赋值到新申请的array上&lt;/p&gt;
&lt;p&gt;性能&lt;/p&gt;
&lt;p&gt;通过上面我们知道slice的扩容涉及到内存的拷贝，这样带来的好处是数据存储在连续内存上，比随机访问快很多，最直接的性能提升就是缓存命中率会高很多,这也就是为什么slice不采用动态链表实现的原因吧&lt;/p&gt;
&lt;p&gt;我们知道拷贝内存数据是有开销的， 而其中最大的开销不在 memmove&lt;/p&gt;
&lt;p&gt;数据上，而是在开辟一块新内存malloc及之后的GC压力&lt;/p&gt;
&lt;p&gt;拷贝连续内存是很快的，随着cap变大，拷贝总成本还是 O(N) ,只是常数大了&lt;/p&gt;
&lt;p&gt;假如不想发生拷贝，那你就没有连续内存。此时随机访问开销会是：链表 O(N)&lt;/p&gt;
&lt;p&gt;当你能大致知道所需的最大空间（在大部分时候都是的）时，在make的时候预留相应的 cap 就好
如果需要的空间很大，而且每次都不确定，那就要在浪费内存和耗 CPU 在 malloc + gc 上做权衡
链表的查找操作是从第一个元素开始，所以相对数组要耗时间的多，因为采用这样的结构对读的性能有很大的提高&lt;/p&gt;
&lt;h2 id=&#34;mysql&#34;&gt;mysql&lt;/h2&gt;
&lt;h3 id=&#34;性能调优&#34;&gt;性能调优&lt;/h3&gt;
&lt;h4 id=&#34;sql优化&#34;&gt;SQL优化&lt;/h4&gt;
&lt;p&gt;小表驱动大表
limit限定
索引添加
适当添加冗余字段，减少表关联。&lt;/p&gt;
&lt;h4 id=&#34;系统优化&#34;&gt;系统优化&lt;/h4&gt;
&lt;p&gt;max_connections 最大连接数&lt;/p&gt;
&lt;p&gt;innodb_buffer_pool_size 数据缓冲区buffer pool大小&lt;/p&gt;
&lt;p&gt;sort_buffer_size 排序缓冲区内存大小&lt;/p&gt;
&lt;p&gt;join_buffer_size 使用连接缓冲区大小&lt;/p&gt;
&lt;p&gt;read_buffer_size 全表扫描时分配的缓冲区大小&lt;/p&gt;
&lt;h1 id=&#34;缓存&#34;&gt;缓存&lt;/h1&gt;
&lt;p&gt;主动式缓存
用户更新数据 同时更新缓存&lt;/p&gt;
&lt;p&gt;被动式缓存
用户更新数据 删除缓存，被读取时载入缓存&lt;/p&gt;
&lt;h1 id=&#34;maps&#34;&gt;Maps&lt;/h1&gt;
&lt;p&gt;线程不安全&lt;/p&gt;
&lt;p&gt;底层使用的hash结构&lt;/p&gt;
&lt;p&gt;hash算法使用aes hash hash值分为 高位hash和低位hash&lt;/p&gt;
&lt;p&gt;高位哈希值：是用来确定当前的bucket（桶）有没有所存储的数据的 bmap a bucket for a Go map&lt;/p&gt;
&lt;p&gt;低位哈希值：是用来确定，当前的数据存在了哪个bucket（桶）hmap a header for a go map&lt;/p&gt;
&lt;h2 id=&#34;参考&#34;&gt;参考&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://wudaijun.com/2017/10/go-select/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;go select机制与常见的坑&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;
&lt;hr&gt;
</description>
    </item>
    
    <item>
      <title>Goroutine</title>
      <link>https://w3xse7en.github.io/docs/lang/goroutine/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>https://w3xse7en.github.io/docs/lang/goroutine/</guid>
      <description>&lt;h1 id=&#34;goroutine&#34;&gt;goroutine&lt;/h1&gt;
&lt;p&gt;leak 协程泄露
Goroutine为什么没有ID号&lt;/p&gt;
&lt;h2 id=&#34;goroutine调度&#34;&gt;Goroutine调度&lt;/h2&gt;
&lt;p&gt;G P M
抢占式调度&lt;/p&gt;
&lt;p&gt;动态栈&lt;/p&gt;
&lt;h1 id=&#34;协程线程进程的区别&#34;&gt;协程，线程，进程的区别&lt;/h1&gt;
&lt;h1 id=&#34;进程&#34;&gt;进程&lt;/h1&gt;
&lt;p&gt;进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位。每个进程都有自己的独立内存空间，不同进程通过进程间通信来通信。由于进程比较重量，占据独立的内存，所以上下文进程间的切换开销（栈、寄存器、虚拟内存、文件句柄等）比较大，但相对比较稳定安全。&lt;/p&gt;
&lt;h4 id=&#34;线程&#34;&gt;线程&lt;/h4&gt;
&lt;p&gt;线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。线程间通信主要通过共享内存，上下文切换很快，资源开销较少，但相比进程不够稳定容易丢失数据。&lt;/p&gt;
&lt;h4 id=&#34;协程&#34;&gt;协程&lt;/h4&gt;
&lt;p&gt;协程是一种用户态的轻量级线程，协程的调度完全由用户控制。协程拥有自己的寄存器上下文和栈。协程调度切换时，将寄存器上下文和栈保存到其他地方，在切回来的时候，恢复先前保存的寄存器上下文和栈，直接操作栈则基本没有内核切换的开销，可以不加锁的访问全局变量，所以上下文的切换非常快。&lt;/p&gt;
&lt;h2 id=&#34;channel&#34;&gt;channel&lt;/h2&gt;
&lt;h2 id=&#34;select&#34;&gt;select&lt;/h2&gt;
&lt;h2 id=&#34;sync&#34;&gt;sync&lt;/h2&gt;
</description>
    </item>
    
    <item>
      <title>MySQL</title>
      <link>https://w3xse7en.github.io/docs/sql/mysql/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>https://w3xse7en.github.io/docs/sql/mysql/</guid>
      <description>&lt;hr&gt;
&lt;h2&gt;目录&lt;/h2&gt;
&lt;nav id=&#34;TableOfContents&#34;&gt;
  &lt;ul&gt;
    &lt;li&gt;&lt;a href=&#34;#数据库三大范式&#34;&gt;数据库三大范式&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#mysql的binlog的几种录入格式&#34;&gt;MySQL的binlog的几种录入格式&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#索引有哪几种类型&#34;&gt;索引有哪几种类型&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#索引的数据结构b树&#34;&gt;索引的数据结构（B+树）&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#索引算法&#34;&gt;索引算法&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#索引设计的原则&#34;&gt;索引设计的原则&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#创建索引的原则&#34;&gt;创建索引的原则&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#创建索引时需要注意什么&#34;&gt;创建索引时需要注意什么&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#聚簇索引与非聚簇索引&#34;&gt;聚簇索引与非聚簇索引&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#事物的四大特性acid&#34;&gt;事物的四大特性(ACID)&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#事务的隔离级别&#34;&gt;事务的隔离级别&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#redo-log事务安全&#34;&gt;Redo Log（事务安全）&lt;/a&gt;
      &lt;ul&gt;
        &lt;li&gt;&lt;a href=&#34;#crash-safe-与两阶段提交&#34;&gt;crash safe 与两阶段提交&lt;/a&gt;&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#mvcc多版本并发控制undo-log&#34;&gt;MVCC（多版本并发控制）（Undo Log）&lt;/a&gt;
      &lt;ul&gt;
        &lt;li&gt;&lt;a href=&#34;#mvcc实现原理&#34;&gt;MVCC实现原理&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#mvcc与事务&#34;&gt;MVCC与事务&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#为什么需要mvcc&#34;&gt;为什么需要MVCC&lt;/a&gt;&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#当前读快照读-与-幻读&#34;&gt;当前读，快照读 与 幻读&lt;/a&gt;
      &lt;ul&gt;
        &lt;li&gt;&lt;a href=&#34;#当前读&#34;&gt;当前读&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#快照读&#34;&gt;快照读&lt;/a&gt;&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#锁的粒度&#34;&gt;锁的粒度&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#锁的类别&#34;&gt;锁的类别&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#锁的算法&#34;&gt;锁的算法&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#什么是死锁怎么解决&#34;&gt;什么是死锁？怎么解决&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#乐观锁和悲观锁&#34;&gt;乐观锁和悲观锁&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#大表数据查询怎么优化&#34;&gt;大表数据查询，怎么优化？&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#参考&#34;&gt;参考&lt;/a&gt;&lt;/li&gt;
  &lt;/ul&gt;
&lt;/nav&gt;
&lt;hr&gt;
&lt;br&gt;
&lt;h2 id=&#34;数据库三大范式&#34;&gt;数据库三大范式&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;第一范式：每个列都不可以再拆分。&lt;/li&gt;
&lt;li&gt;第二范式：在第一范式的基础上，非主键列完全依赖于主键，而不能是依赖于主键的一部分。&lt;/li&gt;
&lt;li&gt;第三范式：在第二范式的基础上，非主键列只依赖于主键，不依赖于其他非主键。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在设计数据库结构的时候，要尽量遵守三范式，如果不遵守，必须有足够的理由。比如性能。事实上我们经常会为了性能而妥协数据库的设计。&lt;/p&gt;
&lt;h2 id=&#34;mysql的binlog的几种录入格式&#34;&gt;MySQL的binlog的几种录入格式&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;statement模式下，每一条会修改数据的sql都会记录在binlog中。不需要记录每一行的变化，减少了binlog日志量，节约了IO，提高性能。由于sql的执行是有上下文的，因此在保存的时候需要保存相关的信息，同时还有一些使用了函数之类的语句无法被记录复制。&lt;/li&gt;
&lt;li&gt;row级别下，不记录sql语句上下文相关信息，仅保存哪条记录被修改。记录单元为每一行的改动，基本是可以全部记下来但是由于很多操作，会导致大量行的改动(比如alter table)，因此这种模式的文件保存的信息太多，日志量太大。&lt;/li&gt;
&lt;li&gt;mixed，一种折中的方案，普通操作使用statement记录，当无法使用statement的时候使用row。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;此外，新版的MySQL中对row级别也做了一些优化，当表结构发生变化的时候，会记录语句而不是逐行记录。&lt;/p&gt;
&lt;h2 id=&#34;索引有哪几种类型&#34;&gt;索引有哪几种类型&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;主键索引: 数据列不允许重复，不允许为NULL，一个表只能有一个主键。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;唯一索引: 数据列不允许重复，允许为NULL值，一个表允许多个列创建唯一索引。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;ALTER TABLE table_name ADD UNIQUE (column);&lt;/code&gt; 创建唯一索引
&lt;code&gt;ALTER TABLE table_name ADD UNIQUE (column1,column2);&lt;/code&gt; 创建唯一组合索引&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;普通索引: 基本的索引类型，没有唯一性的限制，允许为NULL值。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;ALTER TABLE table_name ADD INDEX index_name (column);&lt;/code&gt;创建普通索引
&lt;code&gt;ALTER TABLE table_name ADD INDEX index_name(column1, column2, column3);&lt;/code&gt;创建组合索引。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;全文索引： 是目前搜索引擎使用的一种关键技术。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;ALTER TABLE table_name ADD FULLTEXT (column);&lt;/code&gt;创建全文索引&lt;/p&gt;
&lt;h2 id=&#34;索引的数据结构b树&#34;&gt;索引的数据结构（B+树）&lt;/h2&gt;
&lt;p&gt;一棵 B+ 树需要满足以下条件：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;节点的子树数和关键字数相同（B 树是关键字数比子树数少一）&lt;/li&gt;
&lt;li&gt;节点的关键字表示的是子树中的最大数，在子树中同样含有这个数据&lt;/li&gt;
&lt;li&gt;叶子节点包含了全部数据，同时符合左小右大的顺序&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;B+ 树的三个优点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;层级更低，IO 次数更少&lt;/li&gt;
&lt;li&gt;每次都需要查询到叶子节点，查询性能稳定&lt;/li&gt;
&lt;li&gt;叶子节点形成有序链表，范围查询方便&lt;/li&gt;
&lt;/ol&gt;








  











&lt;figure id=&#34;figure-b树数据结构&#34;&gt;


  &lt;a data-fancybox=&#34;&#34; href=&#34;https://w3xse7en.github.io/media/sql/mysql/mysql_btree.png&#34; data-caption=&#34;B&amp;#43;树数据结构&#34;&gt;


  &lt;img src=&#34;https://w3xse7en.github.io/media/sql/mysql/mysql_btree.png&#34; alt=&#34;&#34;  &gt;
&lt;/a&gt;


  
  
  &lt;figcaption&gt;
    B+树数据结构
  &lt;/figcaption&gt;


&lt;/figure&gt;









  











&lt;figure id=&#34;figure-b树插入过程&#34;&gt;


  &lt;a data-fancybox=&#34;&#34; href=&#34;https://w3xse7en.github.io/media/sql/mysql/mysql_btree_insert.gif&#34; data-caption=&#34;B&amp;#43;树插入过程&#34;&gt;


  &lt;img src=&#34;https://w3xse7en.github.io/media/sql/mysql/mysql_btree_insert.gif&#34; alt=&#34;&#34;  &gt;
&lt;/a&gt;


  
  
  &lt;figcaption&gt;
    B+树插入过程
  &lt;/figcaption&gt;


&lt;/figure&gt;









  











&lt;figure id=&#34;figure-b树删除过程&#34;&gt;


  &lt;a data-fancybox=&#34;&#34; href=&#34;https://w3xse7en.github.io/media/sql/mysql/mysql_btree_delete.gif&#34; data-caption=&#34;B&amp;#43;树删除过程&#34;&gt;


  &lt;img src=&#34;https://w3xse7en.github.io/media/sql/mysql/mysql_btree_delete.gif&#34; alt=&#34;&#34;  &gt;
&lt;/a&gt;


  
  
  &lt;figcaption&gt;
    B+树删除过程
  &lt;/figcaption&gt;


&lt;/figure&gt;

&lt;h2 id=&#34;索引算法&#34;&gt;索引算法&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;BTree算法&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;BTree是最常用的mysql数据库索引算法，也是mysql默认的算法。因为它不仅可以被用在=,&amp;gt;,&amp;gt;=,&amp;lt;,&amp;lt;=和between这些比较操作符上，而且还可以用于like操作符，只要它的查询条件是一个不以通配符开头的常量。&lt;/p&gt;
&lt;ol start=&#34;2&#34;&gt;
&lt;li&gt;Hash算法&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Hash Hash索引只能用于对等比较，例如=,&amp;lt;=&amp;gt;（相当于=）操作符。由于是一次定位数据，不像BTree索引需要从根节点到枝节点，最后才能访问到页节点这样多次IO访问，所以检索效率远高于BTree索引。&lt;/p&gt;
&lt;h2 id=&#34;索引设计的原则&#34;&gt;索引设计的原则&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;适合索引的列是出现在where子句中的列，或者连接子句中指定的列。&lt;/li&gt;
&lt;li&gt;基数较小的类，索引效果较差，没有必要在此列建立索引&lt;/li&gt;
&lt;li&gt;使用短索引，如果对长字符串列进行索引，应该指定一个前缀长度，这样能够节省大量索引空间&lt;/li&gt;
&lt;li&gt;不要过度索引。索引需要额外的磁盘空间，并降低写操作的性能。在修改表内容的时候，索引会进行更新甚至重构，索引列越多，这个时间就会越长。所以只保持需要的索引有利于查询即可。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;创建索引的原则&#34;&gt;创建索引的原则&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;最左前缀匹配原则，组合索引非常重要的原则，mysql会一直向右匹配直到遇到范围查询(&amp;gt;、&amp;lt;、between、like)就停止匹配，
比如a = 1 and b = 2 and c &amp;gt; 3 and d = 4 如果建立(a,b,c,d)顺序的索引，d是用不到索引的，如果建立(a,b,d,c)的索引则都可以用到，a,b,d的顺序可以任意调整。&lt;/li&gt;
&lt;li&gt;较频繁作为查询条件的字段才去创建索引&lt;/li&gt;
&lt;li&gt;更新频繁字段不适合创建索引&lt;/li&gt;
&lt;li&gt;若是不能有效区分数据的列不适合做索引列(如性别，男女未知，最多也就三种，区分度实在太低)&lt;/li&gt;
&lt;li&gt;尽量的扩展索引，不要新建索引。比如表中已经有a的索引，现在要加(a,b)的索引，那么只需要修改原来的索引即可。&lt;/li&gt;
&lt;li&gt;定义有外键的数据列一定要建立索引。&lt;/li&gt;
&lt;li&gt;对于那些查询中很少涉及的列，重复值比较多的列不要建立索引。&lt;/li&gt;
&lt;li&gt;对于定义为text、image和bit的数据类型的列不要建立索引。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;创建索引时需要注意什么&#34;&gt;创建索引时需要注意什么&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;非空字段：应该指定列为NOT NULL，除非你想存储NULL。在mysql中，含有空值的列很难进行查询优化，因为它们使得索引、索引的统计信息以及比较运算更加复杂。你应该用0、一个特殊的值或者一个空串代替空值；&lt;/li&gt;
&lt;li&gt;取值离散大的字段：（变量各个取值之间的差异程度）的列放到联合索引的前面，可以通过count()函数查看字段的差异值，返回值越大说明字段的唯一值越多字段的离散程度高；&lt;/li&gt;
&lt;li&gt;索引字段越小越好：数据库的数据存储以页为单位一页存储的数据越多一次IO操作获取的数据越大效率越高。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;聚簇索引与非聚簇索引&#34;&gt;聚簇索引与非聚簇索引&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;聚簇索引：将数据存储与索引放到了一块，找到索引也就找到了数据&lt;/li&gt;
&lt;li&gt;非聚簇索引：将数据存储于索引分开结构，索引结构的叶子节点指向了数据的对应行，myisam通过key_buffer把索引先缓存到内存中，
当需要访问数据时（通过索引访问数据），在内存中直接搜索索引，然后通过索引找到磁盘相应数据，这也就是为什么索引不在key buffer命中时，速度慢的原因。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;事物的四大特性acid&#34;&gt;事物的四大特性(ACID)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;原子性： 事务是最小的执行单位，不允许分割。事务的原子性确保动作要么全部完成，要么完全不起作用；&lt;/li&gt;
&lt;li&gt;一致性： 执行事务前后，数据保持一致，多个事务对同一个数据读取的结果是相同的；&lt;/li&gt;
&lt;li&gt;隔离性： 并发访问数据库时，一个用户的事务不被其他事务所干扰，各并发事务之间数据库是独立的；&lt;/li&gt;
&lt;li&gt;持久性： 一个事务被提交之后。它对数据库中数据的改变是持久的，即使数据库发生故障也不应该对其有任何影响。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;事务的隔离级别&#34;&gt;事务的隔离级别&lt;/h2&gt;
&lt;p&gt;为了达到事务的四大特性，数据库定义了4种不同的事务隔离级别，由低到高依次为Read uncommitted、Read committed、Repeatable read、Serializable，这四个级别可以逐个解决脏读、不可重复读、幻读这几类问题。&lt;/p&gt;
&lt;p&gt;SQL 标准定义了四个隔离级别：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;READ-UNCOMMITTED(读取未提交)： 最低的隔离级别，允许读取尚未提交的数据变更，可能会导致脏读、幻读或不可重复读。&lt;/li&gt;
&lt;li&gt;READ-COMMITTED(读取已提交)： 允许读取并发事务已经提交的数据，可以阻止脏读，但是幻读或不可重复读仍有可能发生。&lt;/li&gt;
&lt;li&gt;REPEATABLE-READ(可重复读)： 对同一字段的多次读取结果都是一致的，除非数据是被本身事务自己所修改，可以阻止脏读和不可重复读，但幻读仍有可能发生。&lt;/li&gt;
&lt;li&gt;SERIALIZABLE(可串行化)： 最高的隔离级别，完全服从ACID的隔离级别。所有的事务依次逐个执行，这样事务之间就完全不可能产生干扰，也就是说，该级别可以防止脏读、不可重复读以及幻读。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Mysql 默认采用的 REPEATABLE_READ隔离级别 Oracle 默认采用的 READ_COMMITTED隔离级别&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&#34;redo-log事务安全&#34;&gt;Redo Log（事务安全）&lt;/h2&gt;
&lt;p&gt;在工程存储项目中，有一个重要的概念，那就是 crash safe，即当服务器突然断电或宕机，需要保证已提交的数据或修改不会丢失，未提交的数据能够自动回滚，这就是 mysql ACID 特性中的一个十分重要的特性 &amp;ndash; Atomicity 原子性&lt;/p&gt;
&lt;p&gt;依靠 binlog 是无法保证 crash safe 的，因为 binlog 是事务提交时写入的，如果在 binlog 缓存中的数据持久化到硬盘之前宕机或断电，
在服务器恢复工作后，由于 binlog 缺失一部分已提交的操作数据，而主数据库中实际上这部分操作已经存在，从数据库因此无法同步这部分操作，从而造成主从数据库数据不一致，这是很严重的&lt;/p&gt;
&lt;p&gt;innodb 作为具体的一个存储引擎，他通过 redolog 实现了 crash safe 的支持&lt;/p&gt;
&lt;p&gt;mysql 有一个基本的技术理念，那就是 WAL，即  Write-Ahead Logging，先写日志，再写磁盘，从而保证每一次操作都有据可查，这里所说的“先写日志”中的日志就包括 innodb 的 redolog&lt;/p&gt;
&lt;h3 id=&#34;crash-safe-与两阶段提交&#34;&gt;crash safe 与两阶段提交&lt;/h3&gt;
&lt;p&gt;每条 redolog 都有两个状态 &amp;ndash; prepare 与 commit 状态&lt;/p&gt;
&lt;p&gt;例如对于一张 mysql 表，我们执行一条 SQL 语句：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;UPDATE A set C=C+1 WHERE ID=2&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;实际上，mysql 数据库会进行以下操作（下图中深色的是 mysql server 层所做的操作，浅色部分则是 innodb 存储引擎进行的操作）：&lt;/p&gt;








  











&lt;figure &gt;


  &lt;a data-fancybox=&#34;&#34; href=&#34;https://w3xse7en.github.io/media/sql/mysql/mysql_redo_log.png&#34; &gt;


  &lt;img src=&#34;https://w3xse7en.github.io/media/sql/mysql/mysql_redo_log.png&#34; alt=&#34;&#34;  &gt;
&lt;/a&gt;



&lt;/figure&gt;

&lt;p&gt;在写入 binlog 及事务提交前，innodb 先记录了 redolog，并标记为 prepare 状态，在事务提交后，innodb 会将 redolog 更新为 commit 状态，这样在异常发生时，就可以按照下面两条策略来处理：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;当异常情况发生时，如果第一次写入 redolog 成功，写入 binlog 失败，MySQL 会当做事务失败直接回滚，保证了后续 redolog 和 binlog 的准确性&lt;/li&gt;
&lt;li&gt;如果第一次写入 redolog 成功，binlog 也写入成功，当第二次写入 redolog 时候失败了，那数据恢复的过程中，MySQL 判断 redolog 状态为 prepare，且存在对应的 binlog 记录，则会重放事务提交，数据库中会进行相应的修改操作&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;整个过程是一个典型的两阶段提交过程，由 binlog 充当了协调者的角色，针对每一次日志写入，innodb 都会随之记录一个 8 字节序列号 &amp;ndash; LSN（日志逻辑序列号 log sequence number），他会随着日志写入不断单调递增&lt;/p&gt;
&lt;p&gt;binlog、DB 中的数据、redolog 三者就是通过 LSN 关联到一起的，因为数据页上记录了 LSN、日志开始与结束均记录了 LSN、刷盘节点 checkpoint 也记录了 LSN，因此 LSN 成为了整套系统中的全局版本信息&lt;/p&gt;
&lt;p&gt;当异常发生并重新启动后，innodb 会根据出在 prepare 状态的 redo log 记录去查找相同 LSN 的 binlog、数据记录，从而实现异常后的恢复&lt;/p&gt;
&lt;h2 id=&#34;mvcc多版本并发控制undo-log&#34;&gt;MVCC（多版本并发控制）（Undo Log）&lt;/h2&gt;
&lt;p&gt;undo log 与 redo log 一起构成了 MySQL 事务日志，日志先行原则 WAL 除了包含 redo log 外，也包括 undo log，事务中的每一次修改，innodb 都会先记录对应的 undo log 记录&lt;/p&gt;
&lt;p&gt;redo log 用于数据的灾后重新提交，undo log 主要用于数据修改的回滚&lt;/p&gt;
&lt;p&gt;redo log 记录的是物理页的修改，undo log 记录的是逻辑日志&lt;/p&gt;
&lt;p&gt;delete 一条记录时，undo log 中会记录一条对应的 insert 记录，反之亦然，当 update 一条记录时，它记录一条对应相反的 update 记录，如果 update 的是主键，则是对先删除后插入的两个事件的反向逻辑操作的记录&lt;/p&gt;








  











&lt;figure id=&#34;figure-undo-log&#34;&gt;


  &lt;a data-fancybox=&#34;&#34; href=&#34;https://w3xse7en.github.io/media/sql/mysql/mysql_undo_log_chain.png&#34; data-caption=&#34;undo log&#34;&gt;


  &lt;img src=&#34;https://w3xse7en.github.io/media/sql/mysql/mysql_undo_log_chain.png&#34; alt=&#34;&#34;  &gt;
&lt;/a&gt;


  
  
  &lt;figcaption&gt;
    undo log
  &lt;/figcaption&gt;


&lt;/figure&gt;

&lt;p&gt;这样，在事务回滚时，我们就可以从 undo log 中反向读取相应的内容，并进行回滚，同时，我们也可以根据 undo log 中记录的日志读取到一条被修改后数据的原值&lt;/p&gt;
&lt;p&gt;正是依赖 undo log，innodb 实现了 ACID 中的 C &amp;ndash; Consistency 即一致性&lt;/p&gt;
&lt;h3 id=&#34;mvcc实现原理&#34;&gt;MVCC实现原理&lt;/h3&gt;
&lt;p&gt;InnoDB 中 MVCC 的实现方式为：每一行记录都有两个隐藏列：DATA_TRX_ID、DATA_ROLL_PTR（如果没有主键，则还会多一个隐藏的主键列）。&lt;/p&gt;








  











&lt;figure id=&#34;figure-隐藏列data_trx_id-data_roll_ptr&#34;&gt;


  &lt;a data-fancybox=&#34;&#34; href=&#34;https://w3xse7en.github.io/media/sql/mysql/mysql_mvcc_struct.jpeg&#34; data-caption=&#34;隐藏列：DATA_TRX_ID, DATA_ROLL_PTR&#34;&gt;


  &lt;img src=&#34;https://w3xse7en.github.io/media/sql/mysql/mysql_mvcc_struct.jpeg&#34; alt=&#34;&#34;  &gt;
&lt;/a&gt;


  
  
  &lt;figcaption&gt;
    隐藏列：DATA_TRX_ID, DATA_ROLL_PTR
  &lt;/figcaption&gt;


&lt;/figure&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;DATA_TRX_ID：记录最近更新这条行记录的事务 ID，大小为 6 个字节&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;DATA_ROLL_PTR：表示指向该行回滚段（rollback segment）的指针，大小为 7 个字节，InnoDB 便是通过这个指针找到之前版本的数据。该行记录上所有旧版本，在 undo 中都通过链表的形式组织。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;DB_ROW_ID：行标识（隐藏单调自增 ID），大小为 6 字节，如果表没有主键，InnoDB 会自动生成一个隐藏主键，因此会出现这个列。另外，每条记录的头信息（record header）里都有一个专门的 bit（deleted_flag）来表示当前记录是否已经被删除。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;多个事务并行操作某行数据的情况下，不同事务对该行数据的 UPDATE 会产生多个版本，然后通过回滚指针组织成一条 Undo Log 链&lt;/p&gt;








  











&lt;figure id=&#34;figure-版本链&#34;&gt;


  &lt;a data-fancybox=&#34;&#34; href=&#34;https://w3xse7en.github.io/media/sql/mysql/mysql_mvcc_undo_chain.jpeg&#34; data-caption=&#34;版本链&#34;&gt;


  &lt;img src=&#34;https://w3xse7en.github.io/media/sql/mysql/mysql_mvcc_undo_chain.jpeg&#34; alt=&#34;&#34;  &gt;
&lt;/a&gt;


  
  
  &lt;figcaption&gt;
    版本链
  &lt;/figcaption&gt;


&lt;/figure&gt;

&lt;h3 id=&#34;mvcc与事务&#34;&gt;MVCC与事务&lt;/h3&gt;








  











&lt;figure id=&#34;figure-多版本读&#34;&gt;


  &lt;a data-fancybox=&#34;&#34; href=&#34;https://w3xse7en.github.io/media/sql/mysql/mysql_mvcc_example.jpeg&#34; data-caption=&#34;多版本读&#34;&gt;


  &lt;img src=&#34;https://w3xse7en.github.io/media/sql/mysql/mysql_mvcc_example.jpeg&#34; alt=&#34;&#34;  &gt;
&lt;/a&gt;


  
  
  &lt;figcaption&gt;
    多版本读
  &lt;/figcaption&gt;


&lt;/figure&gt;

&lt;p&gt;在事务 A 提交前后，事务 B 读取到的 x 的值是什么呢？答案是：事务 B 在不同的隔离级别下，读取到的值不一样。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果事务 B 的隔离级别是读未提交（RU），那么两次读取均读取到 x 的最新值，即 20。&lt;/li&gt;
&lt;li&gt;如果事务 B 的隔离级别是读已提交（RC），那么第一次读取到旧值 10，第二次因为事务 A 已经提交，则读取到新值 20。&lt;/li&gt;
&lt;li&gt;如果事务 B 的隔离级别是可重复读或者串行（RR，S），则两次均读到旧值 10，不论事务 A 是否已经提交。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在不同的隔离级别下，数据库通过 MVCC 和隔离级别，让事务之间并行操作遵循了某种规则，来保证单个事务内前后数据的一致性。&lt;/p&gt;
&lt;h3 id=&#34;为什么需要mvcc&#34;&gt;为什么需要MVCC&lt;/h3&gt;
&lt;p&gt;InnoDB 相比 MyISAM 有两大特点，一是支持事务而是支持行级锁，事务的引入带来了一些新的挑战。相对于串行处理来说，并发事务处理能大大增加数据库资源的利用率，提高数据库系统的事务吞吐量，从而可以支持可以支持更多的用户。但并发事务处理也会带来一些问题，主要包括以下几种情况：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;更新丢失（Lost Update）：当两个或多个事务选择同一行，然后基于最初选定的值更新该行时，由于每个事务都不知道其他事务的存在，就会发生丢失更新问题 —— 最后的更新覆盖了其他事务所做的更新。如何避免这个问题呢，最好在一个事务对数据进行更改但还未提交时，其他事务不能访问修改同一个数据。&lt;/li&gt;
&lt;li&gt;脏读（Dirty Reads）：一个事务正在对一条记录做修改，在这个事务并提交前，这条记录的数据就处于不一致状态；这时，另一个事务也来读取同一条记录，如果不加控制，第二个事务读取了这些尚未提交的脏数据，并据此做进一步的处理，就会产生未提交的数据依赖关系。这种现象被形象地叫做 “脏读”。&lt;/li&gt;
&lt;li&gt;不可重复读（Non-Repeatable Reads）：一个事务在读取某些数据已经发生了改变、或某些记录已经被删除了！这种现象叫做“不可重复读”。&lt;/li&gt;
&lt;li&gt;幻读（Phantom Reads）：一个事务按相同的查询条件重新读取以前检索过的数据，却发现其他事务插入了满足其查询条件的新数据，这种现象就称为 “幻读”。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;以上是并发事务过程中会存在的问题，解决更新丢失可以交给应用，但是后三者需要数据库提供事务间的隔离机制来解决。实现隔离机制的方法主要有两种：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;加读写锁&lt;/li&gt;
&lt;li&gt;一致性快照读，即 MVCC&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;当前读快照读-与-幻读&#34;&gt;当前读，快照读 与 幻读&lt;/h2&gt;
&lt;h3 id=&#34;当前读&#34;&gt;当前读&lt;/h3&gt;
&lt;p&gt;select&amp;hellip;lock in share mode (共享读锁)
select&amp;hellip;for update
update , delete , insert&lt;/p&gt;
&lt;p&gt;当前读, 读取的是最新版本, 并且对读取的记录加锁, 阻塞其他事务同时改动相同记录，避免出现安全问题。&lt;/p&gt;
&lt;p&gt;例如，假设要update一条记录，但是另一个事务已经delete这条数据并且commit了，如果不加锁就会产生冲突。所以update的时候肯定要是当前读，得到最新的信息并且锁定相应的记录。&lt;/p&gt;
&lt;h4 id=&#34;当前读的实现方式&#34;&gt;当前读的实现方式&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;next-key锁(行记录锁+Gap间隙锁)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;间隙锁：只有在Read Repeatable、Serializable隔离级别才有，就是锁定范围空间的数据，假设id有3,4,5，锁定id&amp;gt;3的数据，是指的4，5及后面的数字都会被锁定，因为此时如果不锁定没有的数据，例如当加入了新的数据id=6，就会出现幻读，间隙锁避免了幻读。&lt;/p&gt;
&lt;p&gt;1.对主键或唯一索引，如果当前读时，where条件全部精确命中(=或者in)，这种场景本身就不会出现幻读，所以只会加行记录锁。&lt;/p&gt;
&lt;p&gt;2.没有索引的列，当前读操作时，会加全表gap锁，生产环境要注意。&lt;/p&gt;
&lt;p&gt;3.非唯一索引列，如果where条件部分命中(&amp;gt;、&amp;lt;、like等)或者全未命中，则会加附近Gap间隙锁。例如，某表数据如下，非唯一索引2,6,9,9,11,15。如下语句要操作非唯一索引列9的数据，gap锁将会锁定的列是(6,11]，该区间内无法插入数据。&lt;/p&gt;








  











&lt;figure id=&#34;figure-非唯一索引列间隙锁&#34;&gt;


  &lt;a data-fancybox=&#34;&#34; href=&#34;https://w3xse7en.github.io/media/sql/mysql/mysql_gap_lock.png&#34; data-caption=&#34;&amp;lt;strong&amp;gt;非唯一索引列&amp;lt;/strong&amp;gt;间隙锁&#34;&gt;


  &lt;img src=&#34;https://w3xse7en.github.io/media/sql/mysql/mysql_gap_lock.png&#34; alt=&#34;&#34;  &gt;
&lt;/a&gt;


  
  
  &lt;figcaption&gt;
    &lt;strong&gt;非唯一索引列&lt;/strong&gt;间隙锁
  &lt;/figcaption&gt;


&lt;/figure&gt;

&lt;h3 id=&#34;快照读&#34;&gt;快照读&lt;/h3&gt;
&lt;p&gt;简单的select操作(不包括 select &amp;hellip; lock in share mode, select &amp;hellip; for update)。　　　　&lt;/p&gt;
&lt;p&gt;Read Committed隔离级别：每次select都生成一个快照读。&lt;/p&gt;
&lt;p&gt;Read Repeatable隔离级别：&lt;strong&gt;开启事务后第一个select语句才是快照读的地方，而不是一开启事务就快照读。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;快照读不会加锁，因此开启事务后进行快照读select，并不能阻止其他事务写入或修改当前事务里的数据，因此可能会出现幻读。&lt;/p&gt;
&lt;h4 id=&#34;快照读的实现方式&#34;&gt;快照读的实现方式&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;MVCC(undo log)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;锁的粒度&#34;&gt;锁的粒度&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;行级锁：行级锁是Mysql中锁定粒度最细的一种锁，表示只针对当前操作的行进行加锁。行级锁能大大减少数据库操作的冲突。其加锁粒度最小，但加锁的开销也最大。行级锁分为共享锁 和 排他锁。特点：开销大，加锁慢；会出现死锁；锁定粒度最小，发生锁冲突的概率最低，并发度也最高。&lt;/li&gt;
&lt;li&gt;表级锁：表级锁是MySQL中锁定粒度最大的一种锁，表示对当前操作的整张表加锁，它实现简单，资源消耗较少，被大部分MySQL引擎支持。最常使用的MYISAM与INNODB都支持表级锁定。表级锁定分为表共享读锁（共享锁）与表独占写锁（排他锁）。特点：开销小，加锁快；不会出现死锁；锁定粒度大，发出锁冲突的概率最高，并发度最低。&lt;/li&gt;
&lt;li&gt;页级锁：页级锁是MySQL中锁定粒度介于行级锁和表级锁中间的一种锁。表级锁速度快，但冲突多，行级冲突少，但速度慢。所以取了折衷的页级，一次锁定相邻的一组记录。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;锁的类别&#34;&gt;锁的类别&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;共享锁: 又叫做读锁。 当用户要进行数据的读取时，对数据加上共享锁。共享锁可以同时加上多个。&lt;/li&gt;
&lt;li&gt;排他锁: 又叫做写锁。 当用户要进行数据的写入时，对数据加上排他锁。排他锁只可以加一个，他和其他的排他锁，共享锁都相斥。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;锁的算法&#34;&gt;锁的算法&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Record lock：单个行记录上的锁&lt;/li&gt;
&lt;li&gt;Gap lock：间隙锁，锁定一个范围，不包括记录本身&lt;/li&gt;
&lt;li&gt;Next-key lock：record+gap 锁定一个范围，包含记录本身&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;什么是死锁怎么解决&#34;&gt;什么是死锁？怎么解决&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;死锁是指两个或多个事务在同一资源上相互占用，并请求锁定对方的资源，从而导致恶性循环的现象。&lt;/li&gt;
&lt;li&gt;常见的解决死锁的方法
&lt;ul&gt;
&lt;li&gt;如果不同程序会并发存取多个表，尽量约定以相同的顺序访问表，可以大大降低死锁机会。&lt;/li&gt;
&lt;li&gt;在同一个事务中，尽可能做到一次锁定所需要的所有资源，减少死锁产生概率；&lt;/li&gt;
&lt;li&gt;对于非常容易产生死锁的业务部分，可以尝试使用升级锁定颗粒度，通过表级锁定来减少死锁产生的概率；&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;如果业务处理不好可以用分布式事务锁或者使用乐观锁&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;乐观锁和悲观锁&#34;&gt;乐观锁和悲观锁&lt;/h2&gt;
&lt;p&gt;数据库管理系统（DBMS）中的并发控制的任务是确保在多个事务同时存取数据库中同一数据时不破坏事务的隔离性和统一性以及数据库的统一性。乐观并发控制（乐观锁）和悲观并发控制（悲观锁）是并发控制主要采用的技术手段。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;悲观锁：假定会发生并发冲突，屏蔽一切可能违反数据完整性的操作。在查询完数据的时候就把事务锁起来，直到提交事务。实现方式：使用数据库中的锁机制&lt;/li&gt;
&lt;li&gt;乐观锁：假设不会发生并发冲突，只在提交操作时检查是否违反数据完整性。在修改数据的时候把事务锁起来，通过version的方式来进行锁定。实现方式：乐一般会使用版本号机制或CAS算法实现。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;大表数据查询怎么优化&#34;&gt;大表数据查询，怎么优化？&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;优化shema、sql语句+索引；&lt;/li&gt;
&lt;li&gt;第二加缓存，memcached, redis；&lt;/li&gt;
&lt;li&gt;主从复制，读写分离；&lt;/li&gt;
&lt;li&gt;垂直拆分，根据你模块的耦合度，将一个大的系统分为多个小的系统，也就是分布式系统&lt;/li&gt;
&lt;li&gt;水平切分，针对数据量大的表，这一步最麻烦，最能考验技术水平，要选择一个合理的sharding key,
为了有好的查询效率，表结构也要改动，做一定的冗余，应用也要改，sql中尽量带sharding key，将数据定位到限定的表上去查，而不是扫描全部的表&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;参考&#34;&gt;参考&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://chenjiabing666.github.io/2020/04/20/Mysql%E6%9C%80%E5%85%A8%E9%9D%A2%E8%AF%95%E6%8C%87%E5%8D%97/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Mysql最全面试指南&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://cloud.tencent.com/developer/article/1454636&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;InnoDB MVCC 机制，看这篇就够了&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://techlog.cn/article/list/10183403&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;mysql 异常情况下的事务安全 &amp;ndash; 详解 mysql redolog&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://techlog.cn/article/list/10183404&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;一文讲透 MySQL 的 MVCC 机制&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.cnblogs.com/wwcom123/p/10727194.html?spm=a2c6h.12873639.0.0.1bf85681isB566&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;【MySQL】当前读、快照读、MVCC&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.modb.pro/db/13606&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;MySQL InnoDB Cluster 详解&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://jeremyxu2010.github.io/2019/05/mysql-innodb-cluster%E5%AE%9E%E6%88%98/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;MySQL InnoDB Cluster实战&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://zhuanlan.zhihu.com/p/128461028&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;MySQL-8.0 Group Replication 研究与改造汇总&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.zhihu.com/question/53617036&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;如何看待MySQL发布的Group Replication？&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://dbaplus.cn/news-141-2231-1.html&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;美团点评基于MGR的CMDB高可用架构搭建之路&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
</description>
    </item>
    
    <item>
      <title>Process</title>
      <link>https://w3xse7en.github.io/docs/os/process/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>https://w3xse7en.github.io/docs/os/process/</guid>
      <description>&lt;hr&gt;
&lt;h2&gt;目录&lt;/h2&gt;
&lt;nav id=&#34;TableOfContents&#34;&gt;
  &lt;ul&gt;
    &lt;li&gt;&lt;a href=&#34;#进程&#34;&gt;进程&lt;/a&gt;
      &lt;ul&gt;
        &lt;li&gt;&lt;a href=&#34;#什么是孤儿进程&#34;&gt;什么是孤儿进程&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#什么是僵尸进程&#34;&gt;什么是僵尸进程&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#进程上下文&#34;&gt;进程上下文&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#内核与用户&#34;&gt;内核与用户&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#进程状态转换&#34;&gt;进程状态转换&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#进程间通信的同步异步-阻塞非阻塞&#34;&gt;进程间通信的同步/异步， 阻塞/非阻塞&lt;/a&gt;&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#线程&#34;&gt;线程&lt;/a&gt;
      &lt;ul&gt;
        &lt;li&gt;&lt;a href=&#34;#线程上下文&#34;&gt;线程上下文&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#线程上下文切换耗时比进程大吗&#34;&gt;线程上下文切换耗时比进程大吗？&lt;/a&gt;&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#协程&#34;&gt;协程&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#参考&#34;&gt;参考&lt;/a&gt;&lt;/li&gt;
  &lt;/ul&gt;
&lt;/nav&gt;
&lt;hr&gt;
&lt;br&gt;
&lt;h2 id=&#34;进程&#34;&gt;进程&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;进程是资源封装的单位。&lt;/strong&gt; 进程封装的资源包括：内存、文件、文件系统、信号、控制台等等。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;在任意时刻， 一个 CPU 核心上（processor）只可能运行一个进程 。&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;什么是孤儿进程&#34;&gt;什么是孤儿进程&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;当父进程退出时，它的子进程们（一个或者多个）就成了孤儿进程&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;当孤儿进程结束后，init进程会释放孤儿进程的资源，因此孤儿进程不会有危害&lt;/p&gt;
&lt;p&gt;一个父进程退出，而它的一个或多个子进程还在运行，那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养，并由init进程对它们完成状态收集工作。
由于孤儿进程会被init进程给收养，所以孤儿进程不会对系统造成危害。&lt;/p&gt;
&lt;h3 id=&#34;什么是僵尸进程&#34;&gt;什么是僵尸进程&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;子进程先退出，而父进程又没有去处理回收释放子进程的资源，这个时候子进程就成了僵尸进程&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;通常系统的进程数量都是有限制的，如果有大量的僵尸进程占用进程号，导致新的进程无法创建&lt;/p&gt;
&lt;p&gt;在fork()/execve()过程中，假设子进程结束时父进程仍存在，而父进程fork()之前既没安装SIGCHLD信号处理函数调用waitpid()等待子进程结束，又没有显式忽略该信号，则子进程成为僵死进程，无法正常结束，此时即使是root身份kill -9也不能杀死僵死进程。&lt;/p&gt;
&lt;p&gt;当我们寻求如何消灭系统中大量的僵尸进程时，答案就是把产生大量僵尸进程的那个元凶枪毙掉（通过kill发送SIGTERM或者SIGKILL信号）。枪毙了元凶进程之后，它产生的僵尸进程就变成了孤儿进程，这些孤儿进程会被init进程接管，init进程会wait()这些孤儿进程，释放它们占用的系统进程表中的资源。&lt;/p&gt;
&lt;h3 id=&#34;进程上下文&#34;&gt;进程上下文&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;当一个进程在执行时,CPU的所有寄存器中的值、进程的状态以及堆栈中的内容被称为该进程的上下文。&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;当内核需要切换到另一个进程时，它需要保存当前进程的所有状态，即保存当前进程的上下文，以便在再次执行该进程时，能够必得到切换时的状态执行下去。&lt;/p&gt;
&lt;p&gt;在LINUX中，当前进程上下文均保存在进程的任务数据结构中。&lt;/p&gt;
&lt;p&gt;在发生中断时,内核就在被中断进程的上下文中，在内核态下执行中断服务例程。但同时会保留所有需要用到的资源，以便中继服务结束时能恢复被中断进程的执行。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;发生进程上下文切换的场景&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;为了保证所有进程可以得到公平调度，CPU 时间被划分为一段段的时间片，这些时间片再被轮流分配给各个进程。这样，当某个进程的时间片耗尽了，就会被系统挂起，切换到其它正在等待 CPU 的进程运行。&lt;/li&gt;
&lt;li&gt;进程在系统资源不足（比如内存不足）时，要等到资源满足后才可以运行，这个时候进程也会被挂起，并由系统调度其他进程运行。&lt;/li&gt;
&lt;li&gt;当进程通过睡眠函数 sleep 这样的方法将自己主动挂起时，自然也会重新调度。&lt;/li&gt;
&lt;li&gt;当有优先级更高的进程运行时，为了保证高优先级进程的运行，当前进程会被挂起，由高优先级进程来运行&lt;/li&gt;
&lt;li&gt;发生硬件中断时，CPU 上的进程会被中断挂起，转而执行内核中的中断服务程序。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;内核与用户&#34;&gt;内核与用户&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;当进程在执行用户自己的代码时，则称其处于用户运行态（用户态）。&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;此时处理器在特权级最低的（3级）用户代码中运行。当正在执行用户程序而突然被中断程序中断时，此时用户程序也可以象征性地称为处于进程的内核态。因为中断处理程序将使用当前进程的内核栈。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;当一个任务（进程）执行系统调用而陷入内核代码中执行时，称进程处于内核运行态（内核态）。&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;此时处理器处于特权级最高的（0级）内核代码中执行。当进程处于内核态时，执行的内核代码会使用当前进程的内核栈。每个进程都有自己的内核栈。&lt;/p&gt;
&lt;h3 id=&#34;进程状态转换&#34;&gt;进程状态转换&lt;/h3&gt;








  











&lt;figure id=&#34;figure-进程状态轮转图&#34;&gt;


  &lt;a data-fancybox=&#34;&#34; href=&#34;https://w3xse7en.github.io/media/os/process-status-transfer-en.jpg&#34; data-caption=&#34;进程状态轮转图&#34;&gt;


  &lt;img src=&#34;https://w3xse7en.github.io/media/os/process-status-transfer-en.jpg&#34; alt=&#34;&#34;  &gt;
&lt;/a&gt;


  
  
  &lt;figcaption&gt;
    进程状态轮转图
  &lt;/figcaption&gt;


&lt;/figure&gt;

&lt;p&gt;上图展示了一个进程的不同状态&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;New. 进程正在被创建.&lt;/li&gt;
&lt;li&gt;Ready. 进程在等待被操作系统调度&lt;/li&gt;
&lt;li&gt;Running. 进程的指令正在被执行&lt;/li&gt;
&lt;li&gt;Waiting. 进程正在等待一些事件的发生（例如 I/O 的完成或者收到某个信号）&lt;/li&gt;
&lt;li&gt;Terminated. 进程执行完毕（可能是被强行终止的）&lt;/li&gt;
&lt;/ul&gt;








  











&lt;figure &gt;


  &lt;a data-fancybox=&#34;&#34; href=&#34;https://w3xse7en.github.io/media/os/process-status-transfer-cn.jpg&#34; &gt;


  &lt;img src=&#34;https://w3xse7en.github.io/media/os/process-status-transfer-cn.jpg&#34; alt=&#34;&#34;  &gt;
&lt;/a&gt;



&lt;/figure&gt;

&lt;ul&gt;
&lt;li&gt;在程序中，创建一个MySQL Client实例，对应创建进程&lt;/li&gt;
&lt;li&gt;MySQL Client启动时会连接MySQL Server，等待MySQL语句执行，对应进程就绪&lt;/li&gt;
&lt;li&gt;使用MySQL Client执行Select语句，对应进程运行&lt;/li&gt;
&lt;li&gt;等待MySQL Server返回Select结果，对应进程阻塞&lt;/li&gt;
&lt;li&gt;Select结果返回后，MySQL Client重新等待语句执行，对应进程就绪&lt;/li&gt;
&lt;li&gt;MySQL Client 执行exit操作，对应进程运行，中止MySQL Client 对应进程中止&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;进程间通信的同步异步-阻塞非阻塞&#34;&gt;进程间通信的同步/异步， 阻塞/非阻塞&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;进程间的通信是通过 send() 和 receive() 两种基本操作完成的。具体如何实现这两种基础操作，存在着不同的设计。 消息的传递有可能是阻塞的或非阻塞的 – 也被称为同步或异步的&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;阻塞式发送（blocking send）发送方进程会被一直阻塞， 直到消息被接受方进程收到。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;非阻塞式发送（nonblocking send）。发送方进程调用 send() 后， 立即就可以其他操作。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;阻塞式接收（blocking receive） 接收方调用 receive() 后一直阻塞， 直到消息到达可用。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;非阻塞式接受（nonblocking receive） 接收方调用 receive() 函数后， 要么得到一个有效的结果， 要么得到一个空值， 即不会被阻塞。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;线程&#34;&gt;线程&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;线程则是Linux的调度单位，共享同一个进程下的资源。&lt;/strong&gt; Linux内核调度器是以线程为单位进行调度和上下文切换的。&lt;/p&gt;
&lt;h3 id=&#34;线程上下文&#34;&gt;线程上下文&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;线程是调度的基本单位，而进程则是资源拥有的基本单位。&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;说白了，所谓内核中的任务调度，实际上的调度对象是线程；而进程只是给线程提供了虚拟内存、全局变量等资源。&lt;/p&gt;
&lt;p&gt;所以，对于线程和进程，我们可以这么理解：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;当进程只有一个线程时，可以认为进程就等于线程。&lt;/li&gt;
&lt;li&gt;当进程拥有多个线程时，这些线程会共享相同的虚拟内存和全局变量等资源。这些资源在上下文切换时是不需要修改的。另外，线程也有自己的私有数据，比如栈和寄存器等，这些在上下文切换时也是需要保存的。&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;发生线程上下文切换的场景&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;前后两个线程属于不同进程。此时，因为资源不共享，所以切换过程就跟进程上下文切换是一样。&lt;/li&gt;
&lt;li&gt;前后两个线程属于同一个进程。此时，因为虚拟内存是共享的，所以在切换时，虚拟内存这些资源就保持不动，只需要切换线程的私有数据、寄存器等不共享的数据&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;线程上下文切换耗时比进程大吗&#34;&gt;线程上下文切换耗时比进程大吗？&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;从上下文切换的耗时上来看，Linux线程（轻量级进程）其实和进程差别不太大。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;协程&#34;&gt;协程&lt;/h2&gt;
&lt;hr&gt;
&lt;h2 id=&#34;参考&#34;&gt;参考&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://www.jianshu.com/p/7390f73ad668&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;孤儿进程、僵尸进程和守护进程&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://xie.infoq.cn/article/3a980c8f6a5a0a7a26cc3d2e8&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;面试官问：僵尸进程和孤儿进程有了解过吗&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.cnblogs.com/Anker/p/3269106.html&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;用户空间与内核空间，进程上下文与中断上下文[总结]&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://zhuanlan.zhihu.com/p/79772089&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;进程/线程上下文切换会用掉你多少CPU&lt;/a&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Socket</title>
      <link>https://w3xse7en.github.io/docs/web/socket/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>https://w3xse7en.github.io/docs/web/socket/</guid>
      <description>&lt;hr&gt;
&lt;h2&gt;目录&lt;/h2&gt;
&lt;nav id=&#34;TableOfContents&#34;&gt;
  &lt;ul&gt;
    &lt;li&gt;&lt;a href=&#34;#io模型&#34;&gt;I/O模型&lt;/a&gt;
      &lt;ul&gt;
        &lt;li&gt;&lt;a href=&#34;#blocking-io---阻塞io&#34;&gt;Blocking I/O - 阻塞I/O&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#nonblocking-io---非阻塞io&#34;&gt;Nonblocking I/O - 非阻塞I/O&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#io-multiplexing---io多路复用&#34;&gt;I/O Multiplexing - I/O多路复用&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#signal-driven-io----信号驱动io&#34;&gt;Signal-Driven I/O  - 信号驱动I/O&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#asynchronous-io---异步io&#34;&gt;Asynchronous I/O - 异步I/O&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#五种-io-模型比较&#34;&gt;五种 I/O 模型比较&lt;/a&gt;&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#select&#34;&gt;select&lt;/a&gt;
      &lt;ul&gt;
        &lt;li&gt;&lt;a href=&#34;#select遇到的问题&#34;&gt;select遇到的问题&lt;/a&gt;&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#epoll&#34;&gt;epoll&lt;/a&gt;
      &lt;ul&gt;
        &lt;li&gt;&lt;a href=&#34;#epoll是如何解决select的三个问题&#34;&gt;epoll是如何解决select的三个问题&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#epoll的伪码描述&#34;&gt;epoll的伪码描述&lt;/a&gt;&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#参考&#34;&gt;参考&lt;/a&gt;&lt;/li&gt;
  &lt;/ul&gt;
&lt;/nav&gt;
&lt;hr&gt;
&lt;br&gt;
&lt;h2 id=&#34;io模型&#34;&gt;I/O模型&lt;/h2&gt;
&lt;p&gt;[UNIX: registered: Network Programming] 提供了5种IO模型&lt;/p&gt;
&lt;h3 id=&#34;blocking-io---阻塞io&#34;&gt;Blocking I/O - 阻塞I/O&lt;/h3&gt;








  











&lt;figure &gt;


  &lt;a data-fancybox=&#34;&#34; href=&#34;https://w3xse7en.github.io/media/web/socket/block-io.png&#34; &gt;


  &lt;img src=&#34;https://w3xse7en.github.io/media/web/socket/block-io.png&#34; alt=&#34;&#34;  &gt;
&lt;/a&gt;



&lt;/figure&gt;

&lt;h3 id=&#34;nonblocking-io---非阻塞io&#34;&gt;Nonblocking I/O - 非阻塞I/O&lt;/h3&gt;








  











&lt;figure &gt;


  &lt;a data-fancybox=&#34;&#34; href=&#34;https://w3xse7en.github.io/media/web/socket/nonblock-io.png&#34; &gt;


  &lt;img src=&#34;https://w3xse7en.github.io/media/web/socket/nonblock-io.png&#34; alt=&#34;&#34;  &gt;
&lt;/a&gt;



&lt;/figure&gt;

&lt;h3 id=&#34;io-multiplexing---io多路复用&#34;&gt;I/O Multiplexing - I/O多路复用&lt;/h3&gt;








  











&lt;figure &gt;


  &lt;a data-fancybox=&#34;&#34; href=&#34;https://w3xse7en.github.io/media/web/socket/io-multiplexing.png&#34; &gt;


  &lt;img src=&#34;https://w3xse7en.github.io/media/web/socket/io-multiplexing.png&#34; alt=&#34;&#34;  &gt;
&lt;/a&gt;



&lt;/figure&gt;

&lt;h3 id=&#34;signal-driven-io----信号驱动io&#34;&gt;Signal-Driven I/O  - 信号驱动I/O&lt;/h3&gt;








  











&lt;figure &gt;


  &lt;a data-fancybox=&#34;&#34; href=&#34;https://w3xse7en.github.io/media/web/socket/signal-driven-io.png&#34; &gt;


  &lt;img src=&#34;https://w3xse7en.github.io/media/web/socket/signal-driven-io.png&#34; alt=&#34;&#34;  &gt;
&lt;/a&gt;



&lt;/figure&gt;

&lt;h3 id=&#34;asynchronous-io---异步io&#34;&gt;Asynchronous I/O - 异步I/O&lt;/h3&gt;








  











&lt;figure &gt;


  &lt;a data-fancybox=&#34;&#34; href=&#34;https://w3xse7en.github.io/media/web/socket/asynchronous-io.png&#34; &gt;


  &lt;img src=&#34;https://w3xse7en.github.io/media/web/socket/asynchronous-io.png&#34; alt=&#34;&#34;  &gt;
&lt;/a&gt;



&lt;/figure&gt;

&lt;h3 id=&#34;五种-io-模型比较&#34;&gt;五种 I/O 模型比较&lt;/h3&gt;








  











&lt;figure &gt;


  &lt;a data-fancybox=&#34;&#34; href=&#34;https://w3xse7en.github.io/media/web/socket/compare-five-io.png&#34; &gt;


  &lt;img src=&#34;https://w3xse7en.github.io/media/web/socket/compare-five-io.png&#34; alt=&#34;&#34;  &gt;
&lt;/a&gt;



&lt;/figure&gt;

&lt;hr&gt;
&lt;h2 id=&#34;select&#34;&gt;select&lt;/h2&gt;
&lt;p&gt;io多路复用是为了解决一个进程同时处理多个socket问题&lt;/p&gt;
&lt;p&gt;一个简单的思路是&lt;/p&gt;
&lt;p&gt;假设有N个socket链接，检测有socket接收到数据，遍历所有socket进行处理&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// fds = file decriptors
for {
    select (fds) // wait while fds poll callback POLL_IN
    for fd range fds{
        if fd has data{
            read fd
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;被监控的fds需要从用户空间拷贝到内核空间
为了减少数据拷贝带来的性能损坏，内核对被监控的fds集合大小做了限制，并且这个是通过宏控制的，大小不可改变(限制为1024)。&lt;/li&gt;
&lt;li&gt;被监控的fds集合中，只要有一个有数据可读，整个socket集合就会被遍历一次调用sk的poll函数收集可读事件
由于当初的需求是朴素，仅仅关心是否有数据可读这样一个事件，当事件通知来的时候，由于数据的到来是异步的，我们不知道事件来的时候，有多少个被监控的socket有数据可读了，于是，只能挨个遍历每个socket来收集可读事件。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;select遇到的问题&#34;&gt;select遇到的问题&lt;/h3&gt;
&lt;p&gt;总共有三个问题需要解决&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;被监控的fds集合大小被限制了1024，不够用&lt;/li&gt;
&lt;li&gt;fds集合需要从用户空间拷贝到内核空间的问题，耗费性能&lt;/li&gt;
&lt;li&gt;需要遍历fds集合才能知道有数据接收的fds列表&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;epoll&#34;&gt;epoll&lt;/h2&gt;
&lt;p&gt;epoll 是对 select 和 poll 的改进，避免了“性能开销大”和“文件描述符数量少”两个缺点。&lt;/p&gt;
&lt;p&gt;epoll 有以下几个特点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;使用&lt;strong&gt;红黑树&lt;/strong&gt;存储文件描述符集合&lt;/li&gt;
&lt;li&gt;使用&lt;strong&gt;队列&lt;/strong&gt;存储就绪的文件描述符&lt;/li&gt;
&lt;li&gt;每个文件描述符只需在添加时传入一次；通过事件更改文件描述符状态&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;epoll一共有3个接口&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;epoll_create创建epoll实例，其实例内部存储：&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;监听列表：所有要监听的文件描述符，使用&lt;strong&gt;红黑树&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;就绪列表：所有就绪的文件描述符，使用&lt;strong&gt;队列&lt;/strong&gt;&lt;/p&gt;
&lt;ol start=&#34;2&#34;&gt;
&lt;li&gt;epoll_ctl用来维护监视列表，可以添加或删除所要监听的 socket&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;epoll_ctl 会将文件描述符 fd 添加到 epoll 实例的监听列表里，同时为 fd 设置一个回调函数，并监听事件event。当fd上发生相应事件时，会调用回调函数，将 fd 添加到 epoll 实例的就绪队列上。&lt;/p&gt;
&lt;ol start=&#34;3&#34;&gt;
&lt;li&gt;当调用epoll_wait时，如果就绪列表中存在socket，则直接返回，如果没有，则阻塞进程&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;epoll是如何解决select的三个问题&#34;&gt;epoll是如何解决select的三个问题&lt;/h3&gt;
&lt;details&gt;
  &lt;summary&gt;被监控的fds集合大小被限制了1024，不够用&lt;/summary&gt;
&lt;p&gt;select 使用整型数组存储文件描述符集合，而 epoll 使用红黑树存储，数量较大。&lt;/p&gt;
&lt;/details&gt;
&lt;details&gt;
  &lt;summary&gt;fds集合需要从用户空间拷贝到内核空间的问题，耗费性能&lt;/summary&gt;
&lt;p&gt;epoll通过内核与用户空间使用mmap(内存映射)，将用户空间的一块地址和内核空间的一块地址同时映射到相同的一块物理内存地址，减少用户态和内核态之间的数据交换。&lt;/p&gt;
&lt;p&gt;epoll 对于每个描述符，只需要在 epoll_ctl 传递一次，之后 epoll_wait 不需要再次传递这也大大提高了效率。&lt;/p&gt;
&lt;/details&gt;
&lt;details&gt;
  &lt;summary&gt;需要遍历fds集合才能知道有数据接收的fds列表&lt;/summary&gt;
&lt;p&gt;epoll_ctl 中为每个文件描述符指定了回调函数，并在就绪时将其加入到就绪列表，因此 epoll 不需要像 select 那样遍历检测每个文件描述符，只需要判断就绪列表是否为空即可。这样，在没有描述符就绪时，epoll 能更早地让出系统资源。&lt;/p&gt;
&lt;p&gt;相当于时间复杂度从 O(n) 降为 O(1)&lt;/p&gt;
&lt;/details&gt;
&lt;h3 id=&#34;epoll的伪码描述&#34;&gt;epoll的伪码描述&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;for{
    active_fd = epoll_wait(fds)
    read fd
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2 id=&#34;参考&#34;&gt;参考&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://notes.shichao.io/unp/ch6/#signal-driven-io-model&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Chapter 6. I/O Multiplexing: The select and poll Functions&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/CyC2018/CS-Notes/blob/master/notes/Socket.md&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Socket&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://imageslr.github.io/2020/02/27/select-poll-epoll.html&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;【操作系统】I/O 多路复用，select / poll / epoll 详解&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.zhihu.com/question/19732473/answer/241673170&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;怎样理解阻塞非阻塞与同步异步的区别&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://cloud.tencent.com/developer/article/1005481&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;大话 Select、Poll、Epoll&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;http://gityuan.com/2019/01/06/linux-epoll/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;源码解读epoll内核机制&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;
&lt;hr&gt;
</description>
    </item>
    
    <item>
      <title>Q&amp;A</title>
      <link>https://w3xse7en.github.io/docs/lang/qa/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>https://w3xse7en.github.io/docs/lang/qa/</guid>
      <description>&lt;h1 id=&#34;qa&#34;&gt;Q&amp;amp;A&lt;/h1&gt;
</description>
    </item>
    
    <item>
      <title>Redis</title>
      <link>https://w3xse7en.github.io/docs/sql/redis/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>https://w3xse7en.github.io/docs/sql/redis/</guid>
      <description>&lt;hr&gt;
&lt;h2&gt;目录&lt;/h2&gt;
&lt;nav id=&#34;TableOfContents&#34;&gt;
  &lt;ul&gt;
    &lt;li&gt;&lt;a href=&#34;#数据结构&#34;&gt;数据结构&lt;/a&gt;
      &lt;ul&gt;
        &lt;li&gt;&lt;a href=&#34;#redis内部存储结构&#34;&gt;Redis内部存储结构&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#string&#34;&gt;String&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#hash&#34;&gt;Hash&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#rehash&#34;&gt;ReHash&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#skiplist&#34;&gt;SkipList&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#高级数据结构&#34;&gt;高级数据结构&lt;/a&gt;&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#缓存&#34;&gt;缓存&lt;/a&gt;
      &lt;ul&gt;
        &lt;li&gt;&lt;a href=&#34;#缓存穿透击穿&#34;&gt;缓存穿透/击穿&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#缓存雪崩&#34;&gt;缓存雪崩&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#热点缓存&#34;&gt;热点缓存&lt;/a&gt;&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#布隆过滤器bloom-filter&#34;&gt;布隆过滤器(Bloom Filter)&lt;/a&gt;
      &lt;ul&gt;
        &lt;li&gt;&lt;a href=&#34;#概念&#34;&gt;概念&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#原理&#34;&gt;原理&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#缺点&#34;&gt;缺点&lt;/a&gt;&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#分布式锁&#34;&gt;分布式锁&lt;/a&gt;
      &lt;ul&gt;
        &lt;li&gt;&lt;a href=&#34;#为什么要用分布式锁&#34;&gt;为什么要用分布式锁&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#简单的分布式锁实现&#34;&gt;简单的分布式锁实现&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#redlock&#34;&gt;RedLock&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#个人想法&#34;&gt;个人想法&lt;/a&gt;&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#持久化&#34;&gt;持久化&lt;/a&gt;
      &lt;ul&gt;
        &lt;li&gt;&lt;a href=&#34;#rdb优缺点&#34;&gt;RDB优缺点&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#aof优缺点&#34;&gt;AOF优缺点&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#同步机制&#34;&gt;同步机制&lt;/a&gt;&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#高可用集群&#34;&gt;高可用/集群&lt;/a&gt;
      &lt;ul&gt;
        &lt;li&gt;&lt;a href=&#34;#gossip协议&#34;&gt;Gossip协议&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#redis的分片机制&#34;&gt;Redis的分片机制&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#为什么rediscluster会设计成16384个槽呢&#34;&gt;为什么RedisCluster会设计成16384个槽呢？&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#redis数据增多了是该加内存还是加实例&#34;&gt;Redis数据增多了，是该加内存还是加实例？&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#集群脑裂&#34;&gt;集群脑裂&lt;/a&gt;&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#参考&#34;&gt;参考&lt;/a&gt;&lt;/li&gt;
  &lt;/ul&gt;
&lt;/nav&gt;
&lt;hr&gt;
&lt;br&gt;
&lt;h2 id=&#34;数据结构&#34;&gt;数据结构&lt;/h2&gt;
&lt;p&gt;String Hash List Set SortedSet。&lt;/p&gt;
&lt;h3 id=&#34;redis内部存储结构&#34;&gt;Redis内部存储结构&lt;/h3&gt;
&lt;p&gt;dictEntry&lt;/p&gt;
&lt;p&gt;因为 Redis 是 KV 的数据库，它是通过 hashtable 实现的（我们把这个叫做外层的哈希）。&lt;/p&gt;
&lt;p&gt;所以每个键值对都会有一个 dictEntry，里面指向了 key 和 value 的指针。next 指向下一个 dictEntry。源码如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;typedef struct dictEntry {
    void *key;              //关键字
    union {
        void *val;
        uint64_t u64;
        int64_t s64;
        double d;
    } v;                    //val
    struct dictEntry *next; //next
} dictEntry;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;key 是字符串，但是 Redis 没有直接使用 C 的字符数组，而是存储在自定义的 SDS中。&lt;/p&gt;
&lt;p&gt;value 既不是直接作为字符串存储，也不是直接存储在 SDS 中，而是存储在redisObject 中。&lt;/p&gt;
&lt;p&gt;实际上五种常用的数据类型的任何一种，都是通过 redisObject 来存储的。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;typedef struct redisObject {
    unsigned type:4; /* 对象的类型， 包括： OBJ_STRING、 OBJ_LIST、 OBJ_HASH、 OBJ_SET、 OBJ_ZSET */
    unsigned encoding:4; /* 具体的数据结构 */
    unsigned lru:LRU_BITS; /* 24 位， 对象最后一次被命令程序访问的时间， 与内存回收有关 */
    int refcount; /* 引用计数。 当 refcount 为 0 的时候， 表示该对象已经不被任何对象引用， 则可以进行垃圾回收了*/
    void *ptr; /* 指向对象实际的数据结构 */
} robj;
&lt;/code&gt;&lt;/pre&gt;








  











&lt;figure &gt;


  &lt;a data-fancybox=&#34;&#34; href=&#34;https://w3xse7en.github.io/media/sql/redis/redis_object.jpg&#34; &gt;


  &lt;img src=&#34;https://w3xse7en.github.io/media/sql/redis/redis_object.jpg&#34; alt=&#34;&#34;  &gt;
&lt;/a&gt;



&lt;/figure&gt;

&lt;h3 id=&#34;string&#34;&gt;String&lt;/h3&gt;
&lt;p&gt;sds是在Redis中被广泛使用的字符串结构，它的全称是Simple Dynamic String。与其它语言环境中出现的字符串相比，它具有如下显著的特点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;可动态扩展内存。sds表示的字符串其内容可以修改，也可以追加。在很多语言中字符串会分为mutable和immutable两种，显然sds属于mutable类型的。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;二进制安全（Binary Safe）。sds能存储任意二进制数据，而不仅仅是可打印字符。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;与传统的C语言字符串类型兼容。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;hash&#34;&gt;Hash&lt;/h3&gt;








  











&lt;figure id=&#34;figure-hash链接法&#34;&gt;


  &lt;a data-fancybox=&#34;&#34; href=&#34;https://w3xse7en.github.io/media/sql/redis/hash_dict_jg.png&#34; data-caption=&#34;Hash链接法&#34;&gt;


  &lt;img src=&#34;https://w3xse7en.github.io/media/sql/redis/hash_dict_jg.png&#34; alt=&#34;&#34;  &gt;
&lt;/a&gt;


  
  
  &lt;figcaption&gt;
    Hash链接法
  &lt;/figcaption&gt;


&lt;/figure&gt;

&lt;h3 id=&#34;rehash&#34;&gt;ReHash&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;为ht[1] 分配空间，这个哈希表的空间大小取决于要执行的操作， 以及ht[0]当前包含的键值对数量 （也即是ht[0].used属性的值）：
&lt;ul&gt;
&lt;li&gt;如果执行的是扩展操作， 那么 ht[1] 的大小为第一个大于等于 ht[0].used * 2 的 2^n （2 的 n 次方幂）；&lt;/li&gt;
&lt;li&gt;如果执行的是收缩操作， 那么 ht[1] 的大小为第一个大于等于 ht[0].used 的 2^n 。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;将rehashidx 初始化为0 ，代表rehash 工作正式开始。&lt;/li&gt;
&lt;li&gt;每次字典进行删除、查找、更新操作时， 会同时在两个hash表上进行（先查找ht[0], 如果没找到，再去查找ht[1]）。 进行添加操作时，会直接添加到ht[1]。&lt;/li&gt;
&lt;li&gt;在进行每次增删改查操作时， 会同时把ht[0] 在rehashidx 索引上的所有键值对都rehash到ht[1]上， 完成后 rehashidx 加1.&lt;/li&gt;
&lt;li&gt;当ht[0] 所有元素都被复制到ht[1]， 设置rehashidx 的值为-1 。&lt;/li&gt;
&lt;li&gt;回收 ht[0]，将 ht[1] 设置为 ht[0] ， 并在 ht[1] 新创建一个空白哈希表， 为下一次 rehash 做准备。。&lt;/li&gt;
&lt;/ol&gt;








  











&lt;figure id=&#34;figure-rehash&#34;&gt;


  &lt;a data-fancybox=&#34;&#34; href=&#34;https://w3xse7en.github.io/media/sql/redis/hash_hashtable_rehash.png&#34; data-caption=&#34;rehash&#34;&gt;


  &lt;img src=&#34;https://w3xse7en.github.io/media/sql/redis/hash_hashtable_rehash.png&#34; alt=&#34;&#34;  &gt;
&lt;/a&gt;


  
  
  &lt;figcaption&gt;
    rehash
  &lt;/figcaption&gt;


&lt;/figure&gt;

&lt;h3 id=&#34;skiplist&#34;&gt;SkipList&lt;/h3&gt;








  











&lt;figure id=&#34;figure-跳表&#34;&gt;


  &lt;a data-fancybox=&#34;&#34; href=&#34;https://w3xse7en.github.io/media/sql/redis/ziplist_yl.png&#34; data-caption=&#34;跳表&#34;&gt;


  &lt;img src=&#34;https://w3xse7en.github.io/media/sql/redis/ziplist_yl.png&#34; alt=&#34;&#34;  &gt;
&lt;/a&gt;


  
  
  &lt;figcaption&gt;
    跳表
  &lt;/figcaption&gt;


&lt;/figure&gt;









  











&lt;figure id=&#34;figure-跳表查找过程&#34;&gt;


  &lt;a data-fancybox=&#34;&#34; href=&#34;https://w3xse7en.github.io/media/sql/redis/skip_list_search.png&#34; data-caption=&#34;跳表查找过程&#34;&gt;


  &lt;img src=&#34;https://w3xse7en.github.io/media/sql/redis/skip_list_search.png&#34; alt=&#34;&#34;  &gt;
&lt;/a&gt;


  
  
  &lt;figcaption&gt;
    跳表查找过程
  &lt;/figcaption&gt;


&lt;/figure&gt;

&lt;p&gt;跳表不要求上下相邻两层链表之间的节点个数有严格的对应关系，而是为每个节点随机出一个层数(level)。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#define ZSKIPLIST_MAXLEVEL 32 
#define ZSKIPLIST_P 0.25 

int zslRandomLevel(void) {
    int level = 1;
    while ((random()&amp;amp;0xFFFF) &amp;lt; (ZSKIPLIST_P * 0xFFFF))
        level += 1;
    return (level&amp;lt;ZSKIPLIST_MAXLEVEL) ? level : ZSKIPLIST_MAXLEVEL;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;执行插入操作时计算随机数的过程，是一个很关键的过程，它对skiplist的统计特性有着很重要的影响。&lt;/p&gt;
&lt;p&gt;这并不是一个普通的服从均匀分布的随机数，而是服从一定规则的：&lt;/p&gt;
&lt;p&gt;首先，每个节点肯定都有第1层指针（每个节点都在第1层链表里）。
如果一个节点有第i层(i&amp;gt;=1)指针（即节点已经在第1层到第i层链表中），那么它有第(i+1)层指针的概率为p。
节点最大的层数不允许超过一个最大值，记为MaxLevel（Redis里是32）。
比如，一个节点随机出的层数是3，那么就把它链入到第1层到第3层这三层链表中。&lt;/p&gt;
&lt;p&gt;下图展示了如何通过一步步的插入操作从而形成一个skiplist的过程：&lt;/p&gt;








  











&lt;figure id=&#34;figure-跳表插入过程&#34;&gt;


  &lt;a data-fancybox=&#34;&#34; href=&#34;https://w3xse7en.github.io/media/sql/redis/skip_list_insert.png&#34; data-caption=&#34;跳表插入过程&#34;&gt;


  &lt;img src=&#34;https://w3xse7en.github.io/media/sql/redis/skip_list_insert.png&#34; alt=&#34;&#34;  &gt;
&lt;/a&gt;


  
  
  &lt;figcaption&gt;
    跳表插入过程
  &lt;/figcaption&gt;


&lt;/figure&gt;









  











&lt;figure id=&#34;figure-跳表&#34;&gt;


  &lt;a data-fancybox=&#34;&#34; href=&#34;https://w3xse7en.github.io/media/sql/redis/skiplist.png&#34; data-caption=&#34;跳表&#34;&gt;


  &lt;img src=&#34;https://w3xse7en.github.io/media/sql/redis/skiplist.png&#34; alt=&#34;&#34;  &gt;
&lt;/a&gt;


  
  
  &lt;figcaption&gt;
    跳表
  &lt;/figcaption&gt;


&lt;/figure&gt;

&lt;h4 id=&#34;为什么跳表层数上限是32&#34;&gt;为什么跳表层数上限是32？&lt;/h4&gt;
&lt;p&gt;根据前面的随机算法当level[0]有2的64次方个节点时，才能达到32层，因此层数上限是32完全够用了。&lt;/p&gt;
&lt;h4 id=&#34;为什么采用跳表而不使用哈希表或平衡树实现&#34;&gt;为什么采用跳表，而不使用哈希表或平衡树实现&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;skiplist和各种平衡树（如AVL、红黑树等）的元素是有序排列的，而哈希表不是有序的。因此，在哈希表上只能做单个key的查找，不适宜做范围查找。所谓范围查找，指的是查找那些大小在指定的两个值之间的所有节点。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;在做范围查找的时候，平衡树比skiplist操作要复杂。在平衡树上，我们找到指定范围的小值之后，还需要以中序遍历的顺序继续寻找其它不超过大值的节点。如果不对平衡树进行一定的改造，这里的中序遍历并不容易实现。而在skiplist上进行范围查找就非常简单，只需要在找到小值之后，对第1层链表进行若干步的遍历就可以实现。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;平衡树的插入和删除操作可能引发子树的调整，逻辑复杂，而skiplist的插入和删除只需要修改相邻节点的指针，操作简单又快速。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;从内存占用上来说，skiplist比平衡树更灵活一些。一般来说，平衡树每个节点包含2个指针（分别指向左右子树），而skiplist每个节点包含的指针数目平均为1/(1-p)，具体取决于参数p的大小。如果像Redis里的实现一样，取p=1/4，那么平均每个节点包含1.33个指针，比平衡树更有优势。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;高级数据结构&#34;&gt;高级数据结构&lt;/h3&gt;
&lt;p&gt;Bitmaps Hyperloglogs GEO&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;HyperLogLog是用于计算唯一事物的概率数据结构（从技术上讲，这被称为估计集合的基数）。
如果统计唯一项，项目越多，需要的内存就越多。因为需要记住过去已经看过的项，从而避免多次统计这些项。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;GEO可以将用户给定的地理位置（经度和纬度）信息储存起来，并对这些信息进行操作&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;br&gt;
&lt;hr&gt;
&lt;h2 id=&#34;缓存&#34;&gt;缓存&lt;/h2&gt;
&lt;h3 id=&#34;缓存穿透击穿&#34;&gt;缓存穿透/击穿&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;查询一个数据库中不存在的数据，请求会越过Redis，直接请求DB。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;做好防高频请求
非正常用户量的请求，10s内发起1000次请求
对于此请求的ip进行验证码校验，或者封禁处理&lt;/p&gt;
&lt;p&gt;接口参数合法性校验
请求id需要&amp;gt;=0,分页每页最多100条等&lt;/p&gt;
&lt;p&gt;将此key对应的value设置为一个默认的值，并设置相对短的失效时间例如30分钟&lt;/p&gt;
&lt;p&gt;使用&lt;a href=&#34;https://w3xse7en.github.io/docs/sql/redis/#布隆过滤器(Bloom Filter)&#34;&gt;布隆过滤器(Bloom Filter)&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&#34;缓存雪崩&#34;&gt;缓存雪崩&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;大量Key同时失效，又有大量请求同时到来，导致请求冲向DB，DB最终卡死。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;处理缓存雪崩，在批量往Redis存数据的时候，把每个Key的失效时间都加个随机值，这样可以保证Key不会在同一时间大面积失效&lt;/p&gt;
&lt;h3 id=&#34;热点缓存&#34;&gt;热点缓存&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;某个Key过热，压力集中到一台Redis上&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;使用多级缓存机制，将过热的Key分散到各个服务器的本地缓存中，降低过热Key所在的Redis节点的压力，其他的Key依旧由分布式Redis集群承担&lt;/p&gt;
&lt;br&gt;
&lt;hr&gt;
&lt;h2 id=&#34;布隆过滤器bloom-filter&#34;&gt;布隆过滤器(Bloom Filter)&lt;/h2&gt;
&lt;h3 id=&#34;概念&#34;&gt;概念&lt;/h3&gt;
&lt;p&gt;布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法，缺点是有一定的误识别率和删除困难。&lt;/p&gt;
&lt;h3 id=&#34;原理&#34;&gt;原理&lt;/h3&gt;
&lt;p&gt;布隆过滤器的原理是，当一个元素被加入集合时，通过K个散列函数将这个元素映射成一个位数组中的K个点，把它们置为1。&lt;/p&gt;
&lt;p&gt;检索时，我们只要看看这些点是不是都是1就（大约）知道集合中有没有它了&lt;/p&gt;
&lt;p&gt;如果这些点有任何一个0，则被检元素一定不在&lt;/p&gt;
&lt;p&gt;如果都是1，则被检元素很可能在。这就是布隆过滤器的基本思想。&lt;/p&gt;
&lt;p&gt;Bloom Filter跟单哈希函数Bit-Map不同之处在于：Bloom Filter使用了k个哈希函数，每个字符串跟k个bit对应。从而降低了冲突的概率。&lt;/p&gt;








  











&lt;figure id=&#34;figure-bloom-filter&#34;&gt;


  &lt;a data-fancybox=&#34;&#34; href=&#34;https://w3xse7en.github.io/media/sql/redis/Bloom-Filter.jpg&#34; data-caption=&#34;Bloom Filter&#34;&gt;


  &lt;img src=&#34;https://w3xse7en.github.io/media/sql/redis/Bloom-Filter.jpg&#34; alt=&#34;&#34;  &gt;
&lt;/a&gt;


  
  
  &lt;figcaption&gt;
    Bloom Filter
  &lt;/figcaption&gt;


&lt;/figure&gt;

&lt;h3 id=&#34;缺点&#34;&gt;缺点&lt;/h3&gt;
&lt;p&gt;bloom filter之所以能做到在时间和空间上的效率比较高，是因为牺牲了判断的准确率、删除的便利性&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;存在误判，可能要查到的元素并没有在容器中，但是hash之后得到的k个位置上值都是1。如果bloom filter中存储的是黑名单，那么可以通过建立一个白名单来存储可能会误判的元素。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;删除困难。一个放入容器的元素映射到bit数组的k个位置上是1，删除的时候不能简单的直接置为0，可能会影响其他元素的判断。可以采用Counting Bloom Filter&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;br&gt;
&lt;hr&gt;
&lt;h2 id=&#34;分布式锁&#34;&gt;分布式锁&lt;/h2&gt;
&lt;h3 id=&#34;为什么要用分布式锁&#34;&gt;为什么要用分布式锁&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Efficiency（效率） 在分布式系统中，避免不同节点重复做相同的工作，节约计算机资源。&lt;/li&gt;
&lt;li&gt;Correctness（正确) 避免不同节点并发处理同一段数据时，相互干扰结果。例如对一个订单同时进行不同流程，最终订单状态出现混乱&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;简单的分布式锁实现&#34;&gt;简单的分布式锁实现&lt;/h3&gt;
&lt;p&gt;单节点Redis&lt;/p&gt;
&lt;p&gt;简单实现，可以使用 &lt;code&gt;SET key value PX milliseoncds NX&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;这个方案会引申出两个问题&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;锁从master复制到slave的时候挂了，会出现同一资源被多个client加锁。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;执行时间超过了锁的过期时间。很难保证任务一定能在锁的过期时间内完成。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;redlock&#34;&gt;RedLock&lt;/h3&gt;
&lt;p&gt;Redlock算法是Antirez在单Redis节点基础上引入的高可用模式。&lt;/p&gt;
&lt;p&gt;在Redis的分布式环境中，我们假设有N个完全互相独立的Redis节点，在N个Redis实例上使用与在Redis单实例下相同方法获取锁和释放锁。&lt;/p&gt;
&lt;p&gt;现在假设有5个Redis主节点(大于3的奇数个)，这样基本保证他们不会同时都宕掉。&lt;/p&gt;
&lt;p&gt;获取锁和释放锁的过程中，客户端会执行以下操作:&lt;/p&gt;
&lt;p&gt;1.获取当前Unix时间，以毫秒为单位&lt;/p&gt;
&lt;p&gt;2.依次尝试从5个实例，使用相同的key和具有唯一性的value获取锁当向Redis请求获取锁时，客户端应该设置一个网络连接和响应超时时间，这个超时时间应该小于锁的失效时间，这样可以避免客户端死等&lt;/p&gt;
&lt;p&gt;3.客户端使用当前时间减去开始获取锁时间就得到获取锁使用的时间。当且仅当从半数以上的Redis节点取到锁，并且使用的时间小于锁失效时间时，锁才算获取成功&lt;/p&gt;
&lt;p&gt;4.如果取到了锁，key的真正有效时间等于有效时间减去获取锁所使用的时间，这个很重要&lt;/p&gt;
&lt;p&gt;5.如果因为某些原因，获取锁失败（没有在半数以上实例取到锁或者取锁时间已经超过了有效时间），客户端应该在所有的Redis实例上进行解锁，无论Redis实例是否加锁成功，因为可能服务端响应消息丢失了但是实际成功了，毕竟多释放一次也不会有问题&lt;/p&gt;
&lt;h3 id=&#34;个人想法&#34;&gt;个人想法&lt;/h3&gt;
&lt;p&gt;能不用分布式锁就不用分布式锁，避免引入新的复杂度，对于需要使用锁的场景，优先基于中间件原子性的机制操作。&lt;/p&gt;
&lt;p&gt;MySQL数据库，加上version字段，强制要求所有update语句带上&lt;code&gt;set version=version+1 where version={old_version}&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;可能重复insert的场景，对合理的业务id加上唯一索引，由数据库自有机制保证不会有重复数据插入&lt;/p&gt;
&lt;p&gt;秒杀，统计等场景，使用Redis的incr,decr语句来替代分布式锁操作库存&lt;/p&gt;
&lt;br&gt;
&lt;hr&gt;
&lt;h2 id=&#34;持久化&#34;&gt;持久化&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;RDB 持久化可以在指定的时间间隔内生成数据集的时间点快照（point-in-time snapshot）。（适合冷备）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;AOF 持久化记录服务器执行的所有写操作命令，并在服务器启动时，通过重新执行这些命令来还原数据集。
AOF 文件中的命令全部以 Redis 协议的格式来保存，新命令会被追加到文件的末尾。（适合热备）&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Redis 还可以在后台对 AOF 文件进行重写（rewrite），使得 AOF 文件的体积不会超出保存数据集状态所需的实际大小。&lt;/p&gt;
&lt;p&gt;Redis 还可以同时使用 AOF 持久化和 RDB 持久化。
在这种情况下， 当 Redis 重启时， 它会优先使用 AOF 文件来还原数据集， 因为 AOF 文件保存的数据集通常比 RDB 文件所保存的数据集更完整。&lt;/p&gt;
&lt;p&gt;但实际上持久化会对Redis的性能造成非常严重的影响，如果一定需要保存数据，那么数据就不应该依靠缓存来保存，建议使用其他方式如数据库。所以Redis的持久化意义不大。&lt;/p&gt;
&lt;h3 id=&#34;rdb优缺点&#34;&gt;RDB优缺点&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;优点：&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;他会生成多个数据文件，每个数据文件分别都代表了某一时刻Redis里面的数据，这种方式，适合做冷备。&lt;/p&gt;
&lt;p&gt;RDB对Redis的性能影响非常小，是因为在同步数据的时候他只是fork了一个子进程去做持久化的，而且他在数据恢复的时候速度比AOF来的快。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;缺点：&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;RDB都是快照文件，都是默认五分钟甚至更久的时间才会生成一次，这意味着你这次同步到下次同步这中间五分钟的数据都很可能全部丢失掉。&lt;/p&gt;
&lt;p&gt;RDB在生成数据快照的时候，如果文件很大，客户端可能会暂停几毫秒甚至几秒。&lt;/p&gt;
&lt;h3 id=&#34;aof优缺点&#34;&gt;AOF优缺点&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;优点：&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;RDB五分钟一次生成快照，但是AOF是一秒一次去通过一个后台的线程fsync操作，那最多丢这一秒的数据。&lt;/p&gt;
&lt;p&gt;AOF在对日志文件进行操作的时候是以append-only的方式去写的，他只是追加的方式写数据，自然就少了很多磁盘寻址的开销了，写入性能惊人，文件也不容易破损。&lt;/p&gt;
&lt;p&gt;AOF的日志是通过一个叫非常可读的方式记录的，这样的特性就适合做灾难性数据误删除的紧急恢复了，比如公司的实习生通过flushall清空了所有的数据，只要这个时候后台重写还没发生，你马上拷贝一份AOF日志文件，把最后一条flushall命令删了就完事了。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;缺点：&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;一样的数据，AOF文件比RDB还要大。&lt;/p&gt;
&lt;p&gt;AOF开启后，Redis支持写的QPS会比RDB支持写的要低。&lt;/p&gt;
&lt;h3 id=&#34;同步机制&#34;&gt;同步机制&lt;/h3&gt;
&lt;p&gt;Redis可以使用主从同步，从从同步。&lt;/p&gt;
&lt;p&gt;第一次同步时，主节点做一次bgsave，并同时将后续修改操作记录到内存buffer，待完成后将RDB文件全量同步到复制节点，复制节点接受完成后将RDB镜像加载到内存。&lt;/p&gt;
&lt;p&gt;加载完成后，再通知主节点将期间修改的操作记录同步到复制节点进行重放就完成了同步过程。&lt;/p&gt;
&lt;p&gt;后续的增量数据通过AOF日志同步即可，有点类似数据库的binlog。&lt;/p&gt;
&lt;br&gt;
&lt;hr&gt;
&lt;h2 id=&#34;高可用集群&#34;&gt;高可用/集群&lt;/h2&gt;
&lt;p&gt;Redis Sentinal 着眼于高可用，在master宕机时会自动将slave提升为master，继续提供服务。&lt;/p&gt;
&lt;p&gt;Redis Cluster 着眼于扩展性，在单个redis内存不足时，使用Cluster进行分片存储。&lt;/p&gt;
&lt;h3 id=&#34;gossip协议&#34;&gt;Gossip协议&lt;/h3&gt;
&lt;p&gt;Gossip算法又被称为反熵（Anti-Entropy），熵是物理学上的一个概念，代表杂乱无章，而反熵就是在杂乱无章中寻求一致&lt;/p&gt;
&lt;p&gt;这充分说明了Gossip的特点：在一个有界网络中，每个节点都随机地与其他节点通信，经过一番杂乱无章的通信，最终所有节点的状态都会达成一致。&lt;/p&gt;
&lt;p&gt;每个节点可能知道所有其他节点，也可能仅知道几个邻居节点，只要这些节可以通过网络连通，最终他们的状态都是一致的&lt;/p&gt;
&lt;p&gt;Redis Gossip消息分为消息头和消息体，消息体一共有4类，其中MEET、PING和PONG消息都用clusterMsgDataGossip结构来表示。&lt;/p&gt;
&lt;p&gt;随机周期性发送PING消息&lt;/p&gt;








  











&lt;figure id=&#34;figure-gossip协议下一种可能的消息传播过程&#34;&gt;


  &lt;a data-fancybox=&#34;&#34; href=&#34;https://w3xse7en.github.io/media/sql/redis/gossip.gif&#34; data-caption=&#34;Gossip协议下一种可能的消息传播过程&#34;&gt;


  &lt;img src=&#34;https://w3xse7en.github.io/media/sql/redis/gossip.gif&#34; alt=&#34;&#34;  &gt;
&lt;/a&gt;


  
  
  &lt;figcaption&gt;
    Gossip协议下一种可能的消息传播过程
  &lt;/figcaption&gt;


&lt;/figure&gt;

&lt;h3 id=&#34;redis的分片机制&#34;&gt;Redis的分片机制&lt;/h3&gt;
&lt;p&gt;Redis 集群没有使用一致性hash, 而是引入了哈希槽的概念。&lt;/p&gt;
&lt;p&gt;Redis Cluster 采用虚拟哈希槽分区，所有的键根据哈希函数映射到 0 ~ 16383 整数槽内，每个key通过CRC16校验后对16384取模来决定放置哪个槽(Slot)，每一个节点负责维护一部分槽以及槽所映射的键值数据。&lt;/p&gt;
&lt;p&gt;计算公式：slot = CRC16(key) &amp;amp; 16383。&lt;/p&gt;
&lt;p&gt;这种结构很容易添加或者删除节点，并且无论是添加删除或者修改某一个节点，都不会造成集群不可用的状态。使用哈希槽的好处就在于可以方便的添加或移除节点。&lt;/p&gt;
&lt;p&gt;当需要增加节点时，只需要把其他节点的某些哈希槽挪到新节点就可以了；&lt;/p&gt;
&lt;p&gt;当需要移除节点时，只需要把移除节点上的哈希槽挪到其他节点就行了。&lt;/p&gt;
&lt;h3 id=&#34;为什么rediscluster会设计成16384个槽呢&#34;&gt;为什么RedisCluster会设计成16384个槽呢？&lt;/h3&gt;
&lt;p&gt;1.如果槽位为65536，发送心跳信息的消息头达8k，发送的心跳包过于庞大。&lt;/p&gt;
&lt;p&gt;如上所述，在消息头中，最占空间的是 slots[CLUSTER_SLOTS/8]。 当槽位为65536时，这块的大小是: 65536÷8÷1024=8kb因为每秒钟，redis节点需要发送一定数量的ping消息作为心跳包，如果槽位为65536，这个ping消息的消息头太大了，浪费带宽。&lt;/p&gt;
&lt;p&gt;2.redis的集群主节点数量基本不可能超过1000个。&lt;/p&gt;
&lt;p&gt;如上所述，集群节点越多，心跳包的消息体内携带的数据越多。如果节点过1000个，也会导致网络拥堵。因此redis作者，不建议redis cluster节点数量超过1000个。 那么，对于节点数在1000以内的redis cluster集群，16384个槽位够用了。没有必要拓展到65536个。&lt;/p&gt;
&lt;p&gt;3.槽位越小，节点少的情况下，压缩率高&lt;/p&gt;
&lt;p&gt;Redis主节点的配置信息中，它所负责的哈希槽是通过一张bitmap的形式来保存的，在传输过程中，会对bitmap进行压缩，但是如果bitmap的填充率slots / N很高的话(N表示节点数)，bitmap的压缩率就很低。 如果节点数很少，而哈希槽数量很多的话，bitmap的压缩率就很低。&lt;/p&gt;
&lt;h3 id=&#34;redis数据增多了是该加内存还是加实例&#34;&gt;Redis数据增多了，是该加内存还是加实例？&lt;/h3&gt;
&lt;p&gt;这跟 Redis 的持久化机制有关系。&lt;/p&gt;
&lt;p&gt;在使用 RDB 进行持久化时，Redis 会 fork 子进程来完成，fork 操作的用时和 Redis 的数据量是正相关的，而 fork 在执行时会阻塞主线程。&lt;/p&gt;
&lt;p&gt;数据量越大，fork 操作造成的主线程阻塞的时间越长。所以，在使用 RDB 对 25GB 的数据进行持久化时，数据量较大，后台运行的子进程在 fork 创建时阻塞了主线程，于是就导致 Redis 响应变慢了。&lt;/p&gt;
&lt;h3 id=&#34;集群脑裂&#34;&gt;集群脑裂&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;min-replicas-to-write 3
min-replicas-max-lag 10
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;要求至少3个slave节点，且数据复制和同步的延迟不能超过10秒。否则master就拒绝读写，这样发生集群脑裂原先的master节点接收到写入请求就会拒绝&lt;/p&gt;
&lt;h4 id=&#34;raft协议解决脑裂&#34;&gt;Raft协议解决脑裂&lt;/h4&gt;
&lt;p&gt;选举安全性，即在一个任期内最多一个领导人被选出，如果有多余的领导人被选出，则被称为脑裂（brain split），如果出现脑裂会导致数据的丢失或者覆盖。&lt;/p&gt;
&lt;p&gt;Raft通过下面两点保证了不会出现脑裂的情况；&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;一个节点某一任期内最多只能投一票；&lt;/li&gt;
&lt;li&gt;只有获得大多数选票才能成为领导人；&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;通过增加约束避免了脑裂的情况出现，保证了同一时间集群中只有一个领导者。&lt;/p&gt;
&lt;p&gt;但是当一个节点崩溃了一段时间，他的状态机已经落后其他节点很多，突然他重启恢复被选举为领导者，这个时候，客户端发来的请求再经由他复制给其他节点的状态机执行，就会出现集群状态机状态不一致的问题。&lt;/p&gt;
&lt;br/&gt;
&lt;hr&gt;
&lt;h2 id=&#34;参考&#34;&gt;参考&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://itzones.cn/2020/06/30/Redis%E5%9F%BA%E6%9C%AC%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%E4%B9%8BHash/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Redis基本数据类型之Hash&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://juejin.cn/post/6844903446475177998&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Redis 为什么用跳表而不用平衡树？&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://juejin.cn/post/6844903433716105224&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Redis 内部数据结构详解 (2)——sds&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://itzones.cn/2020/07/11/Redis%E5%9F%BA%E6%9C%AC%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%E4%B9%8BZSet/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Redis基本数据类型之ZSet&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://juejin.cn/post/6844904039218429960&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;基于Redis的分布式锁和Redlock算法&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/javagrowing/JGrowing/blob/master/%E5%88%86%E5%B8%83%E5%BC%8F/%E5%86%8D%E6%9C%89%E4%BA%BA%E9%97%AE%E4%BD%A0%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%EF%BC%8C%E8%BF%99%E7%AF%87%E6%96%87%E7%AB%A0%E6%89%94%E7%BB%99%E4%BB%96.md&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;再有人问你分布式锁，这篇文章扔给他&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.jianshu.com/p/b52336ebfc43&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;漫谈Gossip协议与其在Redis Cluster中的实现&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://zhuanlan.zhihu.com/p/308641354&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;redis集群中的gossip协议&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;http://qyuan.top/2019/07/16/raft-1/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;分布式一致性协议之Raft(二)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.bilibili.com/video/BV1Fi4y147ad&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;动画演示 raft 在脑裂发生之后仍然可以正常工作吗？&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
</description>
    </item>
    
    <item>
      <title>TCP/IP</title>
      <link>https://w3xse7en.github.io/docs/web/tcp-ip/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>https://w3xse7en.github.io/docs/web/tcp-ip/</guid>
      <description>&lt;hr&gt;
&lt;h2&gt;目录&lt;/h2&gt;
&lt;nav id=&#34;TableOfContents&#34;&gt;
  &lt;ul&gt;
    &lt;li&gt;&lt;a href=&#34;#tcp-头格式&#34;&gt;TCP 头格式&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#tcp-工作在哪一层&#34;&gt;TCP 工作在哪一层&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#什么是-tcp-连接&#34;&gt;什么是 TCP 连接&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#如何唯一确定一个-tcp-连接&#34;&gt;如何唯一确定一个 TCP 连接&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#udp-头格式&#34;&gt;UDP 头格式&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#tcp-udp-区别&#34;&gt;TCP UDP 区别&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#tcp-和-udp-应用场景&#34;&gt;TCP 和 UDP 应用场景：&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#tcp-三次握手过程和状态变迁&#34;&gt;TCP 三次握手过程和状态变迁&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#为什么三次握手才可以建立连接&#34;&gt;为什么三次握手才可以建立连接&lt;/a&gt;
      &lt;ul&gt;
        &lt;li&gt;&lt;a href=&#34;#原因一避免历史连接&#34;&gt;原因一：避免历史连接&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#原因二同步双方初始序列号&#34;&gt;原因二：同步双方初始序列号&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#原因三避免资源浪费&#34;&gt;原因三：避免资源浪费&lt;/a&gt;&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#小结&#34;&gt;小结&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#tcp-四次挥手过程和状态变迁&#34;&gt;TCP 四次挥手过程和状态变迁&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#为什么挥手需要四次&#34;&gt;为什么挥手需要四次&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#为什么-time_wait-等待的时间是-2msl&#34;&gt;为什么 TIME_WAIT 等待的时间是 2MSL&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#为什么需要-time_wait-状态&#34;&gt;为什么需要 TIME_WAIT 状态&lt;/a&gt;
      &lt;ul&gt;
        &lt;li&gt;&lt;a href=&#34;#原因一防止旧连接的数据包&#34;&gt;原因一：防止旧连接的数据包&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#原因二保证连接正确关闭&#34;&gt;原因二：保证连接正确关闭&lt;/a&gt;&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#time_wait-过多有什么危害&#34;&gt;TIME_WAIT 过多有什么危害&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#既然-ip-层会分片为什么-tcp-层还需要-mss-&#34;&gt;既然 IP 层会分片，为什么 TCP 层还需要 MSS ？&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#参考&#34;&gt;参考&lt;/a&gt;&lt;/li&gt;
  &lt;/ul&gt;
&lt;/nav&gt;
&lt;hr&gt;
&lt;br&gt;
&lt;h2 id=&#34;tcp-头格式&#34;&gt;TCP 头格式&lt;/h2&gt;








  











&lt;figure &gt;


  &lt;a data-fancybox=&#34;&#34; href=&#34;https://w3xse7en.github.io/media/web/tcp/tcp_head.webp&#34; &gt;


  &lt;img src=&#34;https://w3xse7en.github.io/media/web/tcp/tcp_head.webp&#34; alt=&#34;&#34;  &gt;
&lt;/a&gt;



&lt;/figure&gt;

&lt;p&gt;序列号：在建立连接时由计算机生成的随机数作为其初始值，通过 SYN 包传给接收端主机，每发送一次数据，就「累加」一次该「数据字节数」的大小。用来解决网络包乱序问题。&lt;/p&gt;
&lt;p&gt;确认应答号：指下一次「期望」收到的数据的序列号，发送端收到这个确认应答以后可以认为在这个序号以前的数据都已经被正常接收。用来解决不丢包的问题。&lt;/p&gt;
&lt;p&gt;控制位：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;ACK：该位为 1 时，「确认应答」的字段变为有效，TCP 规定除了最初建立连接时的 SYN 包之外该位必须设置为 1 。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;RST：该位为 1 时，表示 TCP 连接中出现异常必须强制断开连接。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;SYC：该位为 1 时，表示希望建立连，并在其「序列号」的字段进行序列号初始值的设定。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;FIN：该位为 1 时，表示今后不会再有数据发送，希望断开连接。当通信结束希望断开连接时，通信双方的主机之间就可以相互交换 FIN 位置为 1 的 TCP 段。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;br&gt;
&lt;h2 id=&#34;tcp-工作在哪一层&#34;&gt;TCP 工作在哪一层&lt;/h2&gt;








  











&lt;figure &gt;


  &lt;a data-fancybox=&#34;&#34; href=&#34;https://w3xse7en.github.io/media/web/tcp/tcp_osi.webp&#34; &gt;


  &lt;img src=&#34;https://w3xse7en.github.io/media/web/tcp/tcp_osi.webp&#34; alt=&#34;&#34;  &gt;
&lt;/a&gt;



&lt;/figure&gt;

&lt;p&gt;IP 层（网络层）是「不可靠」的，它不保证网络包的交付、不保证网络包的按序交付、也不保证网络包中的数据的完整性。&lt;/p&gt;
&lt;p&gt;如果需要保障网络数据包的可靠性，那么就需要由上层（传输层）的 TCP 协议来负责。&lt;/p&gt;
&lt;p&gt;因为 TCP 是一个工作在传输层的可靠数据传输的服务，它能确保接收端接收的网络包是无损坏、无间隔、非冗余和按序的。&lt;/p&gt;
&lt;br&gt;
&lt;h2 id=&#34;什么是-tcp-连接&#34;&gt;什么是 TCP 连接&lt;/h2&gt;
&lt;p&gt;我们来看看 RFC 793 是如何定义「连接」的：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Connections:&lt;/p&gt;
&lt;p&gt;The reliability and flow control mechanisms described above require that TCPs initialize and maintain certain status information for each data stream.&lt;/p&gt;
&lt;p&gt;The combination of this information, including sockets, sequence numbers, and window sizes, is called a connection.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;简单来说就是，用于保证可靠性和流量控制维护的某些状态信息，这些信息的组合，包括Socket、序列号和窗口大小称为连接。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Socket：由 IP 地址和端口号组成&lt;/li&gt;
&lt;li&gt;序列号：用来解决乱序问题等&lt;/li&gt;
&lt;li&gt;窗口大小：用来做流量控制&lt;/li&gt;
&lt;/ul&gt;
&lt;br&gt;
&lt;h2 id=&#34;如何唯一确定一个-tcp-连接&#34;&gt;如何唯一确定一个 TCP 连接&lt;/h2&gt;
&lt;p&gt;TCP 四元组可以唯一的确定一个连接，四元组包括如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;源地址&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;源端口&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;目的地址&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;目的端口&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;源地址和目的地址的字段（32位）是在 IP 头部中，作用是通过 IP 协议发送报文给对方主机。&lt;/p&gt;
&lt;p&gt;源端口和目的端口的字段（16位）是在 TCP 头部中，作用是告诉 TCP 协议应该把报文发给哪个进程。&lt;/p&gt;
&lt;br&gt;
&lt;h2 id=&#34;udp-头格式&#34;&gt;UDP 头格式&lt;/h2&gt;
&lt;p&gt;UDP 不提供复杂的控制机制，利用 IP 提供面向「无连接」的通信服务。&lt;/p&gt;
&lt;p&gt;UDP 协议真的非常简，头部只有 8 个字节（ 64 位），UDP 的头部格式如下&lt;/p&gt;








  











&lt;figure &gt;


  &lt;a data-fancybox=&#34;&#34; href=&#34;https://w3xse7en.github.io/media/web/tcp/udp_head.webp&#34; &gt;


  &lt;img src=&#34;https://w3xse7en.github.io/media/web/tcp/udp_head.webp&#34; alt=&#34;&#34;  &gt;
&lt;/a&gt;



&lt;/figure&gt;

&lt;br&gt;
&lt;h2 id=&#34;tcp-udp-区别&#34;&gt;TCP UDP 区别&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;连接&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;TCP 是面向连接的传输层协议，传输数据前先要建立连接。&lt;/p&gt;
&lt;p&gt;UDP 是不需要连接，即刻传输数据。&lt;/p&gt;
&lt;ol start=&#34;2&#34;&gt;
&lt;li&gt;服务对象&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;TCP 是一对一的两点服务，即一条连接只有两个端点。&lt;/p&gt;
&lt;p&gt;UDP 支持一对一、一对多、多对多的交互通信&lt;/p&gt;
&lt;ol start=&#34;3&#34;&gt;
&lt;li&gt;可靠性&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;TCP 是可靠交付数据的，数据可以无差错、不丢失、不重复、按需到达。&lt;/p&gt;
&lt;p&gt;UDP 是尽最大努力交付，不保证可靠交付数据。&lt;/p&gt;
&lt;ol start=&#34;4&#34;&gt;
&lt;li&gt;拥塞控制、流量控制&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;TCP 有拥塞控制和流量控制机制，保证数据传输的安全性。&lt;/p&gt;
&lt;p&gt;UDP 则没有，即使网络非常拥堵了，也不会影响 UDP 的发送速率。&lt;/p&gt;
&lt;ol start=&#34;5&#34;&gt;
&lt;li&gt;首部开销&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;TCP 首部长度较长，会有一定的开销，首部在没有使用「选项」字段时是 20 个字节，如果使用了「选项」字段则会变长的。&lt;/p&gt;
&lt;p&gt;UDP 首部只有 8 个字节，并且是固定不变的，开销较小。&lt;/p&gt;
&lt;br&gt;
&lt;h2 id=&#34;tcp-和-udp-应用场景&#34;&gt;TCP 和 UDP 应用场景：&lt;/h2&gt;
&lt;p&gt;由于 TCP 是面向连接，能保证数据的可靠性交付，因此经常用于：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;FTP 文件传输&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;HTTP / HTTPS&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;由于 UDP 面向无连接，它可以随时发送数据，再加上UDP本身的处理既简单又高效，因此经常用于：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;包总量较少的通信，如 DNS 、SNMP 等&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;视频、音频等多媒体通信&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;广播通信&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;br&gt;
&lt;h2 id=&#34;tcp-三次握手过程和状态变迁&#34;&gt;TCP 三次握手过程和状态变迁&lt;/h2&gt;
&lt;p&gt;TCP 是面向连接的协议，所以使用 TCP 前必须先建立连接，而建立连接是通过三次握手而进行的。&lt;/p&gt;








  











&lt;figure &gt;


  &lt;a data-fancybox=&#34;&#34; href=&#34;https://w3xse7en.github.io/media/web/tcp/tcp_connect.webp&#34; &gt;


  &lt;img src=&#34;https://w3xse7en.github.io/media/web/tcp/tcp_connect.webp&#34; alt=&#34;&#34;  &gt;
&lt;/a&gt;



&lt;/figure&gt;

&lt;p&gt;一开始，客户端和服务端都处于 CLOSED 状态。先是服务端主动监听某个端口，处于 LISTEN 状态&lt;/p&gt;
&lt;p&gt;客户端会随机初始化序号（client_isn），将此序号置于 TCP 首部的「序号」字段中，同时把 SYN 标志位置为 1 ，表示 SYN 报文。接着把第一个 SYN 报文发送给服务端，表示向服务端发起连接，该报文不包含应用层数据，之后客户端处于 SYN-SENT 状态。&lt;/p&gt;
&lt;p&gt;服务端收到客户端的 SYN 报文后，首先服务端也随机初始化自己的序号（server_isn），将此序号填入 TCP 首部的「序号」字段中，其次把 TCP 首部的「确认应答号」字段填入 client_isn + 1, 接着把 SYN 和 ACK 标志位置为 1。最后把该报文发给客户端，该报文也不包含应用层数据，之后服务端处于 SYN-RCVD 状态。&lt;/p&gt;
&lt;p&gt;客户端收到服务端报文后，还要向服务端回应最后一个应答报文，首先该应答报文 TCP 首部 ACK 标志位置为 1 ，其次「确认应答号」字段填入 server_isn + 1 ，最后把报文发送给服务端，这次报文可以携带客户到服务器的数据，之后客户端处于 ESTABLISHED 状态。&lt;/p&gt;
&lt;p&gt;服务器收到客户端的应答报文后，也进入 ESTABLISHED 状态。&lt;/p&gt;
&lt;h2 id=&#34;为什么三次握手才可以建立连接&#34;&gt;为什么三次握手才可以建立连接&lt;/h2&gt;
&lt;p&gt;以三个方面分析三次握手的原因：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;三次握手才可以阻止历史重复连接的初始化（主要原因）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;三次握手才可以同步双方的初始序列号&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;三次握手才可以避免资源浪费&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;原因一避免历史连接&#34;&gt;原因一：避免历史连接&lt;/h3&gt;
&lt;p&gt;我们来看看 RFC 793 指出的 TCP 连接使用三次握手的首要原因：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The principle reason for the three-way handshake is to prevent old duplicate connection initiations from causing confusion.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;简单来说，三次握手的首要原因是为了防止旧的重复连接初始化造成混乱。&lt;/p&gt;
&lt;p&gt;网络环境是错综复杂的，往往并不是如我们期望的一样，先发送的数据包，就先到达目标主机，反而它很骚，可能会由于网络拥堵等乱七八糟的原因，会使得旧的数据包，先到达目标主机，那么这种情况下 TCP 三次握手是如何避免的呢？&lt;/p&gt;








  











&lt;figure id=&#34;figure-三次握手避免历史连接&#34;&gt;


  &lt;a data-fancybox=&#34;&#34; href=&#34;https://w3xse7en.github.io/media/web/tcp/tcp_connect_reason1.webp&#34; data-caption=&#34;三次握手避免历史连接&#34;&gt;


  &lt;img src=&#34;https://w3xse7en.github.io/media/web/tcp/tcp_connect_reason1.webp&#34; alt=&#34;&#34;  &gt;
&lt;/a&gt;


  
  
  &lt;figcaption&gt;
    三次握手避免历史连接
  &lt;/figcaption&gt;


&lt;/figure&gt;

&lt;p&gt;客户端连续发送多次 SYN 建立连接的报文，在网络拥堵等情况下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;一个「旧 SYN 报文」比「最新的 SYN 」 报文早到达了服务端；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;那么此时服务端就会回一个 SYN + ACK 报文给客户端；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;客户端收到后可以根据自身的上下文，判断这是一个历史连接（序列号过期或超时），那么客户端就会发送 RST 报文给服务端，表示中止这一次连接。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果是两次握手连接，就不能判断当前连接是否是历史连接，三次握手则可以在客户端（发送方）准备发送第三次报文时，客户端因有足够的上下文来判断当前连接是否是历史连接：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;如果是历史连接（序列号过期或超时），则第三次握手发送的报文是 RST 报文，以此中止历史连接；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;如果不是历史连接，则第三次发送的报文是 ACK 报文，通信双方就会成功建立连接；&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;所以， TCP 使用三次握手建立连接的最主要原因是 &lt;strong&gt;防止历史连接初始化了连接。&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&#34;原因二同步双方初始序列号&#34;&gt;原因二：同步双方初始序列号&lt;/h3&gt;
&lt;p&gt;TCP 协议的通信双方， 都必须维护一个「序列号」， 序列号是可靠传输的一个关键因素，它的作用：&lt;/p&gt;
&lt;p&gt;接收方可以去除重复的数据；&lt;/p&gt;
&lt;p&gt;接收方可以根据数据包的序列号按序接收；&lt;/p&gt;
&lt;p&gt;可以标识发送出去的数据包中， 哪些是已经被对方收到的；&lt;/p&gt;
&lt;p&gt;可见，序列号在 TCP 连接中占据着非常重要的作用，所以当客户端发送携带「初始序列号」的 SYN 报文的时候，需要服务端回一个 ACK 应答报文，
表示客户端的 SYN 报文已被服务端成功接收，那当服务端发送「初始序列号」给客户端的时候，依然也要得到客户端的应答回应，
这样一来一回，才能 &lt;strong&gt;确保双方的初始序列号能被可靠的同步。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;而两次握手只保证了一方的初始序列号能被对方成功接收，没办法保证双方的初始序列号都能被确认接收。&lt;/p&gt;
&lt;h3 id=&#34;原因三避免资源浪费&#34;&gt;原因三：避免资源浪费&lt;/h3&gt;
&lt;p&gt;如果只有「两次握手」，当客户端的 SYN 请求连接在网络中阻塞，客户端没有接收到 ACK 报文，就会重新发送 SYN ，由于没有第三次握手，服务器不清楚客户端是否收到了自己发送的建立连接的 ACK 确认信号，所以每收到一个 SYN 就只能先主动建立一个连接，这会造成什么情况呢？&lt;/p&gt;
&lt;p&gt;如果客户端的 SYN 阻塞了，重复发送多次 SYN 报文，那么服务器在收到请求后就会 &lt;strong&gt;建立多个冗余的无效链接，造成不必要的资源浪费。&lt;/strong&gt;&lt;/p&gt;








  











&lt;figure id=&#34;figure-两次握手会造成资源浪费&#34;&gt;


  &lt;a data-fancybox=&#34;&#34; href=&#34;https://w3xse7en.github.io/media/web/tcp/tcp_connect_reason3.webp&#34; data-caption=&#34;两次握手会造成资源浪费&#34;&gt;


  &lt;img src=&#34;https://w3xse7en.github.io/media/web/tcp/tcp_connect_reason3.webp&#34; alt=&#34;&#34;  &gt;
&lt;/a&gt;


  
  
  &lt;figcaption&gt;
    两次握手会造成资源浪费
  &lt;/figcaption&gt;


&lt;/figure&gt;

&lt;p&gt;即两次握手会造成消息滞留情况下，服务器重复接受无用的连接请求 SYN 报文，而造成重复分配资源。&lt;/p&gt;
&lt;p&gt;四次握手其实也能够可靠的同步双方的初始化序号，但由于第二步和第三步可以优化成一步，所以就成了「三次握手」。&lt;/p&gt;








  











&lt;figure id=&#34;figure-四次握手与三次握手&#34;&gt;


  &lt;a data-fancybox=&#34;&#34; href=&#34;https://w3xse7en.github.io/media/web/tcp/tcp_connect_reason2.webp&#34; data-caption=&#34;四次握手与三次握手&#34;&gt;


  &lt;img src=&#34;https://w3xse7en.github.io/media/web/tcp/tcp_connect_reason2.webp&#34; alt=&#34;&#34;  &gt;
&lt;/a&gt;


  
  
  &lt;figcaption&gt;
    四次握手与三次握手
  &lt;/figcaption&gt;


&lt;/figure&gt;

&lt;br&gt;
&lt;h2 id=&#34;小结&#34;&gt;小结&lt;/h2&gt;
&lt;p&gt;TCP 建立连接时，通过三次握手 &lt;strong&gt;能防止历史连接的建立，能减少双方不必要的资源开销，能帮助双方同步初始化序列号。&lt;/strong&gt; 序列号能够保证数据包不重复、不丢弃和按序传输。&lt;/p&gt;
&lt;p&gt;不使用「两次握手」和「四次握手」的原因：&lt;/p&gt;
&lt;p&gt;「两次握手」：无法防止历史连接的建立，会造成双方资源的浪费，也无法可靠的同步双方序列号；&lt;/p&gt;
&lt;p&gt;「四次握手」：三次握手就已经理论上最少可靠连接建立，所以不需要使用更多的通信次数。&lt;/p&gt;
&lt;br&gt;
&lt;h2 id=&#34;tcp-四次挥手过程和状态变迁&#34;&gt;TCP 四次挥手过程和状态变迁&lt;/h2&gt;








  











&lt;figure id=&#34;figure-客户端主动关闭连接--tcp-四次挥手&#34;&gt;


  &lt;a data-fancybox=&#34;&#34; href=&#34;https://w3xse7en.github.io/media/web/tcp/tcp_close.webp&#34; data-caption=&#34;客户端主动关闭连接 —— TCP 四次挥手&#34;&gt;


  &lt;img src=&#34;https://w3xse7en.github.io/media/web/tcp/tcp_close.webp&#34; alt=&#34;&#34;  &gt;
&lt;/a&gt;


  
  
  &lt;figcaption&gt;
    客户端主动关闭连接 —— TCP 四次挥手
  &lt;/figcaption&gt;


&lt;/figure&gt;

&lt;p&gt;客户端打算关闭连接，此时会发送一个 TCP 首部 FIN 标志位被置为 1 的报文，也即 FIN 报文，之后客户端进入 FIN_WAIT_1 状态。&lt;/p&gt;
&lt;p&gt;服务端收到该报文后，就向客户端发送 ACK 应答报文，接着服务端进入 CLOSED_WAIT 状态。&lt;/p&gt;
&lt;p&gt;客户端收到服务端的 ACK 应答报文后，之后进入 FIN_WAIT_2 状态。&lt;/p&gt;
&lt;p&gt;等待服务端处理完数据后，也向客户端发送 FIN 报文，之后服务端进入 LAST_ACK 状态。&lt;/p&gt;
&lt;p&gt;客户端收到服务端的 FIN 报文后，回一个 ACK 应答报文，之后进入 TIME_WAIT 状态&lt;/p&gt;
&lt;p&gt;服务器收到了 ACK 应答报文后，就进入了 CLOSE 状态，至此服务端已经完成连接的关闭。&lt;/p&gt;
&lt;p&gt;客户端在经过 2MSL 一段时间后，自动进入 CLOSE 状态，至此客户端也完成连接的关闭。&lt;/p&gt;
&lt;p&gt;你可以看到，&lt;strong&gt;每个方向都需要一个 FIN 和一个 ACK，因此通常被称为四次挥手。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这里一点需要注意是：&lt;strong&gt;主动关闭连接的，才有 TIME_WAIT 状态。&lt;/strong&gt;&lt;/p&gt;
&lt;br&gt;
&lt;h2 id=&#34;为什么挥手需要四次&#34;&gt;为什么挥手需要四次&lt;/h2&gt;
&lt;p&gt;再来回顾下四次挥手双方发 FIN 包的过程，就能理解为什么需要四次了。&lt;/p&gt;
&lt;p&gt;关闭连接时，客户端向服务端发送 FIN 时，仅仅表示客户端不再发送数据了但是还能接收数据。&lt;/p&gt;
&lt;p&gt;服务器收到客户端的 FIN 报文时，先回一个 ACK 应答报文，而服务端可能还有数据需要处理和发送，等服务端不再发送数据时，才发送 FIN 报文给客户端来表示同意现在关闭连接。&lt;/p&gt;
&lt;p&gt;从上面过程可知，&lt;strong&gt;服务端通常需要等待完成数据的发送和处理&lt;/strong&gt;，所以服务端的 ACK 和 FIN 一般都会分开发送，从而比三次握手导致多了一次。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;服务端已经完成了数据的发送和处理&lt;/strong&gt;，那么服务端的 ACK 和 FIN 会合并发送，变成三次挥手&lt;/p&gt;








  











&lt;figure id=&#34;figure-curl-http1921683100&#34;&gt;


  &lt;a data-fancybox=&#34;&#34; href=&#34;https://w3xse7en.github.io/media/web/tcp/tcp_connect_wireshark.webp&#34; data-caption=&#34;curl http://192.168.3.100&#34;&gt;


  &lt;img src=&#34;https://w3xse7en.github.io/media/web/tcp/tcp_connect_wireshark.webp&#34; alt=&#34;&#34;  &gt;
&lt;/a&gt;


  
  
  &lt;figcaption&gt;
    curl http://192.168.3.100
  &lt;/figcaption&gt;


&lt;/figure&gt;

&lt;br&gt;
&lt;h2 id=&#34;为什么-time_wait-等待的时间是-2msl&#34;&gt;为什么 TIME_WAIT 等待的时间是 2MSL&lt;/h2&gt;
&lt;p&gt;MSL 是 Maximum Segment Lifetime，&lt;strong&gt;报文最大生存时间&lt;/strong&gt;，它是任何报文在网络上存在的最长时间，超过这个时间报文将被丢弃。因为 TCP 报文基于是 IP 协议的，而 IP 头中有一个 TTL 字段，是 IP 数据报可以经过的最大路由数，每经过一个处理他的路由器此值就减 1，当此值为 0 则数据报将被丢弃，同时发送 ICMP 报文通知源主机。&lt;/p&gt;
&lt;p&gt;MSL 与 TTL 的区别：MSL 的单位是时间，而 TTL 是经过路由跳数。所以 MSL 应该要大于等于 TTL 消耗为 0 的时间，以确保报文已被自然消亡。&lt;/p&gt;
&lt;p&gt;TIME_WAIT 等待 2 倍的 MSL，比较合理的解释是：网络中可能存在来自发送方的数据包，当这些发送方的数据包被接收方处理后又会向对方发送响应，&lt;strong&gt;所以一来一回需要等待 2 倍的时间。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;比如，如果被动关闭方没有收到断开连接的最后的 ACK 报文，就会触发超时重发 Fin 报文，另一方接收到 FIN 后，会重发 ACK 给被动关闭方， 一来一去正好 2 个 MSL。&lt;/p&gt;
&lt;p&gt;2MSL 的时间是从客户端接收到 FIN 后发送 ACK 开始计时的。如果在 TIME-WAIT 时间内，因为客户端的 ACK 没有传输到服务端，客户端又接收到了服务端重发的 FIN 报文，那么 2MSL 时间将重新计时。&lt;/p&gt;
&lt;p&gt;在 Linux 系统里 2MSL 默认是 60 秒，那么一个 MSL 也就是 30 秒。&lt;strong&gt;Linux 系统停留在 TIME_WAIT 的时间为固定的 60 秒。&lt;/strong&gt;&lt;/p&gt;
&lt;br&gt;
&lt;h2 id=&#34;为什么需要-time_wait-状态&#34;&gt;为什么需要 TIME_WAIT 状态&lt;/h2&gt;
&lt;p&gt;主动发起关闭连接的一方，才会有 TIME-WAIT 状态。&lt;/p&gt;
&lt;p&gt;需要 TIME-WAIT 状态，主要是两个原因：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;防止具有相同「四元组」的「旧」数据包被收到；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;保证「被动关闭连接」的一方能被正确的关闭，即保证最后的 ACK 能让被动关闭方接收，从而帮助其正常关闭；&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;原因一防止旧连接的数据包&#34;&gt;原因一：防止旧连接的数据包&lt;/h3&gt;
&lt;p&gt;假设 TIME-WAIT 没有等待时间或时间过短，被延迟的数据包抵达后会发生什么呢？&lt;/p&gt;








  











&lt;figure id=&#34;figure-接收到历史数据的异常&#34;&gt;


  &lt;a data-fancybox=&#34;&#34; href=&#34;https://w3xse7en.github.io/media/web/tcp/tcp_close_reason1.webp&#34; data-caption=&#34;接收到历史数据的异常&#34;&gt;


  &lt;img src=&#34;https://w3xse7en.github.io/media/web/tcp/tcp_close_reason1.webp&#34; alt=&#34;&#34;  &gt;
&lt;/a&gt;


  
  
  &lt;figcaption&gt;
    接收到历史数据的异常
  &lt;/figcaption&gt;


&lt;/figure&gt;

&lt;p&gt;如上图黄色框框服务端在关闭连接之前发送的 SEQ = 301 报文，被网络延迟了。&lt;/p&gt;
&lt;p&gt;这时有相同端口的 TCP 连接被复用后，被延迟的 SEQ = 301 抵达了客户端，那么客户端是有可能正常接收这个过期的报文，这就会产生数据错乱等严重的问题。&lt;/p&gt;
&lt;p&gt;所以，TCP 就设计出了这么一个机制，经过 2MSL 这个时间，&lt;strong&gt;足以让两个方向上的数据包都被丢弃，使得原来连接的数据包在网络中都自然消失，再出现的数据包一定都是新建立连接所产生的。&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&#34;原因二保证连接正确关闭&#34;&gt;原因二：保证连接正确关闭&lt;/h3&gt;
&lt;p&gt;在 RFC 793 指出 TIME-WAIT 另一个重要的作用是：&lt;/p&gt;
&lt;p&gt;TIME-WAIT - represents waiting for enough time to pass to be sure the remote TCP received the acknowledgment of its connection termination request.&lt;/p&gt;
&lt;p&gt;也就是说，TIME-WAIT 作用是等待足够的时间以确保最后的 ACK 能让被动关闭方接收，从而帮助其正常关闭。&lt;/p&gt;
&lt;p&gt;假设 TIME-WAIT 没有等待时间或时间过短，断开连接会造成什么问题呢？&lt;/p&gt;








  











&lt;figure id=&#34;figure-没有确保正常断开的异常&#34;&gt;


  &lt;a data-fancybox=&#34;&#34; href=&#34;https://w3xse7en.github.io/media/web/tcp/tcp_close_reason2.webp&#34; data-caption=&#34;没有确保正常断开的异常&#34;&gt;


  &lt;img src=&#34;https://w3xse7en.github.io/media/web/tcp/tcp_close_reason2.webp&#34; alt=&#34;&#34;  &gt;
&lt;/a&gt;


  
  
  &lt;figcaption&gt;
    没有确保正常断开的异常
  &lt;/figcaption&gt;


&lt;/figure&gt;

&lt;p&gt;如上图红色框框客户端四次挥手的最后一个 ACK 报文如果在网络中被丢失了，此时如果客户端 TIME-WAIT 过短或没有，则就直接进入了 CLOSE 状态了，那么服务端则会一直处在 LASE-ACK 状态。&lt;/p&gt;
&lt;p&gt;当客户端发起建立连接的 SYN 请求报文后，服务端会发送 RST 报文给客户端，连接建立的过程就会被终止。&lt;/p&gt;
&lt;p&gt;如果 TIME-WAIT 等待足够长的情况就会遇到两种情况：&lt;/p&gt;
&lt;p&gt;服务端正常收到四次挥手的最后一个 ACK 报文，则服务端正常关闭连接。&lt;/p&gt;
&lt;p&gt;服务端没有收到四次挥手的最后一个 ACK 报文时，则会重发 FIN 关闭连接报文并等待新的 ACK 报文。&lt;/p&gt;
&lt;p&gt;所以客户端在 TIME-WAIT 状态等待 2MSL 时间后，&lt;strong&gt;就可以保证双方的连接都可以正常的关闭。&lt;/strong&gt;&lt;/p&gt;
&lt;br&gt;
&lt;h2 id=&#34;time_wait-过多有什么危害&#34;&gt;TIME_WAIT 过多有什么危害&lt;/h2&gt;
&lt;p&gt;如果服务器有处于 TIME-WAIT 状态的 TCP，则说明是由服务器方主动发起的断开请求。&lt;/p&gt;
&lt;p&gt;过多的 TIME-WAIT 状态主要的危害有两种：&lt;/p&gt;
&lt;p&gt;第一是内存资源占用；&lt;/p&gt;
&lt;p&gt;第二是对端口资源的占用，一个 TCP 连接至少消耗一个本地端口；&lt;/p&gt;
&lt;p&gt;第二个危害是会造成严重的后果的，要知道，端口资源也是有限的，一般可以开启的端口为 32768～61000，也可以通过如下参数设置指定&lt;/p&gt;
&lt;p&gt;&lt;code&gt;net.ipv4.ip_local_port_range&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;服务端 TIME_WAIT 状态过多，占满了所有端口资源，则会导致无法创建新连接。&lt;/strong&gt;&lt;/p&gt;
&lt;br&gt;
&lt;h2 id=&#34;既然-ip-层会分片为什么-tcp-层还需要-mss-&#34;&gt;既然 IP 层会分片，为什么 TCP 层还需要 MSS ？&lt;/h2&gt;








  











&lt;figure id=&#34;figure-mtu-与-mss&#34;&gt;


  &lt;a data-fancybox=&#34;&#34; href=&#34;https://w3xse7en.github.io/media/web/tcp/tcp_mtu_mss.webp&#34; data-caption=&#34;MTU 与 MSS&#34;&gt;


  &lt;img src=&#34;https://w3xse7en.github.io/media/web/tcp/tcp_mtu_mss.webp&#34; alt=&#34;&#34;  &gt;
&lt;/a&gt;


  
  
  &lt;figcaption&gt;
    MTU 与 MSS
  &lt;/figcaption&gt;


&lt;/figure&gt;

&lt;p&gt;MTU：一个网络包的最大长度，以太网中一般为 1500 字节；&lt;/p&gt;
&lt;p&gt;MSS：除去 IP 和 TCP 头部之后，一个网络包所能容纳的 TCP 数据的最大长度；&lt;/p&gt;
&lt;p&gt;如果TCP 的整个报文（头部 + 数据）交给 IP 层进行分片，会有什么异常呢？&lt;/p&gt;
&lt;p&gt;当 IP 层有一个超过 MTU 大小的数据（TCP 头部 + TCP 数据）要发送，那么 IP 层就要进行分片，把数据分片成若干片，保证每一个分片都小于 MTU。把一份 IP 数据报进行分片以后，由目标主机的 IP 层来进行重新组装后，在交给上一层 TCP 传输层。&lt;/p&gt;
&lt;p&gt;这看起来井然有序，但这存在隐患的，&lt;strong&gt;那么当如果一个 IP 分片丢失，整个 IP 报文的所有分片都得重传。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;因为 IP 层本身没有超时重传机制，它由传输层的 TCP 来负责超时和重传。&lt;/p&gt;
&lt;p&gt;当接收方发现 TCP 报文（头部 + 数据）的某一片丢失后，则不会响应 ACK 给对方，那么发送方的 TCP 在超时后，就会重发「整个 TCP 报文（头部 + 数据）」。&lt;/p&gt;
&lt;p&gt;因此，可以得知由 IP 层进行分片传输，是非常没有效率的。&lt;/p&gt;
&lt;p&gt;所以，为了达到最佳的传输效能 TCP 协议在 &lt;strong&gt;建立连接的时候通常要协商双方的 MSS 值&lt;/strong&gt; ，当 TCP 层发现数据超过 MSS 时，则就先会进行分片，当然由它形成的 IP 包的长度也就不会大于 MTU ，自然也就不用 IP 分片了。&lt;/p&gt;
&lt;p&gt;经过 TCP 层分片后，如果一个 TCP 分片丢失后，进行重发时也是以 MSS 为单位，而不用重传所有的分片，大大增加了重传的效率。&lt;/p&gt;
&lt;br&gt;
&lt;hr&gt;
&lt;h2 id=&#34;参考&#34;&gt;参考&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://datatracker.ietf.org/doc/rfc1644/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;TCP 协议 RFC 文档&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://mp.weixin.qq.com/s?__biz=MzUxODAzNDg4NQ==&amp;amp;mid=2247484005&amp;amp;idx=1&amp;amp;sn=cb07ee1c891a7bdd0af3859543190202&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;硬不硬你说了算！近 40 张图解被问千百遍的 TCP 三次握手和四次挥手面试题&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://mp.weixin.qq.com/s?__biz=MzUxODAzNDg4NQ==&amp;amp;mid=2247484017&amp;amp;idx=1&amp;amp;sn=dc54d43bfd5dc088e48adcfa2e2bc13f&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;你还在为 TCP 重传、滑动窗口、流量控制、拥塞控制发愁吗？看完图解就不愁了&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.bilibili.com/video/BV1c4411d7jb?p=1&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;湖南科技大学 - 计算机网络微课堂&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://zhuanlan.zhihu.com/p/58668946&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;HTTP/3 都来了，你却还在用 HTTP/1.1？&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
</description>
    </item>
    
    <item>
      <title>Http</title>
      <link>https://w3xse7en.github.io/docs/web/http/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>https://w3xse7en.github.io/docs/web/http/</guid>
      <description>&lt;hr&gt;
&lt;h2&gt;目录&lt;/h2&gt;
&lt;nav id=&#34;TableOfContents&#34;&gt;
  &lt;ul&gt;
    &lt;li&gt;&lt;a href=&#34;#http&#34;&gt;Http&lt;/a&gt;
      &lt;ul&gt;
        &lt;li&gt;&lt;a href=&#34;#http-常见的状态码有哪些&#34;&gt;Http 常见的状态码，有哪些？&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#长连接&#34;&gt;长连接&lt;/a&gt;&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#https&#34;&gt;Https&lt;/a&gt;
      &lt;ul&gt;
        &lt;li&gt;&lt;a href=&#34;#https-解决了-http-的哪些问题&#34;&gt;HTTPS 解决了 HTTP 的哪些问题？&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#混合加密&#34;&gt;混合加密&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#摘要算法&#34;&gt;摘要算法&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#数字证书&#34;&gt;数字证书&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#https-建立连接&#34;&gt;HTTPS 建立连接&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#clienthello&#34;&gt;ClientHello&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#severhello&#34;&gt;SeverHello&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#客户端回应&#34;&gt;客户端回应&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#服务器的最后回应&#34;&gt;服务器的最后回应&lt;/a&gt;&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#http2&#34;&gt;Http/2&lt;/a&gt;
      &lt;ul&gt;
        &lt;li&gt;&lt;a href=&#34;#多路复用-multiplexing&#34;&gt;多路复用 (Multiplexing)&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#二进制分帧&#34;&gt;二进制分帧&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#首部压缩header-compression&#34;&gt;首部压缩（Header Compression）&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#server-push&#34;&gt;Server Push&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#总结&#34;&gt;总结：&lt;/a&gt;&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#http3&#34;&gt;Http/3&lt;/a&gt;
      &lt;ul&gt;
        &lt;li&gt;&lt;a href=&#34;#0-rtt&#34;&gt;0-RTT&lt;/a&gt;&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#qa&#34;&gt;Q&amp;amp;A&lt;/a&gt;
      &lt;ul&gt;
        &lt;li&gt;&lt;a href=&#34;#与服务器建立了一个tcp连接后是否会在一个-http-请求完成后断开什么情况下会断开&#34;&gt;与服务器建立了一个TCP连接后是否会在一个 HTTP 请求完成后断开？什么情况下会断开？&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#一个tcp连接中http请求发送可以一起发送么比如一起发三个请求再三个响应一起接收&#34;&gt;一个TCP连接中HTTP请求发送可以一起发送么（比如一起发三个请求，再三个响应一起接收）&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#为什么有的时候刷新页面不需要重新建立-ssl-连接&#34;&gt;为什么有的时候刷新页面不需要重新建立 SSL 连接？&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#浏览器对同一-host-建立-tcp-连接到数量有没有限制&#34;&gt;浏览器对同一 Host 建立 TCP 连接到数量有没有限制？&lt;/a&gt;&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#参考&#34;&gt;参考&lt;/a&gt;&lt;/li&gt;
  &lt;/ul&gt;
&lt;/nav&gt;
&lt;hr&gt;
&lt;br&gt;
&lt;h2 id=&#34;http&#34;&gt;Http&lt;/h2&gt;
&lt;h3 id=&#34;http-常见的状态码有哪些&#34;&gt;Http 常见的状态码，有哪些？&lt;/h3&gt;








  











&lt;figure id=&#34;figure-http状态码&#34;&gt;


  &lt;a data-fancybox=&#34;&#34; href=&#34;https://w3xse7en.github.io/media/web/http/http_status.webp&#34; data-caption=&#34;Http状态码&#34;&gt;


  &lt;img src=&#34;https://w3xse7en.github.io/media/web/http/http_status.webp&#34; alt=&#34;&#34;  &gt;
&lt;/a&gt;


  
  
  &lt;figcaption&gt;
    Http状态码
  &lt;/figcaption&gt;


&lt;/figure&gt;

&lt;p&gt;&lt;code&gt;1xx&lt;/code&gt; 类状态码属于提示信息，是协议处理中的一种中间状态，实际用到的比较少。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;2xx&lt;/code&gt; 类状态码表示服务器成功处理了客户端的请求，也是我们最愿意看到的状态。&lt;/p&gt;
&lt;p&gt;「200 OK」是最常见的成功状态码，表示一切正常。如果是非 HEAD 请求，服务器返回的响应头都会有 body 数据。&lt;/p&gt;
&lt;p&gt;「204 No Content」也是常见的成功状态码，与 200 OK 基本相同，但响应头没有 body 数据。&lt;/p&gt;
&lt;p&gt;「206 Partial Content」是应用于 HTTP 分块下载或断电续传，表示响应返回的 body 数据并不是资源的全部，而是其中的一部分，也是服务器处理成功的状态。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;3xx&lt;/code&gt; 类状态码表示客户端请求的资源发送了变动，需要客户端用新的 URL 重新发送请求获取资源，也就是重定向。&lt;/p&gt;
&lt;p&gt;「301 Moved Permanently」表示永久重定向，说明请求的资源已经不存在了，需改用新的 URL 再次访问。&lt;/p&gt;
&lt;p&gt;「302 Moved Permanently」表示临时重定向，说明请求的资源还在，但暂时需要用另一个 URL 来访问。&lt;/p&gt;
&lt;p&gt;301 和 302 都会在响应头里使用字段 Location，指明后续要跳转的 URL，浏览器会自动重定向新的 URL。&lt;/p&gt;
&lt;p&gt;「304 Not Modified」不具有跳转的含义，表示资源未修改，重定向已存在的缓冲文件，也称缓存重定向，用于缓存控制。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;4xx&lt;/code&gt; 类状态码表示客户端发送的报文有误，服务器无法处理，也就是错误码的含义。&lt;/p&gt;
&lt;p&gt;「400 Bad Request」表示客户端请求的报文有错误，但只是个笼统的错误。&lt;/p&gt;
&lt;p&gt;「403 Forbidden」表示服务器禁止访问资源，并不是客户端的请求出错。&lt;/p&gt;
&lt;p&gt;「404 Not Found」表示请求的资源在服务器上不存在或未找到，所以无法提供给客户端。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;5xx&lt;/code&gt; 类状态码表示客户端请求报文正确，但是服务器处理时内部发生了错误，属于服务器端的错误码。&lt;/p&gt;
&lt;p&gt;「500 Internal Server Error」与 400 类型，是个笼统通用的错误码，服务器发生了什么错误，我们并不知道。&lt;/p&gt;
&lt;p&gt;「501 Not Implemented」表示客户端请求的功能还不支持，类似“即将开业，敬请期待”的意思。&lt;/p&gt;
&lt;p&gt;「502 Bad Gateway」通常是服务器作为网关或代理时返回的错误码，表示服务器自身工作正常，访问后端服务器发生了错误。&lt;/p&gt;
&lt;p&gt;「503 Service Unavailable」表示服务器当前很忙，暂时无法响应服务器，类似“网络服务正忙，请稍后重试”的意思。&lt;/p&gt;
&lt;h3 id=&#34;长连接&#34;&gt;长连接&lt;/h3&gt;
&lt;p&gt;早期 HTTP/1.0 性能上的一个很大的问题，那就是每发起一个请求，都要新建一次 TCP 连接（三次握手），而且是串行请求，做了无畏的 TCP 连接建立和断开，增加了通信开销。&lt;/p&gt;
&lt;p&gt;为了解决上述 TCP 连接问题，HTTP/1.1 提出了长连接的通信方式，也叫持久连接。这种方式的好处在于减少了 TCP 连接的重复建立和断开所造成的额外开销，减轻了服务器端的负载。&lt;/p&gt;
&lt;p&gt;持久连接的特点是，只要任意一端没有明确提出断开连接，则保持 TCP 连接状态。&lt;/p&gt;








  











&lt;figure id=&#34;figure-短链接与长链接&#34;&gt;


  &lt;a data-fancybox=&#34;&#34; href=&#34;https://w3xse7en.github.io/media/web/http/http_short_long_connect.webp&#34; data-caption=&#34;短链接与长链接&#34;&gt;


  &lt;img src=&#34;https://w3xse7en.github.io/media/web/http/http_short_long_connect.webp&#34; alt=&#34;&#34;  &gt;
&lt;/a&gt;


  
  
  &lt;figcaption&gt;
    短链接与长链接
  &lt;/figcaption&gt;


&lt;/figure&gt;

&lt;br&gt;
&lt;h2 id=&#34;https&#34;&gt;Https&lt;/h2&gt;
&lt;h3 id=&#34;https-解决了-http-的哪些问题&#34;&gt;HTTPS 解决了 HTTP 的哪些问题？&lt;/h3&gt;
&lt;p&gt;HTTP 由于是明文传输，所以安全上存在以下三个风险：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;窃听风险，比如通信链路上可以获取通信内容，用户账号容易没。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;篡改风险，比如强制入垃圾广告，视觉污染，用户眼容易瞎。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;冒充风险，比如冒充淘宝网站，用户钱容易没。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;HTTPS 在 HTTP 与 TCP 层之间加入了 SSL/TLS 协议，来解决上述风险。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;混合加密&lt;/code&gt;的方式实现信息的机密性，解决了窃听的风险。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;摘要算法&lt;/code&gt;的方式来实现完整性，它能够为数据生成独一无二的「指纹」，指纹用于校验数据的完整性，解决了篡改的风险。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;将服务器公钥放入到&lt;code&gt;数字证书&lt;/code&gt;中，解决了冒充的风险。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;混合加密&#34;&gt;混合加密&lt;/h3&gt;
&lt;p&gt;HTTPS 采用的是对称加密和非对称加密结合的「混合加密」方式：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;通信建立前采用非对称加密的方式交换「会话秘钥」，后续就不再使用非对称加密。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;通信过程中全部使用对称加密的「会话秘钥」的方式加密明文数据。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;采用「混合加密」的方式的原因：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;对称加密只使用一个密钥，运算速度快，密钥必须保密，无法做到安全的密钥交换。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;非对称加密使用两个密钥：公钥和私钥，公钥可以任意分发而私钥保密，解决了密钥交换问题但速度慢。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;








  











&lt;figure id=&#34;figure-混合加密&#34;&gt;


  &lt;a data-fancybox=&#34;&#34; href=&#34;https://w3xse7en.github.io/media/web/http/https_mix_crypt.webp&#34; data-caption=&#34;混合加密&#34;&gt;


  &lt;img src=&#34;https://w3xse7en.github.io/media/web/http/https_mix_crypt.webp&#34; alt=&#34;&#34;  &gt;
&lt;/a&gt;


  
  
  &lt;figcaption&gt;
    混合加密
  &lt;/figcaption&gt;


&lt;/figure&gt;

&lt;h3 id=&#34;摘要算法&#34;&gt;摘要算法&lt;/h3&gt;
&lt;p&gt;客户端在发送明文之前会通过摘要算法算出明文的「指纹」&lt;/p&gt;
&lt;p&gt;发送的时候把「指纹 + 明文」一同加密成密文后，发送给服务器&lt;/p&gt;
&lt;p&gt;服务器解密后，用相同的摘要算法算出发送过来的明文&lt;/p&gt;
&lt;p&gt;通过比较客户端携带的「指纹」和当前算出的「指纹」做比较，若「指纹」相同，说明数据是完整的。&lt;/p&gt;








  











&lt;figure id=&#34;figure-校验完整性&#34;&gt;


  &lt;a data-fancybox=&#34;&#34; href=&#34;https://w3xse7en.github.io/media/web/http/https_verify.webp&#34; data-caption=&#34;校验完整性&#34;&gt;


  &lt;img src=&#34;https://w3xse7en.github.io/media/web/http/https_verify.webp&#34; alt=&#34;&#34;  &gt;
&lt;/a&gt;


  
  
  &lt;figcaption&gt;
    校验完整性
  &lt;/figcaption&gt;


&lt;/figure&gt;

&lt;h3 id=&#34;数字证书&#34;&gt;数字证书&lt;/h3&gt;
&lt;p&gt;客户端先向服务器端索要公钥，然后用公钥加密信息，服务器收到密文后，用自己的私钥解密。&lt;/p&gt;
&lt;p&gt;这就存在些问题，如何保证公钥不被篡改和信任度？&lt;/p&gt;
&lt;p&gt;所以这里就需要借助第三方权威机构 &lt;code&gt;CA&lt;/code&gt; （数字证书认证机构），将服务器公钥放在数字证书（由数字证书认证机构颁发）中，只要证书是可信的，公钥就是可信的。&lt;/p&gt;








  











&lt;figure id=&#34;figure-数字证书工作流程&#34;&gt;


  &lt;a data-fancybox=&#34;&#34; href=&#34;https://w3xse7en.github.io/media/web/http/https_ca.webp&#34; data-caption=&#34;数字证书工作流程&#34;&gt;


  &lt;img src=&#34;https://w3xse7en.github.io/media/web/http/https_ca.webp&#34; alt=&#34;&#34;  &gt;
&lt;/a&gt;


  
  
  &lt;figcaption&gt;
    数字证书工作流程
  &lt;/figcaption&gt;


&lt;/figure&gt;

&lt;h3 id=&#34;https-建立连接&#34;&gt;HTTPS 建立连接&lt;/h3&gt;
&lt;p&gt;SSL/TLS 协议基本流程：&lt;/p&gt;
&lt;p&gt;客户端向服务器索要并验证服务器的公钥。&lt;/p&gt;
&lt;p&gt;双方协商生产「会话秘钥」。&lt;/p&gt;
&lt;p&gt;双方采用「会话秘钥」进行加密通信。&lt;/p&gt;
&lt;p&gt;前两步也就是 SSL/TLS 的建立过程，也就是握手阶段。&lt;/p&gt;
&lt;p&gt;SSL/TLS 的「握手阶段」涉及四次通信，可见下图：&lt;/p&gt;








  











&lt;figure id=&#34;figure-https-连接建立过程&#34;&gt;


  &lt;a data-fancybox=&#34;&#34; href=&#34;https://w3xse7en.github.io/media/web/http/https_connect.webp&#34; data-caption=&#34;HTTPS 连接建立过程&#34;&gt;


  &lt;img src=&#34;https://w3xse7en.github.io/media/web/http/https_connect.webp&#34; alt=&#34;&#34;  &gt;
&lt;/a&gt;


  
  
  &lt;figcaption&gt;
    HTTPS 连接建立过程
  &lt;/figcaption&gt;


&lt;/figure&gt;

&lt;p&gt;SSL/TLS 协议建立的详细流程：&lt;/p&gt;
&lt;h3 id=&#34;clienthello&#34;&gt;ClientHello&lt;/h3&gt;
&lt;p&gt;首先，由客户端向服务器发起加密通信请求，也就是 ClientHello 请求。&lt;/p&gt;
&lt;p&gt;在这一步，客户端主要向服务器发送以下信息：&lt;/p&gt;
&lt;p&gt;（1）客户端支持的 SSL/TLS 协议版本，如 TLS 1.2 版本。&lt;/p&gt;
&lt;p&gt;（2）客户端生产的随机数（Client Random），后面用于生产「会话秘钥」。&lt;/p&gt;
&lt;p&gt;（3）客户端支持的密码套件列表，如 RSA 加密算法。&lt;/p&gt;
&lt;h3 id=&#34;severhello&#34;&gt;SeverHello&lt;/h3&gt;
&lt;p&gt;服务器收到客户端请求后，向客户端发出响应，也就是 SeverHello。服务器回应的内容有如下内容：&lt;/p&gt;
&lt;p&gt;（1）确认 SSL/ TLS 协议版本，如果浏览器不支持，则关闭加密通信。&lt;/p&gt;
&lt;p&gt;（2）服务器生产的随机数（Server Random），后面用于生产「会话秘钥」。&lt;/p&gt;
&lt;p&gt;（3）确认的密码套件列表，如 RSA 加密算法。&lt;/p&gt;
&lt;p&gt;（4）服务器的数字证书。&lt;/p&gt;
&lt;h3 id=&#34;客户端回应&#34;&gt;客户端回应&lt;/h3&gt;
&lt;p&gt;客户端收到服务器的回应之后，首先通过浏览器或者操作系统中的 CA 公钥，确认服务器的数字证书的真实性。&lt;/p&gt;
&lt;p&gt;如果证书没有问题，客户端会从数字证书中取出服务器的公钥，然后使用它加密报文，向服务器发送如下信息：&lt;/p&gt;
&lt;p&gt;（1）一个随机数（pre-master key）。该随机数会被服务器公钥加密。&lt;/p&gt;
&lt;p&gt;（2）加密通信算法改变通知，表示随后的信息都将用「会话秘钥」加密通信。&lt;/p&gt;
&lt;p&gt;（3）客户端握手结束通知，表示客户端的握手阶段已经结束。这一项同时把之前所有内容的发生的数据做个摘要，用来供服务端校验。&lt;/p&gt;
&lt;p&gt;上面第一项的随机数是整个握手阶段的第三个随机数，这样服务器和客户端就同时有三个随机数，接着就用双方协商的加密算法，各自生成本次通信的「会话秘钥」。&lt;/p&gt;
&lt;h3 id=&#34;服务器的最后回应&#34;&gt;服务器的最后回应&lt;/h3&gt;
&lt;p&gt;服务器收到客户端的第三个随机数（pre-master key）之后，通过协商的加密算法，计算出本次通信的「会话秘钥」。然后，向客户端发生最后的信息：&lt;/p&gt;
&lt;p&gt;（1）加密通信算法改变通知，表示随后的信息都将用「会话秘钥」加密通信。&lt;/p&gt;
&lt;p&gt;（2）服务器握手结束通知，表示服务器的握手阶段已经结束。这一项同时把之前所有内容的发生的数据做个摘要，用来供客户端校验。&lt;/p&gt;
&lt;p&gt;至此，整个 SSL/TLS 的握手阶段全部结束。接下来，客户端与服务器进入加密通信，就完全是使用普通的 HTTP 协议，只不过用「会话秘钥」加密内容。&lt;/p&gt;
&lt;br&gt;
&lt;h2 id=&#34;http2&#34;&gt;Http/2&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://http2.akamai.com/demo&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Http/1.1 vs Http/2 demo&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;多路复用-multiplexing&#34;&gt;多路复用 (Multiplexing)&lt;/h3&gt;
&lt;p&gt;在 HTTP/1.1 协议中 「浏览器客户端在同一时间，针对同一域名下的请求有一定数量限制。超过限制数目的请求会被阻塞」&lt;/p&gt;
&lt;p&gt;RFC-2616-8.1.4 Practical Considerations:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Clients that use persistent connections SHOULD limit the number of simultaneous connections that they maintain to a given server.
A single-user client SHOULD NOT maintain more than 2 connections with any server or proxy.
A proxy SHOULD use up to 2*N connections to another server or proxy, where N is the number of simultaneously active users.
These guidelines are intended to improve HTTP response times and avoid congestion.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;翻译：Chrome中，对Http/1.1协议，最多允许6个tcp链接，超过的则会被阻塞&lt;/p&gt;
&lt;p&gt;HTTP/2 的多路复用(Multiplexing) 则允许同时通过单一的 HTTP/2 连接发起多重的请求-响应消息。&lt;/p&gt;
&lt;p&gt;移除了 HTTP/1.1 中的串行请求，不需要排队等待，也就不会再出现「队头阻塞」问题，降低了延迟，大幅度提高了连接的利用率。&lt;/p&gt;
&lt;p&gt;HTTP/2 的数据包不是按顺序发送的，同一个连接里面连续的数据包，可能属于不同的回应。因此，必须要对数据包做标记，指出它属于哪个回应。&lt;/p&gt;
&lt;p&gt;每个请求或回应的所有数据包，称为一个数据流（Stream）。&lt;/p&gt;
&lt;p&gt;每个数据流都标记着一个独一无二的编号，其中规定客户端发出的数据流编号为奇数， 服务器发出的数据流编号为偶数&lt;/p&gt;
&lt;p&gt;客户端还可以指定数据流的优先级。优先级高的请求，服务器就先响应该请求。&lt;/p&gt;








  











&lt;figure id=&#34;figure-http112-链接发起对比&#34;&gt;


  &lt;a data-fancybox=&#34;&#34; href=&#34;https://w3xse7en.github.io/media/web/http/http1.1_http2.0_connect.jpg&#34; data-caption=&#34;Http/1.1/2 链接发起对比&#34;&gt;


  &lt;img src=&#34;https://w3xse7en.github.io/media/web/http/http1.1_http2.0_connect.jpg&#34; alt=&#34;&#34;  &gt;
&lt;/a&gt;


  
  
  &lt;figcaption&gt;
    Http/1.1/2 链接发起对比
  &lt;/figcaption&gt;


&lt;/figure&gt;

&lt;br&gt;
&lt;h3 id=&#34;二进制分帧&#34;&gt;二进制分帧&lt;/h3&gt;
&lt;p&gt;在不改动 HTTP/1.x 的语义、方法、状态码、URI 以及首部字段&amp;hellip;的情况下, HTTP/2 是如何做到「突破 HTTP/1.1 的性能限制，改进传输性能，实现低延迟和高吞吐量」的 ?&lt;/p&gt;
&lt;p&gt;关键之一就是在 应用层(HTTP/2)和传输层(TCP or UDP)之间增加一个二进制分帧层。&lt;/p&gt;








  











&lt;figure id=&#34;figure-http2二进制分帧层&#34;&gt;


  &lt;a data-fancybox=&#34;&#34; href=&#34;https://w3xse7en.github.io/media/web/http/http2.0_binary.jpg&#34; data-caption=&#34;Http/2二进制分帧层&#34;&gt;


  &lt;img src=&#34;https://w3xse7en.github.io/media/web/http/http2.0_binary.jpg&#34; alt=&#34;&#34;  &gt;
&lt;/a&gt;


  
  
  &lt;figcaption&gt;
    Http/2二进制分帧层
  &lt;/figcaption&gt;


&lt;/figure&gt;

&lt;p&gt;在二进制分帧层中，HTTP/2 会将所有传输的信息分割为更小的消息和帧（frame），并对它们采用二进制格式的编码，
其中 HTTP1.x 的首部信息会被封装到 HEADER frame，而相应的 Request Body 则封装到 DATA frame 里面。&lt;/p&gt;








  











&lt;figure &gt;


  &lt;a data-fancybox=&#34;&#34; href=&#34;https://w3xse7en.github.io/media/web/http/http2.0_stream.jpg&#34; &gt;


  &lt;img src=&#34;https://w3xse7en.github.io/media/web/http/http2.0_stream.jpg&#34; alt=&#34;&#34;  &gt;
&lt;/a&gt;



&lt;/figure&gt;

&lt;h3 id=&#34;首部压缩header-compression&#34;&gt;首部压缩（Header Compression）&lt;/h3&gt;
&lt;p&gt;HTTP/1.1并不支持 HTTP 首部压缩，为此 SPDY 和 HTTP/2 应运而生， SPDY 使用的是通用的DEFLATE 算法，而 HTTP/2 则使用了专门为首部压缩而设计的 HPACK 算法。&lt;/p&gt;
&lt;p&gt;客户端和服务器两端建立“字典”，用索引号表示重复的字符串，还采用哈夫曼编码来压缩整数和字符串，可以达到 50%~90% 的高压缩率。&lt;/p&gt;
&lt;p&gt;具体来说：&lt;/p&gt;
&lt;p&gt;在客户端和服务器端使用“首部表”来跟踪和存储之前发送的键 - 值对，对于相同的数据，不再通过每次请求和响应发送。&lt;/p&gt;
&lt;p&gt;首部表在 HTTP/2 的连接存续期内始终存在，由客户端和服务器共同渐进地更新。&lt;/p&gt;
&lt;p&gt;每个新的首部键 - 值对要么被追加到当前表的末尾，要么替换表中之前的值。&lt;/p&gt;
&lt;p&gt;例如下图中的两个请求， 请求一发送了所有的头部字段，第二个请求则只需要发送差异数据，这样可以减少冗余数据，降低开销 。&lt;/p&gt;








  











&lt;figure &gt;


  &lt;a data-fancybox=&#34;&#34; href=&#34;https://w3xse7en.github.io/media/web/http/http2.0_header_compression.png&#34; &gt;


  &lt;img src=&#34;https://w3xse7en.github.io/media/web/http/http2.0_header_compression.png&#34; alt=&#34;&#34;  &gt;
&lt;/a&gt;



&lt;/figure&gt;

&lt;h3 id=&#34;server-push&#34;&gt;Server Push&lt;/h3&gt;
&lt;p&gt;Server Push 即服务端能通过 push 的方式将客户端需要的内容预先推送过去&lt;/p&gt;








  











&lt;figure &gt;


  &lt;a data-fancybox=&#34;&#34; href=&#34;https://w3xse7en.github.io/media/web/http/http2.0_service_push.png&#34; &gt;


  &lt;img src=&#34;https://w3xse7en.github.io/media/web/http/http2.0_service_push.png&#34; alt=&#34;&#34;  &gt;
&lt;/a&gt;



&lt;/figure&gt;

&lt;p&gt;服务端可以主动推送，客户端也有权利选择是否接收。&lt;/p&gt;
&lt;p&gt;如果服务端推送的资源已经被浏览器缓存过，浏览器可以通过发送 RST_STREAM 帧来拒收。&lt;/p&gt;
&lt;p&gt;主动推送也遵守同源策略，换句话说，服务器不能随便将第三方资源推送给客户端，而必须是经过双方确认才行。&lt;/p&gt;
&lt;h3 id=&#34;总结&#34;&gt;总结：&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;同域名下所有通信都在单个连接上完成。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;单个连接可以承载任意数量的双向数据流。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;数据流以消息的形式发送，而消息又由一个或多个帧组成，多个帧之间可以乱序发送，因为根据帧首部的流标识可以重新组装。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;同个域名只需要占用一个 TCP 连接，使用一个连接并行发送多个请求和响应, 这样整个页面资源的下载过程只需要一次慢启动，同时也避免了多个 TCP 连接竞争带宽所带来的问题。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;并行交错地发送多个请求 / 响应，请求 / 响应之间互不影响。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;在 HTTP/2 中，每个请求都可以带一个 31bit 的优先值，0 表示最高优先级， 数值越大优先级越低。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;br&gt;
&lt;h2 id=&#34;http3&#34;&gt;Http/3&lt;/h2&gt;
&lt;p&gt;HTTP/2 使用了多路复用，一般来说同一域名下只需要使用一个 TCP 连接。但当这个连接中出现了丢包的情况，那就会导致 HTTP/2 的表现情况反倒不如 HTTP/1 了。&lt;/p&gt;
&lt;p&gt;因为在出现丢包的情况下，整个 TCP 都要开始等待重传，也就导致了后面的所有数据都被阻塞了。但是对于 HTTP/1.1 来说，可以开启多个 TCP 连接，出现这种情况反到只会影响其中一个连接，剩余的 TCP 连接还可以正常传输数据。&lt;/p&gt;








  











&lt;figure &gt;


  &lt;a data-fancybox=&#34;&#34; href=&#34;https://w3xse7en.github.io/media/web/http/http2.0_block.png&#34; &gt;


  &lt;img src=&#34;https://w3xse7en.github.io/media/web/http/http2.0_block.png&#34; alt=&#34;&#34;  &gt;
&lt;/a&gt;



&lt;/figure&gt;

&lt;p&gt;那么可能就会有人考虑到去修改 TCP 协议，其实这已经是一件不可能完成的任务了。因为 TCP 存在的时间实在太长，已经充斥在各种设备中，并且这个协议是由操作系统实现的，更新起来不大现实。&lt;/p&gt;
&lt;p&gt;基于这个原因，Google 就更起炉灶搞了一个基于 UDP 协议的 QUIC 协议，并且使用在了 HTTP/3 上，HTTP/3 之前名为 HTTP-over-QUIC，从这个名字中我们也可以发现，HTTP/3 最大的改造就是使用了 QUIC。&lt;/p&gt;
&lt;h3 id=&#34;0-rtt&#34;&gt;0-RTT&lt;/h3&gt;
&lt;p&gt;通过使用类似 TCP 快速打开的技术，缓存当前会话的上下文，在下次恢复会话的时候，只需要将之前的缓存传递给服务端验证通过就可以进行传输了。&lt;strong&gt;0RTT 连接可以说是 QUIC 相比 HTTP2 最大的性能优势。&lt;/strong&gt;&lt;/p&gt;
&lt;br&gt;
&lt;h2 id=&#34;qa&#34;&gt;Q&amp;amp;A&lt;/h2&gt;
&lt;h3 id=&#34;与服务器建立了一个tcp连接后是否会在一个-http-请求完成后断开什么情况下会断开&#34;&gt;与服务器建立了一个TCP连接后是否会在一个 HTTP 请求完成后断开？什么情况下会断开？&lt;/h3&gt;
&lt;p&gt;HTTP/1.1 把 Connection 头写进标准，并且默认开启持久连接，除非请求中写明 Connection: close，那么浏览器和服务器之间是会维持一段时间的 TCP 连接，不会一个请求结束就断掉。&lt;/p&gt;
&lt;h3 id=&#34;一个tcp连接中http请求发送可以一起发送么比如一起发三个请求再三个响应一起接收&#34;&gt;一个TCP连接中HTTP请求发送可以一起发送么（比如一起发三个请求，再三个响应一起接收）&lt;/h3&gt;
&lt;p&gt;HTTP/1.1 存在一个问题，单个 TCP 连接在同一时刻只能处理一个请求，意思是说：两个请求的生命周期不能重叠，任意两个 HTTP 请求从开始到结束的时间在同一个 TCP 连接里不能重叠。&lt;/p&gt;
&lt;p&gt;虽然 HTTP/1.1 规范中规定了 Pipelining 来试图解决这个问题，但是这个功能在浏览器中默认是关闭的。&lt;/p&gt;
&lt;p&gt;RFC 2616 Pipelining&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A client that supports persistent connections MAY &amp;ldquo;pipeline&amp;rdquo; its requests (i.e., send multiple requests without waiting for each response).
A server MUST send its responses to those requests in the same order that the requests were received.&lt;/p&gt;
&lt;p&gt;一个支持持久连接的客户端可以在一个连接中发送多个请求（不需要等待任意请求的响应）。收到请求的服务器必须按照请求收到的顺序发送响应。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;为什么有的时候刷新页面不需要重新建立-ssl-连接&#34;&gt;为什么有的时候刷新页面不需要重新建立 SSL 连接？&lt;/h3&gt;
&lt;p&gt;TCP 连接有的时候会被浏览器和服务端维持一段时间。TCP 不需要重新建立，SSL 自然也会用之前的。&lt;/p&gt;
&lt;h3 id=&#34;浏览器对同一-host-建立-tcp-连接到数量有没有限制&#34;&gt;浏览器对同一 Host 建立 TCP 连接到数量有没有限制？&lt;/h3&gt;
&lt;p&gt;Chrome 最多允许对同一个 Host 建立六个 TCP 连接。不同的浏览器有一些区别。&lt;/p&gt;
&lt;br&gt;
&lt;hr&gt;
&lt;h2 id=&#34;参考&#34;&gt;参考&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://mp.weixin.qq.com/s?__biz=MzUxODAzNDg4NQ==&amp;amp;mid=2247483971&amp;amp;idx=1&amp;amp;sn=8f2d5dae3d95efc446061b352c8e9961&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;硬核！30 张图解 HTTP 常见的面试题&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.zhihu.com/question/34074946/answer/75364178&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;HTTP/2 相比 1.0 有哪些重大改进？&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://blog.fundebug.com/2019/03/07/understand-http2-and-http3/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;一文读懂 HTTP/2 及 HTTP/3 特性&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://zhuanlan.zhihu.com/p/61423830&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;你猜一个 TCP 连接上面能发多少个 HTTP 请求&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;
&lt;hr&gt;
</description>
    </item>
    
    <item>
      <title>hyper-v 安装 centos</title>
      <link>https://w3xse7en.github.io/k8s_local/10_hyper-v_install_centos/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>https://w3xse7en.github.io/k8s_local/10_hyper-v_install_centos/</guid>
      <description>&lt;hr&gt;
&lt;h2 id=&#34;centos&#34;&gt;Centos&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://mirrors.tuna.tsinghua.edu.cn/centos/7.8.2003/isos/x86_64/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;清华大学centos7.8.2003镜像&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;








  











&lt;figure id=&#34;figure-清华大学镜像站&#34;&gt;


  &lt;a data-fancybox=&#34;&#34; href=&#34;https://w3xse7en.github.io/media/k8s_local/1-tsinghua-mirror.png&#34; data-caption=&#34;清华大学镜像站&#34;&gt;


  &lt;img src=&#34;https://w3xse7en.github.io/media/k8s_local/1-tsinghua-mirror.png&#34; alt=&#34;&#34;  &gt;
&lt;/a&gt;


  
  
  &lt;figcaption&gt;
    清华大学镜像站
  &lt;/figcaption&gt;


&lt;/figure&gt;

&lt;h2 id=&#34;虚拟机&#34;&gt;虚拟机&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;宿主机i5-6200u 2c16g&lt;/li&gt;
&lt;li&gt;每台机器均为2c4g配置&lt;/li&gt;
&lt;/ul&gt;








  











&lt;figure id=&#34;figure-hyper-v&#34;&gt;


  &lt;a data-fancybox=&#34;&#34; href=&#34;https://w3xse7en.github.io/media/k8s_local/2-hyper-v.png&#34; data-caption=&#34;hyper-v&#34;&gt;


  &lt;img src=&#34;https://w3xse7en.github.io/media/k8s_local/2-hyper-v.png&#34; alt=&#34;&#34;  &gt;
&lt;/a&gt;


  
  
  &lt;figcaption&gt;
    hyper-v
  &lt;/figcaption&gt;


&lt;/figure&gt;

&lt;h2 id=&#34;网络&#34;&gt;网络&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;安装完的centos是不能上网的&lt;/li&gt;
&lt;li&gt;此处使用桥接模式进行网络连接&lt;/li&gt;
&lt;li&gt;使用桥接模式 使node和host处在同一网段更方便其他设备访问&lt;/li&gt;
&lt;/ul&gt;








  











&lt;figure &gt;


  &lt;a data-fancybox=&#34;&#34; href=&#34;https://w3xse7en.github.io/media/k8s_local/3-net-bridge.png&#34; &gt;


  &lt;img src=&#34;https://w3xse7en.github.io/media/k8s_local/3-net-bridge.png&#34; alt=&#34;&#34;  &gt;
&lt;/a&gt;



&lt;/figure&gt;









  











&lt;figure id=&#34;figure-桥接详情&#34;&gt;


  &lt;a data-fancybox=&#34;&#34; href=&#34;https://w3xse7en.github.io/media/k8s_local/4-bridge-info.png&#34; data-caption=&#34;桥接详情&#34;&gt;


  &lt;img src=&#34;https://w3xse7en.github.io/media/k8s_local/4-bridge-info.png&#34; alt=&#34;&#34;  &gt;
&lt;/a&gt;


  
  
  &lt;figcaption&gt;
    桥接详情
  &lt;/figcaption&gt;


&lt;/figure&gt;

&lt;ul&gt;
&lt;li&gt;配置Centos 静态ip地址&lt;/li&gt;
&lt;li&gt;网段参考网桥ip&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;vi /etc/sysconfig/network-scripts/ifcfg-eth0

ONBOOT=yes
BOOTPROTO=static
IPADDR=192.168.1.201
NETMASK=255.255.255.0
GATEWAY=192.168.1.1
DNS1=192.168.1.1

service network restart
&lt;/code&gt;&lt;/pre&gt;








  











&lt;figure id=&#34;figure-网络拓扑图&#34;&gt;


  &lt;a data-fancybox=&#34;&#34; href=&#34;https://w3xse7en.github.io/media/k8s_local/5-net.png&#34; data-caption=&#34;网络拓扑图&#34;&gt;


  &lt;img src=&#34;https://w3xse7en.github.io/media/k8s_local/5-net.png&#34; alt=&#34;&#34;  &gt;
&lt;/a&gt;


  
  
  &lt;figcaption&gt;
    网络拓扑图
  &lt;/figcaption&gt;


&lt;/figure&gt;

</description>
    </item>
    
    <item>
      <title>kubespray 安装 k8s</title>
      <link>https://w3xse7en.github.io/k8s_local/20_install_k8s/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>https://w3xse7en.github.io/k8s_local/20_install_k8s/</guid>
      <description>&lt;hr&gt;
&lt;h2 id=&#34;参考&#34;&gt;参考&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://kubespray.io/#/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;kubespray&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=8Jh4yZQOVZU&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;[ Kube 65.1 ] Kubespray - Kubernetes cluster provisioning&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=CJ5G4GpqDy0&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Deploying kubernetes using Kubespray&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.d-nix.nl/2019/05/installing-a-multinode-kubernetes-cluster-using-kubespray/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;INSTALLING A MULTINODE KUBERNETES CLUSTER USING KUBESPRAY&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/kubernetes-sigs/kubespray/issues/6207&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;it&amp;rsquo;s really really really hard run kubespray in china! #6207&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.bookstack.cn/read/huweihuang-kubernetes-notes/setup-install-k8s-by-kubespray.md&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;使用kubespray安装kubernetes&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.bboy.app/2020/07/20/%E4%BD%BF%E7%94%A8kubespray%E6%90%AD%E5%BB%BA%E7%94%9F%E4%BA%A7%E7%BA%A7%E9%AB%98%E5%8F%AF%E7%94%A8%E9%9B%86%E7%BE%A4/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;使用kubespray搭建生产级高可用集群&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://segmentfault.com/a/1190000015186299&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;使用Kubespray安装k8s集群&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;docker/kubernetes%e5%9b%bd%e5%86%85%e6%ba%90/%e9%95%9c%e5%83%8f%e6%ba%90%e8%a7%a3%e5%86%b3%e6%96%b9%e5%bc%8f&#34;&gt;docker/kubernetes国内源/镜像源解决方式&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;
&lt;hr&gt;
&lt;h2 id=&#34;配置免密码登录&#34;&gt;配置免密码登录&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;ssh-keygen -t rsa
ssh-copy-id root@192.168.1.200
ssh-copy-id root@192.168.1.201
ssh-copy-id root@192.168.1.202
&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;
&lt;h2 id=&#34;下载kubespray&#34;&gt;下载kubespray&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;# master分支还在更新中，此处使用当前最新release的版本v2.14.1
git clone --single-branch -b v2.14.1 https://github.com/kubernetes-sigs/kubespray.git
&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;
&lt;h2 id=&#34;安装python3-pip&#34;&gt;安装python3-pip&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;yum install python3-pip
&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;
&lt;h2 id=&#34;安装kubespray依赖&#34;&gt;安装kubespray依赖&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;pip3 install -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple/
&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;
&lt;h2 id=&#34;使用ansible对每台机器进行批处理&#34;&gt;使用ansible对每台机器进行批处理&lt;/h2&gt;
&lt;p&gt;配置ansible访问k8s集群hosts&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;vi /etc/ansible/hosts
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;[k8s]

192.168.1.200 ansible_ssh_user=root
192.168.1.201 ansible_ssh_user=root
192.168.1.202 ansible_ssh_user=root
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;测试&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;ansible k8s -m ping
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;更新yum&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;ansible k8s -m shell -a &#39;yum update -y&#39;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;安装ntp服务，统一集群时间&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;ansible k8s -m shell -a &#39;yum install -y ntp &amp;amp;&amp;amp;
systemctl enable ntpd &amp;amp;&amp;amp;
systemctl start ntpd &amp;amp;&amp;amp;
timedatectl set-timezone Asia/Shanghai &amp;amp;&amp;amp;
timedatectl set-ntp yes &amp;amp;&amp;amp; 
ntpq -p&#39;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;配置ipv4转发&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;ansible k8s -m shell -a &#39;echo net.ipv4.ip_forward = 1 &amp;gt;&amp;gt; /etc/sysctl.conf &amp;amp;&amp;amp; sysctl -p&#39;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;关闭防火墙&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;ansible k8s -m shell -a &#39;systemctl stop firewalld &amp;amp;&amp;amp; systemctl disable firewalld&#39;
&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;
&lt;h2 id=&#34;生成kubespray所需配置&#34;&gt;生成kubespray所需配置&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;创建可自定义的配置&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;cp -rfp inventory/sample inventory/mycluster
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;IPS=(此处填写k8s集群的ip地址，注意&lt;code&gt;空格&lt;/code&gt;分割)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;declare -a IPS=(192.168.1.200 192.168.1.201 192.168.1.202)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;CONFIG_FILE=inventory/mycluster/hosts.yaml python3 contrib/inventory_builder/inventory.py ${IPS[@]}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;查看生成的hosts.yaml&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;cat inventory/mycluster/hosts.yaml
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;此处是 2个master 3个node 3个etcd 的高可用配置，可以根据喜好进行配置&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;all:
  hosts:
    node1:
      ansible_host: 192.168.1.200
      ip: 192.168.1.200
      access_ip: 192.168.1.200
    node2:
      ansible_host: 192.168.1.201
      ip: 192.168.1.201
      access_ip: 192.168.1.201
    node3:
      ansible_host: 192.168.1.202
      ip: 192.168.1.202
      access_ip: 192.168.1.202
  children:
    kube-master:
      hosts:
        node1:
        node2:
    kube-node:
      hosts:
        node1:
        node2:
        node3:
    etcd:
      hosts:
        node1:
        node2:
        node3:
    k8s-cluster:
      children:
        kube-master:
        kube-node:
    calico-rr:
      hosts: {}
&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;
&lt;h2 id=&#34;下载依赖&#34;&gt;下载依赖&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;方案1&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.bboy.app/2020/07/20/%E4%BD%BF%E7%94%A8kubespray%E6%90%AD%E5%BB%BA%E7%94%9F%E4%BA%A7%E7%BA%A7%E9%AB%98%E5%8F%AF%E7%94%A8%E9%9B%86%E7%BE%A4/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;将所有源换成国内镜像加速&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;方案2&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/kubernetes-sigs/kubespray/issues/6207&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;离线安装&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;方案3&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;设置代理&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;此处使用方案3&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;vi inventory/mycluster/group_vars/all/all.yml

http_proxy: &amp;quot;http://192.168.1.9:1080&amp;quot;
https_proxy: &amp;quot;http://192.168.1.9:1080&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;在阿里云环境下使用代理&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;开启自己电脑上的http proxy server&lt;/li&gt;
&lt;li&gt;使用&lt;a href=&#34;https://github.com/fatedier/frp&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;frp&lt;/a&gt;将本地电脑http proxy port 映射至公网&lt;/li&gt;
&lt;li&gt;配置http_proxy&lt;/li&gt;
&lt;li&gt;直接在阿里云内使用代理可能会被警告，因此使用frp做一层中转&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;配置docker镜像源&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;vi inventory/mycluster/group_vars/all/docker.yml
## Add other registry,example China registry mirror.
docker_registry_mirrors:
  - https://mirror.aliyuncs.com
  - https://registry.docker-cn.com
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;提前下载k8s所需依赖，避免安装时报错&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;ansible-playbook -i inventory/mycluster/hosts.yaml  --become --become-user=root cluster.yml --tags container_engine

ansible-playbook -i inventory/mycluster/hosts.yaml  --become --become-user=root cluster.yml --tags download
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;若遇到报错，可尝试将http_proxy注释掉重新下载，重复试几次后一般都能下完
&lt;ul&gt;
&lt;li&gt;阿里云apt, yum等源设置的是内网地址，需要直连才可以访问&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;vi inventory/mycluster/group_vars/all/all.yml

# http_proxy: &amp;quot;http://192.168.1.9:1080&amp;quot;
# https_proxy: &amp;quot;http://192.168.1.9:1080&amp;quot;
&lt;/code&gt;&lt;/pre&gt;








  











&lt;figure id=&#34;figure-frp-v2ray-代理下载&#34;&gt;


  &lt;a data-fancybox=&#34;&#34; href=&#34;https://w3xse7en.github.io/media/k8s_local/frp-v2ray-proxy.png&#34; data-caption=&#34;frp-v2ray-代理下载&#34;&gt;


  &lt;img src=&#34;https://w3xse7en.github.io/media/k8s_local/frp-v2ray-proxy.png&#34; alt=&#34;&#34;  &gt;
&lt;/a&gt;


  
  
  &lt;figcaption&gt;
    frp-v2ray-代理下载
  &lt;/figcaption&gt;


&lt;/figure&gt;

&lt;div class=&#34;alert alert-warning&#34;&gt;
  &lt;div&gt;
    ubuntu 系统安装完毕后，请删除&lt;code&gt;/etc/apt/apt.conf&lt;/code&gt;里的代理配置
  &lt;/div&gt;
&lt;/div&gt;
&lt;br/&gt;
&lt;h2 id=&#34;可选开启helm&#34;&gt;(可选)开启helm&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;vi inventory/mycluster/group_vars/k8s-cluster/addons.yml
# Helm deployment
helm_enabled: true
&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;
&lt;h2 id=&#34;安装&#34;&gt;安装&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;ansible-playbook -i inventory/mycluster/hosts.yaml  --become --become-user=root cluster.yml
&lt;/code&gt;&lt;/pre&gt;








  











&lt;figure id=&#34;figure-安装花费21分钟&#34;&gt;


  &lt;a data-fancybox=&#34;&#34; href=&#34;https://w3xse7en.github.io/media/k8s_local/6-install.png&#34; data-caption=&#34;安装花费21分钟&#34;&gt;


  &lt;img src=&#34;https://w3xse7en.github.io/media/k8s_local/6-install.png&#34; alt=&#34;&#34;  &gt;
&lt;/a&gt;


  
  
  &lt;figcaption&gt;
    安装花费21分钟
  &lt;/figcaption&gt;


&lt;/figure&gt;

&lt;br/&gt;
&lt;h2 id=&#34;验证&#34;&gt;验证&lt;/h2&gt;








  











&lt;figure id=&#34;figure-k8s-v1189安装成功&#34;&gt;


  &lt;a data-fancybox=&#34;&#34; href=&#34;https://w3xse7en.github.io/media/k8s_local/7-check.png&#34; data-caption=&#34;k8s v1.18.9安装成功&#34;&gt;


  &lt;img src=&#34;https://w3xse7en.github.io/media/k8s_local/7-check.png&#34; alt=&#34;&#34;  &gt;
&lt;/a&gt;


  
  
  &lt;figcaption&gt;
    k8s v1.18.9安装成功
  &lt;/figcaption&gt;


&lt;/figure&gt;

&lt;br/&gt;
&lt;h2 id=&#34;重置&#34;&gt;重置&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;若在安装过程中遇到问题可用以下命令重置&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;ansible-playbook -i inventory/mycluster/hosts.yaml reset.yml
&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;
&lt;h2 id=&#34;dashboard&#34;&gt;Dashboard&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Kubespray并没有替你创建用户，所以需要创建用户，然后获得Token，使用Token登录。&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;
&lt;ul&gt;
&lt;li&gt;添加admin-user用户&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;cat &amp;lt;&amp;lt;EOF | kubectl apply -f -
apiVersion: v1
kind: ServiceAccount
metadata:
  name: admin-user
  namespace: kube-system

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: admin-user
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
- kind: ServiceAccount
  name: admin-user
  namespace: kube-system
EOF
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;获取admin-user的token&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;kubectl -n kube-system describe secret $(kubectl -n kube-system get secret | grep admin-user | awk &#39;{print $1}&#39;)
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;访问dashboard&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&#34;language-http&#34;&gt;https://192.168.1.200:6443/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy/#!/login
&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;
&lt;div class=&#34;alert alert-warning&#34;&gt;
  &lt;div&gt;
    &lt;p&gt;&lt;code&gt;services &amp;quot;https:kubernetes-dashboard:&amp;quot; is forbidden: User &amp;quot;system:anonymous&amp;quot; cannot get services/proxy in the namespace &amp;quot;kube-system&amp;quot;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;遇到此问题请查看&lt;a href=&#34;https://w3xse7en.github.io/k8s_local/999_qa/#system:anonymous-%E6%97%A0%E6%B3%95%E8%AE%BF%E9%97%AE-k8s-dashboard&#34;&gt;system:anonymous 无法访问 k8s dashboard&lt;/a&gt;&lt;/p&gt;
  &lt;/div&gt;
&lt;/div&gt;
</description>
    </item>
    
    <item>
      <title>添加/删除节点</title>
      <link>https://w3xse7en.github.io/k8s_local/30_add_remove_node/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>https://w3xse7en.github.io/k8s_local/30_add_remove_node/</guid>
      <description>&lt;hr&gt;
&lt;h2 id=&#34;参考&#34;&gt;参考&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/kubernetes-sigs/kubespray/blob/master/docs/getting-started.md&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;kubespray&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=duAvUhNydh4&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;[ Kube 65.3 ] Kubespray - Adding &amp;amp; Removing Kubernetes nodes&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;
&lt;hr&gt;
&lt;h2 id=&#34;添加节点&#34;&gt;添加节点&lt;/h2&gt;
&lt;h3 id=&#34;配置ansible访问新节点1921681203&#34;&gt;配置ansible访问新节点(192.168.1.203)&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;vi /etc/ansible/hosts
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;[k8s]

192.168.1.200 ansible_ssh_user=root
192.168.1.201 ansible_ssh_user=root
192.168.1.202 ansible_ssh_user=root
192.168.1.203 ansible_ssh_user=root
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;测试&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;ansible k8s -m ping
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://w3xse7en.github.io/k8s_local/20_install_k8s/#使用ansible对每台机器进行批处理&#34;&gt;更新新节点功能(时区,防火墙等)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;新增node4&#34;&gt;新增node4&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;vi inventory/mycluster/hosts.yaml
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;all:
  hosts:
    node1:
      ansible_host: 192.168.1.200
      ip: 192.168.1.200
      access_ip: 192.168.1.200
    node2:
      ansible_host: 192.168.1.201
      ip: 192.168.1.201
      access_ip: 192.168.1.201
    node3:
      ansible_host: 192.168.1.202
      ip: 192.168.1.202
      access_ip: 192.168.1.202
    node4:
      ansible_host: 192.168.1.203
      ip: 192.168.1.203
      access_ip: 192.168.1.203
  children:
    kube-master:
      hosts:
        node1:
        node2:
    kube-node:
      hosts:
        node1:
        node2:
        node3:
        node4:
    etcd:
      hosts:
        node1:
        node2:
        node3:
    k8s-cluster:
      children:
        kube-master:
        kube-node:
    calico-rr:
      hosts: {}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&#34;下载依赖&#34;&gt;下载依赖&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;假设&lt;code&gt;node1&lt;/code&gt;与&lt;code&gt;node4&lt;/code&gt;为同系统
将&lt;code&gt;node1&lt;/code&gt; &lt;code&gt;/tmp/releases&lt;/code&gt; &lt;code&gt;copy&lt;/code&gt;至&lt;code&gt;node4&lt;/code&gt; &lt;code&gt;/tmp/releases&lt;/code&gt;即可避免&lt;code&gt;node4&lt;/code&gt;再次下载k8s依赖组件&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;scp /tmp/releases root@192.168.1.203:/tmp/releases
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;确定依赖已完善&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;ansible-playbook -i inventory/mycluster/hosts.yaml  --become --become-user=root cluster.yml --tags container_engine

ansible-playbook -i inventory/mycluster/hosts.yaml  --become --become-user=root cluster.yml --tags download
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&#34;添加节点-1&#34;&gt;添加节点&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;ansible-playbook -i inventory/mycluster/hosts.yaml scale.yml
&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;
&lt;hr&gt;
&lt;h2 id=&#34;移除节点&#34;&gt;移除节点&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;ansible-playbook -i inventory/mycluster/hosts.yaml remove-node.yml --extra-vars &amp;quot;node=node4&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
</description>
    </item>
    
    <item>
      <title>创建 service account</title>
      <link>https://w3xse7en.github.io/k8s_local/40_service_account/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>https://w3xse7en.github.io/k8s_local/40_service_account/</guid>
      <description>&lt;hr&gt;
&lt;h2 id=&#34;参考&#34;&gt;参考&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://www.auroria.io/kubernetes-ci-cd-service-account-setup/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Setting up a Kubernetes CI/CD Service Account&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;
&lt;hr&gt;
&lt;ul&gt;
&lt;li&gt;场景&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;权限划分，&lt;code&gt;cicd&lt;/code&gt;限定&lt;code&gt;namespace=dev&lt;/code&gt;使用&lt;code&gt;kubectl apply -f&lt;/code&gt;部署服务&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;创建 &lt;code&gt;namespace&lt;/code&gt; &lt;code&gt;dev&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;cat &amp;lt;&amp;lt;EOF | kubectl apply -f -
{
  &amp;quot;apiVersion&amp;quot;: &amp;quot;v1&amp;quot;,
  &amp;quot;kind&amp;quot;: &amp;quot;Namespace&amp;quot;,
  &amp;quot;metadata&amp;quot;: {
    &amp;quot;name&amp;quot;: &amp;quot;dev&amp;quot;,
    &amp;quot;labels&amp;quot;: {
      &amp;quot;name&amp;quot;: &amp;quot;dev&amp;quot;
    }
  }
}
EOF
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&#34;创建-service-account-cicd-user&#34;&gt;创建 &lt;code&gt;service account&lt;/code&gt; &lt;code&gt;cicd-user&lt;/code&gt;&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;# 该脚本依赖 jq 组件
yum install jq -y

# [可修改]
SERVICE_ACCOUNT_NAME=&amp;quot;cicd-user&amp;quot;

# [可修改]
NAMESPACE=&amp;quot;dev&amp;quot;

# [可修改]
TARGET_FOLDER=&amp;quot;/home/k8s/kube&amp;quot;

kubectl create sa &amp;quot;${SERVICE_ACCOUNT_NAME}&amp;quot; --namespace &amp;quot;${NAMESPACE}&amp;quot;

mkdir -p &amp;quot;${TARGET_FOLDER}&amp;quot;

SECRET_NAME=$(kubectl get sa &amp;quot;${SERVICE_ACCOUNT_NAME}&amp;quot; --namespace=&amp;quot;${NAMESPACE}&amp;quot; -o json | jq -r .secrets[].name)

kubectl get secret --namespace &amp;quot;${NAMESPACE}&amp;quot; &amp;quot;${SECRET_NAME}&amp;quot; -o json | jq -r &#39;.data[&amp;quot;ca.crt&amp;quot;]&#39; | base64 --decode &amp;gt; &amp;quot;${TARGET_FOLDER}/ca.crt&amp;quot;

USER_TOKEN=$(kubectl get secret --namespace &amp;quot;${NAMESPACE}&amp;quot; &amp;quot;${SECRET_NAME}&amp;quot; -o json | jq -r &#39;.data[&amp;quot;token&amp;quot;]&#39; | base64 --decode)

KUBECFG_FILE_NAME=&amp;quot;${TARGET_FOLDER}/k8s-${SERVICE_ACCOUNT_NAME}-${NAMESPACE}-conf&amp;quot;

context=$(kubectl config current-context)

CLUSTER_NAME=$(kubectl config get-contexts &amp;quot;$context&amp;quot; | awk &#39;{print $3}&#39; | tail -n 1)

ENDPOINT=$(kubectl config view \
-o jsonpath=&amp;quot;{.clusters[?(@.name == \&amp;quot;${CLUSTER_NAME}\&amp;quot;)].cluster.server}&amp;quot;)

kubectl config set-cluster &amp;quot;${CLUSTER_NAME}&amp;quot; \
--kubeconfig=&amp;quot;${KUBECFG_FILE_NAME}&amp;quot; \
--server=&amp;quot;${ENDPOINT}&amp;quot; \
--certificate-authority=&amp;quot;${TARGET_FOLDER}/ca.crt&amp;quot; \
--embed-certs=true

kubectl config set-credentials \
&amp;quot;${SERVICE_ACCOUNT_NAME}-${NAMESPACE}-${CLUSTER_NAME}&amp;quot; \
--kubeconfig=&amp;quot;${KUBECFG_FILE_NAME}&amp;quot; \
--token=&amp;quot;${USER_TOKEN}&amp;quot;


kubectl config set-context \
&amp;quot;${SERVICE_ACCOUNT_NAME}-${NAMESPACE}-${CLUSTER_NAME}&amp;quot; \
--kubeconfig=&amp;quot;${KUBECFG_FILE_NAME}&amp;quot; \
--cluster=&amp;quot;${CLUSTER_NAME}&amp;quot; \
--user=&amp;quot;${SERVICE_ACCOUNT_NAME}-${NAMESPACE}-${CLUSTER_NAME}&amp;quot; \
--namespace=&amp;quot;${NAMESPACE}&amp;quot;

kubectl config use-context &amp;quot;${SERVICE_ACCOUNT_NAME}-${NAMESPACE}-${CLUSTER_NAME}&amp;quot; \
--kubeconfig=&amp;quot;${KUBECFG_FILE_NAME}&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&#34;为-cicd-user-配置权限&#34;&gt;为 &lt;code&gt;cicd-user&lt;/code&gt; 配置权限&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;cat &amp;lt;&amp;lt;EOF | kubectl apply -f -
kind: Role
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: &amp;quot;${SERVICE_ACCOUNT_NAME}-role&amp;quot;
  namespace: &amp;quot;${NAMESPACE}&amp;quot;
rules:
- apiGroups:
  - &amp;quot;&amp;quot;
  - apps
  - extensions
  resources:
  - &#39;*&#39;
  verbs:
  - &#39;*&#39;
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: &amp;quot;${SERVICE_ACCOUNT_NAME}-role&amp;quot;
  namespace: &amp;quot;${NAMESPACE}&amp;quot;
subjects:
  - kind: ServiceAccount
    name: &amp;quot;${SERVICE_ACCOUNT_NAME}&amp;quot;
roleRef:
  kind: Role
  name: &amp;quot;${SERVICE_ACCOUNT_NAME}-role&amp;quot;
  apiGroup: rbac.authorization.k8s.io
EOF
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&#34;其他实例使用-cicd-user&#34;&gt;其他实例使用 &lt;code&gt;cicd-user&lt;/code&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;将&lt;code&gt;${TARGET_FOLDER}&lt;/code&gt;生成的&lt;code&gt;k8s-cicd-user-dev-conf&lt;/code&gt;文件&lt;code&gt;copy&lt;/code&gt;至其他实例&lt;code&gt;~/.kube&lt;/code&gt;路径下改名为&lt;code&gt;config&lt;/code&gt;使用&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;alert alert-warning&#34;&gt;
  &lt;div&gt;
    &lt;p&gt;注意查看&lt;code&gt;k8s-cicd-user-dev-conf&lt;/code&gt;里&lt;code&gt;cluster server&lt;/code&gt;地址是否合法&lt;/p&gt;
&lt;p&gt;遇到证书ip问题请查看&lt;a href=&#34;https://w3xse7en.github.io/k8s_local/999_qa/#kubectl-get-nodes-%E8%AF%81%E4%B9%A6ip%E9%97%AE%E9%A2%98&#34;&gt;kubectl get pods 证书ip问题&lt;/a&gt;&lt;/p&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;br/&gt;
&lt;hr&gt;
</description>
    </item>
    
    <item>
      <title>solve k8s problem</title>
      <link>https://w3xse7en.github.io/k8s_local/999_qa/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>https://w3xse7en.github.io/k8s_local/999_qa/</guid>
      <description>&lt;hr&gt;
&lt;h2 id=&#34;介绍&#34;&gt;介绍&lt;/h2&gt;
&lt;p&gt;记录在使用k8s过程中所遇到的问题，以及解决方案&lt;/p&gt;
&lt;br/&gt;
&lt;hr&gt;
&lt;h2 id=&#34;kubectl-get-nodes-证书ip问题&#34;&gt;kubectl get nodes 证书ip问题&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;Unable to connect to the server: x509: certificate is valid for 10.233.0.1, 172.19.157.57, 172.19.157.57, 10.233.0.1, 127.0.0.1, 172.19.157.57, 172.19.252.199, not 47.xxx.xx.x
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;场景&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;阿里云安装，全部使用私网地址，创建&lt;code&gt;service account&lt;/code&gt;后使用&lt;code&gt;kubectl&lt;/code&gt;从公网访问报错&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;解决方案
&lt;code&gt;supplementary_addresses_in_ssl_keys&lt;/code&gt; 添加公网地址&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;vi inventory/mycluster/group_vars/k8s-cluster/k8s-cluster.yml

supplementary_addresses_in_ssl_keys: [47.xxx.xx.x]

# 重新安装部署，使配置生效
ansible-playbook -i inventory/mycluster/hosts.yaml  --become --become-user=root cluster.yml
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;参考&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&#34;https://stackoverflow.com/a/55065519&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Invalid x509 certificate for kubernetes master&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;经验证&lt;code&gt;kubespray v2.14.1&lt;/code&gt;不需要执行&lt;code&gt;rm /etc/kubernetes/pki/apiserver.*&lt;/code&gt;&lt;/p&gt;
&lt;br/&gt;
&lt;hr&gt;
&lt;h2 id=&#34;systemanonymous-无法访问-k8s-dashboard&#34;&gt;system:anonymous 无法访问 k8s dashboard&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;`services &amp;quot;https:kubernetes-dashboard:&amp;quot; is forbidden: User &amp;quot;system:anonymous&amp;quot; cannot get services/proxy in the namespace &amp;quot;kube-system&amp;quot;`
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;解决方案
添加&lt;code&gt;system:anonymous&lt;/code&gt;的访问权限&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;cat &amp;lt;&amp;lt;EOF | kubectl apply -f -
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: kubernetes-dashboard-anonymous
rules:
- apiGroups: [&amp;quot;&amp;quot;]
  resources: [&amp;quot;services/proxy&amp;quot;]
  resourceNames: [&amp;quot;https:kubernetes-dashboard:&amp;quot;]
  verbs: [&amp;quot;get&amp;quot;, &amp;quot;list&amp;quot;, &amp;quot;watch&amp;quot;, &amp;quot;create&amp;quot;, &amp;quot;update&amp;quot;, &amp;quot;patch&amp;quot;, &amp;quot;delete&amp;quot;]
- nonResourceURLs: [&amp;quot;/ui&amp;quot;, &amp;quot;/ui/*&amp;quot;, &amp;quot;/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy/*&amp;quot;]
  verbs: [&amp;quot;get&amp;quot;, &amp;quot;list&amp;quot;, &amp;quot;watch&amp;quot;, &amp;quot;create&amp;quot;, &amp;quot;update&amp;quot;, &amp;quot;patch&amp;quot;, &amp;quot;delete&amp;quot;]

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: kubernetes-dashboard-anonymous
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: kubernetes-dashboard-anonymous
subjects:
- kind: User
  name: system:anonymous
EOF
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;参考&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&#34;https://stackoverflow.com/a/57830002&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Enable Access for Kubernetes Dashboard via external VIP or Floating IP&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;
&lt;hr&gt;
</description>
    </item>
    
  </channel>
</rss>
