Redis
- redis是一个key-value存储系统
- Redis 是一个高性能的key-value数据库
- Redis支持主从同步
- redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步
**memcached:**是一套分布式的高速缓存系统
Nosql
NoSQL,not only SQL ,泛指非关系型的数据库。
典型的表现:Map<String,Object>
随着互联网web2.0网站的兴起,传统的关系数据库在处理web2.0网站,特别是超大规模和高并发的SNS类型的web2.0纯动态网站已经显得力不从心,出现了很多难以克服的问题,而非关系型的数据库则由于其本身的特点得到了非常迅速的发展。
NoSQL数据库的产生就是为了解决大规模数据集合多重数据种类带来的挑战,特别是大数据应用难题。
1、单机mysql年代
2、Memchched(缓存)+ mysql + 垂直拆分(读写分离)
-
Memcached是一个自由开源的,高性能,分布式内存对象缓存系统
-
Memcached是一种基于内存的key-value存储,用来存储小块的任意数据(字符串、对象)。这些数据可以是数据库调用、API调用或者是页面渲染的结果。
-
Memcached简洁而强大。它的简洁设计便于快速开发,减轻开发难度,解决了大数据量缓存的很多问题。它的API兼容大部分流行的开发语言。
本质上,它是一个简洁的key-value存储系统。
一般的使用目的是,通过缓存数据库查询结果,减少数据库访问次数,以提高动态Web应用的速度、提高可扩展性。
3、分库分表 + 水平拆分 + MySQL集群
本 质 : 数 据 库 ( 读 , 写 ) \textcolor{red}{本质:数据库(读,写)} 本质:数据库(读,写)
-
MyISAM 存储引擎 【表锁】
- MySQL的默认数据库引擎(5.5版之前), 由早期的ISAM所改良
- 性能极佳,但
不
支
持
事
务
处
理
(
t
r
a
n
s
a
c
t
i
o
n
)
\textcolor{green}{不支持事务处理(transaction)}
不支持事务处理(transaction)。不过,在这几年的发展下,MySQL也导入了InnoDB(另一种数据库引擎),以强化
参
照
完
整
性
\textcolor{blue}{参照完整性}
参照完整性 与并发违规处理机制,后来就逐渐取代MyISAM
- 参照的完整性要求关系中不允许引用不存在的实体。
- 与 实体完整性是关系模型必须满足的完整性约束条件,目的是保证数据的一致性。参照完整性又称引用完整性。
-
InnoDB 【行锁】
-
是MySQL的数据库引擎之一
-
现为MySQL的默认存储引擎,为MySQL AB发布binary的标准之一
-
与传统的 ISAM与 MyISAM 相比,InnoDB的最大特色就是支持了ACID兼容的 事务 (Transaction)功能,类似于PostgreSQL
InnoDB采用双轨制授权,一个是 GPL 授权,另一个是 专有软件 授权
-
**MySQL AB:**是由MySQL创始人和主要开发人创办的公司
**PostgreSQL:**是一种特性非常齐全的自由软件的对象-关系型数据库管理系统 ORDBMS
**ISAM:**索引顺序访问方法
**GPL:**GNU通用公共许可证
专有软件: 指在使用、修改上有限制的软件
ACID,是指 数据库管理系统(DBMS)在写入或更新资料的过程中,为保证事务(transaction)是正确可靠的,所必须具备的四个特性:原子性(atomicity,或称不可分割性)、一致性、隔离性(isolation,又称独立性)、持久性(durability)
NoSQL特点
-
方便扩展(数据之间耦合性低)
-
大数据量高性能 (写8w/s,读11w/s,NoSQL的缓存记录级,是一种细粒度的缓存,性能高)
-
数据类型是多样型的(不需要实现设计数据库!随取随用)
-
传统 RDBMS 和 NoSQL
-
传统的RDBMS - 结构化组织 - SQL - 数据和关系都存在单独的表中 - 操作,数据定义语言 - 严格的一致性 - 基础的事务
-
nosql - 不仅仅是数据 - 没有固定的查询语言 - 键值对存储,列存储 ,文档存储,图形数据库(社交关系) - 最终一致性 - CAP定理 和 BASE (异地多活)
-
**CPA定律:**一致性,可用性和分区容错性 CAP 说明一个数据处理系统不能同时满足一致性,可用性和分区容错性这三个需求。CAP永远不可能同时满足,最多只能同时满足两个,提高其中任意两者的同时,必然要牺牲第三者;
-
**BASE :**解决 关系数据库 强一致性引起的问题而引起的可用性降低而提出的解决方案
- BA 是 Basically Available (基本可用)
基本可用是指分布式系统在出现不可预知故障的时候,允许损失部分可用性。
可以理解为在一个平台中,假设部分出现故障,这个时候系统应该在允许这些故障出现的情况下依旧保持部分功能(应该是核心功能)可以正常使用,另一部分功能出现些许可以允许的问题。响应时间上的损失:正常情况下的搜索引擎0.5秒即返回给用户结果,而基本可用看的搜索结果可能要1秒,2秒甚至3秒(超过3秒用户就接受不了了)
功能上的损失:在一个电商网站上,正常情况下,用户可以顺利完成每一笔订单,但是到了促销时间,可能为了应对并发,保护购物系统的稳定性,部分用户会被引导到一个降级页面 - S 是Soft state(软状态)
软状态,允许部分节点的数据存在一定的延时,这个延时不影响可用性。
举个例子,就是一个集群中,需要保持所有节点的数据时时刻刻都一致,这是一种强一致性的要求,这个很有可能会造成可用性的问题。
在保证了可用性的的前提下,允许各个节点在数据的一致性操作的时候有一定的延时。软状态是相对原子性来说的原子性(硬状态) -> 要求多个节点的数据副本都是一致的,这是一种"硬状态"
软状态(弱状态) -> 允许系统中的数据存在中间状态,并认为该状态不影响系统的整体可用性,即允许系统在多个不同节点的数据副本存在数据延迟 - E 是Eventually consistent(最终一致性)
最终一致性很好理解,软状态允许有一定延时,所以这个最终一致含义就是在一定的延时过去之后,所有节点的数据必须保持一致。
相比的假如在更新的同时,所有节点都必须查询到最新的数据,这样的话是一种 强一致性 。
在更新的同时,可以容忍节点查询到的数据不是最新的那么是一种 弱一致性。
最终一致性根据更新数据后各进程访问到数据的时间和方式的不同可以分类为:
- BA 是 Basically Available (基本可用)
-
3V + 3高
-
大数据时代的3V:主要是描述问题的
1.海量Voulme(用户多)
2.多样Varierty(每种数据都是不一样的)
3.实时Velocity(实时性,人是很难接受延迟的,要保证实时性)
-
大数据时代的3高:主要是对程序的要求
1.高并发
2.高可扩(扩展性必须要高)
3.高性能(保证用户体验和性能)
敏捷开发:把一个大项目分为多个相互联系,但也可独立运行的小项目,并分别完成,在此过程中软件一直处于可使用状态
极限编程:强调可适应性能性以及面临的困难
网页构成
# 1、商品的基本信息
名称、价格、商家信息 : 关系型数据库 mysql、Oracle
# 2、 商品描述、评论
文档型数据库 redis、MongoDB
# 3、图片
分布式文件系统 FastDFS(入门
- 淘宝 TFS
- Gooale GFS
- Hadoop HDFS
- 阿里云 oss
# 4、关键字(搜索
- 搜索引擎 solr 、elasticsearch
- ISerach:多隆
# 5、商品热门波段信息
- 内存数据库
- redis tair memache
# 6、商品的交易,外部支付接口
- 三方应用
NOSQL 四大分类
KV 键值对:
- 新浪:redis
- 美团 :redis + tair
- 阿里、百度:redis +memecache
文档型数据库(bson格式):
- MongoDB
- MongoDB是一个基于分布式文件存储 的数据库。由C++ 语言编写。旨在为WEB应用提供可扩展的高性能数据存储解决方案。
- MongoDB是一个介于 关系数据库 和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的
- 最大的特点是它支持的查询语言非常强大,其语法有点类似于面向对象的查询语言,几乎可以实现类似关系数据库单表查询的绝大部分功能,而且还支持对数据建立索引
- 高性能,易部署,易使用
- ConthDB
列存储数据库
- HBase
- 分布式文件系统
图-关系数据库
- 以点、边为基础存储单元,以高效存储、查询图数据为设计原理的数据管理系统
- 图数据库属于非关系型数据库(NoSQL)
- 图数据库主要应用为联机事务处理OLTP(on-line transaction processing),针对数据做事务(ACID)处理
- 社交网络,广告推荐
- Neo4j、infoGrid
关系模型 | 图-关系模型 |
---|---|
表(relation) | 对象类型(type) |
列或字段(attribute) | 属性(property)或链接(link) |
行(tuple) | 对象(object) |
比较
Redis入门
Redis数据类型大全
“5种基础”数据类型+“3种特殊”数据类型:
https://baijiahao.baidu.com/s?id=1709170155160213718&wfr=spider&for=pc
作用
-
内部存储、持久化,内存中是断电即失
- Redis 为我们提供了两种持久化方案,一种是基于快照,另外一种是基于 AOF 日志
- RDB 关系数据库(Relational Database,RDB)
- AOF日志存储的是Redis服务器指令序列,AOF只记录对内存进行修改的指令记录
-
效率高,可以用于高速缓存
-
发布订阅系统
-
计时器、计数器(浏览量)
特性
- 多样的数据类型
- 持久化
- 集群
- 事务
文档学习
菜鸟:https://www.runoob.com/redis/redis-tutorial.html
redis命令:https://redis.io/commands/
redis官方安装:https://redis.io/docs/getting-started/installation/
C语言中文网:http://c.biancheng.net/redis/windows-installer.html
视频学习
狂神:https://www.bilibili.com/video/BV1S54y1R7SB
Windows安装
下载
微软:https://github.com/microsoftarchive/redis/releases/tag/win-3.2.100
tporadowski:https://github.com/tporadowski/redis/releases
VMware:https://www.vmware.com/cn.html
xftp & xshell:https://www.xshell.com/zh/free-for-home-school/(学习版)
点击启动即可
redis默认端口号:6379
客户端连接服务端
检测是否连接成功
Redis 性能测试
-
redis-benchmark 压力测试工具
-
Redis 性能测试是通过同时执行多个命令实现的
Linux redis 性能测试的基本命令如下:
redis-benchmark [option] [option value]
注意:该命令是在 redis 的目录下执行的,而不是 redis 客户端的内部指令
基础知识
- redis默认建立了16个库
-
redis是一个字典结构的存储服务器,一个redis实例提供了多个用来存储数据的字典,客户端可以指定将数据存储在哪个字典中,所有可以将每个字典都理解成一个独立的数据库
-
redis默认支持16个数据库,可以通过调整redis的配置文件redis/redis.conf中的databases来修改这一个值,设置完毕后重启redis便完成配置
-
redis默认选择0库
-
# 切换库 select 1
-
- 清除当前库 flushdb
- 清除全部库 flushall
Redis是单线程的
Redis 绝大部分请求是纯粹的内存操作,CPU不是Redis的瓶颈,机器的内存和网络带宽才是
Redis 单线程为什么这么快
- 误区:高性能的服务器一定是多线程的
- 误区:多线程(CPU上下文会切换,耗时)一定比单线程效率高
- 运行速度:CPU > 内存 > 硬盘
- 核心:redis将所有的数据全部放在内存中,对于内存系统使用单线程来说是最佳方案
面向高速执行的数据库
Redis采用单线程模型,每条命令执行如果占用大量时间,会造成其他线程阻塞,对于Redis这种高性能服务是致命的,所以Redis是面向高速执行的数据库
数据类型
Redis不区分大小写命令
Redis-key
# 查看所有的key
keys *
# set key
set 自定义属性名 值
# 判断当前key是否存在
EXISTS 属性名
# 移除
move 属性名 使用的库
# 设置过期时间
EXPIRE 属性名 自定义时间/s
# 查看key的有效时间 -2结束
ttl 属性名
# 查看key类型
type 属性名
String
基础
# 追加字符串 (属性不存在,则新增
APPEND 属性名 值
# 获得字符串长度
STRLEN 属性名
# 自增
incr 属性 步长
# 自减
decr 属性 步长
# 截取字符串 (下标从0开始
GETRANGE 属性 开始下标 结束下标
# 替换
SETRANGE 属性 下标 替换值
# 为指定的 key 设置值及其过期时间
setex(set with expire)
setex 属性 过期时间/s 值
# 指定的 key 不存在时,为 key 设置指定的值
# 设置成功时返回 1 , 设置失败时返回 0
# 原子性操作
setnx(set id not exist)
setnx 属性 值
# 批量设置值
mset key value ...
# 批量获取值
mget key ...
进阶
# 对象 ,值为JSON
set 对象名{属性:值...}
# 打印当前值,获取后修改 (没有就会新增
# 更新操作
getset key value
List
-
栈、队列、阻塞队列
-
所有的list命名都是l开头
-
可以存在重复值
# 新增 (列表左
LPUSH key value
# 新增 (列表右
RPUSH key value
# 获取值
# 获取全部值
LRANGE key 0 -1
# 移除 (左
lpop key
# 移除 (右
rpop key
# 通过下标获取
lindex key value
# 查询长度
llen key
# 移除指定值 (精确匹配
# 移除顺序 从左到右
lrem key 移除行数 value
# 截取 (0开始
ltrim key 开始下标 结束下标
# 移除列表的最后一个元素,并将该元素添加到另一个列表
rpoplpush 原列表 新列表
# 通过索引来设置元素的值
# 当索引参数超出范围,或对一个空列表进行 LSET 时,返回一个错误
lset key 下标 value
# 在列表的元素前或者后插入元素。当指定元素不存在于列表中时,不执行任何操作
linsert key before|after 类表中的具体值 插入值
- redis -list实际上是一个链表,可以再node before,after,left,after插入值
- 如果key不存在,创建新的链表
- 如果key存在,新增链表
- 如果移除了所有的值,空链表,也代表不存在
- 在两边插入或者改动值,效率最高!中间元素效率相对会低一些
消息排队,消息队列
Set(集合)
- Set 是 String 类型的无序集合
- 集合成员是唯一的
# 添加
sadd set1 value
# 查看指定set的所有值
smembers set1
# 判断指定值是否存在于指定key中
sismember set1 查询的值
# 获取set的大小
scard set1
# 移除指定值
srem set1 value
# 随机取值
srandmember set1 抽选数量
# 随机删除key
spop set1
# 将指定元素从集合key1中移动到集合key2中
smove set1 set2 value
# 差集 返回第一个参数集合与其余集合的差集,不存在的集合key被视为空集
sdiff set1 set2
# 交集 返回所有指定集合中元素的交集,指定集合数量可以大于2。当任一指定集合为空或不存在时,返回空
sinter set1 set2 set3
# 并集 返回所有指定集合中元素的并集,不存在的集合key被视为空集
sunion set1 set2 set3
# 差集,交集,并集
`sinter`store
规则与sinter相同,只是并不直接返回交集,而是将该交集保存在集合destination中,如果集合destination存在,则重写集合destination
Hash
- string 类型的 field(字段) 和 value(值) 的映射表
- hash 特别适合用于存储对象
- Redis 中每个 hash 可以存储 232 - 1 键值对(40多亿)
- map集合,key-map
# 添加
hset hash1 field1 value1
# 取值
hget hash1 field1
# 同时添加多个值
hmset hash1 field1 val1 field val2
# 查询所有值 (key-value输出显示)
hgetall hash1
# 删除 一个或多个字段,不存在的字段将被忽略
hdel hash1 field1 ...
# 获取hash长度
hlen hash1
# 判断hash中某个字段是否存在 (1存在 0不存在
hexists hash1 fiedl1
# 获取所有的field
hkeys hash1
#获取所有的value
hvals hash1
- 存储经常变动的数据
- 用户信息
Zset 有序集合
- Redis 有序集合和集合一样也是 string 类型元素的集合,且不允许重复的成员
- redis每个元素都会关联一个 double 类型的分数
- redis 通过分数来为集合中的成员进行从小到大的排序
- 有序集合的成员唯一,但分数(score)可以重复
- 集合是通过哈希表实现的
# 添加
zadd set1 score1 value1
# 排序 通过评分排序(最小值到最大值)默认从小到大
zrangbyscore set1 -inf(最小值,负无穷) +inf(最大值,正无穷) [withscores(显示键值对)] [显示数量]
# 排序(反转 从大到小
zrevrange set1 0 -1
# 移除集合中的指定元素
zrange set1 value1
# 获取集合大小
zcard set1
# 获取指定区间的成员数量
zcount set1 min max
- set 排序 存储班级成绩表,工资表排序
- 普通消息:1,重要消息:2 带权重进行判断
- 排行榜应用
geospatial 地理位置
- 有效经度为 -180 到 180 度。
- 有效纬度为 -85.05112878 到 85.05112878 度。
- 从 Redis 版本 6.2.0 开始:添加了
CH
,NX
和XX
选项。- XX:只更新已经存在的元素。永远不要添加元素。
- NX:不要更新已经存在的元素。总是添加新元素。
- CH:将返回值从添加的新元素数修改为改变的元素总数(CH 是changed的缩写)。更改的元素是添加的新元素和已更新坐标的元素。因此,命令行中指定的与过去得分相同的元素不计算在内。注意:通常情况下, 的返回值
GEOADD
只计算添加的新元素的数量。 - 注意:XX和NX选项是互斥的
- GEO 类型的底层数据结构就是用 Sorted Set(zset) 来实现的
- 用 Sorted Set 来保存 名称 经纬度信息时,Sorted Set 的元素是名称 ID,元素的权重分数是经纬度信息
# 添加
# 两级无法直接添加
geoadd geoSet1 经度 纬度 名称 ...
# 查询两地的直线距离
geodist geoSet1 value1 value2 长度单位(m,km,ft等 默认为m)
# 返回一个或多个位置的经纬度信息,由于采用了geohash算法,返回的经纬度和添加时的数据可能会有细小误差
geopos geoSet1 value1 ...
# 以给定位置为中心,半径不超过给定半径的附近所有位置
georadius geoSet1 经度 纬度 范围 长度单位(m,km,ft,mi) [withcoord(经纬度)] [withdist(直线距离)] [withhash(返回被解释为字符串的Geohash值)] [count 查询数量]
# 指定已添加的某个位置作为中心
georaniusbymember geoSet1 value1 范围 长度单位(m,km,ft,mi) [withcoord(返回经纬度)] [withdist(返回直线距离)] [withhash(返回被解释为字符串的Geohash值)] [count 查询数量]
# geohash是一种地址编码方法,能够把二维的空间经纬度数据编码成一个11长度的字符串
# 缩短右边的字符,失去精度,但依旧指向同一地区
# 更多:百度文库:geohash算法:https://wenku.baidu.com/view/85cb1ece866a561252d380eb6294dd88d1d23d45.html
geohash geoSet1 value1 ...
hyperloglog 基数
- 基数(不重复元素),可以接受误差
- 用来做基数统计的算法
- 在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的、并且是很小的
- HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素
# 添加
pfadd key1 value1 ...
# 统计长度
pfcount key1
# 合并
pfmerge destkey(合并后存储的对象) key1 key2 ...
- 网页的 UV (网站独立访客)
bitmap 位图场景
-
对位操作
-
节省内存空间
-
Bitmaps属于redis字符串的一种,它的出现只是让我们可以对字符串按位操作
-
redis提供了单独的一套操作命令,相当于一个元素是bit的数组,每个位置只能存储0和1,其有从0开始的下标(偏移量)对应每个bit。
# 添加
setbit key1 offset1 value1(0,1)
# 查询
gitbit key1 offset1
# 统计
bitcount key1
- 签到、点赞、日活
事务
Redis单条命令保证原子性,但是事务不保证原子性!
Redis事务没有隔离级别(幻读,脏读,不可重复读)的概念
单个 Redis 命令的执行是原子性的,但 Redis 没有在事务上增加任何维持原子性的机制,所以 Redis 事务的执行并不是原子性的。
事务可以理解为一个打包的批量执行脚本,但批量指令并非原子化的操作,中间某条指令的失败不会导致前面已做指令的回滚,也不会造成后续的指令不做
本质:一组命令的集合 | 一次执行多个命令
特性:一次性、顺序性、排他性
- 批量操作在发送 EXEC 命令前被放入队列缓存。
- 收到 EXEC 命令后进入事务执行,事务中任意命令执行失败,其余的命令依然被执行。
- 在事务执行过程,其他客户端提交的命令请求不会插入到事务执行命令序列中
redis事务执行流程:
- 开始事务(multi)
- 命令入队(…)
- 执行事务(exec)
- 取消事务(discard)
案例
# 开始
multi
# 入队
set k1 v1
set k2 v3
get k2
set k3 v3
# 执行
exec
编译型异常 (java代码有问题,Redis命令错误),事务中所有命令后不会被执行
运行时异常 (事务队列中存在语法性错误),事务中其他命令正常执行,错误命令抛出异常
乐观锁
Redis
中WATCH
命令的实现是基于乐观锁Redis
的乐观锁不是通常实现乐观锁的一般方法:检测版本号,而是在执行完一个写命令后,会进行检查,检查是否是被WATCH
监视的键
乐观锁控制的事务一般包括三个阶段:
- 读取:当执行完
MULTI
命令后,客户端进入事务模式,客户端接下来输入的命令会读入到事务队列中,入队过程中出错会设置CLIENT_DIRTY_EXEC
标识。 - 校验:如果数据库有键被修改,那么会检测被修改的键是否是被
WATCH
命令监视的命令,如果是则会设置对应的标识(CLIENT_DIRTY_CAS),并且在命令执行前会检测这两个标识,如果检测到该标识,则会取消事务的执行。 - 写入:如果没有设置以上两种标识,那么会执行事务的命令,而
Redis
是单进程模型,因此可以避免执行事务命令时其他请求可能修改数据库键的可能。
Redis监视测试
set k1 100
set k2 0
watch k1 (加锁)
multi (开启事务)
decrby k1 20
incrby k2 20
exec (提交事务)
unwatch (解锁)
Jedis(了解)
Jedis 是 Redis 官方首选的 Java 客户端开发包
pom
<!-- 导入jedis包 -->
<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.6.0</version>
</dependency>
<!-- fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.76</version>
</dependency>
测试连接
// 创建jedis对象
Jedis jedis = new Jedis("127.0.0.1",6379);
System.out.println("测试连接返回 " + jedis.ping());
jedis中的命令就是之前的指令
事务
public void a(){
Jedis jedis = new Jedis("127.0.0.1",6379);
jedis.flushDB();
// 开始事务
Transaction multi = jedis.multi();
JSONObject jsonObject = new JSONObject();
jsonObject.put("key1","value1");
jsonObject.put("hello","jedis");
String val = jsonObject.toJSONString();
try {
multi.set("TesTran",val);
multi.set("TesTran2",val);
// 执行事务
multi.exec();
}catch (Exception e){
// 放弃事务
multi.discard();
e.printStackTrace();
}finally {
// 输出
System.out.println(jedis.get("TesTran"));
System.out.println(jedis.get("TesTran2"));
// 关闭连接
jedis.close();
}
}
springboot 整合
<!-- springboot 启动 redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
在springboot2.x之后,原来使用的jedis被替换为lettuce
区别
- jedis
- 采用的直连,多个线程操作时不安全的,如果想要避免不安全,就需要使用jedis pool连接池!BIO模式(同步并阻塞(传统阻塞型),服务器实现模式为一个连接一个线程)
- lettuce
- 可伸缩线程安全的Redis客户端。多个线程可以共享同一个RedisConnection。它利用优秀netty NIO框架来高效地管理多个连接
- 采用netty,实例可以在多个线程中共享,不存在线程不安全情况!可以减少线程数据,更像NIO模式(New I/O,是一种同步非阻塞的I/O模型,也是I/O多路复用的基础)
原生配置
源码: RedisAutoConfiguration类
// 可以自定义一个redisTemplate来替换
@Bean
@ConditionalOnMissingBean(
name = {"redisTemplate"}
)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
// 默认的 RedisTemplate 没有过多的设置,redis对象都需要序列化!
// 泛型都是object,使用的时候需要强制转换<String,Object>
RedisTemplate<Object, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
// 由于String是redis中最常使用的类型,所以单独提出来了一个bean
@Bean
@ConditionalOnMissingBean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
简单properties
# 简单配置redis
# 自动配置类 RedisAutoConfiguration
# 配置文件 RedisProperties
spring.redis.host=127.0.0.1
spring.redis.port=6379
RedisTemplate类
@Autowired
private RedisTemplate redisTemplate;
@Test
void contextLoads() {
// redisTemplate 操作不同的数据类型,api和指令一样
// opsForValue 操作字符串
// opsForList 操作list
// opsFor数据类型...
// 常用的方法都可以通过 redisTemplate 操作,比如事务,基本的CRUD
// 操作关于数据库的连接
// 获取连接对象
// RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
// connection.flushDb();
// connection.flushAll();
// value1如果为中文,会导致乱码! 因为没有序列化
// 默认序列化方式是序列化,我们可能会使用json来序列化 (自己定义配置类)
redisTemplate.opsForValue().set("key1","value1");
System.out.println(redisTemplate.opsForValue().get("key1"));
}
存值- 中文 - 乱码原因
默认序列化方式是序列化,我们可能会使用json来序列化
案例
pojo
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.stereotype.Component;
import java.io.Serializable;
/**
* 用户实体
* 序列化实现 implements Serializable
* @author pursueSong
* 2022-06-21
*/
@Component
@AllArgsConstructor
@NoArgsConstructor
@Data
public class User {
private String name;
private Integer age;
}
Test
@Test
public void UserSerializable() throws JsonProcessingException {
// 未序列化
User user = new User("中文乱码-报错原因-没有序列化", 1);
// 序列化
// String jsonUser = new ObjectMapper().writeValueAsString(user);
redisTemplate.opsForValue().set("user",user);
System.out.println(redisTemplate.opsForValue().get("user"));
}
解决:
- 实体类 : implements Serializable
- 转json :new ObjectMapper().writeValueAsString(user);
- 自定义 RedisTemplate类
自定义RedisTemplate
序列化实现方式
config
package com.example.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
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.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* redis-RedisTemplate配置类
*
* @author pursueSong
* 2022-06-21
*/
@Configuration
public class RedisConfig {
// 编写配置类
@Bean
@SuppressWarnings("all")
public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory factory){
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
// 序列化配置
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
// 转义
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
// 如果enableDefaultTyping过期(SpringBoot后续版本过期了),则使用下面这个代替
om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
// String 的序列化
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// key,hash采用String的序列化方式
template.setKeySerializer(stringRedisSerializer);
template.setHashKeySerializer(stringRedisSerializer);
// key,hash采用jackson的序列化方式
template.setValueSerializer(jackson2JsonRedisSerializer);
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
Redis工具类
package com.example.utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* redis工具类
*
* @author pursueSong
* 2022-06-21
*/
@Component
public class RedisUtils {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// =============================common============================
/**
* 指定缓存失效时间
*
* @param key 键
* @param time 时间(秒)
*/
public boolean expire(String key, long time) {
try {
if (time > 0) {
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根据key 获取过期时间
*
* @param key 键 不能为null
* @return 时间(秒) 返回0代表为永久有效
*/
public long getExpire(String key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
/**
* 判断key是否存在
*
* @param key 键
* @return true 存在 false不存在
*/
public boolean hasKey(String key) {
try {
return redisTemplate.hasKey(key);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除缓存
*
* @param key 可以传一个值 或多个
*/
@SuppressWarnings("unchecked")
public void del(String... key) {
if (key != null && key.length > 0) {
if (key.length == 1) {
redisTemplate.delete(key[0]);
} else {
redisTemplate.delete(CollectionUtils.arrayToList(key));
}
}
}
// ============================String=============================
/**
* 普通缓存获取
*
* @param key 键
* @return 值
*/
public Object get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}
/**
* 普通缓存放入
*
* @param key 键
* @param value 值
* @return true成功 false失败
*/
public boolean set(String key, Object value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 普通缓存放入并设置时间
*
* @param key 键
* @param value 值
* @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
* @return true成功 false 失败
*/
public boolean set(String key, Object value, long time) {
try {
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
} else {
set(key, value);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 递增
*
* @param key 键
* @param delta 要增加几(大于0)
*/
public long incr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递增因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, delta);
}
/**
* 递减
*
* @param key 键
* @param delta 要减少几(小于0)
*/
public long decr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递减因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, -delta);
}
// ================================Map=================================
/**
* HashGet
*
* @param key 键 不能为null
* @param item 项 不能为null
*/
public Object hget(String key, String item) {
return redisTemplate.opsForHash().get(key, item);
}
/**
* 获取hashKey对应的所有键值
*
* @param key 键
* @return 对应的多个键值
*/
public Map<Object, Object> hmget(String key) {
return redisTemplate.opsForHash().entries(key);
}
/**
* HashSet
*
* @param key 键
* @param map 对应多个键值
*/
public boolean hmset(String key, Map<String, Object> map) {
try {
redisTemplate.opsForHash().putAll(key, map);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* HashSet 并设置时间
*
* @param key 键
* @param map 对应多个键值
* @param time 时间(秒)
* @return true成功 false失败
*/
public boolean hmset(String key, Map<String, Object> map, long time) {
try {
redisTemplate.opsForHash().putAll(key, map);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一张hash表中放入数据,如果不存在将创建
*
* @param key 键
* @param item 项
* @param value 值
* @return true 成功 false失败
*/
public boolean hset(String key, String item, Object value) {
try {
redisTemplate.opsForHash().put(key, item, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一张hash表中放入数据,如果不存在将创建
*
* @param key 键
* @param item 项
* @param value 值
* @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
* @return true 成功 false失败
*/
public boolean hset(String key, String item, Object value, long time) {
try {
redisTemplate.opsForHash().put(key, item, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除hash表中的值
*
* @param key 键 不能为null
* @param item 项 可以使多个 不能为null
*/
public void hdel(String key, Object... item) {
redisTemplate.opsForHash().delete(key, item);
}
/**
* 判断hash表中是否有该项的值
*
* @param key 键 不能为null
* @param item 项 不能为null
* @return true 存在 false不存在
*/
public boolean hHasKey(String key, String item) {
return redisTemplate.opsForHash().hasKey(key, item);
}
/**
* hash递增 如果不存在,就会创建一个 并把新增后的值返回
*
* @param key 键
* @param item 项
* @param by 要增加几(大于0)
*/
public double hincr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, by);
}
/**
* hash递减
*
* @param key 键
* @param item 项
* @param by 要减少记(小于0)
*/
public double hdecr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, -by);
}
// ============================set=============================
/**
* 根据key获取Set中的所有值
*
* @param key 键
*/
public Set<Object> sGet(String key) {
try {
return redisTemplate.opsForSet().members(key);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 根据value从一个set中查询,是否存在
*
* @param key 键
* @param value 值
* @return true 存在 false不存在
*/
public boolean sHasKey(String key, Object value) {
try {
return redisTemplate.opsForSet().isMember(key, value);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将数据放入set缓存
*
* @param key 键
* @param values 值 可以是多个
* @return 成功个数
*/
public long sSet(String key, Object... values) {
try {
return redisTemplate.opsForSet().add(key, values);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 将set数据放入缓存
*
* @param key 键
* @param time 时间(秒)
* @param values 值 可以是多个
* @return 成功个数
*/
public long sSetAndTime(String key, long time, Object... values) {
try {
Long count = redisTemplate.opsForSet().add(key, values);
if (time > 0)
expire(key, time);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 获取set缓存的长度
*
* @param key 键
*/
public long sGetSetSize(String key) {
try {
return redisTemplate.opsForSet().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 移除值为value的
*
* @param key 键
* @param values 值 可以是多个
* @return 移除的个数
*/
public long setRemove(String key, Object... values) {
try {
Long count = redisTemplate.opsForSet().remove(key, values);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
// ===============================list=================================
/**
* 获取list缓存的内容
*
* @param key 键
* @param start 开始
* @param end 结束 0 到 -1代表所有值
*/
public List<Object> lGet(String key, long start, long end) {
try {
return redisTemplate.opsForList().range(key, start, end);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 获取list缓存的长度
*
* @param key 键
*/
public long lGetListSize(String key) {
try {
return redisTemplate.opsForList().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 通过索引 获取list中的值
*
* @param key 键
* @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
*/
public Object lGetIndex(String key, long index) {
try {
return redisTemplate.opsForList().index(key, index);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
*/
public boolean lSet(String key, Object value) {
try {
redisTemplate.opsForList().rightPush(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @param time 时间(秒)
*/
public boolean lSet(String key, Object value, long time) {
try {
redisTemplate.opsForList().rightPush(key, value);
if (time > 0)
expire(key, time);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @return
*/
public boolean lSet(String key, List<Object> value) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return
*/
public boolean lSet(String key, List<Object> value, long time) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
if (time > 0)
expire(key, time);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根据索引修改list中的某条数据
*
* @param key 键
* @param index 索引
* @param value 值
* @return
*/
public boolean lUpdateIndex(String key, long index, Object value) {
try {
redisTemplate.opsForList().set(key, index, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 移除N个值为value
*
* @param key 键
* @param count 移除多少个
* @param value 值
* @return 移除的个数
*/
public long lRemove(String key, long count, Object value) {
try {
Long remove = redisTemplate.opsForList().remove(key, count, value);
return remove;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
}
测试
@Autowired
private RedisUtils redisUtils;
@Test
public void testRedisTo(){
redisUtils.set("key1","测试工具类");
System.err.println(redisUtils.get("key1"));
}
Redis.conf详解
大小写不敏感
单位
包含 [多个配置文件]
网络配置
# 绑定的id
bind 127.0.0.1
# 保护模式 默认开启
protected-mode yes
# 默认端口
port 6379
通用配置 general
# 以守护进程的方式运行,默认是no
daemonize yes
# 如果以后台方式运行,我们需要指定一个pid文件
pidfile /var/run/redis_6379.pid
# 日志
# specify the server verbosity level.
# This can be one of:
# 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)
1oglevel notice
# 日志的文件位置名,为空的时候为单纯的输出
logfile ""
# 默认的数据库数量
databases 16
# 是否总是显示log
always-show-logo yes
快照
顾名思义可以理解为拍照一样,把整个内存数据映射到硬盘中,保存一份到硬盘,因此恢复数据起来比较快,把数据映射回去即可,不像AOF,一条条的执行操作命令
持久化,在规定的时间内,执行了多少次操作,则会持久化到文件 .rdb .aof
# 持久化规则
save 900 1 // 900秒内,有1条写入,则产生快照
save 300 10 // 300秒内有10次写入,则产生快照
save 60 10000 // 60秒内有10000次写入,则产生快照
# (这3个选项都屏蔽,则rdb禁用)
# 后台备份进程出错时,主进程是否停止写入 默认开启
stop-writes-on-bgsave-error yes
# 导出的rdb文件是否压缩,需要消耗一些cpu资源 默认开始
rdbcompression yes
# 导入rbd恢复时数据时,要不要检验rdb的完整性
# (默认开启)进行错误的校验
Rdbchecksum yes
# 导出来的rdb文件名
dbfilename dump.rdb
# rdb的放置路径
dir ./
security
# 密码默认为空
requirepass foobared
# bash设置密码
config set requirepass "12345"
限制 clients
# 最大连接数
maxclients 10000
# 最大内存数
maxmemory <bytes>
# 内存达到上限后的处理策略
maxmemory-policy noeviction
1、volatile-lru:只对设置了过期时间的key进行LRU(默认值)
2、allkeys-lru : 删除lru算法的key
3、volatile-random:随机删除即将过期key
4、allkeys-random:随机删除
5、volatile-ttl : 删除即将过期的
6、noeviction : 永不过期,返回错误
APPEND ONLY MODE 模式
aof基本配置
# 默认不开启aof模式
# 原因:默认使用rdb方式持久化,大部分情况下完全够用
appendonly no
# 持久化文件名
appendfilename "appendonly.aof"
# 同步
# appendfsync always # 每次修改都会sync,消耗性能
appendfsync everysec # 每秒执行一次sync,可能会丢失数据
# appendfsync no # 不同步,这时候操作系统同步数据,速度最快
Redis持久化
为了使Redis在重启之后仍能保证数据不丢失,需要将数据从内存中以某种形式同步到硬盘中,这一过程就是持久化
RDB
通过保存数据库中的键值对来记录数据库的状态
在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是行话讲的Snapshot快照,它恢复时是将快照文件直接读到内存里。
Redis会单独创建 ( fork )一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。
整个过程中,主进程是不进行任何IO操作的。这就确保了极高的性能。
如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。
RDB的缺点是最后一次持久化后的数据可能丢失。我们默认的就是RDB,一般情况下不需要修改这个配置!
生产环境下,需要备份!
rdb保存的文件是 dump.rdb
触发机制
- save的规则满足的情况下.会自动触发rdb规则
- 执行flushall命令.也会触发我们的rdb规则!
- 退出redis. 也会产生rdb文件!
备份就自动生成一个dump.rdb
恢复rdb文件
-
将rdb文件放置在redis启动目录即可,redis启动时会自动检查dump.rdb恢复其中的数据
-
查看需要存在的位置
127.0.0.1:6379> config get dir 1)"dir" 2)"/usr/local/bin" # rdb 文件存放位置
默认配置就够用了
优缺点
优点:
- 适合大规模的数据恢复
- 对数据的完整性要求不搞
- rdb文件是二进制存储的,节省磁盘空间
- rdb保存的是数据库状态快照,恢复速度快
缺点:
- 需要一定的时间间隔进程操作!由于rdb的save策略,如果redis服务器意外宕机,会导致最后一次保存快照后的修改数据丢失
- 虽然redis在fork时使用了写时拷贝技术,但是如果数据庞大时还是比较消耗性能
AOF
AOF 持久化记录服务器执行的所有写操作命令,并在服务器启动时,通过重新执行这些命令来还原数据集
以日志的形式来记录每个写操作, 将Redis执行过的所有指令记录下来(读操作不记录) , 只许追加文件但不可以改写文件, redis.
启动之初会读取该文件重新构建数据,换言之, redis重启的话就根据日志文件的内容将写指令从前到后执行一-次以完成数据的恢复工作
AOF保存的是appendonly.aof文件
append 默认关闭
手动开启后,重启Redis生效!
appendonly.aof 出错后,redis就会启动失败,需要通过redis-check-aof工具进行修复
redis-check-aof --fix appendonly.aof
优缺点
# 默认是不开启aof模式的,默认是使用rdb方式持久化的,在大部分所有的情况下,rdb完 全够用!
appendonly no
# 持久化的文件的名字
appendfilename "appendonly.aof"
# 每次修改都会sync,消耗性能
# appendfsync always
# 每秒执行一次sync,可能会丢失这1s的数据!
appendfsync everysec
# 不执行sync,这个时候操作系统自己同步数据,速度最快!
# appendfsync no
# rewrite 重写规则
# aof默认文件无限追加,文件会越来越大
# 如果文件大于64mb,fork一个新的进程来将文件重写
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
优点:
- 每次修改都同步,文件完整性好
- 每秒同步一次,可能会丢失一秒数据
- 从不同步,效率最高
缺点:
- 相对于数据文件来说,aof文件大小远远大于rdb,修复速度比rdb慢
- aof运行效率也比较慢,所有redis的默认配置是rdb
扩展:
- RDB 持久化方式能够在指定的时间间隔内对你的数据进行快照存储
- AOF 持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据, AOF命令以Redis协.
议追加保存每次写的操作到文件末尾, Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大。 - 只做缓存,如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化
- 同时开启两种持久化方式
- 在这种情况下,当redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整。
- RDB 的数据不实时,同时使用两者时服务器重启也只会找AOF文件,那要不要只使用AOF呢?作者建议不要,因为RDB更适合用于备份数据库( AOF在不断变化不好备份) , 快速重启,而且不会有AOF可能潜在的Bug ,留着作为一个万一的手段。
- 性能建议
- 因为RDB文件只用作后备用途,建议只在Slave上持久化RDB文件,而且只要15分钟备份一次就够了,只保留save 900 1这条规则。
- 如果Enable AOF ,好处是在最恶劣情况下也只会丢失不超过两秒数据,启动脚本较简单只load自己的AOF文件就可以了,代价
一是带来了持续的I0 ,二是AOF rewrite的最后将rewrite过程中产生的新数据写到新文件造成的阻塞几乎是不可避免的。只要硬盘许可,应该尽量减少AOF rewrite的频率, AOF重写的基础大小默认值64M太小了,可以设到5G以上,默认超过原大小100%大小重写可以改到适当的数值。 - 如果不Enable AOF , 仅靠Master-Slave Repllcation 实现高可用性也可以,能省掉一大笔IO ,也减少了rewrite时带来的系统
波动。代价是如果Master/Slave 同时宕机(断点),会丢失十几分钟的数据,启动脚本也要比较两个Master/Slave中的RDB文件,载入较新的那个,微博就是这种架构。
Redis发布订阅
Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息
原理
Redis是使用C实现的,通过分析Redis源码里面的pubsub.c文件,了解发布和订阅机制的底层实现,即此加深对Redis的理解。
Redis通过 PUBLISH、SUBSCRIBE 和 PSUBSCRIBE 等命令实现发布和订阅功能。
通过 SUBSCRIBE 命令订阅某频道后,redis-server 里维护一个字典,字典的键就是一个个channel,而字典的值则是一个链表,链表中保存了所有订阅这个channel的客户端。SUBSCRIBE命令的关键,就是将客户端添加到给定 channel 的订阅链表中。
通过 PUBLISH 命令向订阅者发送消息,redis-server 会使用给定的频道作为键,在它所维护的 channel 字典中查找记录了订阅这个频道的所有客户端的链表,遍历这个链表,将消息发布给所有订阅者。
Pub/Sub 从字面上理解就是发布(Publish)与订阅(Subscribe),在Redis中,你可以设定对某一个key值进行消息发布及消息订阅,当一个key值上进行了消息发布后,所有订阅它的客户端都会收到相应的消息。这一功能最明显的用法就是用作实时消息系统,比如普通的即时聊天,群聊等功能。
订阅/发布消息图
Redis 客户端可以订阅任意数量的频道。
下图展示了频道 channel1 , 以及订阅这个频道的三个客户端 —— client2 、 client5 和 client1 之间的关系:
当有新消息通过 PUBLISH 命令发送给频道 channel1 时, 这个消息就会被发送给订阅它的三个客户端:
Redis 发布订阅命令
cli1 订阅
127.0.0.1:6379> SUBSCRIBE channel1 ...
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "channel1"
3) (integer) 1
# 订阅者的客户端会显示如下消息
1) "message" 消息
2) "channel1" 频道
3) "message1" 消息内容
1) "channel1"
2) "runoobChat"
3) "redisLearn"
cli2 发布
127.0.0.1:6379> PUBLISH channel1 "message1"
(integer) 1
127.0.0.1:6379> PUBLISH channel1 "redisLearn"
(integer) 1
序号 | 命令及描述 |
---|---|
1 | [PSUBSCRIBE pattern pattern …] 订阅一个或多个符合给定模式的频道。 |
2 | [PUBSUB subcommand argument [argument …]] 查看订阅与发布系统状态。 |
3 | PUBLISH channel message 将信息发送到指定的频道。 |
4 | [PUNSUBSCRIBE pattern [pattern …]] 退订所有给定模式的频道。 |
5 | [SUBSCRIBE channel channel …] 订阅给定的一个或多个频道的信息。 |
6 | [UNSUBSCRIBE channel [channel …]] 指退订给定的频道。 |
使用场景:
- 实时消息系统
- 实时聊天(频道当做聊天室)
- 订阅
复杂的场景就需要使用 消息中间件 MQ
进阶知识:
Redis主从复制
Redis缓存穿透和雪崩
Redis使用规范
知乎:使用Redis,你必须知道的21个注意要点:https://zhuanlan.zhihu.com/p/359284601
Jedis常用API
Jedis常用API整理-详细 - 百度文库:https://wenku.baidu.com/view/87b4638cd2f34693daef5ef7ba0d4a7302766cd0.html