redis
redis
一、是什么
概念
1.Redis(Remote Dictionary Server ),即远程字典服务,是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。
2.Redis是用C语言开发的一个开源的高性能基于内存运行的键值对NoSQL数据库
3.区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。
特点
(1) 支持数据的持久化,可以将数据保存在磁盘中,重启之后可以再次加载到内存中使用
(2) 支持多种数据类型,除了KV类型的数据,还支持list、set、hash等数据结构
(3) 支持master-slave模式的数据备份
(4)rdb、aof两种模式
官方提供测试数据,50个并发执行100000个请求,读的速度是110000次/s,写的速度是81000次/s ,且Redis通过提供多种键值数据类型来适应不同场景下的存储需求,目前为止Redis支持的键值数据类型如下:五种数据类型
string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash(哈希类型)
**注意:**Redis采用键值对存储数据,key永远是String类型,五大数据类型指的是value部分
二、什么是NOSQL
- NoSQL(NoSQL = Not Only SQL),意即“不仅仅是SQL”,是一项全新的数据库理念,泛指非关系型的数据库。
- 随着互联网web2.0网站的兴起,传统的关系数据库在应付web2.0网站,特别是超大规模和高并发的SNS类型的web2.0纯动态网站已经显得力不从心,暴露了很多难以克服的问题,而非关系型的数据库则由于其本身的特点得到了非常迅速的发展。NoSQL数据库的产生就是为了解决大规模数据集合多重数据种类带来的挑战,尤其是大数据应用难题。
NOSQL和关系型数据库比较
优点:
1)成本:nosql数据库简单易部署,基本都是开源软件,不需要像使用oracle那样花费大量成本购买使用,相比关系型数据库价格便宜。
2)查询速度:nosql数据库将数据存储于缓存之中,关系型数据库将数据存储在硬盘中,自然查询速度远不及nosql数据库。
3)存储数据的格式:nosql的存储格式是key,value形式、文档形式、图片形式等等,所以可以存储基础类型以及对象或者是集合等各种格式,而数据库则只支持基础类型。
4)扩展性:关系型数据库有类似join这样的多表查询机制的限制导致扩展很艰难。
缺点:
1)维护的工具和资料有限,因为nosql是属于新的技术,不能和关系型数据库10几年的技术同日而语。
2)不提供对sql的支持,如果不支持sql这样的工业标准,将产生一定用户的学习和使用成本。
3)不提供关系型数据库对事务的处理。
非关系型数据库的优势:
1)性能NOSQL是基于键值对的,可以想象成表中的主键和值的对应关系,而且不需要经过SQL层的解析,所以性能非常高。
2)可扩展性同样也是因为基于键值对,数据之间没有耦合性,所以非常容易水平扩展。
关系型数据库的优势:
1)复杂查询可以用SQL语句方便的在一个表以及多个表之间做非常复杂的数据查询。
2)事务支持使得对于安全性能很高的数据访问要求得以实现。对于这两类数据库,对方的优势就是自己的弱势,反之亦然。
总结:
关系型数据库与NoSQL数据库并非对立而是互补的关系,即通常情况下使用关系型数据库,在适合使用NoSQL的时候使用NoSQL数据库,让NoSQL数据库对关系型数据库的不足进行弥补。一般会将数据存储在关系型数据库中,在nosql数据库中备份存储关系型数据库的数据
Redis应用场景
- 热点数据加速查询(主要场景),如热点商品、热点信息等访问量较高的数据
- 即时信息查询,如公交到站信息、在线人数信息等
- 时效性信息控制,如验证码控制、投票控制等
- 分布式数据共享,如分布式集群架构中的session分离消息队列
redis推荐在linux服务器上搭建的
下载xshell,和xftp
三、Redis基础知识
netstat -nplt 查看进程
kill -9 - pid 杀死一个进程
查看mysql密码:
- cd /root/oneinstack
- grep dbrootpwd options.conf
- mysql -u root -p 连接数据库
修改mysql密码:
- use mysql 切换库
- show tables
- select * from user;
- update user set authentication_string=password(‘123456’) where user=‘root’ and Host=‘localhost’;
- flush privileges;刷新权限
systemctl stop firewalld 关闭防火墙
systemctl restart network 重启网卡
grep 3306查看端口号是否开放
ctrl+shift+{}
Redis采用单线程机制进行工作
Redis默认拥有16个数据库,数据库编号从0开始,默认使用0号数据库
使用select 数据库编号 可以切换使用的数据库
dbsize 命令查看当前数据库key的数量
keys * 命令查看当前数据库所有的key
flushdb 命令清空当前数据库
flushall 命令清空所有数据库
Redis中所有数据库使用同一个密码,默认没有密码,Redis认为安全层面应该由Linux来保证
Redis中所有索引都是从0开始
Redis默认端口是6379
#redis.config文件没了怎么办
cd bin/#q切换到bin目录
mkdir sconfig#创建sconfig文件夹
cp /opt/redis-6.2.2/redis.conf sconfig#把redis.config复制一份到sconfig下
cd /sconfig#切换到sconfig文件夹
修改redis默认后台启动
修改daemonize yes–>然后esc退出编辑模式
然后:wq推出编辑
四、Redis-key
单点登录可以把数据放在redis里面,可以设置过期时间
修改redis配置文件,vim /opt/redis-3.0.4/redis.conf
启动redis服务,先切换到bin目录
执行redis-server 文件夹/redis.conf
查看服务是否启动,ps aux | grep redis-server
redis-cli -p 6379 使用redis命令行工具
ping 测试是否连通。出现pong代表连接成功
FLUSHALL 全部清空
keys * 查看所有的key值
set 键 值 创建键名和对应值
EXISTS 键 判断这个键是否存在,返回1则存在
move 键 1 移动key,1表示1号数据库
get 键 获取这个键的值
EXPIRE 键 10 让这个键十秒后过期
ttl 键 看这个键过期时间 看到-2,代表过期
type 键名 查看键名类型
五、String
APPEND 键名 值 给key的值追加值,如果当前key不存在就相当于set了一个key
STRLEN 键 查看该键的值的长度
set views 0 键,0代表默认值
incr key key值加一,自增,计数器 i++
decr key key值减一,i–
INCRBY key 10 步长为10 ,i+=10
DECRBY …
GETRANGE key 开始索引 结束索引 截取字符串
GETRANGE key 0 -1 获取全部字符串
SETRANGE key 开始索引 要替换的值 相当于replace
setex (ser with expire) 设置过期时间,后面跟着提示呢~
setnx (set if not exist) 不存在则set成功返回1,存在返回0
mset 同时设置多个值
mget 同时获取多个值
msetnx 原子性操作,要么一起成功,要么一起失败
对象
set user:1 {name:shy,age:18} 设置一个user:1 对象,值为json字符串保存的以一个对象
user:{id}:{field},这种设计在redis中是完全ok的,eg:mset user:1:name shy user:1:age 18
getset 先get后set,如果不存在值,返回null,如果存在,就获取原来的值,并设置新的值
好像真的,数据结构是相同的~
Spring类似的使用场景:value除了是字符串还可以是数字
- 计数器
- 统计多单位的数量
- 粉丝数
- 对象缓存存储
六、list
列表
在redis中,可以把list玩成栈,队列,阻塞队列
所有的list命令都是以"l"开头的,redis不区分大小写
push:
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> LRANGE list 0 -1#获取list中的值
1) "two"
2) "one"
127.0.0.1:6379> LRANGE list 0 1#通过区间获取具体的值
1) "two"
2) "one"
127.0.0.1:6379> RPUSH list right#将一个值或者多个值,插入列表尾部,取值顺序,从左往右
(integer) 3
127.0.0.1:6379> LRANGE list 0 -1
1) "two"
2) "one"
3) "right"
127.0.0.1:6379>
pop:
127.0.0.1:6379> LRANGE list 0 -1
1) "two"
2) "one"
3) "right"
127.0.0.1:6379> LPOP list#移除列表的第一个元素
"two"
127.0.0.1:6379> RPOP list#移除列表的最后一个元素
"right"
127.0.0.1:6379> LRANGE list 0 -1
1) "one"
127.0.0.1:6379>
index:
127.0.0.1:6379> LINDEX list 1#通过下表获得list中的某个值
(nil)
127.0.0.1:6379> LINDEX list 0
"one"
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
###########################################################################
移除指定的值
127.0.0.1:6379> LPUSH list three
(integer) 4
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"
127.0.0.1:6379>
###########################################################################
trim 修剪
127.0.0.1:6379> RPUSH list "hello"
(integer) 1
127.0.0.1:6379> RPUSH list "hello1"
(integer) 2
127.0.0.1:6379> RPUSH list "hello2"
(integer) 3
127.0.0.1:6379> RPUSH list "hello3"
(integer) 4
127.0.0.1:6379> LTRIM list 1 2#通过下标截取指定的长度
OK
127.0.0.1:6379> LRANGE list 0 -1
1) "hello1"
2) "hello2"
###########################################################################
rpoplpush 移除列表的最后一个元素,并将它移动到新的列表中
127.0.0.1:6379> RPUSH list "hello"
(integer) 3
127.0.0.1:6379> RPOPLPUSH list mylist
"hello"
127.0.0.1:6379> LRANGE mylist 0 -1
1) "hello"
###########################################################################
lset ,将列表中指定的值替换为另外一个值
127.0.0.1:6379> LSET mylist 0 ccc
OK
127.0.0.1:6379> LRANGE mylist 0 -1#如果不存在,报错,存在就更新
1) "ccc"
###########################################################################
linsert,将某一个具体的值插入到列表中某个元素的前面或者后面
127.0.0.1:6379> LINSERT mylist before "ccc" "hhh"
(integer) 2
127.0.0.1:6379> LRANGE mylist 0 -1
1) "hhh"
2) "ccc"
127.0.0.1:6379> LINSERT mylist after "ccc" "lll"
(integer) 3
127.0.0.1:6379> LRANGE mylist 0 -1
1) "hhh"
2) "ccc"
3) "lll"
实际上是一个链表,before node after,right,left都可以插入值
如果key不存在,创建新的链表,存在的话,新增内容
两边插入或者改动值,效率最高!中间元素效率低
消息排队,消息队列
七、set
集合,值不能重复
###########################################################################
127.0.0.1:6379> sadd names "shy"#set集合中添加元素
(integer) 1
127.0.0.1:6379> sadd names "sb"
(integer) 1
127.0.0.1:6379> sadd names "ccc"
(integer) 1
127.0.0.1:6379> SMEMBERS names#查看指定set集合的所有值
1) "ccc"
2) "shy"
3) "sb"
127.0.0.1:6379> SISMEMBER names shy
(integer) 1
127.0.0.1:6379> SISMEMBER names sss
(integer) 0
127.0.0.1:6379> SCARD names#获取set集合中的内容元素个数
###########################################################################
127.0.0.1:6379> srem names hhh#移除set集合中的指定元素
###########################################################################
set无序不重复集合
127.0.0.1:6379> SRANDMEMBER names#随机抽选出一个元素
"shy"
127.0.0.1:6379> SRANDMEMBER names
"ccc"
127.0.0.1:6379> SRANDMEMBER names 2#随机抽选出指定个数的元素
1) "ccc"
2) "shy"
###########################################################################
移除指定的key,随机删除一个key
127.0.0.1:6379> SMEMBERS names
1) "ccc"
2) "shy"
3) "sb"
127.0.0.1:6379> SPOP names#随机移除元素
"ccc"
127.0.0.1:6379> SPOP names
"sb"
###########################################################################
将一个指定的值,移动到另外一个set集合中
127.0.0.1:6379> SMOVE names ages "shy"#将一个指定的值,移动到另外一个集合
(integer) 1
###########################################################################
共同关注,交集,,,微博,b站,用户将所有的关注放在一个set集合中,粉丝也放在集合中共同关注,共同爱好,推荐好友
127.0.0.1:6379> SDIFF name1 name2#差集
1) "a"
2) "b"
127.0.0.1:6379> SINTER name1 name2#交集,共同好友
1) "c"
127.0.0.1:6379> SUNION name1 name2#并集
1) "c"
2) "e"
3) "a"
4) "b"
5) "d"
八、hash
map集合,key-map集合,本质和String类型没有太大区别,还是一个简单的key-value
127.0.0.1:6379> hset myhash name1 shy#set一个具体的k-v
(integer) 1
127.0.0.1:6379> hget myhash name1#获取一个字段值
"shy"
127.0.0.1:6379> hmset myhash name1 hello name2 world#set多个k-v
OK
127.0.0.1:6379> hmget myhash name1 name2#获取多个字段值
1) "hello"
2) "world"
127.0.0.1:6379> HGETALL myhash#获取全部数据
1) "name1"
2) "hello"
3) "name2"
4) "world"
127.0.0.1:6379> hdel myhash name1#删除hash指定的key字段,对应的value值也就无了
(integer) 1
127.0.0.1:6379> HGETALL myhash
1) "name2"
2) "world"
###########################################################################
hlen
127.0.0.1:6379> hlen myhash#获取hash表的字段数量
(integer) 1
127.0.0.1:6379> HMSET myhash name1 hello name2 world
OK
127.0.0.1:6379> HGETALL myhash
1) "name2"
2) "world"
3) "name1"
4) "hello"
127.0.0.1:6379> hlen myhash
(integer) 2
127.0.0.1:6379> HEXISTS myhash name3#判断hash中指定的字段是否存在
(integer) 0
###########################################################################
只获取所有的key或只获取所有的value
127.0.0.1:6379> HKEYS myhash#获取所有的字段
1) "name2"
2) "name1"
127.0.0.1:6379> HVALS myhash#获取所有的值
1) "world"
2) "hello"
###########################################################################
incr decr
127.0.0.1:6379> hset myhash name3 3#指定增量
(integer) 1
127.0.0.1:6379> HINCRBY myhash name3 1
(integer) 4
127.0.0.1:6379> HINCRBY myhash name3 -1
(integer) 3
127.0.0.1:6379> hsetnx myhash name4 hey#如果不存在可以设置
(integer) 1
127.0.0.1:6379> hsetnx myhash name4 key#如果存在不能设置
(integer) 0
hash的应用:变更的数据user name age,用户信息的保存,经常变动的信息,更适合存对象,string更适合字符串
九、Zset
有序集合,在set的基础上增加了一个值,
127.0.0.1:6379> zadd myset 1 one#添加一个值
(integer) 1
127.0.0.1:6379> zadd myset 2 two 3 three#添加多个值
(integer) 2
127.0.0.1:6379> ZRANGE myset 0 -1
1) "one"
2) "two"
3) "three"
###########################################################################
排序
127.0.0.1:6379> zadd salary 3000 shy#添加三个用户
(integer) 1
127.0.0.1:6379> zadd salary 100 shy1
(integer) 1
127.0.0.1:6379> zadd salary 1001 shy2
(integer) 1
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf#显示全部用户,从小到大排序
1) "shy1"
2) "shy2"
3) "shy"
127.0.0.1:6379> ZREVRANGE salary 0 -1#显示全部用户,从大到小排序
1) "shy2"
2) "shy1"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf withscores
1) "shy1"
2) "100"
3) "shy2"
4) "1001"
5) "shy"
6) "3000"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf 1002 withscores
1) "shy1"
2) "100"
3) "shy2"
4) "1001"
###########################################################################
移除rem中的元素
127.0.0.1:6379> zrange salary 0 -1
1) "shy1"
2) "shy2"
3) "shy"
127.0.0.1:6379> zrem salary shy #移除有序集合中的指定元素
(integer) 1
127.0.0.1:6379> zrange salary 0 -1
1) "shy1"
2) "shy2"
127.0.0.1:6379> zcard salary#获取有序集合中的元素数量
(integer) 2
###########################################################################
127.0.0.1:6379> zadd myset 1 hello
(integer) 1
127.0.0.1:6379> zadd myset 2 world 3 shy
(integer) 2
127.0.0.1:6379> ZCOUNT myset 1 3#获取指定区间的成员数量
(integer) 3
127.0.0.1:6379> ZCOUNT myset 1 2
(integer) 2
去官方文档
应用:班级成绩,工资排序,普通消息:1,重要消息:2,带权重进行判断,排行榜
十、三种特殊数据类型
geospatial地理位置
朋友定位,附近的人,打车距离的计算
redis的Geo
###########################################################################
#规则两级无法直接添加,一般会下载程序数据,直接java一次性导入
#参数 key 经度、纬度、名称,经纬度必须有效
#geoadd:将指定的地理空间位置(纬度、经度、名称)添加到指定的key中。
127.0.0.1:6379> geoadd china:city 112.45 37.91 taiyuan
(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 chongqin
(integer) 1
127.0.0.1:6379> geoadd china:city 114.05 22.52 shenzhen
(integer) 1
127.0.0.1:6379> geoadd china:city 120.16 30.14 hangzhou
(integer) 1
127.0.0.1:6379> geoadd china:city 108.96 34.26 xian
###########################################################################
获得当前定位,一定是一个坐标值
#GEOPOS:从key里返回所有给定位置元素的位置(经度和纬度)。
127.0.0.1:6379> GEOPOS china:city taiyuan chongqin#获取指定城市的经纬度
1) 1) "112.45000094175338745"
2) "37.91000064835854744"
2) 1) "106.49999767541885376"
2) "29.52999957900659211"
###########################################################################
GEODIST:返回两个给定位置之间的距离。
指定单位的参数 unit 必须是以下单位的其中一个:
m 表示单位为米。
km 表示单位为千米。
mi 表示单位为英里。
ft 表示单位为英尺。
127.0.0.1:6379> GEODIST china:city taiyuan shanghai
"1110035.5528"
127.0.0.1:6379> GEODIST china:city taiyuan shanghai km
"1110.0356"
###########################################################################
附近的人?获得附近所有人的地址,定位,通过半径来查询
GEORADIUS:以给定的经纬度为中心, 返回键包含的位置元素当中, 与中心的距离不超过给定最大距离的所有位置元素。
范围可以使用以下其中一个单位:
m 表示单位为米。
km 表示单位为千米。
mi 表示单位为英里。
ft 表示单位为英尺。
所有的数据都应该录入,才会让结果更加清晰,可用于周围人功能
127.0.0.1:6379> GEORADIUS china:city 112 37 1000 km#以112 37这个坐标为中心寻找方圆1000km内的城市
1) "xian"
2) "taiyuan"
3) "chongqin"
127.0.0.1:6379> GEORADIUS china:city 112 37 500 km
1) "xian"
2) "taiyuan"
127.0.0.1:6379> GEORADIUS china:city 112 37 500 km withdist#dist是距离显示到中心的距离
1) 1) "xian"
2) "410.3255"
2) 1) "taiyuan"
2) "108.7349"
127.0.0.1:6379> GEORADIUS china:city 112 37 500 km withcoord#coord是坐标
1) 1) "xian"
2) 1) "108.96000176668167114"
2) "34.25999964418929977"
2) 1) "taiyuan"
2) 1) "112.45000094175338745"
2) "37.91000064835854744"
###########################################################################
GEORADIUSBYMEMBER:
这个命令和 GEORADIUS 命令一样, 都可以找出位于指定范围内的元素, 但是 GEORADIUSBYMEMBER 的中心点是由给定的位置元素决定的, 而不是像 GEORADIUS 那样, 使用输入的经度和纬度来决定中心点
指定成员的位置被用作查询的中心。可用于城市定位
127.0.0.1:6379> GEORADIUSBYMEMBER china:city taiyuan 1000 km
1) "xian"
2) "taiyuan"
127.0.0.1:6379> GEORADIUSBYMEMBER china:city shanghai 400 km
1) "hangzhou"
2) "shanghai"
###########################################################################
GEOHASH:该命令将返回11个字符的Geohash字符串
127.0.0.1:6379> GEOHASH china:city taiyuan chongqin#将二维经纬度转化为一维的字符串,两个字符串越接近距离越近
1) "wqxzwv79fb0"
2) "wm5xzrybty0"
###########################################################################
查看地图中全部元素,移除指定元素
127.0.0.1:6379> ZRANGE china:city 0 -1
1) "chongqin"
2) "xian"
3) "taiyuan"
4) "shenzhen"
5) "hangzhou"
6) "shanghai"
127.0.0.1:6379> zrem china:city xian
(integer) 1
127.0.0.1:6379> ZRANGE china:city 0 -1
1) "chongqin"
2) "taiyuan"
3) "shenzhen"
4) "hangzhou"
5) "shanghai"
geo底层就是zset
Hyperloglog
基数:不重复的元素 ,可以接受误差!
是什么:基数统计的算法
网页的uv(一个人访问一个网站多次,但还是算作一个人)
优点:占用内存固定,2^64不同的元素的技术,只需要12kb的内存,只看内存的话,她是首选~
0.81%的错误率~
127.0.0.1:6379> PFADD mykey a b c d e f g
(integer) 1
127.0.0.1:6379> PFCOUNT mykey
(integer) 7
127.0.0.1:6379> PFADD mykey2 as e t b c y u d e f g
(integer) 1
127.0.0.1:6379> PFCOUNT mykey
(integer) 7
127.0.0.1:6379> PFCOUNT mykey2
(integer) 9
127.0.0.1:6379> PFMERGE mykey3 mykey mykey2
OK
127.0.0.1:6379> PFCOUNT mykey3
(integer) 10
Bitmaps
位存储
0 1 0 1
统计用户信息,活跃,不活跃!登录,未登录!打卡,全年打卡!两个状态的都可以用Bitmaps
Bitmaps:位图,数据结构!都是操作二进制位来进行记录,就只有0和1两个状态,省内存
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> getbit sign 2
(integer) 0
统计打卡的天数
127.0.0.1:6379> BITCOUNT sign
(integer) 2
十一、事务
redis事务的本质:一组命令的集合,一个事务中的所有命令都会被序列化,在事务执行的过程中,会按照顺序执行。
一次性、顺序性、排他性,执行一系列命令
redis事务没有隔离级别的概念
所有的命令在事务中,并没有直接执行!只有发起执行命令的时候才会执行!Exec
redis事务:
- 开启事务 MULTI
- 命令入队 …
- 执行事务 exec
127.0.0.1:6379> MULTI#开启事务
OK
127.0.0.1:6379(TX)> set k1 v1#命令入队
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> get k2
QUEUED
127.0.0.1:6379(TX)> set k3 v3
QUEUED
127.0.0.1:6379(TX)> EXEC#执行事务
1) OK
2) OK
3) "v2"
4) OK
#事务执行完就没了
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> set k4 v4
QUEUED
127.0.0.1:6379(TX)> DISCARD#放弃事务
OK
127.0.0.1:6379> get k4
(nil)
编译型异常:代码有错,所有命令都不会执行
运行时异常:如果队列中存在语法型错误,执行命令的时候,其他事务依旧正常执行,错误命令抛出异常
十二、redis实现乐观锁
悲观锁:很悲观,什么时候都会出问题,无论做什么都会加锁,很影响性能
乐观锁:
- 很乐观,认为什么时候都不会出问题,所以不会上锁!更新数据的时候去判断一下,再次期间,是否有人修改数据
- 获取version
- 更新的时候比较version
redis 的监视测试:
正常执行成功:
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(TX)> DECRBY money 20
QUEUED
127.0.0.1:6379(TX)> INCRBY out 20
QUEUED
127.0.0.1:6379(TX)> exec
1) (integer) 80
2) (integer) 20
测试,另外开一个客户端操作,使用watch可以当作redis 的乐观锁操作
如果修改失败,获取最新的值就好
十三、Jedis
正片来了~
使用java操作redis
是什么:官方推荐的java连接开发工具!使用java操作redis中间件!
测试:
创建项目时注意:jdk,language level都搂一眼
和往常一样~
1.导入依赖
<!--导入jedis包-->
<dependencies>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.2.0</version>
</dependency>
<!--fastjson包-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
</dependencies>
这里要redis在本地装上,安装方法自行百度~
查看运行状态:redis-server.exe redis.windows.conf
小提示:trycatch包裹某块代码快捷键:ctrl+alt+t
2.测试
- 连接数据库
- 操作命令
- 断开连接
package com.shy;
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);
System.out.println(jedis.ping());
}
}
常用的API
语句和上面一样自己试试~
都一样
事务再理解
package com.shy;
import com.alibaba.fastjson.JSONObject;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;
public class TestPing {
public static void main(String[] args) {
//1.new Jedis对象
Jedis jedis = new Jedis("127.0.0.1",6379);
jedis.flushDB();
JSONObject jsonObject = new JSONObject();
jsonObject.put("hello","world");
jsonObject.put("name","shy");
//开启事务
Transaction multi = jedis.multi();
String result = jsonObject.toJSONString();
//jedis.watch(result);
try {
multi.set("user1",result);
multi.set("user2",result);
int i = 1/0;//代码抛出异常,事务执行失败
multi.exec();//执行事务
} catch (Exception e) {
multi.discard();//放弃事务
e.printStackTrace();
} finally {
System.out.println(jedis.get("user1"));
System.out.println(jedis.get("user2"));
jedis.close();//关闭连接
}
}
}
十四、springboot集成redis
springdata:很重要
在springboot2.x之后。原来使用的jedis被替换为了lettuce
jedis:采用直连,多个线程操作的话,是不安全的,要避免的话,使用jedis pool连接池,更像BIO模式,阻塞
lettuce:底层采用netty,实例可以在多个实例中进行共享,不存在线程 不安全的情况,可以减少线程数据了,更像NIO模式
测试:
1.导入依赖
2.配置连接
spring.redis.host=127.0.0.1
spring.redis.port=6379
3.测试
package com.shy;
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.core.RedisTemplate;
@SpringBootTest
class Redis02SpringbootApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void contextLoads() {
//redisTemplate操作不同的数据类型,api指令一样的
//opsForValue 操作字符串,类似String
//opsForList 操作list,类似list
//都是一样的~
//除了基本的操作,常用的方法都可以直接通过redisTemplate操作,比如事务,和基本的CRUD
//获取redis的连接对象
/*RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
connection.flushDb();
connection.flushAll();*/
redisTemplate.opsForValue().set("name","shy");
System.out.println(redisTemplate.opsForValue().get("name"));
}
}
编写一个自己的Template
package com.shy.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;
@Configuration
public class RedisConfig {
//编写自己的redisTemplate
//固定的模板,在企业中直接用
//写一个工具类呗~RedisUtils,到时候用公司的
@Bean
@SuppressWarnings("all")
public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory factory) {
//为了自己开发方便,一般直接使用<String,Object>
RedisTemplate<String,Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
//Json序列化配置
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
//String的序列化
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
//配置具体的序列化方式
//key采用String的序列化方式
template.setKeySerializer(stringRedisSerializer);
//Hash的key也采用String的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
//value序列化方式采用jackson
template.setValueSerializer(jackson2JsonRedisSerializer);
//hash的value序列化方式采用jackson
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
测试:
@Qualifier("redisTemplate")//要让这个redisTemplate指向自己定义的那个,点那个小叶子
@Test
public void test() throws JsonProcessingException {
//真实开发一般都是用json来传递对象
User shy = new User("shy", 18);
//序列化user,没有序列化会报错,必须序列化
//String jsonUser = new ObjectMapper().writeValueAsString(shy);
redisTemplate.opsForValue().set("user",shy);
System.out.println(redisTemplate.opsForValue().get("user"));
}
查看效果,打开本地的redis-cli.exe,输入:
上面的是jdk的序列化
下面的是自定义的序列化,由stringRedisSerializer实现
redis对咱来说,十分简单~,要理解其用处和使用场景
十五、Redis.conf
通过这个文件来启动~
工作中一些小小的配置就可以优化性能
单位
1.配置文件,unit单位不敏感
包含
Spring 、import、include
网络
bind 127.0.0.1 -::1#绑定的ip
protected-mode yes#保护模式
port 6379#端口
通用
daemonize yes#以守护进程的方式运行,默认是no,自己手动修改yes
pidfile /var/run/redis_6379.pid#如果以后台方式运行,需要指定一个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)
loglevel notice
logfile ""#日志生成的文件位置
databases 16#数据库的数量,默认为16个数据库
always-show-logo no#是否总是显示logo
快照
持久化,在规定时间内执行了多少次操作则会持久化到文件.rdb.aof
redis是内存数据库,没有持久化,会断电即失
持久化规则
#如果3600s内,如果至少有一个key进行了修改,会进行持久化操作
# save 3600 1
#如果300s内,如果至少有10个key进行了修改,会进行持久化操作
# save 300 100
#如果60s内,如果至少有一万个key进行了修改,会进行持久化操作
# save 60 10000
#之后学持久化,自己定义测试
stop-writes-on-bgsave-error yes#持久化出错,是否还要继续工作
rdbcompression yes#是否压缩rdb文件(持久化文件),需要消耗一些cpu资源
rdbchecksum yes#保存rdb文件时,是否进行错误的检查校验
dir ./#rdb文件保存的目录
REPLICATION后面说
SECURITY安全
可以设置redis密码,没密码上线小心被攻击
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> config get requirepass#获取redis密码
1) "requirepass"
2) ""
127.0.0.1:6379> config set requirepass "123456"#设置redis密码
OK
127.0.0.1:6379> ping#发现命令使用无权限
(error) NOAUTH Authentication required.
127.0.0.1:6379> auth 123456#使用密码登录
OK
127.0.0.1:6379> config get requirepass
1) "requirepass"
2) "123456"
127.0.0.1:6379> ping
PONG
CLIENTS限制
内存达到上线的处理策略
1.volatile-lru(least recently used):最近最少使用算法,从设置了过期时间的键中选择空转时间最长的键值对清除掉;
2.volatile-lfu(least frequently used):最近最不经常使用算法,从设置了过期时间的键中选择某段时间之内使用频次最小的键值对清除掉;
3.volatile-ttl:从设置了过期时间的键中选择过期时间最早的键值对清除;
4.volatile-random:从设置了过期时间的键中,随机选择键进行清除;
5.allkeys-lru:最近最少使用算法,从所有的键中选择空转时间最长的键值对清除;
6.allkeys-lfu:最近最不经常使用算法,从所有的键中选择某段时间之内使用频次最少的键值对清除;
7.allkeys-random:所有的键中,随机选择键进行删除;
8.noeviction:不做任何的清理工作,在redis的内存超过限制之后,所有的写入操作都会返回错误;但是读操作都能正常的进行;
APPEND ONLY MODE
aof
appendonly no#默认不开启aof模式的,默认使用rdb方式持久化,大部分情况下rdb完全够用了
appendfilename "appendonly.aof"#持久化文件的名字
# appendfsync always#每次修改都会执行一次sync,消耗性能
appendfsync everysec#每秒执行一次sync,可能会丢失1s的数据
# appendfsync no#不执行,这时候操作系统自己同步数据数据最快
十六、redis持久化
redis是内存数据库,如果不将内存中的数据库状态保存到磁盘,那么一旦服务器进程退出,服务器中的数据库状态也会消失,所以redis提供了持久化功能
RDB(Redis DataBase)
RDB持久化是把当前进程数据生成快照保存到硬盘的过程,触发RDB持久化过程分为手动触发和自动触发
1)触发机制
手动触发分别对应save和bgsave命令
·save命令:阻塞当前Redis服务器,直到RDB过程完成为止,对于内存 比较大的实例会造成长时间阻塞,线上环境不建议使用
·bgsave命令:Redis进程执行fork操作创建子进程,RDB持久化过程由子 进程负责,完成后自动结束。阻塞只发生在fork阶段,一般时间很短
2)自动触发RDB的持久
1)使用save相关配置,如“save m n”。表示m秒内数据集存在n次修改 时,自动触发bgsave。
2)如果从节点执行全量复制操作,主节点自动执行bgsave生成RDB文件并发送给从节点,更多细节见6.3节介绍的复制原理。
3)执行debug reload命令重新加载Redis时,也会自动触发save操作。
4)默认情况下执行shutdown命令时,如果没有开启AOF持久化功能则 自动执行bgsave。
bgsave是主流的触发RDB持久化方式
(下图来源:https://www.jianshu.com/p/d3ba7b8ad964)
1)执行bgsave命令,Redis父进程判断当前是否存在正在执行的子进 程,如RDB/AOF子进程,如果存在bgsave命令直接返回。
2)父进程执行fork操作创建子进程,fork操作过程中父进程会阻塞,通 过info stats命令查看latest_fork_usec选项,可以获取最近一个fork操作的耗时,单位为微秒
3)父进程fork完成后,bgsave命令返回“Background saving started”信息并不再阻塞父进程,可以继续响应其他命令。
4)子进程创建RDB文件,根据父进程内存生成临时快照文件,完成后 对原有文件进行原子替换。执行lastsave命令可以获取最后一次生成RDB的 时间,对应info统计的rdb_last_save_time选项。
5)进程发送信号给父进程表示完成,父进程更新统计信息,具体见 info Persistence下的rdb_*相关选项。
RDB文件的处理
保存:RDB文件保存在dir配置指定的目录下,文件名通过dbfilename配 置指定。可以通过执行config set dir{newDir}和config set dbfilename{newFileName}运行期动态执行,当下次运行时RDB文件会保存到新目录。
rdb保存的文件时dump.rdb都是在配置文件中配置的
测试:只要60s内修改了5次key,就会触发rdb操作
触发机制:
1.满足规则,自动触发
2.推出redis也会产生rdb文件
回复rdb文件:
1.只需要将rdb文件放到redis启动目录即可redis启动的时候会自动检查dump.rdb回复其中的数据
2.查看位置
config get dir#如果该目录不存在这个dump.rdb文件,启动时就会自动恢复其中的数据
RDB的优点:
1.RDB是一个紧凑压缩的二进制文件,代表Redis在某个时间点上的数据 快照。非常适用于备份,全量复制等场景。比如每6小时执行bgsave备份, 并把RDB文件拷贝到远程机器或者文件系统中(如hdfs),用于灾难恢复。
2.Redis加载RDB恢复数据远远快于AOF的方式。
RDB的缺点:
1.RDB方式数据没办法做到实时持久化/秒级持久化。因为bgsave每次运 行都要执行fork操作创建子进程,属于重量级操作,频繁执行成本过高。
2.RDB文件使用特定二进制格式保存,Redis版本演进过程中有多个格式 的RDB版本,存在老版本Redis服务无法兼容新版RDB格式的问题。
3.针对RDB不适合实时持久化的问题,Redis提供了AOF持久化方式来解决
这个文件通常会备份
AOF(Append Only File)
AOF(append only file)持久化:以独立日志的方式记录每次写命令, 重启时再重新执行AOF文件中的命令达到恢复数据的目的。AOF的主要作用 是解决了数据持久化的实时性,目前已经是Redis持久化的主流方式。一句话将我们所有的命令都记录下来。history,恢复的时候就把这个文件全部执行一遍
aof保存的文件是:appendonly.aof文件
append
默认为不开启把appendonly改为yes即可
重启redis即可生效
AOF和RDB同时开启,先加载RDB后加载AOF,参考配置 aof-use-rdb-preamble 默认为yes
如果aof文件有错误,这时候redis是启动不起来的,我们需要修复这个aof文件
redis给我们提供了一个工具:redis-check-aof --fix appendonly.aof
如果文件正常,重启就可以直接恢复了
重写规则
aof默认文件无限追加,文件会越来越大
如果aof文件大于64m,fork一个新的进程来将我们的文件进行重写
优点
1.每一次修改都同步,文件完整性更好
2.每秒同步一次可能会丢失一秒的数据
3.从不同步,效率最高
缺点
1.相对于数据文件来说,aof远远大于rdb,修复的速度比rgb慢
2.Aof是读写操作,运行效率慢,所以redis默认的配置就是rdb持久化
十七、redis发布订阅
通信,队列,发送者,订阅者
redis发布订阅是一种消息通信模式
角色:
1.消息发送者
2.频道
3.·消息订阅者
下图展示了频道 channel1 , 以及订阅这个频道的三个客户端 —— client2 、 client5 和 client1 之间的关系:
当有新消息通过 PUBLISH 命令发送给频道 channel1 时, 这个消息就会被发送给订阅它的三个客户端:
Redis 发布订阅命令
下表列出了 redis 发布订阅常用命令:
序号 | 命令及描述 |
---|---|
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 …]] 指退订给定的频道。 |
测试:
订阅端:
127.0.0.1:6379> SUBSCRIBE shy#订阅一个频道
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "shy"
3) (integer) 1
#等待读取推送信息
1) "message"#消息
2) "shy"#哪个频道的消息
3) "hi"#消息的具体内容
1) "message"
2) "shy"
3) "hello lol"
发送端:
127.0.0.1:6379> PUBLISH shy "hi"#发布者发布消息到频道
(integer) 1
127.0.0.1:6379> PUBLISH shy "hello lol"
(integer) 1
十八、redis主从复制*
在Redis集群中,让若干个Redis服务器去复制另一个Redis服务器,我们定义被复制的服务器为主服务器(master),而对主服务器进行复制的服务器则被称为从服务器(slave),这种模式叫做主从复制模式。
数据流向是单向的,只能是从master到slave
一个slave只能有一个master
主从复制的作用:
- 为数据提供多个副本,实现高可用,去数据冗余
- 实现读写分离(主节点负责写数据,从节点负责读数据,主节点定期把数据同步到从节点保证数据的一致性)
- 故障恢复
- 负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务,(即写redis数据时应用连接主节点,读redis数据时应连接从节点),分担服务器负载;尤其是在写少读多的情况下,通过多个节点分担读负载,可以大大提高redis服务器的并发量。
- 高可用基石
主从复制,读写分离!80% 的情况下都在进行读!减缓服务器的压力!架构常用!一主二从!
只有一台redis一宕机就完了,一主而从
单台redis最大使用内存不因该超过20g,超了就搭建集群。真实项目中不可能只用一个
环境配置
只配置从库,不配置主库
127.0.0.1:6379> info replication#查看当前库的信息
# Replication
role:master#角色 master
connected_slaves:0 #没有从机
master_replid:6255fcad941978dfa102d2cd1dad43efda4aff01
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
复制三个配置文件,修改对应的信息
1.端口
2.pid名字
3.日志名字
4.dump.rdb名字
修改完毕后启动三个redis服务,可以通过进程信息查看~:ps -ef|grep redis
一主二从
默认情况下,每台redis服务器都是主节点,一般情况下,只配置从机就好!
主:79,从:80,81
从机中配置
127.0.0.1:6380> SLAVEOF 127.0.0.1 6379 #SLAVEOF host 6379 找谁当主机
OK
127.0.0.1:6380> info replication
# Replication
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:14
slave_priority:100
slave_read_only:1
replica_announced:1
connected_slaves:0
master_failover_state:no-failover
master_replid:a6d688356015f608d02880cb730b52d8610f7b4b
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:14
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:14
#在主机中查看从机信息
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:1#多了从机的配置
slave0:ip=127.0.0.1,port=6380,state=online,offset=42,lag=0#从机信息
master_replid:a6d688356015f608d02880cb730b52d8610f7b4b
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:42
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:42
如果两个都配置完毕:
真实的主从配置应该在配置文件中配置,这样的话是永久的,这里使用的是命令,所以还是暂时的
主机可以写,从机不能写只能读!主机中的所有信息都会被从机保存
主机写:
从机只能读取内容
测试:主机断开连接,这时候从机依旧连着主机,但是没有写操作,这时候从机依旧可以直接读取到主机写的信息
如果是使用命令行配置的主从,这个时候如果重启了,从机就会变成主机!只要变为从机,立马就会从主机中获取值!
复制原理
Slave启动成功连接到master后会发送一个sync同步命令
Master接到命令,启动后台的存盘过程,同时收集所有接收到的用于修改数据集命令,在后台进程执行完毕后,master将传送整个数据文件到slave,并完成一个同步
全量复制:而slave服务在接受到数据库文件数据后,将其存盘并加载到内存中
增量复制:Master继续将新的所有收集到的修改命令依次传给slave,完成同步
但是只要是重新连接master,一次完全同步(全量复制)将被自动执行,数据一定可以在从机中看到
如果主机断开连接,我们可以使用slaveof no one 让自己变成主机!,让其他的节点就可以手动连接到最新的这个主节点(手动) ,如果这时候主机连回来了。那就重新连接~
十九、哨兵模式
自动选举主机
哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立运行。其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。
一句话:后台监控主机是否有故障,如果故障了根据投票数自动将从库转换为主库
这里的哨兵有两个作用
- 通过发送命令,让Redis服务器返回监控其运行状态,包括主服务器和从服务器。
- 当哨兵监测到master宕机,会自动将slave切换成master,然后通过发布订阅模式通知其他的从服务器,修改配置文件,让它们切换主机。
然而一个哨兵进程对Redis服务器进行监控,可能会出现问题,为此,我们可以使用多个哨兵进行监控。各个哨兵之间还会进行监控,这样就形成了多哨兵模式。
用文字描述一下故障切换(failover)的过程。假设主服务器宕机,哨兵1先检测到这个结果,系统并不会马上进行failover过程,仅仅是哨兵1主观的认为主服务器不可用,这个现象成为主观下线。当后面的哨兵也检测到主服务器不可用,并且数量达到一定值时,那么哨兵之间就会进行一次投票,投票的结果由一个哨兵发起,进行failover操作。切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为客观下线。这样对于客户端而言,一切都是透明的。
测试
目前的状态:一主二从
1.配置哨兵配置文件sentinel.conf
#sentinel monitor 被监控的主机名称 host port 1
sentinel monitor myredis 127.0.0.1 6379 1
后面的数字1代表主机挂了,slave投票看让谁接替主机,票数最多的就会成为主机!
2.启动哨兵
[root@VM-0-6-centos bin]# redis-sentinel sconfig/sentinel.conf
15041:X 10 May 2021 17:23:19.720 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
15041:X 10 May 2021 17:23:19.720 # Redis version=6.2.2, bits=64, commit=00000000, modified=0, pid=15041, just started
15041:X 10 May 2021 17:23:19.720 # Configuration loaded
15041:X 10 May 2021 17:23:19.720 * monotonic clock: POSIX clock_gettime
_._
_.-``__ ''-._
_.-`` `. `_. ''-._ Redis 6.2.2 (00000000/0) 64 bit
.-`` .-```. ```\/ _.,_ ''-._
( ' , .-` | `, ) Running in sentinel mode
|`-._`-...-` __...-.``-._|'` _.-'| Port: 26379
| `-._ `._ / _.-' | PID: 15041
`-._ `-._ `-./ _.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' | https://redis.io
`-._ `-._`-.__.-'_.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' |
`-._ `-._`-.__.-'_.-' _.-'
`-._ `-.__.-' _.-'
`-._ _.-'
`-.__.-'
15041:X 10 May 2021 17:23:19.728 # Sentinel ID is 45f29efbd5a7d3216da053c9a00386cb7ba0ad79
15041:X 10 May 2021 17:23:19.728 # +monitor master myredis 127.0.0.1 6379 quorum 1
15041:X 10 May 2021 17:23:19.729 * +slave slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6379
15041:X 10 May 2021 17:23:19.736 * +slave slave 127.0.0.1:6381 127.0.0.1 6381 @ myredis 127.0.0.1 6379
Master节点断开,从从机中随机选一个做主机
如果主机此时回来了,只能归并到新的主机下,当作从机,这就是哨兵模式的规则
优点
1.哨兵集群,基于主从复制模式,所有的主从配置的优点,都有
2.主从可切换,故障可转移,系统的可用性会更好
3.哨兵模式就是主从模式的升级,手动到自动,更健壮
缺点
1.redis不好在线扩容,集群容量一旦达到上限,在线扩容十分麻烦
2.实现哨兵模式很麻烦,有很多选择
二十、redis的缓存穿透和雪崩
服务的高可用问题
在生产环境中,会因为很多的原因造成访问请求绕过了缓存,都需要访问数据库持久层,虽然对Redsi缓存服务器不会造成影响,但是数据库的负载就会增大,使缓存的作用降低
缓存穿透(查不到)
缓存穿透是指查询一个根本不存在的数据,缓存层和持久层都不会命中。在日常工作中出于容错的考虑,如果从持久层查不到数据则不写入缓存层,缓存穿透将导致不存在的数据每次请求都要到持久层去查询,失去了缓存保护后端持久的意义
比如秒杀
解决方案:
-
缓存空对象
- 缓存空对象:是指在持久层没有命中的情况下,对key进行set (key,null)
- 缓存空对象会有两个问题:第一,value为null 不代表不占用内存空间,空值做了缓存,意味着缓存层中存了更多的键,需要更多的内存空间,比较有效的方法是针对这类数据设置一个较短的过期时间,让其自动剔除。第二,缓存层和存储层的数据会有一段时间窗口的不一致,可能会对业务有一定影响。例如过期时间设置为5分钟,如果此时存储层添加了这个数据,那此段时间就会出现缓存层和存储层数据的不一致,此时可以利用消息系统或者其他方式清除掉缓存层中的空对象
-
布隆过滤器拦截
- 在访问缓存层和存储层之前,将存在的key用布隆过滤器提前保存起来,做第一层拦截,当收到一个对key请求时先用布隆过滤器验证是key否存在,如果存在在进入缓存层、存储层。可以使用bitmap做布隆过滤器。这种方法适用于数据命中不高、数据相对固定、实时性低的应用场景,代码维护较为复杂,但是缓存空间占用少。
- 布隆过滤器实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。
布隆过滤器
布隆过滤器:一种数据结构,是由一串很长的二进制向量组成,可以将其看成一个二进制数组。既然是二进制,那么里面存放的不是0,就是1,但是初始默认值都是0。
缓存击穿(量太大,缓存过期!)
微博
系统中存在以下两个问题时需要引起注意:
- 当前key是一个热点key(例如一个秒杀活动),并发量非常大。
- 重建缓存不能在短时间完成,可能是一个复杂计算,例如复杂的SQL、多次IO、多个依赖等。
在缓存失效的瞬间,有大量线程来重建缓存,造成后端负载加大,甚至可能会让应用崩溃。
解决方案:
1. 分布式互斥锁
只允许一个线程重建缓存,其他线程等待重建缓存的线程执行完,重新从缓存获取数据即可。set(key,value,timeout)
2. 永不过期
- 从缓存层面来看,确实没有设置过期时间,所以不会出现热点key过期后产生的问题,也就是“物理”不过期。
- 从功能层面来看,为每个value设置一个逻辑过期时间,当发现超过逻辑过期时间后,会使用单独的线程去更新缓
2种方案对比:
分布式互斥锁:这种方案思路比较简单,但是存在一定的隐患,如果在查询数据库 + 和 重建缓存(key失效后进行了大量的计算)时间过长,也可能会存在死锁和线程池阻塞的风险,高并发情景下吞吐量会大大降低!但是这种方法能够较好地降低后端存储负载,并在一致性上做得比较好。
“永远不过期”:这种方案由于没有设置真正的过期时间,实际上已经不存在热点key产生的一系列危害,但是会存在数据不一致的情况,同时代码复杂度会增大。
缓存雪崩
什么是应用服务雪崩
雪崩问题
分布式系统都存在这样一个问题,由于网络的不稳定性,决定了任何一个服务的可用性都不是 100% 的。当网络不稳定的时候,作为服务的提供者,自身可能会被拖死,导致服务调用者阻塞,最终可能引发雪崩连锁效应。
缓存雪崩
当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,也会给后端系统(比如DB)带来很大压力,造成数据库后端故障,从而引起应用服务器雪崩。
双11:停掉一些服务,保证主要的服务可用(服务降级)
雪崩效应产生的几种场景
- 流量激增:比如异常流量、用户重试导致系统负载升高;
- 缓存刷新:假设A为client端,B为Server端,假设A系统请求都流向B系统,请求超出了B系统的承载能力,就会造成B系统崩溃;
- 程序有Bug:代码循环调用的逻辑问题,资源未释放引起的内存泄漏等问题;
- 硬件故障:比如宕机,机房断电,光纤被挖断等。
- 数据库严重瓶颈,比如:长事务、sql超时等。
- 线程同步等待:系统间经常采用同步服务调用模式,核心服务和非核心服务共用一个线程池和消息队列。如果一个核心业务线程调用非核心线程,这个非核心线程交由第三方系统完成,当第三方系统本身出现问题,导致核心线程阻塞,一直处于等待状态,而进程间的调用是有超时限制的,最终这条线程将断掉,也可能引发雪崩;
解决方案
1.redis高可用?
2.限流降级?
3.数据预热?