官方数据QPS(每秒请求数量)可达到10万
1.完全基于内存
不论读写都是在内存中完成,吊打磁盘读写,直接由CPU控制和内存对接。
2.单线程模型
网络IO(6以后也使用多线程)和键值对指令读写是由一个线程来执行。
持久化,数据同步,异步删除是用其他线程执行的
为什么不使用多线程来充分利用cpu
1.多线程需要不停的并发读写,这是特别消耗资源的
2.如果并发读写的话,就需要保护共享资源
好处:
不用因为创建线程导致性能消耗
避免上下文切换引起CPU消耗,没有多线程切换的开销
避免了线程之间的竞争问题,比如添加锁,释放锁,死锁等 ,不需要考虑锁的问题
代码更清晰,逻辑更简单
3.IO多路复用
Redis 采用 I/O 多路复用技术,并发处理连接。采用了 epoll + 自己实现的简单的事件框架。
epoll 中的读、写、关闭、连接都转化成了事件,然后利用 epoll 的多路复用特性,绝不在 IO 上浪费一点时间。
Redis 线程不会阻塞在某一个特定的监听或已连接套接字上,也就是说,不会阻塞在某一个特定的客户端请求处理上。正因为此,Redis 可以同时和多个客户端连接并处理请求,从而提升并发性。
相关参考链接:
(55条消息) io多路复用的原理和实现_彻底理解 IO 多路复用实现机制_weixin_39934085的博客-优快云博客
4.底层高效的数据结构
Redis 一共有 5 种数据类型,String、List、Hash、Set、SortedSet
。
不同的数据类型,底层用一种或多种不同的数据结构支撑。
动态字符串(String):
空间预分配:不仅会分配所需要的必须空间,还会额外分配未使用空间
惰性空间释放:SDS缩短,不回收多余的内存空间,直接用free字段记录下来,后续append
直接使用,不用在分配。
zipList(List,Hash,SortedSet):
只有少量数据,要么是长度比较短的字符串,内存紧凑节省空间
quicklist(List,Hash,SortedSet)
后续版本对linkedList和ziplist改造,quicklist代替了ziplist和linkedlist,将linkedlist按段儿拆分,每一段用ziplist来紧凑,多个list使用双向指针连链接。
skipList(sortSet)
sortSet类型的排序功能就是通过跳表来实现的
跳表是一种有序的数据结构,每个节点中维持多个指向其他节点的指针
增加多层级索引,通过多次跳转来快速定位数据位置
整型数组(intset)
当一个集合只包含整数值元素,并且这个集合的元素数量不多时,Redis 就会使用整数集合作为集合键的底层实现,节省内存。
5.Redis 全局 hash 字典
Redis 整体就是一个 哈希表来保存所有的键值对,无论数据类型是 5 种的任意一种。哈希表,本质就是一个数组,每个元素被叫做哈希桶,不管什么数据类型,每个桶里面的 entry 保存着实际具体值的指针。
而哈希表的时间复杂度是 O(1),只需要计算每个键的哈希值,便知道对应的哈希桶位置,定位桶里面的 entry 找到对应数据,这个也是 Redis 快的原因之一。
hash冲突怎么办?
Redis 通过链式哈希解决冲突:也就是同一个 桶里面的元素使用链表保存。但是当链表过长就会导致查找性能变差可能,所以 Redis 为了追求快,使用了两个全局哈希表。用于 rehash 操作,增加现有的哈希桶数量,减少哈希冲突。
开始默认使用 「hash 表 1 」保存键值对数据,「hash 表 2」 此刻没有分配空间。当数据越来越多触发 rehash 操作,则执行以下操作:
- 给 「hash 表 2 」分配更大的空间;
- 将 「hash 表 1 」的数据重新映射拷贝到 「hash 表 2」 中;
- 释放 「hash 表 1」 的空间。
值得注意的是,将 hash 表 1 的数据重新映射到 hash 表 2 的过程中并不是一次性的,这样会造成 Redis 阻塞,无法提供服务。
而是采用了渐进式 rehash,每次处理客户端请求的时候,先从「 hash 表 1」 中第一个索引开始,将这个位置的 所有数据拷贝到 「hash 表 2」 中,就这样将 rehash 分散到多次请求过程中,避免耗时阻塞。