文章目录
Redis
一、概述
1.1 NoSQL
- NoSQL=Not Only SQL,泛指非关系型数据库
- 方便扩展,数据之间无关性
- 大数据量高性能(Redis一秒可写8万次,可读11万次)
- 数据类型多样型,不需要事先设计数据库(随取随用)
1.2 Redis
- 是开源的使用C语言编写的、支持网络、可基于内存亦可持久化的日志型、key-value数据库,并提供多种语言的API。
- Redis默认有16个数据库[0-15],默认使用0号数据库
# 切换数据库
select [数据库编号]
# 查看数据库大小
DBSIZE
# 查看数据库所有key
keys *
# 清除当前数据库
flushdb
# 清除全部数据库内容
FLUSHALL
二、Redis数据类型
2.1 String
# 设置key
set key
set name ysw
# 判断key是否存在
EXISTS name
# 移除key
move name 1
# 设置key的过期时间,单位为秒
EXPIRE name 10
# 查看当前key的剩余时间
ttl name
# 获取key的值
get [key]
# 查看当前key的类型
type name
# 追加字符串,如果当前key不存在,则相当于set key
APPEND key1 "hello"
# 获取当前key的长度
STRLEN key1
# value自增1
set views 0
incr views
# value自减1
decr views
# 设置步长
INCRBY views 10
DECRBY views 10
# 字符串范围 range
GETRANGE [key] 0 3 # 截取字符串 [0,3]
2.2 List
# 将一个或多个值插入到列表头部
LPUSH list [值]
# 获取列表一定范围内的值
LRANGE list 0 1
# 将一个或多个值插入到列表尾部
Rpush list [value]
# 移除列表中的第一个值
Lpop list
# 移除列表中的最后一个元素
Rpop list
# 通过下标获得列表中的某一个值
lindex list [下标号]
# 返回列表的长度
Llen list
# 移除list集合中指定个数的value,精确匹配
lrem list 1 [值]
# 更新操作
lset list [下标] [值]
2.3 Set
- set中不能添加重复元素
# set集合中添加元素
sadd myset "hello"
# 查看指定set中的所有值
SMEMBERS myset myset
# 判断某一个值是否在set中
SISMEMBER myset hello
# 获取几个中的元素个数
scard myset
# 移除set中的指定元素
srem myset hello
# 随机抽选出一个元素
SRANDMEMBER myset
# 随机抽选出指定个数的元素
SRANDMEMBER myset 2
# 随机删除集合中的元素
spop myset
# 将一个指定的值移动到另一个集合中
smove myset myset2 "hello"
2.4 Hash
- Map集合,key-<key-value>
# set一个具体的key-value
hset myhash [key] [value]
# 获取值
hget myhash [key]
# 获取多个字段值
hmget myhash [key] [key] [key]
# 获取全部数据
hgetall myhash
# 判断哈希中的key是否存在
HEXISTS myhash [key]
# 获取所有的key
hkeys myhash
# 获取所有的值
hvals myhash
2.5 Zset(有序集合)
- 在set的基础上增加了一个值,zset k1 score1 v1
# 添加元素
zadd myset 1 [value]
zadd myset 2 [value]
zadd myset 3 [value]
……
三、事务
- Redis事务的本质是一组命令的集合,一个事务中的所有命令都会被序列化,在事务执行过程中会按照顺序执行
- 一次性、顺序性、排他性、执行一系列命令
- Redis事务没有隔离级别的概念
- 所有的命令在事务中并没有直接去执行,只有发起执行命令的时候才会被执行
- Redis单条命令保存原子性,但是事务不保证原子性
# 开启事务
multi
# 命令入队
set k1 v1
get k1
……
# 执行事务
exec
# 取消事务
DISCARD
四、乐观锁和悲观锁
五、Redis持久化
1. RDB
1.1 概述
在指定的时间间隔内将内存中的数据集快照写入磁盘,它恢复时是将快照文件直接读到内存中
- 时间间隔:每隔一定的时间
- 快照:当前时间点的数据
2. AOF
2.1 概述
以日志的形式来记录每次写操作,将Redis执行过的所有命令记录下来(读操作不记录),只许追加文件但不可以改写文件,Redis启动之初会读取该文件重新构建数据,换言之,Redis启动就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复。
- AOF默认不开启
- AOF和RDB同时开启时,系统默认使用AOF的数据
2.2 流程
- 客户端的请求写命令会被append追加到AOF缓冲区
- AOF缓冲区根据AOF持久化策略【always、everysec、no】将操作sync同步到磁盘的AOF文件
- AOF文件大小超过重写策略或手动重写时,会对AOF文件rewrite重写,压缩AOF文件容量
- 当Redis重启时,会重新加载AOF文件中的写操作达到数据恢复的目的
六、主从复制
1. 概述
主机数据更新后根据配置和策略,自动同步到备机的master/slaver机制,Master以写为主,Slave以读为主
- 读写分离,性能扩展
- 容灾快速恢复
2. 复制原理
- 当从服务器连接上主服务器后,从服务器向主服务器发送进行数据同步的消息
- 主服务器收到同步消息后,将数据进行持久化到RDB文件中
- 主服务器将RDB文件发送到从服务器,从服务器加载该RDB文件
- 每次主服务器进行写操作后,和从服务器进行数据同步
3. 哨兵模式
后台监控主机是否故障,如果故障了则根据投票数自动将从库转换为主库
- 创建sentinel.conf文件
- 文件中配置:sentinel monitor [mymaster] [主机地址] [端口号] 1
- mymaster:监控对象的服务器名称,1表示至少有多少个哨兵同意迁移的数量
- 启动哨兵:redis-sentinel [配置文件路径]
4. 其他
- 主从复制会有延迟
七、应用问题
1. 缓存穿透
- 特点:
- 应用服务器压力变大
- Redis命中率降低
- 一直查询数据库
- 产生的条件
- Redis查询不到数据库,无法同步数据到缓存
- 出现很多分正常的url访问
- 解决方案
- 对null值做缓存,设置空结果的过期时间很短,最长不超过5min
- 设置白名单访问,使用bitmaps类型定义一个访问白名单,名单id作为bitmaps的偏移量,每次访问时都和bitmaps中的id进行比较
- 布隆过滤器
- 对Redis进行实时监控,当发现Redis命中率降低时,则需要排查访问对象和访问数据,需要运维人员设置黑名单限制
2. 缓存击穿
- 特点
- 数据库访问压力瞬间变大
- Redis里面没有出现大量key过期,只是某个key过期了
- Redis正常运行
- 产生的条件
- Redis中某个key过期了,但是该key又是热门数据
- 解决方案
- 预先设置热门数据,加大热门数据key的时长
- 实时对key的过期时间进行调整
- 使用锁:在缓存失效时,不是立即去加载数据,而是先使用缓存工具的某些带成功操作返回值的操作,当操作返回成功时,再加载数据
3. 缓存雪崩
- 特点
- 数据库压力变大
- 产生的条件
- 极少时间段,大量key集中过期
- 解决方案
- 构建多级缓存架构
- 使用锁或队列:使用锁或者队列,保证不会有大量的线程对数据库进行一次性的读写操作
- 设置过期标志更新缓存:设置提前量,如果过期会触发通知另外的线程在后台去更新实际key的缓存
- 将缓存失效时间分散开
4. 分布式锁
4.1 概述
随着业务发展的需要,单机部署已经演化为分布式集群系统。由于分布式系统多线程、多进程并且分布在不同机器上,这将使原机器部署情况下的并发控制锁策略失效,单纯的JavaAPI并不能提供分布式锁的能力。为了解决这个问题则需要一种跨JVM的互斥机制来控制共享资源的访问,从而引出了 分布式锁。
4.2 基于Redis实现
-
使用setnx和del命令实现
- 使用setnx上锁,通过del释放锁
- 通过设置key过期时间,自动释放锁
- 上锁的同时设置过期时间:setnx users 10 nx ex 12
-
Java代码
public class RedisTest { @Autowired private RedisTemplate redisTemplate; /** * @description 分布式锁测试 * @date 10:24 2022/3/11 * @Param [] * @Return void */ public void testLock() { String uuid = UUID.randomUUID().toString(); //获取锁 Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 5, TimeUnit.SECONDS); if (lock) {//获取锁成功 //获取值 Object value = redisTemplate.opsForValue().get("num"); if (StringUtils.isEmpty(value)) { return; } //将值转为int int num = Integer.parseInt(value + ""); //操作数据 redisTemplate.opsForValue().set("num", ++num); //释放锁 String lockUUID = (String) redisTemplate.opsForValue().get("lock"); if (lockUUID.equals(uuid)) { redisTemplate.delete("lock"); } } else {//获取锁失败 try { //间隔0.1s重新获取锁 Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } }
- 确保锁可用,需要满足4个条件
- 互斥性。在任一时刻,只有一个客户端能持有锁
- 不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动释放锁,也能保证后续其他客户端能加锁
- 加锁和解锁必须是同一个客户端
- 加锁和解锁必须具有原子性
- 确保锁可用,需要满足4个条件