首页 > 数据库 >redis缓存常见问题及解决方案

redis缓存常见问题及解决方案

时间:2024-11-05 21:08:47浏览次数:3  
标签:skuId 缓存 redis value num key 常见问题

redis缓存常见问题及解决方案

1、缓存穿透

缓存穿透: 是指查询一个不存在的数据,由于缓存无法命中,将去查询数据库,但是数据库也无此记录,并且出于容错考虑,我们没有将这次查询的null写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。在流量大时,可能DB就挂掉了,要是有人利用不存在的key频繁攻击我们的应用,这就是漏洞。

  • 解决1 :空结果也进行缓存,但它的过期时间会很短,最长不超过五分钟,但是不能防止随机穿透。

  • 解决2 :使用布隆过滤器或者Redis的Bitmap来解决随机穿透问题

Redis的Bitmap解决缓存穿透

  • setbit key offset value:设置或清除指定偏移量上的位(bit)。offset 是从0开始的位索引,value 可以为 0 或 1。
  • getbit key offset:返回指定偏移量上的位值。

实例

public solution(){
	String key = "sku:product:data";
	//查询mysql里面商品skuId
	List<ProductSku> productSkuList = productSkuMapper.selectList(null);
	productSkuList.forEach(item -> {
      	//将所有商品的SkUId添加到redis里面的bitmap中
      	redisTemplate.opsForValue().setBit(key,item.getId(),true);
  });
}

// 测试
public void getProductSku(Long skuId) {
    	//调用商品接口之前 提前知道用户访问商品SKUID是否存在于bitmap中
        String key = "sku:product:data";
   
   		//根据skuId和可以查询redis中的数据
        Boolean flag = redisTemplate.opsForValue().getBit(key, skuId);
    
        if (!flag) {
            log.error("用户查询商品sku不存在:{}", skuId);
            //查询数据不存在直接返回空对象
            throw new ServiceException("用户查询商品sku不存在");
        }  
}

注意当数据库商品表进行更新时,bitmap也要及时更新。

2、缓存雪崩

缓存雪崩:是指在我们设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,DB瞬时压力过重雪崩。

  • 解决1:原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。

  • 解决2:如果单节点宕机,可以采用集群部署方式防止雪崩

// 设置随机过期时间
redisTemplate.opsForValue().set(key,value,time, TimeUnit.SECONDS);

3、缓存击穿

缓存击穿: 是指对于一些设置了过期时间的key,如果这些key可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。这个时候,需要考虑一个问题:如果这个key在大量请求同时进来之前正好失效,那么所有对这个key的数据查询都落到db,我们称为缓存击穿。

与缓存雪崩的区别:

  1. 击穿是一个热点key失效
  2. 雪崩是很多key集体失效

解决:加锁

当一些key在大量请求同时进来之前正好失效,那么我们需要加锁,只放行一个请求去数据库查询,并把查询到的结果缓存到redis中。后面其他请求进来时都从redis中快速获取数据。

进程内锁:synchronized和lock锁

不能解决多进程之间的多线程并发问题。

public synchronized void testLock() {
   // 查询Redis中的num值
   String value = (String)this.stringRedisTemplate.opsForValue().get("num");
   // 没有该值return
   if (StringUtils.isBlank(value)){
      return ;
   }
   // 有值就转成成int
   int num = Integer.parseInt(value);
   // 把Redis中的num值+1
   this.stringRedisTemplate.opsForValue().set("num", String.valueOf(++num));
}

进程外锁:分布式锁

分布式锁主流的实现方案:

  1. 基于数据库实现分布式锁
  2. 基于缓存( Redis等)
  3. 基于Zookeeper

每一种分布式锁解决方案都有各自的优缺点:

  1. 高性能:Redis最高
  2. 可靠性:zookeeper最高

分布式锁使用的逻辑如下:

尝试获取锁
	成功:执行业务代码    
		执行业务  
			try{
				获取锁
				业务代码-宕机
			} catch(){
			
			}finally{ 
				释放锁
			}
 	失败:等待(回旋);

代码

/**
 * 采用SpringDataRedis实现分布式锁
 * 原理:执行业务方法前先尝试获取锁(setnx存入key val),如果获取锁成功再执行业务代码,业务执行完毕后将锁释放(del key)
 */
public void testLock() {

    //0.先尝试获取锁 setnx key val
    Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent("lock", "lock");
    if(flag){
        //获取锁成功,执行业务代码
        //1.先从redis中通过key num获取值  key提前手动设置 num 初始值:0
        String value = stringRedisTemplate.opsForValue().get("num");
        //2.如果值为空则非法直接返回即可
        if (StringUtils.isBlank(value)) {
            return;
        }
        //3.对num值进行自增加一
        int num = Integer.parseInt(value);
        stringRedisTemplate.opsForValue().set("num", String.valueOf(++num));

        //4.将锁释放
        stringRedisTemplate.delete("lock");

    }else{
        try {
            Thread.sleep(100);
            this.testLock();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

4、数据一致性

在当前环境下,通常我们会首选redis缓存来减轻我们数据库访问压力。但是也会遇到以下这种情况:大量用户来访问我们系统,首先会去查询缓存, 如果缓存中没有数据,则去查询数据库,然后更新数据到缓存中,并且如果数据库中的数据发生了改变则需要同步到redis中,同步过程中需要保证 MySQL与redis数据一致性问题

解决1:使用延时双删策略

延时双删策略是一种常见的保证MySQL和Redis数据一致性的方法。其主要流程包括:先删除缓存,然后更新数据库。这个过程完成后,大约在数据库从库更新后再次删除缓存。具体的步骤如下:

第一步,先执行redis.del(key)操作删除缓存;

第二步,然后执行写数据库的操作;

第三步,休眠一段时间(例如500毫秒),根据具体的业务时间来定;

第四步,再次执行redis.del(key)操作删除缓存。

延时双删策略通过这种方式尝试达到最终的数据一致性,但是这并不是强一致性,因为MySQL和Redis主从节点数据的同步并不是实时的,所以需要等待一段时间以增强它们的数据一致性。同时,由于读写是并发的,可能出现缓存和数据库数据不一致的问题

//修改
@Transactional
@Override
public int updateProduct(Product product) {
    //1 删除缓存(获取spu下的sku id列表)
    List<Long> skuIdList =  product.getProductSkuList().stream()
        .map(ProductSku::getId).collect(Collectors.toList());
    
    //从redis中删除每个sku的缓存
    skuIdList.forEach(skuId -> {
        String dataKey = "product:sku:" + skuId;
        this.redisTemplate.delete(dataKey);
    });

    //2 之前的业务代码,执行更新商品操作.....


    //3 休眠一段时间
    try {
        Thread.sleep(100);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
    
    //4 再次执行操作删除缓存
    skuIdList.forEach(skuId -> {
        String dataKey = "product:sku:" + skuId;
        this.redisTemplate.delete(dataKey);
    });
    return 1;
}

解决2:使用canal解决

标签:skuId,缓存,redis,value,num,key,常见问题
From: https://www.cnblogs.com/21CHS/p/18528862

相关文章

  • MySql与Redis
    MySql与Redis1.Redis和MySQL如何保持数据一致性数据同步可以有延时一、延时双删策略在写库前后都进行redis.del(key)操作,并且设定合理的超时时间。publicvoidwrite(Stringkey,Objectdata){redis.delKey(key);//删除redis缓存db.updateData(data)......
  • 高并发场景常见的三个缓存问题
    缓存穿透大量不存在的数据访问,缓存中没有,直接访问DB,大量的数据并发的访问DB,导致DB崩溃解决方法解决方法1:将空的结果也缓存到Redis解决方法2:在Redis的前面添加一个布隆过滤网,将DB组件放到布隆过滤中,将DB中不存在的数据先过滤一遍缓存雪崩大量的请求发送过来的时......
  • 阿里云cdn缓存过期时间,会导致服务器流量消耗,尤其是大文件
    您成功添加域名后,可以修改缓存时间。设置的缓存时间长短会导致回源流量不一样,费用也有所不同,建议根据不同的业务需求设置缓存时长。缓存过期时间会影响回源频率,建议根据实际业务需求设置资源缓存时长。缓存过期时间过短,会导致CDN频繁回源,增加源站的流量消耗;缓存过期时间过长,会......
  • PCIe系列专题之二:2.5 Flow Control缓存架构及信用积分
    一、故事前传之前我们讲了对PCIe的一些基础概念作了一个宏观的介绍,了解了PCIe是一种封装分层协议(packet-basedlayeredprotocol),主要包括事务层(Transactionlayer),数据链路层(Datalinklayer)和物理层(Physicallayer)。较为详细解释请见之前的文章:1.PCIe技术概述;2.0PCIe......
  • 使用 Go 语言实现 LRU 缓存
    文章目录LRU缓存的关键特性数据结构选型LRU缓存的结构设计操作流程图代码实现1.定义节点和缓存结构2.初始化LRU缓存3.获取缓存值(`Get`方法)4.更新或插入值(`Put`方法)5.辅助方法6.单元测试代码总结在日常开发中,缓存是提高系统性能的重要手段。LRU(LeastRe......
  • AI运动小程序开发常见问题集锦二
    截止到现在写博文时,我们的AI运动识别小程序插件已经迭代了23个版本,成功应用于健身、体育、体测、AR互动等场景;为了让正在集成或者计划进行功能扩展优化的用户,少走弯路、投入更少的开发资源,针对近期的咨询问题,我们又归集了一些常见问题,供大家参考。一、计时、计数计量模式有什么......
  • Linux基础——服务器Raid阵列卡开启cache缓存
    服务器Raid阵列卡开启cache缓存一、问题描述客户业务环境:本地存储型裸金属服务器做NFS服务器,15台以上的客户端接入服务器,读写大量的小文件,客户读写速录慢的现象;影响读写速率:磁盘性能和磁盘缓存,容易造成大量的IO拥塞;二、问题分析裸金属NFS服务器单盘最大IOPS2200,一台主机可能......
  • Redis底层数据结构 SDS
    SDS字符串在Redis中是很常用的,键值对中的键是字符串类型,值有时也是字符串类型。Redis是用C语言实现的,但是它没有直接使用C语言的char*字符数组来实现字符串,而是自己封装了一个名为简单动态字符串(simpledynamicstring,SDS)的数据结构来表示字符串,也就是Redis的Stri......
  • 在Windows中安装Redis
    1、下载Redis下载地址:https://github.com/microsoftarchive/redis选择Release 3.Windows系统中启动和停止RedisWindows系统中启动Redis,直接双击redis-server.exe即可启动Redis服务,redis服务默认端口号为6379双击redis-cli.exe即可启动Redis客户端,默认连接的是本地的Re......