单线程Redis:Redis为什么这么快

1 Redis是不是单线程

在这里插入图片描述

Redis 使用后台线程执行耗时的 I/O 操作,以免阻塞主线程

bio_close_file:后台 I/O 线程之一,异步关闭文件
bio_aof_fsync:后台 I/O 线程之一,异步地将 AOF(Append Only File)日志同步到磁盘
bio_lazy_free:异步释放内存,有些内存释放操作可能比较耗时,因此这些操作可以异步完成,以免阻塞主线程
jemalloc_bg_thd:这是由 jemalloc 内存分配器产生的后台线程。jemalloc 是 Redis 默认使用的内存分配器,因为它在多线程环境中表现出色,能够有效地管理内存碎片。这个后台线程通常用于维护和优化内存使用(例如回收空闲内存)。

如果有io_thd_1之类的,则是在处理网络IO

一般认为Redis是单线程,是因为Redis的命令处理是单线程的

1.1 为什么Redis是单线程

单线程的局限:

  • 不能有耗时操作,包括cpu运算和阻塞io

但Redis仍存在耗时操作

io密集型:

  • 磁盘io:有两种方式持久化,一种是bio_aof_fsync,开启一个线程持久化,另外一种是rdb,通过fok进程,在子进程持久化
  • 网络io:Redis需要处理多个服务,Redis采用Reactor网络模型,实现IO多路复用;若数据请求或返回数据量比较大,则会开启io多线程

cpu密集型:

  • Redis使用高效的数据结构,并允许数据结构切换,当数据量大的时候,需使用O(1)、O(lgn)复杂度的数据结构
  • 渐进式数据迁移

Redis为什么不采用多线程?
采用多线程需加锁,加锁复杂,加锁粒度不好控制,加锁会造成频繁的CPU上下文切换,抵消多线程的优势

1.2 单线程为什么快

  1. Redis使用了内存数据库

  2. Redis是一个key-value数据库,数据存储在hashtable中,复杂度是O(1),为了保证O(1)的复杂度,hash冲突不能太激烈。

若数据太多,而hashtable太小,则非常容易冲突。

而Redis是内存数据库,一开始就分配很大空间,浪费内存,因此Redis动态分配数组大小,允许进行扩容、缩容操作。

负载因子:used / size

  • 如果负载因子 > 1 ,则会发生扩容
  • 如果正在 fork (在 rdb、aof 复写以及 rdb-aof 混用情况下)时,会阻止扩容
  • 但是此时若负载因子 > 5 ,索引效率大大降低, 则马上扩容
  • 如果负载因子 < 0.1 ,则会发生缩容

扩容:位于0号的元素,会分别散落在0号和4号,其余同理
在这里插入图片描述

在这里插入图片描述

渐进式Rehash:

若hashtable的size非常大,进行翻倍迁移的时候,是一个非常耗时的操作,但Redis仍然需要服务用户,因此不能一次性迁移。

server.h:6.2.12版本
在这里插入图片描述

dict:存储的keys
expires:过期的keys
blocking_keys:阻塞的keys

在这里插入图片描述

可以看到有一个ht[2],即hashtable有两个,在没有扩容和缩容的时候,通常只使用ht[0],扩容时,会将ht[0]中的数据放入到ht[1]中,并将ht[1]的大小翻倍。

在这里插入图片描述

rehash步骤:
ht[0] 中的元素重新经过 hash 函数生成 64 位整数,再对 ht[1] 长度进行取余,从而映射到 ht[1]

渐进式rehash:

  • 将数据的rehash操作,分摊在增删改查操作中,每次操作一个索引中的全部元素,直到rehash结束,将ht[1]赋值给ht[0],并将ht[1]置为空
  • 在定时器中,在redis空闲时,最大执行一毫秒 rehash ;每次步长 100 个数组槽位
  1. 高效的reactor网络模型

1.3 scan

KEYS *命令非常耗时,若想获取所有keys,可以使用scan命令

SCAN cursor [MATCH pattern] [COUNT count] [TYPE type]

在这里插入图片描述

采用高位进位加法的遍历顺序,rehash 后的槽位在遍历顺序上是相邻的

在这里插入图片描述

接下来应该遍历16号索引

在这里插入图片描述

2 string

三种编码

  • int:字符串长度小于等于20且能转成整数
    • 对于大整数,int占的字节比字符串更少
  • raw:字符串长度大于44
  • embstr:字符串长度小于等于44

在这里插入图片描述

在Redis中,string被实现成sds,它包含一些头部,但仍返回实际存储数据的地址。

最后的char buf[]是柔性数组,使用sizeof会不包含char buf[]

面试题:为什么Redis中字符串选择44个字节作为分界线?

embstr顾名思义就是嵌入字符串,嵌入到redisObject中

在这里插入图片描述

在这里插入图片描述

redisObject共占用4 + 4 + 8字节

长度为44的话,选择了sdshdr8的结构(表示长度0-128),sdshr8头部占用了3个字节

cpu cache line最小访问单位为64个字节

同时sds为了兼容strlen等函数,在柔性数组最后加上’\0’分隔符

因此64 - (4 + 4 + 8) - 3 - 1 = 44

3 Redis跳表

跳表(多层级有序链表)结构用来实现有序集合,redis 需要实现 zrange 以及 zrevrange功能,需要节点间最好能直接相连并且增删改操作后结构依然有序

节点数量大于 128 或者有一个字符串长度大于 64,则使用跳表(skiplist)

在这里插入图片描述

4 Redis IO多线程原理

对于一个Redis请求,需要经过read、decode、compute、encode、send这5个流程。

而有时候read、decode、encode、send过程很慢,把它们放在主线程操作,很浪费时间,因此Redis使用了IO多线程。

在这里插入图片描述

将多个IO分发到多个线程(包括主线程)中,但所有的compute仍在主线程中,因此,这与Redis是单线程的并不冲突。

参考链接:https://xxetb.xetslk.com/s/1QH6AQ

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值