IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 大数据 -> 自定义注解解决Redis三大问题代码实现 -> 正文阅读

[大数据]自定义注解解决Redis三大问题代码实现

一、背景

当我们想要某个接口访问时,优先访问redis缓存时,可以使用@Cacheable注解实现。但是在处理redis的三大问题的时候,使用自定义的注解可控性更强。根据前面redis的已知的三大问题以及它的解决方案,扩充了注解的功能。

二、实现

1.定义注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RedisCheck {
????String key() default "";//key值
????int delTime() default 180;//key值删除时间
????boolean isUseNull() default true;
????String bloomFilterKey() default "";//使用的布隆过滤器的key值
????boolean isUseLock() default true;//是否使用锁
}

2.使用代理,监听注解,同时处理三大问题。

@Aspect
@Component
public class RedisCheckAop {
????@Autowired
????private RedisCacheUtil redisCacheUtil;
????@Autowired
????private BloomFilterUtil bloomFilterUtil;
????@Autowired
????private LockUtil lockUtil;
????@Around("@annotation(com.dmsl.annotation.RedisCheck)")
????public Object RedisCheck(ProceedingJoinPoint joinPoint) throws Throwable {
????????// 获取注解
????????System.out.println("----------------");
????????MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
????????Method method = methodSignature.getMethod();
????????RedisCheck annotation = method.getAnnotation(RedisCheck.class);
????????String key = annotation.key();
????????int delTime = annotation.delTime();//设置为-1就是用不删除key 解决缓存击穿的方法二
????????boolean isUseNull = annotation.isUseNull();
????????String bloomFilterKey = annotation.bloomFilterKey();
????????boolean isUseLock = annotation.isUseLock();


????????Object[] args = joinPoint.getArgs();
????????String redisKey = KeyUtil.GetKey(key,args);


????????if(redisCacheUtil.CheckHaveCacheData(redisKey))//key存在
????????{
????????????System.out.println("key存在="+redisKey);
????????????Object data = redisCacheUtil.GetCacheData(redisKey);
????????????if(data!=null)//不为空直接返回
????????????????return data;
????????????if(isUseNull)//即使为空也返回 解决缓存穿透的方法一
????????????{
????????????????System.out.println("即使为空也返回");
????????????????return data;
????????????}
????????}
????????else//key不存在
????????{
????????????if(!bloomFilterKey.isEmpty())//使用布隆过滤器 解决缓存穿透的方法二
????????????{
????????????????BloomFilter bloomFilter=bloomFilterUtil.GetBloomFilter(bloomFilterKey);

????????????????if(bloomFilter!=null)//布隆过滤器存在
????????????????{
????????????????????System.out.println("布隆过滤器存在");
????????????????????//不存在说明请求的redisKey mysql不存在
????????????????????if(!bloomFilter.contains(redisKey)) {
????????????????????????System.out.println("redisKey mysql不存在");
????????????????????????return null;
????????????????????}
????????????????}
????????????}


????????????System.out.println("key不存在="+redisKey);
????????????if(isUseLock)//使用互斥锁 解决缓存击穿和雪崩的方法二
????????????{
????????????????if(lockUtil.CheckIsLock(redisKey))//被锁了
????????????????{
????????????????????System.out.println("已经锁了");
????????????????????Thread.sleep(100);
????????????????????return RedisCheck(joinPoint);
????????????????}
????????????????else{
????????????????????System.out.println("没锁,现在加上锁了");
????????????????}
????????????}


????????}
????????Object result = null;
????????try{
????????????result = joinPoint.proceed(args);//调用原本的Service函数,访问mysql获取数据
????????????redisCacheUtil.AddCacheData(redisKey,result,delTime);
????????????System.out.println("请求mysql");
????????}
????????finally {
????????????if(isUseLock)
????????????{
????????????????System.out.println("释放锁");
????????????????lockUtil.UnLock(redisKey);//防止报错
????????????}
????????}
????????return result;
????}
}

这里的解决方法并非最优,一般需要根据项目做修改。

例如:

解决缓存击穿的问题中,这里我们是存下空值,避免下一次请求不存在的数据又访问数据库。但是恶意用户攻击可能会换不同的不存在的key来攻击(例如:我key值选-1递减的来请求),这样会导致内存不断的增加,因此加入了布隆过滤器来处理。

但是如果已知一个项目中的表key值id是大于0,小于N(这个N是可以获取到的)。并且0-N之前被删的id不多,那么我完全可以不用布隆过滤器,直接判断id是不是在0-N之外,就可以确定id是不是不存在的了。就算是最坏的情况,redis也就只是多了0-N之间被删id的空数据。

3.Redis缓存管理工具

@Component
public class RedisCacheUtil {
????@Resource
????private RedisUtil redisUtil;

????public Object GetCacheData(String key, Object[] args )
????{
????????key = GetKey(key,args);
????????return GetCacheData(key);
????}

????public Object GetCacheData(String redisKey)
????{
????????Object data =null;
????????if (redisUtil.hasKey(redisKey)) {
????????????data = redisUtil.get(redisKey);
????????}
????????return data;
????}
????public boolean CheckHaveCacheData(String key, Object[] args )
????{
????????key = GetKey(key,args);
????????return CheckHaveCacheData(key);
????}

????public boolean CheckHaveCacheData(String redisKey)
????{
????????return redisUtil.hasKey(redisKey);
????}
????public void AddCacheData(String key, Object[] args,Object data,int delTime)
????{
????????key = GetKey(key,args);
????????AddCacheData(key,data,delTime);
????}
????public void AddCacheData(String redisKey, Object data,int delTime)
????{
????????redisUtil.set(redisKey, data);
????????if(delTime>0)
????????????redisUtil.expire(redisKey, delTime);
????}


????public void RemoveCacheData(String key, Object[] args){
????????key = GetKey(key,args);
????????if (redisUtil.hasKey(key)) {
????????????redisUtil.del(key);
????????}
????}
????public void RemoveCacheData(String key, Object arg){
????????key = GetKey(key,arg);
????????if (redisUtil.hasKey(key)) {
????????????redisUtil.del(key);
????????}
????}
}

4.布隆过滤器实现

public class BloomFilter {
????private static final int BIT_SIZE = 12;
????private static final int DEFAULT_SIZE = 2 << BIT_SIZE;
????private static final int[] SEEDS = new int[]{3, 13, 46};
????private BitSet bits = new BitSet(DEFAULT_SIZE);
????private HashCode[] hashFunc = new HashCode[SEEDS.length];
????public BloomFilter() {
????????for (int i = 0; i < SEEDS.length; i++) {
????????????hashFunc[i] = new HashCode(DEFAULT_SIZE, SEEDS[i]);
????????}
????}
????
????public void add(Object value) {
????????for (HashCode f : hashFunc) {
????????????bits.set(f.hash(value), true);
????????}
????}

????public boolean contains(Object value) {
????????boolean ret = true;
????????for (HashCode f : hashFunc) {
????????????ret = ret && bits.get(f.hash(value));
????????}
????????return ret;
????}
????
????public static class HashCode {

????????private int bitSize;
????????private int seed;

????????public HashCode(int bitSize, int seed) {
????????????this.bitSize = bitSize;
????????????this.seed = seed;
????????}

????????public int hash(Object value) {
????????????int h;
????????????return (value == null) ? 0 : Math.abs(seed * (bitSize - 1) & ((h = value.hashCode()) ^ (h >>> 16)));
????????}

????}
}

5.布隆过滤器管理

@Component
public class BloomFilterUtil {
????private Map<String,BloomFilter> bloomFilterMap=new HashMap<>();
????public BloomFilter GetBloomFilter(String key)
????{
????????if(!bloomFilterMap.containsKey(key))
????????????return null;
????????return bloomFilterMap.get(key);
????}
????public void AddBloomFilter(String key,BloomFilter bloomFilter)
????{
????????bloomFilterMap.put(key,bloomFilter);
????}
}

6.互斥锁

@Component
public class LockUtil {
????private static final String lockKey="lockKey";
????private Set<String> lockSet=new CopyOnWriteArraySet<>();
????public boolean CheckIsLock(String key){
????????key = GetKey(lockKey,key);
???????return !lockSet.add(key);
????}

????public void UnLock(String key){
????????key = GetKey(lockKey,key);
????????lockSet.remove(key);
????}
}

7.使用方法

@RedisCheck(key = RedisKey.topViewVideoKey)
public List<VideoEntity> FindTopViewCountVideo(int start, int len) {
????return videoMapper.FindTopViewCountVideo(start, len);
}

8.服务器启动时,初始化布隆过滤器

public void InitBloomFilter(){
????List<VideoEntity> videoEntityList = videoService.GetAllVideo();
????BloomFilter bloomFilter=new BloomFilter();
????for(VideoEntity videoEntity:videoEntityList)
????{
????????bloomFilter.add(KeyUtil.GetKey(RedisKey.videoKey,videoEntity.getId()));
????}
????bloomFilterUtil.AddBloomFilter(BloomFilterKey.videoKey,bloomFilter);
}

三、测试用例

一、测试布隆过滤器:

设置

@RedisCheck(key= RedisKey.videoKey,isUseNull=false,bloomFilterKey= BloomFilterKey.videoKey)

单元测试

@Test
//10个线程 执行10次
//@PerfTest(invocations = 10,threads = 10)
public void Test(){
????appInitService.InitBloomFilter();
????VideoEntity videoEntity= videoService.FindVideoByID(-1);
????System.out.println("+++++++++null="+(videoEntity==null));
}

1. 输入不存在的key=-1请求一次。预期输出:

----------------

布隆过滤器存在

redisKey mysql不存在

+++++++++null=true

2.输入不存在的key=-1再请求一次。预期输出和上面一致。

二、测试是否为空也返回

设置

@RedisCheck(key= RedisKey.videoKey,isUseNull=true)

单元测试

@Test
//10个线程 执行10次
//@PerfTest(invocations = 10,threads = 10)
public void Test(){
????appInitService.InitBloomFilter();
????VideoEntity videoEntity= videoService.FindVideoByID(-1);
????System.out.println("+++++++++null="+(videoEntity==null));
}

1. 输入不存在的key=-2请求一次。预期输出:

----------------

key不存在=video--2

没锁,现在加上锁了

请求mysql

释放锁

+++++++++null=true

2. 输入不存在的key=-2再请求一次。预期输出:

----------------

key存在=video--2

即使为空也返回

+++++++++null=true

三、测试互斥锁

设置

@RedisCheck(key= RedisKey.videoKey,isUseLock=true)

单元测试

开10个线程,总共执行10次

@Rule
public ContiPerfRule contiPerfRule = new ContiPerfRule();
@Test
//10个线程 执行10次
@PerfTest(invocations = 10,threads = 10)
public void Test(){
????appInitService.InitBloomFilter();
????VideoEntity videoEntity= videoService.FindVideoByID(152);
????System.out.println("+++++++++");
}

pom.xml需要引入

<dependency>
????<groupId>org.databene</groupId>
????<artifactId>contiperf</artifactId>
????<version>2.3.4</version>
????<scope>test</scope>
</dependency>

1. 输入存在的key=156执行。

当输出中,打印了两次以上“key不存在”(打印一次不能说错,但是达不到测试目的),“请求mysql”打印了一次,那就是正常的。

----------------

key不存在=video-156

没锁,现在加上锁了

----------------

key不存在=video-156

已经锁了

----------------

请求mysql

释放锁

+++++++++null=false

key存在=video-156

----------------

----------------

key存在=video-156

----------------

----------------

----------------

key存在=video-156

key存在=video-156

key存在=video-156

key存在=video-156

----------------

+++++++++null=false

----------------

key存在=video-156

+++++++++null=false

key存在=video-156

+++++++++null=false

+++++++++null=false

+++++++++null=false

+++++++++null=false

+++++++++null=false

+++++++++null=false

----------------

key存在=video-156

+++++++++null=false

  大数据 最新文章
实现Kafka至少消费一次
亚马逊云科技:还在苦于ETL?Zero ETL的时代
初探MapReduce
【SpringBoot框架篇】32.基于注解+redis实现
Elasticsearch:如何减少 Elasticsearch 集
Go redis操作
Redis面试题
专题五 Redis高并发场景
基于GBase8s和Calcite的多数据源查询
Redis——底层数据结构原理
上一篇文章      下一篇文章      查看所有文章
加:2022-06-26 16:58:01  更:2022-06-26 17:00:03 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/19 19:44:44-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码