Redis 是一个开源的、基于内存的数据结构存储系统,常用作数据库、缓存和消息代理。本文记录相关内容。
简介
Redis(REmote DIctionary Server,远程字典服务器)是一个使用ANSI C编写的支持网络、基于内存、分布式、可选持久性的键值对存储数据库。根据月度排行网站DB-Engines.com的数据,Redis是最流行的键值对存储数据库。
Redis 是一个开源的、基于内存的数据结构存储系统,常用作数据库、缓存和消息代理。它支持多种数据结构,如字符串、列表、哈希、集合和有序集合,并且能够快速、灵活地进行数据存取。Redis将数据存储在内存中,这使得它具有极快的读写速度,同时还支持数据持久化,可以将数据保存到磁盘上。
- 核心概念:
-
键值对存储:
Redis 是一种NoSQL 数据库,其核心数据模型是键值对。
每个数据都通过一个唯一的键来访问,这使得数据检索非常高效。
-
内存存储:
Redis 将数据存储在内存中,而不是传统的硬盘上。
这极大地提高了数据读写速度,使其成为缓存和快速数据访问的理想选择。
-
支持多种数据结构:
除了简单的字符串,Redis 还支持列表、哈希、集合和有序集合等多种数据结构。
这使得Redis 能够满足各种不同的应用场景,例如缓存、消息队列、排行榜等。
-
数据持久化:
尽管Redis 主要基于内存,但它也支持数据持久化,可以将数据保存到磁盘上。这确保了在服务器重启后,数据可以被恢复,避免了数据丢失。
- 主要用途:
-
缓存:
Redis 经常被用作缓存层,存储频繁访问的数据,减少对主数据库的访问,从而提高应用程序的性能。
-
会话管理:
Redis 可以用来存储用户会话信息,方便用户在不同页面之间保持登录状态。
-
消息队列:
Redis 的发布/订阅功能可以用于构建消息队列,实现异步处理和解耦系统组件。
-
排行榜:
Redis 的有序集合数据结构非常适合构建排行榜,例如游戏积分榜或社交媒体的点赞榜。
- 主要特点:
-
高性能:
Redis 的内存存储和优化的数据结构使其具有极高的读写性能。
-
灵活性:
支持多种数据结构,可以满足不同的应用需求。
-
可扩展性:
Redis 可以通过集群部署,实现水平扩展,满足高并发、大数据量的需求。
-
**持久化:**支持数据持久化,保证数据的可靠性。
工作原理
基于内存的数据存储
Redis是一个内存中的数据结构存储系统,意味着它使用计算机的主内存(RAM)来存储所有的数据。这种内存优先的设计使得Redis能够提供极高的性能,因为内存的数据访问速度远远超过了传统硬盘存储。
由于存储在内存中,Redis能够以微秒级别的延迟对数据进行读写操作,这对于需要快速响应的应用来说至关重要,如缓存系统、实时分析平台和高频交易系统等。然而,内存资源相对有限且价格较高,因此Redis也提供了数据驱动的逐出策略(如LRU—最近最少使用算法)和精细的内存管理功能,确保有效利用可用内存。
数据结构与操作命令
Redis提供了丰富的数据结构,每种结构都有专门的操作命令:
- 字符串(String):最基本的数据类型,用于存储文本或二进制数据。常用命令包括 GET、SET 用于存取数据,INCR、DECR 用于原子性递增或递减操作。
- 哈希(Hash):用于存储对象,由字段和字段值组成的映射表。HGET、HSET 用于获取和设置字段值,HGETALL 用来获取哈希表的所有字段和值。
- 列表(List):有序集合,支持双端插入和删除操作。LPUSH、RPUSH 用于从左端或右端插入元素,LPOP、RPOP 用于从两端弹出元素,LRANGE 用于获取列表片段。
- 集合(Set):无序且元素唯一的集合。SADD、SREM 用于添加或移除元素,SMEMBERS 用于获取所有元素,SINTER、SUNION 和 SDIFF 用于集合运算。
- 有序集合(Sorted Set):类似 Set,但每个元素关联一个分数值,按分数有序排列。ZADD 添加元素,ZRANGE 按照分数范围查询元素,ZREM 删除元素。
Redis还支持位操作、地理空间索引、HyperLogLogs等高级数据结构,通过一系列特定的命令进行操作,使得Redis能够应用于广泛的场景中。
持久化机制
为了保证内存中的数据在断电或故障时不会丢失,Redis提供了两种主要的持久化机制:RDB(Redis Database)和AOF(Append Only File)。
- RDB:通过创建数据集的时间点快照来实现持久化。这是通过周期性地执行一个称为“BGSAVE”的操作来完成的,它会产生一个包含了Redis在某一时刻所有数据的二进制文件。RDB文件是一个压缩的二进制文件,可以用来在需要的时候,快速恢复整个数据集。它适合于数据备份和灾难恢复,但在发生故障后,自上次快照以来的所有数据都有可能丢失。
- AOF:记录每一个写操作命令到一个日志文件中,命令以追加的方式保存。AOF文件以纯文本格式存储,提供了更好的数据安全性,因为它可以配置为每次写操作后同步到磁盘,或者每秒同步一次。在Redis重启时,AOF文件中的命令会被重新执行,以重建内存中的数据状态。通过AOF恢复数据通常比RDB更慢,但可以更频繁地记录数据状态,减少数据丢失的可能性。
单线程架构
Redis的单线程架构指的是它的核心数据操作是由一个单一的线程来执行的。这种设计带来了简单性和效率,因为避免了多线程上下文切换的开销,并简化了并发控制,因为不需要考虑数据在多个线程间的同步问题。尽管Redis处理命令的主循环是单线程的,它还是能利用IO多路复用技术来同时处理多个客户端的请求。此外,对于某些耗时操作,如持久化和部分网络IO处理,Redis会使用后台线程来避免阻塞主线程,确保了服务的响应性。单线程架构使得Redis可以高效地处理大量的请求,同时保持操作的原子性和一致性。
哨兵和集群机制
为了实现高可用性和水平扩展,Redis提供了哨兵(Sentinel)机制和集群(Cluster)模式。
- Redis哨兵(Sentinel)是一个高可用性解决方案。哨兵系统可以监测Redis主从服务器的健康状态,自动执行故障转移,选举新的主服务器,并通知应用程序新主服务器的地址。哨兵还负责通知管理员,发送警报,并执行自定义脚本响应各种事件。
- Redis集群(Cluster)提供了一个数据分区(sharding)和自动管理的环境,支持在多个节点间进行数据共享。它能够在节点间自动分配数据,并在节点故障时提供自动的故障转移功能。集群通过分片来提高数据库的可扩展性,并能在不中断服务的情况下,动态地添加或移除节点。
数据库特性
数据模型
Redis的外围由一个键、值映射的字典构成。Redis有以下这五种基本类型:

(1)String(字符串)
- 概述:Redis 最基本的数据类型,存储单个键值对,值可以是字符串、数字。
- 命令:
SET key value —— 设置值
GET key —— 获取值
INCR key / DECR key —— 递增/递减(适用于计数)
APPEND key value —— 追加字符串
- 应用场景:
- 缓存用户信息(如
SET user:1:name "Alice")
- 计数(如
INCR views:page1 记录页面浏览量)
(2)Hash(哈希)
- 概述:类似于 Python 的字典,一个键值对可以包含多个字段,每个字段存储一个值。
- 命令:
HSET key field value —— 设置字段值
HGET key field —— 获取字段值
HGETALL key —— 获取所有字段和值
- 应用场景:
- 存储用户信息,如:
- shell
- CopyEdit
HSET user:1 name "Alice" age "25"
- 适用于对象存储,节省空间(相比多个
String 结构)
(3) List(列表)
- 概述:双向链表,可以用作队列(FIFO) 或 栈(LIFO) 结构。
- 命令:
LPUSH key value —— 左侧插入(类似栈)
RPUSH key value —— 右侧插入(类似队列)
LPOP key / RPOP key —— 弹出左/右端的元素
LRANGE key start stop —— 获取指定范围的元素
- 应用场景:
- 消息队列(生产者
RPUSH,消费者 LPOP)
- 任务列表(如异步任务处理)
(4) Set(集合)
- 概述:无序、不重复集合,适用于去重和求交/并/差集运算。
- 命令:
SADD key value —— 添加元素
SMEMBERS key —— 获取集合中的所有元素
SISMEMBER key value —— 判断某元素是否存在
SINTER key1 key2 / SUNION key1 key2 / SDIFF key1 key2 —— 交集/并集/差集
- 应用场景:
- 社交网络好友列表(如
SINTER user:1:friends user:2:friends 获取共同好友)
- 防止重复(如
SADD ip_blacklist "192.168.1.1")
(5) Sorted Set(有序集合)
- 概述:类似
Set,但每个元素都有一个分数(score),支持排序。
- 命令:
ZADD key score value —— 添加元素并指定分数
ZRANGE key start stop [WITHSCORES] —— 按分数排序后取值
ZREVRANGE key start stop —— 逆序获取
ZRANK key value —— 获取某元素的排名
- 应用场景:
- 排行榜(如存储游戏分数
ZADD leaderboard 100 "Alice")
- 限流(记录最近访问的用户)
它还有三种特殊的数据结构类型
(1)HyperLogLog(基数统计)
- 概述:用于基数统计(近似去重),比
Set 更节省内存,但不是 100% 精确。
- 命令:
PFADD key value —— 添加元素
PFCOUNT key —— 获取基数
- 应用场景:
- 统计 UV(Unique Visitor)(如
PFADD uv_counter user1)
(2)Bitmaps(二进制位图)
- 概述:用于存储二进制数据,可实现布隆过滤器等功能。
- 命令:
SETBIT key offset value —— 设置某个位
GETBIT key offset —— 获取某个位
- 应用场景:
- 签到系统(如
SETBIT sign:20250301 user1 1 记录用户是否签到)
- 大规模用户状态跟踪
(3) Streams(流数据)
- 概述:Redis 5.0 引入的新数据结构,适用于消息队列和日志系统。
- 命令:
XADD key * field value —— 添加消息
XRANGE key start end —— 读取消息
XREAD COUNT count STREAMS key start_id —— 读取最新消息
- 应用场景:
值的类型决定了值本身支持的操作。Redis支持不同无序、有序的列表,无序、有序的集合间的交集、并集等高级服务器端原子操作。
| 数据结构 |
主要特点 |
典型应用 |
| String |
单值存储 |
缓存、计数 |
| Hash |
键值对集合 |
对象存储 |
| List |
有序列表 |
消息队列、任务调度 |
| Set |
无序唯一元素 |
去重、交集并集计算 |
| Sorted Set |
带排序的集合 |
排行榜、限流 |
| HyperLogLog |
近似去重 |
统计 UV |
| Bitmaps |
位存储 |
用户状态、签到 |
| Streams |
消息流 |
日志、数据流处理 |
持久化
目前通过两种方式实现持久化:
- 使用快照,一种半持久耐用模式。不时的将数据集以异步方式从内存以RDB格式写入硬盘。
- 1.1版本开始使用更安全的AOF格式替代,一种只能追加的日志类型。将数据集修改操作记录起来。Redis能够在后台对只可追加的记录进行修改,从而避免日志的无限增长。
同步
Redis支持主从同步。数据可以从主服务器向任意数量的从服务器上同步,从服务器可以是关联其他从服务器的主服务器。这使得Redis可执行单层树复制。从服务器可以有意无意的对数据进行写操作。
由于完全实现了发布/订阅机制,使得从数据库在任何地方同步树时,可订阅一个频道并接收主服务器完整的消息发布记录。同步对读取操作的可扩展性和数据冗余很有帮助。
性能
当数据依赖不再需要,Redis这种基于内存的性质,与在执行一个事务时将每个变化都写入硬盘的数据库系统相比就显得执行效率非常高。写与读操作速度没有明显差别。
为什么快

基于内存存储实现
我们都知道内存读写是比在磁盘快很多的,Redis基于内存存储实现的数据库,相对于数据存在磁盘的MySQL数据库,省去磁盘I/O的消耗。
高效的数据结构
我们知道,Mysql索引为了提高效率,选择了B+树的数据结构。其实合理的数据结构,就是可以让你的应用/程序更快。先看下Redis的数据结构&内部编码图:

SDS简单动态字符串

- 字符串长度处理:Redis获取字符串长度,时间复杂度为O(1),而C语言中,需要从头开始遍历,复杂度为O(n);
- 空间预分配:字符串修改越频繁的话,内存分配越频繁,就会消耗性能,而SDS修改和空间扩充,会额外分配未使用的空间,减少性能损耗。
- 惰性空间释放:SDS 缩短时,不是回收多余的内存空间,而是free记录下多余的空间,后续有变更,直接使用free中记录的空间,减少分配。
- 二进制安全:Redis可以存储一些二进制数据,在C语言中字符串遇到’\0’会结束,而 SDS中标志字符串结束的是len属性。
字典
Redis 作为 K-V 型内存数据库,所有的键值就是用字典来存储。字典就是哈希表,比如HashMap,通过key就可以直接获取到对应的value。而哈希表的特性,在O(1)时间复杂度就可以获得对应的值。

- 跳跃表是Redis特有的数据结构,就是在链表的基础上,增加多级索引提升查找效率。
- 跳跃表支持平均 O(logN),最坏 O(N)复杂度的节点查找,还可以通过顺序性操作批量处理节点。
合理的数据编码
Redis 支持多种数据数据类型,每种基本类型,可能对多种数据结构。什么时候,使用什么样数据结构,使用什么样编码,是redis设计者总结优化的结果。
- String:如果存储数字的话,是用int类型的编码;如果存储非数字,小于等于39字节的字符串,是embstr;大于39个字节,则是raw编码。
- List:如果列表的元素个数小于512个,列表每个元素的值都小于64字节(默认),使用ziplist编码,否则使用linkedlist编码
- Hash:哈希类型元素个数小于512个,所有值小于64字节的话,使用ziplist编码,否则使用hashtable编码。
- Set:如果集合中的元素都是整数且元素个数小于512个,使用intset编码,否则使用hashtable编码。
- Zset:当有序集合的元素个数小于128个,每个元素的值小于64字节时,使用ziplist编码,否则使用skiplist(跳跃表)编码
合理的线程模型
I/O 多路复用

I/O 多路复用
多路I/O复用技术可以让单个线程高效的处理多个连接请求,而Redis使用用epoll作为I/O多路复用技术的实现。并且,Redis自身的事件处理模型将epoll中的连接、读写、关闭都转换为事件,不在网络I/O上浪费过多的时间。
- I/O :网络 I/O
- 多路 :多个网络连接
- 复用:复用同一个线程。
- IO多路复用其实就是一种同步IO模型,它实现了一个线程可以监视多个文件句柄;一旦某个文件句柄就绪,就能够通知应用程序进行相应的读写操作;而没有文件句柄就绪时,就会阻塞应用程序,交出cpu。
常用应用场景

1 缓存
- 网页索引:
- 将常用的网页内容缓存到Redis中,减少数据库的访问次数,提高网页的响应速度。
- API缓存:
- 对象索引:
- 存储常用的Java对象、Python对象等,提高应用程序的性能。
2 会话管理
- 网站会话:
- 将的用户会话信息(如登录状态、购物车内容等)存储在Redis中,实现会话共享和持久化。
- 议题讨论:
- 在各地系统中,利用Redis集中管理会话信息,实现跨服务器的会话共享。
3 计数器
4 排行榜
5 分布式锁
- 实现互斥:
- 在多个系统中,使用Redis实现互斥锁,保证同一只有一个线程或进程时刻可以访问共享资源。
- 防止重复提交:
- 在处理用户提交的请求时,使用Redis锁防止重复提交。
6 消息排队
- 异步处理:
- 将一些运行的任务放入Redis队列中,异步处理,提高系统的响应速度。
- 发布/订阅:
- 实现消息的发布和订阅,用于实时消息主体、事件通知等。
7 社交网络
8 地理空间数据
Python 示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| # coding:utf-8 import redis# lredis-server保持開啓狀態,如果在客戶端設定了密碼 添加password=密碼即可 pool = redis.ConnectionPool(host='127.0.0.1', port=6379, db=0) r = redis.StrictRedis(connection_pool=pool) # 字符串 r.set('test', 'aaa') print r.get('test') # 列表 # 注意python、lrange兩個range的範圍 x = 0 for x in range(0, 11): r.lpush('list', x) x = x + 1 print r.lrange('list', '0', '10') # 雜湊 dict_hash = {'name': 'tang', 'password': 'tang_passwd'} r.hmset('hash_test', dict_hash) print r.hgetall('hash_test') # 集合 r.sadd('set_test', 'aaa', 'bbb') r.sadd('set_test', 'ccc') r.sadd('set_test', 'ddd') print r.smembers('set_test') # 有序集 r.zadd('zset_test', {'aaa': 1, 'bbb': 1}) r.zadd('zset_test', {'ccc': 1}) r.zadd('zset_test', {'ddd': 1}) print r.zrange('zset_test', 0, 10)
|
任务获取时尝试加锁
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import redis import uuid# 连接到Redis redis_client = redis.Redis(host='redis-host', port=6379) def acquire_task_lock(task_id, host_id, timeout_ms=5000): # 原子操作:设置锁(不存在时才创建,超时自动释放) return redis_client.set( f"task_lock:{task_id}", host_id, nx=True, px=timeout_ms )
|
任务执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| def execute_task(task_id): host_id = uuid.uuid4().hex # 生成主机唯一标识 if acquire_task_lock(task_id, host_id): try: # 1. 从MySQL获取任务详情 task = mysql_query(f"SELECT * FROM tasks WHERE id={task_id} FOR UPDATE") # 2. 执行业务逻辑(长时任务需续期锁) run_task(task) finally: # 3. 释放锁(校验持有者) release_lock_script = """ if redis.call("GET", KEYS[1]) == ARGV[1] then return redis.call("DEL", KEYS[1]) end return 0 """ redis_client.eval(release_lock_script, 1, f"task_lock:{task_id}", host_id) else: print("获取锁失败,任务已被其他主机执行")
|
分布式锁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| import redis import time from contextlib import contextmanagerclass DistributedLock: def __init__(self, redis_host='192.168.10.142', redis_port=6379): self.redis = redis.StrictRedis( host=redis_host, port=redis_port, decode_responses=True ) @contextmanager def acquire(self, lock_name, timeout=10, retry_interval=0.1): """实现带自动续期的分布式锁""" identifier = str(time.time()) end_time = time.time() + timeout while time.time() < end_time: if self.redis.setnx(lock_name, identifier): # 获取锁成功,设置过期时间 self.redis.expire(lock_name, timeout) try: yield identifier return finally: # 确保只有锁的持有者能释放 if self.redis.get(lock_name) == identifier: self.redis.delete(lock_name) time.sleep(retry_interval) raise TimeoutError("获取分布式锁超时") if __name__ == '__main__': while True: lock = DistributedLock() with lock.acquire("record_123"): # do something pass
|
安装
1
| docker pull redis:8.2-rc1-bookworm
|
参考资料
文章链接:
https://www.zywvvd.com/notes/coding/dataset/redis-intr/redis-intr/