Redis

学习方式:不是为了面试和工作学习!。兴趣是最好得老师?

  • 上手就用(是什么、怎么用)
  • 基本的理论先学习,然后将知识融会贯通

课程安排

  • nosql讲解
  • 阿里巴巴架构演进
  • nosql数据模型
  • nosql四大分类
  • CAP
  • BASE
  • redis入门
  • redis安装(windows,linux)
  • 五大基本数据类型
    • String
    • List
    • Set
    • Hash
    • Zest
  • 三种特殊得数据类型
    • geo
    • hyperlog
    • bitmap
  • 配置详解
  • Redis持久化
    • RDB
    • AOF
  • Redis事务操作
  • Redis实现发布订阅
  • Redis主从复制
  • Redis哨兵模式(现在公司得所有集群都早用)
  • 缓存穿透
  • 缓存击穿
  • 缓存雪崩
  • 基础Api之Jedis
  • SpringBoot集成Redis
  • Redis得实战分析

Nosql概述

为什么要用Nosql

现在时2020年了-------大数据时代;

大数据一般的数据库无法进行分析处理了!2006年Hadoop发布

压力越来越大,适者生存!一定要逼着自己学习,生存法则!

淘宝 最初 PHP

1、单机Mysql的年代!

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jyaWEj7b-1622905371504)(https://i.loli.net/2021/06/05/AeFW1kszhOPUuVd.png)]

90年代,一个基本的网站访问量一般不会太大,单个数据库足够了!

那个时候,更多的去使用静态网页HTML,服务器根本没有太大的压力!

思考:这种情况i按下,整个网站的瓶颈是什么?

1、如果数据量太大,一个机器放不下了

2、数据索引(B+Tree),一个机器的内存放不下

3、访问量(读写混合),一个服务器承受不了

只要你出现以上三种情况,那么你必须要晋级!

2、Memcached(缓存)+Mysql + 垂直拆分(读写分离)

网站80%的情况都在查,每次都去查数据库十分麻烦!所以可以使用缓存减轻压力!

发展过程:优化数据结构和索引----->文件缓存(IO)----->Memcached(当时最热门的的技术)

不确认,你们的竞争对手并不是人才,而是那些图安稳呦踏实的人

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s9rBjr9s-1622905371512)(https://i.loli.net/2021/06/05/pOFfjmPUaH2Iy3A.png)]

​ 读写分离+缓存

3、分库分表+水平拆分+MySQL集群

技术和业务发展的同时,对人的要求也越来越高!

本质:数据库(读写)

早些年MyISAM:表锁,十分影响效率,在高并发下就不行了

转战InnoDB:行锁

慢慢开始分库分表来解决写的压力!MySQL在那个年代推出了表分区,MySQL集群,很好的解决了那个年代的问题,按表行分,按业务分!

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5wnbHP4q-1622905371515)(https://i.loli.net/2021/06/05/MEYPAcejBW92HFw.png)]

4、如今的年代

技术爆炸

2010(按键手机)—2020,十年之间发生了翻天覆地的变化;(定位,也是一种数据;音乐,热榜)

MySQL等关系型数据库就不够用了!数据量大,变化很快!出现了新的如:图形数据库,JSON(如BSON)

MySQL有的使用它来存储一些比较大的文件,博客,图片!数据库表很大,效率就底了!如果有一种专门的数据库来处理这种数据,

MySQL压力就会变得十分小(研究如何处理这些问题!)大数据IO压力下,表几乎没法更大!

目前一个基本的互联网项目

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Z5Mxmg8R-1622905371519)(https://i.loli.net/2021/06/05/8mo3U5ZW7Ra6b1Y.png)]

为什么要用NoSQL

用户的个人信息,社交网络,地理位置。用户自己产生的数据,用户日志等等爆发式增长

这个时候就要使用NoSQL数据库了,NoSQL数据库可以很好解决这种问题!

什么是NoSQL

NoSQL

NoSQL = Not Only SQL

关系行数据库:表格,行,列

泛指非关系数据库,随着web2.0互联网的诞生,传统的关系型数据库很难对付web2.0时代!尤其是超大规模的高并发的社区!暴露很多难以克服的问题,NoSQL在当今大数据的背景下发展的非常迅速,Redis是发展最快,也是必须要掌握的!

很多的数据类型如:用户的个人信息,社交网络,地理位置。用户自己产生的数据,用户日志等。这些数据的存储不需要一个固定的格式!!不需要过多的操作就可以横向扩展!Map<String,Object>使用键值对来控制

NoSQL特点

解耦!

1、方便扩展(数据之间没有关系,很好扩展)

2、大数据量高性能(Redis一秒写入8万次,读取11万次,NoSQL的缓存时记录级的,是一种细粒度的,所以效率比较高)

3、数据类型是多样型的!(不需要实现设计数据库!随取随用!如果数据量非常大的表,很多人就无法设计了)

4、传统的RDBMS和NoSQL

传统的RDBMS
- 结构化组织
- SQL
- 数据和关系都存在单独的表 row col
- 数据操作,数据定义语言
- 严格的数据一致性
- 基础的事务
- ....
NoSQL
- 不仅仅是数据
- 没有固定的查询语言
- 键值对存储,列存储,文档存储,图形数据库(社交关系)
- 最终一致性
- CAP定理和BASE (异地多活)初级架构师!
- 高性能,高可用,高可扩展

了解:3V + 3高

大数据时代的3V:主要是描述问题的

​ 1、海量Volume

​ 2、多样Variety

​ 3、实时Velocity

大数据时代的3V:主要是描述问题的

​ 1、 高并发

​ 2、高可用

​ 3、高性能

真正在公司的实践,都是RDBMS和NoSQL一起使用

技术无高低之分,就看如何使用!(提示内功,思维的提高)

阿里巴巴演进分析

敏捷开发、极限编程

没有什么是中间加一层解决不了的,如果一层解决推荐文档不了就加两层。

# 1、商品的基本信息
	名称、价格、商家信息;
	关系型数据库就可以解决了!MySQL/Oracle (淘宝早年就取IOE了!-王坚:推荐文章-阿里云的这群疯子)
# 2、商品的描述、评论(文字比较多)
	文档数据库中,MongoDB
# 3、图片
	分布式文件系统 FastDFS
	- taobao的TFS
	- Google的GFS
	- HaDoop的HDFS
	- aliyun的OSS
# 4、商品的关键字(搜索)
	- 索索引擎 solar elasticsearch
	- ISerach: 多隆(夺取了解这些技术大佬)
所有牛逼的人都有一段苦逼的岁月!但是只要你像SB一样坚持,中将NB
# 5、商品热门的波段信息
	- 内存数据库
	- Redis、Tair、Memcached...
# 6、商品的交易、外部的支付接口
	- 三方应用

要知道,一各简单的网页背后的技术一定不是大家所想那样简单!

大型互联网应用的问题:

  • 数据类型太多
  • 数据源繁多,经常重构
  • 数据要改造,大面积改造

解决问题:

数据统一服务层

NoSQL四大分类

KV键值对:

  • 新浪:Redis
  • 美团:Redis+Tair
  • 阿里、百度:Redis+memecache

文档数据库(bson格式 和json一样):

  • MongoDB(一般必须要掌握)
    • MongoDB一个基于分布式文件存储的数据库,C++编写,主要用来处理大量的文档
    • MongoDB是一个介于关系型数据库和非关系型数据库中间的产品!MongoDB是非关系型数据库中功能最丰富,最像关系型数据库的。
  • ConthDB

列存储数据库:

  • HBase(大数据中)
  • 分布式文件系统

图形关系数据库:

  • 它不是存图形,放的是关系,比如:朋友圈社交网络,广告推荐
  • Neo4j

四者对比

Redis入门

概述

Redis是什么?
Redis(Remote Dictionary Server ),即远程字典服务,是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。
redis语言支持
区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。。
当下最热门的NoSQL之一!也被人们称之为结构化数据库。
Redis能干吗
1、内存存储、持久化,内存断电即失、所以持久化很重要(rdb、aof)
2、效率高,可用于高速缓存
3、发布订阅系统
4、地图信息分析
5、计时器,计数器(浏览量)
6、…
特性
1、多样的数据
2、持久化
3、集群
4、事务

学习中用到的东西

  1. 狂神的公众号:狂神说
  2. 官网:https://redis.io/
  3. redis中文网:http://www.redis.cn/
  4. 下载:官网下载
    redis下载
    windows版本需要去github下载。
    Redis推荐的都是在Linux上搭建的

Windows安装

  1. 下载安装包:

  2. 下载完得到压缩包:所有的环境都要安装到一个目录下

  3. 解压
    在这里插入图片描述

  4. 开启redis,双击运行服务
    redis

  5. 使用redis客户端连接(redis-cli.exe)
    在这里插入图片描述
    redis推荐使用Linux开发!!!!
    redis官方没有windows支持,是微软自己维护的!

Linux安装

  1. 下载安装包

  2. 解压Redis的安装包!程序/opt目录下

  3. 进入解压后的文件,可以看到reids的配置文件
    在这里插入图片描述

  4. 环境安装:

yum install gcc-c++

make

make install

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

  1. redis的默认安装路径:usr/local/bin
    在这里插入图片描述

  2. 将redis的配置文件拷贝到当前目录下
    在这里插入图片描述

  3. redis默认不是后台安装的,要修改配置文件:daemonize改为yes

  4. 通过指定的配置文件启动redis服务

  5. 使用redid客户端进行连接 redis-cli -p 6379
    在这里插入图片描述

  6. 查看redis进程
    在这里插入图片描述

  7. 关闭redis shutdown

  8. 再次查看进程

  9. 配置其他主机访问,默认只能本机访问

    • 更改配置文件
      在这里插入图片描述
  • 同时修改配置文件,关闭保护模式,改为no
    在这里插入图片描述

  • 重启redis
    关闭:redis-cli shutdown;启动:redis-server ./myconfig/redis.conf

  • 查看进程
    在这里插入图片描述

  1. 后面会使用单机多redis启动集群

性能测试

redis-benchamark 是一个官方自带的压力测试工具
命令:redis-benchmark [option] [option value]
在这里插入图片描述

#测试:100个并发连接 100000请求
redis-benchmark -h 127.0.0.1 -p 6379 -c 100 -n 100000

分析:
在这里插入图片描述

基础知识

redis默认有16个数据库,配置文件中有
在这里插入图片描述

默认使用的是第0个,可以使用select进行切换

[root@iZuf632ma16pdm98oqcsogZ bin]# redis-cli -p 6379
127.0.0.1:6379> select 3 # 切换数据库
OK
127.0.0.1:6379[3]> dbsize # 数据库大小
(integer) 0
127.0.0.1:6379[3]> set name zhangsan # 保存一个值
OK
127.0.0.1:6379[3]> dbsize # 再次查看数据库大小
(integer) 1

查看所有的key:keys *
清空当前数据库:flushdb
清空所有数据库:flushall

思考:为什么是6379?粉丝效应

redis 是单线程的!
明白resid是很快的,官方表示,redis是基于内存操作的,cpu不是redis的性能瓶颈,而是机器的内存和网络带宽,所以就用单线程了。

Redis是c语言编写的,官方 100000+的qbs,完全不比同样使用key-value的Memecahche差!
Redis为什么单线程还这么快?
1、误区1:高性能的服务器一定是多线程的?
2、误区2:多线程(CPU上下文会切换)一定比单线程效率高?
核心:redis是将所有的数据全部放在内存中,所以说使用单线程操作效率就是最高的,多线程调度是会产生上下文切换,耗时比较厉害。对于内存系统来说,单线程才是效率高的,才是最佳方案

五大基本数据类型

官网
在这里插入图片描述
翻译:
Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库缓存消息中间件。 它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。

Redis-Key

127.0.0.1:6379> keys *
1) "name"
127.0.0.1:6379> set key value 
OK
127.0.0.1:6379> exists key #判断键是否存在
(integer) 1
127.0.0.1:6379> exists key1 #判断键是否存在
(integer) 0
127.0.0.1:6379> move key 1 #删除键
(integer) 1
127.0.0.1:6379> keys *
1) "name"
127.0.0.1:6379> set key value
OK
127.0.0.1:6379> expire key 10 #定时数据过期
(integer) 1
127.0.0.1:6379> ttl key # 查看当前key的剩余时间
(integer) 6
127.0.0.1:6379> ttl key
(integer) 4
127.0.0.1:6379> ttl key
(integer) 3
127.0.0.1:6379> ttl key
(integer) 1
127.0.0.1:6379> ttl key
(integer) -2
127.0.0.1:6379> type name  # 查看某键对应值得类型
string
127.0.0.1:6379> 

命令:
在这里插入图片描述

String(字符串)

90%的java程序员只会使用一个String类型!

# 操作成功返回1,操作失败返回0


#基本命令
set key1 v1	 		# 设置值
get key1 			# 获取值
keys * 				# 获取所有key
exists key1			#判断某个key是否存在
append key1 hello 	# 动态修改字符转,若原来不存在,则会新建
get key1
STRLEN key1 		# 字符串长度
eg:实战
127.0.0.1:6379> set key1 v1
OK
127.0.0.1:6379> get key1
"v1"
127.0.0.1:6379> keys *
1) "key1"
127.0.0.1:6379> exists key1
(integer) 1
127.0.0.1:6379> append key1 hello
(integer) 7
127.0.0.1:6379> get key1
"v1hello"
127.0.0.1:6379> strlen key1
(integer) 7
127.0.0.1:6379> 

#################################################################
set views 0		#
get views		#
incr views		#自增1
decr views		#自减1

#设置步长增减
incrby views 10
decrby views 9
eg:实战
127.0.0.1:6379> set views 0
OK
127.0.0.1:6379> get views
"0"
127.0.0.1:6379> incr views
(integer) 1
127.0.0.1:6379> get views
"1"
127.0.0.1:6379> incr views
(integer) 2
127.0.0.1:6379> get views
"2"
127.0.0.1:6379> decr views
(integer) 1
127.0.0.1:6379> get views
"1"
127.0.0.1:6379> incrby views 10
(integer) 11
127.0.0.1:6379> decrby views 5
(integer) 6
#########################################################################
# 字符串范围
set key1 hello
get key1
getrange key01 0 3		#获取字符串的范围 [0,3]
getrange key01 0 -1		#获取整个字符串,此时和get命令一致
eg:实战
127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> set key1 hello
OK
127.0.0.1:6379> get key1
"hello"
127.0.0.1:6379> getrange key1 0 3
"hell"
127.0.0.1:6379> getrange key1 0 -1
"hello"

#替换字符串
set key2 abcdefg
get key2
setrange key2 1 xx		#替换指定位置开始的字符串

eg:实战
127.0.0.1:6379> set key2 abcdefg
OK
127.0.0.1:6379> get key2
"abcdefg"
127.0.0.1:6379> get key2
"abcdefg"
127.0.0.1:6379> setrange key2 1 xx
(integer) 7
127.0.0.1:6379> get key2
"axxdefg"

#########################################################################
setex(set with expire)			#设置过期时间
setnx(set with not exist)		#如果不存在在设置,在分布式锁中常常使用
setnx key ”hello“				#返回0,添加失败

eg:实战
127.0.0.1:6379> setex key 30  value
OK
127.0.0.1:6379> ttl key
(integer) 24
127.0.0.1:6379> ttl key
(integer) 21
127.0.0.1:6379> setnx key value # 第一个命令30s后执行
(integer) 1
127.0.0.1:6379> setnx key value1
(integer) 0
127.0.0.1:6379> 
############################################################################
mset			#一次设置多个值
mset k1 v1 k2 v2
mget			#一次获取多个值
sget k1 k2

msetnx			#如果不存在在创建,有一个失败就失败 
eg:实战
127.0.0.1:6379> mset k1 v1 k2 v2
OK
127.0.0.1:6379> mget k1 k2
1) "v1"
2) "v2"


#实战:redis保存一个对象
set user:1 {name:zhangsan,age:3} #设置一个user:1 对象 值为{name:zhangsan,age:3}
# 这里的key: user:{id}:{field}
mset user:1:name zhangsan user:1:age 25
mget user:1:name user:1:age

127.0.0.1:6379> mset user:1:name zhangsan user:1:age 18
OK
127.0.0.1:6379> mget user:1:name
1) "zhangsan"
#############################################################################
getset db redis		#如果不存在则返回nil,如果存在则返回原值,并保存新值

127.0.0.1:6379> getset db redis
(nil)
127.0.0.1:6379> getset db redis
"redis"
127.0.0.1:6379> getset db mongo
"redis"

数据结构是相同的,jedis

String类型的使用场景:value不仅可以使字符串,还可以是数字

  • 计数器
  • 统计多大那位的数量 uid:6789:follow 0
  • 粉丝数
  • 对象存储

List

基本数据类型,列表

在redis里面,我们可以

所有的List命令都是以l开头的

lpush list one		#将一个值或多个值放到list的头
lpush list two
lrange list 0 -1

rpush list three	##将一个值或多个值放到list的尾部

lrange list 0 -1	#通过区间获取具体的值

eg:实战
127.0.0.1:10002> lpush list one
(integer) 1
127.0.0.1:10002> lpush list two
(integer) 2
127.0.0.1:10002> lpush list three four
(integer) 4
127.0.0.1:10002> rpush list five
(integer) 5
127.0.0.1:10002> lrange list 0 -1
1) "four"
2) "three"
3) "two"
4) "one"
5) "five"
#####################################################################

lpop list 		#移除列表的第一个元素
rpop list		#移除列表的最后一个元素

eg:实战
127.0.0.1:10002> lpop list
"four"
127.0.0.1:10002> lrange list 0 -1
1) "three"
2) "two"
3) "one"
4) "five"
127.0.0.1:10002> rpop list
"five"
127.0.0.1:10002> lrange list 0 -1
1) "three"
2) "two"
3) "one"

#################################################################
lindex list 1		#通过下标获取值,下表从0开始
eg:实战
127.0.0.1:10002> lindex list 1
"two"
127.0.0.1:10002> lrange list 0 -1
1) "three"
2) "two"
3) "one"

#################################################################
llen list		#返回list的长度
eg:实战
127.0.0.1:10002> lrange list 0 -1
1) "three"
2) "two"
3) "one"
127.0.0.1:10002> llen list
(integer) 3
127.0.0.1:10002> 
#################################################################
移除指定值!
lrem list 1 one		2#移除list集合中指定个数的value,精确匹配
lrem list 2 three
eg:实战
127.0.0.1:10002> rpush list 121 122
(integer) 5
127.0.0.1:10002> lpush 121 122
(integer) 1
127.0.0.1:10002> lpush list 121 122
(integer) 7
127.0.0.1:10002> lrange list 0 -1
1) "122"
2) "121"
3) "three"
4) "two"
5) "one"
6) "121"
7) "122"
127.0.0.1:10002> lrem list 2 122
(integer) 2
127.0.0.1:10002> lrange list 0 -1
1) "three"
2) "two"
3) "one"
4) "121"
#################################################################
trim 修减	list截断

ltrim list 1 2		#通过下标截取指定的长度,list变了

eg:实战
127.0.0.1:10002> lrange list 0 -1
1) "three"
2) "two"
3) "one"
4) "121"
127.0.0.1:10002> ltrim list 0 1		#截取list中下标为0-1的值
OK
127.0.0.1:10002> lrange list 0 -1
1) "three"
2) "two"

#################################################################

rpoplpush list newlist		#移除列表的最后一个元素到一个新的列表

eg:实战
127.0.0.1:10002> lrange list 0 -1
1) "three"
2) "two"
127.0.0.1:10002> rpush list 131 132
(integer) 4
127.0.0.1:10002> lrange list 0 -1
1) "three"
2) "two"
3) "131"
4) "132"
127.0.0.1:10002> rpoplpush list newlist
"132"
127.0.0.1:10002> lrange newlist 0 -1
1) "132"
127.0.0.1:10002> lrange list 0 -1
1) "three"
2) "two"
3) "131"
127.0.0.1:10002> rpoplpush list newlist
"131"
127.0.0.1:10002> lrange newlist 0 -1
1) "131"
2) "132"
127.0.0.1:10002> lrange list 0 -1
1) "three"
2) "two"

#################################################################
exists list		#判断列表是否存在
eg:实战
127.0.0.1:10002> exists list
(integer) 1

lpush list-new value
lset list-new 0 newvalue		#将列表中指定下标的值替换为另外一个值,列表不存在会报错,如果存在就更新当前下标的值
eg:实战
127.0.0.1:10002> lpush new-list value
(integer) 1
127.0.0.1:10002> lrange new-liat 0 -1
(empty array)
127.0.0.1:10002> lrange new-list 0 -1
1) "value"
127.0.0.1:10002> lset new-list 0 newvalue
OK
127.0.0.1:10002> lrange new-list 0 -1
1) "newvalue"

#################################################################
lpush list ”hello“
lpush list ”world“

linsert list before|after  ”hello“		#将某个具体的value插入到列表中某个元素的前面或者后面
eg:实战
127.0.0.1:10002> flushdb
OK
127.0.0.1:10002> keys *
(empty array)
127.0.0.1:10002> lpush list hello
(integer) 1
127.0.0.1:10002> linsert list after "hello" world	#引号有没有都行
(integer) 2
127.0.0.1:10002> lrange list 0 -1
1) "hello"
2) "world"

小结

  • 它实际上是一个链表,before—node----after
  • 如果key不存在就创建新的链表
  • 如果key存在,新增内容
  • 如果移除了所有的值,空链表,也代表不存在
  • 在两边插入或改动之,效率偏高,操作中间的值效率会低一些
    消息排队!消息队列 (Lpush rpush)栈(Lpush lpop)

set(集合)

  • set为无序不重复集合
  • set集合操作都是以s开头
sadd myset hello		# 添加一个元素
sadd myset world !# 添加多个元素
smembers myset			#显示所有值
sismember myset			#判断键是否存在

eg:实例
127.0.0.1:10002> sadd myset hello
(integer) 1
127.0.0.1:10002> sadd myset world !
(integer) 2
127.0.0.1:10002> smembers myset
1) "!"
2) "world"
3) "hello"
127.0.0.1:10002> sismember myset hello
(integer) 1
127.0.0.1:10002> 


####################################################

scard myset				#获取set集合中值的个数
srem myset hello		#移除set中某个元素一个set中的元素
spop myset				#随机删除
srandmember myset [n]				#随机选出抽出n个元素,默认1个  做抽奖非常好!!!!!!!
smove set newset valueOfSet			#将一个集合指定的值,移动到新集合

eg:实战
127.0.0.1:10002> scard myset
(integer) 3
127.0.0.1:10002> srem myset hello
(integer) 1
127.0.0.1:10002> scard myset
(integer) 2
127.0.0.1:10002> sadd myset hahaha
(integer) 1
127.0.0.1:10002> spop myset 2
1) "world"
2) "!"
127.0.0.1:10002> smembers myset
1) "hahaha"
127.0.0.1:10002> smove myset newset hahaha
(integer) 1
127.0.0.1:10002> smembers myset
(empty array)
127.0.0.1:10002> smembers newset
1) "hahaha"
####################################################
微博,B站,共同关注(并集)
数字集合类:
- 差集	sdiff set1 set2
- 交集	sinter set1 set2
- 并集	sunion set1 set2

127.0.0.1:10002> flushdb
OK
127.0.0.1:10002> sadd set1 1 2 3 4
(integer) 4
127.0.0.1:10002> sadd set2 3 4 5 6
(integer) 4
127.0.0.1:10002> smembers set1
1) "1"
2) "2"
3) "3"
4) "4"
127.0.0.1:10002> smembers set2
1) "3"
2) "4"
3) "5"
4) "6"
127.0.0.1:10002> sdiff set1 set2
1) "1"
2) "2"
127.0.0.1:10002> sinter set1 set2
1) "3"
2) "4"
127.0.0.1:10002> sunion set1 set2
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
6) "6"

微博:A用户将所有关注的人放在一个set集合中!将他的粉丝也放在一个集合中

Hash (哈希)

  • Map集合,key-<key,value>,这个时候值是一个map集合,本质和String类型没有太大区别,还是一个简单的key-value
  • 命令都已H开头
hset myhash field iwat				#set一个key-value
hget myhash field					#get一个key-value

hmset myhash field1 hello field2 world		
hmget myhash field1 field 2

hgetall myhash			#获取指定hash中所有的key-value

hdel myhash field1		#删除指定hash中的指定key

eg:实战
127.0.0.1:10002> hset myhash key value
(integer) 1
127.0.0.1:10002> hget myhash key
"value"
127.0.0.1:10002> hmset l1 v1 k2 v2
(error) ERR wrong number of arguments for HMSET
127.0.0.1:10002> hmset myhash l1 v1 k2 v2
OK
127.0.0.1:10002> hgetall myhash
1) "key"
2) "value"
3) "l1"
4) "v1"
5) "k2"
6) "v2"
127.0.0.1:10002> hdel myhash l1
(integer) 1
127.0.0.1:10002> hgetall myhash
1) "key"
2) "value"
3) "k2"
4) "v2"
##########################################################
hlen myhash			#获取指定hash'的长度

hexists myhash feild	#判断指定hash中指定key是否存在


hkeys myhash
hvals myhash

eg:实战
127.0.0.1:10002> hgetall myhash
1) "key"
2) "value"
3) "k2"
4) "v2"
127.0.0.1:10002> hlen myhash
(integer) 2
127.0.0.1:10002> hexists myhash k2
(integer) 1
127.0.0.1:10002> hexists myhash k1
(integer) 0
127.0.0.1:10002> hkeys myhash
1) "key"
2) "k2"
127.0.0.1:10002> hvals myhash
1) "value"
2) "v2"

##########################################################

hincreby myhash field3 1

hsetnx myhash field4 hello	#如果不存在则可以设置

eg:实战
127.0.0.1:10002> hset myhash count 1
(integer) 1
127.0.0.1:10002> hget myhash count
"1"
127.0.0.1:10002> hincrby myhash count 1
(integer) 2
127.0.0.1:10002> hincrby myhash count 9
(integer) 11
127.0.0.1:10002> hget myhash count
"11"
127.0.0.1:10002> hsetnx myhash k3 v3
(integer) 1
127.0.0.1:10002> hget myhash k3
"v3"
127.0.0.1:10002> hsetnx myhash k3 v4
(integer) 0
127.0.0.1:10002> hget myhash k3
"v3"

hash变更的数据,hest user:1 name wangweiqiang,尤其是用户信息之类的,经常变动的信息,hash
更适合对象的存储,String更适合字符串存储!

Zset(有序集合)

在set的基础上增加了一个值,set k1 v1 --------------------> zset k1 score1(排序字段) v1

zadd zset 1 one		#添加一个值 add <键名> <排序字段score> <值>
zadd zset 2 two 3 three		#添加多个值

eg:实战
127.0.0.1:10002> 
127.0.0.1:10002> zadd zset 1 hello
(integer) 1
127.0.0.1:10002> zadd zset 2 world 3 !
(integer) 2
127.0.0.1:10002> zrange zset 0 -1
1) "hello"
2) "world"
3) "!"
##########################################################################
zadd salary 2500 xiaoming		#
zadd salary 3000 tom			#
zadd salary 200 iwat

zrangebyscore salary -inf +inf withscores	#zrange zset min max,获取score值在min和max的集合元素,升序排列
zrevrangebyscore salary  +inf -inf # 降序排列

eg:实战
127.0.0.1:10002> zadd salary 2500 tom 5000 zhangsan 99 iwat
(integer) 3
127.0.0.1:10002> zrange salary -inf +inf
(error) ERR value is not an integer or out of range
127.0.0.1:10002> zrangebyscore salary -inf +inf
1) "iwat"
2) "tom"
3) "zhangsan"
127.0.0.1:10002> zrangebyscore salary -inf +inf withscores
1) "iwat"
2) "99"
3) "tom"
4) "2500"
5) "zhangsan"
6) "5000"
127.0.0.1:10002> zrevrangebyscore salary  +inf -inf
1) "zhangsan"
2) "tom"
3) "iwat"


###########################################################################################
zrem salary xiaoming	# 移除指定元素
zcard salary		#获取有序集合中的元素数量
zcount myset 1 2		#获取指定区间的成员数量
zadd myset 1 hello 2 world 3 iwat

eg:实战
127.0.0.1:10002> zrangebyscore salary -inf +inf withscores
1) "iwat"
2) "99"
3) "tom"
4) "2500"
5) "zhangsan"
6) "5000"
127.0.0.1:10002> 
127.0.0.1:10002> zrem salary zhangsan
(integer) 1
127.0.0.1:10002> zrange salary 0 -1
1) "iwat"
2) "tom"
127.0.0.1:10002> zcard salary
(integer) 2
127.0.0.1:10002> zcount salary 0 100
(integer) 1

案例思路:set排序、存储班级成绩、工资表排序
普通消息–1,重要消息–2,带权重识别
排行榜实现

三种特殊的数据类型

geospacial地理位置

朋友的定位,附近的人,打车计算距离?
Redis的Geo在Redis3.2就推出了!这个功能可以推算地理位置的信息,两地之间的距离,方圆几里的人。

geoadd

#  添加地理位置
# 规则:地球的两极无法添加,在java中一键导入了
# 参数 key (精度 维度 名称)
deoadd china:city 经度 维度 name

geopos

geopos china:city beijing #获取指定城市的经度和维度

geodist

两人之间的距离!
单位:
- m:米
- km:千米
- mi:英里
- ft:英尺
geodist china:key beijing xian km

georadius:以给定的经纬度为中心,查询给定半径内的元素

我附近的人?(获取所有人的地址,定位)通过半径去搜索。

geo china:city 110 30 1000 km [withdist] [withcoord] [count n]

GEORADIUSBYMEMBER

找出位于指定元素周围的其他元素
GEORADIUSBYMEMBER key member radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count]

Geohash

GEO低层实现原理其实就是Zset!我们可以Zset的Api操作

查看所有
移除某个元素

Hyperloglog

什么是基数

基数—一个数据集中不重复的元素的个数

简介

Redis Hyperloglog是基数统计的算法!
优点:占用的内存是固定的,2的64次方的次数只需要12KB
网页的UV(一个人访问一个网站多次,但还算一个人!)
传统的方式可以使用set保存用户id,然后就可以统计set中的元素数量作为判断标准

PFadd mykey a b c d e f #hcuangjian diyizu元素
pfadd mykey2 q w e r t y u i a b d #创建第二组元素
pfcount mykey
pfmerge mykey3 mykey mykey2 #合并key和key2到key3,这是一个并集
pfcount mykey3

如果允许容错才能用!有错误率!

Bitmaps

Bitmap详解:https://www.cnblogs.com/54chensongxia/p/13794391.html
在 Redis 中,可以把 Bitmaps 想象成一个以比特位为单位的数组,数组的每个单元只能存储0和1,数组的下标在 Bitmaps 中叫做偏移量
在这里插入图片描述

位存储

疫情:01表示,比如统计疫情感染人数,感染为1未感染为0,则14亿人口也只需要14亿位!打卡!两个状态的都可以使用bitmaps!
Bitmaps位图,数据结构!都是操作二进制位来进行记录,就只要0和1两个状态

测试

setbit sign 1 0 #使用bitmap来记录周一到周末的打卡
setbit sign 2 1
setbit sign 3 0

查看某一天是否打卡
getbit sign 1

统计打卡记录
bitcounts sign

事务

Redis事务本质:一组命令的集合!一个事务中的所有命名会被序列化,在事务执行过程中,会按照顺寻执行(命令队列)!
一次性、顺序性、排他性!执行一系列的命令

--------队列set set set执行----------

Redis中没有隔离级别的概念
命令在入队的时候并没有执行,只是将命令放入队列,执行exec后顺序执行
Redis单条命令是保证原子性的,但是Redis事务不保证原子性

Redis事务:

  • 开启事务:multi
  • 命令入队:·····
  • 执行事务:exec
    锁:Redis可以实现乐观锁

正常开启事务

127.0.0.1:10002> multi		#开启事务
OK
127.0.0.1:10002> set account 1000		#添加命令到队列
QUEUED
127.0.0.1:10002> set out 100			#添加命令到队列
QUEUED
127.0.0.1:10002> decrby account 100			#添加命令到队列
QUEUED
127.0.0.1:10002> exec				#顺序执行事务中(队列中)的命令
1) OK
2) OK
3) (integer) 900

取消事务 discard

127.0.0.1:10002> multi
OK
127.0.0.1:10002> incrby out 200
QUEUED
127.0.0.1:10002> discard		#取消事务
OK
127.0.0.1:10002> get out
"100"

编译性异常(代码有问题!命令有错),事务中所有的命令都不会被执行

127.0.0.1:10002> multi		
OK
127.0.0.1:10002> incrby out 200
QUEUED
127.0.0.1:10002> decrbyy account 200
(error) ERR unknown command `decrbyy`, with args beginning with: `account`, `200`, 
127.0.0.1:10002> exec
(error) EXECABORT Transaction discarded because of previous errors.		#当出现这种错误,整个事务的所有命令都不会执行
127.0.0.1:10002> get out
"100"
127.0.0.1:10002> get account
"900"

运行时异常,任务队列中存在语法性错误,那么执行命令的时候其他命令可以正常执行,错误的抛异常

127.0.0.1:10002> mget k account out
1) "v"
2) "700"
3) "300"
127.0.0.1:10002> multi
OK
127.0.0.1:10002> decrby account 50
QUEUED
127.0.0.1:10002> incrby k 10			#对一个字符串执行加10操作,肯定会出错
QUEUED
127.0.0.1:10002> incrby out 50
QUEUED
127.0.0.1:10002> exec
1) (integer) 650
2) (error) ERR value is not an integer or out of range	#上下两个命令都成功了,只有这句没成功,可见redis的事务不保证原子性
3) (integer) 350

监控 watch

悲观锁:

  • 很悲观,什么时候都会出问题,无论做什么都会加锁
    乐观锁:
  • 很乐观,认为什么时候都不会出问题,所以不会上锁!更新数据的时候去判断一下,在此期间是否有人修改过数据
  • 获取version
  • 更新的时候比较version

测试监控
正常执行

127.0.0.1:10002> set money 1000
OK
127.0.0.1:10002> WATCH money
OK
127.0.0.1:10002> multi
OK
127.0.0.1:10002> set out 100
QUEUED
127.0.0.1:10002> decrby money 100
QUEUED
127.0.0.1:10002> exec
1) OK
2) (integer) 900

测试多线程修改值,使用watch可以当作redis的乐观锁操作

# bash1
127.0.0.1:10002> keys *
(empty array)
127.0.0.1:10002> set money 1000
OK
127.0.0.1:10002> watch money
OK
127.0.0.1:10002> multi
OK
127.0.0.1:10002> set out 1000
QUEUED
127.0.0.1:10002> DECRBY money 1000		#执行完这一条命令后,另一个窗口bash2中执行命令
QUEUED
127.0.0.1:10002> exec		#事务没有执行
(nil)


# bash2
127.0.0.1:10002> incrby money 500
(integer) 1500

操作失败后,取消监视(unwatch),然后早重新监视,获取最新的version

127.0.0.1:10002> UNWATCH
OK
127.0.0.1:10002> WATCH money
OK
127.0.0.1:10002> multi
OK
127.0.0.1:10002> DISCARD
OK
127.0.0.1:10002> get out
(nil)
127.0.0.1:10002> multi
OK
127.0.0.1:10002> set out 500
QUEUED
127.0.0.1:10002> decrby money 500
QUEUED
127.0.0.1:10002> exec
1) OK
2) (integer) 1000
127.0.0.1:10002> 

Jedis

我们要使用java操作redis

什么是Jedis, 是Redis官方推荐的java连接开发工具!使用java操作redis的中间件!如果你要使用Java操作redis,就需要对jedis什么熟悉才是

测试

1、导入依赖

<dependencies>
        <!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>3.6.1</version>
        </dependency>
 </dependencies>

2、测试编码

  • 连接数据库
  • 操作命令
  • 断开连接

常用API

  • String
public class TestString {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("127.0.0.1", 6379);

        System.out.println("所有的键:"+jedis.keys("*"));
        System.out.println("清空数据库:"+jedis.flushDB());
        System.out.println("添加键和值:"+jedis.set("k1", "v1"));
        System.out.println("添加键和值:"+jedis.set("k2", "v2"));
        System.out.println("获取多个键的值:"+jedis.mget("k1", "k2"));
        System.out.println("append修改字符串,不存在则新增:"+jedis.append("k1","append "));
        System.out.println("判断键是否存在"+jedis.exists("k1"));
        System.out.println("某个键对应值的字符串的长度:"+jedis.strlen("k1"));

        System.out.println("添加键和值:"+jedis.set("k3", "0"));
        System.out.println("自增1:"+jedis.incr("k3"));
        System.out.println("按步长10自增:"+jedis.incrBy("k3",10));
        System.out.println("自减1:"+jedis.decr("k3"));
        System.out.println("按步长5自减:"+jedis.decrBy("k3",5));

        System.out.println("按范围读取字符串:"+jedis.getrange("k1",0,3));
        System.out.println("按范围读取字符串(整个):"+jedis.getrange("k1",0,-1));

        System.out.println("替换指定位置的字符串"+jedis.setrange("k1",0,"re"));
        System.out.println("替换之后的字符串:"+jedis.get("k1"));

        System.out.println("设置过期时间:"+jedis.setex("k4",10,"v4"));

        System.out.println("如果不存在则创建:"+jedis.setnx("k4","vv4"));
    }
}

  • List
public class TestList {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("127.0.0.1", 6379);
        System.out.println("所有的键:"+jedis.keys("*"));
        System.out.println("清空数据库:"+jedis.flushDB());
        System.out.println("在头部添加一个元素:"+jedis.lpush("list","one"));
        System.out.println("在头部添加一个元素:"+jedis.lpush("list","two"));
        System.out.println("在头部添加一个元素:"+jedis.lpush("list","three"));
        System.out.println("在头部添加一个元素:"+jedis.lpush("list","four"));
        System.out.println("在头部添加一个元素:"+jedis.lpush("list","five"));
        System.out.println("在尾部添加一个元素:"+jedis.rpush("list","sex"));
        System.out.println("显示List:"+jedis.lrange("list",0,-1));
        System.out.println("从头部移除一个元素:"+jedis.lpop("list"));
        System.out.println("从尾部移除一个元素:"+jedis.rpop("list"));
        System.out.println("显示List:"+jedis.lrange("list",0,-1));
        System.out.println("根据下表获取值:"+jedis.lindex("list",0));
        System.out.println("返回list的长度:"+jedis.llen("list"));
        System.out.println("移除指定个数的值:"+jedis.lrem("list",1,"two"));
        System.out.println("通过下标截取指定的长度,list变了,和lrange不同:"+jedis.ltrim("list",0,1));
        System.out.println("移除列表的最后一个元素到一个新的列表:"+jedis.rpoplpush("list","dstList"));
        System.out.println("显示dstList:"+jedis.lrange("dstList",0,-1));
        System.out.println("判断列表是否存在:"+jedis.exists("list"));
        System.out.println("列表中指定下标的值替换为另外一个值,列表不存在会报错,如果存在就更新当前下标的值:"+jedis.lset("list",0,"1"));
        System.out.println("显示List:"+jedis.lrange("list",0,-1));
        System.out.println("将某个具体的value插入到列表中某个元素的前面或者后面:"+jedis.linsert("list", ListPosition.BEFORE,"four","insret"));
        System.out.println("显示List:"+jedis.lrange("list",0,-1));
    }
}

  • Set
public class TestSet {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("127.0.0.1", 6379);
        System.out.println("往set中添加元素,不存在则创建:"+jedis.sadd("myset", new String[]{"s1", "s2", "s3","s4"}));
        System.out.println("显示所有值:"+jedis.smembers("myset"));
        System.out.println("判断元素是否存在:"+jedis.sismember("myset","s1"));
        System.out.println("获取set中元素的个数:"+jedis.scard("myset"));
        System.out.println("删除set中某个元素:"+jedis.srem("myset","s3"));
        System.out.println("随机删除set中某个元素:"+jedis.spop("myset"));
        System.out.println("显示所有值:"+jedis.smembers("myset"));
        System.out.println("随机抽出n个元素:"+jedis.srandmember("myset",2));
        System.out.println("将一个set的元素一定到新的set:"+jedis.smove("myset","newset","s3"));
        System.out.println("往set中添加元素,不存在则创建:"+jedis.sadd("set1","1","2","3","4"));
        System.out.println("往set中添加元素,不存在则创建:"+jedis.sadd("set2","3","4","5","6"));
        System.out.println("交集:"+jedis.sinter("set1","set2"));
        System.out.println("差集:"+jedis.sdiff("set1","set2"));
        System.out.println("并集:"+jedis.sunion("set1","set2"));
    }
}

  • Hash
public class TestHash {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("127.0.0.1", 6379);
        System.out.println(jedis.ping());
        System.out.println("所有的键:"+jedis.keys("*"));
        System.out.println("清空数据库:"+jedis.flushDB());
        System.out.println("给myhash设置值:"+jedis.hmset("myhash", new HashMap<String, String>() {{
            put("k1", "v1");
            put("k2", "v2");
            put("k3", "v3");
            put("k4", "v4");
            put("k5", "5");
        }}));

        System.out.println("获取所有k-v:"+jedis.hgetAll("myhash"));
        System.out.println("获取hash中某个k的v:"+jedis.hget("myhash","k1"));
        System.out.println("删除某个键-值:"+jedis.hdel("myhash","k1"));
        System.out.println("获取多有k-v:"+jedis.hgetAll("myhash"));
        System.out.println("获取指定hash的长度:"+jedis.hlen("myhash"));
        System.out.println("获取hash所有的key:"+jedis.hkeys("myhash"));
        System.out.println("获取hash所有的value:"+jedis.hvals("myhash"));
        System.out.println("判断某hash中key是否存在:"+jedis.hexists("myhash","k2"));
        System.out.println("hash中某key对应的value自增:"+jedis.hincrBy("myhash","k5",10));
        System.out.println("若hash中某件不存在才创建:"+jedis.hsetnx("myhash","k6","v6"));
        System.out.println("获取所有k-v:"+jedis.hgetAll("myhash"));
    }
}
  • Zset
public class TestZset {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("127.0.0.1", 6379);
        System.out.println("所有的键:"+jedis.keys("*"));
        System.out.println("清空数据库:"+jedis.flushDB());
        System.out.println("添加一(或多)个值 add <键名> <排序字段score> <值>:"+jedis.zadd("salary",new HashMap<String, Double>(4){{
            put("zhangsan",new Double(2000));
            put("lisi",new Double(2500));
            put("wangwu",new Double(3000));
            put("zhaoliu",new Double(5000));
        }}));
        System.out.println("获取score值在min和max的集合元素,升序排列:"+jedis.zrangeByScore("salary",-Infinity,Infinity));
        System.out.println("获取score值在min和max的集合元素,升序排列:"+jedis.zrevrangeByScoreWithScores("salary",Infinity,-Infinity));
        System.out.println("移除指定元素:"+jedis.zrem("salary","zhaoliu"));
        System.out.println("获取指定区间的元素总数:"+jedis.zcount("salary",new Double(2500),new Double(3000)));
        System.out.println("获取指定区间的元素总数:"+jedis.zcard("salary"));
    }
}

  • 事务
public class TestTransaction {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("127.0.0.1", 6379);
        System.out.println(jedis.ping());
        System.out.println("所有的键:"+jedis.keys("*"));
        System.out.println("清空数据库:"+jedis.flushDB());
        jedis.set("out","0");

        Transaction multi = null;
        try {
            //开启一个事务
            multi = jedis.multi();
            //添加命令
            multi.set("account","1000");
            //测试异常
            //int a = 1/0;
            multi.incrBy("out",200);
            multi.decrBy("account",200);
            //执行
            multi.exec();
        } catch (Exception e) {
            //如果出现了异常则对其事务,命令队列中的所有命令均不执行,以保证原子性
            multi.discard();
            e.printStackTrace();
        }finally {
            System.out.println("account、out的值:"+jedis.mget("account", "out"));
            jedis.close();
        }
    }
}

SpringBoot整合

SpringBoot操作数据spring-data jpa jdbc mongodb redis
SpringData也是和SpringBoot齐名的项目

说明:SpringBoot2.x之后,原来使用的jedis被替换成了lettuce
jedis:采用直连方式,多个线程操作的话是不安全的,如果想避免不安全,需要用jedis poll连接池!BIO
lettuce:采用netty,实例可以在多个线程中共享!不存在线程不安全的情况,可以减少线程数据!
源码:

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {

	@Bean
	@ConditionalOnMissingBean(name = "redisTemplate")
	@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
	public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) 		{
		//默认的RedisTemplate
		//两个泛型object,使用需要强转---》<String, Object>
		RedisTemplate<Object, Object> template = new RedisTemplate<>();
		template.setConnectionFactory(redisConnectionFactory);
		return template;
	}

	@Bean
	@ConditionalOnMissingBean
	@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
	public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
		//String常用
		StringRedisTemplate template = new StringRedisTemplate();
		template.setConnectionFactory(redisConnectionFactory);
		return template;
	}

}

测试

1、导入依赖

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

2、配置连接

spring.redis.host=127.0.0.1
spring.redis.port=6379

3、测试

@SpringBootTest
class Redis02SpringBootApplicationTests {
    @Autowired
    private RedisTemplate redisTemplate;
    @Test
    void contextLoads() {
        System.out.println("success");
        /*
            1、ops
            2、常用的直接点出来
            3、获取连接
         */
        //opsForValue
        //opsForHash
        //opsForList
        //opsForSet
        //opsForZSet
        //opsForGeo
        redisTemplate.opsForValue().set("","");
        
        //获取连接
        RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
        connection.flushDb();
    }
}

在这里插入图片描述
在这里插入图片描述
自定义配置RedisTemplate:

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

        //json的序列化配置
        Jackson2JsonRedisSerializer jsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jsonRedisSerializer.setObjectMapper(om);

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

        //设置
        template.setKeySerializer(stringRedisSerializer);
        template.setHashKeySerializer(stringRedisSerializer);
        template.setValueSerializer(jsonRedisSerializer);
        template.setHashValueSerializer(jsonRedisSerializer);

        //使属性设置生效
        template.afterPropertiesSet();
        return template;
    }
}

Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐