单线程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

相关推荐
夜泉_ly2 小时前
MySQL -安装与初识
数据库·mysql
qq_529835353 小时前
对计算机中缓存的理解和使用Redis作为缓存
数据库·redis·缓存
月光水岸New5 小时前
Ubuntu 中建的mysql数据库使用Navicat for MySQL连接不上
数据库·mysql·ubuntu
狄加山6755 小时前
数据库基础1
数据库
我爱松子鱼5 小时前
mysql之规则优化器RBO
数据库·mysql
chengooooooo6 小时前
苍穹外卖day8 地址上传 用户下单 订单支付
java·服务器·数据库
Rverdoser7 小时前
【SQL】多表查询案例
数据库·sql
Galeoto7 小时前
how to export a table in sqlite, and import into another
数据库·sqlite
希忘auto7 小时前
详解Redis在Centos上的安装
redis·centos
人间打气筒(Ada)7 小时前
MySQL主从架构
服务器·数据库·mysql