高性能之道
- 单线程模型
- 基于内存操作
- epoll多路复用模型
- 高效的数据存储结构
至于redis为什么使用单线程处理数据,是因为redis基于内存操作,并且有高效的数据类型,它的性能瓶颈并不在CPU计算,主要在于网络IO,而网络IO在后来的版本中也被独立出来了IO线程,因此它能快速处理数据,单线程反而避免了多线程所带来的并发和资源争抢的问题
全局数据存储
Redis底层存储基于全局Hash表,存储结构和Java的HashMap类似(数组+链表方式)

rehash
Redis 默认使用了两个全局哈希表:哈希表 1 和哈希表 2。一开始,当你刚插入数据时,默认使用哈希表 1,此时的哈希表 2 并没有被分配空间。随着数据逐步增多,Redis 开始执行 rehash
- 给哈希表 2 分配更大的空间,例如是当前哈希表 1 大小的两倍;
- 把哈希表 1 中的数据重新进行打散映射到hash表2中;这个过程采用渐进式hash即拷贝数据时,Redis 仍然正常处理客户端请求,每处理一个请求时,从哈希表 1 中的第一个索引位置开始,顺带着将这个索引位置上的所有 entries 拷贝到哈希表 2 中;等处理下一个请求时,再顺带拷贝哈希表 1 中的下一个索引位置的 entries
- 释放哈希表 1 的空间。
数据类型
查看存储编码类型:object encoding key
1. string
string是最常用的类型,它的底层存储结构是SDS

存储结构
redis的string分三种情况对对象编码,目的是为了节省内存空间:
| robj *tryObjectEncodingEx(robj *o, int try_trim) | |
- if: value长度小于20字节且可以转换为整数(long类型),编码为OBJ_ENCODING_INT,其中若数字在0到10000之间,还可以使用内存共享的数字对象
- else if: 若value长度小于OBJ_ENCODING_EMBSTR_SIZE_LIMIT(44字节),编码为OBJ_ENCODING_EMBSTR
- else: 保持编码为OBJ_ENCODING_RAW
常用命令
| SET key value | |
| MSET key value [key value ...] | |
| SETNX key value #常用作分布式锁 | |
| GET key | |
| MGET key [key ...] | |
| DEL key [key ...] | |
| EXPIRE key seconds | |
| INCR key | |
| DECR key | |
| INCRBY key increment | |
| DECRBY key increment | 
常用场景
- 简单键值对
- 自增计数器
INCR作为主键的问题
- 缺陷:若数据量大的情况下,大量使用INCR来自增主键会让redis的自增操作频繁,影响redis的正常使用
- 优化:每台服务可以使用INCRBY一次性获取一百或者一千或者多少个id段来慢慢分配,这样能大量减少redis的incr命令所带来的消耗
2. list

存储结构
redis的list首先会按紧凑列表存储(listPack),当紧凑列表的长度达到list_max_listpack_size之后,会转换为双向链表
| // 1.LPUSH/RPUSH/LPUSHX/RPUSHX这些命令的统一入口 | |
| void pushGenericCommand(client *c, int where, int xx) | |
| // 2.追加元素,并尝试转换紧凑列表 | |
| void listTypeTryConversionAppend(robj *o, robj **argv, int start, int end, beforeConvertCB fn, void *data) | |
| // 3.尝试转换紧凑列表 | |
| static void listTypeTryConversionRaw(robj *o, list_conv_type lct, robj **argv, int start, int end, beforeConvertCB fn, void *data) | |
| // 4.尝试转换紧凑列表 | |
| // 若紧凑列表的长度达到list_max_listpack_size之后,则转换 | |
| static void listTypeTryConvertQuicklist(robj *o, int shrinking, beforeConvertCB fn, void *data) | |
当redis进行list元素移除时
| // 1.移除list元素的统一入口 | |
| void listElementsRemoved(client *c, robj *key, int where, robj *o, long count, int signal, int *deleted) | |
| // 2.尝试转换 | |
| void listTypeTryConversion(robj *o, list_conv_type lct, beforeConvertCB fn, void *data) | |
| // 3.尝试转换 | |
| static void listTypeTryConversionRaw(robj *o, list_conv_type lct, robj **argv, int start, int end, beforeConvertCB fn, void *data) | |
| // 4.尝试转换双向链表 | |
| // 若双向链表中只剩一个节点,且是压缩节点,则对双向链表转换为紧凑列表 | |
| static void listTypeTryConvertQuicklist(robj *o, int shrinking, beforeConvertCB fn, void *data) | |
以下参数可在redis.conf配置
常用命令
| LPUSH key value [value ...] | |
| RPUSH key value [value ...] | |
| LPOP key | |
| RPOP key | |
| LRANGE key start stop | |
| BLPOP key [key ...] timeout #从key列表头弹出一个元素,若没有元素,则阻塞等待timeout秒,0则一直阻塞等待 | |
| BRPOP key [key ...] timeout #从key列表尾弹出一个元素,若没有元素,则阻塞等待timeout秒,0则一直阻塞等待 | 
组合数据结构

根据list的特性,可以组成实现以下常用的数据结构
- Stack(栈):LPUSH + LPOP
- Queue(队列):LPUSH + RPOP
- Blocking MQ(阻塞队列):LPUSH + BRPOP
redis实现数据结构的意义在于分布式环境的实现
常用场景
- 缓存有序列表结构
- 构建分布式数据结构(栈、队列等)
3. hash

存储结构
redis的hash首先会按紧凑列表存储(listPack),当紧凑列表的长度达到hash_max_listpack_entries或添加的元素大小超过hash_max_listpack_value之后,会转换为Hash表
| // 1.添加hash元素 | |
| void hsetCommand(client *c) | |
| void hsetnxCommand(client *c) | |
| // 2.尝试转换Hash表 | |
| // 若紧凑列表的长度达到hash_max_listpack_entries | |
| // 或添加的元素大小超过hash_max_listpack_value | |
| // 则进行转换 | |
| void hashTypeTryConversion(robj *o, robj **argv, int start, int end) | |
| // 3.尝试转换Hash表 | |
| void hashTypeConvert(robj *o, int enc) | |
| // 4.转换Hash表 | |
| void hashTypeConvertListpack(robj *o, int enc) | |
以下参数可在redis.conf配置
常用命令
| HSET key field value | |
| HSETNX key field value | |
| HMSET key field value [field value ...] | |
| HGET key field | |
| HMGET key field [field ...] | |
| HDEL key field [field ...] | |
| HLEN key | |
| HGETALL key | |
| HINCRBY key field increment | 
常用场景
- 对象缓存
4. set

存储结构
- redis的set添加元素时,若存储对象是整形数字且集合小于set_max_intset_entries,则存储为OBJ_ENCODING_INTSET,若集合长度小于set_max_listpack_entries时,存储为紧凑列表。否则,存储为Hash表
| // 1.添加set元素 | |
| void saddCommand(client *c) | |
| // 2.1.创建set表 | |
| // 若存储对象是整形数字且集合小于set_max_listpack_entries,则存储为OBJ_ENCODING_INTSET | |
| // 若集合长度小于set_max_listpack_entries时,存储为紧凑列表 | |
| // 否则存储为Hash表 | |
| robj *setTypeCreate(sds value, size_t size_hint) | 










