缓存穿透 、缓存击穿、缓存雪崩

编程入门 行业动态 更新时间:2024-10-07 00:15:47

<a href=https://www.elefans.com/category/jswz/34/1771061.html style=缓存穿透 、缓存击穿、缓存雪崩"/>

缓存穿透 、缓存击穿、缓存雪崩

缓存穿透

大量的并发请求同时访问同一个redis中的不存在的数据,就会导致大量的请求同时并发访问数据库 ,对数据库造成了高并发访问压力

解决方式1 

对空值缓存:如果一个查询返回的数据为空(不管是数据是否不存在),我们仍然把这个空结果(null)进行缓存,设置空结果的过期时间会很短,最长不超过五分钟

package com.junsang.jedisdemo01.demos.controller;import java.io.IOException;import com.junsang.jedisdemo01.demos.utils.JedisPoolUtils;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;public class SecKillController4 {static String secKillScript ="local userid=KEYS[1];\r\n" + // 定义第一个变量"local prodid=KEYS[2];\r\n" + 	// 定义第二个变量"local qtkey='sk:'..prodid..\":qt\";\r\n" +  // 库存的key"local usersKey='sk:'..prodid..\":usr\";\r\n" +  // 秒杀成功的用户key"local userExists=redis.call(\"sismember\",usersKey,userid);\r\n" + //sismember 检测key是否在集合中存在"if tonumber(userExists)==1 then \r\n" +  // 如果上面一行的结果是1表示用户抢购过了,那就直接返回2"   return 2;\r\n" +"end\r\n" +"local num= redis.call(\"get\" ,qtkey);\r\n" + // 获取库存"if tonumber(num)<=0 then \r\n" + // 如果库存量<=0"   return 0;\r\n" + 	// 直接返回0,库存为0,抢购空了"else \r\n" +"   redis.call(\"decr\",qtkey);\r\n" +  // 库存-1"   redis.call(\"sadd\",usersKey,userid);\r\n" + // 用户+1"end\r\n" +"return 1" ;static String secKillScript2 ="local userExists=redis.call(\"sismember\",\"{sk}:0102:usr\",userid);\r\n" +" return 1";/*** 正经秒杀函数* @param uid* @param prodid* @return* @throws IOException*/public static String doSecKill(String uid,String prodid) throws IOException {JedisPool jedispool =  JedisPoolUtils.getJedisPoolInstance();Jedis jedis=jedispool.getResource();//String sha1=  .secKillScript;String sha1=  jedis.scriptLoad(secKillScript);Object result= jedis.evalsha(sha1, 2, uid,prodid);String reString=String.valueOf(result);if ("0".equals( reString )  ) {jedis.setex("result:" + uid + ":" + prodid, 300, "null");System.err.println("已抢空!!");return null;}else if("1".equals( reString )  )  {System.out.println("抢购成功!!!!");return "success";}else if("2".equals( reString )  )  {System.err.println("该用户已抢过!!");jedis.setex("result:" + uid + ":" + prodid, 300, "null");return null;}else{System.err.println("抢购异常!!");return null;}}
}

<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<h1>印度美女 !!!  1元秒杀!!!
</h1><form id="msform" action="sk" enctype="application/x-www-form-urlencoded" method="post"><input type="hidden" id="proid" name="proid" value="0102"><input type="button"  id="miaosha_btn" name="seckill_btn" value="秒杀点我"/>
</form>
</body>
<script src=".1.1/jquery.min.js"></script>
<script  type="text/javascript">$(function(){$("#miaosha_btn").click(function(){var url=$("#msform").attr("action");$.post(url,$("#msform").serialize(),function(data){if(data=="false"){alert("抢光了" );}} );})})
</script>
</html>

前端发过来一个商品id 0102 在查询redis 时,没有查询到sk:0102:qt这个key  ,redis里面查询不到,发生了缓存穿透问题,返回null并存储到redis中并设置5分钟有效期可以解决。

解决方法

    对空值缓存:

  • 设置可访问的名单(白名单):使用bitmaps类型定义一个可以访问的名单,名单id作为bitmaps的偏移量,每次访问和bitmap里面的id进行比较,如果访问id不在bitmaps里面,进行拦截,不允许访问。
  • 采用布隆过滤器:(布隆过滤器(Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量(位图)和一系列随机映射函数(哈希函数)。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。)。将所有可能存在的数据哈希到一个足够大的bitmaps中,一个一定不存在的数据会被 这个bitmaps拦截掉,从而避免了对底层存储系统的查询压力。
  • 进行实时监控:当发现Redis的命中率开始急速降低,需要排查访问对象和访问的数据,和运维人员配合,可以设置黑名单限制服务

缓存击穿

key对应的数据存在  但在redis中过期  此时若有大量并发请求过来 ,这些请求发现缓存过期一般会从后端DB加载数据并回设到缓存

package com.junsang.jedisdemo01.demos.config;import java.io.IOException;import com.junsang.jedisdemo01.demos.utils.JedisPoolUtils;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;public class SecKill_redisByScript {static String secKillScript ="local userid=KEYS[1];\r\n" + // 定义第一个变量"local prodid=KEYS[2];\r\n" + 	// 定义第二个变量"local qtkey='sk:'..prodid..\":qt\";\r\n" +  // 库存的key"local usersKey='sk:'..prodid..\":usr\";\r\n" +  // 秒杀成功的用户key"local userExists=redis.call(\"sismember\",usersKey,userid);\r\n" + //sismember 检测key是否在集合中存在"if tonumber(userExists)==1 then \r\n" +  // 如果上面一行的结果是1表示用户抢购过了,那就直接返回2"   return 2;\r\n" +"end\r\n" +"local num= redis.call(\"get\" ,qtkey);\r\n" + // 获取库存"if tonumber(num)<=0 then \r\n" + // 如果库存量<=0"   return 0;\r\n" + 	// 直接返回0,库存为0,抢购空了"else \r\n" +"   redis.call(\"decr\",qtkey);\r\n" +  // 库存-1"   redis.call(\"sadd\",usersKey,userid);\r\n" + // 用户+1"end\r\n" +"return 1" ;static String secKillScript2 ="local userExists=redis.call(\"sismember\",\"{sk}:0101:usr\",userid);\r\n" +" return 1";/*** 正经秒杀函数* @param uid* @param prodid* @return* @throws IOException*/public static String doSecKill(String uid,String prodid) throws IOException {JedisPool jedispool =  JedisPoolUtils.getJedisPoolInstance();Jedis jedis=jedispool.getResource();String lockKey = "lock:" + prodid; // 锁的键名String lockValue = uid; // 锁的值,可以使用用户ID作为锁的值try {// 尝试获取排他锁Long lockResult = jedis.setnx(lockKey, lockValue);//String sha1=  .secKillScript;if (lockResult == 1) {String sha1 = jedis.scriptLoad(secKillScript);Object result = jedis.evalsha(sha1, 2, uid, prodid);String reString = String.valueOf(result);if ("0".equals(reString)) {jedis.setex("result:" + uid + ":" + prodid, 300, "null");System.err.println("已抢空!!");return null;} else if ("1".equals(reString)) {System.out.println("抢购成功!!!!");return "success";} else if ("2".equals(reString)) {System.err.println("该用户已抢过!!");jedis.setex("result:" + uid + ":" + prodid, 300, "null");return null;} else {System.err.println("抢购异常!!");return null;}}else {System.out.println("没有拿到锁");Thread.sleep(100); // 可以根据实际情况调整等待时间return doSecKill(uid, prodid); // 重新尝试获取锁并执行秒杀逻辑}} catch (Exception e) {e.printStackTrace();return null;} finally {// 释放锁jedis.del(lockKey);jedis.close();}}
}

缓存雪崩(指的是大量的key)

  • key对应的数据存在,但在redis中过期,此时若有大量并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。
  • 缓存雪崩与缓存击穿的区别在于这里针对很多key缓存,前者则是某一个key。

更多推荐

缓存穿透 、缓存击穿、缓存雪崩

本文发布于:2024-02-13 10:00:08,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1758051.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:缓存

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!