2. Redis 数据类型


文档摘要

Redis 数据类型 Redis 数据类型详解:代码实践与应用场景 1. 概述:Redis 的键值对模型与数据类型 Redis 是一个键值对(Key-Value)数据库,这意味着所有的数据都以键值对的形式存储。其中,键(Key)始终是字符串类型,而值(Value)则可以是多种不同的数据类型。Redis 支持以下主要数据类型: String(字符串):最基本的数据类型,可以存储字符串、数字甚至二进制数据。 List(列表):有序的字符串列表,可以实现队列、栈等数据结构。 Hash(哈希):键值对的集合,适合存储对象。 Set(集合):无序、唯一的字符串集合,支持集合运算。 Sorted Set(有序集合):有序的字符串集合,每个元素关联一个分数(score),可以根据分数排序。

2. Redis 数据类型

Redis 数据类型详解:代码实践与应用场景

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 存储的字符串值的子字符串,由偏移量 startend 决定(包含 startend)。

  • 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 中指定区间内的元素,区间由 startstop 偏移量决定(包含 startstop,负数索引表示从尾部开始计数,-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,只保留指定区间内的元素,区间由 startstop 偏移量决定。

  • 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 应用场景

  • 消息队列: 使用 LPUSHRPOPLPUSHBRPOP 实现简单的消息队列。

  • 任务队列: 类似消息队列,用于异步处理任务。

  • 最新列表: 例如,最新的文章列表、最新的评论列表等。

  • : 使用 LPUSHLPOP 实现栈数据结构。

  • 排行榜: 存储按时间排序的排行榜数据。

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)

  • HDEL key field [field ...]: 删除哈希表 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)

  • SADD key member [member ...]: 将一个或多个成员添加到集合 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)

  • SREM key member [member ...]: 移除集合 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 集合中。


发布者: 作者: 转发
评论区 (0)
U