Redis 数据类型 Redis 数据类型详解:代码实践与应用场景 1. 概述:Redis 的键值对模型与数据类型 Redis 是一个键值对(Key-Value)数据库,这意味着所有的数据都以键值对的形式存储。其中,键(Key)始终是字符串类型,而值(Value)则可以是多种不同的数据类型。Redis 支持以下主要数据类型: String(字符串):最基本的数据类型,可以存储字符串、数字甚至二进制数据。 List(列表):有序的字符串列表,可以实现队列、栈等数据结构。 Hash(哈希):键值对的集合,适合存储对象。 Set(集合):无序、唯一的字符串集合,支持集合运算。 Sorted Set(有序集合):有序的字符串集合,每个元素关联一个分数(score),可以根据分数排序。
1. 概述:Redis 的键值对模型与数据类型
Redis 是一个键值对(Key-Value)数据库,这意味着所有的数据都以键值对的形式存储。其中,键(Key)始终是字符串类型,而值(Value)则可以是多种不同的数据类型。Redis 支持以下主要数据类型:
String(字符串):最基本的数据类型,可以存储字符串、数字甚至二进制数据。
List(列表):有序的字符串列表,可以实现队列、栈等数据结构。
Hash(哈希):键值对的集合,适合存储对象。
Set(集合):无序、唯一的字符串集合,支持集合运算。
Sorted Set(有序集合):有序的字符串集合,每个元素关联一个分数(score),可以根据分数排序。
Bitmap(位图):位数组,用于高效地进行位操作。
HyperLogLog:基数估计算法,用于统计集合中唯一元素的数量。
Geospatial Indexes(地理位置索引):用于存储地理位置信息,并进行地理位置相关的操作。
Stream(流):用于消息队列,支持发布/订阅模式和持久化。
接下来,我们将逐一深入这些数据类型,并通过代码示例进行实践。
2. String(字符串)
2.1 类型详解
String 是 Redis 中最基本的数据类型,也是最常用的类型。它可以存储任何形式的字符串,包括文本、数字、序列化的对象,甚至二进制数据。String 类型的最大存储限制为 512MB。
2.2 常用命令与代码实践
我们使用 Redis 客户端(例如 redis-cli 命令行工具或各种编程语言的 Redis 客户端库)来操作 Redis。以下以 Python 的 redis-py 库为例进行代码实践。
2.2.1 设置值 (SET, SETEX, SETNX, MSET)
SET key value [EX seconds] [PX milliseconds] [NX|XX] [KEEPTTL]: 设置键 key 的值为 value。
EX seconds: 设置过期时间,单位秒。
PX milliseconds: 设置过期时间,单位毫秒。
NX: 键不存在时才设置 (Not eXists)。
XX: 键存在时才设置 (eXists)。
KEEPTTL: 保留键的生存时间。
import redis # 连接 Redis r = redis.Redis(host='localhost', port=6379, db=0) # 设置字符串值 r.set('mykey', 'Hello Redis') print(r.get('mykey')) # 输出: b'Hello Redis' (bytes 类型) print(r.get('mykey').decode('utf-8')) # 输出: Hello Redis (解码为字符串) # 设置带过期时间的键值对 (秒) r.setex('expire_key_sec', 10, 'This will expire in 10 seconds') # 设置带过期时间的键值对 (毫秒) r.psetex('expire_key_ms', 10000, 'This also expires in 10 seconds') # 仅当键不存在时设置值 (SETNX) set_nx_result = r.setnx('new_key_nx', 'Only set if key does not exist') print(f"SETNX result for new_key_nx: {set_nx_result}") # 输出: True (如果 new_key_nx 原本不存在) set_nx_result_again = r.setnx('new_key_nx', 'Trying to set again') print(f"SETNX result for new_key_nx again: {set_nx_result_again}") # 输出: False (因为 new_key_nx 已经存在) # 批量设置多个键值对 (MSET) r.mset({'key1': 'value1', 'key2': 'value2'}) print(r.mget(['key1', 'key2'])) # 输出: [b'value1', b'value2']
2.2.2 获取值 (GET, MGET)
GET key: 获取键 key 的值。如果键不存在,返回 nil。
MGET key [key ...]: 批量获取多个键的值。返回一个列表,列表中对应键的值,如果键不存在,则对应位置为 nil。
# 获取单个键的值 (GET) value = r.get('mykey') print(f"Value of mykey: {value.decode('utf-8') if value else None}") # 获取多个键的值 (MGET) multi_values = r.mget(['mykey', 'key1', 'non_existent_key']) print(f"Values of multiple keys: {[v.decode('utf-8') if v else None for v in multi_values]}") # 输出: Values of multiple keys: ['Hello Redis', 'value1', None]
2.2.3 数值操作 (INCR, DECR, INCRBY, DECRBY, INCRBYFLOAT)
如果 String 类型存储的是数字,Redis 提供了原子性的数值操作命令。
INCR key: 将键 key 存储的数字值加 1。
DECR key: 将键 key 存储的数字值减 1。
INCRBY key increment: 将键 key 存储的数字值加上 increment。
DECRBY key decrement: 将键 key 存储的数字值减去 decrement。
INCRBYFLOAT key increment: 将键 key 存储的浮点数值加上 increment。
# 数值操作 r.set('counter', 10) r.incr('counter') print(f"Counter after INCR: {r.get('counter').decode('utf-8')}") # 输出: 11 r.decr('counter') print(f"Counter after DECR: {r.get('counter').decode('utf-8')}") # 输出: 10 r.incrby('counter', 5) print(f"Counter after INCRBY 5: {r.get('counter').decode('utf-8')}") # 输出: 15 r.decrby('counter', 3) print(f"Counter after DECRBY 3: {r.get('counter').decode('utf-8')}") # 输出: 12 r.set('float_counter', 1.5) r.incrbyfloat('float_counter', 0.5) print(f"Float counter after INCRBYFLOAT 0.5: {r.get('float_counter').decode('utf-8')}") # 输出: 2.0
2.2.4 字符串操作 (APPEND, GETRANGE, SETRANGE, STRLEN)
APPEND key value: 将 value 追加到键 key 已存在的值的末尾。如果键不存在,则相当于 SET key value。
GETRANGE key start end: 返回键 key 存储的字符串值的子字符串,由偏移量 start 和 end 决定(包含 start 和 end)。
SETRANGE key offset value: 从偏移量 offset 开始,用 value 覆写键 key 存储的字符串值。
STRLEN key: 返回键 key 存储的字符串值的长度。
# 字符串操作 r.set('my_string', 'Hello') r.append('my_string', ' World') print(f"String after APPEND: {r.get('my_string').decode('utf-8')}") # 输出: Hello World print(f"Substring using GETRANGE: {r.getrange('my_string', 0, 4).decode('utf-8')}") # 输出: Hello r.setrange('my_string', 6, 'Redis') # 从偏移量 6 开始替换 print(f"String after SETRANGE: {r.get('my_string').decode('utf-8')}") # 输出: Hello Redis print(f"String length using STRLEN: {r.strlen('my_string')}") # 输出: 11
2.3 应用场景
缓存: 缓存热点数据,例如网页内容、API 响应等。
计数器: 网站访问计数、点赞数、库存计数等。
限速: 限制用户在一定时间内的操作频率。
分布式锁: 基于 SETNX 命令实现分布式锁。
存储简单的键值对数据: 存储配置信息、用户信息等。
3. List(列表)
3.1 类型详解
List 是 Redis 中的列表数据类型,它可以存储一个有序的字符串列表。List 中的元素可以重复,可以从列表的两端(头部或尾部)进行添加或删除操作,因此可以实现队列、栈等数据结构。List 底层实现是双向链表或压缩列表。
3.2 常用命令与代码实践
3.2.1 添加元素 (LPUSH, RPUSH, LPUSHX, RPUSHX, LINSERT)
LPUSH key value [value ...]: 将一个或多个值插入到列表 key 的头部(左侧)。
RPUSH key value [value ...]: 将一个或多个值插入到列表 key 的尾部(右侧)。
LPUSHX key value: 仅当列表 key 存在时,将值插入到列表头部。
RPUSHX key value: 仅当列表 key 存在时,将值插入到列表尾部。
LINSERT key BEFORE|AFTER pivot value: 将值 value 插入到列表 key 中元素 pivot 的之前或之后。
# 添加元素到列表 r.lpush('mylist', 'item1') # 从头部添加 r.rpush('mylist', 'item2') # 从尾部添加 r.rpush('mylist', 'item3', 'item4') # 批量从尾部添加 print(f"List after LPUSH and RPUSH: {r.lrange('mylist', 0, -1)}") # 输出: [b'item4', b'item3', b'item2', b'item1'] (注意顺序) r.lpushx('existing_list', 'item_lpushx') # 如果 'existing_list' 不存在,则不执行任何操作 r.rpushx('existing_list', 'item_rpushx') # 同上 r.linsert('mylist', 'BEFORE', 'item2', 'inserted_before_item2') r.linsert('mylist', 'AFTER', 'item3', 'inserted_after_item3') print(f"List after LINSERT: {r.lrange('mylist', 0, -1)}") # 输出: [b'item4', b'item3', b'inserted_after_item3', b'item2', b'inserted_before_item2', b'item1']
3.2.2 获取元素 (LPOP, RPOP, LINDEX, LRANGE, LLEN)
LPOP key: 移除并返回列表 key 的头部元素。
RPOP key: 移除并返回列表 key 的尾部元素。
LINDEX key index: 返回列表 key 中索引为 index 的元素(索引从 0 开始)。
LRANGE key start stop: 返回列表 key 中指定区间内的元素,区间由 start 和 stop 偏移量决定(包含 start 和 stop,负数索引表示从尾部开始计数,-1 表示最后一个元素)。
LLEN key: 返回列表 key 的长度。
# 获取和移除元素 head_element = r.lpop('mylist') print(f"Popped head element: {head_element.decode('utf-8') if head_element else None}") # 输出: item4 tail_element = r.rpop('mylist') print(f"Popped tail element: {tail_element.decode('utf-8') if tail_element else None}") # 输出: item1 print(f"List after LPOP and RPOP: {r.lrange('mylist', 0, -1)}") # 输出: [b'item3', b'inserted_after_item3', b'item2', b'inserted_before_item2'] # 获取指定索引和范围的元素 index_element = r.lindex('mylist', 1) print(f"Element at index 1: {index_element.decode('utf-8') if index_element else None}") # 输出: inserted_after_item3 range_elements = r.lrange('mylist', 0, 2) print(f"Elements in range 0-2: {[e.decode('utf-8') for e in range_elements]}") # 输出: ['item3', 'inserted_after_item3', 'item2'] list_length = r.llen('mylist') print(f"Length of list: {list_length}") # 输出: 4
3.2.3 其他列表操作 (LREM, LTRIM, LSET, BLPOP, BRPOP)
LREM key count value: 根据 count 的值,移除列表中与 value 相等的元素。
count > 0: 从列表头部开始,移除最多 count 个与 value 相等的元素。
count < 0: 从列表尾部开始,移除最多 abs(count) 个与 value 相等的元素。
count = 0: 移除所有与 value 相等的元素。
LTRIM key start stop: 修剪列表 key,只保留指定区间内的元素,区间由 start 和 stop 偏移量决定。
LSET key index value: 将列表 key 中索引为 index 的元素的值设置为 value。
BLPOP key [key ...] timeout: 阻塞式弹出列表头部元素。如果列表为空,则阻塞直到有元素被添加到列表或超时。
BRPOP key [key ...] timeout: 阻塞式弹出列表尾部元素。与 BLPOP 类似,但操作尾部。
# 其他列表操作 r.lpush('mylist_rem', 'a', 'b', 'c', 'b', 'a') r.lrem('mylist_rem', 2, 'a') # 从头部移除最多 2 个 'a' print(f"List after LREM (count=2, value='a'): {r.lrange('mylist_rem', 0, -1)}") # 输出: [b'b', b'c', b'b'] r.ltrim('mylist', 0, 1) # 只保留索引 0 和 1 的元素 print(f"List after LTRIM 0-1: {r.lrange('mylist', 0, -1)}") # 输出: [b'item3', b'inserted_after_item3'] r.lset('mylist', 1, 'replaced_item') # 设置索引 1 的元素 print(f"List after LSET index 1 to 'replaced_item': {r.lrange('mylist', 0, -1)}") # 输出: [b'item3', b'replaced_item'] # 阻塞式弹出 (BLPOP/BRPOP - 需要在不同的客户端或线程中测试) # 示例:在客户端 1 中执行 BLPOP,然后在客户端 2 中 LPUSH 元素到相同的列表 # 客户端 1: # result = r.blpop('blocking_list', timeout=5) # 阻塞 5 秒,等待元素 # print(f"BLPOP result: {result}") # 如果在 5 秒内有元素被添加到 'blocking_list',则会返回 (b'blocking_list', b'element') 或超时返回 None # 客户端 2: # r.lpush('blocking_list', 'new_element_for_blocking_list')
3.3 应用场景
消息队列: 使用 LPUSH 和 RPOP 或 LPUSH 和 BRPOP 实现简单的消息队列。
任务队列: 类似消息队列,用于异步处理任务。
最新列表: 例如,最新的文章列表、最新的评论列表等。
栈: 使用 LPUSH 和 LPOP 实现栈数据结构。
排行榜: 存储按时间排序的排行榜数据。
4. Hash(哈希)
4.1 类型详解
Hash 是 Redis 中的哈希表(或字典)数据类型,它存储了字段(field)和值(value)之间的映射。Hash 特别适合存储对象,可以将对象的属性存储为 Hash 的字段,属性值存储为 Hash 的值。Hash 本身也是一个键值对结构,但它将一个 String 键映射到一个键值对集合。
4.2 常用命令与代码实践
4.2.1 设置字段值 (HSET, HMSET, HSETNX)
HSET key field value: 设置哈希表 key 中字段 field 的值为 value。
HMSET key field value [field value ...]: 批量设置哈希表 key 中多个字段的值。
HSETNX key field value: 仅当哈希表 key 中字段 field 不存在时,才设置字段的值。
# 设置哈希字段值 r.hset('myhash', 'field1', 'value1') r.hset('myhash', 'field2', 'value2') r.hmset('myhash', {'field3': 'value3', 'field4': 'value4'}) # 批量设置 (注意 redis-py 版本,老版本 HMSET 参数形式不同) print(f"Hash after HSET and HMSET: {r.hgetall('myhash')}") # 输出: {b'field1': b'value1', b'field2': b'value2', b'field3': b'value3', b'field4': b'value4'} hsetnx_result = r.hsetnx('myhash', 'field1', 'new_value_field1') # 字段已存在,设置失败 print(f"HSETNX result for existing field: {hsetnx_result}") # 输出: False hsetnx_result_new = r.hsetnx('myhash', 'new_field', 'new_value') # 新字段,设置成功 print(f"HSETNX result for new field: {hsetnx_result_new}") # 输出: True print(f"Hash after HSETNX: {r.hgetall('myhash')}") # 输出: {b'field1': b'value1', b'field2': b'value2', b'field3': b'value3', b'field4': b'value4', b'new_field': b'new_value'}
4.2.2 获取字段值 (HGET, HMGET, HGETALL, HKEYS, HVALS, HLEN, HEXISTS)
HGET key field: 获取哈希表 key 中字段 field 的值。
HMGET key field [field ...]: 批量获取哈希表 key 中多个字段的值。
HGETALL key: 获取哈希表 key 中所有的字段和值。
HKEYS key: 获取哈希表 key 中所有的字段。
HVALS key: 获取哈希表 key 中所有的值。
HLEN key: 获取哈希表 key 中字段的数量。
HEXISTS key field: 检查哈希表 key 中字段 field 是否存在。
# 获取哈希字段值 field1_value = r.hget('myhash', 'field1') print(f"Value of field1: {field1_value.decode('utf-8') if field1_value else None}") # 输出: value1 multi_field_values = r.hmget('myhash', ['field1', 'field3', 'non_existent_field']) print(f"Values of multiple fields: {[v.decode('utf-8') if v else None for v in multi_field_values]}") # 输出: ['value1', 'value3', None] all_fields_values = r.hgetall('myhash') print(f"All fields and values: {all_fields_values}") # 输出: {b'field1': b'value1', b'field2': b'value2', b'field3': b'value3', b'field4': b'value4', b'new_field': b'new_value'} all_fields = r.hkeys('myhash') print(f"All fields: {[f.decode('utf-8') for f in all_fields]}") # 输出: ['field1', 'field2', 'field3', 'field4', 'new_field'] all_values = r.hvals('myhash') print(f"All values: {[v.decode('utf-8') for v in all_values]}") # 输出: ['value1', 'value2', 'value3', 'value4', 'new_value'] hash_length = r.hlen('myhash') print(f"Hash length: {hash_length}") # 输出: 5 field1_exists = r.hexists('myhash', 'field1') print(f"Does field1 exist: {field1_exists}") # 输出: True non_existent_field_exists = r.hexists('myhash', 'non_existent_field') print(f"Does non_existent_field exist: {non_existent_field_exists}") # 输出: False
4.2.3 删除字段 (HDEL)
key 中一个或多个字段。# 删除哈希字段 r.hdel('myhash', 'field4', 'non_existent_field') # 删除 'field4','non_existent_field' 不存在,但命令执行成功 print(f"Hash after HDEL: {r.hgetall('myhash')}") # 输出: {b'field1': b'value1', b'field2': b'value2', b'field3': b'value3', b'new_field': b'new_value'}
4.2.4 数值操作 (HINCRBY, HINCRBYFLOAT)
HINCRBY key field increment: 将哈希表 key 中字段 field 的数值加上 increment。
HINCRBYFLOAT key field increment: 将哈希表 key 中字段 field 的浮点数值加上 increment。
# 哈希字段数值操作 r.hset('myhash_counter', 'count', 10) r.hincrby('myhash_counter', 'count', 2) print(f"Hash field 'count' after HINCRBY 2: {r.hget('myhash_counter', 'count').decode('utf-8')}") # 输出: 12 r.hincrbyfloat('myhash_counter', 'float_count', 1.5) # 如果字段不存在,会先初始化为 0 再增加 r.hincrbyfloat('myhash_counter', 'float_count', 0.5) print(f"Hash field 'float_count' after HINCRBYFLOAT 0.5: {r.hget('myhash_counter', 'float_count').decode('utf-8')}") # 输出: 2.0
4.3 应用场景
存储对象: 例如,用户信息、商品信息等,将对象的属性存储为 Hash 的字段。
购物车: 以用户 ID 为 key,商品 ID 和数量作为 Hash 的字段和值。
缓存复杂的对象: 缓存结构化的数据,比 String 更节省空间和更高效。
计数器: 针对对象的不同属性进行计数。
5. Set(集合)
5.1 类型详解
Set 是 Redis 中的集合数据类型,它存储了一个无序、唯一的字符串集合。Set 提供了高效的添加、删除、检查元素是否存在以及集合运算(交集、并集、差集)等操作。Set 底层实现是哈希表或整数集合。
5.2 常用命令与代码实践
5.2.1 添加元素 (SADD)
key 中。如果成员已存在,则忽略。# 添加集合元素 r.sadd('myset', 'member1') r.sadd('myset', 'member2', 'member3', 'member2') # 'member2' 重复添加,但集合中只会有一个 print(f"Set after SADD: {r.smembers('myset')}") # 输出: {b'member3', b'member2', b'member1'} (顺序可能不同,因为 Set 是无序的)
5.2.2 获取元素 (SMEMBERS, SCARD, SISMEMBER, SRANDMEMBER, SPOP)
SMEMBERS key: 返回集合 key 中的所有成员。
SCARD key: 返回集合 key 的成员数量(基数)。
SISMEMBER key member: 检查成员 member 是否是集合 key 的成员。
SRANDMEMBER key [count]: 返回集合 key 中一个或多个随机成员。
SPOP key [count]: 移除并返回集合 key 中一个或多个随机成员。
# 获取集合元素 all_members = r.smembers('myset') print(f"All members of set: {[m.decode('utf-8') for m in all_members]}") # 输出: ['member3', 'member2', 'member1'] (顺序可能不同) set_cardinality = r.scard('myset') print(f"Cardinality of set: {set_cardinality}") # 输出: 3 is_member2 = r.sismember('myset', 'member2') print(f"Is 'member2' a member: {is_member2}") # 输出: True is_member4 = r.sismember('myset', 'member4') print(f"Is 'member4' a member: {is_member4}") # 输出: False random_member = r.srandmember('myset') print(f"Random member: {random_member.decode('utf-8') if random_member else None}") # 随机返回一个成员 random_members_count = r.srandmember('myset', 2) # 返回 2 个随机成员 print(f"Random members (count=2): {[m.decode('utf-8') for m in random_members_count]}") popped_member = r.spop('myset') # 移除并返回一个随机成员 print(f"Popped random member: {popped_member.decode('utf-8') if popped_member else None}") print(f"Set after SPOP: {r.smembers('myset')}") # 集合元素减少
5.2.3 删除元素 (SREM)
key 中一个或多个成员。# 删除集合元素 r.srem('myset', 'member1', 'non_existent_member') # 移除 'member1','non_existent_member' 不存在,但命令执行成功 print(f"Set after SREM: {r.smembers('myset')}") # 输出: {b'member3', b'member2'} (或者只有 b'member2' 如果之前 SPOP 移除了一个)
5.2.4 集合运算 (SINTER, SUNION, SDIFF, SINTERSTORE, SUNIONSTORE, SDIFFSTORE)
SINTER key [key ...]: 返回给定所有集合的交集。
SUNION key [key ...]: 返回给定所有集合的并集。
SDIFF key [key ...]: 返回给定集合的差集(第一个集合与其他集合的差)。
SINTERSTORE destination key [key ...]: 将给定所有集合的交集存储在 destination 集合中。
SUNIONSTORE destination key [key ...]: 将给定所有集合的并集存储在 destination 集合中。
SDIFFSTORE destination key [key ...]: 将给定集合的差集存储在 destination 集合中。