Redis学习笔记

Redis学习笔记

一、什么是Redis

Redis是一个非关系型数据库,它允许使用者以键值对的形式存储数据,每个键中可以存储不同的数据结构,如字符串,数值,哈希,有序数据结构等。

关键词:单线程、基于内存操作、高性能

使用redis-benchmark可以测试redis的性能,比如:

测试当前机器上100并发量下处理10万次请求的性能。

redis-benchmark -h localhost -p 6379 -c 100 -n 100000 -a password

-h:主机IP -p:端口号 -c:连接数 -n:请求数 -a:密码

二、Redis安装

1. Linux下安装

1)官方网站下载压缩包,上传到服务器

2)解压文件

tar -zxvf redis-6.0.6.tar.gz

3)进入redis目录

cd redis-6.0.6

4)编译安装

make

2. Windows下安装

1)Github下载安装包

在这里插入图片描述
2)按步骤安装
在这里插入图片描述在这里插入图片描述

在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述

三、基本指令

1. 启动Redis服务

步骤:1)进入Redis目录下的src文件夹

​ 2)执行命令 ./redis-server

2. 启动Redis客户端

步骤:1)进入Redis目录下的src文件夹

​ 2)执行命令 ./redis-cli

3. 关闭Redis服务
方式一:使用kill命令结束进程

​ 步骤:1)查看进程,命令:ps -ef | grep redis

​ 2)找到进程编号

​ 3)结束进程,命令:kill -9 进程编号

方式二:使用shutdown命令停止服务

​ 步骤:1)启动redis客户端

​ 2)停止服务,命令:shutdown

4. 后台启动Redis服务

步骤:1)进入Redis目录

​ 2)编辑redis.conf文件,命令:vim redis.conf

​ 3)找到"daemonize no"这一行,修改为"daemonize yes",保存退出

​ 4)启动Redis服务,命令:./src/redis-server ./redis.conf

5. 设置Redis连接密码

步骤:1)编辑redis.conf文件

​ 2)找到"requirepass"这一行,修改为"requirepass 123456",保存退出

​ 3)重启Redis服务

测试:1)启动redis-cli

​ 2)输入ping命令,回车,出现"(error) NOAUTH Authentication required."则表示设置成功

​ 3)连接Redis服务器,命令:auth 123456,出现“OK"提示,则表示连接成功

​ 4)输入ping命令,提示"pong",表示连接畅通。

6. 切换数据库

redis默认有16个数据库,索引从0~15,可以使用命令切换

语法格式:

select index

例如:

> select 3
OK
> select 0
OK
7. 查看数据量

查看当前数据库存在的数据总量。

> dbsize
8. 查看当前数据库的key

1)查看所有key

语法格式:

keys *

2)检查key是否存在

语法格式:

EXISTS key

例如:检查name是否存在

> EXISTS name
(integer) 0
9. 移动key

把键值对移动到指定索引的数据库

语法格式:

move key index

例如:把name移动到索引为1的数据库

> move name 1
10. 删除key

从Redis中删除一个键值对,需要指定Key

语法格式:

del key

例如:

> del name
(integer) 1
> get name
(nil)
11. 清空数据

1)清空当前库

> flushdb

2)清空全部数据

> flushall
12. 设置key的时效

指定key在一定时间后失效

语法格式:

expire key seconds

例如:把name设置成10秒后失效

> expire name 10
13. 查看key的时效

语法格式:

ttl key

例如:

> ttl name
(integer) 10
> set age 10
OK
> ttl age
(integer) -1
> ttl sex
(integer) -2

-1表示没有设置时效,-2表示已失效。

14. 查看key的类型

语法格式:

type key

例如:

> type name
string

四、五大数据类型

1. string 字符串

string 是 redis 最基本的类型,你可以理解成与 Memcached 一模一样的类型,一个 key 对应一个 value。

string 类型是二进制安全的。意思是 redis 的 string 可以包含任何数据。比如jpg图片或者序列化的对象。

string 类型是 Redis 最基本的数据类型,string 类型的值最大能存储 512MB

1)SET 新增/修改

向Redis中添加一个键值对,需要指定Key和Value

> SET name "Miguel"
ok
> SET count 10
ok
2)GET 查询

从Redis中读取数据,需要指定Key

> GET name
"Miguel"
3)APPEND 拼接

向指定key的值后拼接字符串,如果指定的key不存在,则相当于set key value

> APPEND name "_Pan"
(integer) 10
4)STRLEN 长度

查看指定key的字符串值的长度

> STRLEN name
(integer) 10
5)INCR 自增

指定key的字符串值加一

> INCR count
(integer) 11
6)INCRBY 加法

指定key的字符串值加上特定数值

> INCRBY count 10
(integer) 21
7)DECR 自减

指定key的字符串值减一

> DECR count
(integer) 20
8)DECRBY 减法

指定key的字符串值减去特定数值

> DECRBY count 10
(integer) 10
9)GETRANGE 截取

返回指定key的指定索引范围的字符串

> GETRANGE name 0 5
"miguel"
10)SETRANGE 范围修改

替换指定key的字符串值的从指定位置开始的字符

> SETRANGE name 7 "winnie"
(integer) 13
> GET name
"miguel_winnie"
2. hash 哈希

Redis hash 是一个键值(key=>value)对集合。

Redis hash 是一个 string 类型的 field 和 value 的映射表,hash 特别适合用于存储对象。

1)HSET 新增/修改

设置一个字段值。

语法格式:

HSET key field value

例如:

> HSET runoob name "miguel"
OK
2)HGET 查询

查询一个字段值。

语法格式:

HGET key field

例如:

> HGET runoob name
"miguel"
3)HMSET 新增/修改

设置多个字段值。

语法格式:

HMSET key field value [field value …]

例如:

> HMSET runoob field1 value1 field2 value2
OK
4)HMGET 查询

查询多个字段值。

语法格式:

HGET key field [field …]

例如:

> HGET runoob field1
"value1"
5)HGETALL 查询全部

查询全部字段值。

语法格式:

HGETALL key

例如:

> HGETALL runoob
1) "name"
2) "miguel"
3) "field1"
4) "value1"
5) "field2"
6) "value2"
6)HLEN 统计

查询hash表的字段数量。

语法格式:

HLEN key

例如:

> HLEN runoob
(integer) 3
7)HEXISTS 判断存在

判断指定字段是否存在于指定hash表,存在则返回1,不存在则返回0。语法格式:

HEXISTS key field

例如:

> HEXISTS runoob name
(integer) 1
> HEXISTS runoob age
(integer) 0
8)HKEYS 查询全部字段

查询指定hash表的全部字段。

语法格式:

HKEYS key

例如:

> HKEYS runoob
1) "name"
2) "field1"
3) "field2"
9)HVALS 查询全部值

查询指定hash表的全部值。

语法格式:

HVALS key

例如:

> HVALS runoob
1) "miguel"
2) "value1"
3) "value2"
10)HINCRBY 加法

给指定字段值加上一个数值,成功后返回结果值。

语法格式:

HINCRBY key field num

例如:

> HSET runoob age 10
OK
> HINCRBY runoob age 5
(integer) 15
3. list 列表

Redis 列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)。

1)lpush 左侧新增

从列表的左边插入元素,成功则返回当前列表的大小,语法格式:

lpush key value

例如:

> DEL runoob
(integer) 1
> lpush runoob redis
(integer) 1
> lpush runoob mongodb
(integer) 2
> lpush runoob mysql
(integer) 3
2)rpush 右侧新增

从列表的右边插入元素,成功则返回当前列表的大小,语法格式:

rpush key value

例如:

> rpush runoob oracle
(integer) 4
3)lpop 左侧移除

移除列表左边第一个元素并返回,语法格式:

lpop key

例如:

> lpop runoob
"mysql"
4)rpop 右侧移除

移除列表右边第一个元素并返回,语法格式:

rpop key

例如:

> rpop runoob
"oracle"
5)lindex 索引查找

通过下标获取列表元素,语法格式:

lindex key index

例如:

> lindex runoob 1
"mongodb"
6)lrange 范围遍历

获取列表指定范围的元素,语法格式:

lrange key start stop

例如:

> lrange runoob 0 2
"redis"
"mongodb"
"mysql"
7)llen 统计

查看列表的大小,语法格式:

llen key

例如:

> llen runoob
(integer) 2
8)lrem 指定移除

移除列表中的指定个数的指定元素,成功则返回删除的元素个数,语法格式:

lrem key count value

例如:

> lrem runoob 1 "mongodb"
(integer) 1
9)ltrim 截取

通过下标截取列表,成功后列表会被改变,语法格式:

ltrim key start stop

例如:

> flushdb
OK
> lpush runoob "redis" "mongodb" "mysql" "oracle"
(integer) 4
> ltrim runoob 0 2
OK
> lrange runoob 0 -1
1) "oracle"
2) "mysql"
3) "mongodb"
10)lset 修改

替换列表指定下标的元素值,前提是指定下标存在元素,语法格式:

lset key index value

例如:

> lset runoob 0 redis
OK
11)linsert 插入

在列表指定元素的前面或后面插入新的元素,成功则返回列表大小,语法格式:

linsert key before|after pivot element

例如:

> linsert runoob after redis oracle
(integer) 4
4. set 集合

Redis 的 Set 是 string 类型的无序集合。

集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。

1)sadd 增加

用于添加一个 string 元素到 key 对应的 set 集合中,成功返回 1,如果元素已经在集合中返回 0,语法格式:

​ sadd key member

例如:

> DEL runoob
(integer) 1
> sadd runoob redis
(integer) 1
> sadd runoob mongodb
(integer) 1
> sadd runoob rabbitmq
(integer) 1
> sadd runoob rabbitmq
(integer) 0
2)smembers 遍历

遍历set集合中的元素,语法格式:

smembers key

例如:

> smembers runoob
1) "redis"
2) "rabbitmq"
3) "mongodb"
3)sismember 判断存在

判断指定值是否存在于set集合中,存在返回1,否则返回0,语法格式:

sismember key value

例如:

> smembers runoob redis
(integer) 1
4)scard 统计

获取set集合中元素的个数,语法格式:

scard key

例如:

> scard runoob
(integer) 3
5)srem 删除

移除set集合中指定元素,语法格式:

srem key value

例如:

> srem runoob rabbitmq
(integer) 1
6)srandmember 随机返回

随机返回set集合中指定个数的元素,语法格式:

srandmember key count

例如:

> srandmember runoob 1
"redis"
7)spop 随机移除

随机移除元素并返回,语法格式:

spop key

例如:

> spop runoob
"mongodb"
8)smove 移动

移动指定元素到另一个set集合中,成功则返回移动的元素个数,语法格式:

smove source destination member

例如:

> smove runoob myset "redis"
(integer) 1
9)sdiff 差集

返回指定set集合与一个或多个set集合的差集,语法格式:

sdiff key [key …]

例如:

> sadd runoob "redis" "mongodb" "mysql"
(integer) 3
> sadd myset "hello" "world"
(integer) 3
> sdiff runoob myset
1) "mongodb"
2) "mysql"
10)sinter 交集

返回指定set集合与一个或多个set集合的交集,语法格式:

sinter key [key …]

例如:

> sinter runoob myset
1) "redis"
11)sunion 并集

返回指定set集合与一个或多个set集合的并集,语法格式:

sunion key [key …]

例如:

> sunion runoob myset
1) "redis"
2) "mongodb"
3) "mysql"
4) "hello"
5) "world"
5. zset 有序集合

Redis zset 和 set 一样也是string类型元素的集合,且不允许重复的成员。

不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。

zset的成员是唯一的,但分数(score)却可以重复。

1)zadd 新增

添加元素到集合,元素在集合中存在则更新对应score。

语法格式:

zadd key score member

例如:

> zadd myset 100 redis
(integer) 1
> zadd myset 200 mongodb
(integer) 1
> zadd myset 300 rabbitmq
(integer) 1
2)zrange 范围查询

遍历zset集合中在指定索引范围内的元素,分数从低到高排序

语法格式:

zrange key start stop [withscores]

例如:

> zrange myset 0 -1
1) "redis"
2) "mongodb"
3) "rabbitmq"
> zrange myset 0 -1 withscores
1) "redis"
2) "100"
3) "mongodb"
4) "200"
5) "rabbitmq"
6) "300"

-1表示倒数第一个元素,-2代表倒数第二个元素,withscores表示把分数一起返回。

3)zrevrange 范围查询

遍历zset集合中在指定索引范围内的元素,分数从高到低排序

语法格式:

zrevrange key start stop [withscores]

例如:

> zrevrange myset 0 -1 withscores
1) "rabbitmq"
2) "300"
3) "mongodb"
4) "200"
5) "redis"
6) "100"
4)zrangebyscore 通过分数范围查询

遍历zset集合中在指定分数区间的元素。

语法格式:

zrangebyscore key min max [withscores] [limit offset count]

例如:

> zrangebyscore myset 0 1000
1) "redis"
2) "mongodb"
3) "rabbitmq"
5)zrem 删除

删除zset集合中的指定元素

语法格式:

zrem key member

例如:

> zrem myset rabbitmq
(integer) 1
6)zcard 统计

查询zset集合中的元素个数。

语法格式:

zcard key

例如:

> zcard myset
(integer) 2
7)zcount 范围统计

查询zset集合中指定分数范围内的元素个数。

语法格式:

zcount key min max

例如:

> zcount myset 0 100
(integer) 1

五、三种特殊数据类型

1. Geospatial 地理空间
1)geoadd 新增

向key中新增一个地理位置。

语法格式:

geoadd key longitude latitude member

例如:

> geoadd china:city 116.23 40.22 beijing
(integer) 1
2)geopos 查询

查询key中的地理位置。

语法格式:

geopos key member [member …]

例如:

> geopos china:city beijing
1) 1) "116.23000055551528931"
   2) "40.2200010338739844"
3)geodist 计算距离

计算key中存在的两个地理位置之间的直线距离。

语法格式:

geodist key member1 member2 [m|km|ft|mi]

  • m 表示单位为 米
  • km 表示单位为 千米
  • ft 表示单位为 英里
  • mi 表示 单位为 英尺

例如:

> geodist china:city beijing shanghai #默认单位为m
"1088785.4302"
> geodist china:city beijing shanghai km
"1088.7854"
4)georadius | georadiusbymember 范围查询

以给定的经纬度(地理位置)为中心,查询指定半径范围内的key中存在的元素。

语法格式:

georadius key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count]

georadius key member radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count]

  • WITHCOORD 表示携带 经纬度
  • WITHDIST 表示携带 距离
  • WITHHASH 表示 以 52 位有符号整数的形式, 返回位置元素经过原始 geohash 编码的有序集合分值
  • COUNT 表示 限制返回元素的个数

例如:

> georadius china:city 110 30 1000 km
1) "shenzhen"
2) "guangzhou"
> georadiusbymember china:city guangzhou 500 km
1) "shenzhen"
2) "guangzhou"
5)geohash 查询哈希

把指定的地理位置的经纬度转换为一个11位的字符串并返回。

语法格式:

geohash key member [member …]

例如:

> geohash china:city beijing shanghai
1) "wx4sucu47r0"
2) "wtw6sk5n300"

PS:GEO底层的实现原理就是Zset,所以我们可以用Zset命令操作GEO的元素。

2. HyperLogLog 基数统计

HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素

1)pfadd 新增

添加指定元素到 HyperLogLog 中。

语法格式:

pfadd key [member …]

例如:

> pfadd myhlog "redis" "mongodb" "rabbitmq"
(integer) 1
> pfadd myhlog2 "mysql" "oracle" "mongodb"
(integer) 1
2)pfcount 统计

返回给定 HyperLogLog 的基数估算值。

语法格式:

pfcount key

例如:

> pfcount myhlog
(integer) 3
> pfcount myhlog2
(integer) 3
3)pfmerge 整合

将多个 HyperLogLog 合并为一个 HyperLogLog。

语法格式:

pfmerge destkey [sourcekey…]

例如:

> pfmerge myhlog3 myhlog myhlog2
OK
> pfcount myhlog3
(integer) 5
3. Bitmap 位图

Bitmap是用于存储二进制位的数据结构。

1)setbit 新增/修改

给指定索引的位设置值。

语法格式:

setbit key offset value

例如:

> setbit sign 0 1
(integer) 0
> setbit sign 1 0
(integer) 0
> setbit sign 2 1
(integer) 0
> setbit sign 3 1
(integer) 0
2)getbit 查询

查询指定索引的位的值。

语法格式:

getbit key offset

例如:

> getbit sign 1
(integer) 0
> getbit sign 2
(integer) 1

六、事务

redis事务有三大特性:

  • 批量操作:开启事务后输入的操作指令被放入队列缓存,发送exec命令前不会执行。

  • 原子性:事务中的命令要么全部被执行,要么全部不执行。

  • 排他性:其他客户端提交的命令不会插入到事务执行命令序列中。

事务中的命令如果出现编译错误,那么事务中的全部命令都不会执行;如果命令中只存在逻辑异常,全部命令都会执行。

1. 开启事务

开启一个事务,后续输入的命令都会进入队列,但是不会立刻执行。

语法格式:

multi

例如:

> multi
OK
> keys *
QUEUED
> flushdb
QUEUED
2. 执行事务

执行事务中的全部命令。

语法格式:

exec

例如:

> multi
OK
> keys *
QUEUED
> flushdb
QUEUED
> exec
1) 1) "china:city"
2) OK
3. 乐观锁

乐观锁,即不加锁,只在数据更新时进行版本校验。如果版本不对,则更新失败;否则,更新数据。

1)watch 监视(锁定)

监视所有指定的key,在事务中有条件的执行。

语法格式:

watch key [key …]

例如:

> watch money
OK
2)unwatch 解除监视(解锁)

解除监视所有指定的key。

语法格式:

unwatch key [key…]

例如:

> unwatch money
OK

PS:如果事务执行失败,一般应该先解除监视,再重新监视以获取最新的值。

七、Jedis

Jedis是Java连接和操作redis的中间件,可以让Java完成所有Redis的操作。

1. 导入依赖

使用前必须导入Jedis相关依赖,以maven工程为例:

<dependency>
	<!--导入jedis的包-->
    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
        <version>3.2.0</version>
    </dependency>
</dependency>
2. 建立连接

创建Jedis对象时,传入主机名和端口号,即可建立redis连接:

Jedis jedis = new Jedis("127.0.0.1", 6379);
3. 操作

jedis对redis的所有操作的方法名都与redis命令相同,如:

// 五大数据类型
// set
jedis.set("name", "miguel");
// lpush
jedis.lpush("mylist", "redis", "oracle", "mongodb");
// sadd
jedis.sadd("myset", "java");
// hset
jedis.hset("myhash", "age", 10);
// zadd
jedis.zadd("myzset", 1000, "zhangsan");

// 三种特殊类型
// geoadd
jedis.geoadd("mygeo", 116.23, 40.22, "Beijing");
// pfadd
jedis.pfadd("mylog", "Tencent", "Alibaba", "JingDong");
// setbit
jedis.setbit("mybitmap", 0, 1);
4. 断开连接

为了避免占用线程,每次使用完后尽量主动断开redis连接,如:

jedis.close();

八、Springboot整合

Springboot 2.0以下的版本使用的是Jedis连接redis,但是从Springboot 2.0开始改为使用lectuce连接redis,原因是:

jedis是基于tcp的阻塞式连接方式,在并发量高的前提下,性能低;并且,jedis采用的是socket共享的方式,每个线程操作的是同一个jedis实例,这会导致线程不安全,所以通常会采用连接池管理jedis连接,使每个线程拥有自己的Jedis实例

lectuce是基于netty的多路复用异步非阻塞的连接方式,连接实例可以在多个线程间并发访问,所以一个实例就可以满足多线程并发的情况,在并发量高的前提下,相比于jedis,性能较高,线程更安全;而且可以根据实际情况增减连接实例的数量

1. 创建工程

创建一个springboot的maven工程,版本为2.4.2,并勾选相应的依赖:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uXnzBue4-1613040297269)(C:\Users\Miguel\AppData\Roaming\Typora\typora-user-images\image-20210131155606396.png)]

2. 查看源码

创建完毕后可以看到pom.xml文件中已经导入以下依赖:

<dependencies>
    	<!--用于操作redis的依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
    	<!--其他依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>		
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <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>
    </dependencies>

深入上面的redis依赖查看,如下图:

<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
    <version>2.4.2</version>
    <scope>compile</scope>
  </dependency>
  <dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-redis</artifactId>
    <version>2.4.3</version>
    <scope>compile</scope>
  </dependency>
  <dependency>
    <groupId>io.lettuce</groupId>
    <artifactId>lettuce-core</artifactId>
    <version>6.0.2.RELEASE</version>
    <scope>compile</scope>
  </dependency>
</dependencies>

可以看到底层采用的是lettuce连接redis。

3. 基本配置

通常我们都是连接远程服务器上的redis,所以需要在application.properties配置,如下:

## 主机名
spring.redis.host=127.0.0.1
## 端口号
spring.redis.port=6379
4. 测试运行

springboot为redis设计了一个模板对象RedisTemplate,我们可以直接注入使用:

@SpringBootTest
class Redis02SpringbootApplicationTests {

    @Autowired
    private RedisTemplate redisTemplate;

    @Test
    void contextLoads() {
        // 五大数据类型
        redisTemplate.opsForValue().set("name", "miguel");
        redisTemplate.opsForHash().put("myhash", "age", 10);
        redisTemplate.opsForList().leftPush("mylist", "redis");
        redisTemplate.opsForSet().add("myset", "java");
        redisTemplate.opsForZSet().add("myzset", "zhangsan", 100);
        // 三种特殊类型
        redisTemplate.opsForGeo().add("mygeo", new GeoLocation("Beijing", new Point(116.23, 40.22)));
        redisTemplate.opsForHyperLogLog().add("mylog", "redis", "mongodb", "mysql");
        redisTemplate.opsForValue().setBit("mybitmap", 0, false); //opsForValue包含操作String和Bitmap的方法
        // 获取连接对象
        RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();

    }

}
5. 保存对象

企业开发中,Java对象经常是数据的载体,但是对于redis,却不能直接保存对象,如下面代码:

@SpringBootTest
class Redis02SpringbootApplicationTests {

    @Autowired
    private RedisTemplate redisTemplate;

    @Test
    void contextLoads() {
        // 关于对象的保存
        User user = new User("miguel", 10);
        redisTemplate.opsForValue().set("user", user);
        System.out.println(redisTemplate.opsForValue().get("user"));
    }
}

运行后报出以下错误:

org.springframework.data.redis.serializer.SerializationException: Cannot serialize; nested exception is org.springframework.core.serializer.support.SerializationFailedException: Failed to serialize object using DefaultSerializer; nested exception is java.lang.IllegalArgumentException: DefaultSerializer requires a Serializable payload but received an object of type [com.miguel.pojo.User]

从错误信息中可以知道,缺少了序列化操作,这说明我们首先要让对象序列化,然后再进行保存,所以应该改为下面代码:

@SpringBootTest
class Redis02SpringbootApplicationTests {

    @Autowired
    private RedisTemplate redisTemplate;

    @Test
    void contextLoads() {
        // 关于对象的保存
        User user = new User("miguel", 10);
        String jsonUser = new ObjectMapper().writeValueAsString(user);
        redisTemplate.opsForValue().set("user", jsonUser);
        System.out.println(redisTemplate.opsForValue().get("user"));
    }
}

或者在实体类中实现Serializable接口:

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User implements Serializable {

    private String name;
    private Integer age;
}

企业开发中通常采用第二种方式,这样对象就能转化为字符串保存到Redis中了。

6. 自定义配置类

由于springboot默认采用的JDK序列化会导致key存入redis后出现乱码的情况,不能满足我们的需求,所以通常在企业开发中会自己编写配置类,以便自定义序列化器,如下代码:

@Configuration
public class RedisConfig {

    // 编写自己的restTemplate
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        // 设置连接工厂
        template.setConnectionFactory(redisConnectionFactory);
        // 创建自己想要的序列化器
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        // 对象映射
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        // 加载对象映射
        jackson2JsonRedisSerializer.setObjectMapper(om);

        // String序列化器
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();

        // Key采用String序列化器
        template.setKeySerializer(stringRedisSerializer);
        // Hash的key采用String序列化器
        template.setHashKeySerializer(stringRedisSerializer);
        // Value采用jackson序列化器
        template.setValueSerializer(jackson2JsonRedisSerializer);
        // Hash的value采用jackson序列化器
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }
}

这里使用jackson的序列化器把key转换成json字符串,可以有效的避免乱码问题。

7. 封装工具类

如果在日常开发中使用redisTemplate中现成的方法来操作redis,就无法避免每次都需要编写redisTemplate.opsForXXX(),使得代码非常臃肿,所以在企业开发中我们通常封装自己的工具类,方便使用,还能使代码看起来更加简洁易懂,以本人封装的工具类作为参考:

@Component
public final class RedisUtil {

    @Autowired
    @Qualifier("redisTemplate")
    private RedisTemplate redisTemplate;

    public boolean expire(String key, long timeout) {
        try {
            if (timeout > 0) {
                redisTemplate.expire(key, timeout, TimeUnit.SECONDS);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }

    }

    public Long ttl(String key) {
        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
    }

    public boolean del(String... keys) {
        if (keys.length != 0) {
            return redisTemplate.delete(Arrays.asList(keys)) > 0;
        }
        return false;
    }

    //---------------String相关操作-----------------

    public boolean set(String key, Object value) {
        try {
            redisTemplate.opsForValue().set(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    public boolean set(String key, Object value, Long seconds) {
        try {
            redisTemplate.opsForValue().set(key, value, seconds, TimeUnit.SECONDS);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    public Object get(String key) {
        return StringUtils.hasLength(key) ? redisTemplate.opsForValue().get(key) : null;
    }

    public Double incr(String key, double num) throws Exception {
        if (num < 0) {
            throw new RuntimeException("递增因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, num);
    }

    public Long decr(String key, long num) {
        if (num > 0) {
            throw new RuntimeException("递减因子必须小于0");
        }
        return redisTemplate.opsForValue().decrement(key, num);
    }

    //---------------Hash相关操作-----------------

    public boolean hset(String key, String field, Object value) {
        try {
            redisTemplate.opsForHash().put(key, field, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    public boolean hset(String key, String field, Object value, long timeout) {
        try {
            this.hset(key, field, value);
            if (timeout > 0) {
                this.expire(key, timeout);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    public boolean hmset(String key, Map<String, Object> entries) {
        try {
            redisTemplate.opsForHash().putAll(key, entries);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    public boolean hmset(String key, Map<String, Object> entries, long timeout) {
        try {
            this.hmset(key, entries);
            if (timeout > 0) {
                this.expire(key, timeout);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    public Object hget(String key, String field) {
        return StringUtils.hasLength(key) ? redisTemplate.opsForHash().get(key, field) : null;
    }

    public List hmget(String key, List<String> fields) {
        return redisTemplate.opsForHash().multiGet(key, fields);
    }

    public Map<String, Object> hgetall(String key) {
        return redisTemplate.opsForHash().entries(key);
    }

    public Long hdel(String key, Object... fields) {
        return redisTemplate.opsForHash().delete(key, fields);
    }

    public Boolean hexists(String key, String field) {
        return redisTemplate.opsForHash().hasKey(key, field);
    }

    public double hincr(String key, String field, double incr) {
        return redisTemplate.opsForHash().increment(key, field, incr);
    }

    public double hdecr(String key, String field, double decr) {
        return redisTemplate.opsForHash().increment(key, field, -decr);
    }

    //---------------List相关操作-----------------
    public boolean lpush(String key, Object value) {
        try {
            redisTemplate.opsForList().leftPush(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    public boolean lpush(String key, List value) {
        try {
            redisTemplate.opsForList().leftPushAll(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    public boolean rpush(String key, Object value) {
        try {
            redisTemplate.opsForList().leftPush(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    public boolean rpush(String key, List value) {
        try {
            redisTemplate.opsForList().rightPushAll(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    public Object lpop(String key) {
        return StringUtils.hasLength(key) ? redisTemplate.opsForList().leftPop(key) : null;
    }

    public Object rpop(String key) {
        return StringUtils.hasLength(key) ? redisTemplate.opsForList().rightPop(key) : null;
    }

    public Object lindex(String key, long index) {
        return StringUtils.hasLength(key) ? redisTemplate.opsForList().index(key, index) : null;
    }

    public Long llen(String key) {
        return redisTemplate.opsForList().size(key);
    }

    public boolean lrem(String key, Object value) {
        try {
            redisTemplate.opsForList().remove(key, 1, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    public boolean lset(String key, long index, Object value) {
        try {
            redisTemplate.opsForList().set(key, index, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    //---------------Set相关操作-----------------
    public boolean sadd(String key, Object... value) {
        try {
            redisTemplate.opsForSet().add(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    public Set smembers(String key) {
        return redisTemplate.opsForSet().members(key);
    }

    public Long srem(String key, Object... values) {
        return redisTemplate.opsForSet().remove(key, values);
    }

    public boolean sismember(String key, Object value) {
        return redisTemplate.opsForSet().isMember(key, value);
    }

    public Long scard(String key) {
        return redisTemplate.opsForSet().size(key);
    }

    //---------------Zset相关操作-----------------
    public boolean zadd(String key, Object value, double score) {
        try {
            redisTemplate.opsForZSet().add(key, value, score);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    public boolean zrem(String key, Object... values) {
        try {
            redisTemplate.opsForZSet().remove(key, values);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    public Long zcard(String key) {
        return redisTemplate.opsForZSet().size(key);
    }

    public Long zcount(String key, double min, double max) {
        return redisTemplate.opsForZSet().count(key, min, max);
    }
}

到这里,Springboot与redis的整合已经基本完成。

九、Redis.conf

Redis.conf 作为Redis的配置文件,我们有必要了解其中的一些重要配置,以便日后的项目搭建。

1. Unit 单位

指定Redis分配的内存大小,默认是不配置的,配置时不区分大小写字母。

不同内存单位对应的字节数
# 1k => 1000 bytes
# 1kb => 1024 bytes
# 1m => 1000000 bytes
# 1mb => 1024*1024 bytes
# 1g => 1000000000 bytes
# 1gb => 1024*1024*1024 bytes
2. Includes 包含

指定其他配置文件的路径,引入它们的配置。

# include /path/to/local.conf
# include /path/to/other.conf
3. Network 网络

指定可访问的IP地址,可设置多个,如下:

bind 127.0.0.1 58.62.32.108
4. Protect Mode 保护模式

保护模式关闭后,外部网络可以直接访问;开启后,只允许绑定的IP地址访问,一般默认是开启的。

protected-mode yes
5. Port 端口号

指定Redis占用的端口号,默认是6379,可根据情况自行修改。

port 6379
6. Daemonize 守护进程

是否把Redis作为守护进程启动。一般都设置为yes,避免断开连接后Redis服务自动关闭。

daemonize yes
7. Pid File pid文件

当指定了端口号,就需要指定pid文件路径。该文件会在redis服务启动时创建,redis服务关闭时删除。

pidfile /var/run/redis_6379.pid #日志将记录在这个文件
8. Log Level 日志级别

设置输出日志的级别,有如下选项:

# debug (a lot of information, useful for development/testing)  开发环境
# verbose (many rarely useful info, but not a mess like the debug level)
# notice (moderately verbose, what you want in production probably) 生产环境
# warning (only very important / critical messages are logged)
loglevel notice #默认是notice
9. Log File 日志文件

指定日志记录的文件路径,默认是空字符串,会输出到空设备,即无记录:

logfile "/develop/redis-6.0.8/log/redis.log"
10. Databases 数据库

设置初始的数据库数量,默认是16。

databases 16
11. Always show logo 始终展示图标

是否始终展示redis的图标,默认是yes。

always-show-logo yes

十、Redis持久化

Redis是内存数据库,如果不把内存中的数据保存到磁盘中,那么一旦服务器进程退出,redis中的数据也会丢失,所以Redis提供了持久化功能。

1. RDB ( Redis Database )

在指定的时间间隔内将内存中的数据集状态写入磁盘,也就是行话讲的Snapshot快照,redis进行数据恢复时是将快照文件直接读取到内存里。

Redis会单独创建一个子进程(fork)来进行持久化。它会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。

优点:整个过程中,主进程是不进行任何IO操作的,确保了极高的性能。如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感的话,RDB方式要比AOF方式更加的高效。

缺点:RDB的缺点是最后一次持久化以后的数据可能会丢失。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4mOZ7UwG-1613040297271)(C:\Users\Miguel\AppData\Roaming\Typora\typora-user-images\image-20210202210339857.png)]

1) 快照间隔

可以给快照设定条件,达到条件就执行快照。

save 900 1     # 900秒内,有至少1个key被修改,就执行快照
save 300 10    # 300秒内,有至少10个key被修改,就执行快照
save 60 10000  # 60秒内, 有至少10000个key被修改,就执行快照
2) 快照文件

可以指定快照文件的名称,默认是dump.rdb,该文件默认保存在redis根目录。redis启动时会自动检查dump.rdb 并恢复其中的数据。

dbfilename dump.rdb
3)执行快照的条件

以下三种条件会触发快照:

  • 满足save配置的规则
  • 执行flushdb / flushall 命令后
  • 退出redis
2. AOF ( Append Only File)

AOF是以日志的性质把Redis执行过的每个写操作都记录下来,只追加文件而不修改文件,Redis启动时会读取aof文件重新构建数据,换言之,就是把日志记录的操作指令从前到后执行一遍。

1)开启AOF

默认情况下,AOF是关闭的,如果想采用AOF方式持久化,需要自行修改为以下配置:

appendonly yes
2)文件名设置

默认情况下,文件名为appendonly.aof,可自行修改:

appendfilename "appendonly.aof"
3) 频率设置

默认情况是每秒保存一次。

appendfsync everysec
4)文件修复

如果 appendonly.aof 文件被损坏,就会导致Redis服务无法启动,此时我们可以使用redis-check-aof 对其进行修复,命令如下:

> redis-check-aof --fix ../appendonly.aof

十一、发布订阅

1. 订阅频道

语法格式:

SUBSCRIBE [channel…]

例如:

> SUBSCRIBE runoobChat
2. 发布消息

语法格式:

PUBLISH channel message

例如:

> PUBLISH runoobChat "Redis PUBLISH test"
3. 实现原理

redis-server里维护了一个字典,字典的键就是一个个频道,而字典的值则是一个链表,链表中保存了所有订阅这个频道的客户端。当用户通过SUBSCRIBE命令订阅某频道时,就是将客户端添加到指定频道的链表中。

当发布者通过PUBLISH命令发布消息时,redis-server会使用指定的频道作为键去查找链表,最后遍历这个链表,将消息发布给每个订阅者客户端。

十二、Redis主从复制

1. 概念

主从复制,是指将一台Redis服务器的数据复制到其他的Redis服务器。前者称为主节点(Master),后者称为从节点(Slave);数据的复制是单向的,只能由主节点到从节点。

默认情况下,每台Redis服务器都是主节点;且一个主节点可以有多个从节点(或没有从节点),但一个从节点只能有一个主节点。

2. 主要作用

主从复制的作用主要包括:

  1. 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
  2. 故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;这实际上是一种服务冗余。
  3. 负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写Redis数据时应用连接主节点,读Redis数据时应用连接从节点),分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量。
  4. 高可用基石:主从复制是哨兵和集群能够实施的基础,
3. 环境搭建

在同一台主机上搭建一个Redis伪集群。

1)查看配置

首先了解一下Redis主从复制的相关信息:

> info replication # 查看当前库的主从复制信息
# Replication
role:master  # 角色 默认是master
connected_slaves:0  # 从机数量
master_replid:80834ceaa532efb740ff48d2aa1ec1d12c8219b5
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
2)修改相关配置

复制多份配置文件,对应多个redis服务

> mkdir mconfig
> mv redis.conf mconfig
> cd mconfig
> cp redis.conf redis79.conf
> cp redis.conf redis80.conf
> cp redis.conf redis81.conf

三个配置文件都要做相应的配置修改,主要有:

  1. port:端口号,这里分别配成6379、6380、6381
  2. logfile:日志文件,这里分别设置为6379.log、6380.log和6381.log
  3. pidfile:端口对应文件,改为对应的端口号,例如:/var/run/redis_6380.pid
  4. dbfilename:快照文件,加上对应的端口号,例如:dump6381.rdb
3)启动服务

分别启动三个redis服务,语法格式如下:

> redis-server mconfig/redis_63XX.conf

启动完成后,可通过命令查看进程:

> ps -ef | grep redis
root     25942     1  0 15:07 ?        00:00:00 redis-server *:6379
root     25951     1  0 15:08 ?        00:00:00 redis-server *:6380
root     25960     1  0 15:09 ?        00:00:00 redis-server *:6381
root     25969 25880  0 15:09 pts/3    00:00:00 grep --color=auto redis

看到上面三个redis进程,证明过程无误。

4)主从设置

默认情况下,redis服务器都是主机,我们有两种方式可以设置从机:

(1) 手动在redis-cli中操作认主。

我们现在有3台redis,如果让端口号为6379的redis作为主机,则我们只需要在另外两台redis服务器上操作,语法格式:

SLAVEOF host port

例如:

> slaveof 127.0.0.1 6379
OK
(2) 修改Replication配置

在配置文件中,可以配置主机IP、端口号和主机密码。

# replicaof <masterip> <masterport>
replicaof 127.0.0.1 6379
# masterauth <masterpass>
masterauth 123456

修改完成后,重新启动redis服务,这样就建立了一主二从的关系。

再次通过命令查看相关信息,如下:

> info replication
# Replication
role:master
connected_slaves:2
slave0:ip=127.0.0.1,port=6381,state=online,offset=126,lag=0
slave1:ip=127.0.0.1,port=6380,state=online,offset=126,lag=1
master_replid:8b52394f08cdd677a6c6ce533576e21543a4d7fe
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:126
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:126

注意:

  1. 主从关系建立后,从机就会被禁止写操作。

  2. 如果是链式的主从关系,主机宕机后可手动通过命令 slaveof no one 设置新的主机。

  3. 企业开发中,一般使用哨兵模式,而很少通过手动干预设置新主机。

4. 哨兵模式

Redis的哨兵模式是对所有Redis服务进行监控,每个哨兵需要一个独立的进程。

当一个哨兵监控的主机没有响应时,该哨兵就会认为这台主机已下线,称为主观下线

该哨兵会通知其他哨兵继续请求这台主机,如果也没有响应,这台主机就会被认为客观下线

一台主机客观下线后,所有哨兵会基于投票算法选出新的主机。

1)配置哨兵

首先需要创建配置文件sentinel.conf,并配置以下内容:

sentinel monitor myredis 127.0.0.1 6379 1

这行配置的格式为:

sentinel monitor 主机名 主机IP 服务端口号 X

最后的数字X表示当有X个哨兵认为主机下线了,这台主机才算真正下线。

2)启动哨兵

redis-sentinel用于启动哨兵进程,命令如下:

> redis-sentinel sentinel.conf
3)测试

停止端口为6379的redis服务,等待一段时间后,哨兵进程将会执行failover操作,重新选出新的主机。

优点:

  1. 哨兵集群,基于主从复制模式,所有的主从配置优点,它都有。
  2. 主从可以切换,故障可以转移,系统的可用性更好。
  3. 哨兵模式就是主从模式的升级,从手动到自动,更加健壮。

缺点:

  1. Redis不好在线扩容,集群容量一旦达到上限,在线扩容就十分麻烦。
  2. 实现哨兵模式的配置特别麻烦,有很多选项。

十三、Redis缓存穿透和雪崩

1. Redis缓存穿透

概念:用户发送的读请求一般会先到Redis缓存服务器,如果缓存中没有,即缓存未命中,用户的读请求会直接访问后台的数据库(如:Mysql),这时候就发生了缓存穿透。

解决方法:

​ 1)布隆过滤器:它是一种数据结构,对所有可能查询的参数以hash形式存储,在控制层进行校验,不符合则丢弃,从而避免了对底层存储系统的查询压力。

​ 2)缓存空对象:当缓存未命中后,即使返回的空对象也缓存起来,同时设置一个过期时间,之后再访问这个数据将会从缓存中获取,保存了后端数据源。

2. 缓存击穿

概念:一个Key非常热点,在不断承担着大并发访问,当这个key在失效的瞬间,持续的高并发就穿破缓存,直接请求数据库,这就是缓存击穿。

解决方法:

​ 1)设置热点key永不过期:从缓存层面来看,没有设置过期时间,所以不会出现热点key过期后产生的问题。

​ 2)加互斥锁:使用分布式锁,保证对于每个key同时只有一个线程去查询后端服务,其他线程没有获得分布式锁的权限,因此只能等待。这种方式将高并发的压力转移到了分布式锁,因此对分布式锁的考验很大。

3. 缓存雪崩

概念:缓存雪崩,是指在某一个时间段,缓存集中过期失效,这会导致数据库承受周期性的波峰式压力。

解决方案:

​ 1)redis高可用:增设多台Redis服务器,搭建Redis集群。

​ 2)限流降级:在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。

​ 3)数据预热:在即将发生高并发访问前,手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量合理。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值