# redis-基础篇

Redis

  • 键值数据库
  • NoSQL数据库

目录:

  • 初识 Redis

    • 认识 NoSQL
    • 认识 Redis
    • 安装 Redis
  • Redis 常见命令

    • 5 种常见数据结构
    • 通用命令
    • 不同数据结构的操作命令
  • Redis 的 Java 客户端

    • Jedis 客户端
    • SpringDataRedis 客户端

# 1. 初识 Redis

# 1.1. 认识 NoSQL

类型 SQL NoSQL
数据结构 结构化 非结构化(松散)
数据关联 关联的(外键) 无关联的
查询方式 SQL查询(统一的语法) 非SQL(五花八门)
事务特性 事务 ACID BASE
存储方式 磁盘 内存
扩展性 垂直 水平
使用场景 1)数据结构固定
2)数据安全性、一致性要求较高
1)数据结构不固定
2)数据安全性、一致性要求不高
3)对性能要求高

# 1.2. 认识 Redis

Redis 诞生于 2009 年,是一个基于内存的键值型 NoSQL 数据库

全称:

  • Remote Dictionary Server
  • 远程词典服务器

特征:

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

# 1.3. 安装 Redis

下载: (windows版本)

  • 地址: https://github.com/tporadowski/redis/releases
  • 版本: Redis for Windows 5.0.14.1
  • 文件: Redis-x64-5.0.14.1.zip

启动:

D:

cd D:\soft\redis-5.0.14.1

redis-server.exe redis.windows.conf

配置: (redis.windows.conf)

# 允许访问的地址,默认是127.0.0.1,会导致只能在本地访问。
# 修改为0.0.0.0则可以在任意IP访问,生产环境不要设置为0.0.0.0
bind 0.0.0.0

# 守护进程,修改为yes后即可后台运行
daemonize yes 

# 密码,设置后访问Redis必须输入密码
requirepass 123321



# 监听的端口
port 6379

# 工作目录,默认是当前目录,也就是运行redis-server时的命令,日志、持久化等文件会保存在这个目录
dir .

# 数据库数量,设置为1,代表只使用1个库,默认有16个库,编号0~15
databases 1

# 设置redis能够使用的最大内存
maxmemory 512mb

# 日志文件,默认为空,不记录日志,可以指定日志文件名
logfile "redis.log"

参考:

# 1.4. 命令行客户端

在 Redis 安装目录存在 redis-cli.exe 客户端

D:\install\redis-5.0.14.1>redis-cli -h 127.0.0.1 -p 6379

127.0.0.1:6379> ping
PONG

127.0.0.1:6379> set name zhangsan
OK

127.0.0.1:6379> get name
"zhangsan"

# 1.5. 图形化桌面客户端

GitHub上的大神编写了Redis的图形化桌面客户端,地址:https://github.com/uglide/RedisDesktopManager

不过该仓库提供的是RedisDesktopManager的源码,并未提供 windows 安装包。

在下面这个仓库可以找到安装包:https://github.com/lework/RedisDesktopManager-Windows/releases

# 2. Redis 常见命令

# 2.1. Redis 数据结构介绍

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

基本类型:

  • String
  • Hash
  • List
  • Set
  • SortedSet

特殊类型:

  • GEO
  • BitMap
  • HyperLog

帮助文档:

  • https://redis.io/docs/latest/commands/

# 2.2. Redis 通用命令

常见指定:

  • KEYS: 查看符合模板的所有 key,不建议在生产环境使用
  • DEL: 删除指定的 key
  • EXISTS: key 是否存在
  • EXPIRE: 设置有效期,到期自动删除
  • TTL: 查看剩余有效期;-1 永久有效, -2 已删除

KEYS 的模式:

Supported glob-style patterns:

   h?llo       matches hello, hallo and hxllo
   h*llo       matches hllo and heeeello
   h[ae]llo    matches hello and hallo, but not hillo
   h[^e]llo    matches hallo, hbllo, ... but not hello
   h[a-b]llo   matches hallo and hbllo

示例:

127.0.0.1:6379> MSET name ZhangSan age 18

127.0.0.1:6379> help KEYS

  KEYS pattern
  summary: Find all keys matching the given pattern
  since: 1.0.0
  group: generic

127.0.0.1:6379> KEYS *
1) "age"
2) "name"

127.0.0.1:6379> DEL age
(integer) 1

127.0.0.1:6379> KEYS *
1) "name"

127.0.0.1:6379> EXISTS name
(integer) 1

127.0.0.1:6379> EXISTS age
(integer) 0

127.0.0.1:6379> EXPIRE name 10
(integer) 1

127.0.0.1:6379> TTL name
(integer) 1

127.0.0.1:6379> TTL name
(integer) -2

127.0.0.1:6379> KEYS *
(empty list or set)

# 2.3. String 类型

说明:

  • 字符串类型,最简单的存储类型

value 是字符串,可以分为三类:

  • string
  • int
  • float

常见命令:

  • SET: 增 或 改

  • GET: 查

  • MSET: 批量增

  • MGET: 批量查

  • INCR: int 自增1

  • INCRBY: int 自增指定步长

  • INCRBYFLOAT: float 自增指定步长

  • SETNX: 增。不存在时新增

  • SETEX: 增或改。设置有效期

示例:

127.0.0.1:6379> set name zhangsan
OK
127.0.0.1:6379> get name
"zhangsan"
127.0.0.1:6379> set name zhangsan2
OK
127.0.0.1:6379> get name
"zhangsan2"


127.0.0.1:6379> mset k1 v1 k2 v2
OK
127.0.0.1:6379> keys *
1) "name"
2) "k1"
3) "k2"
127.0.0.1:6379> mget k1 k2
1) "v1"
2) "v2"


127.0.0.1:6379> set age 18
OK
127.0.0.1:6379> get age
"18"
127.0.0.1:6379> incr age
(integer) 19
127.0.0.1:6379> incrby age 2
(integer) 21
127.0.0.1:6379> incrby age -2
(integer) 19
127.0.0.1:6379> set score 82.5
OK
127.0.0.1:6379> incrbyfloat score 0.5
"83"
127.0.0.1:6379> incrbyfloat score 0.5
"83.5"


127.0.0.1:6379> setnx name zhangsan2
(integer) 0
127.0.0.1:6379> setnx name2 zhangsan2
(integer) 1
# 等价于如下命令
127.0.0.1:6379> set name2 zhangsan2 nx
(nil)


127.0.0.1:6379> set name3 lisi
OK
127.0.0.1:6379> setex name3 10 lisi2
OK
127.0.0.1:6379> get name3
"lisi2"
127.0.0.1:6379> ttl name3
(integer) -2
127.0.0.1:6379> set name3 lisi
OK
# 等价于如下命令
127.0.0.1:6379> set name3 lisi ex 10
OK
127.0.0.1:6379> ttl name3
(integer) 8

# 2.4. key 的格式

建议格式:

  • 项目名:业务名:类型:id

说明:

  • 通过 : 分隔的 key 在图形界面中会形成层级结构

注意:

  • windows cmd 要设置 UTF-8 编码,否则会导致乱码
  • 在 CMD 执行: chcp 65001

示例:

set user:1 '{"id":1,"name":"zhangsan","age":18}'
set user:2 '{"id":2,"name":"lisi","age":20}'

set product:1 '{"id":1,"name":"小米11","price":1888}'
set product:2 '{"id":2,"name":"荣耀100","price":1999}'

keys *
# 1) "product:1"
# 2) "product:2"
# 3) "user:2"
# 4) "user:1"

# 2.5. Hash 类型

说明:

  • 散列,无序字典,类似 Java 中的 HashMap

常见命令:

  • HSET key field value: 增或改 key 的字段
  • HGET key field: 查 key 的字段的值
  • HMSET: 批量 增或改 key 的字段
  • HMGET: 批量 查 key 的字段
  • HGETALL: 查 key 的所有字段和值
  • HKEYS: 查 key 的所有字段
  • HVALS: 查 key 的所有值
  • HINCRBY: key 的字段 自增
  • HSETNX: 增。存在则增

示例:

# 增 字段
HSET user:3 name wangwu
# (integer) 1

# 查 字段
HGET user:3 name
# "wangwu"

# 批量增 字段
HMSET user:3 age 18 gender man
# OK

# 批量查 字段
HMGET user:3 name age gender
# 1) "wangwu"
# 2) "18"
# 3) "man"

# 查所有 字段-值 对
HGETALL user:3
# 1) "name"
# 2) "wangwu"
# 3) "age"
# 4) "18"
# 5) "gender"
# 6) "man"

# 查所有 字段
HKEYS user:3
# 1) "name"
# 2) "age"
# 3) "gender"

# 查所有 值
HVALS user:3
# 1) "wangwu"
# 2) "18"
# 3) "man"

# 字段自增长
HINCRBY user:3 age 2
# (integer) 20

# 不存在则新增
HSETNX user:3 name wangwu2
# (integer) 0

# 2.6. List 类型

说明:

  • 类似 Java 中的 LinkedList
  • 双向链表,支持 正向、反向 检索

特征:

  • 有序
  • 元素可重复
  • 插、删 快
  • 查 慢

应用:

  • 通用用于存储有序数据,比如 XX排行榜

常用命令:

  • LPUSH key element ...: 左侧(队首)插入 一个或多个 元素

  • LPOP key: 左侧(队首)移除并返回第一个元素,没有则返回 nil

  • RPUSH key element ...: 右侧(队尾)插入 一个或多个 元素

  • RPOP key: 右侧(队尾)移除并返回第一个元素,没有则返回 nil

  • LRANGE key start end: 返回范围内所有元素,索引从 0 开始

  • BLPOP / BRPOP: 与 LPOP/RPOP 类似,没有元素时阻塞指定时间

示例:

LPUSH users u1 u2 u3
#=> u3 u2 u1

RPUSH users u4 u5 u6
#=> u3 u2 u1 u4 u5 u6

LPOP users
# u3 
#=> u2 u1 u4 u5 u6

RPOP users
# u6
#=> u2 u1 u4 u5 

LRANGE users 1 2
# 1) "u1"
# 2) "u4"

BLPOP users2 10
# (nil)
# (10.03s)

思考:

  • 如何利用 List 结构模拟一个栈?

    • 入口 和 出口 在同一边
    • 左入 左出
    • 右入 右出
  • 如何利用 List 结构模拟一个队列?

    • 入口 和 出口 在不同边
    • 左入 右出
    • 右入 左出
  • 如何利用 List 结构模拟一个阻塞队列?

    • 入口 和 出口 在不同边
    • 出队时使用 BLPOP 或 BRPOP

# 2.7. Set 类型

说明:

  • 与 Java 中的 HashSet 类似
  • 可以看作一个 HashMap (value 都为 null)

特征:

  • 无序
  • 元素不可重复
  • 查 快
  • 支持 交集、并集、差集

常用命令:

  • SADD key member ...: 增加元素
  • SREM key member ...: 删除元素
  • SCARD key: 元素个数
  • SISMEMBER key member: 是否包含
  • SMEMBERS: 返回所有元素
  • SINTER key1 key2 ...: 交集
  • SDIFF key1 key2 ...: 差集,key1 有 而 key2 无
  • SUNION key1 key2 ...: 并集

示例:

SADD s1 a b c
# (integer) 3

SCARD s1
# (integer) 3

SMEMBERS s1
# 1) "c"
# 2) "b"
# 3) "a"

SREM s1 a
# (integer) 1

SMEMBERS s1
# 1) "c"
# 2) "b"

SADD s2 a b c
# (integer) 3
SADD s3 b c d
# (integer) 3

SINTER s2 s3
# 1) "c"
# 2) "b"

SDIFF s2 s3
# 1) "a"

SUNION s2 s3
# 1) "b"
# 2) "a"
# 3) "d"
# 4) "c"

# 2.8. SortedSet 类型

说明:

  • 可排序的 Set 集合
  • 每个元素都有一个 score 属性,元素基于 score 顺序排序

特征:

  • 可排序
  • 元素不可重复
  • 查 快

应用:

  • 排行榜

常用命令:

  • ZADD key score member: 增或改
  • ZREM key member: 删
  • ZSCORE key member: 查 元素的 score
  • ZRANK key member: 查 元素的排名,从 0 开始
  • ZCARD key: 查 元素个数
  • ZCOUNT key min max: score 在 [min, max] 的元素个数
  • ZINCRBY key increment member: 增加 元素 的 score
  • ZRANGE key min max: 排序后,排名 在 [min, max] 的元素
  • ZRANGEBYSCORE key min max: 排序后,score 在 [min, max] 的元素
  • ZDIFF / ZINTER / ZUNION

注意:

  • 排名是升序,降序可以使用 ZREV 前缀

示例:

ZADD stus 85 Jack 89 Lucy 82 Rose 95 Tom 78 Jerry 92 Amy 76 Miles
# (integer) 7

# 删除 Tom
ZREM stus Tom
# (integer) 1

# Amy 的分数
ZSCORE stus Amy
# "92"

# Rose 的排名
ZRANK stus Rose
# (integer) 2

# 80 以下的元素个数
ZCOUNT stus 0 80
# (integer) 2

# Amy 加 2 分 
ZINCRBY stus 2 Amy
# "94"

# 分数前 3 的同学
ZREVRANGE stus 0 2
# 1) "Amy"
# 2) "Lucy"
# 3) "Jack"

# 80 以下的元素
ZRANGEBYSCORE stus 0 80
# 1) "Miles"
# 2) "Jerry"

# 3. Redis 的 Java 客户端

Connect with Redis client API libraries: https://redis.io/docs/latest/develop/clients/

常用客户端:

  • Jedis
  • lettuce
  • Redisson

Jedis:

  • 以 Redis 命令作为方法名称
  • 学习成本低,简单实用
  • 线程不安全。一个线程 一个 Jedis 实例,需要配合线程池来使用。

Lettuce:

  • 基于 Netty 实现
  • 支持 同步、异步、响应式编程,线程安全
  • 支持 Redis 的哨兵模式、集群模式、管道模式

Redisson:

  • 基于 Redis 实现了一套 分布式、可伸缩的数据结构 集合,
  • 比如 Map、Queue、Lock、Semaphore、AtomicLong

Spring Data Redis:

  • 提供了一套通用的 API 接口
  • 接口实现 可以是 Jedis 也可以是 Lettuce

# 3.1. Jedis 客户端

# 3.1.1. Jedis 快速入门

使用步骤:

  1. 引入依赖
  2. 建立连接
  3. 使用
  4. 释放资源

官网:

  • https://github.com/redis/jedis

依赖:

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>6.2.0</version>
</dependency>

示例:

public class TestJedis {
    private Jedis jedis;

    @BeforeEach
    void setup() {
        // 建立连接
        jedis = new Jedis("127.0.0.1", 6379);

        // 密码
        // jedis.auth("");

        // 选择库(默认 0 号库)
        jedis.select(0);
    }

    @Test
    void testString() {
        String result = jedis.set("name", "张三");

        System.out.println("result = " + result); //=> OK

        String name = jedis.get("name");

        System.out.println("name = " + name); //=> 张三
    }

    @Test
    void testHash() {
        jedis.hset("user:1", "name", "李四");
        jedis.hset("user:1", "age", "18");

        Map<String, String> map = jedis.hgetAll("user:1");

        System.out.println("map = " + map); //=> {name=李四, age=18}
    }

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

# 3.1.2. Jedis 连接池

说明:

  • Jedis 是线程不安全的
  • 频繁 创建、销毁 连接,会有性能损耗
  • 因此,推荐使用 Jedis 连接池

示例:

public class JedisConnectionFactory {
    private static final JedisPool jedisPool;

    static {
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();

        // 最大连接数
        jedisPoolConfig.setMaxTotal(8);
        // 最大空闲连接
        jedisPoolConfig.setMaxIdle(8);
        // 最小空闲连接 (常驻连接数)
        jedisPoolConfig.setMinIdle(0);

        Duration duration = Duration.of(1, ChronoUnit.SECONDS);
        // (从连接池获取连接,如果没有连接)最长等待时间
        jedisPoolConfig.setMaxWait(duration);

        jedisPool = new JedisPool(
                jedisPoolConfig,
                "127.0.0.1",
                6379,
                // 超时时间
                1000
        );
    }

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

# 3.2. SpringDataRedis 客户端

# 3.2.1. 介绍

说明:

  • SpringData 是 Spring 中 操作数据库 的模块
  • 对 Redis 的集成模块叫做 SpringDataRedis
  • 官网: https://spring.io/projects/spring-data-redis

特点:

  • 对不同 Redis 客户端的整合(Lettuce 和 Jedis)
  • 使用 RedisTemplate API 来操作 Redis

支持:

  • Redis 的发布订阅模式
  • Redis 哨兵、Redis 集群
  • Lettuce 的响应式编程
  • 基于 JDK、JSON、字符串、Spring对象 的序列化和反序列化
  • 基于 Redis 的 JDKConnection 实现

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 - 通用的命令

# 3.2.2. 快速开始

步骤:

  1. 依赖
  2. 配置
  3. 使用

依赖:

<!-- 默认 引入 io.lettuce:lettuce-core -->
<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>

配置:

spring:
  data:
    redis:
      host: localhost
      port: 6379
      # 指定使用 哪个 Redis 客户端实现
      lettuce:
        # 配置线程池后,线程池才会生效
        pool:
          max-active: 8
          max-idle: 8
          min-idle: 0
          max-wait: 1000ms

使用:

@SpringBootTest
public class TestSpringDataRedis {
    @Autowired
    private RedisTemplate redisTemplate;

    @Test
    void testString() {
        ValueOperations valueOperations = redisTemplate.opsForValue();

        valueOperations.set("name", "张三");

        Object name = valueOperations.get("name");
        System.out.println("name = " + name);
    }
}

# 3.2.3. 序列化: 自定义 RedisTemplate

说明:

  • 默认情况下, RedisTemplate 存入 Redis 的 键和值 都被序列化为字节了
  • 因此,需要自定义 键和值 的序列化器

序列化:

  • key 使用 StringRedisSerializer, 只能是字符串
  • value 使用 Jackson2JsonRedisSerializer, 则可以是任意类型
    • 如果未引入 SpringMVC,则需要显式引入 jackson-databind 依赖

自定义 RedisTemplate 对象:

@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        // 创建 RedisTemplate 对象
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();

        // 设置 连接工厂
        redisTemplate.setConnectionFactory(connectionFactory);

        // 创建 JSON 序列化器
        GenericJackson2JsonRedisSerializer jsonRedisSerializer = new GenericJackson2JsonRedisSerializer();

        // 设置 key 的序列器
        redisTemplate.setKeySerializer(RedisSerializer.string());
        redisTemplate.setHashKeySerializer(RedisSerializer.string());

        // 设置 value 的序列器
        redisTemplate.setValueSerializer(jsonRedisSerializer);
        redisTemplate.setHashValueSerializer(jsonRedisSerializer);

        // 返回
        return redisTemplate;
    }
}

使用:

@SpringBootTest
public class TestSpringDataRedis {
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Test
    void testString() {
        ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();

        valueOperations.set("name", "张三");

        Object name = valueOperations.get("name");
        System.out.println("name = " + name);
    }

    @Test
    void testObject() {
        ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();

        // 存: value 为 Pojo
        valueOperations.set("user:100", new User("张三", 18));
        /* redis =>
        {
          "@class": "org.example.pojo.User",
          "name": "张三",
          "age": 18
        }
        */

        // 取
        User user = (User) valueOperations.get("user:100");
        System.out.println("user = " + user);
        //=> User(name=张三, age=18)

        // 存: value 为 List
        List<User> userList = new ArrayList<>();
        userList.add(new User("李四", 21));
        userList.add(new User("王五", 28));
        valueOperations.set("userList:1", userList);
        /* redis =>
        [
          "java.util.ArrayList",
          [
            {
              "@class": "org.example.pojo.User",
              "name": "李四",
              "age": 21
            },
            {
              "@class": "org.example.pojo.User",
              "name": "王五",
              "age": 28
            }
          ]
        ]
        */

        // 取
        List<User> list = (List<User>) valueOperations.get("userList:1");
        System.out.println("list = " + list);
        //=> [User(name=李四, age=21), User(name=王五, age=28)]
    }
}

# 3.2.4. 序列化: StringRedisTemplate

说明:

  • SpringDataRedis 提供了一个 StringRedisTemplate
  • key、value 都是要字符串序列化器
  • value 如果是对象,则需要手动 序列化、反序列化

示例:

@SpringBootTest
public class TestStringRedisTemplate {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Test
    void testString() {
        ValueOperations<String, String> valueOperations = stringRedisTemplate.opsForValue();

        valueOperations.set("name", "张三");

        Object name = valueOperations.get("name");
        System.out.println("name = " + name);
    }

    @Test
    void testObject() throws JsonProcessingException {
        ValueOperations<String, String> valueOperations = stringRedisTemplate.opsForValue();

        ObjectMapper objectMapper = new ObjectMapper();

        User user = new User("张三", 18);

        // 手动序列化
        String userJsonStr = objectMapper.writeValueAsString(user);

        valueOperations.set("user:20", userJsonStr);

        String userJsonStrFromRedis = valueOperations.get("user:20");

        // 手动反序列化
        User userFromRedis = objectMapper.readValue(userJsonStrFromRedis, User.class);

        System.out.println("userFromRedis = " + userFromRedis); //=> User(name=张三, age=18)
    }
}

# 3.2.5. 序列化: 总结

RedisTemplate 的两种序列化方案

1)自定义 RedisTemplate

  • 说明: 修改序列化器,如下
    • key: StringRedisSerializer
    • value: GenericJackson2JsonRedisSerializer
  • 优点: 自动 序列化、反序列化 对象类型
  • 缺点: 序列化对象类型,会将该对象的 类的字节码 也存入,占用大量存储空间

2)StringRedisTemplate

  • 说明: StringDataRedis 提供的对象,key 和 value 都使用 StringRedisSerializer
  • 优点: 干净
  • 缺点: 手动 序列化、反序列化 对象类型

# 3.2.6. 操作 Hash 类型

@SpringBootTest
public class TestHash {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Test
    void testHash() {
        HashOperations<String, String, Object> hashOperations = stringRedisTemplate.opsForHash();

        // 写 key 的单个属性
        hashOperations.put("user:30", "name", "小明");
        hashOperations.put("user:30", "age", "18"); // value 不能是 数字

        // 写 key 的所有属性
        Map<String, Object> map = new HashMap<>();
        map.put("name", "小红");
        map.put("age", "28");
        hashOperations.putAll("user:31", map);

        // 读 key 的单个属性
        Object name = hashOperations.get("user:30", "name");
        System.out.println("name = " + name);

        // 读 key 的所有条目
        Map<String, Object> entries = hashOperations.entries("user:31");
        System.out.println("entries = " + entries);
    }
}
本章目录