Redis-4-Java操作Redis
一、存对象
这个写死了,key和value只能是String。可以序列化成json。存进去。取出来再转成对象
用RedisTemplate<k, v>就比较灵活,默认使用了jdk的序列化,序列化为二进制格式的文件,不太好看,但安全,实际还是不好用
注意k为object,所以假如存的1和"1",它俩不一样,取得数据不一样,而redis的key都为string,这里有冲突,后面自定义序列化器进行配置
注意:用框架传输,实体类必须序列化,包括redis和cloud
二、数据序列化方式
RedisTemplate<k, v>如下默认指定系统的序列化器,如何指定自定义序列化器
onMissingBean:如果没有自定义的类(序列化器),就用这个默认的jdk序列化器;有自定义序列化器,它就不生效了
自定义不同类型键和值得序列化器,这里可以直接将key改成String类型了,我们以后就按这种格式来写
三、具体API
自己去看,跟linux差不多,名字改一下,见名知意
加必须是数字类型,"3"这样的加不了
存不存在才能添加
四、缓存数据库
4.1自定义缓存实现(最灵活)
这个写到业务层,逻辑如下,模拟一下(第一次查询数据库存入缓存,第二次直接在缓存中拿)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hv0TmG7I-1604136849786)(C:/Users/CEO/AppData/Roaming/Typora/typora-user-images/image-20201030094543222.png)]
4.2声明式缓存(有限制)
类似于声明式事务
4.2.1添加pom依赖
4.2.2 启动类启用声明式缓存
4.2.3 使用
value:同一个类型的缓存有一个区域,类似于student前缀
key:就是存入缓存键值对的key
会默认将value和key用:连接起来,下图是第二次查询,直接从缓存中取,没有再走数据库查询的方法
业务方法和上面写法一样,这个注解会自动判断缓存中是否存在数据,有就直接取缓存数据,没有就执行数据库查询,存入缓存
底层是AOP,在核心业务基础上,在之前之后进行交叉业务逻辑的织入
4.2.4 缓存序列化器
缓存结果默认还是二进制
我们不自定义,用声明式缓存的话,还要按它的规则配缓存序列化器,自定义缓存格式,配置下面类
设置key和value的序列化
设置所有key的过期时间,只能统一设置
这时候原来的还是没变,因为底层是return new+新的对象,类似于string里的concat等方法,不改变原有结构,要用新的对象去接收
这样缓存的就是string和json格式了,我们就写查询代码即可
4.3 缓存注解使用
4.3.1 查询缓存@cacheable
@cacheable这个注解就是查询方法使用的
value是键的一部分,跟key组合在一起是整个键。
4.3.1.1 key支持EL表达式
4.3.1.2 底层有一个root对象,为方法名称(在key中)
root即为getStudentBySid
root可省略
4.3.1.3 满足条件的才存入缓存
4.3.2 添加和修改缓存@CachePut
给添加和修改方法使用
注意必须有返回值,会将方法的返回值存入缓存
如果返回null,那么存入缓存中的也为null,再次查询也会去缓存中取到,只不过为null,如下
执行修改,会覆盖
4.3.3 删除缓存@cacheEvict
给删除方法使用
有个参数beforeInvocation,默认false,先删除数据,后删除缓存
true,先删除缓存,再删除数据,不管删除数据有没有异常,缓存都删除了
有个属性是删除student空间中所有数据(正常是删除这个student空间里的sid对应的一条数据),但是删除也是以组为单位,就是以命名空间来删;同样的key:sid,下面可能有很多个组,但只能删除student空间里的数据
五、分布式锁
5.1 概念
不是频繁修改的数据,用的频率比较高的(热点数据),存到缓存中。经常修改的数据存的意义不大(得修改数据库还得修改缓存)
用缓存的目的是为了降低数据库的压力
5.1.1缓存穿透
解决办法:没有的数据我也存,过期时间设的很短
5.1.2 缓存雪崩(大量key失效)
解决办法:存大量数据的时候在失效时间的基础上增加一个随机值
5.1.3 缓存击穿
要先把一个key先管住,重点解决击穿的问题
5.2 操作锁
5.2.1 Jmeter
压力测试工具,点这两个启动,都可以
改语言
2秒内每次发100个,循环10次,就一共1000次
配置一个完整的请求路径
查看结果
5.2.2 压力测试出现的各种问题
5.2.2.1 一个网站服务器访问(单机高并发有问题)
这么加不会出错,无论怎样访问,因为redis的指令是单线程运行的,不会被打断
这样多线程访问就会出错,缺失加的次数
上面是单线程所以高并发是对的,下面的是多线程都在排队
看似很多线程在加,其实一堆人抢到了cpu,最终只加了一次
本应加1000,实际24
5.2.2.2 单机高并发+同步锁,数据安全
解决方法:单机加同步锁可以保证一个服务器的安全,但是效率低一些
5.2.2.2 集群环境+同步锁
集群环境加锁就无法保证了,synchronized只能保证一个虚拟机环境是安全的
用windows版的nginx来模拟集群环境:
- idea模拟多个服务器,先编辑再拷贝
- 配置nginx反向代理轮询
注意:nginx一定要右键管理员运行,否则运行不起来
- 集群访问同步锁
本应加1000,实际747,出现加的次数缺失
跨服务器,是两个虚拟机,同步锁只能保证当前虚拟机是安全的,别人的管不住
示意图:同步锁只能管住自己虚拟机的这500个是安全的,另外的500个管不住,这500个运行时,那500个也能过来取到数据,夺取cpu,最终造成数据缺失
注意:同步锁是只能锁一个进程,一个进程有多线程,但都属于一个进程,我们在jvm里模拟了两个服务器,是两个进程,但实际上只是一个虚拟机,而同步锁不能跨进程,已经锁不住了,实际上是真正的不同的服务器不同的虚拟机来访问,所以更锁不住了
5.2.2.3解决集群锁不住问题
解决方法:一个请求在操作时在redis里加个变量锁,操作未完成时,另一个请求过来时先判断,有锁就无法执行,操作完将锁删除,下一个来继续创建一个锁,依次同理。
这个锁的赋值要用setnx命令,有这个键就set不进去,没有才能赋锁的值
有个问题,你抢到cpu添加了锁,你可以进行一系列的操作,别人无法干扰,但是你突然挂掉了,锁没有删掉,所有其他线程在那挂着,也执行不了,抢不到cpu,这个问题怎么解决
解决:要设置锁的过期时间,一般不会超过5秒,到5秒就执行删除锁的代码
又随之出现问题:绝大多数代码5秒内都能执行完,极少数代码5秒还没执行完,超出一点点,但此时锁已经没了,如下图,8080超出了5秒,但是锁已经没了 ,代码执行完后它还要执行删锁的代码,而此时没有锁8081就进来了,8080就会把8081的锁删掉,删完8082又进来了。。。以此类推,这个问题怎么解决
解决方法:给锁加个id,先判断再删除,只能删除自己的锁
又随之出现问题:判断和删锁是两步,当一个线程代码执行完的时候发出判断指令,判断这个8080的锁还有,返回代码要再发出删除指令,而此时时间刚刚过了5秒,这个锁正好自动删除了,此时8081判断没有锁,就进来了并创建了自己的锁,此时8080已经执行删除的代码了,并不能判断是不是自己的锁,就又会把8081的锁删除了,然后8082又进来了,这个问题怎么解决
解决方法:让判断和删锁一起执行,redis是无法做到的
用lua脚本,让判断和删锁一起执行
详见5.2.3分布式锁实现
5.2.3 分布式锁实现(框架)
5.2.3.1 添加Redisson依赖
5.2.3.2 添加配置类
5.2.3.3 加锁解锁
线程锁锁不住,就不要了,用这个redisson取锁,保证两个服务器请求1000次操作
集群访问1000次,已经成功锁住了
原理:锁里面存住了信息,必须等我用完了,你才能用。
存了线程id作为锁的id
加个线程间隔,就能看到锁
怎么保证安全:
极端情况下会发生什么问题: