目录
课程学习笔记总结:【狂神说Java】Redis最新超详细版教程通俗易懂_哔哩哔哩_bilibili
Redis
1.Nosql 概述
发展
大数据 2006 Hadoop
1 单机MySQL的年代
问题:
-
如果数据量太大,一个机器放不下了
-
数据索引,一个机器内存也放不了
-
访问量(读写混合),一个服务器承受不了
解决:
Memcached(缓存)+MySQL+垂直拆分
网站80%的情况在读取,每次都要去数据库的话就十分的麻烦,所以我们希望减轻数据的压力,我们可以使用缓存来保证效率
2 分库分表+水平分表+MySQL集群
集群
3 最近的年代
技术爆炸:
MySQL等关系型数据库就不够用了,数据量大
图形 Json BSON 各种数据关系
如果用MySQL来存一些比较大的文件,博客,图片,数据库表很大,效率就低了,如果有一种数据库来专门处理这种数据,MYSQL压力就变得十分小
为什么要用nosql
用户的个人信息,社交网络,地理位置,用户自己产生的数据,用户日志等等爆发式增长
这时候我们就需要使用NoSQL数据库的,NoSQL可以很好的处理以上的情况
1.1 什么是NoSQL
NoSQL
NoSQL=Not Only SQL
关系型数据库:表格 行 列
泛指非关系型数据库二点,随着web2.0互联网的诞生,传统的关系型数据库很难对付web2.0时代,尤其是超大规模的高并发的社区, NoSQL在当今大数据环境下发展的十分迅速,Redis是发展最快的
用户的个人信息,社交网络,地理位置 这些数据类型的存储不需要一个固定的格式,不需要多余的操作可以横向发展的,使用键值对来控制
NoSQL 特点
-
方便扩展,数据之间没有关系
-
大数据量高性能(Redis一秒可以写8万次,读11万,NoSQL的缓存记录级的,是一种细粒度的缓存)
-
数据类型是多类型的(不需要事先设计数据库,随取随用)
-
传统的RDBMS和NoSQL
<span style="background-color:#f8f8f8"><span style="color:#333333">传统的RDBMS
- 结构化组织
- SQL
- 数据和关系都存在单独的表中
- 操作,数据定义语言
- 严格的一致性
- 基础的事务</span></span>
<span style="background-color:#f8f8f8"><span style="color:#333333">NoSQL
- 不仅仅是数据
- 没有固定的查询语言
- 键值对存储。列存储,文档存储,图形数据可
- 最终一致性
- CAP定理
- 高性能,高可用,高可扩</span></span>
了解:3v+3高
3v
-
海量Volume
-
多样Variety
-
实时Volocity
3高
高并发
高可扩
高性能
1.2 NoSQL 四大分类
kv键值对
-
新浪:Redis
-
美团:Redis+Tair
-
阿里、百度:Redis+memecache
文档型数据库(bson格式和json一样)
-
MongoDB
-
基于分布式文件存储的数据库,c++编写,主要用来处理大量的文档
-
介于关系型数据库和非关系型数据库中间的产品
-
-
ConthDB
列存储数据库
-
Hbase
-
分布式文件系统
图形关系数据库
-
他不是用来存图形,放的式关系
-
Neo4j InfoGird
2Redis入门
1.1概述
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(Remote Dictionary Server ),即远程字典服务,是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。
免费开源
redis是一个key-value存储系统。和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash(哈希类型)。这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,redis支持各种不同方式的排序。与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。
Redis 能干什么
-
内存存储、持久化,持久化(rdba,aof)
-
效率高,可用于高速缓存
-
发布订阅系统
-
地图信息分析
-
计时器、计数器,浏览量
-
。。。
特性
-
开源
-
支持多语言
-
多样的数据类型
-
集群
-
持久化
-
事务
-
。。。。
学习中需要用到的东西
-
狂神公众号:狂神说
-
官网:Redis
-
中文网:redis中文官方网站
-
下载地址 通过官网下载即可,注意:Windos在github上下载(停更很久了,不建议使用)
1.2 安装
1.2.1在Window下安装
-
下载 的 Redis-x64-3.2.100 版本
-
下载完毕得到压缩包
-
开启redis,双击redis-server.exe 即可
-
-
使用客户端连接服务器
启动redis-cli.exe
1.2.2在Linux下安装
-
下载安装包
2 解压Redis安装包
<span style="background-color:#f8f8f8"><span style="color:#333333">#移动到opt目录下
mv redis-6.0.6.tar.gz /opt
cd /opt
ls
tar -zxvf redis-6.0.6.tar.gz
</span></span>
3 进入解压后的文件可以看到redis的配置文件
我把redis放在/usr/local下,可以将redis-6.0.6改名为redis
<span style="background-color:#f8f8f8"><span style="color:#333333">mv /opt/redis-6.0.6 /usr/local/redis-6.0.6
</span></span>
4 基本环境安装
<span style="background-color:#f8f8f8"><span style="color:#333333">yum install gcc-c++
make
make install</span></span>
如果有遇到此类报错问题
gcc版本问题,新版本的。redis6.0以上
查看gcc版本
gcc -v
解决办法:
<span style="background-color:#f8f8f8"><span style="color:#333333">yum -y install centos-release-scl
yum -y install devtoolset-9-gcc devtoolset-9-gcc-c++ devtoolset-9-binutils
scl enable devtoolset-9 bash
</span></span>
安装完成
5 redis的默认安装路径 /usr
cd /usr/local/bin
6.将redis的配置文件复制到我们当前的目录下
<span style="background-color:#f8f8f8"><span style="color:#333333">mkdir redis-config
cp /usr/local/redis-6.0.6/redis.conf redis-config</span></span>
7 redis 默认不是后台启动的,需要修改配置文件
<span style="background-color:#f8f8f8"><span style="color:#333333">vim redis.conf</span></span>
8 启动redis 服务
<span style="background-color:#f8f8f8"><span style="color:#333333">在 /usr/local/bin 下 通过指定的配置文件启动服务
redis-server redis-config</span></span>
9 客户端连接
<span style="background-color:#f8f8f8"><span style="color:#333333">redis-cli -p 6379</span></span>
10 查看redis 的进程是否开启
<span style="background-color:#f8f8f8"><span style="color:#333333">ps -ef|grep redis
</span></span>
11 如何关闭Redis服务呢?
<span style="background-color:#f8f8f8"><span style="color:#333333">shutdown
exit</span></span>
12 再查看进程
1.3 性能测试
redis-benchmark 是一个压力测试工具
来自菜鸟教程
redis 性能测试工具可选参数如下所示:
序号 | 选项 | 描述 | 默认值 |
---|---|---|---|
1 | -h | 指定服务器主机名 | 127.0.0.1 |
2 | -p | 指定服务器端口 | 6379 |
3 | -s | 指定服务器 socket | |
4 | -c | 指定并发连接数 | 50 |
5 | -n | 指定请求数 | 10000 |
6 | -d | 以字节的形式指定 SET/GET 值的数据大小 | 2 |
7 | -k | 1=keep alive 0=reconnect | 1 |
8 | -r | SET/GET/INCR 使用随机 key, SADD 使用随机值 | |
9 | -P | 通过管道传输 <numreq> 请求 | 1 |
10 | -q | 强制退出 redis。仅显示 query/sec 值 | |
11 | --csv | 以 CSV 格式输出 | |
12 | *-l*(L 的小写字母) | 生成循环,永久执行测试 | |
13 | -t | 仅运行以逗号分隔的测试命令列表。 | |
14 | *-I*(i 的大写字母) | Idle 模式。仅打开 N 个 idle 连接并等待。 |
测试
<span style="background-color:#f8f8f8"><span style="color:#333333"># 测试:100个并发连接 100000请求
redis-benchmark -h localhost -p 6379 -c 100 -n 100000</span></span>
1.4 基础的知识
redis 默认有16个数据库
默认使用第0个,可以使用select 切换
<span style="background-color:#f8f8f8"><span style="color:#333333">127.0.0.1:6379> select 3 #切换数据库
OK
127.0.0.1:6379[3]> dbsize #当前数据库大小
(integer) 0
127.0.0.1:6379[3]>
keys * # 查看数据库所有的key
flushdb #清空当前数据库
flushall # 清空全部数据库数据</span></span>
Redsi 是单线程的
Redis 是基于内存操作的,根据机器的内存和网络带宽
Redis是C语言写的,官方提供的数据为100000+的QPS,完全不比同样使用的key-valueEDMemechache差
Redis为什么单线程还这么快?
1、误区一:高性能的服务器一定多线程?
2 、误区二:多线程(CPU上下文会切换)一定比单线程效率高
先去CPU>内存>硬盘的速度要有所了解
核心:redis是将所有的数据全部放在内存中,所以说使用单线程去操作效率最高
3 五大数据类型
3.1 Redis-Key
<span style="background-color:#f8f8f8"><span style="color:#333333">127.0.0.1:6379> keys *
1) "name"
2) "counter:{tag}:__rand_int__"
3) "key:{tag}:__rand_int__"
127.0.0.1:6379> set age 1 # 设置值
OK
127.0.0.1:6379> keys *
1) "age"
2) "name"
3) "counter:{tag}:__rand_int__"
4) "key:{tag}:__rand_int__"
127.0.0.1:6379> exists name #判断name 是否存在
(integer) 1
127.0.0.1:6379> move name
(error) ERR wrong number of arguments for 'move' command
127.0.0.1:6379> move age 1 移除age
(integer) 1
127.0.0.1:6379> keys *
1) "name"
2) "counter:{tag}:__rand_int__"
3) "key:{tag}:__rand_int__"
127.0.0.1:6379> get name 获取name的值
"yy"
127.0.0.1:6379> expire name 10 给name限制时限 10s后过期
(integer) 1
127.0.0.1:6379> ttl
(error) ERR wrong number of arguments for 'ttl' command
127.0.0.1:6379> ttl name
(integer) 2
127.0.0.1:6379> ttl name
(integer) -2
127.0.0.1:6379> get name
(nil)
none
127.0.0.1:6379> set name yy
OK
127.0.0.1:6379> type name # 查看类型
string
127.0.0.1:6379> set age 1
OK
127.0.0.1:6379> type age
string
127.0.0.1:6379>
</span></span>
3.2 String(字符串)
基本命令
<span style="background-color:#f8f8f8"><span style="color:#333333">127.0.0.1:6379> set key1 v1 #设置值
OK
127.0.0.1:6379> get key1 #获取值
"v1"
127.0.0.1:6379> exists key1 #判断某个key是否存在
(integer) 1
127.0.0.1:6379> append key1 "hello" #追加字符串,如果当前key不存在,就相当于set key
(integer) 7
127.0.0.1:6379> get key1
"v1hello"
127.0.0.1:6379> strlen key1 #获取字符串长度
(integer) 7
127.0.0.1:6379> append key1 ",yewei"
(integer) 13
127.0.0.1:6379> strlen key1
(integer) 13
127.0.0.1:6379>
###############################################################################
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> set views 0
OK
127.0.0.1:6379> get views
"0"
127.0.0.1:6379> incr views #自增1 i++
(integer) 1
127.0.0.1:6379> get views
"1"
127.0.0.1:6379> decr views #自减1
(integer) 0
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) 9
127.0.0.1:6379> incrby views 10
(integer) 19
127.0.0.1:6379> decrby views 5 #可以设置步长,指定增量
(integer) 14
#################################################################################
#字符串范围 range
OK
127.0.0.1:6379> clear
127.0.0.1:6379> keys *
(empty array)
127.0.0.1:6379> set key1 "hello,kuangshen"
OK
127.0.0.1:6379> get key1
"hello,kuangshen"
127.0.0.1:6379> getrange key1 0 1 #截取0-1 起始位置,结束位置
"he"
127.0.0.1:6379> getrange key1 0 -1 # 获取全部字符串 和get key 一样
"hello,kuangshen"
#替换
127.0.0.1:6379> set key2 abcdefg
OK
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 if not exist) 不存在设置(在分布式锁中会常常使用)
127.0.0.1:6379> setex key3 30 "hello" #设置key3的值为hello,30秒后过期
OK
127.0.0.1:6379> ttl key3
(integer) 21
127.0.0.1:6379> get key3
"hello"
127.0.0.1:6379> keys *
1) "key1"
2) "key2"
127.0.0.1:6379> setnx mykey "redis" #如果mykey不存在,创建mykey
(integer) 1
127.0.0.1:6379> keys *
1) "mykey"
2) "key1"
3) "key2"
127.0.0.1:6379> ttl key3
(integer) -2
127.0.0.1:6379> setnx mykey "MongoDB" #如果myeky不存在,创建失败
(integer) 0
127.0.0.1:6379> get mykey
"redis"
################################################################################
mset #同时设置多个值
mget #同时获取多个值
127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3 #同时设置多个值
OK
127.0.0.1:6379> keys *
1) "k2"
2) "k1"
3) "k3"
127.0.0.1:6379> mget k1 k2 k3 #同时获取多个值
1) "v1"
2) "v2"
3) "v3"
127.0.0.1:6379> msetnx k1 v1 k4 v4 #msetnx 是一个原子性的操作,要么一起成功,要么一起失败
(integer) 0
127.0.0.1:6379> get k4
(nil)
#对象
set user:1{name:zbangsanmage:3} #设置user:1 对象值为json字符来保存对象
127.0.0.1:6379> mset user:1:anme zhangsan user:1:age 2
OK
127.0.0.1:6379> mget user:1:anme user:1:age
1) "zhangsan"
2) "2"
#####################################################################################getset
127.0.0.1:6379> getset db redis #如果不存在的值,则返回nil
(nil)
127.0.0.1:6379> get db
"redis"
127.0.0.1:6379> getset db mongodb #如果存在值,获取原来二点值,并设置新的值
"redis"
127.0.0.1:6379> get db
"mongodb"
</span></span>
使用场景,value除了字符串还可以是数字
-
计数器
-
统计数量
-
对象缓存
3.3 list(列表)
在redis里面,我们可以把list完成,栈,队列 ,阻塞队列
<span style="background-color:#f8f8f8"><span style="color:#333333">##################################################################################
127.0.0.1:6379> lpush list one #将一个值或多个值插入列表的头部(左)
(integer) 1
127.0.0.1:6379> lpush list two
(integer) 2
127.0.0.1:6379> lpush list three
(integer) 3
127.0.0.1:6379> lrange list 0 -1 #获取list中的值
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> lrange list 0 1
1) "three"
2) "two"
127.0.0.1:6379> Rpush list righr
(integer) 4
127.0.0.1:6379> lrange list 0 -1 #将一个值或多个值插入列表的尾部(右)
1) "three"
2) "two"
3) "one"
4) "righr"
##############################################################################
Lpop
Rpop
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "two"
3) "one"
4) "righr"
127.0.0.1:6379> lpop list #移除列表的第一个元素
"three"
127.0.0.1:6379> Rpop list
"righr"
127.0.0.1:6379> lrange list 0 -1 #移除列表最后一个元素
1) "two"
2) "one"
##############################################################################
lindex
127.0.0.1:6379> lrange list 0 -1
1) "two"
2) "one"
127.0.0.1:6379> lindex list 1
"one"
127.0.0.1:6379> lindex list 0
"two"
##############################################################################
llen
127.0.0.1:6379> lpush list one
(integer) 1
127.0.0.1:6379> lpush list two
(integer) 2
127.0.0.1:6379> lpush list three
(integer) 3
127.0.0.1:6379> llen list
(integer) 3
##############################################################################
移除指定的值
Lrem
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "three"
3) "two"
4) "one"
127.0.0.1:6379> lrem list 1 one #移除list集合中指定个数的value ,精确匹配
(integer) 1
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "three"
3) "two"
127.0.0.1:6379> lrem list 2 three
(integer) 2
127.0.0.1:6379> lrange list 0 -1
1) "two"
##############################################################################
将list截断
127.0.0.1:6379> rpush mylist "hello"
(integer) 1
127.0.0.1:6379> rpush mylist "hello1"
(integer) 2
127.0.0.1:6379> rpush mylist "hello2"
(integer) 3
127.0.0.1:6379> rpush mylist "hello3"
(integer) 4
127.0.0.1:6379> ltrim mylist 1 2 #通过下标截取指定的长度,这个list已经被改变了
OK
127.0.0.1:6379> lrange mylist 0 -1
1) "hello1"
2) "hello2"
##############################################################################
rpoplpush #移除列表的最后一个元素,将他移动到新的列表中
127.0.0.1:6379> rpush mylist "hello"
(integer) 1
127.0.0.1:6379> rpush mylist "hello1"
(integer) 2
127.0.0.1:6379> rpush mylist "hello2"
(integer) 3
127.0.0.1:6379> rpoplpush mylist myotherlist
"hello2"
127.0.0.1:6379> lrange mylist 0 -1
1) "hello"
2) "hello1"
127.0.0.1:6379> lrange myotherlist 0 -1
1) "hello2"
##############################################################################
lset更改值 将列表中指定下标的值替换为另一个值
127.0.0.1:6379> exists list #判断这个表是否存在
(integer) 0
127.0.0.1:6379> lset list 0 item #如果不存在更新会报错
(error) ERR no such key
127.0.0.1:6379> lpush list value1
(integer) 1
127.0.0.1:6379> lrange list 0 -1
1) "value1"
127.0.0.1:6379> lset list 0 item 如果存在会更新当前下标的值
OK
127.0.0.1:6379> lrange list 0 -1
1) "item"
##############################################################################
linsert 将某个具体的value插入到列表中某个元素的前面或者后面
127.0.0.1:6379> rpush mylist "hello"
(integer) 1
127.0.0.1:6379> rpush mylist "world"
(integer) 2
127.0.0.1:6379> linsert mylist before "world" "other"
(integer) 3
127.0.0.1:6379> lrange mylist 0 -1
1) "hello"
2) "other"
3) "world"
127.0.0.1:6379> linsert mylist after "world" "new“
Invalid argument(s)
127.0.0.1:6379> linsert mylist after world new
(integer) 4
127.0.0.1:6379> lrange mylist 0 -1
1) "hello"
2) "other"
3) "world"
4) "new"
</span></span>
小结
-
它实际上是一个链表,before Mode after left right 都可以插入值
-
如果key不存在,创建新的链表
-
如果key存在,新增内容
-
如果移除了所有值,空链表,也代表不存在
-
在两边插入或者改动值,效率最高,中间元素相对来说效率低一点
3.4 set(集合)
set中的值是不能重复的
无序不重复 集合
<span style="background-color:#f8f8f8"><span style="color:#333333">##############################################################################
127.0.0.1:6379> sadd myset "redis" #set集合中添加元素
(integer) 1
127.0.0.1:6379> sadd myset "world"
(integer) 1
127.0.0.1:6379> smembers myset #查看指定set的所有值
1) "world"
2) "redis"
3) "hello"
127.0.0.1:6379> sismember myset hello #判断某个值是否在集合中
(integer) 1
127.0.0.1:6379> sismember myset hello1
(integer) 0
##############################################################################
127.0.0.1:6379> scard myset 获取est集合中的内容元素个数
(integer) 3
##############################################################################
127.0.0.1:6379> srem myset hello1 移除集合中的某个元素
(integer) 1
##############################################################################
随机抽出元素 srandmember
127.0.0.1:6379> smembers myset
1) "world"
2) "redis"
3) "hello"
127.0.0.1:6379> srandmember myset
"redis"
127.0.0.1:6379> srandmember myset
"world"
127.0.0.1:6379> srandmember myset
"world"
127.0.0.1:6379> srandmember myset
"world"
127.0.0.1:6379> srandmember myset
"hello"
##############################################################################
删除指定key 随机key
127.0.0.1:6379> smembers myset
1) "world"
2) "redis"
3) "hello"
127.0.0.1:6379> spop myset # 随机移除元素
"world"
127.0.0.1:6379> spop myset
"hello"
127.0.0.1:6379> smembers myset
1) "redis"
##############################################################################
将一个指定的值移动到另一个集合
127.0.0.1:6379> sadd myset "hello"
(integer) 1
127.0.0.1:6379> sadd myset "redis"
(integer) 1
127.0.0.1:6379> sadd myset "world"
(integer) 1
127.0.0.1:6379> sadd myset2 "set2"
(integer) 1
127.0.0.1:6379> smove myset myset2 "redis" #将一个指定的值移动到另一个集合
(integer) 1
127.0.0.1:6379> smembers myset
1) "world"
2) "hello"
127.0.0.1:6379> smembers myset2
1) "redis"
2) "set2"
##############################################################################
共同关注 并集
127.0.0.1:6379> sadd key1 a
(integer) 1
127.0.0.1:6379> sadd key1 b
(integer) 1
127.0.0.1:6379> sadd key1 c
(integer) 1
127.0.0.1:6379> sadd key2 c
(integer) 1
127.0.0.1:6379> sadd key2 d
(integer) 1
127.0.0.1:6379> sadd key2 e
(integer) 1
127.0.0.1:6379> sdiff key1 key2 #差集
1) "a"
2) "b"
127.0.0.1:6379> sinter key1 key2 #交集
1) "c"
127.0.0.1:6379> sunion key1 key2 #并集
1) "c"
2) "a"
3) "b"
4) "e"
5) "d"
</span></span>
3.5 hash(哈希)
Map集合,key-map 集合 本质和String没有太大区别
<span style="background-color:#f8f8f8"><span style="color:#333333">127.0.0.1:6379> hset myhash field1 kuangshen #set key-value
(integer) 1
127.0.0.1:6379> hget myhash field1 #获取一个字段值
"kuangshen"
127.0.0.1:6379> hmset myhash field1 hello field2 world #set 多个key-value
OK
127.0.0.1:6379> hmget myhash field1 field2 #获取多个值
1) "hello"
2) "world"
127.0.0.1:6379> hgetall myhash #获取所有
1) "field1"
2) "hello"
3) "field2"
4) "world"
127.0.0.1:6379> hdel myhash field1 #删除
(integer) 1
127.0.0.1:6379> hgetall myhash
1) "field2"
2) "world"
##############################################################################
127.0.0.1:6379> hgetall myhash
1) "field2"
2) "world"
3) "field3"
4) "hello111"
5) "field4"
6) "world2222"
127.0.0.1:6379> hlen myhash #获取hash表的字段数量
(integer) 3
##############################################################################
127.0.0.1:6379> hexists myhash field2 #判断 hash表中指定字段是否存在
(integer) 1
127.0.0.1:6379> hexists myhash field1
(integer) 0
##############################################################################
127.0.0.1:6379> hkeys myhash #只获得所有field
1) "field2"
2) "field3"
3) "field4"
127.0.0.1:6379> hvals mhash #只获得所有value
(empty array)
127.0.0.1:6379> hvals myhash
1) "world"
2) "hello111"
3) "world2222"
##############################################################################
incr decr
127.0.0.1:6379> hset myhash field5 5
(integer) 1
127.0.0.1:6379> hincrby myhash field5 1 #指定增量
(integer) 6
127.0.0.1:6379> hkeys myhash
1) "field2"
2) "field3"
3) "field4"
4) "field5"
127.0.0.1:6379> hsetnx myhash field4 uuuu #如果key 存在 则不能设置
(integer) 0
127.0.0.1:6379> hsetnx myhash field6 uuuu #如果key 不存在 则可以设置
(integer) 1
</span></span>
应用
hash 变更数据 name age ,尤其是信息之类的,经常变动二点信息,hash 更适合于对象的存储,string更加适合字符串的存储
3.6 Zset/Sorted Set(有序集合)
在set 的基础上 增加了一个值
<span style="background-color:#f8f8f8"><span style="color:#333333">127.0.0.1:6379> zadd myset 1 one #添加一个值
(integer) 1
(0.73s)
127.0.0.1:6379> zadd myset 2 two
(integer) 1
127.0.0.1:6379> zadd myset 3 three
(integer) 1
127.0.0.1:6379> zrange myset 0 -1
1) "one"
2) "two"
3) "three"
#################################################################################
排序
127.0.0.1:6379> zadd salary 2500 xiaohong
(integer) 1
127.0.0.1:6379> zadd salary 5000 zhangsan
(integer) 1
127.0.0.1:6379> zadd salary 500 kuangshen
(integer) 1
127.0.0.1:6379> zrangebyscore salary -inf +inf #显示全部用户 从小到大排序
1) "kuangshen"
2) "xiaohong"
3) "zhangsan"
127.0.0.1:6379> zrevrange salary 0 -1 ##显示全部用户 从大到小排序
1) "zhangsan"
2) "kuangshen"
127.0.0.1:6379> zrangebyscore salary -inf +inf withscores #显示全部用户 从小到大排序 附带成绩
1) "kuangshen"
2) "500"
3) "xiaohong"
4) "2500"
5) "zhangsan"
6) "5000"
127.0.0.1:6379> zrangebyscore salary -inf 2500 withscores #显示工资<=2500的用户
1) "kuangshen"
2) "500"
3) "xiaohong"
4) "2500"
#################################################################################
移除元素
127.0.0.1:6379> zrange salary 0 -1
1) "kuangshen"
2) "xiaohong"
3) "zhangsan"
127.0.0.1:6379> zrem salary xiaohong #移除指定数量
(integer) 1
127.0.0.1:6379> zrange salary 0 -1
1) "kuangshen"
2) "zhangsan"
#################################################################################
查询大小
127.0.0.1:6379> zrange salary 0 -1 #获取有序集合中的个数
1) "kuangshen"
2) "zhangsan"
127.0.0.1:6379
#################################################################################
127.0.0.1:6379> zadd zset1 1 hello 2 oop 3 tto
(integer) 3
127.0.0.1:6379> zcount zset1 1 3 #获取指定区间集合的数量
(integer) 3
127.0.0.1:6379> zcount zset1 1 2
(integer) 2
</span></span>
还有很多命令可以去官方文档查询
应用
-
排序 存储班级成绩表 工资表排序
-
加权重 普通消息 1 重要消息 2
-
排行榜应用实现 取Top N
4三种特殊数据类型
4.1 Geospatial 地理位置详解
将指定的地理空间位置(纬度、经度、名称)添加到指定的key
中。这些数据将会存储到sorted set
这样的目的是为了方便使用GEORADIUS或者GEORADIUSBYMEMBER命令对数据进行半径查询等操作。
该命令以采用标准格式的参数x,y,所以经度必须在纬度之前。这些坐标的限制是可以被编入索引的,区域面积可以很接近极点但是不能索引。具体的限制,由EPSG:900913 / EPSG:3785 / OSGEO:41001 规定如下:
-
有效的经度从-180度到180度。
-
有效的纬度从-85.05112878度到85.05112878度。
当坐标位置超出上述指定范围时,该命令将会返回一个错误。
Redis的Geo在3.2版本就推出了,这个功能可以推算出地理位置的信息,两地之间的距离,方圆几里的人。
可以查询一些测试数据 城市经纬度查询-国内城市经度纬度在线查询工具
4.1.1 geoad
<span style="background-color:#f8f8f8"><span style="color:#333333">$geoadd 添加地理位置
#规则:两级无法直接添加,我们一般会下载城市数据,直接通过java程序一次性导入
#参数 key 经度 纬度 名称
#有效的经度从-180度到180度。
#有效的纬度从-85.05112878度到85.05112878度。
127.0.0.1:6379> geoadd china:city 116.40 39.90 beijing
(integer) 1
127.0.0.1:6379> geoadd china:city 121.47 31.23 shanghai
(integer) 1
127.0.0.1:6379> geoadd china:city 106.50 29.53 chongqing 114.05 22.53 shengzhen
(integer) 2
127.0.0.1:6379> geoadd china:city 120.16 30.24 hangzhou
(integer) 1
127.0.0.1:6379> geoadd china:city 108.96 34.26 xian
(integer) 1
</span></span>
4.1.2 geopos
从key
里返回所有给定位置元素的位置(经度和纬度)。
<span style="background-color:#f8f8f8"><span style="color:#333333">127.0.0.1:6379> geopos china:city beijing chongqing
1) 1) "116.39999896287918091"
2) "39.90000009167092543"
2) 1) "106.49999767541885376"
2) "29.52999957900659211"</span></span>
4.1.3 geodist
返回两个给定位置之间的距离。
如果两个位置之间的其中一个不存在, 那么命令返回空值。
指定单位的参数 unit 必须是以下单位的其中一个:
-
m 表示单位为米。
-
km 表示单位为千米。
-
mi 表示单位为英里。
-
ft 表示单位为英尺。
如果用户没有显式地指定单位参数, 那么 GEODIST
默认使用米作为单位。
GEODIST
命令在计算距离时会假设地球为完美的球形, 在极限情况下, 这一假设最大会造成 0.5% 的误差。
<span style="background-color:#f8f8f8"><span style="color:#333333">127.0.0.1:6379> geodist china:city beijing shanghai #北京到上海的距离
"1067378.7564"
127.0.0.1:6379> geodist china:city beijing shanghai km
"1067.3788"
127.0.0.1:6379> geodist china:city beijing chongqing km
"1464.0708"
</span></span>
4.1.4 georedius
以给定的经纬度为中心, 返回键包含的位置元素当中, 与中心的距离不超过给定最大距离的所有位置元素。
我附近的人 ? (获得所有附近的人的位置,定位)通过半径来查询
所有的数据都应该录入 china:city 才会让结果更加精确
<span style="background-color:#f8f8f8"><span style="color:#333333">127.0.0.1:6379> georadius china:city 110 30 1000 km #以100 30 这个经纬度为中心,寻找方圆1000km内的城市
1) "chongqing"
2) "xian"
3) "shengzheng"
4) "hangzhou"
(0.62s)
127.0.0.1:6379> georadius china:city 110 30 500 km
1) "chongqing"
2) "xian"
127.0.0.1:6379> georadius china:city 110 30 500 km withdist withcoord #显示到中间位置的距离 以及城市经纬度
1) 1) "chongqing"
2) "341.9374"
3) 1) "106.49999767541885376"
2) "29.52999957900659211"
2) 1) "xian"
2) "483.8340"
3) 1) "108.96000176668167114"
2) "34.25999964418929977"
127.0.0.1:6379> georadius china:city 110 30 500 km withdist withcoord count 3 # 筛选出指定的结果数
1) 1) "chongqing"
2) "341.9374"
3) 1) "106.49999767541885376"
2) "29.52999957900659211"
2) 1) "xian"
2) "483.8340"
3) 1) "108.96000176668167114"
2) "34.25999964418929977"
</span></span>
4.1.5 georadiusbymember
这个命令和 GEORADIUS 命令一样, 都可以找出位于指定范围内的元素, 但是 GEORADIUSBYMEMBER
的中心点是由给定的位置元素决定的
<span style="background-color:#f8f8f8"><span style="color:#333333">127.0.0.1:6379> georadiusbymember china:city beijing 1000 km
1) "beijing"
2) "xian"
127.0.0.1:6379> georadiusbymember china:city hangzhou 400 km
1) "hangzhou"
2) "shanghai"
</span></span>
4.1.6 geohash
通常使用表示位置的元素使用不同的技术,使用Geohash位置52点整数编码。由于编码和解码过程中所使用的初始最小和最大坐标不同,编码的编码也不同于标准。此命令返回一个标准的Geohash,在维基百科和geohash.org网站都有相关描述
Geohash字符串属性
该命令将返回11个字符的Geohash字符串,所以没有精度Geohash,损失相比,使用内部52位表示。返回的geohashes具有以下特性:
-
他们可以缩短从右边的字符。它将失去精度,但仍将指向同一地区。
-
它可以在
geohash.org
网站使用,网址http://geohash.org/<geohash-string>
。查询例子:Geohash - geohash.org/sqdtr74hyu0. -
与类似的前缀字符串是附近,但相反的是不正确的,这是可能的,用不同的前缀字符串附近。
<span style="background-color:#f8f8f8"><span style="color:#333333">将二维的经纬度转换为一维的字符串,如果两个字符串越接近,那么距离就越近
127.0.0.1:6379> geohash china:city beijing chongqing
1) "wx4fbxxfke0"
2) "wm5xzrybty0"
</span></span>
Geo 底层的实现原理其实就是zset 我们可以使用Zset 命令来操作geo
<span style="background-color:#f8f8f8"><span style="color:#333333">127.0.0.1:6379> zrange china:city 0 -1 #查看地图中全部元素
1) "chongqing"
2) "xian"
3) "shengzheng"
4) "hangzhou"
5) "shanghai"
6) "beijing"
127.0.0.1:6379> zrem china:city xian $移除地图中某个元素
(integer) 1
127.0.0.1:6379> zrange china:city 0 -1
1) "chongqing"
2) "shengzheng"
3) "hangzhou"
4) "shanghai"
5) "beijing"
</span></span>
4.2 Hyperloglog 基数统计
什么是基数
A {1,3,5,7,8,7}
B {1,3,5,7,8}
基数(不重复的元素)=
简介
Redis 2.8.9 版本就更新了Hyperloglog 数据结构
Redis Hyperloglog 基数统计算法
优点:占用的内存是固定的,2^64不同元素的技术,只需要废12kb内存,如果要从内存角度比较的话 Hyperloglog首选!
网页的uv(一个人访问一个网站多次,但还是算作一个人)
传统的解决方式,set保存用户的id,然后就可以统计set中的元素数量作为标准判断,
这个方式如果保存大量的用户id,就比较占内存,我们的目的是为了计数,而不是保存用户id
使用测试
<span style="background-color:#f8f8f8"><span style="color:#333333">127.0.0.1:6379> pfadd mylog a b c d e f g h i j #创建第一组元素
(integer) 1
127.0.0.1:6379> pfcount mylog # 统计元素基数数量
(integer) 10
127.0.0.1:6379> pfadd mylog2 i j z x c v b n m #创建第二组元素
(integer) 1
127.0.0.1:6379> pfcount mylog2 # 统计元素基数数量
(integer) 9
127.0.0.1:6379> pfmerge mylog3 mylog mylog2 #合并两组
OK
127.0.0.1:6379> pfcount mylog3 #看并集的数量
(integer) 15
</span></span>
4.3 Bitmap 位图场景详解
位存储
01010101 二进制表示
统计用户信息 活跃 不活跃 / 登录 未登录 / 打卡 未打卡 两个状态的,都可以使用bitmap
bitmaps位图,树结构,都是二进制位来进行记录,只有0和1两个状态
测试
记录周一到周日的打卡
<span style="background-color:#f8f8f8"><span style="color:#333333">127.0.0.1:6379> setbit sign 0 1
(integer) 0
127.0.0.1:6379> setbit sign 1 0
(integer) 0
127.0.0.1:6379> setbit sign 2 0
(integer) 0
127.0.0.1:6379> setbit sign 3 1
(integer) 0
127.0.0.1:6379> setbit sign 4 1
(integer) 0
127.0.0.1:6379> setbit sign 5 0
(integer) 0
127.0.0.1:6379> setbit sign 6 0
(integer) 0</span></span>
查看某一天是否打卡
<span style="background-color:#f8f8f8"><span style="color:#333333">127.0.0.1:6379> getbit sign 3
(integer) 1
127.0.0.1:6379> getbit sign 6
(integer) 0
</span></span>
统计操作,统计打卡天数
<span style="background-color:#f8f8f8"><span style="color:#333333">127.0.0.1:6379> bitcount sign
(integer) 3
</span></span>
5 事务
要么同时成功,要么同时失败,原子性。
Redis 事务本质:一组命令的集合,一个事务中的所有命令都会被序列化,在事务执行过程中,会按照顺序执行
一次性、顺序性、排他性
Redis事务没有隔离级别的概念
所有的命令在事务中,并没有直接被执行,只有发起执行命令的时候才会执行
Redis单条命令是保证原子性的,但是事务是不保证原子性
redis事务:
-
开启事务(multi)
-
命令入列(......)
-
执行事务(exec)
<span style="background-color:#f8f8f8"><span style="color:#333333">127.0.0.1:6379> multi #开启事务
OK
#命令入队
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> exec #执行事务
1) OK
2) OK
3) "v2"
4) OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k4 v4
QUEUED
127.0.0.1:6379> discard #取消事务
OK
127.0.0.1:6379> get k4 #事务队列中的命令都不会被执行
(nil)
</span></span>
编译型异常(代码有问题,命令有错),事务中所有的命令都不会被执行
<span style="background-color:#f8f8f8"><span style="color:#333333">127.0.0.1:6379> nulti
(error) ERR unknown command `nulti`, with args beginning with:
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> getset k3 #错误的命令
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379> set k4 v4
QUEUED
127.0.0.1:6379> set k5 v5
QUEUED
127.0.0.1:6379> exec #执行事务报错
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get k1
(nil)
</span></span>
运行时异常(1/0),如果事务队列中存在语法性,那么执行命令的时候,其他命令是可以正常执行的,错误命令抛出异常
<span style="background-color:#f8f8f8"><span style="color:#333333">127.0.0.1:6379> set k1 "v1"
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incr k1 #k1 是字符串,不能自增
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> get k3
QUEUED
127.0.0.1:6379> exec
1) (error) ERR value is not an integer or out of range #第一条执行失败
2) OK
3) OK
4) "v3"
</span></span>
监控
悲观锁
-
很悲观,认为什么时候都会出问题,无论做什么都会枷锁
乐观锁
-
很乐观,认为什么时候都不会出问题,所以不会上锁没更新数据二点时候去判断一下,在此期间是否有人修改过这个数据
-
获取version
-
更新的时候比较version
Redis 监视测试
单个线程
<span style="background-color:#f8f8f8"><span style="color:#333333">127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> set out 0
OK
127.0.0.1:6379> watch money #监视money
OK
127.0.0.1:6379> multi #事务正常结束,数据期间没有发生变动,这个时候就正常执行
OK
127.0.0.1:6379> decrby money 20
QUEUED
127.0.0.1:6379> incrby out 20
QUEUED
127.0.0.1:6379> exec
1) (integer) 80
2) (integer) 20
</span></span>
测试多线程
现在再连接一个客户端,查询money值,并将money值改为1000
<span style="background-color:#f8f8f8"><span style="color:#333333">[root@yewei bin]<span style="color:#aa5500"># redis-cli -p 6379</span>
<span style="color:#116644">127</span>.0.0.1:6379> <span style="color:#3300aa">get</span> money
<span style="color:#aa1111">"80"</span>
<span style="color:#116644">127</span>.0.0.1:6379> <span style="color:#770088">set</span> money <span style="color:#116644">1000</span>
OK
</span></span>
测试多线程改值,使用watch可以当作redis的乐观锁操作
<span style="background-color:#f8f8f8"><span style="color:#333333"><span style="color:#116644">127</span>.0.0.1:6379> watch money
OK
<span style="color:#116644">127</span>.0.0.1:6379> multi
OK
<span style="color:#116644">127</span>.0.0.1:6379> decrby money <span style="color:#116644">10</span>
QUEUED
<span style="color:#116644">127</span>.0.0.1:6379> incrby out <span style="color:#116644">10</span>
QUEUED
<span style="color:#116644">127</span>.0.0.1:6379> exec <span style="color:#aa5500">#执行之前,另外一个线程修改了money的值,这时候就会导致事务执行失败</span>
(nil)
</span></span>
解决问题
<span style="background-color:#f8f8f8"><span style="color:#333333"><span style="color:#116644">127</span>.0.0.1:6379> unwatch <span style="color:#aa5500">#如果发现事务执行失败,就解锁</span>
OK
<span style="color:#116644">127</span>.0.0.1:6379> watch money <span style="color:#aa5500">#获取最新的值,再次监视</span>
OK
<span style="color:#116644">127</span>.0.0.1:6379> multi <span style="color:#aa5500">#事务</span>
OK
<span style="color:#116644">127</span>.0.0.1:6379> decrby money <span style="color:#116644">1</span>
QUEUED
<span style="color:#116644">127</span>.0.0.1:6379> incrby money <span style="color:#116644">1</span>
QUEUED
<span style="color:#116644">127</span>.0.0.1:6379> exec
<span style="color:#116644">1</span>) (integer) <span style="color:#116644">999</span>
<span style="color:#116644">2</span>) (integer) <span style="color:#116644">1000</span>
</span></span>
6 Jedis
我们要使用Java来操作redis
什么是Jedis? 是Redis官方推荐的java连接开发工具,使用Java操作Redis中间件,如果你要使用Java 操作redis,那么一定要对jedis十分的熟悉
测试
1 导入对应的依赖
<span style="background-color:#f8f8f8"><span style="color:#333333"> <dependencies>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.78</version>
</dependency>
</dependencies></span></span>
2 编码测试
-
连接数据库
<span style="background-color:#f8f8f8">import redis.clients.jedis.Jedis; public class TestPing { public static void main(String[] args) { //1. new Jedis 对象即可 Jedis jedis = new Jedis("127.0.0.1",6379); //jedis 命令 System.out.println(jedis.ping()); } }</span>
输出
-
操作命令
-
常用api
-
string
-
List
-
Set
-
Hash
-
Zset
<span style="background-color:#f8f8f8"> System.out.println("清空数据:"+jedis.flushDB()); System.out.println("判断某个键是否存在:"+jedis.exists("username")); System.out.println("新增<'username','kuangshen;>键值对:"+jedis.set("username","kuangshen")); System.out.println("新增<'password','123456;>键值对:"+jedis.set("password","123456")); System.out.println("系统中所有的键如下:"); Set<String> keys=jedis.keys("*"); System.out.println(keys); System.out.println("删除键password:"+jedis.del("password")); System.out.println("判断password键是否存在:"+jedis.exists("password")); System.out.println("查看username索存储的值的类型"+jedis.type("username")); System.out.println("随机返回key空间的一个:"+jedis.randomKey()); System.out.println("重命名key:"+jedis.rename("username","name")); System.out.println("取出改后的name:"+jedis.get("name")); System.out.println("按索引查询:"+jedis.select(0)); System.out.println("删除当前数据库中所有的key:"+jedis.flushDB()); System.out.println("返回当前数据库中key的数目:"+jedis.dbSize()); System.out.println("删除所有数据库中所有key:"+jedis.flushAll());</span>
-
事务
<span style="background-color:#f8f8f8">package com.yw; import com.alibaba.fastjson.JSONObject; import redis.clients.jedis.Jedis; import redis.clients.jedis.Transaction; public class TestTX { public static void main(String[] args) { Jedis jedis = new Jedis(); JSONObject jsonObject=new JSONObject(); jsonObject.put("hello","world"); jsonObject.put("name","kuangshen"); //开启事务 Transaction multi = jedis.multi(); String result = jsonObject.toJSONString(); try { multi.set("user1",result); multi.set("user2",result); multi.exec(); //执行事务 }catch (Exception e){ multi.discard(); //放弃事务 e.printStackTrace(); } finally { System.out.println(jedis.get("user1")); System.out.println(jedis.get("user2")); jedis.close(); } } } </span>
-
-
断开连接
7 Springboot 整合
Spring Boot操作数据:Spring-data :jpa jdbc mongodb redis
说明,再springboot2.X之后,原来使用的jedis被替换为了lettuce
jedis:底层采用直连,多个线程操作的话,是不安全的,如果想要避免,使用jedis pool 连接池,类似BIO模式
letuce:采用netty,实例额可以在多个线程中进行共享,不存在线程不安全的情况,可以减少线程数据了,类似NIO模式
创建项目redis-02-sptingboot
依赖
<span style="background-color:#f8f8f8"><span style="color:#333333"><?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.6</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.yw</groupId>
<artifactId>redis-02-springboot</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>redis-02-springboot</name>
<description>redis-02-springboot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<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>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
</span></span>
源码分析RedisAutoConfiguration
<span style="background-color:#f8f8f8"><span style="color:#333333">@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 来替换这个默认的
//默认的RedisTemplate没有过多的设置,redis对象都是需要序列化的
//两个泛型Object, Object ,我们每次使用都需要强转<String,Object>
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
</span></span>
整合测试
1导入依赖
<span style="background-color:#f8f8f8"><span style="color:#333333"> <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency></span></span>
2 配置连接
application.properties
<span style="background-color:#f8f8f8"><span style="color:#333333">spring.redis.host=127.0.0.1
spring.redis.port=6379</span></span>
2 测试
<span style="background-color:#f8f8f8"><span style="color:#333333">package com.yw;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
@SpringBootTest
class Redis02SpringbootApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void contextLoads() {
// redisTemplate.opsForValue() 操作字符串
//redisTemplate.opsForList() 操作ilst
//opsForSet
//opsForHash
//opsForZSet
//opsForGeo
//opsForHyperLogLog
redisTemplate.opsForValue().set("mykey","redis");
System.out.println(redisTemplate.opsForValue().get("mykey"));
//获取redis的连接对象
// RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
// connection.flushDb();
// connection.flushAll();
}
}
</span></span>
自定义RedisTemplate
源码分析
测试
<span style="background-color:#f8f8f8"><span style="color:#333333"> @Test
public void test2() throws JsonProcessingException {
User user = new User("狂神说", 3);
String jsonUser = new ObjectMapper().writeValueAsString(user); //序列化
redisTemplate.opsForValue().set("user",jsonUser);
System.out.println(redisTemplate.opsForValue().get("user"));
}</span></span>
如果传入的是user对象
所有的对象需要序列化
源码分析
除了默认的序列化方式,以下还有其他的序列化实现
自定义RedisTemplate
config 包
<span style="background-color:#f8f8f8"><span style="color:#333333">package com.yw.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
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;
import java.net.UnknownHostException;
@Configuration
public class RedisConfig {
//编写我们自己的 RedisTemplate
@Bean
public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)throws UnknownHostException {
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);
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;
}
}
</span></span>
RedisTemplate 工具类
<span style="background-color:#f8f8f8"><span style="color:#333333">package com.yw.utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* RedisTemplate 工具类
*/
@Component
public class RedisUtil {
@Autowired
@Qualifier("redisTemplate")
private RedisTemplate redisTemplate;
/**
* 给一个指定的 key 值附加过期时间
*
* @param key
* @param time
* @return
*/
public boolean expire(String key, long time) {
return redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
/**
* 根据key 获取过期时间
*
* @param key
* @return
*/
public long getTime(String key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
/**
* 根据key 获取过期时间
*
* @param key
* @return
*/
public boolean hasKey(String key) {
return redisTemplate.hasKey(key);
}
/**
* 移除指定key 的过期时间
*
* @param key
* @return
*/
public boolean persist(String key) {
return redisTemplate.boundValueOps(key).persist();
}
//- - - - - - - - - - - - - - - - - - - - - String类型 - - - - - - - - - - - - - - - - - - - -
/**
* 根据key获取值
*
* @param key 键
* @return 值
*/
public Object get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}
/**
* 将值放入缓存
*
* @param key 键
* @param value 值
* @return true成功 false 失败
*/
public void set(String key, String value) {
redisTemplate.opsForValue().set(key, value);
}
/**
* 将值放入缓存并设置时间
*
* @param key 键
* @param value 值
* @param time 时间(秒) -1为无期限
* @return true成功 false 失败
*/
public void set(String key, String value, long time) {
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
} else {
redisTemplate.opsForValue().set(key, value);
}
}
/**
* 批量添加 key (重复的键会覆盖)
*
* @param keyAndValue
*/
public void batchSet(Map<String, String> keyAndValue) {
redisTemplate.opsForValue().multiSet(keyAndValue);
}
/**
* 批量添加 key-value 只有在键不存在时,才添加
* map 中只要有一个key存在,则全部不添加
*
* @param keyAndValue
*/
public void batchSetIfAbsent(Map<String, String> keyAndValue) {
redisTemplate.opsForValue().multiSetIfAbsent(keyAndValue);
}
/**
* 对一个 key-value 的值进行加减操作,
* 如果该 key 不存在 将创建一个key 并赋值该 number
* 如果 key 存在,但 value 不是长整型 ,将报错
*
* @param key
* @param number
*/
public Long increment(String key, long number) {
return redisTemplate.opsForValue().increment(key, number);
}
/**
* 对一个 key-value 的值进行加减操作,
* 如果该 key 不存在 将创建一个key 并赋值该 number
* 如果 key 存在,但 value 不是 纯数字 ,将报错
*
* @param key
* @param number
*/
public Double increment(String key, double number) {
return redisTemplate.opsForValue().increment(key, number);
}
//- - - - - - - - - - - - - - - - - - - - - set类型 - - - - - - - - - - - - - - - - - - - -
/**
* 将数据放入set缓存
*
* @param key 键
* @return
*/
public void sSet(String key, String value) {
redisTemplate.opsForSet().add(key, value);
}
/**
* 获取变量中的值
*
* @param key 键
* @return
*/
public Set<Object> members(String key) {
return redisTemplate.opsForSet().members(key);
}
/**
* 随机获取变量中指定个数的元素
*
* @param key 键
* @param count 值
* @return
*/
public void randomMembers(String key, long count) {
redisTemplate.opsForSet().randomMembers(key, count);
}
/**
* 随机获取变量中的元素
*
* @param key 键
* @return
*/
public Object randomMember(String key) {
return redisTemplate.opsForSet().randomMember(key);
}
/**
* 弹出变量中的元素
*
* @param key 键
* @return
*/
public Object pop(String key) {
return redisTemplate.opsForSet().pop("setValue");
}
/**
* 获取变量中值的长度
*
* @param key 键
* @return
*/
public long size(String key) {
return redisTemplate.opsForSet().size(key);
}
/**
* 根据value从一个set中查询,是否存在
*
* @param key 键
* @param value 值
* @return true 存在 false不存在
*/
public boolean sHasKey(String key, Object value) {
return redisTemplate.opsForSet().isMember(key, value);
}
/**
* 检查给定的元素是否在变量中。
*
* @param key 键
* @param obj 元素对象
* @return
*/
public boolean isMember(String key, Object obj) {
return redisTemplate.opsForSet().isMember(key, obj);
}
/**
* 转移变量的元素值到目的变量。
*
* @param key 键
* @param value 元素对象
* @param destKey 元素对象
* @return
*/
public boolean move(String key, String value, String destKey) {
return redisTemplate.opsForSet().move(key, value, destKey);
}
/**
* 批量移除set缓存中元素
*
* @param key 键
* @param values 值
* @return
*/
public void remove(String key, Object... values) {
redisTemplate.opsForSet().remove(key, values);
}
/**
* 通过给定的key求2个set变量的差值
*
* @param key 键
* @param destKey 键
* @return
*/
public Set<Set> difference(String key, String destKey) {
return redisTemplate.opsForSet().difference(key, destKey);
}
//- - - - - - - - - - - - - - - - - - - - - hash类型 - - - - - - - - - - - - - - - - - - - -
/**
* 加入缓存
*
* @param key 键
* @param map 键
* @return
*/
public void add(String key, Map<String, String> map) {
redisTemplate.opsForHash().putAll(key, map);
}
/**
* 获取 key 下的 所有 hashkey 和 value
*
* @param key 键
* @return
*/
public Map<Object, Object> getHashEntries(String key) {
return redisTemplate.opsForHash().entries(key);
}
/**
* 验证指定 key 下 有没有指定的 hashkey
*
* @param key
* @param hashKey
* @return
*/
public boolean hashKey(String key, String hashKey) {
return redisTemplate.opsForHash().hasKey(key, hashKey);
}
/**
* 获取指定key的值string
*
* @param key 键
* @param key2 键
* @return
*/
public String getMapString(String key, String key2) {
return redisTemplate.opsForHash().get("map1", "key1").toString();
}
/**
* 获取指定的值Int
*
* @param key 键
* @param key2 键
* @return
*/
public Integer getMapInt(String key, String key2) {
return (Integer) redisTemplate.opsForHash().get("map1", "key1");
}
/**
* 弹出元素并删除
*
* @param key 键
* @return
*/
public String popValue(String key) {
return redisTemplate.opsForSet().pop(key).toString();
}
/**
* 删除指定 hash 的 HashKey
*
* @param key
* @param hashKeys
* @return 删除成功的 数量
*/
public Long delete(String key, String... hashKeys) {
return redisTemplate.opsForHash().delete(key, hashKeys);
}
/**
* 给指定 hash 的 hashkey 做增减操作
*
* @param key
* @param hashKey
* @param number
* @return
*/
public Long increment(String key, String hashKey, long number) {
return redisTemplate.opsForHash().increment(key, hashKey, number);
}
/**
* 给指定 hash 的 hashkey 做增减操作
*
* @param key
* @param hashKey
* @param number
* @return
*/
public Double increment(String key, String hashKey, Double number) {
return redisTemplate.opsForHash().increment(key, hashKey, number);
}
/**
* 获取 key 下的 所有 hashkey 字段
*
* @param key
* @return
*/
public Set<Object> hashKeys(String key) {
return redisTemplate.opsForHash().keys(key);
}
/**
* 获取指定 hash 下面的 键值对 数量
*
* @param key
* @return
*/
public Long hashSize(String key) {
return redisTemplate.opsForHash().size(key);
}
//- - - - - - - - - - - - - - - - - - - - - list类型 - - - - - - - - - - - - - - - - - - - -
/**
* 在变量左边添加元素值
*
* @param key
* @param value
* @return
*/
public void leftPush(String key, Object value) {
redisTemplate.opsForList().leftPush(key, value);
}
/**
* 获取集合指定位置的值。
*
* @param key
* @param index
* @return
*/
public Object index(String key, long index) {
return redisTemplate.opsForList().index("list", 1);
}
/**
* 获取指定区间的值。
*
* @param key
* @param start
* @param end
* @return
*/
public List<Object> range(String key, long start, long end) {
return redisTemplate.opsForList().range(key, start, end);
}
/**
* 把最后一个参数值放到指定集合的第一个出现中间参数的前面,
* 如果中间参数值存在的话。
*
* @param key
* @param pivot
* @param value
* @return
*/
public void leftPush(String key, String pivot, String value) {
redisTemplate.opsForList().leftPush(key, pivot, value);
}
/**
* 向左边批量添加参数元素。
*
* @param key
* @param values
* @return
*/
public void leftPushAll(String key, String... values) {
// redisTemplate.opsForList().leftPushAll(key,"w","x","y");
redisTemplate.opsForList().leftPushAll(key, values);
}
/**
* 向集合最右边添加元素。
*
* @param key
* @param value
* @return
*/
public void leftPushAll(String key, String value) {
redisTemplate.opsForList().rightPush(key, value);
}
/**
* 向左边批量添加参数元素。
*
* @param key
* @param values
* @return
*/
public void rightPushAll(String key, String... values) {
//redisTemplate.opsForList().leftPushAll(key,"w","x","y");
redisTemplate.opsForList().rightPushAll(key, values);
}
/**
* 向已存在的集合中添加元素。
*
* @param key
* @param value
* @return
*/
public void rightPushIfPresent(String key, Object value) {
redisTemplate.opsForList().rightPushIfPresent(key, value);
}
/**
* 向已存在的集合中添加元素。
*
* @param key
* @return
*/
public long listLength(String key) {
return redisTemplate.opsForList().size(key);
}
/**
* 移除集合中的左边第一个元素。
*
* @param key
* @return
*/
public void leftPop(String key) {
redisTemplate.opsForList().leftPop(key);
}
/**
* 移除集合中左边的元素在等待的时间里,如果超过等待的时间仍没有元素则退出。
*
* @param key
* @return
*/
public void leftPop(String key, long timeout, TimeUnit unit) {
redisTemplate.opsForList().leftPop(key, timeout, unit);
}
/**
* 移除集合中右边的元素。
*
* @param key
* @return
*/
public void rightPop(String key) {
redisTemplate.opsForList().rightPop(key);
}
/**
* 移除集合中右边的元素在等待的时间里,如果超过等待的时间仍没有元素则退出。
*
* @param key
* @return
*/
public void rightPop(String key, long timeout, TimeUnit unit) {
redisTemplate.opsForList().rightPop(key, timeout, unit);
}
}
</span></span>
8 Redis.conf 详解
单位
1 配置unit单位对大小写不敏感
包含
network
<span style="background-color:#f8f8f8"><span style="color:#333333">bind 127.0.0.1 #绑定ip
protected-mode yes #保护模式
port 6379 #端口设置</span></span>
通用 GENERAL
<span style="background-color:#f8f8f8"><span style="color:#333333">dawmonize <span style="color:#3300aa">yes</span> <span style="color:#aa5500">#以守护进程的方式运行,默认是no,我们需要自己开启为yes</span>
pidfile /var/run/redis_6379.pid <span style="color:#aa5500">#如果是以后台的方式运行,我们就需要指定一个pid 文件</span>
</span></span>
日志级别
<span style="background-color:#f8f8f8"><span style="color:#333333">loglevel notice
logfile <span style="color:#aa1111">""</span> <span style="color:#aa5500">#日志文件名</span>
databases <span style="color:#116644">16</span> <span style="color:#aa5500">#数据库的数量</span>
always-show-log-yes <span style="color:#aa5500">#是否显示Logo</span></span></span>
快照
持久化 在规定时间内,执行了多次操作,则会持久化到文件 .rdb .aof
redis是内存数据库,如果没有持久化,那么数据断电及丢失
<span style="background-color:#f8f8f8"><span style="color:#333333"><span style="color:#aa5500">#如果900s内,如果至少有1个key 进行了修改,我们及进行持久化操作</span>
save <span style="color:#116644">900</span> <span style="color:#116644">1</span>
<span style="color:#aa5500">#如果200s内,如果至少有10个key 进行了修改,我们及进行持久化操作</span>
save <span style="color:#116644">300</span> <span style="color:#116644">10</span>
<span style="color:#aa5500">#如果60s内,如果至少有10000个key 进行了修改,我们及进行持久化操作</span>
save <span style="color:#116644">60</span> <span style="color:#116644">10000</span>
stop-writes-on-bgsave-error <span style="color:#3300aa">yes</span> <span style="color:#aa5500">#持久化如果出现错误,redis是否继续工昨</span>
rdbcompression <span style="color:#3300aa">yes</span> <span style="color:#aa5500">#是否压缩rdb文件,需要消耗cpu资源</span>
rdbchecksum <span style="color:#3300aa">yes</span> <span style="color:#aa5500">#保存rdb文件的时候,进行错误的检查校验</span>
dir ./ <span style="color:#aa5500">#rdb文件保存目录</span>
</span></span>
REPLICATION 复制 后面讲到主从复制时讲
SECURITY
<span style="background-color:#f8f8f8"><span style="color:#333333">requirepass foobared <span style="color:#aa5500"># redis 连接默认是没有密码的</span>
<span style="color:#aa5500">#通过命令设置密码</span>
config <span style="color:#3300aa">get</span> requirepass
config <span style="color:#770088">set</span> requirepass <span style="color:#aa1111">"123456"</span>
auth <span style="color:#116644">123456</span>
</span></span>
client 客户端
<span style="background-color:#f8f8f8"><span style="color:#333333">maxclients <span style="color:#116644">10000</span> <span style="color:#aa5500">#设置最大客户端数量</span>
</span></span>
MEMORY MANAGEMENT 内存设置
<span style="background-color:#f8f8f8"><span style="color:#333333">maxmemory <bytes> <span style="color:#aa5500">#最大内存容量</span>
maxmemory-policy noeviction <span style="color:#aa5500">#内存达到上限后的处理策略</span>
<span style="color:#116644">1</span>、volatile-lru:只对设置了过期时间的key进行LRU(默认值)
<span style="color:#116644">2</span>、allkeys-lru : 删除lru算法的key
<span style="color:#116644">3</span>、volatile-random:随机删除即将过期key
<span style="color:#116644">4</span>、allkeys-random:随机删除
<span style="color:#116644">5</span>、volatile-ttl : 删除即将过期的
<span style="color:#116644">6</span>、noeviction : 永不过期,返回错误
</span></span>
APPEND ONLY MODE aof配置
<span style="background-color:#f8f8f8"><span style="color:#333333">appendonly no <span style="color:#aa5500">#默认是不开启aof模式</span>
appendfilename <span style="color:#aa1111">"appendonly.aof"</span> <span style="color:#aa5500">#持久化文件名称</span>
<span style="color:#aa5500"># appendfsync always #每次修改都会同步 </span>
appendfsync everysec <span style="color:#aa5500">#每秒同步一次</span>
<span style="color:#aa5500"># appendfsync no #不同步,这时候操作系统自己同步数据</span>
</span></span>
9 Redis 持久化
Redis是内存数据库,如果不将内存中的数据库状态保存到磁盘,那么一旦服务器进程退出,服务器中的数据库状态也会消失,所以redis提供了持久化功能
9.1 RDB(Redis DataBase)
什么是RDB
在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是Snaoshot快照,他恢复时是将快照文件直接读到内存里。
Redis会单独创建(fork)一个子进程来进化持久化,会先将数据写入到一个临时文件中,待持久化过程结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何IO操作的,这就确保了极高的性能。如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。RDB的缺点是最后一次持久化后的数据可能丢失。
<span style="background-color:#f8f8f8"><span style="color:#333333"><span style="color:#aa5500">#如果900s内,如果至少有1个key 进行了修改,我们及进行持久化操作</span>
save <span style="color:#116644">900</span> <span style="color:#116644">1</span>
<span style="color:#aa5500">#如果200s内,如果至少有10个key 进行了修改,我们及进行持久化操作</span>
save <span style="color:#116644">300</span> <span style="color:#116644">10</span>
<span style="color:#aa5500">#如果60s内,如果至少有10000个key 进行了修改,我们及进行持久化操作</span>
save <span style="color:#116644">60</span> <span style="color:#116644">10000</span>
stop-writes-on-bgsave-error <span style="color:#3300aa">yes</span> <span style="color:#aa5500">#持久化如果出现错误,redis是否继续工昨</span>
rdbcompression <span style="color:#3300aa">yes</span> <span style="color:#aa5500">#是否压缩rdb文件,需要消耗cpu资源</span>
rdbchecksum <span style="color:#3300aa">yes</span> <span style="color:#aa5500">#保存rdb文件的时候,进行错误的检查校验</span>
dir ./ <span style="color:#aa5500">#rdb文件保存目录</span>
</span></span>
触发机制
1.save 的规则满足的情况下,会自动触发rdb规则
2.执行flushall命令,也会触发我们的rdb规则
3.退出redis,也会产生rdb文件
备份就自动生成一个dump.rdb
如何恢复rdb文件
1只需要将rdb文件放在我们redis启动目录就可以,redis启动的时候会检查dumprdb恢复其中的数据
优点
1适合大规模的数据恢复
2对数据完整性要不高
缺点
1 需要一定的时间间隔进程操作,如果redis意外宕机了,这个最后一次修改数据就没有了
2 fork进程的时候,会占用一定的内容空间
9.2 AOF(Append Only File)
将我们的所有命令都记录下来,history,恢复的时候就把这个文件全部再执行一遍
以日志的形式来记录每个写操作,将Redis执行过的所有指令记录下来(读操作不记录),只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作
aof 保存的是appendonly.aof 文件
默认是不开启aof的,需要更改redis.conf 配置
<span style="background-color:#f8f8f8"><span style="color:#333333">appendonly yes #默认是不开启aof模式</span></span>
如果这个aof文件有错位,这时候redis启动不起来的,我们需要修复这个aof文件
redis给我们提供了一个工具 redis-check-aof --fix
优点和缺点
<span style="background-color:#f8f8f8"><span style="color:#333333">appendonly no <span style="color:#aa5500">#默认是不开启aof模式</span>
appendfilename <span style="color:#aa1111">"appendonly.aof"</span> <span style="color:#aa5500">#持久化文件名称</span>
<span style="color:#aa5500"># appendfsync always #每次修改都会同步 </span>
appendfsync everysec <span style="color:#aa5500">#每秒同步一次</span>
<span style="color:#aa5500"># appendfsync no #不同步,这时候操作系统自己同步数据</span>
rewrite <span style="color:#aa5500">#重写规则说明,如果文件大小超过规则大小,就会创建一个新的进程来将我们的文件重写</span></span></span>
优点
1 每一次修改都同步,文件的完整性更加好
2 每秒同步一次,可能会丢失一秒数据
3 从不同步,效率最高
缺点
1 相对与数据文件来说,aof远远大于rdb,修复的速度也比rdb慢
2 aof运行效率也要比rdb慢。所以我们redis默认的配置就是rdb持久化
10Redis 发布订阅
Redis发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。
Redis 客户端可以订阅任意数量的频道
订阅/发布 消息图
下图展示了频道Channel1,以及订阅这个频道的三个客户端-- client2 ,client5 和 client1 之间的关系
当有新消息通过PUBLISH命令发给频道channel1时,这个消息就会被发送给订阅它的三个客户端
命令
下表列出了 redis 发布订阅常用命令:
这些命令被广泛应用于构建即使通信应用,比如网络聊天室(chartroom)和实时广播和实时提醒
序号 | 命令及描述 |
---|---|
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 ...]] 指退订给定的频道。 |
测试
订阅段
<span style="background-color:#f8f8f8"><span style="color:#333333"><span style="color:#116644">127</span>.0.0.1:6379> SUBSCRIBE kuangshenshuo <span style="color:#aa5500">#订阅一个频道 kuangshenshuo</span>
Reading messages... (press Ctrl-C to quit)
<span style="color:#116644">1</span>) <span style="color:#aa1111">"subscribe"</span>
<span style="color:#116644">2</span>) <span style="color:#aa1111">"kuangshenshuo"</span>
<span style="color:#116644">3</span>) (integer) <span style="color:#116644">1</span>
<span style="color:#aa5500">#等待读取推送的消息</span>
<span style="color:#116644">1</span>) <span style="color:#aa1111">"message"</span> <span style="color:#aa5500">#消息</span>
<span style="color:#116644">2</span>) <span style="color:#aa1111">"kuangshenshuo"</span> <span style="color:#aa5500">#频道</span>
<span style="color:#116644">3</span>) <span style="color:#aa1111">"hello kuangshen"</span> <span style="color:#aa5500">#消息的具体内容</span>
<span style="color:#116644">1</span>) <span style="color:#aa1111">"message"</span>
<span style="color:#116644">2</span>) <span style="color:#aa1111">"kuangshenshuo"</span>
<span style="color:#116644">3</span>) <span style="color:#aa1111">"hello redis"</span>
</span></span>
发送端
<span style="background-color:#f8f8f8"><span style="color:#333333"><span style="color:#116644">127</span>.0.0.1:6379> PUBLISH kuangshenshuo <span style="color:#aa1111">"hello kuangshen"</span> <span style="color:#aa5500">#发布者发布消息到频道</span>
(integer) <span style="color:#116644">1</span>
<span style="color:#116644">127</span>.0.0.1:6379> PUBLISH kuangshenshuo <span style="color:#aa1111">"hello redis"</span>
(integer) <span style="color:#116644">1</span>
</span></span>
原理
使用场景
1 实时消息系统
2 实时聊天-频道当做聊天室,将信息回显给所有的人即可
3 订阅,关注系统都可以的
11Reds主从复制
主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(master),后者称为从节点(slave);数据的复制是单向的,只能由主节点到从节点。
默认情况下,每台Redis服务器都是主节点;且一个主节点可以有多个从节点(或没有从节点),但一个从节点只能有一个主节点。
主从复制的作用
主从复制的作用主要包括:
-
数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
-
故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务的冗余。
-
负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写Redis数据时应用连接主节点,读Redis数据时应用连接从节点),分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量。
-
高可用基石:除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础。
环境配置
只需配置从库,不用配置主库
查看当前库信息
<span style="background-color:#f8f8f8"><span style="color:#333333"><span style="color:#116644">127</span>.0.0.1:6379> info replication
<span style="color:#aa5500"># Replication</span>
role:master <span style="color:#aa5500">#角色 master</span>
connected_slaves:0 <span style="color:#aa5500">#从机个数</span>
master_replid:c5f61cc93a35b9b464b47eee21a01b7772d30c8b
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
</span></span>
复制3个配置文件,然后修改对应信息
<span style="background-color:#f8f8f8"><span style="color:#333333">[root@yewei bin]<span style="color:#aa5500"># cd redis-config/</span>
[root@yewei redis-config]<span style="color:#aa5500"># ls</span>
redis.conf
[root@yewei redis-config]<span style="color:#aa5500"># cp redis.conf redis79.conf</span>
[root@yewei redis-config]<span style="color:#aa5500"># cp redis.conf redis80.conf</span>
[root@yewei redis-config]<span style="color:#aa5500"># cp redis.conf redis81.conf</span>
[root@yewei redis-config]<span style="color:#aa5500"># ls</span>
redis79.conf redis80.conf redis81.conf redis.conf
</span></span>
修改 port 、 pid名称 、 logfile 名称 、dump.rdb 名称
启动6379,6380,6381 端口的redis-serve
一主二从配置
默认情况下,每台redis服务器都是主节点;我们一般情况下只用配置从机就好了
例如:一主(79) 二从(80,81)
<span style="background-color:#f8f8f8"><span style="color:#333333"><span style="color:#aa5500"># SLAVEOF</span>
<span style="color:#aa5500">#####################6380######################################</span>
<span style="color:#116644">127</span>.0.0.1:6380> SLAVEOF <span style="color:#116644">127</span>.0.0.1 <span style="color:#116644">6379</span>
OK
<span style="color:#116644">127</span>.0.0.1:6380> info replication
<span style="color:#aa5500"># Replication</span>
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:up
master_last_io_seconds_ago:2
master_sync_in_progress:0
slave_repl_offset:0
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:41db10663826ac6ea99c09181989a9adb1e5100f
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:0
<span style="color:#aa5500">#####################6381######################################</span>
<span style="color:#116644">127</span>.0.0.1:6381> SLAVEOF <span style="color:#116644">127</span>.0.0.1 <span style="color:#116644">6379</span>
OK
<span style="color:#116644">127</span>.0.0.1:6381> info replication
<span style="color:#aa5500"># Replication</span>
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:up
master_last_io_seconds_ago:4
master_sync_in_progress:0
slave_repl_offset:210
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:41db10663826ac6ea99c09181989a9adb1e5100f
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:210
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:211
repl_backlog_histlen:0
<span style="color:#116644">127</span>.0.0.1:6381>
<span style="color:#aa5500">#####################6379######################################</span>
<span style="color:#116644">127</span>.0.0.1:6379> info replication
<span style="color:#aa5500"># Replication</span>
role:master
connected_slaves:2
slave0<span style="color:#0000ff">:ip</span><span style="color:#981a1a">=</span><span style="color:#116644">127</span>.0.0.1<span style="color:#0000ff">,port</span><span style="color:#981a1a">=</span><span style="color:#116644">6380</span><span style="color:#0000ff">,state</span><span style="color:#981a1a">=</span>online<span style="color:#0000ff">,offset</span><span style="color:#981a1a">=</span><span style="color:#116644">252</span><span style="color:#0000ff">,lag</span><span style="color:#981a1a">=</span><span style="color:#116644">1</span>
slave1<span style="color:#0000ff">:ip</span><span style="color:#981a1a">=</span><span style="color:#116644">127</span>.0.0.1<span style="color:#0000ff">,port</span><span style="color:#981a1a">=</span><span style="color:#116644">6381</span><span style="color:#0000ff">,state</span><span style="color:#981a1a">=</span>online<span style="color:#0000ff">,offset</span><span style="color:#981a1a">=</span><span style="color:#116644">252</span><span style="color:#0000ff">,lag</span><span style="color:#981a1a">=</span><span style="color:#116644">0</span>
master_replid:41db10663826ac6ea99c09181989a9adb1e5100f
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:252
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:252
</span></span>
真实的主从配置应该在配置文件中配置,这样的话是永久的
细节
主机可以写,从机不能写操作,主机中的所有信息都会同步到从机
<span style="background-color:#f8f8f8"><span style="color:#333333"><span style="color:#116644">127</span>.0.0.1:6379> <span style="color:#770088">set</span> k1 v1
OK
<span style="color:#116644">127</span>.0.0.1:6379> keys *
<span style="color:#116644">1</span>) <span style="color:#aa1111">"k1"</span></span></span>
<span style="background-color:#f8f8f8"><span style="color:#333333"><span style="color:#116644">127</span>.0.0.1:6380> keys *
<span style="color:#116644">1</span>) <span style="color:#aa1111">"k1"</span>
</span></span>
<span style="background-color:#f8f8f8"><span style="color:#333333"><span style="color:#116644">127</span>.0.0.1:6381> keys *
<span style="color:#116644">1</span>) <span style="color:#aa1111">"k1"</span>
<span style="color:#116644">127</span>.0.0.1:6381> <span style="color:#770088">set</span> k2 v2
(error) READONLY You can<span style="color:#aa1111">'t write against a read only replica. </span>
</span></span>
主机如果断开连接了,从机依旧连接主机的,但是没有写操作,这个时候,主机回来了,从机依旧可以直接获取到主机写的信息
如果是使用命令行,来配置的从机,这个时候如果重启了,就会变回主机,只要变回从机,立马就会从主机中获取值
复制原理
Slave启动成功连接到master 后会发送一个sync同步命令
master 接到命令,启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令,在后台进程执行完毕之后,master将传送整个数据文件到slave,并完成一次完全同步
全量复制:slave服务在接收到数据库文件数据后,将其存盘并加载到内存中
增量复制:master 继续将新的所有收集到的修改命令依次传给slave,完成同步
但是只要是重新连接master,一次完全同步(全量复制)将被自动执行
12 哨兵模式
12.1 引入
如果环境配置如下:
<span style="background-color:#f8f8f8"><span style="color:#333333"><span style="color:#116644">127</span>.0.0.1:6379> info replication
<span style="color:#aa5500"># Replication</span>
role:master
connected_slaves:1
slave0<span style="color:#0000ff">:ip</span><span style="color:#981a1a">=</span><span style="color:#116644">127</span>.0.0.1<span style="color:#0000ff">,port</span><span style="color:#981a1a">=</span><span style="color:#116644">6380</span><span style="color:#0000ff">,state</span><span style="color:#981a1a">=</span>online<span style="color:#0000ff">,offset</span><span style="color:#981a1a">=</span><span style="color:#116644">4896</span><span style="color:#0000ff">,lag</span><span style="color:#981a1a">=</span><span style="color:#116644">1</span>
master_replid:41db10663826ac6ea99c09181989a9adb1e5100f
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:4896
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:4896
</span></span>
<span style="background-color:#f8f8f8"><span style="color:#333333"><span style="color:#116644">127</span>.0.0.1:6380> info replication
<span style="color:#aa5500"># Replication</span>
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:up
master_last_io_seconds_ago:10
master_sync_in_progress:0
slave_repl_offset:4896
slave_priority:100
slave_read_only:1
connected_slaves:1
slave0<span style="color:#0000ff">:ip</span><span style="color:#981a1a">=</span><span style="color:#116644">127</span>.0.0.1<span style="color:#0000ff">,port</span><span style="color:#981a1a">=</span><span style="color:#116644">6381</span><span style="color:#0000ff">,state</span><span style="color:#981a1a">=</span>online<span style="color:#0000ff">,offset</span><span style="color:#981a1a">=</span><span style="color:#116644">4896</span><span style="color:#0000ff">,lag</span><span style="color:#981a1a">=</span><span style="color:#116644">1</span>
master_replid:41db10663826ac6ea99c09181989a9adb1e5100f
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:4896
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:4896
</span></span>
<span style="background-color:#f8f8f8"><span style="color:#333333"><span style="color:#116644">127</span>.0.0.1:6381> SLAVEOF <span style="color:#116644">127</span>.0.0.1 <span style="color:#116644">6380</span>
OK
<span style="color:#116644">127</span>.0.0.1:6381> info replication
<span style="color:#aa5500"># Replication</span>
role:slave
master_host:127.0.0.1
master_port:6380
master_link_status:up
master_last_io_seconds_ago:3
master_sync_in_progress:0
slave_repl_offset:4882
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:41db10663826ac6ea99c09181989a9adb1e5100f
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:4882
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:211
repl_backlog_histlen:4672
</span></span>
这时候也可以完成主从复制
如果主机断开了,可以使用 slaveof on one 使自己变成主机,手动命令行操作。
如果主机修复了,就只能重新配置关系了
12.2概述
主从切换技术的方法是:当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,费事费力,还会造成一段时间内服务不可用。这不是一种推荐的方式,更多时候,我们优先考虑哨兵模式。
哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立运行。其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。
这里的哨兵有两个作用
-
通过发送命令,让Redis服务器返回监控其运行状态,包括主服务器和从服务器。
-
当哨兵监测到master宕机,会自动将slave切换成master,然后通过发布订阅模式通知其他的从服务器,修改配置文件,让它们切换主机。
然而一个哨兵进程对Redis服务器进行监控,可能会出现问题,为此,我们可以使用多个哨兵进行监控。各个哨兵之间还会进行监控,这样就形成了多哨兵模式。
用文字描述一下故障切换(failover)的过程。假设主服务器宕机,哨兵1先检测到这个结果,系统并不会马上进行failover过程,仅仅是哨兵1主观的认为主服务器不可用,这个现象成为主观下线。当后面的哨兵也检测到主服务器不可用,并且数量达到一定值时,那么哨兵之间就会进行一次投票,投票的结果由一个哨兵发起,进行failover操作。切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为客观下线。这样对于客户端而言,一切都是透明的。
12.3测试
一主二从
1 配置哨兵配置文件 sentinel.conf
<span style="background-color:#f8f8f8"><span style="color:#333333">
sentinel monitor myredis <span style="color:#116644">127</span>.0.0.1 <span style="color:#116644">6379</span> <span style="color:#116644">1</span>
</span></span>
数字1 ,代表主机挂了,slave 投票看让谁接替成为主机,票数最多的饿,就会成为主机
2 启动哨兵
<span style="background-color:#f8f8f8"><span style="color:#333333">[root@yewei bin]<span style="color:#aa5500"># redis-sentinel redis-config/sentinel.conf </span>
<span style="color:#116644">10104</span>:X <span style="color:#116644">04</span> Nov <span style="color:#116644">2021</span> <span style="color:#116644">14</span>:01:41.238 <span style="color:#aa5500"># oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo</span>
<span style="color:#116644">10104</span>:X <span style="color:#116644">04</span> Nov <span style="color:#116644">2021</span> <span style="color:#116644">14</span>:01:41.238 <span style="color:#aa5500"># Redis version=6.0.6, bits=64, commit=00000000, modified=0, pid=10104, just started</span>
<span style="color:#116644">10104</span>:X <span style="color:#116644">04</span> Nov <span style="color:#116644">2021</span> <span style="color:#116644">14</span>:01:41.238 <span style="color:#aa5500"># Configuration loaded</span>
<span style="color:#116644">10104</span>:X <span style="color:#116644">04</span> Nov <span style="color:#116644">2021</span> <span style="color:#116644">14</span>:01:41.239 * Increased maximum number of open files to <span style="color:#116644">10032</span> (it was originally <span style="color:#770088">set</span> to <span style="color:#116644">1024</span>).
_._
_.-<span style="color:#009900">``</span>__ <span style="color:#aa1111">''</span><span style="color:#0000cc">-</span>._
_.-<span style="color:#009900">``</span> <span style="color:#009900">`. `</span>_. <span style="color:#aa1111">''</span><span style="color:#0000cc">-</span>._ Redis <span style="color:#116644">6</span>.0.6 (00000000/0) <span style="color:#116644">64</span> bit
.-<span style="color:#009900">``</span> .-<span style="color:#009900">```. ```</span>\/ _.,_ <span style="color:#aa1111">''</span><span style="color:#0000cc">-</span>._
( <span style="color:#aa1111">' , .-` | `, ) Running in sentinel mode</span>
|<span style="color:#009900">`-._`</span><span style="color:#0000cc">-</span>...-<span style="color:#009900">` __...-.``-._|'`</span> _.-<span style="color:#aa1111">'| Port: 26379</span>
| <span style="color:#009900">`-._ `</span>._ / _.-<span style="color:#aa1111">' | PID: 10104</span>
<span style="color:#009900">`-._ `</span><span style="color:#0000cc">-</span>._ <span style="color:#009900">`-./ _.-' _.-' </span>
|<span style="color:#009900">`-._`</span><span style="color:#0000cc">-</span>._ <span style="color:#009900">`-.__.-' _.-'_.-'| </span>
| <span style="color:#009900">`-._`</span><span style="color:#0000cc">-</span>._ _.-<span style="color:#aa1111">'_.-'</span> | http://redis.io
<span style="color:#009900">`-._ `</span><span style="color:#0000cc">-</span>._<span style="color:#009900">`-.__.-'_.-' _.-' </span>
|<span style="color:#009900">`-._`</span><span style="color:#0000cc">-</span>._ <span style="color:#009900">`-.__.-' _.-'_.-'| </span>
| <span style="color:#009900">`-._`</span><span style="color:#0000cc">-</span>._ _.-<span style="color:#aa1111">'_.-'</span> |
<span style="color:#009900">`-._ `</span><span style="color:#0000cc">-</span>._<span style="color:#009900">`-.__.-'_.-' _.-' </span>
<span style="color:#009900">`-._ `</span><span style="color:#0000cc">-</span>.__.-<span style="color:#aa1111">' _.-'</span>
<span style="color:#009900">`-._ _.-' </span>
<span style="color:#009900">`-.__.-' </span>
<span style="color:#116644">10104</span>:X <span style="color:#116644">04</span> Nov <span style="color:#116644">2021</span> <span style="color:#116644">14</span>:01:41.277 <span style="color:#aa5500"># WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.</span>
<span style="color:#116644">10104</span>:X <span style="color:#116644">04</span> Nov <span style="color:#116644">2021</span> <span style="color:#116644">14</span>:01:41.291 <span style="color:#aa5500"># Sentinel ID is eaaf2eb7c1731ceeb7d50178efde7606d08f184d</span>
<span style="color:#116644">10104</span>:X <span style="color:#116644">04</span> Nov <span style="color:#116644">2021</span> <span style="color:#116644">14</span>:01:41.291 <span style="color:#aa5500"># +monitor master myredis 127.0.0.1 6379 quorum 1</span>
<span style="color:#116644">10104</span>:X <span style="color:#116644">04</span> Nov <span style="color:#116644">2021</span> <span style="color:#116644">14</span>:01:41.293 * <span style="color:#981a1a">+</span>slave slave <span style="color:#116644">127</span>.0.0.1:6380 <span style="color:#116644">127</span>.0.0.1 <span style="color:#116644">6380</span> @ myredis <span style="color:#116644">127</span>.0.0.1 <span style="color:#116644">6379</span>
<span style="color:#116644">10104</span>:X <span style="color:#116644">04</span> Nov <span style="color:#116644">2021</span> <span style="color:#116644">14</span>:01:41.295 * <span style="color:#981a1a">+</span>slave slave <span style="color:#116644">127</span>.0.0.1:6381 <span style="color:#116644">127</span>.0.0.1 <span style="color:#116644">6381</span> @ myredis <span style="color:#116644">127</span>.0.0.1 <span style="color:#116644">6379</span>
</span></span>
3 测试主机断开
哨兵日志
哨兵 监控到主机master宕机了,
从机6381 自动成为了主机
现在再次将6379 开启服务,6381仍旧是主机,6379只能作为6381的从机了
哨兵模式的优点和缺点
优点:
1,哨兵集群,基于主从复制模式,所有的主从配置优点,他都有
2,主从可以切换,故障可以转移,系统二点可用性就会很好
3,哨兵模式就是主从模式的升级,手动到自动
缺点:
1,Redis 不好在线扩容,集群容量一旦达到上限,在线扩容就会十分麻烦
2,实现哨兵模式的配置其实是很麻烦的
哨兵模式的全部配置
<span style="background-color:#f8f8f8"><span style="color:#333333"><span style="color:#aa5500">#配置端口</span>
port <span style="color:#116644">26379</span>
<span style="color:#aa5500">#以守护进程模式启动</span>
daemonize <span style="color:#3300aa">yes</span>
<span style="color:#aa5500">#日志文件名</span>
logfile <span style="color:#aa1111">"sentinel_26379.log"</span>
<span style="color:#aa5500">#存放备份文件以及日志等文件的目录</span>
dir <span style="color:#aa1111">"/opt/redis/data"</span>
<span style="color:#aa5500">#监控的IP 端口号 名称 sentinel通过投票后认为mater宕机的数量,此处为至少2个</span>
sentinel monitor mymaster <span style="color:#116644">192</span>.168.14.101 <span style="color:#116644">6379</span> <span style="color:#116644">2</span>
<span style="color:#aa5500">#30秒ping不通主节点的信息,主观认为master宕机</span>
sentinel down-after-milliseconds mymaster <span style="color:#116644">30000</span>
<span style="color:#aa5500">#故障转移后重新主从复制,1表示串行,>1并行</span>
sentinel parallel-syncs mymaster <span style="color:#116644">1</span>
<span style="color:#aa5500">#故障转移开始,三分钟内没有完成,则认为转移失败</span>
sentinel failover-timeout mymaster <span style="color:#116644">180000</span></span></span>
13 Redis缓存穿透,击穿, 和 雪崩
13.1 前言
在我们日常的开发中,无不都是使用数据库来进行数据的存储,由于一般的系统任务中通常不会存在高并发的情况,所以这样看起来并没有什么问题,可是一旦涉及大数据量的需求,比如一些商品抢购的情景,或者是主页访问量瞬间较大的时候,单一使用数据库来保存数据的系统会因为面向磁盘,磁盘读/写速度比较慢的问题而存在严重的性能弊端,一瞬间成千上万的请求到来,需要系统在极短的时间内完成成千上万次的读/写操作,这个时候往往不是数据库能够承受的,极其容易造成数据库系统瘫痪,最终导致服务宕机的严重生产问题。
为了克服上述的问题,项目通常会引入NoSQL技术,这是一种基于内存的数据库,并且提供一定的持久化功能。
redis技术就是NoSQL技术中的一种,但是引入redis又有可能出现缓存穿透,缓存击穿,缓存雪崩等问题。本文就对这三种问题进行较深入剖析。
13.2 缓存穿透
缓存穿透:key对应的数据在数据源并不存在,每次针对此key的请求从缓存获取不到,请求都会到数据源,从而可能压垮数据源。比如用一个不存在的用户id获取用户信息,不论缓存还是数据库都没有,若黑客利用此漏洞进行攻击可能压垮数据库。
解决方案
布隆过滤器
,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被 这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。
缓存空对象
如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。
但是这种方法会存在两个问题:
1,如果空值能够被缓存起来,这就意味着缓存需要更多的空间存储更多的键,以为这当中可能会有很多空值的键
2,即使对空值设置了过期时间,还是会存在缓存层和存储层数据会有一段时间窗口的不一致,这对于保持一致性的业务会有影响
13.2 缓存击穿
缓存击穿:key对应的数据存在,但在redis中过期,此时若有大量并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。
解决方案
设置热点数据永不过期
从缓存层面来看,没有设置过期时间,所以不会出现热点key过期后产生的问题
使用互斥锁(mutex key)
简单地来说,就是在缓存失效的时候(判断拿出来的值为空),不是立即去load db,而是先使用缓存工具的某些带成功操作返回值的操作(比如Redis的SETNX或者Memcache的ADD)去set一个mutex key,当操作返回成功时,再进行load db的操作并回设缓存;否则,就重试整个get缓存的方法。
13.3 缓存雪崩
缓存雪崩:当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,也会给后端系统(比如DB)带来很大压力。
缓存失效时的雪崩效应对底层系统的冲击非常可怕!大多数系统设计者考虑用加锁或者队列的方式保证来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上。还有一个简单方案就时讲缓存失效时间分散开,比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。
解决方案
redis 高可用
限流降级
数据预热