缓存、分布式锁、Redisson、布隆过滤器

概述

目的

  • 1.降低请求的处理时间,进而提高服务的吞吐能力(并发量)
  • 2.保护数据库

原理:内存的访问速度 远远 高于 硬盘的访问速度

场景:读多写少

技术选型

  • redis:单线程,五种数据模型。主流
  • memcache:多线程,kv结构

实现

  • 1.注解:@EnableCache @Cacheable
  • 2.代码:jedis spring-data-redis(默认的序列化器RedisTemplate)

步骤

  • 1.先查缓存,如果缓存中命中,则直接返回
  • 2.如果缓存没有命中,则查询数据库或者远程调用 查询数据 放入缓存

RedisTemplate的序列化器

XMLJSONStringJDK
性能最低稍低最高
内存占用最高稍低最低较高
可读性较差最高较高没有

最常用的是:
StringRedisTemplate:对象 集合 需要手动序列化

缓存写的一致性问题:

读取缓存数据一致性一般没有什么问题,但是一旦涉及到数据更新:数据库和缓存更新,就容易出现缓存(Redis)和数据库(MySQL)间的数据一致性问题

不管先保存到MySQL,还是先保存到Redis都面临着一个保存成功而另外一个保存失败的情况。

不管是先写MySQL数据库,再删除Redis缓存;还是先删除缓存,再写库,都有可能出现数据不一致的情况。举一个例子:

  1. 如果删除了缓存Redis,还没有来得及写库MySQL,另一个线程就来读取,发现缓存为空,则去数据库中读取数据写入缓存,此时缓存中为脏数据。

  2. 如果先写了库,在删除缓存前,写库的线程宕机了,没有删除掉缓存,则也会出现数据不一致情况。

因为写和读是并发的,没法保证顺序,就会出现缓存和数据库的数据不一致的问题。
解决:
1.基于mysql的binlog日志(canal)
2.消息队列

1.双写模式(写数据库,写缓存)
	1.先写redis:写redis成功了(新)  -->  mysql失败了(旧) -->  数据不一致
	2.先写mysql:写mysql成功了(未提交)	-->  写redis成功了(新) --> 代码异常或者服务器宕机了,mysql会回滚(旧) --> 数据不一致
2.失效模式(缓存失效(删除缓存),写数据库——删除redis	高并发下出现数据不一致)
	1.先删redis:
		a用户:先删了redis(空)  			-->   再去写mysql  						--> 提交(mysql 新)
		b用户:					查询数据 --> 先查redis --> 再查myql,放入redis(旧)
	2.先写mysql
		a用户:写mysql成功了 (未提交)-->  删除redis(空)							-->					提交数据(新)
		b用户:										查询数据 --> redis --> mysql,放入redis(旧)
3.双删模式
	1.删除缓存(常规操作——防止旧数据堆积)
	2.写mysql
	3.提交事务
	4.异步删除(借助于AOP后置通知)(保险操作——防止中间操作改变数据)

4.canal中间件-阿里开源
	基于mysql的binlog日志
	canal的工作原理就是把自己伪装成MySQL slave,
	模拟MySQL slave的交互协议向MySQL Mater发送 dump协议,
	MySQL mater收到canal发送过来的dump请求,
	开始推送binary log给canal,然后canal解析binary log,再发送到存储目的地,
	比如MySQL,Kafka,Elastic Search等等。

缓存常见问题:并发读

1.缓存穿透:大量请求访问不存在的数据,由于数据不存在,对应的缓存可能就没有,请求就会直达数据库,导致mysql服务器宕机。
		  解决方案:数据即使为null也缓存 	布隆过滤器
			
2.缓存雪崩:由于缓存时间相同,导致大量缓存数据同时过期,此时请求就会直达数据库
		  解决方案:给缓存时间添加随机值
	
3.缓存击穿:一个热点的key过期,此时大量请求直达数据库
		  解决方案:加分布式锁

分布式锁

jdk没有提供分布式锁的实现方案,只能自己实现或者使用第三方框架实现

1.基于关系型数据库实现
2.基于redis实现
3.基于zk实现
可靠性:zk > redis == mysql
性能:redis > zk > mysql
实现复杂度:zk > mysql > redis

基于redis实现。锁:

1.手写分布式锁

	**特征**:

1.独占排他:setnx
2.防止死锁:
	1.客户端程序(index)获取redis锁之后,服务器立马宕机,导致死锁。
		给锁添加过期时间:expire  set key value ex 30 nx
	2.不可重入
3.保证锁操作的原子性
	加锁和设置过期时间之间:set key value ex 30 nx
	判断和解锁之间:lua
	
4.防误删:解铃还须系铃人
	删除之前要先判断是否自己的锁才能解锁
	
5.可重入:Hash + lua脚本
6.自动续期:
	定时任务:Timer
	lua脚本:
7.集群情况下锁机制可能失效:RedLock算法(红锁算法,redis特有的)
	1.获取客户端当前时间
	2.序列化的从N个相互独立的redis节点获取锁,获取锁的方式采用之前的方式,每个节点获取锁的过期时间一般不超过50
	3.计算逃逸时间=系统当前时间-step的时间
	4.计算剩余锁定时间=总的锁定时间 - step3逃逸时间
	5.如果获取锁失败,把锁定成功的节点解锁

操作:

1.加锁:独占排他 防死锁(过期时间) 可重入(hash + lua) 自动续期
	1.初步:setnx 保证独占排他  问题:没有过期时间可能会导致死锁	不可重入
	2.改进:set key value ex 30 nx  解决死锁(添加过期时间) 加锁和过期时间之间可以保证原子性  问题:不可重入 
	3.可重入:hash + lua脚本  实现可重入 问题:自动续期 
	4.续期:Timer定时器 + lua脚本	 实现自动续期
	

2.解锁:防误删(判断) 判断和删除原子性  可重入解锁
	1.初步:del指令	简单释放锁  问题:可能会导致误删
	2.先判断再删:lua脚本  防止误删同时保证了原子性	不可重入
	3.可重入:hash + lua脚本 
	4.续期:取消定时器

3.重试:递归

redis对lua脚本提供了主动支持,redis中输出的是lua脚本的返回值,而不是输出
由于redis是单线程,它接受及处理请求,遵守one-by-one规则
eval script numKeys key arg
script:脚本
numkeys:lua脚本所需KEYS元素数量

全局变量:a=5  redis中的lua脚本不支持全局变量
局部变量:local a=5

分支控制:
	if 条件 
	then
		。。
	elseif 条件 
	then 
		。。
	else
		。。
	end

防误删脚本:

if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end

if redis.call('get', KEYS[1]) == ARGV[1]
then
	return redis.call('del', KEYS[1])
else 
	return 0
end

KEYS:lock 
ARGV:uuid

2.redisson框架-分布式锁

	1.引入依赖
	2.java配置 初始化一个RedissonClient
	3.可重入非公平锁
		RLock lock = redissonClient.getLock("xx");
		lock.lock()/unlock()
	
		公平锁
			RLock lock = redissonClient.getFairLock("xx");
			lock.lock()/unlock()
		
		读写锁
			RReadWriteLock rwLock = redissonClient.getReadWriteLock("xx");
			rwLock.readLock().lock()/unlock()
			rwLock.writeLock().lock()/unlock()
		
		信号量
			RSemaphore semaphore = redissonClient.getSemaphore("xx");
			semaphore.trySetCount(资源量)
			Semaphore.tryAcquire()
			semaphore.tryRelease()
		
		闭锁(倒计数器)
			RCountDownLatch cdl = redissonClient.getCountDownLatch("xx");
			cdl.trySetCount(6)
			cdl.await()/countDown()

使用AOP实现缓存封装

1.自定义缓存注解GmallCache
2.通过AOP给注解赋能

布隆过滤器

BloomFilter,判断一个元素是否存在。以牺牲精确度换取空间和时间效率的算法
结构:
1.二进制数组
2.一系列的hash函数

特征: 
	1.判定一个数据存在,可能不存在
	2.判定一个数据不存在,就一定不存在
	3.删除困难:CountingBloomFilter

准确度因子:
	1.hash函数个数:个数越多精确度越高,但性能就会越高
	2.二进制数组的长度:长度越长精确度越高,但是占用空间越大
	
场景:
	1.缓存防止缓存穿透
	2.垃圾邮件
	3.骚扰电话
	4.爬虫程序中防止重复爬取
	
实现:
	1.google的guava工具库 langs
	2.redisson 
	3.redis插件 违背了devops
  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Redisson布隆过滤器是基于Redis实现的Java分布式布隆过滤器。它可以在多个JVM节点或其他进程中通过相同的KEY获取到布隆过滤器布隆过滤器的主要功能是判断某个元素是否存在于容器中,因此非常适合解决缓存穿透问题和检查数据是否重复的场景。使用Redisson布隆过滤器时,需要单独开一个定时任务来初始化布隆过滤器的数据,并在删除或更新数据时重新刷新布隆过滤器。虽然使用布隆过滤器可能稍显麻烦,但可以使用Redis的Set类型来替代。\[1\]\[2\] 在代码示例中,可以看到使用Redisson布隆过滤器的示例代码。首先通过redisson.getBloomFilter方法获取布隆过滤器实例,然后使用tryInit方法初始化布隆过滤器的容量和误差率。接下来可以使用add方法向布隆过滤器中添加元素,使用contains方法判断元素是否存在于布隆过滤器中。\[2\]\[3\] 总结来说,Redisson布隆过滤器是一种解决缓存穿透和检查数据重复的有效工具,但在使用时需要注意初始化和刷新布隆过滤器的操作。 #### 引用[.reference_title] - *1* *2* *3* [从头开始学Redisson--------布隆过滤器](https://blog.csdn.net/yanluandai1985/article/details/104848454)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值