avatar


1.基础

简介

Redis,Remote Dictionary Server,远程词典服务器,一个基于内存的键值型NoSQL数据库。

官网:https://redis.io

特点:

  • 键值(key-value)型,value支持多种数据结构。
  • 单线程,每个命令具备原子性。
    虽然从6版本开始,已经支持多线程,但是这里的多线程仅仅针对网络请求,在核心的命令执行,依旧是单线程。
  • 低延迟,速度快。
    原因:基于内存、IO多路复用、良好的编码,其中最主要的原因是基于内存。
  • 支持数据持久化。
    Redis会定期将数据从内存持久化到磁盘
  • 支持主从集群、分片集群。

安装

本文讨论的是在Linux上安装Redis,一般也都是在Linux服务器上安装Redis,而且Redis官方没有提供Windows的安装包,我们在网上看到的一些Windows的安装包,并不是Redis官方的。

gcc依赖

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

1
yum install -y gcc tcl

解压安装包

可以通过官网下载安装包,然后上传至Linux服务器。或者直接在Linux服务器上通过wget的方式下载。

1
wget https://github.com/redis/redis/archive/7.2.3.tar.gz

下载完成后,可以把redis压缩包,移动到一个合适的目录(例如/usr/local/src),redis会被安装在我们解压目录的bin中。

解压缩:

1
tar -xzf redis-7.2.3.tar.gz

解压后,进入redis目录:

1
cd redis-7.2.3/

编译并安装:

1
make && make install

此时,我们会发现在解压包下,多了一个目录bin,redis就被安装在该目录下。

此外,redis还会在/usr/local/bin/目录下新建"快捷方式"。因为,/usr/local/bin/目录已经默认配置到环境变量,所以可以在任意目录下运行这些命令。

  • redis-cli:是redis提供的命令行客户端。
  • redis-server:是redis的服务端启动脚本。
  • redis-sentinel:是redis的哨兵启动脚本.

有些资料说,会被安装在/usr/local/bin/目录下,这是不准确的。在/usr/local/bin/目录下在只是"快捷方式"。

启动

Redis的启动方式有很多种,例如:

  • 默认启动
  • 指定配置启动
  • 系统服务启动

默认启动

安装完成后,在任意目录输入redis-server命令即可启动Redis。

示例代码:

1
redis-server

运行结果:

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
24186:C 28 Nov 2023 22:20:06.412 * oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
24186:C 28 Nov 2023 22:20:06.412 * Redis version=7.2.3, bits=64, commit=00000000, modified=0, pid=24186, just started
24186:C 28 Nov 2023 22:20:06.412 # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf
24186:M 28 Nov 2023 22:20:06.412 * Increased maximum number of open files to 10032 (it was originally set to 1024).
24186:M 28 Nov 2023 22:20:06.412 * monotonic clock: POSIX clock_gettime
_._
_.-``__ ''-._
_.-`` `. `_. ''-._ Redis 7.2.3 (00000000/0) 64 bit
.-`` .-```. ```\/ _.,_ ''-._
( ' , .-` | `, ) Running in standalone mode
|`-._`-...-` __...-.``-._|'` _.-'| Port: 6379
| `-._ `._ / _.-' | PID: 24186
`-._ `-._ `-./ _.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' | https://redis.io
`-._ `-._`-.__.-'_.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' |
`-._ `-._`-.__.-'_.-' _.-'
`-._ `-.__.-' _.-'
`-._ _.-'
`-.__.-'

24186:M 28 Nov 2023 22:20:06.413 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
24186:M 28 Nov 2023 22:20:06.415 * Server initialized
24186:M 28 Nov 2023 22:20:06.415 * Ready to accept connections tcp

这种启动属于前台启动,会阻塞整个会话窗口,窗口关闭或者按下CTRL + C则Redis停止。不推荐使用。

指定配置启动

如果要让Redis以"后台"方式启动,可以修改Redis配置文件。

配置文件

配置文件路径:/usr/local/src/redis-7.2.3/redis.conf

bind:监听的地址,也就是允许访问的地址,默认是127.0.0.1,只能在本地访问。修改为0.0.0.0,则可以在任意IP访问。

1
bind 0.0.0.0

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

1
daemonize yes

requirepass:密码。

1
requirepass Redis@2023

port:端口。

1
port 6379

databases:数据库数量,设置为1,代表只使用1个库,最多不超过16,编号0到15,不能自定义仓库名称。默认是16个库。

1
databases 16

dir:工作目录,默认是当前目录,日志、持久化等文件会保存在这个日录。

1
dir .

**logfile,日志文件,默认为空的字符串("")。

不记录日志:

1
logfile ""

指定日志文件名:

1
logfile "redis.log"

maxmemory:设置redis能够使用的最大内存。

1
maxmemory 512mb

启动Redis

指定配置文件,启动Redis。

1
redis-server /usr/local/src/redis-7.2.3/redis.conf

启动之后,我们敲入redis-cli命令,然后ping一下:

1
2
3
redis-cli
127.0.0.1:6379> ping
NOAUTH Authentication required.

因为我们上文设置了密码,所以需要先认证:

1
2
3
4
5
redis-cli
127.0.0.1:6379> auth Redis@2023
OK
127.0.0.1:6379> ping
PONG

系统服务启动

新建一个系统服务文件:

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

内容如下:

1
2
3
4
5
6
7
8
9
10
11
[Unit]
Description=redis-server
After=network.target

[Service]
Type=forking
ExecStart=/usr/local/bin/redis-server /usr/local/src/redis-7.2.3/redis.conf
PrivateTmp=true

[Install]
WantedBy=multi-user.target

重载系统服务:

1
systemctl daemon-reload

这样,便加入了系统服务。

  • 启动:systemctl start redis
  • 停止:systemctl stop redis
  • 重启:systemctl restart redis
  • 查看状态:systemctl status redis
  • 开机自启:systemctl enable redis

命令行客户端

Redis安装完成后就自带了命令行客户端,redis-cli

使用方式如下:

1
redis-cli [options] [commonds]

常见的options有:

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

commonds举例:

  • 选择0号库:select 0
  • 关闭Redis服务端:shutdown
    注意,该命令是关闭Redis服务端。

图形化客户端

图形化客户端有很多,例如:

  1. Redis官方的RedisInsight
    下载地址:https://redis.com/redis-enterprise/redis-insight/
  2. DataGrip

问题解决

我们可能会收到如下的告警:

1
25405:C 28 Nov 2023 22:26:28.501 # WARNING Memory overcommit must be enabled! Without it, a background save or replication may fail under low memory condition. Being disabled, it can also cause failures without low memory condition, see https://github.com/jemalloc/jemalloc/issues/1328. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.

在警告里已经告诉我们如何解决该警告了,需要修改/etc/sysctl.conf配置文件,增加vm.overcommit_memory = 1这个配置项。

需要注意的是,修改完成后,需要执行sysctl -p使配置文件生效。

overcommit_memory,内存分配策略,可选值:012

  • 0,表示内核将检查是否有足够的可用内存供应用进程使用;如果有足够的可用内存,内存申请允许;否则,内存申请失败,并把错误返回给应用进程。
  • 1,表示内核允许分配所有的物理内存,而不管当前的内存状态如何。
  • 2,表示内核允许分配超过所有物理内存和交换空间总和的内存

如果客户端出现了无法连接的异常,可以检查一下Redis服务器的相关端口(6379),是否开启。

无法连接

具体方法,可以参考我们在《ElasticSearch实战入门(6.X):1.工具、概念、集群和倒排索引》关于防火墙的讨论。

卸载

  1. 停止redis服务器
  2. 删除/usr/local/bin
    1
    2
    3
    cd /usr/local/bin
    ll redis*
    rm -f redis*
  3. 删除解压后的文件目录和所有文件
    1
    2
    cd /usr/local/src/
    rm -rf redis-7.0.7
  4. 如果我们配置了加入系统服务,需要把相关的文件(/etc/systemd/system/redis.service)也删掉。
  5. 如果我们还配置了开机启动,还需要先关闭开机启动。

Key的层级

Redis没有类似MySQL中的Table的概念,我们该如何区分不同类型的key呢?
例如,需要存储用户、商品信息到redis,有一个用户id是1,有一个商品id恰好也是1

Redis的key允许有多个单词形成层级结构,多个单词之间用:隔开,格式如下:

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

例如我们的项目名称叫kaka,有userproduct两种不同类型的数据,我们可以这样定义key:

  • user相关的key:kaka:user:1
  • product相关的key:kaka:product:1

这个格式并非固定,也可以根据自己的需求来删除或添加词条。

数据结构

分类

Redis的数据结构有很多种,常见的是8种。

5种基本类型:

  • String
    1
    hello world
  • Hash
    1
    {name: "Jack", age: 21}
  • List
    1
    [A -> B -> C -> C]
  • Set
    1
    {A, B, C}
  • SortedSet
    1
    {A: 1, B: 2, C: 3}

3种特殊类型:

  • GEO
    1
    {A:(120.3, 30.5)}
  • BitMap
    1
    0110110101110101011
  • HyperLog
    1
    0110110101110101011

除了上述8种,还有很多,例如消息队列等。

帮助文档

在Redis的官网可以查看不同数据结构的操作命令

也可以通过命令查看帮助。

help命令,示例代码:

1
help

运行结果:

1
2
3
4
5
6
7
8
9
10
11
redis-cli 7.2.3
To get help about Redis commands type:
"help @<group>" to get a list of commands in <group>
"help <command>" for help on <command>
"help <tab>" to get a list of possible help topics
"quit" to exit

To set redis-cli preferences:
":set hints" enable online hints
":set nohints" disable online hints
Set your preferences in ~/.redisclirc

查看keys命令的帮助,示例代码:

1
help keys

运行结果:

1
2
3
4
KEYS pattern
summary: Returns all key names that match a pattern.
since: 1.0.0
group: generic

通用命令

查看通用命令

查看通用命令的帮助文档,help @generic,示例代码:

1
help @generic
运行结果:
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

COPY source destination [DB destination-db] [REPLACE]
summary: Copies the value of a key to a new key.
since: 6.2.0

DEL key [key ...]
summary: Deletes one or more keys.
since: 1.0.0

DUMP key
summary: Returns a serialized representation of the value stored at a key.
since: 2.6.0

【部分运行结果略】

UNLINK key [key ...]
summary: Asynchronously deletes one or more keys.
since: 4.0.0

WAIT numreplicas timeout
summary: Blocks until the asynchronous replication of all preceding write commands sent by the connection is completed.
since: 3.0.0

WAITAOF numlocal numreplicas timeout
summary: Blocks until all of the preceding write commands sent by the connection are written to the append-only file of the master and/or replicas.
since: 7.2.0

KEYS

KEYS:查看符合模板的所有key

注意!不建议在生产环境设备上使用。因为:

  • 该操作本身比较占用资源。
  • Redis是单线程的,期间无法处理其他操作,会阻塞所有请求。

示例代码:

1
KEYS *name*
运行结果:
1
2
1) "lastname"
2) "firstname"

示例代码:

1
redis> KEYS a??
运行结果:
1
1) "age"

示例代码:

1
redis> KEYS *
运行结果:
1
2
3
1) "lastname"
2) "age"
3) "firstname"

DEL

DEL:删除指定的key

示例代码:

1
DEL k1 k2 k3
运行结果:
1
(integer) 3

EXISTS

EXISTS:判断key是否存在

示例代码:

1
EXISTS name
运行结果:
1
(integer) 1

示例代码:

1
EXISTS age
运行结果:
1
(integer) 0

EXPIRE

EXPIRE:给一个key设置有效期,有效期到期时该key会被自动删除,单位是秒。

示例代码:

1
EXPIRE name 20
运行结果:
1
(integer) 1

TTL

TTL:查看一个KEY的剩余有效期。

示例代码:

1
TTL name
运行结果:
1
(integer) 12

解释说明:

  • -2代表已经失效。
  • -1代表永久有效。

String类型

String类型,也就是字符串类型,其value是字符串。
根据字符串的格式不同,又可以分为3类:

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

String类型的常见命令:

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

在SET命令中,有许多可用选项可以修改命令的行为。

1
SET KEY VALUE [EX seconds] [PX milliseconds] [NX|XX]
  • EX seconds,设置指定的终止时间,以秒为单位。
  • PX milliseconds,设置指定的终止时间,以毫秒为单位。
  • NX,仅在 不存在 的情况下设置key。
  • XX,仅在 已存在 的情况下设置key。

例如,仅在不存在情况下,设置键coo,有效期为60秒:

1
SET coo redis EX 60 NX

如果Value是一个Java对象,例如一个User对象,则可以将对象序列化为JSON字符串后存储:

KEY VALUE
kaka:user:1 {“id”:1, “name”: “Jack”, “age”: 21}
kaka:product:1 {“id”:1, “name”: “小米11”, “price”: 4999}

示例代码:

1
set kaka:user:1 '{"id":1, "name": "Jack", "age": 21}'

但是这个方法并不好,下文的Hash类型是一种更好的方法。

Hash类型

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

String结构是将对象序列化为JSON字符串后存储,当需要修改对象某个字段时很不方便。

String结构

Hash结构可以将对象中的每个字段独立存储,可以针对单个字段做CRUD。value,分为了两部分,一部分是field,一部分是value

Hash结构

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不存在,否则不执行。

可以一次执行多个field的值,示例代码:

1
HSET kaka:user:1 name Lee age 23 sex male

List类型

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

特征也与LinkedList类似:

  • 有序
  • 元素可以重复
  • 插入和删除快
  • 查询速度一般

List的常见命令有:

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

LinkedList

使用方法:

  • 如何利用List结构模拟一个栈?
    入口和出口在同一边
  • 如何利用List结构模拟一个队列?
    入口和出口在不同边
  • 如何利用List结构模拟一个阻塞队列?
    入口和出口在不同边,出队时采用BLPOP或BRPOP

《算法入门经典(Java与Python描述):3.栈、队列》,我们先实现了一个双向链表,然后在其基础上,分别实现了栈和队列。当时的思路就和这个类似。

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的交集。

SortedSet类型

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

SortedSet具备下列特性:

  • 可排序
  • 元素不重复
  • 查询速度快

SortedSet的常见命令有:

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

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

  • 升序获取SortedSet中的指定元素的排名:ZRANK key member
  • 降序获取SortedSet中的指定元素的排名:ZREVRANK key memeber

再举一个例子,将班级的下列学生得分存入Redis的SortedSet中:

1
Jack 85, Lucy 89, Rose 82, Tom 95, Jerry 78, Amy 92, Miles 76

示例代码:

1
ZADD stus 85 Jack 89 Lucy 82 Rose 95 Tom 78 Jerry 92 Amy 76 Miles

Java客户端

四种客户端

  1. Jedis
    以Redis命令作为方法名称,学习成本低,简单实用。但是Jedis实例是线程不安全的,多线程环境下需要基于连接池来使用。
  2. Lettuce
    Lettuce是基于Netty实现的,支持同步、异步和响应式编程方式,并且是线程安全的。支持Redis的哨兵模式、集群模式和管道模式。
  3. Redisson
    Redisson是一个基于Redis实现的分布式、可伸缩的Java数据结构集合。包含了诸如Map、Queue、Lock、 Semaphore、AtomicLong等强大功能。
  4. SpringDataRedis
    提供了对不同Redis客户端的整合(Lettuce和Jedis)

在本文,我们讨论JedisSpringDataRedis

Jedis

Github地址:https://github.com/redis/jedis

通过这个Github地址,我们可以看出来,jedis是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
33
34
35
36
37
38
39
40
package com.kakawanyifan;

import redis.clients.jedis.Jedis;

import java.util.Map;

public class Main {

public static void main(String[] args) {


// 建立连接
Jedis jedis = new Jedis("10.211.55.14", 6379);
// 设置密码
jedis.auth("Redis@2023");
// 选择库
jedis.select(0);


// 存入数据
String result = jedis.set("name", "卡卡");
System.out.println("result = " + result);
// 获取数据
String name = jedis.get("name");
System.out.println("name = " + name);


// 插入hash数据
jedis.hset("user:1", "name", "kaka");
jedis.hset("user:1", "age", "21");

// 获取hash数据
Map<String, String> map = jedis.hgetAll("user:1");
System.out.println(map);

// 释放资源
jedis.close();

}
}

运行结果:

1
2
3
result = OK
name = 卡卡
{name=kaka, age=21}

连接池

Jedis本身是线程不安全的,并且频繁的创建和销毁连接会有性能损耗,推荐使用Jedis连接池代替Jedis的直连方式。

Jedis连接池,JedisConnectionFactory

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.heima.jedis.util;

import redis.clients.jedis.*;

public class JedisConnectionFactory {

private static JedisPool jedisPool;

static {
// 配置连接池
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxTotal(8);
poolConfig.setMaxIdle(8);
poolConfig.setMinIdle(0);
poolConfig.setMaxWaitMillis(1000);
// 创建连接池对象,参数:连接池配置、服务端ip、服务端端口、超时时间、密码
jedisPool = new JedisPool(poolConfig, "192.168.150.101", 6379, 1000, "123321");
}

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

建立连接,示例代码:

1
2
// 建立连接
Jedis jedis = JedisConnectionFactory.getJedis();

释放连接,示例代码:

1
jedis.close();

特别的,我们可以点进jedis.close()方法,会发现该方法内部根据是否有连接池判断是应该归还连接,还是释放连接。
示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void close() {
if (this.dataSource != null) {
Pool<Jedis> pool = this.dataSource;
this.dataSource = null;
if (this.isBroken()) {
pool.returnBrokenResource(this);
} else {
pool.returnResource(this);
}
} else {
this.connection.close();
}

}

再提一个问题,上文的JedisConnectionFactory是静态工厂还是实例工厂?

静态工厂

关于静态工厂和实例工厂,我们在《基于Java的后端开发入门:15.Spring Framework [1/2]》的Bean的创建部分有讨论。

SpringDataRedis

一个现象

关于SpringDataRedis,我们在《基于Java的后端开发入门:21.SpringBoot [1/3]》的"整合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
package com.kakawanyifan.demo;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;

@SpringBootTest
class DemoApplicationTests {

// 注入 RedisTemplate
@Autowired
private RedisTemplate redisTemplate;

@Test
void testString(){
// 插入数据
redisTemplate.opsForValue().set("name","Kaka");

// 读取数据
Object name = redisTemplate.opsForValue().get("name");
System.out.println(name);
}

}

运行结果:

1
Kaka

这个没有任何问题,然后我们通过redis-cli命令get name,示例代码:

1
get name

运行结果:

1
(nil)

没有?

通过keys看一下,示例代码:

1
keys *name*

运行结果:

1
1) "\xac\xed\x00\x05t\x00\x04name"

key怎么这么奇怪?

自定义序列化

RedisTemplate默认是采用JDK序列化,这种方式存在两个问题:

  1. 可读性差
  2. 内存占用较大

上文的"\xac\xed\x00\x05t\x00\x04name"就是例子。

我们可以自定义RedisTemplate的序列化方式:

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
package com.kakawanyifan.demo;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;

@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
// 创建RedisTemplate对象
RedisTemplate<String, Object> template = new RedisTemplate<>();
// 设置连接工厂
template.setConnectionFactory(connectionFactory);

// 设置Key的序列化
template.setKeySerializer(RedisSerializer.string());
template.setHashKeySerializer(RedisSerializer.string());

// 创建JSON序列化工具
GenericJackson2JsonRedisSerializer jsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
// 设置Value的序列化
template.setValueSerializer(jsonRedisSerializer);
template.setHashValueSerializer(jsonRedisSerializer);

// 返回
return template;
}
}
  • 对于value,采用了JSON序列化来代替默认的JDK序列化方式。

对象序列化

现在,假设我们有一个对象User,我们要把这个对象保存到Redis中。

User类,示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.kakawanyifan.demo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {

private Integer id;

private String user;

}

存取对象,示例代码:

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
package com.kakawanyifan.demo;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;

@SpringBootTest
class DemoApplicationTests {

// 注入 RedisTemplate
@Autowired
private RedisTemplate redisTemplate;

@Test
void testString(){
// 插入数据
redisTemplate.opsForValue().set("user:1",new User(1,"kaka"));

// 读取数据
User user = (User) redisTemplate.opsForValue().get("user:1");
System.out.println(user);
}

}

运行结果:

1
User(id=1, user=kaka)

我们通过get命令看一下,示例代码:

1
get user:1

运行结果:

1
"{\"@class\":\"com.kakawanyifan.demo.User\",\"id\":1,\"user\":\"kaka\"}"

在Redis的内容中,有这么一项@class,专门标明了User对象的类com.kakawanyifan.demo.User(序列化时对应的class名称)。

StringRedisTemplate

在上文,专门标明了序列化时对应的class名称,这样会带来额外的内存开销,为了节省内存空间,可以不使用JSON序列化器来处理value,而是统一使用String序列化器,要求只能存储String类型的key和value。

特别的,SpringDataRedis就提供了RedisTemplate的子类:StringRedisTemplate,它的key和value的序列化方式默认就是String方式。

省去了我们自定义RedisTemplate的序列化方式的步骤。

示例代码:

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
package com.kakawanyifan.demo;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.StringRedisTemplate;

@SpringBootTest
class DemoApplicationTests {

// 注入 StringRedisTemplate
@Autowired
private StringRedisTemplate stringRedisTemplate;
// JSON序列化工具
private static final ObjectMapper mapper = new ObjectMapper();


@Test
void testString() throws JsonProcessingException {

// 创建对象
User u1 = new User(2, "kaka-2");
// 手动序列化
String json = mapper.writeValueAsString(u1);

// 写入数据
stringRedisTemplate.opsForValue().set("user:2", json);

// 获取数据
String jsonUser = stringRedisTemplate.opsForValue().get("user:2");

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

}

运行结果:

1
User(id=2, user=kaka-2)

通过get命令看一下,示例代码:

1
get user:2

运行结果:

1
"{\"id\":2,\"user\":\"kaka-2\"}"

Python客户端

redis-py

安装命令:

1
pip install redis

PiPy地址:https://pypi.org/project/redis/
GitHub地址:https://github.com/redis/redis-py
官方文档:https://redis.readthedocs.io/en/stable/index.html

decode_responses

示例代码:

1
2
3
4
5
6
7
8
import redis

r = redis.Redis(host='10.211.55.14', port=6379, password='Redis@2023')
# 设置 name 对应的值
r.set('name', 'Kaka')
print(r['name'])
print(r.get('name'))
print(type(r.get('name')))

运行结果:

1
2
3
b'Kaka'
b'Kaka'
<class 'bytes'>

默认是字节类型,可以设置decode_responses=True,示例代码:

1
2
3
4
5
6
7
8
import redis

r = redis.Redis(host='10.211.55.14', port=6379, password='Redis@2023', decode_responses=True)
# 设置 name 对应的值
r.set('name', 'Kaka')
print(r['name'])
print(r.get('name'))
print(type(r.get('name')))

运行结果:

1
2
3
Kaka
Kaka
<class 'str'>

连接池

redis-py使用ConnectionPool来管理对一个RedisServer的所有连接,避免每次建立、释放连接的开销。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
import redis


# 拿到一个redis的连接池
pool = redis.ConnectionPool(host='10.211.55.14', port=6379, password="Redis@2023", decode_responses=True, max_connections=10)

# 从池子中拿一个链接
conn = redis.Redis(connection_pool=pool)

conn.set('111', 'Kaka')
print(conn['name'])
print(conn.get('name'))
print(type(conn.get('name')))

运行结果:

1
2
3
Kaka
Kaka
<class 'str'>

常见操作

关于Redis中的各种操作,可以参考官方文档。
https://redis.readthedocs.io/en/stable/commands.html

这里列举一些常用的。

String

  1. set

    1
    set(name, value, ex=None, px=None, nx=False, xx=False)

    参数:

    • ex,过期时间(秒)。
    • px,过期时间(毫秒)。
    • nx,如果设置为True,则只有name不存在时,当前set操作才执行。
    • xx,如果设置为True,则只有name存在时,当前set操作才执行。
  2. get,获取值。

  3. mget,批量获取值。

  4. getset(name, value),设置新值并获取原来的值。

  5. strlen(name),返回name对应值的字节长度(默认字符集是UTF-8,一个汉字三个字节)

  6. incr(name, amount=1),自增name对应的值,当name不存在时,则创建name=amount,否则,则自增。
    incrbyfloat方法自增浮点数类型。
    decr进行自减操作。

  7. append(key, value),在name对应的值后面追加内容。

hash

  1. hset(name, key, value)
  2. hmset(name, mapping),在name对应的hash中批量设置键值对:
    1
    r.hmset("hash1", {"k1": "v1", "k2": "v2", "k3": "v3"})
  3. hkeys(name),获取所有的key
  4. hvals(name),得到所有的value。
  5. hgetall(name),取出所有的键值对。
  6. hdel(name),删除。
  7. hincrby(name, key, amount=1),自增自减整数。
  8. hincrbyfloat(name, key, amount=1.0)表示自增自减浮点数。

list

  1. lpush(name,values),增加。rpush表示从右边增加。
  2. r.lset(name, index, value),对name对应的list中的某一个索引位置重新赋值。
  3. r.lrem(name, value, num),删除。
    1
    2
    3
    4
    5
    6
    # 将列表中左边第一次出现的"11"删除
    r.lrem("list2", "11", 1)
    # 将列表中右边第一次出现的"99"删除
    r.lrem("list2", "99", -1)
    # 将列表中所有的"22"删除
    r.lrem("list2", "22", 0)
  4. lindex(name, index),取值。

set

  1. sadd(name, values),新增。
  2. srem(name, values),在name对应的集合中删除某些值。

其他操作

  1. delete(name),删除。
  2. expire(name ,time),为某个redis的某个name设置超时时间。
  3. rename(src, dst),重命名。

管道

Redis默认在执行每次请求都会创建连接(连接池申请连接)和断开连接(归还连接池),如果想要在一次请求中指定多个命令,则可以使用pipline实现一次请求指定多个命令,并且默认情况下一次pipline是原子性操作。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
import redis

# 拿到一个redis的连接池
pool = redis.ConnectionPool(host='10.211.55.14', port=6379, password="Redis@2023", decode_responses=True, max_connections=10)

# 从池子中拿一个链接
conn = redis.Redis(connection_pool=pool)

# 创建一个管道
pipe = conn.pipeline()
pipe.set('name', 'kaka')
pipe.set('role', 's')
pipe.execute()
文章作者: Kaka Wan Yifan
文章链接: https://kakawanyifan.com/12301
版权声明: 本博客所有文章版权为文章作者所有,未经书面许可,任何机构和个人不得以任何形式转载、摘编或复制。

留言板