一、初识Redis

Redis(Remote Dictionary Server)是一款开源、基于内存、高性能的 key-value 数据库

1.1 Redis特点

  • 内存存储、极低延迟:数据主要驻留内存,读写延迟通常在微秒到毫秒级,适合高吞吐场景。
  • 单线程 + I/O 多路复用:命令处理采用单线程模型(避免锁竞态),通过事件驱动实现高并发。
  • 内存管理与淘汰策略:支持 noevictionallkeys-lruvolatile-lruallkeys-random 等多种策略,方便控制内存上限后的行为。
  • 复制与高可用:主从复制(replication)+ Sentinel 用于监控与故障转移,Cluster 支持分片水平扩展。
  • 原子操作与事务:大多数命令是原子性的,提供 MULTI/EXEC 批量事务语义(非隔离);通过 Lua 脚本还能实现复杂的原子逻辑。
  • 适用场景:缓存、会话存储、排行榜、计数器、分布式锁、消息队列、实时统计等;注意:不是替代关系型数据库的事务一致性场景(除非做额外设计)。

1.2 安装Redis及其图形化界面

建议看上一篇基于Docker安装Redis,内容详实,还可以直接按照教程给出的DockerCompose文件轻松配置。

二、Redis数据结构

2.1 数据结构简介

大致包含以下数据结构:

数据结构 典型命令 特点 / 能力 常用场景
String SET / GET / INCR / APPEND 最基础结构,可存文本、数字、二进制;支持原子自增 缓存、计数器、分布式锁标识、配置存储
Hash HSET / HGET / HGETALL / HINCRBY 类似小型对象存储,适合存用户数据;单字段可独立读写 用户信息、对象属性、配置项集合
List LPUSH / RPUSH / LPOP / BRPOP 有序、支持快速首尾操作;支持阻塞读 消息队列、任务队列、按顺序记录日志
Set SADD / SMEMBERS / SINTER / SUNION 自动去重;支持交并差;适合关系运算 标签系统、共同好友、抽奖池、唯一集合
Sorted Set (ZSet) ZADD / ZRANGE / ZREVRANGE / ZINCRBY 带分数的有序集合,可按分值排序 排行榜、优先级队列、按权重排序的数据
Bitmap SETBIT / GETBIT / BITCOUNT 位级操作,非常省内存;适合百万级布尔记录 用户签到、活跃统计、特征标记
HyperLogLog PFADD / PFCOUNT 基数(去重人数)估算,内存占用极低(12KB) UV 统计、百万级去重计数
Stream XADD / XREAD / XGROUP 日志流结构,支持消费组、持久化、消息队列能力 消息系统、事件流、日志处理、高并发 MQ 替代

如果你太着急想知道他们的用法,可以先在配置好的Redis客户端命令行执行help来查看他们的对应的命令。就像下面这样

2.2Redis通用命令

KEYS:根据通配符匹配查询所有符合模式的键,可使用 *(任意字符)、?(单个字符)、[](字符集合)、^(取反集合)。但由于该命令会全库扫描、时间复杂度高,在生产环境不推荐使用,尤其键数量较大时会阻塞整个 Redis 线程。

DEL:删除指定的 key,可一次删除多个。操作是立即生效的,如果删除的是大型数据结构,会有一定的阻塞风险。

EXISTS:判断某个 key 是否存在,返回存在的数量(从 Redis 3.0 开始支持同时检查多个 key)。

EXPIRE:为 key 设置过期时间(秒),过期后 Redis 会自动将其删除。适用于缓存、临时标记、会话等需要自动失效的场景。

TTL:查看 key 的剩余有效时间。

  • 返回 -1:该 key 存在,但没有设置过期时间

  • 返回 -2:key 已过期或不存在

  • 返回 >=0:表示剩余秒数

TYPE:查询指定 key 当前的数据类型,例如 stringhashlist 等。用于调试或确认结构。

RENAME:将某个 key 修改成新的名称,如果新 key 已存在则会被覆盖,因此使用需谨慎。

SCAN:比 KEYS 更安全的遍历方式,通过游标分段扫描,不阻塞主线程,适合大数据量下分页式遍历 key。

MOVE:将 key 从当前数据库移动到另一个数据库(如从 DB0 移到 DB1),适用于逻辑分库场景。

PERSIST:移除 key 的过期时间,使其变为永久有效。

EXISTS:检查 key 是否存在,通常用于业务逻辑判断,比如防重复提交、分布式锁占位。

PING:测试与 Redis 的连接是否正常,正常会返回 PONG

搭配help,查看命令的参数信息,就像下面这样,

2.2 String 类型与 Key 层级结构

2.2.1 String 类型简介

String 类型是 Redis 中最基础、最常用的数据结构。它的 value 本质上是字节数组,根据内容格式不同可表现为三类:

  • string:普通字符串

  • int:整数,可执行原子自增/自减

  • float:浮点数,可执行浮点增减

底层统一使用字节数组存储,只是编码方式不同。

2.2.2 常用命令

命令 作用
SET key value 新增或修改一个 String 类型的键值对
GET key 获取 String 类型的 value
MSET key1 v1 key2 v2 ... 批量新增多个 String 键值对
MGET key1 key2 ... 批量获取多个 String 的值
INCR key 整数类型自增 1(若 key 不存在,先初始化为 0)
INCRBY key n 整数类型按指定步长自增 n(若 key 不存在,先初始化为 0)
INCRBYFLOAT key f 浮点类型按步长自增(若 key 不存在,先初始化为 0)
SETNX key value 仅当 key 不存在时写入(新增语义)
SETEX key seconds value 写入并设置过期时间(秒)

示例

SET user:name "Tom" 
GET user:name 
INCR counter 
INCRBY counter 5 
INCRBYFLOAT score 1.5 
SETEX login:token:1 3600 "abc-123" 
​​​​​​​SETNX config:mode "dev" 

2.2.3 Key 的层级结构规范

Redis 没有类似 MySQL Table 的概念,因此约定式的 key 命名规范非常重要。
推荐使用多段结构,并用 : 分隔,构成分层:

项目名:业务模块:数据主键

示例(以 chatroom为项目名):

chatroom:user:1

chatroom:product:1

若 value 是 Java 对象,通常序列化为 JSON 存储:​​​​​​​

chatroom:user:1 {"id":1, "name":"Jack", "age":21} chatroom:product:1 {"id":1, "name":"小米11", "price":4999}

样例

在可视化客户端可以看到

2.3 Hash类型

  • Hash类型,也叫散列,其中value是一个无序字典,类似于Java中的HashMap结构

  • String结构是将对象序列化为JSON字符串后存储,当我们要修改对象的某个属性值的时候很不方便

KEY VALUE
reggie:user:1 {“id”:1, “name”: “Jack”, “age”: 21}
reggie:dish:1 {“id”:1, “name”: “鲟鱼火锅”, “price”: 4999}
  • Hash结构可以将对象中的每个字段独立存储,可以针对单个字段做CRUD

2.3.1 Hash类型的常见命令

命令 作用
HSET key field value 添加或修改 hash 类型 key 的 field 的值
HGET key field 获取一个 hash 类型 key 的 field 的值
HMSET key field1 value1 [field2 value2 ...] 批量添加多个 hash 类型 key 的 field 的值
HMGET key field1 [field2 ...] 批量获取多个 hash 类型 key 的 field 的值
HGETALL key 获取一个 hash 类型 key 中的所有 field 和 value
HKEYS key 获取一个 hash 类型 key 中的所有 field
HVALS key 获取一个 hash 类型 key 中的所有 value
HEXISTS key field 判断指定 key 中是否存在 field,存在返回 1,不存在返回 0
HDEL key field [field ...] 删除指定 key 中的 field,如果所有字段被删除,则 key 也被删除;key 不存在返回 0
HLEN key 返回该 key 所含有的 field 字段数量

2.3.2 Hash类型使用场景

  • 存储对象(用户信息、商品信息、配置)

  • 部分字段频繁修改的对象(Hash 不需要整个 JSON 重写,更新性能更高)

  • 需要读取某一个字段时更高效、占用更少内存

  • 避免大量 key 造成的内存碎片化(相比 string-json)

2.3.3 Hash 与 String(JSON) 的对比总结

很多人搞不清什么时候用啥,下面是一个简单的对比

对比项 String(JSON) Hash
修改单字段 ❌ 需要整体重写 JSON ✔ 可单独改字段
内存占用 小(结构压缩优化)
序列化/反序列化 需要 不需要
字段数量大时 不适合 非常适合
存储结构 一个 key 一个 JSON

一个 key 多个 field

2.4 List类型

  • List 类型是 链表结构(双向链表),其元素保持 有序、可重复

  • 可在两端高效插入和删除,非常适合实现 消息队列、日志流、时间线、排行榜的局部更新 等场景。

  • List 的操作大多是 O(1),适合频繁 push/pop。

典型结构如下:

Key: user:msg Value: ["Hi", "OK", "See you"]

2.4.1 List的常见命令

命令 作用
LPUSH key value [value ...] 从左侧插入元素
RPUSH key value [value ...] 从右侧插入元素
LPOP key 从左侧弹出一个元素
RPOP key 从右侧弹出一个元素
LRANGE key start stop 获取指定区间的元素列表
LINDEX key index 按下标获取元素
LSET key index value 修改指定位置的值
LREM key count value 删除指定元素
BLPOP key timeout 阻塞式弹出(左侧)
BRPOP key timeout 阻塞式弹出(右侧)

使用BLPOP和BRPO时,若不存在该元素,会阻塞进程等待指定时间。若指定时间内等待元素出现,返回该元素值,否则返回nil。

使用示范

# 在消息列表左侧插入
LPUSH user:msg "Hello" "OK" "Bye"

# 从右侧插入
RPUSH user:msg "newMsg"

# 按区间查询消息
LRANGE user:msg 0 -1

# 弹出最早的消息(队列语义)
LPOP user:msg

# 弹出最新的消息(栈语义)
RPOP user:msg

# 只保留最近 100 条消息
LTRIM user:msg 0 99

2.4.2 List 的典型场景

  • 消息队列(简单队列):LPUSH + BRPOP

  • 时间线 / 评论流:按插入顺序记录

  • 最新 N 条记录:配 LTRIM 高效维护

  • 任务列表:worker 消费任务

2.5 Set类型

Redis 的 Set 结构类似 Java 中的 HashSet,可以看作 value 为 null 的 HashMap。
主要特性:

  • 无序:元素在集合中没有顺序

  • 唯一性:元素不可重复

  • 查找快:基于哈希表实现

  • 支持集合运算:交集、并集、差集等

2.5.1 Redis Set 常用命令

命令 功能说明
SADD key member ... 向 set 中添加一个或多个元素
SREM key member ... 移除 set 中的一个或多个元素
SCARD key 返回 set 中元素个数
SISMEMBER key member 判断元素是否存在于 set,存在返回 1,不存在返回 0
SMEMBERS key 获取 set 中所有元素
SINTER key1 key2 ... 求多个 set 的交集
SINTERSTORE dest key1 key2 ... 求交集并将结果存入新 set(dest)
SDIFF key1 key2 ... 求多个 set 的差集(key1 - key2 - ...)
SDIFFSTORE dest key1 key2 ... 求差集并存入新 set(dest)
SUNION key1 key2 ... 求多个 set 的并集
SUNIONSTORE dest key1 key2 ... 求并集并存入新 set(dest)

2.5.2 Set类型使用场景

  • 存储对象,可以将对象的各个属性作为 field 存储在同一个 key 下,逻辑清晰

  • 部分字段频繁修改的对象,Hash 可以单独修改某个 field,无需重写整个对象,性能更高

  • 需要读取某个字段时更高效,只读取特定 field,占用内存更少,避免序列化/反序列化整个对象

  • 避免大量 key 导致内存碎片化,相比 String 存储 JSON,每个对象一个 key,Hash 将多个字段集中存储,减少内存开销

2.6 Sorted Set 类型简介

Redis 的 Sorted Set(有序集合) 是在 Set 基础上加了 分数(score) 的集合,每个元素都是唯一的,但可以根据分数自动排序。
主要特性:

  • 元素唯一:元素本身不能重复

  • 有序:每个元素都有一个 score,用于排序

  • 快速查询:可以按分数范围或排名快速查找

  • 支持集合操作:交集、并集(结果可根据 score 聚合)

2.6.1 Sorted Set 常用命令

命令 功能说明
ZADD key score member [score member ...] 向有序集合添加一个或多个元素,已存在则更新 score
ZREM key member [member ...] 删除一个或多个元素
ZCARD key 返回有序集合元素个数
ZSCORE key member 获取元素的 score
ZRANGE key start stop [WITHSCORES] 按索引范围获取元素,0 为第一个元素
ZREVRANGE key start stop [WITHSCORES] 按索引倒序获取元素
ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count] 按分数范围获取元素
ZREMRANGEBYSCORE key min max 删除指定分数范围的元素
ZRANK key member 获取元素排名(从 0 开始,按 score 升序)
ZREVRANK key member 获取元素倒序排名
ZINCRBY key increment member 对元素的 score 增加指定值

三、Reidisjava客户端

  • 目前主流的Redis的Java客户端有三种

    • Jedis和Lettuce:这两个主要是提供了Redis命令对应的API,方便我们操作Redis,而SpringDataRedis又对这两种做了抽象和封装,因此我们后期会直接以SpringDataRedis来学习。

    • Redisson:是在Redis基础上实现了分布式的可伸缩的java数据结构,例如Map、Queue等,而且支持跨进程的同步机制:Lock、Semaphore等待,比较适合用来实现特殊的功能需求。

3.1 Jedis客户端

导入依赖

<!--        jedis依赖-->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>6.2.0</version>
        </dependency>
<!--        单元测试依赖-->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>5.13.3</version>
            <scope>test</scope>
        </dependency>
<!--        日志依赖-->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>2.0.17</version>
        </dependency>

普通连接

@BeforeEach
void setUp() {
    //建立连接
    jedis = new Jedis("192.168.84.40",6379);
    //验证密码
    jedis.auth("123456");
    //选择数据库
    jedis.select(0);
}

连接池连接

@BeforeEach
void setUp() {
    //改为使用连接池
    jedis = JedisCollectionFactory.getJedis();
    //验证密码
    jedis.auth("123456");
    //选择数据库
    jedis.select(0);
}

操作数据

@Test
public void testString() {
    String result = jedis.set("name", "ling");
    System.out.println("result = " + result);
    String name = jedis.get("name");
    System.out.println("name = " + name);
}

@Test
public void testHash() {
    jedis.hset("user:1", "name", "ling");
    jedis.hset("user:1", "age", "18");
    Map<String, String> map = jedis.hgetAll("user:1");
    System.out.println("map = " + map);
}

关闭连接

@AfterEach
​void tearDown() {
​    if (jedis != null) {
​        jedis.close();
​    }
}

总结

对于复杂的对象或者键值对集合,需要自己定义键值对,不方便。

3.2 SpringDataRedis客户端

  • SpringData是Spring中数据操作的模块,包含对各种数据库的集成,其中对Redis的集成模块就叫做SpringDataRedis

  • SpringDataRedis中提供了RedisTemplate工具类,其中封装了各种对Redis的操作。并且将不同数据类型的操作API封装到了不同的类型中:

API 返回值类型 说明
redisTemplate.opsForValue() ValueOperations 操作String类型数据
redisTemplate.opsForHash() HashOperations 操作Hash类型数据
redisTemplate.opsForList() ListOperations 操作List类型数据
redisTemplate.opsForSet() SetOperations 操作Set类型数据
redisTemplate.opsForzSet() ZSetOperations 操作SortedSet类型数据
redisTemplate 通用的命令

快速入门

  1. 导入依赖

    <!--redis依赖-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <!--common-pool-->
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-pool2</artifactId>
    </dependency>
    <!--Jackson依赖-->
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
    </dependency>
    <!--lombok-->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
  2. 配置Redis

    spring:
      redis:
        host: 192.168.84.40
        port: 6379
        password: 123456
        lettuce:
          pool:
            max-active: 8
            max-idle: 8
            min-idle: 0
            max-wait: 100ms
  3. 注入RedisTemplate 因为有了SpringBoot的自动装配,我们可以拿来就用

    @Autowired
    private RedisTemplate redisTemplate;
  4. 编写测试方法

    @Test
    void stringTest(){
        redisTemplate.opsForValue().set("username","David");
        String username = (String) redisTemplate.opsForValue().get("username");
        System.out.println(username);
    }

自定义序列化

  • RedisTemplate可以接收任意Object作为值写入Redis

  • 只不过写入前会把Object序列化为字节形式,默认是采用JDK序列化,得到的结果是这样的,被剁碎了

    \xAC\xED\x00\x05t\x00\x06\xE5\xBC\xA0\xE4\xB8\x89

  • 缺点:

    • 可读性差

    • 内存占用较大

自己定义序列化方式

在启动类中配置即可

@Bean
public RedisTemplate<String,Object>  redisTemplate(RedisConnectionFactory connectionFactory) {
    //创建RedisTemplate对象
    RedisTemplate<String,Object> template = new RedisTemplate<>();
    //设置连接工厂
    template.setConnectionFactory(connectionFactory);
    //创建json序列化器
    GenericJackson2JsonRedisSerializer jsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
    //设置Key的序列化器
    template.setKeySerializer(RedisSerializer.string());
    template.setHashKeySerializer(RedisSerializer.string());
    //设置Value的序列化器
    template.setValueSerializer(jsonRedisSerializer);
    template.setHashValueSerializer(jsonRedisSerializer);
    return template;
}

定义实体类并测试

@Test
public void testUser() {
    //创建对象
    User user = new User("小王", 18);
    redisTemplate.opsForValue().set("user:1", user);
    //获取
    System.out.println(redisTemplate.opsForValue().get("user:1"));
    User client = (User) redisTemplate.opsForValue().get("user:1");
    System.out.println(client);
}

客户端结果

{

  "@class": "com.ling.pojo.User",

  "name": "小王",

  "age": 18

}
  • 整体可读性有了很大提升,并且能将Java对象自动的序列化为JSON字符串,并且查询时能自动把JSON反序列化为Java对象。不过,其中记录了序列化时对应的class名称,目的是为了查询时实现自动反序列化。这会带来额外的内存开销。

统一使用String序列化器

StringRedisTemplate
  • 为了节省内存空间,我们可以不使用JSON序列化器来处理value,而是统一使用String序列化器,要求只能存储String类型的key和value。当需要存储Java对象时,手动完成对象的序列化和反序列化。

  • 因为存入和读取时的序列化及反序列化都是我们自己实现的,SpringDataRedis就不会将class信息写入Redis了

  • 这种用法比较普遍,因此SpringDataRedis就提供了RedisTemplate的子类:StringRedisTemplate,它的key和value的序列化方式默认就是String方式。源码如下

    public class StringRedisTemplate extends RedisTemplate<String, String> {
        public StringRedisTemplate() {
            this.setKeySerializer(RedisSerializer.string());
            this.setValueSerializer(RedisSerializer.string());
            this.setHashKeySerializer(RedisSerializer.string());
            this.setHashValueSerializer(RedisSerializer.string());
        }
  • 省去了我们自定义RedisTemplate的序列化方式的步骤(可以将之前配置的RedisConfig删除掉),而是直接使用:

    @Test
    void stringTest() throws JsonProcessingException {
        //创建对象
        User user = new User("张三", 18);
        //手动序列化
        String json = mapper.writeValueAsString(user);
        //写入数据
        stringRedisTemplate.opsForValue().set("userdata", json);
        //获取数据
        String userdata = stringRedisTemplate.opsForValue().get("userdata");
        //手动反序列化
        User readValue = mapper.readValue(userdata, User.class);
        System.out.println(readValue);
    }
    
    

四、总结

Redis 是一个高性能的内存数据存储,提供多种数据结构(String、Hash、List、Set、Sorted Set),每种类型都有其适用场景和操作特点:

  • String:最简单的键值对,支持数值自增、批量操作,适合存储简单数据或计数器。

  • Hash:适合存储对象或部分字段频繁修改的数据,如用户信息、商品信息,支持单字段操作,节省内存。

  • Set:无序且唯一的集合,支持快速去重、集合运算(交集、并集、差集),常用于标签管理、好友关系或随机抽取。

  • Sorted Set:带分数的有序集合,元素唯一但可按 score 排序,适用于排行榜、延时队列、带权重的数据排序。

在 Java 中,常用客户端有两类:

  • Jedis:操作直观,适合简单场景或快速开发,但需要自行管理序列化和复杂对象存储。

  • Spring Data Redis:通过 RedisTemplate 提供类型化 API(StringOperations、HashOperations、SetOperations 等),支持自定义序列化,适合在 Spring 项目中统一管理 Redis,尤其适合存储复杂对象。

  • StringRedisTemplate:统一使用 String 序列化,便于与 JSON 结合进行对象存储和反序列化。

 核心实践经验

  1. 根据数据特点选择合适的数据结构,避免误用导致性能或内存浪费。

  2. 对复杂对象或集合数据,优先考虑 Hash 或 JSON 序列化存储,减少 key 数量,避免内存碎片。

  3. 集合操作(Set、Sorted Set)可以充分利用 Redis 的交并差运算和排名查询能力,减少客户端计算。

  4. 在 Java 项目中,RedisTemplate 配合自定义序列化器可以让 Redis 存储和对象操作无缝对接,提高开发效率。

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐