https://www.cnblogs.com/strick/p/14851429.html

  最近在读一篇关于Redis的专栏,叫做《Redis核心技术与实战》,作者在Redis方面研究颇深,读后非常受益,特在此做记录。

一、Redis基础

1)知识图和问题画像图

  Redis知识全景图都包括“两大维度,三大主线”。“两大维度”就是指系统维度和应用维度,“三大主线”也就是指高性能、高可靠和高可扩展。

  

  高性能主线,包括线程模型、数据结构、持久化、网络框架;高可靠主线,包括主从复制、哨兵机制;高可扩展主线,包括数据分片、负载均衡。

  Redis 各大典型问题,同时结合相关的技术点,手绘了一张 Redis 的问题画像图。按照“问题 --> 主线 --> 技术点”的方式梳理出来。

  

2)数据结构

  底层数据结构一共有 6 种,分别是简单动态字符串、双向链表、压缩列表、哈希表、跳表和整数数组。

  

  压缩列表实际上类似于一个数组,数组中的每一个元素都对应保存一个数据。和数组不同的是,压缩列表在表头有三个字段 zlbytes、zltail 和 zllen,分别表示列表长度、列表尾的偏移量和列表中的 entry 个数;压缩列表在表尾还有一个 zlend,表示列表结束。在压缩列表中,如果我们要查找定位第一个元素和最后一个元素,可以通过表头三个字段的长度直接定位,复杂度是 O(1)。而查找其他元素时,就没有这么高效了,只能逐个查找,此时的复杂度就是 O(N) 了。

  跳表在链表的基础上,增加了多级索引,通过索引位置的几个跳转,实现数据的快速定位。

  集合常见操作的复杂度:

  • 单元素操作是基础;
  • 范围操作非常耗时;
  • 统计操作通常高效;
  • 例外情况只有几个,例如压缩列表和双向链表都会记录表头和表尾的偏移量。

3)单线程

  Redis 是单线程,主要是指 Redis 的网络 IO 和键值对读写是由一个线程来完成的,这也是 Redis 对外提供键值存储服务的主要流程。但 Redis 的其他功能,比如持久化、异步删除、集群数据同步等,其实是由额外的线程执行的。

  多线程的开销,系统中通常会存在被多线程同时访问的共享资源,比如一个共享的数据结构。当有多个线程要修改这个共享资源时,为了保证共享资源的正确性,就需要有额外的机制进行保证,而这个额外的机制,就会带来额外的开销。

  通常来说,单线程的处理能力要比多线程差很多,但是 Redis 却能使用单线程模型达到每秒数十万级别的处理能力。一方面,Redis 的大部分操作在内存上完成,再加上它采用了高效的数据结构,例如哈希表和跳表,这是它实现高性能的一个重要原因。另一方面,就是 Redis 采用了多路复用机制,使其在网络 IO 操作中能并发处理大量的客户端请求,实现高吞吐率。

  在 Redis 只运行单线程的情况下,该机制允许内核中,同时存在多个监听套接字和已连接套接字。内核会一直监听这些套接字上的连接请求或数据请求。一旦有请求到达,就会交给 Redis 线程处理,这就实现了一个 Redis 线程处理多个 IO 流的效果。

4)AOF和RDB

  Redis 的持久化主要有两大机制,即 AOF(Append Only File)日志和 RDB 快照。

  AOF 日志正好相反,它是写后日志,“写后”的意思是 Redis 是先执行命令,把数据写入内存,然后才记录日志。

  AOF 里记录的是 Redis 收到的每一条命令,这些命令是以文本形式保存的。

  • Redis 使用写后日志这一方式的一大好处是,可以避免出现记录错误命令的情况。
  • 还有一个好处:它是在命令执行后才记录日志,所以不会阻塞当前的写操作。

  AOF 也有两个潜在的风险。

  • 首先,如果刚执行完一个命令,还没有来得及记日志就宕机了,那么这个命令和相应的数据就有丢失的风险。
  • 其次,AOF 虽然避免了对当前命令的阻塞,但可能会给下一个操作带来阻塞风险。

二、实践

1)string

  当你保存 64 位有符号整数时,String 类型会把它保存为一个 8 字节的 Long 类型整数,这种保存方式通常也叫作 int 编码方式。

  但是,当你保存的数据中包含字符时,String 类型就会用简单动态字符串(Simple Dynamic String,SDS)结构体来保存,

  • buf:字节数组,保存实际数据。为了表示字节数组的结束,Redis 会自动在数组最后加一个“\0”,这就会额外占用 1 个字节的开销。
  • len:占 4 个字节,表示 buf 的已用长度。
  • alloc:也占个 4 字节,表示 buf 的实际分配长度,一般大于 len。

  另外,对于 String 类型来说,除了 SDS 的额外开销,还有一个来自于 RedisObject 结构体的开销。一个 RedisObject 包含了 8 字节的元数据和一个 8 字节指针。

  当字符串大于 44 字节时,SDS 的数据量就开始变多了,Redis 就不再把 SDS 和 RedisObject 布局在一起了,而是会给 SDS 分配独立的空间,并用指针指向 SDS 结构。

2)统计模式

  聚合统计,就是指统计多个集合元素的聚合结果,包括:统计多个集合的共有元素(交集统计);把两个集合相比,统计其中一个集合独有的元素(差集统计);统计多个集合的所有元素(并集统计)。

  Set 的差集、并集和交集的计算复杂度较高,在数据量较大的情况下,如果直接执行这些计算,会导致 Redis 实例阻塞。小建议:你可以从主从集群中选择一个从库,让它专门负责聚合计算,或者是把数据读取到客户端,在客户端来完成聚合统计。

  在面对需要展示最新列表、排行榜等场景时,如果数据更新频繁或者需要分页显示,建议你优先考虑使用 Sorted Set。

  二值状态就是指集合元素的取值就只有 0 和 1 两种。Bitmap 本身是用 String 类型作为底层数据结构实现的一种统计二值状态的数据类型。

  基数统计就是指统计一个集合中不重复的元素个数。

3)GEO

  GEO 类型的底层数据结构就是用 Sorted Set 来实现的。

  Redis 采用了业界广泛使用的 GeoHash 编码方法,这个方法的基本原理就是“二分区间,区间编码”。

  对于一个地理位置信息来说,它的经度范围是[-180,180]。GeoHash 编码会把一个经度值编码成一个 N 位的二进制值,我们来对经度范围[-180,180]做 N 次的二分区操作,其中 N 可以自定义。

4)异步机制

  和客户端交互时的阻塞点。复杂度高的增删改查操作肯定会阻塞 Redis。

  • 第一个阻塞点:集合全量查询和聚合操作。
  • 第二个阻塞点:bigkey 删除操作。
  • 第三个阻塞点:清空数据库。

  和磁盘交互时的阻塞点。Redis 开发者早已认识到磁盘 IO 会带来阻塞,所以就把 Redis 进一步设计为采用子进程的方式生成 RDB 快照文件,以及执行 AOF 日志重写操作。

  • 第四个阻塞点了:AOF 日志同步写。

  主从节点交互时的阻塞点。在主从集群中,主库需要生成 RDB 文件,并传输给从库。主库在复制的过程中,创建和传输 RDB 文件都是由子进程来完成的,不会阻塞主线程。

  • 第五个阻塞点:加载 RDB 文件。

  Redis 主线程启动后,会使用操作系统提供的 pthread_create 函数创建 3 个子线程,分别由它们负责 AOF 日志写操作、键值对删除以及文件关闭的异步执行。

5)内存碎片

  Redis 释放的内存空间可能并不是连续的,那么,这些不连续的内存空间很有可能处于一种闲置的状态。

  这就会导致一个问题:虽然有空闲空间,Redis 却无法用来保存数据,不仅会减少 Redis 能够实际保存的数据量,还会降低 Redis 运行机器的成本回报率。

  内存碎片的形成有内因和外因两个层面的原因。简单来说,内因是操作系统的内存分配机制,外因是 Redis 的负载特征。

  Redis 是内存数据库,内存利用率的高低直接关系到 Redis 运行效率的高低。为了让用户能监控到实时的内存使用情况,Redis 自身提供了 INFO 命令。

  这里有一个 mem_fragmentation_ratio 的指标,它表示的就是 Redis 当前的内存碎片率。mem_fragmentation_ratio 大于 1 但小于 1.5。这种情况是合理的。

6)替换策略

  “八二原理”,有 20% 的数据贡献了 80% 的访问了,而剩余的数据虽然体量很大,但只贡献了 20% 的访问量。

  • volatile-ttl 在筛选时,会针对设置了过期时间的键值对,根据过期时间的先后进行删除,越早过期的越先被删除。
  • volatile-random 就像它的名称一样,在设置了过期时间的键值对中,进行随机删除。
  • volatile-lru 会使用 LRU 算法筛选设置了过期时间的键值对。
  • volatile-lfu 会使用 LFU 算法选择设置了过期时间的键值对。
  • allkeys-random 策略,从所有键值对中随机选择并删除数据;
  • allkeys-lru 策略,使用 LRU 算法在所有数据中进行筛选。
  • allkeys-lfu 策略,使用 LFU 算法在所有数据中进行筛选。

7)原子操作

  原子操作是指执行过程保持原子性的操作,而且原子操作执行时并不需要再加锁,实现了无锁操作。

  Redis 的原子操作采用了两种方法:

  • 把多个操作在 Redis 中实现成一个操作,也就是单命令操作;
  • 把多个操作写到一个 Lua 脚本中,以原子性方式执行单个 Lua 脚本。

  Redis 是使用单线程来串行处理客户端的请求操作命令的,所以,当 Redis 执行某个命令操作时,其他命令是无法执行的,这相当于命令操作是互斥执行的。

  当然,Redis 的快照生成、AOF 重写这些操作,可以使用后台线程或者是子进程执行,也就是和主线程的操作并行执行。不过,这些操作只是读取数据,不会修改数据,所以,我们并不需要对它们做并发控制。

8)脑裂

  脑裂就是指在主从集群中,同时有两个主节点,它们都能接收写请求。而脑裂最直接的影响,就是客户端不知道应该往哪个主节点写入数据,结果就是不同的客户端会往不同的主节点上写入数据。而且,严重的话,脑裂会进一步导致数据丢失。

  主从切换后,从库一旦升级为新主库,哨兵就会让原主库执行 slave of 命令,和新主库重新进行全量同步。而在全量同步执行的最后阶段,原主库需要清空本地的数据,加载新主库发送的 RDB 文件,这样一来,原主库在主从切换期间保存的新写数据就丢失了。

  

[转帖]Redis核心技术与实战的更多相关文章

  1. 二、Redis基本操作——String(实战篇)

    小喵万万没想到,上一篇博客,居然已经被阅读600次了!!!让小喵感觉压力颇大.万一有写错的地方,岂不是会误导很多筒子们.所以,恳请大家,如果看到小喵的博客有什么不对的地方,请尽快指正!谢谢! 小喵的唠 ...

  2. 基于redis排行榜的实战总结

    前言: 之前写过排行榜的设计和实现, 不同需求其背后的架构和设计模型也不一样. 平台差异, 有的立足于游戏平台, 为多个应用提供服务, 有的仅限于单个游戏.排名范围差异, 有的面向全局排名, 有的只做 ...

  3. nginx+play framework +mongoDB+redis +mysql+LBS实战总结

    nginx+play framework +mongoDB+redis +mysql+LBS实战总结(一) 使用这个样的组合结构已经很久了,主要是实现web-server,不是做网站,二是纯粹的数据服 ...

  4. 《Netty Zookeeper Redis 高并发实战》 图书简介

    <Netty Zookeeper Redis 高并发实战> 图书简介 本书为 高并发社群 -- 疯狂创客圈 倾力编著, 高度剖析底层原理,深度解读面试难题 疯狂创客圈 Java 高并发[ ...

  5. Elasticsearch核心技术与实战-学习笔记

    学习资源: Elasticsearch中文社区日报https://elasticsearch.cn/article/ Elasticsearch 官网 https://www.elastic.co/ ...

  6. Kafka核心技术与实战,分布式的高性能消息引擎服务

    Kafka是LinkedIn开发并开源的一套分布式的高性能消息引擎服务,是大数据时代数据管道技术的首选. 如今的Kafka集消息系统.存储系统和流式处理平台于一身,并作为连接着各种业务前台和数据后台的 ...

  7. Elasticsearch核心技术与实战,性能是真牛

    Elasticsearch 是一款非常强大的开源搜索及分析引擎.结合 Kibana.Logstash和Beats,Elasticsearch 还被广泛运用在大数据近实时分析,包括日志分析.指标监控.信 ...

  8. 《机器人SLAM导航核心技术与实战》第1季:第4章_机器人传感器

    <机器人SLAM导航核心技术与实战>第1季:第4章_机器人传感器 视频讲解 [第1季]4.第4章_机器人传感器-视频讲解 [第1季]4.1.第4章_机器人传感器_惯性测量单元-视频讲解 [ ...

  9. Python核心技术与实战——十九|一起看看Python全局解释器锁GIL

    我们在前面的几节课里讲了Python的并发编程的特性,也了解了多线程编程.事实上,Python的多线程有一个非常重要的话题——GIL(Global Interpreter Lock).我们今天就来讲一 ...

  10. Python核心技术与实战——六|异常处理

    和其他语言一样,Python中的异常处理是很重要的机制和代码规范. 一.错误与异常 通常来说程序中的错误分为两种,一种是语法错误,另一种是异常.首先要了解错误和异常的区别和联系. 语法错误比较容易理解 ...

随机推荐

  1. 欢迎使用CSDN-markdown编辑器测试

    这里写自定义目录标题 欢迎使用Markdown编辑器 新的改变 功能快捷键 合理的创建标题,有助于目录的生成 如何改变文本的样式 插入链接与图片 如何插入一段漂亮的代码片 生成一个适合你的列表 创建一 ...

  2. 15、Flutter 按钮组件

    按钮组件的属性 ButtonStylee里面的常用的参数 ElevatedButton ElevatedButton 即"凸起"按钮,它默认带有阴影和灰色背景.按下后,阴影会变大 ...

  3. 30秒,2种方法解决SQL Server的内存管理问题

    今天和大家聊一聊SQL server的内存管理,说之前我们需要先提出一个问题,SQL Server到底是如何使用内存的?弄清楚如何使用之后,才能谈如何管理. 简单说,SQL Server 数据库的内存 ...

  4. GaussDB拿下的安全认证CC EAL4+究竟有多难?

    摘要:近日,经过全球知名独立认证机构SGS Brightsight实验室的安全评估,华为云GaussDB企业级分布式数据库内核获得全球权威信息技术安全性评估标准CC EAL4+级别认证 本文分享自华为 ...

  5. 云小课 | 玩转HiLens Studio之手机实时视频流调试代码

    摘要:在开发技能过程中,搭配视频流调试技能是非常必要的环节,也是检验技能效果的重要环节.HiLens Studio推出使用手机实时视频流调试代码的功能,以手机摄像头实时的视频流作为技能输入,查看技能输 ...

  6. webpack原理(2):ES6 module在Webpack中如何Tree-shaking构建

    Tree-shaking 最早由打包工具 Rollup 提出 DCE 作用于模块内(webpack 的 DCE 通过 UglifyJS 完成),而 Tree-shaking 则是在打包的时候通过模块之 ...

  7. 脑机接口 | 面向步态&神经电生理研究的非人灵长类模型与系统

    近期,海南大学生物医学工程学院脑机芯片神经工程团队在Frontiers in Neuroscience期刊上发表了题为<面向步态&神经电生理研究的非人灵长类模型与系统>的学术论文. ...

  8. 文件描述符&文件句柄

    一.概念 1.1 文件描述符&文件描述符表 文件描述符(file descriptor, fd)是Linux系统中对已打开文件的一个抽象标记,所有I/O系统调用对已打开文件的操作都要用到它.这 ...

  9. CO02生产订单新增组件

    "-----------------------------------------@斌将军-------------------------------------------- LOOP ...

  10. AtCoder Regular Contest 123 (A~C 三道好题)

    比赛链接:Here A - Arithmetic Sequence (good) 注意细节 int main() { cin.tie(nullptr)->sync_with_stdio(fals ...