Redis基础概念+数据结构

2025-10-12

今天学习一下Redis的一些基础内容。

1. Redis是什么

Redis,Remote Dictionary Server是一个开源的、内存中的数据结构存储系统。它存储的是键值对(Key-Value Pair)。因为数据存储在内存中,所以读写速度极快,不过也可以通过配置将数据持久化到硬盘。Redis核心优势在于:极高的性能、丰富的数据结构、原子性操作、持久化支持。

因为数据我我现在指接触过MySQL,所以只能和MySQL进行一些对比:

RedisMySQL
存储介质主要是内存主要是硬盘
数据结构键值对,支持复杂数据结构表结构,行和列
适用场景缓存、计数器、排行榜、实时消息持久化存储、复杂查询、事物

2. Redis数据结构

Redis支持字符串(string)、哈希(hash)、列表(list)、集合(set)、有序集合(sorted set)等等。

为了更直观感受,我们直接测试下。通过Docker安装Redis,启动。然后指令窗口docker exec -it my-redis redis-cli

设置(SET)/获取(GET)键值对:

127.0.0.1:6379> set username "zhangsan"
OK
127.0.0.1:6379> set user:1:token 123abc
OK
127.0.0.1:6379> set a 10
OK
127.0.0.1:6379> get a
"10"
127.0.0.1:6379> get username
"zhangsan"
127.0.0.1:6379> get user:1:token
"123abc"

原子递增(INCR)/递减(DECR)

127.0.0.1:6379> incr a
(integer) 11
127.0.0.1:6379> get a
"11"
127.0.0.1:6379> decr a
(integer) 10
127.0.0.1:6379> get a
"10"

有一说一,看起来蠢蠢的。为啥会有这个操作呢?自增一和自减一明明可以在程序里搞定,为啥放到Redis里呢?这儿就涉及一个点,原子性(Atomicity)。其实类似于事务,就是这个操作是整体的,中间不会被任何其他操作打断。如果程序操作,不同服务器同时写入+1,容易出现问题。高并发情况下,提供了一个可靠的计数功能,常用于计数或者网站唯一ID生成。

设置过期时间(EXPIRE)/查看剩余时间(TTL)

127.0.0.1:6379> set mykey "hello world!"
OK
127.0.0.1:6379> get mykey
"hello world!"
127.0.0.1:6379> expire mykey 100
(integer) 1
127.0.0.1:6379> ttl mykey
(integer) 96
127.0.0.1:6379> get mykey
"hello world!"

127.0.0.1:6379> ttl mykey
(integer) -2
127.0.0.1:6379> get mykey
(nil)

这个操作也很有趣,给某个键倒计时,hhh,时间一到这个键值就GG,给了数据一个生命周期。

设置(HSET)/获取(HGET-HGETALL)键值对/删除(HDEL):

127.0.0.1:6379> hset user:1 id 1 name "tom" age 25 city "苏州"
(integer) 4
127.0.0.1:6379> hget user:1
(error) ERR wrong number of arguments for 'hget' command
127.0.0.1:6379> hget user:1 name
"tom"
127.0.0.1:6379> hget user:1 id
"1"
127.0.0.1:6379> hgetall user:1
1) "id"
2) "1"
3) "name"
4) "tom"
5) "age"
6) "25"
7) "city"
8) "\xe8\x8b\x8f\xe5\xb7\x9e"

127.0.0.1:6379> hdel user:1 city
(integer) 1
127.0.0.1:6379> hgetall user:1
1) "id"
2) "1"
3) "name"
4) "tom"
5) "age"
6) "25"

看起来似乎和python的字段、json数据一样,可以存储好几个不同类型的数据。苏州似乎16进制表示,应该是命令行窗口某些编码问题,暂时不管,继续向下学习。

插入元素(LPUSH、RPUSH)/删除元素(LPOP、RPOP)/获取指定范围元素(LRANGE(0,-1))

127.0.0.1:6379> lpush study "a" "b"
(integer) 2
127.0.0.1:6379> lrange study 0 -1
1) "b"
2) "a"
127.0.0.1:6379> lpush study "c"
(integer) 3
127.0.0.1:6379> lrange study 0 -1
1) "c"
2) "b"
3) "a"
127.0.0.1:6379> rpush study "c1"
(integer) 4
127.0.0.1:6379> lrange study 0 -1
1) "c"
2) "b"
3) "a"
4) "c1"

有点……栈的感觉?

添加(SADD)/获取(SMEMBERS)/判断是否存在(SISMEMBER)/集合交集(SINTER)/集合并集(SUNION)

127.0.0.1:6379> SADD game "game1" "game2" "game3"
(integer) 3
127.0.0.1:6379> SMEMBERS game
1) "game1"
2) "game2"
3) "game3"
127.0.0.1:6379> SISMEMBER game "game4"
(integer) 0
127.0.0.1:6379> SISMEMBER game "game3"
(integer) 1
127.0.0.1:6379> SADD gamegame "game1" "game4"
(integer) 2
127.0.0.1:6379> SINTER game gamegame
1) "game1"
127.0.0.1:6379> SUNION game gamegame
1) "game3"
2) "game2"
3) "game1"
4) "game4"

添加(ZADD)/升序(ZRANGE)/降序(ZREVRANGE)

127.0.0.1:6379> ZADD game_score 10 "a"
(integer) 1
127.0.0.1:6379> ZADD game_score 10 "a" 20 "b" 30 "c"
(integer) 2
127.0.0.1:6379> zrange game_score 0 -1
1) "a"
2) "b"
3) "c"
127.0.0.1:6379> zrevrange game_score 0 -1
1) "c"
2) "b"
3) "a"

3. Redis 缓存策略

缓存可以较少数据库压力、提高访问速度、改善用户体验,提升系统性能和可伸缩性的利器,好处多多。

最常用的缓存策略是旁路缓存Cache Aside。核心思想读数据:尝试从Redis缓存中获取数据,命中直接返回/未命中查询数据库+写入Redis+设置过期时间+返回。写数据:先更新数据库中的数据,删除Redis对应的缓存(为啥不更新而是直接删除?因为更新缓存比较复杂,考虑多线程并发更新,以及缓存和数据库可能出现不一致,删除时最简单策略)。

缓存过期策略一般就是上面学到的那个EXPIRE设置过期时间,淘汰策略则包括LRU(Least Recently Used-删除最近很少使用数据)、LFU(Least Frequently Used-删除不常用数据)、TTL(Time TO Live-删除快过期的数据)。

4. Redis应用场景

(1)高并发计数器

统计网页访问量、点赞数、商品库存等等。使用字符串数据类型,键存储计数器名称,值存储当前的数量。使用INCR/DECR进行原子递增/递减计数。

(2)会话管理/Token存储

用户登录后生成的会话ID或者JWT Token认证和过期管理。使用字符串数据类型,存储用户ID或其他会话信息。使用EXPIRE设置过期时间,实现自动登出和Token失效。

(3)排行榜功能

游戏积分榜、商品销量榜、热搜等实时更新和排序的功能。使用有序集合类型,成员为排行榜的实体(如玩家ID、商品ID),分数为对应的得分/销量。使用 ZADD 添加或更新成员分数,ZREVRANGE 按照分数降序获取排行榜数据。

(4)消息队列/任务队列

处理异步任务发邮件或生成报表,存储用户最近操作记录。使用列表数据类型,LPUSH、RPUSH添加任务/消息,LPOP/RPOP从列表中取出数据。

(5)页面缓存/热点缓存

缓存访问频繁、更新不频繁的页面内容。使用字符串数据类型,值为完整的HTML内容或JSON数据。结合 Cache Aside 模式,设置合理的EXPIRE 时间。


← 返回