一文掌握 Redis 必备知识

本文全面介绍了Redis,包括其数据类型如STRING、LIST等,底层数据结构如SDS、双向链表等。对比了Redis与Memcached,阐述了Redis持久化、主从同步策略等。还介绍了Redis做分布式锁、异步队列等应用,以及集群框架、事务、分片等内容,并给出相关策略问题的解决方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

参考
https://www.jianshu.com/p/f8ccf8806095
https://zhuanlan.zhihu.com/p/32540678
https://juejin.im/post/5ad6e4066fb9a028d82c4b66
https://blog.csdn.net/ydyang1126/article/details/72667602
http://www.cnblogs.com/jaycekon/p/6227442.html
https://www.cnblogs.com/jaycekon/p/6277653.html
https://cyc2018.gitbooks.io/interview-notebook/content/notes/Redis.html

Redis 是基于内存的键值 NoSQL 数据库,可以存储键和五种不同类型的值之间的映射。
键的类型只能为 字符串 ,值支持的五种类型数据类型为:字符串、列表、集合、散列表、有序集合

一、Redis 数据类型
  • STRING: 虽然名字叫 string ,但实际上可以存储 字符串、整数或者浮点数,甚至可以对整数 / 浮点数
    的 value 进行自增、自减(1 或者 其他大小数值)。

    常用命令有:GET、SET、DEL、INCR、DECR、INCRBY、DECRBY、INCRBYFLOAT
127.0.0.1:6379> set hello world
OK
127.0.0.1:6379> get hello
"world"
127.0.0.1:6379> del hello
(integer) 1
127.0.0.1:6379> del hello
(integer) 0
127.0.0.1:6379> set one 1
OK
127.0.0.1:6379> incr one
(integer) 2
127.0.0.1:6379> decr one
(integer) 1
127.0.0.1:6379> incrby one 10
(integer) 11
127.0.0.1:6379> decrby one 10
(integer) 1
127.0.0.1:6379> incrbyfloat one 1.5
"2.5"
127.0.0.1:6379> del one
(integer) 1
  • LIST 列表: 支持从两端压入或者弹出元素。
    这里写图片描述
    常用命令有:LPUSH / RPUSH、LPOP / RPOP、LINDEX、LRANGE
127.0.0.1:6379> lrange list 0 -1 
(empty list or set)
127.0.0.1:6379> lpush list l1
(integer) 1
127.0.0.1:6379> rpush list r1
(integer) 2
127.0.0.1:6379> rpush list r2
(integer) 3
127.0.0.1:6379> rpush list r3
(integer) 4
127.0.0.1:6379> lpush list l2
(integer) 5
127.0.0.1:6379> lpush list l3
(integer) 6
127.0.0.1:6379> lrange list 0 -1 
1) "l3"
2) "l2"
3) "l1"
4) "r1"
5) "r2"
6) "r3"
127.0.0.1:6379> lpop list
"l3"
127.0.0.1:6379> rpop list
"r3"
127.0.0.1:6379> lrange list 0 -1 
1) "l2"
2) "l1"
3) "r1"
4) "r2"
127.0.0.1:6379> lindex list 0
"l2"
  • SET 无序集合:不含重复元素的无序集合。
    这里写图片描述
    常用命令有:SADD、SREM、SISMEMBER、SMEMBERS
127.0.0.1:6379> sadd set a
(integer) 1
127.0.0.1:6379> sadd set b
(integer) 1
127.0.0.1:6379> sadd set a
(integer) 0
127.0.0.1:6379> sismember set a
(integer) 1
127.0.0.1:6379> sismember set b
(integer) 1
127.0.0.1:6379> sismember set c
(integer) 0
127.0.0.1:6379> smembers set
1) "a"
2) "b"
  • HASH 哈希/散列:和STRING 一样,散列存储的值既可以是字符串也可以是数值,也支持自增自减等操作。
    这里写图片描述
    常用命令有:HSET、HGET、HGETALL、HDEL、HKEYS、HVALS、HEXISTS、HINCRBY、HINCRBYFLOAT
127.0.0.1:6379> hset hash k1 v1
(integer) 1
127.0.0.1:6379> hset hash k1 v2
(integer) 0
127.0.0.1:6379> hset hash k2 v2
(integer) 1
127.0.0.1:6379> hget hash k1
"v2"
127.0.0.1:6379> hget hash k2
"v2"
127.0.0.1:6379> hgetall hash
1) "k1"
2) "v2"
3) "k2"
4) "v2"
127.0.0.1:6379> hkeys hash
1) "k1"
2) "k2"
127.0.0.1:6379> hvals hash
1) "v2"
2) "v2"
127.0.0.1:6379> hset hash one 1
(integer) 1
127.0.0.1:6379> hincrby hash one 10
(integer) 11
127.0.0.1:6379> hincrbyfloat hash one -10.0
"1"
127.0.0.1:6379> hexists hash one
(integer) 1
  • ZSET (SortedSet) 有序集合:和 SET 一样,有序集合每一个 MEMBER 都是不相同的,有序集合还多了一项值,叫做
    分值(score),有序既可以通过成员访问元素,也可以根据分值以及分值的排列顺序访问元素。
    这里写图片描述
    常用命令有:ZADD、ZREM、ZRANGE、ZRANGEBYSCORE
127.0.0.1:6379> zadd zset 100 member1
(integer) 1
127.0.0.1:6379> zadd zset 200 member2
(integer) 1
127.0.0.1:6379> zadd zset 300 member3
(integer) 1
127.0.0.1:6379> zadd zset 300 member4
(integer) 1
127.0.0.1:6379> zadd zset 300 member5
(integer) 1
127.0.0.1:6379> zrem zset member5
(integer) 1
127.0.0.1:6379> zrange zset 0 500
1) "member1"
2) "member2"
3) "member3"
4) "member4"
127.0.0.1:6379> zrange zset 0 -1 withscores
1) "member1"
2) "100"
3) "member2"
4) "200"
5) "member3"
6) "300"
7) "member4"
8) "300"
127.0.0.1:6379> zadd zset 123 member5
(integer) 1
127.0.0.1:6379> zrange zset 0 -1 withscores
 1) "member1"
 2) "100"
 3) "member5"
 4) "123"
 5) "member2"
 6) "200"
 7) "member3"
 8) "300"
 9) "member4"
10) "300"
127.0.0.1:6379> zrangebyscore zset 0 -1 withscores
(empty list or set)
127.0.0.1:6379> zrangebyscore zset 0 500 withscores
 1) "member1"
 2) "100"
 3) "member5"
 4) "123"
 5) "member2"
 6) "200"
 7) "member3"
 8) "300"
 9) "member4"
10) "300"
二、Redis 底层数据结构

* 简单动态字符串(simple dynamic string)SDS*
定义:

/*  
 * 保存字符串对象的结构  
 */  
struct sdshdr {  

    // buf 中已占用空间的长度  
    int len;  

    // buf 中剩余可用空间的长度  
    int free;  

    // 数据空间  
    char buf[];  
};  

这里写图片描述
SDS 与 传统 C 语言字符串相比:

  1. 获取字符串长度(SDS O(1)/C 字符串 O(n))
  2. SDS 的空间分配策略完全杜绝了发生缓冲区溢出的可能性
  3. 减少修改字符串时带来的内存重分配次数
  4. 惰性空间释放
  5. 二进制安全

* 双向链表 Linkedlist*
定义:

// 链表节点
typedef struct listNode{
      struct listNode *prev;
      struct listNode *next;
      void * value;  
}

//链表
typedef struct list{
    //表头节点
    listNode  * head;
    //表尾节点
    listNode  * tail;
    //链表长度
    unsigned long len;
    //节点值复制函数
    void *(*dup) (void *ptr);
    //节点值释放函数
    void (*free) (void *ptr);
    //节点值对比函数
    int (*match)(void *ptr, void *key);
}

这里写图片描述

字典
dictht 是一个散列表结构,使用 拉链法 保存哈希冲突的 dictEntry。
定义:

typedef struct dictht {
   //哈希表数组
   dictEntry **table;
   //哈希表大小
   unsigned long size;

   //哈希表大小掩码,用于计算索引值
   unsigned long sizemask;
   //该哈希表已有节点的数量
   unsigned long used;
}

//hash 节点
typeof struct dictEntry{
   //键
   void *key;
   //值
   union{
      void *val;
      uint64_tu64;
      int64_ts64;
   }
   struct dictEntry *next;
}

//字典
typedef struct dict {
    // 类型特定函数
    dictType *type;
    // 私有数据
    void *privedata;
    // 哈希表
    dictht  ht[2];
    // rehash 索引
    in trehashidx;
}

这里写图片描述

跳跃表 Skiplist
跳跃表是一种有序数据结构,它通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的。
定义:

//跳跃表节点
typedef struct zskiplistNode{
   //层
     struct zskiplistLevel{
     //前进指针
        struct zskiplistNode *forward;
    //跨度
        unsigned int span;
    } level[];
  //后退指针
    struct zskiplistNode *backward;
  //分值
    double score;
  //成员对象
    robj *obj;
}
//跳跃表
typedef struct zskiplist {
     //表头节点和表尾节点
     structz skiplistNode *header,*tail;
     //表中节点数量
     unsigned long length;
     //表中层数最大的节点的层数
     int level;

} zskiplist;

这里写图片描述

整数集合 Intset
当一个集合中只包含整数,且这个集合中的元素数量不多时,Redis 使用 整数集合intset 作为集合的底层实现。
实际上是有序无重复的数组, 可以通过二分法进行查找操作。
定义:

typedef struct intset{
    //编码方式
    uint32_t enconding;
   // 集合包含的元素数量
    uint32_t length;
    //保存元素的数组    
    int8_t contents[];
}  

这里写图片描述

压缩列表 Ziplist
压缩列表是Redis为了节约内存而开发的, 是由一系列特殊编码的连续内存块组成的顺序型数据结构,每个压缩列表节点可以保存一个字节数组或一个整数值。

 ----------------------------------------
| prev_entry_length | encoding | content |
-----------------------------------------

STRING 类型:
String类型在Redis底层可以是 int、raw、embstr
如果一个String对象保存的是整数值,并且可以使用long来表示,那么将会以整数形式保存。
如果一个String对象保存的是字符串值,并且其长度大于32字节,那么就直接使用SDS结构来保存,其encoding标记为raw。如果一个String对象保存的字符串长度小于32字节,那么会使用embstr编码,embstr是一种短字符串的优化,其存储还是使用SDS结构,但raw编码会调用两次内存分配函数来分别创建redisObject结构和SDS结构,而embstr编码则通过调用一次内存分配函数来分配 一块连续的空间,空间中依次包含redisObject和SDS结构。

List类型: ziplist(压缩列表) 或 linkedlist(双向列表)。

Hash类型: ziplist(压缩列表) 或 hashtable(Hash表)。

Set 类型: intset(整数集合) 或 hashtable(Hash表)。

Zset类型: ziplist(压缩列表) 或 skiplist(跳跃表)。

Redis 顶层数据结构:

##如下为Reis顶层数据结构,redisDB实例表示为一个"database",任何K-V/expire信息均隶属一个db  
##一个redis可以有多个databse,参见配置文件  
//redis.h(源码)  
typedef struct redisDb {  
    dict *dict;                 /* The keyspace for this DB */  
    dict *expires;              /* Timeout of keys with a timeout set */  
    dict *blocking_keys;        /* Keys with clients waiting for data (BLPOP) */  
    dict *ready_keys;           /* Blocked keys that received a PUSH */  
    dict *watched_keys;         /* WATCHED keys for MULTI/EXEC CAS */  
    int id;                     /*DB 索引号*/  
} redisDb;

顶层数据结构维护了K-V表 / 过期key集合 / 基于阻塞操作的Key集合 / 基于事务的watch-key集合等。
其中K-V表就是一个hashtable,此表维护了“k-v对”集合,无论是查询还是插入,均基于HASH,HASH的默认尺寸为2,此后会根据“需要”做rehash操作(2*N),对于HASH冲突的解决,V的直接数据结构是 linkedlist。
key就是简单的string,key 原则上应该尽可能的短小且可读性强,无论是否基于持久存储,key 在服务的整个生命周期中都会在内存中,因此减小key的尺寸可以有效的节约内存,同时也能优化key检索的效率。
value在redis中,存储层面仍然基于string,在逻辑层面,可以是string / set / list / map,不过redis为了性能考虑,使用不同的“encoding”数据结构类型来表示它们。(例如:linkedlist,ziplist等)。

三、Redis 与 Memcached 对比

Memcached是一个开源的,高性能,分布式基于内存的key-value缓存系统。
Redis 与 Memcached 相比,主要有以下优点:

  1. Memcached 的值仅支持 字符串,Redis 支持更为丰富的数据类型如 list,set,hash,zset 。
  2. Redis 比 Memcached 速度更快
  3. Redis 支持数据的备份,即master-slave 主从模式的数据备份。
  4. Redis支持数据的持久化,而 Memcached 不支持持久化。
  5. Memcached是多线程,非阻塞IO复用的网络模型;Redis使用 单线程 的IO复用模型。
  6. Redis Cluster 实现了分布式的支持, Memcached 不支持分布式。

如何选择:
使用Redis的String类型做的事,都可以用Memcached替换,以此换取更好的性能提升;
除此以外,优先考虑Redis。

四、Redis 持久化

Redis 提供两种数据持久化方式:

  1. RDB 快照持久化 也叫镜像全量持久化 (snapshotting)将存在于某一时刻的所有数据都写入到磁盘,如果系统发生故障,将会丢失最后一次创建快照之后的数据。如果数据量很大,保存快照的时间会很长。。
  2. AOF 增量持久化,AOF 全称 append-only file 只追加文件,它会在执行写命令时,将被执行的写命令追加到磁盘里。

Redis 中 bgsave 命令创建一个快照,因为bgsave会耗费较长时间,不够实时,在停机的时候会导致大量丢失数据,所以需要 AOF 来配合使用。
在 Redis 实例重启时,会使用 bgsave 持久化文件重新构建内存,再使用 AOF 重放近期的操作指令来实现完整恢复重启之前的状态。

AOF 日志 sync 属性的配置:

  1. always:如果不要求性能,在每条写指令时都 sync 一下磁盘,就不会丢失数据,但会严重减低服务器的性能。
  2. everysec:在高性能的要求下每次都 sync 是不现实的,一般都使用定时 sync,1秒一次,这个时候最多就会丢失1s的数据。
  3. no 让操作系统来决定何时同步,并不能给服务器性能带来多大的提升,而且也会增加系统崩溃时数据丢失的数量。

随着服务器写请求的增多,AOF 文件会越来越大。Redis 提供了一种将 AOF 重写的特性,能够去除 AOF 文件中的冗余写命令。

bgsave 创建快照的实现原理:fork和cow。

  • fork是指redis通过创建子进程来进行bgsave操作。
  • cow指的是copy on write,子进程创建后,父子进程共享数据段,父进程继续提供读写服务,写脏的页面数据会逐渐和子进程分离开来。
五: Redis 的主从同步策略

slave 启动后向 master 发送同步指令SYNC 要求进行数据同步,master 启动一个备份进程用于数据同步,做一次 bgsave 创建镜像生成 rdb 文件,并同时将后续修改操作记录到内存 Buffer,待完成后将 rdb 文件全量同步到 slave,slave 接受完成后将 rdb 镜像加载到内存。加载完成后,再通知 master 将期间修改的操作记录同步到复制节点进行重放就完成了同步过程。

即:主从刚刚连接的时候,进行全量同步;全同步结束后,进行增量同步。当然,如果有需要,slave 在任何时候都可以发起全量同步。redis 策略是,无论如何,首先会尝试进行增量同步,如不成功,要求从机进行全量同步。

六: Redis 做分布式锁

使用Redis的 SETNX 命令可以实现分布式锁。
使用 SETNX 作为锁进行抢占,抢到之后,再用 expire 给锁加一个过期时间防止锁忘记了释放。
可以使用 SET key value [EX seconds] [PX milliseconds] [NX|XX],同时把setnx和expire合成一条指令避免在setnx之后执行expire之前进程意外crash导致锁就永远得不到释放的情况。

SETNX key value 
ex: SETNX mykey “hello” 
//or
SET key value [EX seconds] [PX milliseconds] [NX|XX]
ex: SET key-with-expire-time "hello" EX 10086

返回整数:
- 1,当 key 的值被设置 
- 0,当 key 的值没被设置
六: Redis 数据淘汰策略

Redis 内存数据集大小上升到一定大小的时候,就会施行数据淘汰。
Redis 提供 6种数据淘汰策略:

  1. volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
  2. volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
  3. volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
  4. allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
  5. allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
  6. no-enviction(驱逐):禁止驱逐数据
七: Redis 做异步队列

使用list结构作为队列,rpush生产消息,lpop消费消息。
当lpop没有消息的时候,可以 sleep 一会再重试或者使用blpop,在没有消息的时候,它会阻塞住直到消息到来。

使用 Redis pub/sub 主题发布/订阅模式,可以实现一对多的消息队列。

SUBSCRIBE channel [channel ...] 订阅频道
UNSUBSCRIBE channel [channel ...] 取消订阅频道
PUBLISH channel message 向给定频道发送信息

Redis pub/sub 主题发布/订阅模式的缺点:

  1. 稳定性,如果客户端读取订阅的频道的过程中,速度不够快,造成大量消息的积压很容易导致系统变慢甚至崩溃。
  2. 可靠性,如消费者在执行订阅的过程中断线,将会丢失掉线期间发送的所以消息。

Redis 实现延时队列,可以使用 ZSET 有序集合,时间戳作为 score,消息内容作为 key 调用 zadd 来生产消息,消费者用 zrangebyscore 指令获取 N 秒之前的数据轮询进行处理。

八、策略问题
  1. 假如Redis里面有1亿个key,其中有10w个key是以某个固定的已知的前缀开头的,如果将它们全部找出来?
    答:使用keys指令可以扫出指定模式的key列表,也可以使用scan指令无阻塞的提取出指定模式的key列表。
    Redis的单线程的,keys指令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复。
    这个时候可以使用scan指令,scan指令可以无阻塞的提取出指定模式的key列表,但是会有一定的重复概率,在客户端做一次去重就可以了,但是整体所花费的时间会比直接用keys指令长。
  2. 如果有大量的key需要设置同一时间过期,一般需要注意什么?
    如果大量的key过期时间设置的过于集中,到过期的那个时间点,Redis 可能会出现短暂的卡顿现象。一般需要在时间上加一个随机值,使得过期时间分散一些。
九、Redis 集群框架
  1. Redis Sentinel的高可用,在master宕机时会自动将slave提升为master,继续提供服务。
    Redis Sentinel是一个分布式架构,包含若干个Sentinel节点和Redis数据节点,每个Sentinel节点会对数据节点和其余Sentinel节点进行监控,当发现节点不可达时,会对节点做下线标识。
    Sentinel(哨兵)可以监听主服务器,并在主服务器进入下线状态时,自动从从服务器中选举出新的主服务器。
    如果被标识的是主节点,他还会选择和其他Sentinel节点进行“协商”,当大多数的Sentinel节点都认为主节点不可达时,他们会选举出一个Sentinel节点来完成自动故障转移工作,同时将这个变化通知给Redis应用方。
    整个过程完全自动,不需要人工介入,所以可以很好解决Redis的高可用问题。

  2. Redis Cluster的扩展性,在单个redis内存不足时,使用Cluster进行分片存储。
    Redis Cluster是Redis的分布式解决方案,在Redis 3.0版本正式推出的,有效解决了Redis分布式方面的需求。当遇到单机内存、并发、流量等瓶颈时,可以采用Cluster架构达到负载均衡的目的。

十、Redis 事务

一个事务包含了多个命令,服务器在执行事务期间,不会改去执行其它客户端的命令请求。
事务中的多个命令被一次性发送给服务器,而不是一条一条发送,这种方式被称为流水线(Pipeline),它可以减少客户端与服务器之间的网络通信次数从而提升性能。
Pipeline可以将多次IO往返的时间缩减为一次,前提是pipeline执行的指令之间没有因果相关性。
Redis 最简单的事务实现方式是使用 MULTIEXEC 命令将事务操作包围起来。

十一、Redis 分片

分片是将数据划分为多个部分的方法,可以将数据存储到多台机器里面,这种方法在解决某些问题时可以获得线性级别的性能提升。
假设有 4 个 Reids 实例 R0,R1,R2,R3,还有很多表示用户的键 user:1,user:2,… ,有不同的方式来选择一个指定的键存储在哪个实例中。

  1. 范围分片:例如用户 id 从 0~1000 的存储到实例 R0 中,用户 id 从 1001~2000 的存储到实例 R1 中,等等。但是这样需要维护一张映射范围表,维护操作代价很高。
  2. 哈希分片:使用 CRC32 哈希函数将键转换为一个数字,再对实例数量求模就能知道应该存储的实例。

根据执行分片的位置,可以分为三种分片方式:

  • 客户端分片:客户端使用一致性哈希等算法决定键应当分布到哪个节点。
  • 代理分片:将客户端请求发送到代理上,由代理转发请求到正确的节点上。
  • 服务器分片:Redis Cluster。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值