Redis-4-Java操作Redis

一、存对象

这个写死了,key和value只能是String。可以序列化成json。存进去。取出来再转成对象

image-20201029142531676

image-20201029142753182

用RedisTemplate<k, v>就比较灵活,默认使用了jdk的序列化,序列化为二进制格式的文件,不太好看,但安全,实际还是不好用

注意k为object,所以假如存的1和"1",它俩不一样,取得数据不一样,而redis的key都为string,这里有冲突,后面自定义序列化器进行配置

image-20201029143428714

注意:用框架传输,实体类必须序列化,包括redis和cloud

image-20201029142857962

二、数据序列化方式

RedisTemplate<k, v>如下默认指定系统的序列化器,如何指定自定义序列化器

onMissingBean:如果没有自定义的类(序列化器),就用这个默认的jdk序列化器;有自定义序列化器,它就不生效了

image-20201029143847138

自定义不同类型键和值得序列化器,这里可以直接将key改成String类型了,我们以后就按这种格式来写

image-20201029145023516

三、具体API

自己去看,跟linux差不多,名字改一下,见名知意

加必须是数字类型,"3"这样的加不了

image-20201029172631643

存不存在才能添加

image-20201029172941554

image-20201029173201608

image-20201029173648200

image-20201029174101346

image-20201029174623781

四、缓存数据库

4.1自定义缓存实现(最灵活)

image-20201030093624565

这个写到业务层,逻辑如下,模拟一下(第一次查询数据库存入缓存,第二次直接在缓存中拿)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hv0TmG7I-1604136849786)(C:/Users/CEO/AppData/Roaming/Typora/typora-user-images/image-20201030094543222.png)]

image-20201030095941837

image-20201030100804463

4.2声明式缓存(有限制)

类似于声明式事务

4.2.1添加pom依赖

image-20201030100633612

4.2.2 启动类启用声明式缓存

image-20201030100723251

4.2.3 使用

value:同一个类型的缓存有一个区域,类似于student前缀

key:就是存入缓存键值对的key

会默认将value和key用:连接起来,下图是第二次查询,直接从缓存中取,没有再走数据库查询的方法

业务方法和上面写法一样,这个注解会自动判断缓存中是否存在数据,有就直接取缓存数据,没有就执行数据库查询,存入缓存

底层是AOP,在核心业务基础上,在之前之后进行交叉业务逻辑的织入

image-20201030101446278

4.2.4 缓存序列化器

缓存结果默认还是二进制

image-20201030101919470

我们不自定义,用声明式缓存的话,还要按它的规则配缓存序列化器,自定义缓存格式,配置下面类

image-20201030102113277

设置key和value的序列化

image-20201030102610299

设置所有key的过期时间,只能统一设置

image-20201030113152304

这时候原来的还是没变,因为底层是return new+新的对象,类似于string里的concat等方法,不改变原有结构,要用新的对象去接收

image-20201030102949543

这样缓存的就是string和json格式了,我们就写查询代码即可

4.3 缓存注解使用

4.3.1 查询缓存@cacheable

@cacheable这个注解就是查询方法使用的

value是键的一部分,跟key组合在一起是整个键。

4.3.1.1 key支持EL表达式

image-20201030105046363

4.3.1.2 底层有一个root对象,为方法名称(在key中)

image-20201030105450545

root即为getStudentBySid

image-20201030105516284

root可省略

image-20201030105604903

4.3.1.3 满足条件的才存入缓存

image-20201030105824333

4.3.2 添加和修改缓存@CachePut

给添加和修改方法使用

注意必须有返回值,会将方法的返回值存入缓存

image-20201030110553105

如果返回null,那么存入缓存中的也为null,再次查询也会去缓存中取到,只不过为null,如下

image-20201030110846464

执行修改,会覆盖

image-20201030111231534

4.3.3 删除缓存@cacheEvict

给删除方法使用

image-20201030111748440

有个参数beforeInvocation,默认false,先删除数据,后删除缓存

true,先删除缓存,再删除数据,不管删除数据有没有异常,缓存都删除了

image-20201030111924985

有个属性是删除student空间中所有数据(正常是删除这个student空间里的sid对应的一条数据),但是删除也是以组为单位,就是以命名空间来删;同样的key:sid,下面可能有很多个组,但只能删除student空间里的数据

image-20201030112737110五、分布式锁

5.1 概念

不是频繁修改的数据,用的频率比较高的(热点数据),存到缓存中。经常修改的数据存的意义不大(得修改数据库还得修改缓存)

用缓存的目的是为了降低数据库的压力

image-20201030140958501

5.1.1缓存穿透

image-20201030141044909

解决办法:没有的数据我也存,过期时间设的很短

5.1.2 缓存雪崩(大量key失效)

image-20201030141437877

解决办法:存大量数据的时候在失效时间的基础上增加一个随机值

5.1.3 缓存击穿

image-20201030141640251

image-20201030141653441

要先把一个key先管住,重点解决击穿的问题

5.2 操作锁

5.2.1 Jmeter

压力测试工具,点这两个启动,都可以

image-20201030144353903

改语言

image-20201030144458228 image-20201030144541987

image-20201030144656971

2秒内每次发100个,循环10次,就一共1000次

image-20201030144820712

配置一个完整的请求路径

image-20201030151544221

image-20201030144930226

查看结果

image-20201030144954214

5.2.2 压力测试出现的各种问题

5.2.2.1 一个网站服务器访问(单机高并发有问题)

这么加不会出错,无论怎样访问,因为redis的指令是单线程运行的,不会被打断

image-20201031142831684

这样多线程访问就会出错,缺失加的次数

image-20201030151929255

image-20201030152013237

上面是单线程所以高并发是对的,下面的是多线程都在排队

image-20201030152234087

看似很多线程在加,其实一堆人抢到了cpu,最终只加了一次

image-20201030152350032

本应加1000,实际24

image-20201031144025837

5.2.2.2 单机高并发+同步锁,数据安全

解决方法:单机加同步锁可以保证一个服务器的安全,但是效率低一些

image-20201030152844677

5.2.2.2 集群环境+同步锁

集群环境加锁就无法保证了,synchronized只能保证一个虚拟机环境是安全的

image-20201030162733905

用windows版的nginx来模拟集群环境:

  1. idea模拟多个服务器,先编辑再拷贝
image-20201031130722382 image-20201031132524576 image-20201031132453978 image-20201031132431654
  1. 配置nginx反向代理轮询
image-20201031133859745

注意:nginx一定要右键管理员运行,否则运行不起来

  1. 集群访问同步锁
image-20201031143626016

本应加1000,实际747,出现加的次数缺失

跨服务器,是两个虚拟机,同步锁只能保证当前虚拟机是安全的,别人的管不住

image-20201031143804111

示意图:同步锁只能管住自己虚拟机的这500个是安全的,另外的500个管不住,这500个运行时,那500个也能过来取到数据,夺取cpu,最终造成数据缺失

image-20201031144428047

注意:同步锁是只能锁一个进程,一个进程有多线程,但都属于一个进程,我们在jvm里模拟了两个服务器,是两个进程,但实际上只是一个虚拟机,而同步锁不能跨进程,已经锁不住了,实际上是真正的不同的服务器不同的虚拟机来访问,所以更锁不住了

5.2.2.3解决集群锁不住问题

解决方法:一个请求在操作时在redis里加个变量锁,操作未完成时,另一个请求过来时先判断,有锁就无法执行,操作完将锁删除,下一个来继续创建一个锁,依次同理。

image-20201031163211659

这个锁的赋值要用setnx命令,有这个键就set不进去,没有才能赋锁的值

image-20201031163357795

有个问题,你抢到cpu添加了锁,你可以进行一系列的操作,别人无法干扰,但是你突然挂掉了,锁没有删掉,所有其他线程在那挂着,也执行不了,抢不到cpu,这个问题怎么解决

解决:要设置锁的过期时间,一般不会超过5秒,到5秒就执行删除锁的代码

又随之出现问题:绝大多数代码5秒内都能执行完,极少数代码5秒还没执行完,超出一点点,但此时锁已经没了,如下图,8080超出了5秒,但是锁已经没了 ,代码执行完后它还要执行删锁的代码,而此时没有锁8081就进来了,8080就会把8081的锁删掉,删完8082又进来了。。。以此类推,这个问题怎么解决

image-20201031171050898

解决方法:给锁加个id,先判断再删除,只能删除自己的锁

又随之出现问题:判断和删锁是两步,当一个线程代码执行完的时候发出判断指令,判断这个8080的锁还有,返回代码要再发出删除指令,而此时时间刚刚过了5秒,这个锁正好自动删除了,此时8081判断没有锁,就进来了并创建了自己的锁,此时8080已经执行删除的代码了,并不能判断是不是自己的锁,就又会把8081的锁删除了,然后8082又进来了,这个问题怎么解决

解决方法:让判断和删锁一起执行,redis是无法做到的

用lua脚本,让判断和删锁一起执行

image-20201030160849558

详见5.2.3分布式锁实现

5.2.3 分布式锁实现(框架)

5.2.3.1 添加Redisson依赖

image-20201030163135732

5.2.3.2 添加配置类

image-20201030163802487

5.2.3.3 加锁解锁

线程锁锁不住,就不要了,用这个redisson取锁,保证两个服务器请求1000次操作

image-20201030163925438

集群访问1000次,已经成功锁住了

image-20201031145910073

原理:锁里面存住了信息,必须等我用完了,你才能用。

存了线程id作为锁的id

image-20201030164517979

加个线程间隔,就能看到锁

image-20201031150453075 image-20201031150435400

怎么保证安全:

极端情况下会发生什么问题:

©️2020 CSDN 皮肤主题: 深蓝海洋 设计师:CSDN官方博客 返回首页