Reids
Nosql解决session问题,解决IO压力。
适用场景:对于数据高并发的读写、海量数据的读写、对数据高可拓展性的。
# 查看版本
gcc --version
# 解压缩文件
tar -zvxf redis-6.2.1.tar.gz
cd redis-6.2.1
# 编译
make all
make install
# 后台启动
cp redis.conf /etc/redis.conf
# 后台启动设置daemonize no为yes
key键操作
# 查看当前库所有key
keys *
# 判断某个key是否存在
exists key
# 查看你的key是什么类型
type key
# 删除指定的key数据
del key
# 根据value选择非阻塞删除
unlink key
string操作
get <key>查询对应键值
append <key><value>将给定的<value>追加到原值的末尾
strlen <key>获得值的长度
setnx <key><value>只有在key不存在时 设置key的值
incr <key>将key中存储的数字值增1 <原子操作>
decr <key>将key中存储的数字减1
incrby/decrby <key><步长>将key中存储的数字值增减。自定义步长。
mset <key1><value1><key2><value2>
同时设置一个或多个key-value对
mget <key1><key2><key3>
同时获取一个或多个value
msetnx <key1><value1><key2><value2>
同时设置一个或多个key-value对,当且仅当所有给定key都不存在。
原子性,有一个失败则都失败
getrange <key><起始位置><结束位置>
获得值的范围,类似java中的substring,前包,后包
setrange <key><起始位置><value>
设置指定范围的值
setex <key><过期时间><value>
设置过期时间
getset <key> <value>
设置新值的同时获得旧值
List操作
底层是双向链表,快速链表quicklist,数据量较少时ziplist,数据量增大时多个ziplist结合起来组成quicklist,避免指针的空间浪费。
lpush/rpush <key><value1><value2><value3>从左边/右边插入一个或多个值。
lpop/rpop <key>从左边/右边吐出一个值。值在健在,值光键亡。
rpoplpush <key1><key2>从<key1>列表右边吐出一个值,插到<key2>列表左边。
lrange <key><start><stop> 0左边第一个 -1右边第一个
按照索引下标获得元素(从左到右)
lindex <key><index>按照索引下标获得元素
llen <key>获得列表长度
linsert<key> before<value><newvalue>在<value>的后面插入<newvalue>插入值
lrem <key><n><value>从左边删除n个value(从左到右)
lset <key><index><value>将列表key下标为index的值替换成value
集合set
无序集合,自动排重
底层是一个value为null的hash表
常用命令
sadd <key><value1><value2>
将一个或多个member元素加入到集合key中,已经存在的member元素将被忽略。
smembers <key>取出该集合的所有值
sismember <key><value>判断集合<key>是否为含有该<value>值,有1,没有0
scard <key>返回该集合的元素个数
srem <key><value1><value2><value3>删除集合中的某个元素
spop <key>随机从该集合中吐出一个值
srandmember <key><n>随机从该集合中取出n个值。不会从集合中删除。
smove<source><destination>value把集合中一个值从一个集合移动到另一个集合
sinter <key1><key2>返回两个集合的交集元素
sunion <key1><key2>返回两个集合的并集元素
sdiff <key1><key2>返回集合的差集元素(key1中的,不包含key2中的)
Hash哈希
键值对集合,是一个string类型的field和value的映射表,hash特别适合用于存储对象。
类似Java里面的Map<String,Object>
hset <key><field><value>给<key>集合中的<field>键赋值<value>
hget <key1><field>从<key1>集合<field>取出value
hmset <key1><field1><value1><field2><value2>批量设置
hexists <key1><field>查看哈希表key中,给定field是否存在
hkeys <key>列出该hash集合的所有field
hvals <key>列出该hash集合的所有value
hincrby <key><field><indrement>为哈希表key中的域field的值加上增量1 -1
hsetnx <key><field><value>将哈希表key中的域field的值设置为value,当且仅当域field不存在
有序集合zset
zadd <key><score1><value1><score2><value2>
将一个或多个member元素及其score值加入到有序集key当中
zrange <key><start><stop> [WITHSCORES]
返回有序集key中,下标在<start><stop>之间的元素
带WITHSCORES,可以让分数一起和值返回到结果集。
zrangebyscore key minmax [withscores][limit offset count]
返回有序集key中,所有score值介于min和max之间的成员
有序集成员按score值递增次序排列
zrevrangebyscore key maxmin[withscores][limit offset count]
同上从大到小递减
zincrby <key><increment><value> 为元素的score加上增量
zrem <key><value>删除该集合下,指定值的元素
zcount <key><min><max>统计该集合,分数区间内的元素个数
zrank <key><value>返回该值在集合中的排名,从0开始。
外网访问
- 进入config文件
- bind行注释掉
- protected-mode yes改成no
Redis的发布和订阅
消息的通讯模式
- 客户端可以订阅频道
- 当给这个频道发布消息后,消息就会发送给订阅的客户端
发布订阅命令实现
-
打开一个客户端订阅channell
subscribe channel1
-
打开另一个客户端,给channel1发布消息hello
publish channel1 hello
新数据类型Bitmaps
实例:每个用户是否访问过网站存放在Bitmaps中,将访问的用户记做1,没有访问的用户记做0,用偏移量作为用户的id。
setbit users:20201106 10 1
users:20201106代表20201106这天独立访问用户的bitmaps
getbit获取bitmaps
bitcount <key>[start end]统计字符串从start字节到end字节比特值为1的数量
bitop 计算任意一天都访问过网站的用户数量
bitmap存储空间占用很少,数据量较少的时候可以选用set,bitmap大量的数据为0,相反占用空间会大。
新数据类型HyperLogLog
基数问题
- mysql中使用distinct count计算不重复个数
- reids的hash、set、bitmaps等数据结构来处理
当数据量很大的时候,不适合。
HyperLogLog只需要12KB的内存就可以计算接近2^64个不同元素的基数。
pfadd <key> <value1><value2>添加元素
pfcount program 返回数量
pfmerge <newkey><key1><key2> 合并操作
新数据类型Geographic
对GEO的支持,提供了经纬度设置,查询,范围查询,距离查询,经纬度Hash等常见操作。
geoadd <key><longitude><latitude><member>[longitude latitude member]
添加地理位置(经度,纬度,名称)
geoadd china:city 121.47 31.229 shanghai
geopos china:city shanghai
取经纬度
geodist china:city beijing shanghai km
获取两个位置之间的直线距离
georadius china:city 110 30 1000 km
取该经纬度周边1000km的城市
Jedis操作
需要外网访问操作并关闭防火墙
systemctl status firewalld
# shutdown
systemctl shutdown firewalld
Jedis jedis = new Jedis("ip",port);
SpringBoot整合Redis
yaml配置
#redis集群
spring:
redis:
host: 127.0.0.1
port: 6379
timeout: 20000
# 集群环境打开下面注释,单机不需要打开
# cluster:
# 集群信息
# nodes: xxx.xxx.xxx.xxx:xxxx,xxx.xxx.xxx.xxx:xxxx,xxx.xxx.xxx.xxx:xxxx
# #默认值是5 一般当此值设置过大时,容易报:Too many Cluster redirections
# maxRedirects: 3
pool:
max-active: 8
min-idle: 0
max-idle: 8
max-wait: -1
password:
秒杀并发模拟案例
使用工具ab模拟测试
CentOS6默认安装
CentOS7需要手动安装
AOF
日志的形式来记录每个写操作,将Redis执行过的写指令记录下来,只需追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据。redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。
AOF和RDB同时开启,默认取AOF数据
主从复制
唯一主服务器写入 复制到多个从服务器,多个主从复制关系的小组组成集群,集群解决主机宕机问题。master/slave机制
- 读写分离
- 容灾快速回复
Redis集群
扩容问题
并发写操作,集群分担写压力
无中心化集群,任何一台可以作为集群的入口,互相传递请求。
实现水平扩容
缓存穿透
- 应用服务器压力变大
- redis命中率降低
- redis查询不到
- 出现非正常url访问,服务器攻击
- 一直查询数据库
解决方案
-
对空值缓存:如果一个查询返回的数据为空(不管数据是否不存在),我们仍然把这个空结果(null)进行缓存,设置空结果的过期时间会很短,最长不超过五分钟。
-
设置可访问的名单(白名单):
使用bitmaps类型定义一个可以访问的名单,命大id作为bitmaps的偏移量,每次访问和bitmap里面的id进行比较,如果访问id不在bitmaps里面,进行拦截,不允许访问。
-
采用布隆过滤器:Bloom Filter时1970年由布隆提出的,它实际上是一个很长的二进制向量和一系列随即映射函数(哈希函数)。
-
进行实时监控:当发下reids命中率开始急速降低,需要排查访问对象和访问的数据,和运维人员配合,可以设置黑名单限制服务。
缓存击穿
现象
- 数据库访问压力瞬间变大
- redis里面没有出现大量key过期
- redis正常运行
redis某个key过期了,大量访问使用这个key
- 预先设置热门数据:在redis高访问之前,把一些热门数据提前存入到redis里面,加大这些热门数据key的时长。
- 实时调整:现场监控哪些数据热门,实施调整key的过期时间。
- 使用锁:
- 在缓存失效的时候(判断拿出来的值为空),不是立即去load db。
- 先使用缓存工具的某些带成功操作返回值的操作(比如Redis的SETNX)去set一个mutex key
- 当操作返回成功时,再进行load db的操作,并回设缓存,最后删除mutex。
- 当操作返回失败,证明有线程再load db,当前线程睡眠一段时间再重试整个get缓存的方法。
缓存雪崩
数据库压力变大服务器崩溃
在极少的时间段,查询大量key的集中过期情况
-
构建多级缓存架构:nginx缓存+redis缓存+其他缓存(ehcache等)
-
使用锁或队列
-
设置过期标志更新缓存
-
将缓存失效时间分散开
分布式锁
JVM无法跨系统进行锁的控制
分布式锁主流解决方案
- 基于数据库实现分布式锁
- 基于缓存
- 基于Zookeeper
每一种分布式锁解决方案都有各自的优缺点:
- 性能:redis最好
- 可靠性:zookeeper最高
setnx users 10
setnx users 20
执行失败必须执行
del users
才可继续执行
setnx users 20
无限等待->设置锁过期时间
expire users 20
设置过期时间20s
总结:
-
使用setnx上锁,通过del释放锁
-
无限等待设置锁的过期时间,自动释放
-
上锁之后无法设置过期时间
同时设置
set users 10 nx ex 12 上锁并设置过期时间12s
分布式锁(UUID防止误删)
问题:多个服务器之间当出现问题会误删他人的锁
a上锁之后 服务器出现问题 锁过期自动释放
b获得锁上锁之后 锁未过期 a服务器好了之后执行释放锁操作,将b的锁释放了
- uuid表示不同的操作
- 释放锁的时候判断uuid和要释放锁的uuid是否一样,一样则释放,不一样则不释放。
分布式锁(LUA保证原子性)
a上锁——具体操作——释放锁
- uuid比较一样
- 正要删除的时候,还没有删除,锁到了过期的时间。
b锁——具体操作——a直接释放了b的锁
原子性操作问题
LUA脚本将判断uuid变成原子性操作
// 释放锁del
String script = "if reids.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
// 设置LUA脚本返回的数据类型
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
// 设置lus脚本返回类型为Long
redisScript.setResultType(Long.class);
redisScript.setScriptText(script);
redisTemplate.execute(redisScript, Arrays.asList("lock"),uuid);
不能发生死锁
任何时候只有一个客户端有锁
加锁和解锁必须有原子性
加锁和解锁必须是同一个客户端