LK 博客
redis
前后端
约 1 分钟阅读 2 赞 0 条评论 鸿蒙黑体

redis

steletoyn@gmail.com
金城武 @steletoyn@gmail.com
累计点赞 2 登录后每个账号只能点一次
内容长度 0 正文词元数
正文
目录会跟随阅读位置移动。
阅读进度

Redis基础篇

1. 初识Redis

1.1 认识NoSQL

Redis是一种键值型的NoSQL数据库。

键值型:Redis中存储的数据都是以key-value对的形式存储,而value的形式多种多样,可以是字符串、数值、甚至json。

NoSQL:相对于传统关系型数据库而言,有很大差异的一种特殊的数据库,因此也叫非关系型数据库。

SQL vs NoSQL 对比:

SQL NoSQL
数据结构 结构化(Structured) 非结构化
数据关联 关联的(Relational) 无关联的
查询方式 SQL查询 非SQL
事务特性 ACID BASE
存储方式 磁盘 内存
扩展性 垂直 水平
使用场景 1) 数据结构固定 2) 相关业务对数据安全性、一致性要求较高 1) 数据结构不固定 2) 对一致性、安全性要求不高 3) 对性能要求较高

1.2 认识Redis

特征:

  • 键值(key-value)型,value支持多种不同数据结构,功能丰富
  • 单线程,每个命令具备原子性
  • 低延迟,速度快(基于内存、IO多路复用、良好的编码)
  • 支持数据持久化
  • 支持主从集群、分片集群
  • 支持多语言客户端

1.3 安装Redis

Redis是基于C语言编写的,因此首先需要安装Redis所需要的gcc依赖:

yum install -y gcc tcl

或者用docker一键安装

启动Redis:

# 默认启动(前台运行)
redis-server

# 指定配置文件启动
redis-server /usr/local/src/redis-6.2.6/redis.conf

# 开机自启(配置为系统服务)
systemctl start redis
systemctl enable redis

停止Redis:

# 利用redis-cli来执行shutdown命令
redis-cli -u 密码 shutdown

1.4 Redis客户端

命令行客户端:

Redis安装完成后就自带了命令行客户端:redis-cli,使用方式如下:

redis-cli [options] [commonds]

常见options:

  • -h 127.0.0.1:指定要连接的redis节点的IP地址,默认是127.0.0.1
  • -p 6379:指定要连接的redis节点的端口,默认是6379
  • -a 密码:指定redis的访问密码

图形化桌面客户端:

GitHub上的 Another Redis Desktop Manager,地址:https://github.com/qishibo/AnotherRedisDesktopManager

2. Redis常见命令

2.1 Redis数据结构介绍

Redis是一个key-value的数据库,key一般是String类型,不过value的类型多种多样:

类型 举例
String hello world
Hash {name: "Jack", age: 21}
List [A -> B -> C -> C]
Set {A, B, C}
SortedSet {A: 1, B: 2, C: 3}
GEO {A: (120.3, 30.5)}
BitMap 0110110101110101011
HyperLog 0110110101110101011

2.2 Redis通用命令

通用指令是部分数据类型的,都可以使用的指令,常见的有:

  • KEYS:查看符合模板的所有key,不建议在生产环境设备上使用
  • DEL:删除一个指定的key
  • EXISTS:判断key是否存在
  • EXPIRE:给一个key设置有效期,有效期到期时该key会被自动删除
  • TTL:查看一个key的剩余有效期
# 示例
KEYS *        # 查看所有key
KEYS a*       # 查看以a开头的key
DEL key1 key2 # 删除多个key
EXISTS key    # 判断key是否存在,返回1存在,0不存在
EXPIRE key 20 # 设置key的有效期为20秒
TTL key       # 查看key的剩余有效期,-1表示永久有效,-2表示已过期

2.3 String类型

String类型,也就是字符串类型,是Redis中最简单的存储类型。

其value是字符串,不过根据字符串的格式不同,又可以分为3类:

  • string:普通字符串
  • int:整数类型,可以做自增、自减操作
  • float:浮点类型,可以做自增、自减操作

不管是哪种格式,底层都是字节数组形式存储,只不过是编码方式不同。字符串类型的最大空间不能超过512m。

String的常见命令:

命令 描述
SET 添加或者修改已经存在的一个String类型的键值对
GET 根据key获取String类型的value
MSET 批量添加多个String类型的键值对
MGET 根据多个key获取多个String类型的value
INCR 让一个整型的key自增1
INCRBY 让一个整型的key自增并指定步长
INCRBYFLOAT 让一个浮点类型的数字自增并指定步长
SETNX 添加一个String类型的键值对,前提是这个key不存在,否则不执行
SETEX 添加一个String类型的键值对,并且指定有效期

key的层级格式:

Redis没有类似MySQL中的Table的概念,通过给key添加前缀加以区分,格式:项目名:业务名:类型:id

例如:

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

2.4 Hash类型

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

Hash结构可以将对象中的每个字段独立存储,可以针对单个字段做CRUD。

Hash的常见命令:

命令 描述
HSET key field value 添加或者修改hash类型key的field的值
HGET key field 获取一个hash类型key的field的值
HMSET 批量添加多个hash类型key的field的值
HMGET 批量获取多个hash类型key的field的值
HGETALL 获取一个hash类型的key中的所有的field和value
HKEYS 获取一个hash类型的key中的所有的field
HVALS 获取一个hash类型的key中的所有的value
HINCRBY 让一个hash类型key的字段值自增并指定步长
HSETNX 添加一个hash类型的key的field值,前提是这个field不存在,否则不执行

2.5 List类型

Redis中的List类型与Java中的LinkedList类似,可以看做是一个双向链表结构。既可以支持正向检索和也可以支持反向检索。

特征:有序、元素可以重复、插入和删除快、查询速度一般。

常用来存储一个有序数据,例如:朋友圈点赞列表,评论列表等。

List的常见命令:

命令 描述
LPUSH key element ... 向列表左侧插入一个或多个元素
LPOP key 移除并返回列表左侧的第一个元素,没有则返回nil
RPUSH key element ... 向列表右侧插入一个或多个元素
RPOP key 移除并返回列表右侧的第一个元素
LRANGE key star end 返回一段角标范围内的所有元素
BLPOP和BRPOP 与LPOP和RPOP类似,只不过在没有元素时等待指定时间,而不是直接返回nil

2.6 Set类型

Redis的Set结构与Java中的HashSet类似,可以看做是一个value为null的HashMap。

特征:无序、元素不可重复、查找快、支持交集、并集、差集等功能。

Set的常见命令:

命令 描述
SADD key member ... 向set中添加一个或多个元素
SREM key member ... 移除set中的指定元素
SCARD key 返回set中元素的个数
SISMEMBER key member 判断一个元素是否存在于set中
SMEMBERS 获取set中的所有元素
SINTER key1 key2 ... 求key1与key2的交集
SDIFF key1 key2 ... 求key1与key2的差集
SUNION key1 key2 ... 求key1和key2的并集

2.7 SortedSet类型

Redis的SortedSet是一个可排序的set集合,与Java中的TreeSet有些类似,但底层数据结构却差别很大。SortedSet中的每一个元素都带有一个score属性,可以基于score属性对元素排序,底层的实现是一个跳表(SkipList)加 hash表。

特征:可排序、元素不重复、查询速度快。

因为SortedSet的可排序特性,经常被用来实现排行榜这样的功能。(天生适合排行榜)

SortedSet的常见命令:

命令 描述
ZADD key score member 添加一个或多个元素到sorted set,如果已经存在则更新其score值
ZREM key member 删除sorted set中的一个指定元素
ZSCORE key member 获取sorted set中的指定元素的score值
ZRANK key member 获取sorted set中的指定元素的排名
ZCARD key 获取sorted set中的元素个数
ZCOUNT key min max 统计score值在给定范围内的所有元素的个数
ZINCRBY key increment member 让sorted set中的指定元素自增,步长为指定的increment值
ZRANGE key min max 按照score排序后,获取指定排名范围内的元素
ZRANGEBYSCORE key min max 按照score排序后,获取指定score范围内的元素
ZDIFF、ZINTER、ZUNION 求差集、交集、并集

注意:所有的排名默认都是升序,如果要降序则在命令的Z后面添加REV即可,例如:ZREVRANK

2.8 Redis的Java客户端

前面学习的都是通过 redis-cli 来操作 Redis,但在实际开发中,我们更多是通过 Java客户端 来访问 Redis。 常见的 Java 客户端主要有:

  • Jedis
  • Lettuce
  • Spring Data Redis
  • Redisson

对于初学者来说,现阶段重点掌握两个就够了:

  • Jedis:更加贴近 Redis 原生命令,适合入门理解 Redis 的 Java 操作方式
  • Spring Data Redis:Spring 生态中最常用,企业开发里更常见

2.9 Jedis客户端

2.9.1 什么是Jedis

Jedis 是一个比较经典的 Redis Java 客户端,它对 Redis 的命令做了 Java API 封装。 比如 Redis 里的 setgethsethgetAll 等命令,在 Jedis 中都能找到对应的方法来操作。

它的特点是:

  • 使用简单
  • 上手快
  • 和 Redis 原生命令对应关系清晰
  • 适合学习 Redis 的基本操作

不过需要注意的是:Jedis 本身是线程不安全的,因此在实际项目里一般不会频繁 new Jedis(),而是配合 连接池 来使用。

2.9.2 引入依赖

如果要在项目中使用 Jedis,需要先引入依赖:

<!-- jedis -->
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>3.7.0</version>
</dependency>

<!-- junit测试 -->
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter</artifactId>
    <version>5.7.0</version>
    <scope>test</scope>
</dependency>/*看看就行了没必要记*/

2.9.3 Jedis快速入门

1)建立连接

private Jedis jedis;

@BeforeEach
void setUp() {
    jedis = new Jedis("127.0.0.1", 6379);
    jedis.auth("123456");
    jedis.select(0);
}

这里的含义分别是:

  • new Jedis("127.0.0.1", 6379):连接 Redis 服务
  • auth("123456"):输入 Redis 密码
  • select(0):选择 0 号库

2)操作String类型数据

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

这段代码就对应 Redis 命令:

SET name zhangsan
GET name

也就是说,Jedis 本质上就是把 Redis 命令封装成了 Java 方法。

3)操作Hash类型数据

@Test
void testHash() {
    jedis.hset("user:1", "name", "Jack");
    jedis.hset("user:1", "age", "21");

    Map<String, String> userMap = jedis.hgetAll("user:1");
    System.out.println(userMap);
}

执行后,Redis 中会保存一个 Hash 结构的数据。

4)释放资源

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

这里的 close() 很重要。 如果不及时释放连接,连接资源会被一直占用,项目并发一高就容易出问题。

2.10 Jedis连接池

2.10.1 为什么要用连接池

虽然 Jedis 简单好用,但它有两个明显问题:

  • 线程不安全
  • 频繁创建和销毁连接有性能损耗

所以,实际开发中通常会使用 JedisPool 来管理连接。 这和数据库连接池、线程池的思想是一样的: 提前创建好一批可复用的资源,需要时直接拿,用完后归还,提高性能。

2.10.2 创建连接池

可以写一个工具类来统一获取 Jedis 连接:

public class JedisConnectionFactory {

    private static final JedisPool jedisPool;

    static {
        JedisPoolConfig poolConfig = new JedisPoolConfig();
        poolConfig.setMaxTotal(8);      // 最大连接数
        poolConfig.setMaxIdle(8);       // 最大空闲连接
        poolConfig.setMinIdle(0);       // 最小空闲连接
        poolConfig.setMaxWaitMillis(1000); // 最大等待时间

        jedisPool = new JedisPool(
                poolConfig,
                "127.0.0.1",
                6379,
                1000,
                "123456"
        );
    }

    public static Jedis getJedis() {
        return jedisPool.getResource();
    }
}

这样做的好处是:

  • 统一管理 Redis 连接
  • 降低代码耦合
  • 提高连接复用率
  • 更适合项目开发

2.10.3 使用连接池改造代码

private Jedis jedis;

@BeforeEach
void setUp() {
    jedis = JedisConnectionFactory.getJedis();
    jedis.select(0);
}

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

注意这里的 close() 不再是真正断开连接,而是把连接 归还给连接池

2.11 Spring Data Redis

2.11.1 什么是Spring Data Redis

如果说 Jedis 更适合学习 Redis 命令,那么 Spring Data Redis 更适合企业开发。

它是 Spring 提供的数据访问模块之一,专门用来整合 Redis。 它的核心优点有:

  • 对 Jedis 和 Lettuce 做了统一封装
  • 提供了统一的 RedisTemplate
  • 与 Spring Boot 集成方便
  • 更适合在 SpringBoot 项目中使用

在实际开发中,我们通常不会直接操作 Jedis,而是更多使用:

  • RedisTemplate
  • StringRedisTemplate

2.11.2 引入依赖

<!-- redis依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<!-- 连接池依赖 -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>

<!-- jackson -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
</dependency>

2.11.3 配置Redis连接信息

application.yml 中配置:

spring:
  redis:
    host: 127.0.0.1
    port: 6379
    password: 123456
    lettuce:
      pool:
        max-active: 8
        max-idle: 8
        min-idle: 0
        max-wait: 100ms

这里底层默认使用的是 Lettuce

2.11.4 RedisTemplate基本使用

@SpringBootTest
class RedisTest {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Test
    void testString() {
        redisTemplate.opsForValue().set("name", "zhangsan");
        Object name = redisTemplate.opsForValue().get("name");
        System.out.println(name);
    }
}

可以看到,RedisTemplate 已经把不同数据结构的操作都封装好了:

  • opsForValue():操作 String
  • opsForHash():操作 Hash
  • opsForList():操作 List
  • opsForSet():操作 Set
  • opsForZSet():操作 SortedSet

这比直接写 Jedis 更符合 SpringBoot 项目的开发习惯。

2.12 RedisTemplate的序列化问题

2.12.1 默认序列化的问题

RedisTemplate<String, Object> 虽然很方便,但它默认会使用 JDK序列化 来处理 value。 这样会带来两个问题:

  • 可读性差
  • 占用内存更大

也就是说,你往 Redis 里存一个 Java 对象,看到的往往不是清晰的 JSON,而是一串不太直观的序列化结果。

2.12.2 自定义RedisTemplate序列化器

可以把 RedisTemplate 的序列化方式改成 JSON:

@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);

        GenericJackson2JsonRedisSerializer jsonRedisSerializer =
                new GenericJackson2JsonRedisSerializer();

        template.setKeySerializer(RedisSerializer.string());
        template.setHashKeySerializer(RedisSerializer.string());

        template.setValueSerializer(jsonRedisSerializer);
        template.setHashValueSerializer(jsonRedisSerializer);

        return template;
    }
}

这样存到 Redis 里的数据可读性会更好,也更方便调试。

2.13 StringRedisTemplate

2.13.1 为什么推荐StringRedisTemplate

虽然 RedisTemplate 可以通过 JSON 序列化解决可读性问题,但它通常会额外保存类型信息,还是会多占一点内存。 因此在实际开发中,很多场景更推荐使用 StringRedisTemplate

它的特点是:

  • key 按 String 序列化
  • value 也按 String 序列化
  • 存储结果更直观
  • 内存占用更低

简单理解就是: 对象自己转 JSON,再把 JSON 字符串存进去。

2.13.2 操作普通字符串

@SpringBootTest
class RedisStringTest {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Test
    void testString() {
        stringRedisTemplate.opsForValue().set("code:phone:13800000000", "123456");
        String code = stringRedisTemplate.opsForValue().get("code:phone:13800000000");
        System.out.println(code);
    }
}

2.13.3 存储Java对象

假设有一个 User 对象:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private String name;
    private Integer age;
}

使用 StringRedisTemplate 时,需要手动把对象转成 JSON:

@SpringBootTest
class RedisStringTest {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    private static final ObjectMapper mapper = new ObjectMapper();

    @Test
    void testSaveUser() throws Exception {
        User user = new User("zhangsan", 20);

        // 手动序列化
        String json = mapper.writeValueAsString(user);
        stringRedisTemplate.opsForValue().set("user:100", json);

        // 读取
        String userJson = stringRedisTemplate.opsForValue().get("user:100");

        // 手动反序列化
        User result = mapper.readValue(userJson, User.class);
        System.out.println(result);
    }
}

这种方式在项目里非常常见,因为它既保证了数据清晰,又减少了额外内存开销。

2.14总结

关于对Java客户端中redis的总结

  • Jedis:帮助你快速理解“Java是如何操作Redis命令的”(理解就好没必要用,线程不安全)
  • JedisPool:解决 Jedis 线程不安全和连接复用问题(解决了也用的少)
  • Spring Data Redis:SpringBoot 项目里更常用的方案(要学)
  • RedisTemplate:通用,但默认序列化不太友好(有坑不好使)
  • StringRedisTemplate:更推荐,尤其适合实际开发(夯爆了)

后续将更新session与缓存

作者名片

steletoyn@gmail.com
金城武
@steletoyn@gmail.com

这个作者暂时还没有填写个人简介。

评论区
文章作者和管理员都可以管理这里的评论。
0 条评论
登录后即可参与评论。 去登录
还没有评论,欢迎留下第一条交流内容。