Redis——基础篇

本文最后更新于:2 个月前

引言

Redis,作为一种高性能的键值数据库,自2009年诞生以来,因其出色的性能和丰富的数据结构支持,已成为现代数据处理中不可或缺的工具。它的设计为处理高速数据操作和实时应用提供了理想的解决方案。本文将介绍Redis的主要特性、安装过程、基本命令、以及如何通过Java客户端与之交互,帮助读者快速掌握Redis的使用和应用。

简介

Redis诞生于2009年,全称为Remote Dictionary Server,即远程词典服务器,是一个开源的高性能键值数据库。因其支持字符串、哈希、列表、集合、有序集合、位图、地理空间索引等多种类型的数据结构,又被称为数据结构服务器。

特征

  • 高性能: Redis将其数据主要存储在内存中,同时大部分操作都是单线程的、避免了不必要的上下文切换和竞态条件,加上搞笑的数据结构和算法,使得它提供极高的性能,能够支持每秒高达数十万次的读写操作。
  • 持久化: 尽管是基于内存的,Redis也提供了灵活的持久化选项,如RDB(Redis 数据库文件)快照和AOF(Append Only File)日志,确保数据安全。
  • 数据结构丰富: Redis支持多种数据结构,使得它在应对不同场景时非常灵活,比如缓存、消息队列系统、应用排行榜、计数器等。
  • 发布/订阅消息系统: Redis支持发布/订阅模式,能够构建实时的消息系统。
  • 原子操作: Redis中的大多数操作都是原子性的,这意味着在并发环境中执行时,Redis可以保证这些命令的安全执行。
  • 事务支持: Redis支持事务功能,允许通过一系列命令的批量执行来进行复合操作。
  • 高可用和分布式: 通过Redis Sentinel提供高可用性,并且通过Redis Cluster提供自动分区的支持。
  • 多语言客户端:广泛的客户端支持,几乎所有的编程语言都有成熟的Redis客户端库,使得Redis可以轻松集成到各种应用程序和开发环境中。

应用场景

  • 缓存系统: 利用Redis的高速数据访问能力,常用作应用的缓存层,减轻后端数据库的压力,提高数据访问速度。
  • 会话缓存(Session Cache): 在Web应用中广泛用于存储用户会话。
  • 消息队列系统: 利用Redis的发布/订阅功能以及列表操作,实现消息队列的功能,支持高并发消息传递。
  • 实时计数系统: 如网站访问计数、在线用户统计等,Redis的原子操作非常适合。
  • 排行榜或计数器: 利用Redis的有序集合,可以非常方便地实现各类排行榜功能。

源码安装

  1. 安装编译Redis所需的基础工具和开发库

    1
    sudo yum install -y gcc make
  2. 下载源码压缩包

    1
    wget http://download.redis.io/releases/redis-6.2.6.tar.gz
  3. 解压源码压缩包

    1
    tar -zxvf redis-6.2.6.tar.gz
  4. 编译及测试

    1
    2
    3
    make
    make test
    make install
  5. 创建配置文件

    1
    2
    sudo mkdir /etc/redis
    sudo cp redis.conf /etc/redis/redis.conf
  6. 可选:编辑配置文件,设置外网访问及后台运行

    1
    2
    3
    4
    5
    bind 0.0.0.0

    requirepass [redis连接密码]

    daemonize yes
  7. 可选:创建systemd服务文件,确保redis在系统重启后自动启动

    1
    sudo vim /etc/systemd/system/redis.service

    粘贴以下内容

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    [Unit]
    Description=Redis In-Memory Data Store
    After=network.target

    [Service]
    User=root
    Group=root
    ExecStart=/usr/local/bin/redis-server /etc/redis/redis.conf
    ExecStop=/usr/local/bin/redis-cli shutdown
    Restart=always

    [Install]
    WantedBy=multi-user.target

    保存并退出,重新加载systemd管理器配置并启动redis服务

    1
    2
    3
    sudo systemctl daemon-reload
    sudo systemctl enable redis
    sudo systemctl start redis

    需要注意:如果使用systemd来管理Redis服务,应该让systemd完全控制服务的启动、停止和重启,此时redis配置文件中的daemonize应设置为no。如果Redis配置为守护进程模式(即 daemonize yes),这会与systemd的操作发生冲突。因为Redis尝试自己分离出一个守护进程,systemd可能会认为主进程已经退出,这导致systemd认为服务已经“停止”,然后根据配置尝试重新启动它,从而形成一个不断的重启循环。

  8. 验证

    1
    2
    3
    sudo systemctl status redis

    redis-cli

系统级命令

redis-server

命令说明:用于启动Redis服务器实例的命令行工具,通过多种方式配置和启动Redis服务。

常用选项

  • /path/redis.config:通过指定的配置文件启动。

  • --port <port>:设置Redis侦听的端口号。

  • --bind <address> :绑定到一个或多个接口,如果需要多个地址,可以重复这个选项。

  • --conf <file>:指定配置文件的路径。

  • --loglevel <level>:设置日志级别(debug、verbose、notice、warning)。

  • --logfile <filename>:指定日志文件路径。如果设置为stdout,日志将输出到标准输出。

  • --daemonize yes:指示Redis以守护进程模式运行。

  • --pidfile <file>:设置PID文件的路径,这在以守护进程形式运行时非常有用。

  • --protected-mode [yes|no]:设置保护模式,当Redis没有设置密码,并且绑定到公共接口时,拒绝客户端的连接。

  • --dir <directory>:指定持久化文件存储的目录。

  • --dbfilename <filename>:指定持久化文件的文件名。

  • --appendonly [yes|no]:是否开启AOF (Append Only File) 持久化模式。

redis-cli

命令说明:Redis的命令行接口客户端,它用于与Redis服务器进行交互。通过redis-cli可以执行各种 Redis 命令,查看服务器状态,进行调试,以及管理数据。

常用选项

  • -h:指定服务器的主机名。
  • -p:指定服务器的端口。
  • -a:指定连接密码。
  • --raw:以原始格式输出(例如,不转义字符串中的特殊字符)。
  • -c:启用集群模式,允许 redis-cli 在Redis集群节点间自动重定向。
  • --scan:以迭代方式列出所有的键,可以与-p--pattern参数一起使用。
  • --pipe:使用管道模式,可以通过标准输入批量执行命令。
  • --stat:提供Redis服务器的统计信息,如每秒命令执行数等。
  • --bigkeys:找出并报告数据库中的大键。
  • --help:显示帮助信息,列出所有可用的命令和选项。
  • --version:显示redis-cli的版本信息。

redis-benchmark

命令说明:Redis提供的一个性能测试工具,用于通过运行特定的命令序列来测试Redis服务器的性能。它可以快速生成大量请求,测量Redis实例在不同负载下的响应速度和处理能力。这个工具对于评估Redis配置的性能、比较不同硬件或软件设置的影响,或者进行压力测试等都非常有用。

常用选项

  • -n <requests>:设置要执行的请求总数,默认是 100000。
  • -c <clients>:设置并发客户端的数量,默认是 50。
  • -t <tests>:指定要运行的测试类型,例如setgetincrlpush等等。
  • -d <size>:数据大小(以字节为单位),用于测试SET/GET等命令,默认是 3 字节。
  • --csv:输出结果以CSV格式,适合导入到表格或数据库中分析。
  • -k <0/1>:1(默认)表示保持连接开启(pipelining),0 表示每个请求后关闭连接。
  • -P <number>:使用pipelining的方式来增加每个请求的响应时间,默认不启用。
  • -r:对setgetincrlpushrpopsaddspoplremlpoprpop等命令使用随机 key,避免对同一个 key 的重复操作。

通用命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 选择数据库
select <num>

# 查看当前数据库的key数量
dbsize

# 清空当前数据库
flushdb

# 清空所有数据库
flushall

# 查看所有key
keys *

# 查看是否存在key
exists key

# 将key转移到指定的数据库
move key index

# 设置过期时间
expire key second

# 查看key的存活时间
ttl key

# 查看key的类型
type key

数据结构及常用命令

字符串 (Strings)

  • 概述:字符串是Redis中最基本的类型,它可以存储任何形式的字符串(包括二进制数据),最大可以支持存储512MB

  • 用途:常用于存储文本或二进制数据,如缓存用户的邮箱、序列化的对象、图片或小文件等。

  • 操作:提供设置(SET)、获取(GET)、追加(APPEND)、多键获取(MGET)等操作。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    # 设置键key的值为value
    set key value

    # 获取键key对应的值
    get key

    # 删除键key
    del key

    # 若键key存在,则对值后面拼接字符串value,若键key不存在,设置键key的值为value
    append key value

    # 获取键key对应的值的字符串长度
    strlen key

    # 将键key对应的值自增(只针对Integer类型的值)
    incr key

    # 将键key对应的值自减(只针对Integer类型的值)
    decr key

    # 将键key对应的值增加incrment(只针对Integer类型的值)
    incrby key incrment

    # 将键key对应的值减少decrment(只针对Integer类型的值)
    decrby key decrment

    # 获取键key对应的值特定范围的内容,下标区间为[start,end],end为-1代表到字符串结束
    getrange key start end

    # 设置键key指定下标的内容为value,替换区间为[offset, offset + value.length]
    setrange key offset value

    # 设置键key的值为value,且过期时间为seconds(秒)
    setex key seconds value

    # 如果键key不存在,则设置键key对应的值为value,键key存在不进行设置
    setnx key value

    # 设置多个键值对,如果命令后面出现前面已存在的键key,则会覆盖前面设置的值
    mset key value [key value...]

    # 获取多个值
    mget key [key]

    # 如果键key不存在(于数据库中),则设置键key对应的值为value,键key存在不进行设置
    msetnx key value [key value...]

    # 设置键key对应的值为value,返回上一次设置的值
    getset key value

哈希 (Hashes)

  • 概述:哈希是键值对集合,适用于存储对象。

  • 用途:非常适合存储和表示对象(类似于编程语言中的字典)。例如,可以用哈希存储用户的属性,如name、age等。

  • 操作:可以一次性设置或获取多个字段(HSET, HGET, HMSET, HMGET),删除字段(HDEL),检查字段是否存在(HEXISTS)等。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    # 设置键值对key,对应的值为键值对<field,value>
    hset key field value

    # 获取键值对key中键field的值
    hget key field

    # 设置键值对key的多个键及其对应值
    hmset key filed value [field value ...]

    # 获取键值对key的多个键的对应值
    hmget key field [filed ...]

    # 获取键值对key中所有的键及其对应值
    hgetall key

    # 删除键值对key中的键field
    hdel key field [field ...]

    # 获取键值对key中键的数量
    hlen key

    # 查询键值对key中是否存在键field
    hexists key field

    # 获取键值对key中的所有键
    hkeys key

    # 获取键值对key中的所有值
    hvals key

    # 将键值对key对应的键field对应值增加incrment
    hincrby key field incrment

    # 如果键值对key中不存在键field,则设置键field的值为value,存在则不进行设置
    hsetnx key field value

列表 (Lists)

  • 概述:列表是简单的字符串列表,按插入顺序排序。你可以在列表的头部或尾部添加元素。

  • 用途:适合实现消息队列,通过将元素从列表头部插入,并从尾部移除来模拟队列的操作。

  • 操作:添加元素到头部(LPUSH)或尾部(RPUSH),移除并获取头部(LPOP)或尾部元素(RPOP),修剪(LTRIM)等。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    # 往列表key头部添加值value
    lpush key value [value...]

    # 往列表key尾部添加值value
    rpush key value [value...]

    # 获取列表key下表范围为[start,stop]的值,stop为-1代表到列表结尾
    lrange key start stop

    # 弹出列表key的第一个值
    lpop key

    # 弹出列表key的最后一个值
    rpop key

    # 获取列表key下标为index的值
    lindex key index

    # 获取列表key的长度
    llen key

    # (从头开始)移除列表key中count个与value相等的值
    lrem key count value

    # 移除列表key中下标[start,stop]以外的值
    ltrim key start stop

    # 将列表source的最后一个值弹出并添加到列表destination头部
    rpoplpush source destination

    # 将列表key下标为index处的值设置为value
    lset key index value

    # 在列表元素pivot前面|后面插入value
    linsert key before|after pivot value

集合 (Sets)

  • 概述:集合是字符串的无序集合,它保证内部存储的元素是唯一的。

  • 用途:适合存储没有重复的元素,例如,存储一个用户所有喜欢的标签,集合可以自动去重。

  • 操作:添加(SADD)、移除(SREM)、检查存在性(SISMEMBER)、集合间的运算如交集(SINTER)、并集(SUNION)和差集(SDIFF)等。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    # 往集合key中添加元素member
    sadd key member [member...]

    # 获取集合key中的所有元素
    smenbers key

    # 查询集合key中是否含有元素member的成员之一
    sismember key member

    # 获取集合key里面的元素个数
    scard key

    # 移除集合key中的元素member
    srem key member [member...]

    # 随机获取集合key中的count个元素,count为空则代表1
    srandmember key [count]

    # 随机弹出集合key中的count个元素,count为空则代表1
    spop key [count]

    # 将集合source中的元素member转移到集合destination中
    smove source destination member

    # 获取存在于集合key1但不存在于集合key2的所有元素
    sdiff key1 [key2...]

    # 获取集合key1和集合key2的交集
    sinter key1 key2

    # 获取集合key1和集合key2的并集
    sunion key1 key2

有序集合 (Sorted Sets)

  • 概述:有序集合类似于集合,但每个元素都会关联一个浮点数分数,Redis正是通过分数来为集合中的元素提供了顺序。

  • 用途:非常适合需要按顺序访问数据的场景,如排行榜、带权重的队列等。

  • 操作:添加元素(ZADD)、删除元素(ZREM)、修改元素的分数(ZINCRBY)、获取元素的排名(ZRANK)等。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    # 往有序集合key中添加分数为score的元素member,集合中的元素会根据分数从小到大排序,分数相同则按首字母排序
    zadd key score member [score member ...]

    # 获取有序集合key中排名为[start,stop]的元素,withscores不为空则可显示对应分数
    zrange key start stop [withscores]

    # 获取有序集合key中分数为[min,max]的元素,withscores不为空则可显示对应分数,limit可指定从offset处开始截取count个元素,min为-inf表示负无穷,+inf表示正无穷
    zrangebyscore key min max [withscores] [limit offset count]

    # 移除有序集合key中元素member
    zrem key member [member...]

    # 获取有序集合key的元素数量
    zcard key

    # 获取有序集合key中元素member的排位(从小到大顺序,起始为0)
    zrank key member

    # 获取有序集合key中元素member的排位(从大到小顺序,起始为0)
    zrevrank key member

位图 (Bitmaps)

  • 概述:位图本质上不是一种独立的数据结构,它是字符串的一种特殊操作方式,通过位来表示二进制数据。

  • 用途:适合进行大规模的位运算,常用于统计和分析,如用户登录、签到等。

  • 操作:设置位(SETBIT)、获取位(GETBIT)、统计位(BITCOUNT)、位运算(BITOP)等。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    # (以字节为单位分配字符串)设置位图key下标为offset的位的值为value,value默认为0,可设置为1
    setbit key offset value

    # 获取位图key下标为offset的位的值
    getbit key offset

    # 获取位图key上位为1的个数,可指定下标范围为[start, end]
    bitcount key [start end]

    # 对位图key进行操作(operation为and、or、xor、not运算),将结果存储到destkey
    bitop operation destkey key [key ...]

    # 查询位图key中第一个为bit(0或1)的下标位置,可指定字节的开始与结束范围为[start,end]
    bitpos key bit [start] [end]

HyperLogLogs

  • 概述:HyperLogLog是一种概率型数据结构,用于高效地估计数据集中唯一元素的数量。

  • 用途:非常适合需要统计大量数据的去重数量,而对精确值要求不是非常严格的场景。

  • 操作:添加元素(PFADD)、计数(PFCOUNT)、合并多个HyperLogLog(PFMERGE)等。

    1
    2
    3
    4
    5
    6
    7
    8
    # 往集合key中添加元素element
    pfadd key element [element...]

    # 获取多个集合key的技术估算值
    pfcount key [key...]

    # 将多个集合sourcekey合并为一个集合destkey
    pfmerge destkey sourcekey [sourcekey...]

地理空间索引 (Geospatial Indexes)

  • 概述:Redis的地理空间索引是通过有序集合实现的,允许你存储经度和纬度坐标作为元素。

  • 用途:适用于地理位置的存储、查询和半径搜索,如查询某范围内的位置点。

  • 操作:添加地理空间坐标(GEOADD)、查询半径内的元素(GEORADIUS)、获取两点之间的距离(GEODIST)等。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    # 往集合key中添加经度为longitudu、纬度为latitude的元素member
    geoadd key longitudu latitude member [longitudu latitude member ...]

    # 获取集合key中指定元素member的经纬度信息
    geopos key member [member...]

    # 获取集合key中元素member1和元素member2的距离,单位为空时代表“m”
    geodist key member1 member2 [m|km|ft|mi]

    # 获取集合key中的元素经纬度字符串
    geohash key member [member...]

    # 获取集合key中在经度longitude、维度latitude、半径radius[m|km|ft|mi]内的元素,withdist可显示距离,withcoord可显示经纬度,count n可限制数量为n,asc升序或desc降序
    georadius key longitude latitude radius [m|km|ft|mi] [withcoord] [withdist] [withhash] [asc|desc] [count n]

    # 获取集合key中在元素member半径radius[m|km|ft|mi]内的元素(含自身)
    georadiusbymember key member radius [m|km|ft|mi] [withcoord] [withdist] [withhash] [asc|desc] [count n]

    # 删除集合key中的元素member(geo并没有删除,底层通过zset实现,因此用zrem)
    zrem key member [member...]

Java客户端

Jedis

简介

Jedis是一个比较直接和轻量级的Redis客户端实现,提供了简单直接的API来与Redis进行交互。它支持几乎所有的Redis特性,并且由于其简洁性,是学习和实现Redis操作的好选择。 官方文档地址:Jedis guide | Docs (redis.io)

优点

  • 轻量级和易于使用。
  • 支持连接池。

缺点

  • 不支持异步和非阻塞 I/O。
  • 多线程环境下管理连接池较为复杂。
  • API较为基础,没有分布式护具结构和高级事务处理等功能。

示例

单个连接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class JedisTest {

private Jedis jedis;

@BeforeEach
public void before() {
jedis = new Jedis("127.0.0.1", 6379);
// jedis.auth("xxxx");
jedis.select(0);
}

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

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

}

运行程序,控制台输出如下:

1
2
result = OK
name = yang

Jedis也支持连接池,示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class JedisConnectionFactory {

private static final JedisPool JEDIS_POOL;

static {
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
// 最大连接数
jedisPoolConfig.setMaxTotal(10);
// 最大空闲连接
jedisPoolConfig.setMaxIdle(5);
// 最小空闲链接
jedisPoolConfig.setMinIdle(0);
// 等待时长
jedisPoolConfig.setMaxWaitMillis(1000);
JEDIS_POOL = new JedisPool(jedisPoolConfig, "127.0.0.1", 6379, 1000);
}

// 获取连接资源
public static Jedis getJedis() {
return JEDIS_POOL.getResource();
}

public static void main(String[] args) {
Jedis jedis = JedisConnectionFactory.getJedis();
String result = jedis.set("name", "yang2");
System.out.println("result = " + result);
String name = jedis.get("name");
System.out.println("name = " + name);
jedis.close();
}

}

运行程序,控制台输出如下:

1
2
result = OK
name = yang2

lettuce

简介

Lettuce是一个高级Redis客户端,提供异步、基于事件的API,并且内部使用Netty实现非阻塞通信。Lettuce可以在多个线程间共享单个连接,而不是使用传统的连接池。

优点

  • 支持同步、异步和反应式编程模型。
  • 内部使用Netty,实现了高效的非阻塞I/O。
  • 允许连接多路复用,大大减少线程和连接的使用。
  • 支持自动重连和命令重试。

缺点

  • 使用了异步网络应用框架Netty,在处理大量并发连接时可能会消耗更多系统资源。
  • API和配置相对复杂,尤其是高级功能的配置和优化。
  • 要求开发者在错误处理和回调管理上更加小心,否则可能导致难以追踪的错误和内存泄漏。

Redisson

简介

Redisson是Redis的高级客户端之一,提供了丰富的Redis对象和服务,如分布式锁、集合、映射、有序集合、发布/订阅、Bloom filter 等高级功能。

优点

  • 提供了许多Jedis和Lettuce中不包含的高级分布式Java对象和服务。
  • 支持同步、异步和反应式接口。
  • 内置Redis命令的高级抽象。
  • 非常适合需要利用Redis提供分布式数据结构和服务的应用程序。

缺点

  • 由于提供了大量高级功能和抽象,Redisson可能会比其他更轻量级的客户端使用更多的内存。
  • 一些高级特性,如分布式锁或数据结构,可能引入额外的性能开销,特别是在高负载或大规模部署情况下。

三者对比

性能和并发处理

  • LettuceRedisson提供异步处理能力,对于需要处理高并发请求的应用更加合适。
  • Jedis由于其同步的实现方式,适合简单的应用场景,对于复杂的或高并发的场景则可能成为瓶颈。

API丰富度

  • Redisson提供的API最为丰富,支持大量的分布式数据结构和服务。
  • LettuceJedis提供的API较为基础,主要围绕Redis的核心功能。

连接管理

  • Lettuce提供连接多路复用,减少了连接总数的需求。
  • Jedis依赖于传统的连接池来管理多个连接。
  • Redisson支持连接池和单个连接的多路复用。

SpringDataRedis

Spring Data Redis是Spring框架为Redis数据库提供的一个高级抽象模块,它简化了在Java应用中使用Redis的方式。通过Spring Data Redis,可以利用Spring框架提供的各种便利功能,如声明式事务支持、简化的数据访问模式和数据存取的透明化,从而使得与Redis的交互更加简单和高效。官方文档地址:Spring Data Redis

核心特性

  • 简化配置:Spring Data Redis提供了易于使用的配置方式,可以快速集成Redis到Spring应用中。
  • 模板 APIRedisTemplateStringRedisTemplate 提供了丰富的方法来操作Redis,包括对keysstringslistssetshashes等数据结构的操作。
  • 存储库支持:类似于其他Spring Data模块,Spring Data Redis支持通过创建接口来定义存储库,用于访问Redis keys`和进行复杂查询。
  • 对象映射:通过Spring Data的对象映射特性,可以将对象透明地存储在Redis中,而无需手动转换数据格式。
  • 事务支持:支持Redis事务,并能与Spring的声明式事务管理无缝整合。
  • 发布订阅:支持Redis的发布/订阅功能,可以在应用中实现消息通信模式。
  • 高级序列化:支持多种序列化和反序列化机制,如JDK序列化、JSON序列化等。

使用场景

  • 缓存实现:利用Redis强大的性能作为应用的缓存,减少数据库访问,提高响应速度。
  • 会话存储:在分布式系统中,使用Redis存储会话信息,确保各个节点之间会话的一致性。
  • 全局锁:使用Redis实现分布式锁,确保分布式环境下资源同步访问。
  • 实时消息系统:利用Redis的发布/订阅功能实现实时消息系统。

与Jedis、Lettuce的关系

客户端库作为依赖

  • Jedis:在Spring Data Redis的早期版本中,Jedis是主要使用的客户端。它是一个简单且直接的客户端库,提供了同步的API来与Redis进行交互。Spring Data Redis通过封装Jedis提供了一套更为简洁和Spring风格的操作方式。
  • Lettuce:随着对响应式编程的支持和需求的增加,Lettuce成为了Spring Data Redis的首选客户端。Lettuce提供非阻塞和异步的API,且基于Netty实现、支持连接多路复用,适合需要高并发处理的场景。Spring Data Redis利用Lettuce提供了包括响应式编程在内的高级特性。

配置与抽象层

  • 在Spring Data Redis中,可以自由选择使用Jedis或Lettuce作为底层连接库。Spring提供了统一的配置接口和操作模板(如RedisTemplate和响应式的ReactiveRedisTemplate),这些模板抽象出了底层客户端的具体实现细节,使得开发者可以不必关心底层连接库是使用的Jedis还是Lettuce。
  • 开发者在使用Spring Data Redis时,可以通过简单的配置更改所使用的客户端库,而不需要修改业务代码,这提供了极大的灵活性和便利性。

实际开发

在选择RedisTemplate的底层实现时,可以根据以下几个方面来决定:

  1. 性能需求
    • 如果应用需要高性能的阻塞操作,且并发需求不高,可以选择Jedis。
    • 对于需要高并发访问的应用,Lettuce的非阻塞和多路复用特性可能更适合。
  2. 并发模型
    • 在微服务架构中,服务通常需要处理大量并发请求,因此选择支持异步处理的客户端(如Lettuce)会更合适。
  3. 功能支持
    • 根据应用需求选择支持特定功能的客户端,例如,如果需要详细的连接管理、自动重连等高级功能,Lettuce 可能是更好的选择。
  4. 资源利用
    • 考虑到资源利用效率,Lettuce的连接多路复用可以减少连接总数,从而降低资源消耗。

示例:项目中Redis配置类(使用Lettuce)

1
2
3
4
5
6
7
8
9
10
11
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.username=xxx
spring.redis.password=xxx
spring.redis.database=0
# 由于Lettuce 自身支持连接多路复用,因此不需要传统意义上的连接池
#spring.redis.lettuce.pool.enabled=true
#spring.redis.lettuce.pool.max-active=8
#spring.redis.lettuce.pool.max-idle=8
#spring.redis.lettuce.pool.min-idle=0
#spring.redis.lettuce.pool.max-wait=-1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Configuration
public class RedisConfig {

@Bean
public LettuceConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(); // Spring Boot默认使用Lettuce,可不用显式配置
}

@Bean
public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
// 使用Letture连接工厂
redisTemplate.setConnectionFactory(redisConnectionFactory);
return redisTemplate;
}

}

常用API

不同于Jedis将所有的方法都封装到一个类中,Spring Data Redis提供了RedisTemplate工具类,这个类中封装了各种对Redis的操作,把不同数据类型的操作API封装到不同的Operations对象中

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

Demo:注入一个RedisTemplate对象,先设置键值对,再获取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@SpringBootTest
public class RedisTemplateTest {

@Resource
private RedisTemplate<String, Object> redisTemplate;

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

}

运行程序,控制台输出如下:

1
name = yangtao

由控制台输出可知,RedisTemplate已正确设置键值对。

序列化

序列化器

上一个DEMO中设置了一个key为name的键值对,但是通过命令行却看不到这个key

1
2
127.0.0.1:6379> keys *
1) "\xac\xed\x00\x05t\x00\x04name"

可以看到设置的键并不是name,这是因为RedisTemplate默认的序列化器为JDK的序列化器(通过org.springframework.data.redis.core.AbstractOperationskeySerializer()valueSerializer()方法可以查看)

Redis序列化器

我们可以将key的序列化器设置为更加常用的字符串序列化器StringRedisSerializer,而对于value通常设置为JSON序列化器GenericJackson2JsonRedisSerializer,Redis的配置类添加配置后如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@Configuration
public class RedisConfig {

@Bean
public LettuceConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory();
}

@Bean
public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
// 使用Letture连接工厂
redisTemplate.setConnectionFactory(redisConnectionFactory);
// 设置key序列化方式为String,value序列化方式为json
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(redisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(redisSerializer());
redisTemplate.afterPropertiesSet();
return redisTemplate;
}

public RedisSerializer<Object> redisSerializer() {
// 创建JSON序列化器
ObjectMapper objectMapper = new ObjectMapper();
// 设置可见度
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
return new GenericJackson2JsonRedisSerializer(objectMapper);
}

}

再次运行测试程序,这次RedisTemplate会使用字符换序列化器和JSON序列化器在redis中存入一个键值对,通过Redis控制台查看如下:

1
2
3
4
5
127.0.0.1:6379> keys *
1) "\xac\xed\x00\x05t\x00\x04name"
2) "name"
127.0.0.1:6379> get name
"\"yangtao\""

类标识

在使用RedisTemplate配置为JSON序列化器存储数据时,存入Redis的数据中包含类的标识信息(@class属性),如运行以下程序后,往Redis中存入一个HashMap对象

1
2
3
4
Map<String, Object> user1 = new HashMap<>();
user1.put("name", "yangtao");
user1.put("age", 18);
redisTemplate.opsForValue().set("user1", user1);

查看Redis数据库中键user1对应的值

1
2
127.0.0.1:6379> get user1
"[\"java.util.HashMap\",{\"name\":\"yangtao\",\"age\":18}]"

从结果可以看到值中包含了对象的类标识java.util.HashMap,这是因为JSON序列化器GenericJackson2JsonRedisSerializer被配置为存储类型信息,这种配置便于反序列化过程中能够精确地恢复出原始对象的类型。

注意事项:通过匿名内部类创建的HashMap对象,创建的是HashMap的一个匿名子类的实例,因此这个对象存入Redis后,值标识的类名是外部类信息的名称及其匿名类,如运行以下程序

1
2
3
4
5
6
7
Map<String, Object> user2 = new HashMap<String, Object>() {{
put("name", "zhangsan");
put("age", 19);
}};
redisTemplate.opsForValue().set("user2", user2);
Object user2Obj = redisTemplate.opsForValue().get("user2");
System.out.println("user2 = " + user2Obj);

运行出错,对应的错误日志为:

1
2
3
org.springframework.data.redis.serializer.SerializationException: Could not read JSON: Cannot construct instance of `space.yangtao.client.RedisTemplateTest$1` (no Creators, like default constructor, exist): no default constructor found
at [Source: (byte[])"["space.yangtao.client.RedisTemplateTest$1",{"name":"zhangsan","age":19}]"; line: 1, column: 45]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `space.yangtao.client.RedisTemplateTest$1` (no Creators, like default constructor, exist): no default constructor found
at [Source: (byte[])"["space.yangtao.client.RedisTemplateTest$1",{"name":"zhangsan","age":19}]"; line: 1, column: 45]

可以看到程序报错的原因为space.yangtao.client.RedisTemplateTest$1RedisTemplateTest的第一个匿名类)缺少默认的构造器,从Redis中查看对应的值为

1
2
127.0.0.1:6379> get user2
"[\"space.yangtao.client.RedisTemplateTest$1\",{\"name\":\"zhangsan\",\"age\":19}]"

可以看到值中类标识并不是HashMap,而是RedisTemplateTest的第一个匿名类。

因此,在使用RedisTemplate操作对象时,要尽量避免使用匿名类创建对象。

当然,这种问题也有解决方案,那就是类型擦除或手动序列化。

类型擦除

配置RedisTemplate的序列化器为Jackson2JsonRedisSerializer,或对GenericJackson2JsonRedisSerializerObjectMapper对象进行设置,使其序列化以后不包含类型信息

1
2
3
4
5
6
7
8
9
10
11
Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
template.setValueSerializer(serializer);
template.setHashValueSerializer(serializer);

// 或

ObjectMapper objectMapper = new ObjectMapper();
objectMapper.disable(MapperFeature.DEFAULT_VIEW_INCLUSION);
RedisSerializer<Object> serializer = new GenericJackson2JsonRedisSerializer(objectMapper);
template.setValueSerializer(serializer);
template.setHashValueSerializer(serializer);

类型擦除以后,存入Redis中的值没有类型,程序的反序列化便可成功

1
user2 = {name=zhangsan, age=19}
1
2
127.0.0.1:6379> get user2
"{\"name\":\"zhangsan\",\"age\":19}"

注意事项:不存储类型信息可能会在反序列化时导致问题,特别是在处理多态和复杂对象结构时。如果序列化的数据结构比较复杂或涉及继承,不包含类型信息可能会导致反序列化失败或数据不正确。

手动序列化

在操作RedisTemplate往Redis存入对象时,可以先将其进行序列化,从Redis取出对象时,再将其反序列化,如

1
2
3
4
5
6
7
8
Map<String, Object> user3 = new HashMap<String, Object>() {{
put("name", "lisi");
put("age", 20);
}};
ObjectMapper objectMapper = new ObjectMapper();
redisTemplate.opsForValue().set("user3", objectMapper.writeValueAsString(user3));
Object user3Obj = redisTemplate.opsForValue().get("user3");
System.out.println("user3 = " + objectMapper.readValue((String) user3Obj, Map.class));

相比于自动序列化,这种方法具有

优点

  1. 灵活性:开发者可以自由选择什么时候以及如何转换数据,适合特定的需求,如减小数据大小或处理复杂的数据结构。
  2. 减少数据冗余:手动转换可以避免存储不必要的类型信息,使存储在Redis中的数据更加精简。
  3. 依赖性:不会依赖序列化库(如Jackson)的更新对存取和更新有兼容性和稳定性有影响。

缺点

  1. 代码复杂性:需要手动管理数据的序列化和反序列化,增加了代码的复杂度和出错概率。

总结

通过本文的学习,读者应能够对Redis有一个全面的认识,从其基本概念到复杂的应用实现。Redis不仅支持多种数据结构,如字符串、列表、集合、哈希表、有序集合等,还提供了事务、消息订阅与发布和持久化等高级功能,满足现代应用的各种需求。此外,文章还探讨了如何在Java环境中利用Jedis、Lettuce和Redisson等客户端与Redis交互,以及如何通过Spring Data Redis简化代码和提高开发效率。掌握这些知识将极大地增强开发者在数据处理和应用开发中的能力。