思路
每一个key都有一个附属key1,附属key1可以是key加特定前缀组成,key对应value为真正的缓存数据,附属key1对应的value不重要,可以是随便一个值,附属key1的作用主要是维护缓存更新时间并保证只有一个线程到数据源拉取数据更新缓存
附属key1的过期时间设置为缓存刷新时间,比如30s,key的过期时间设置 缓存刷新时间 + 数据源修复预期时间(比如2天)
每次请求数据时,使用setnx(将 key 的值设为 value ,当且仅当 key 不存在)设置附属key1,返回结果为1:设置成功,代表附属key1过期,需要刷新数据,从数据源获取数据更新缓存,若返回结果为0:设置失败,代表附属key1未过期,不需要刷新数据,从缓存key中获取数据
由于redis是单线程,setnx操作相当与互斥锁,在并发情况下只有一个线程能获取到锁,杜绝了大量并发击穿缓存请求到数据库的问题
流程图

代码演示

package com.liutf.util;

import redis.clients.jedis.Jedis;

/**
* redis工具
**/
public class ReidsUtil { private static final String HOST = "192.168.11.23";
private static final int PORT = 6379; /**
* 附属key前缀
*/
private static final String PREFIX = "prefix:"; /**
* 数据源修复预期时间
*/
private static final int FIX_TIME = 2 * 26 * 60 * 60; /**
* 缓存时间过期时PREKEY_TIME的缓存时间
*/
private static final int PREKEY_TIME_COMMON = 30; private static Jedis jedis = null; static {
jedis = new Jedis(HOST, PORT);
} public static String get(String key) {
/**
* 组装设置附属key
*/
String prefixKey = PREFIX + key;
Long setnxResult = jedis.setnx(prefixKey, "1"); /**
* 附属key过期返回null,从数据源获取数据
* 附属key未过期,从key中获取数据
*/
if (setnxResult == 1) {
jedis.expire(prefixKey, PREKEY_TIME_COMMON);
return null;
} else {
return jedis.get(key);
}
} public static boolean set(String key, String value) {
/**
* 组装设置附属key
*/
String prefixKey = PREFIX + key;
jedis.setnx(prefixKey, "1");
jedis.expire(prefixKey, PREKEY_TIME_COMMON); jedis.set(key, value);
jedis.expire(key, PREKEY_TIME_COMMON + FIX_TIME);
return true;
} }
public String get(key) {  

      String value = redis.get(key);  

      if (value == null) { //代表缓存值过期  

          //设置3min的超时,防止del操作失败的时候,下次缓存过期一直不能load db  

          if (redis.setnx(key_mutex, 1, 3 * 60) == 1) {  //代表设置成功  

               value = db.get(key);  

                      redis.set(key, value, expire_secs);  

                      redis.del(key_mutex);  

              } else {  //这个时候代表同时候的其他线程已经load db并回设到缓存了,这时候重试获取缓存值即可  

                      sleep(50);  

                      get(key);  //重试  

              }  

          } else {  

              return value;        

          }  

缺点分析

  1. 每次请求数据,就需要先操作附属key,再设置附属key过期时间,请求量在原有的两倍多
  2. 并发情况下,附属key过期,抢到锁的线程从数据源获取数据,再更新缓存,其他未获取锁的线程获取老数据返回

redis缓存击穿问题一种思路分享的更多相关文章

  1. $.ajax()方法详解 ajax之async属性 【原创】详细案例解剖——浅谈Redis缓存的常用5种方式(String,Hash,List,set,SetSorted )

    $.ajax()方法详解   jquery中的ajax方法参数总是记不住,这里记录一下. 1.url: 要求为String类型的参数,(默认为当前页地址)发送请求的地址. 2.type: 要求为Str ...

  2. redis 缓存击穿 看一篇成高手系列3

    什么是缓存击穿 在谈论缓存击穿之前,我们先来回忆下从缓存中加载数据的逻辑,如下图所示 因此,如果黑客每次故意查询一个在缓存内必然不存在的数据,导致每次请求都要去存储层去查询,这样缓存就失去了意义.如果 ...

  3. Redis缓存击穿

    缓存击穿 缓存击穿,是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞. 比如在做 ...

  4. redis缓存击穿和缓存雪崩

    工作中经常会用到redis来做缓存,以防止后台db挂掉.但是db数据一般都在10T以上,不可能把mysql中的数据全部放入redis中,所以一般是将一些热key放入redis中. 缓存击穿 一个请求先 ...

  5. Redis缓存击穿、缓存穿透、缓存雪崩

    文章原创于公众号:程序猿周先森.本平台不定时更新,喜欢我的文章,欢迎关注我的微信公众号. 上篇文章谈到了Redis分布式锁,实际上就是为了解释为什么做缓存采用Redis而不使用map/guava.缓存 ...

  6. Redis 缓存击穿(失效)、缓存穿透、缓存雪崩怎么解决?

    原始数据存储在 DB 中(如 MySQL.Hbase 等),但 DB 的读写性能低.延迟高. 比如 MySQL 在 4 核 8G 上的 TPS = 5000,QPS = 10000 左右,读写平均耗时 ...

  7. [Redis]处理定时任务的2种思路

    用redis完成类似 at 命令的功能,例如订单24小时后没有支付自动关闭,定时发邮件,主要说下任务生成之后怎么触发消费. 使用 有序集合 思路: 使用sorted Sets的自动排序, key 为任 ...

  8. 【原创】详细案例解剖——浅谈Redis缓存的常用5种方式(String,Hash,List,set,SetSorted )

    很多小伙伴没接触过Redis,以至于去学习的时候感觉云里雾里的,就有一种:教程随你出,懂了算我输的感觉. 每次听圈内人在谈论的时候总是插不上话,小编就偷偷去了解了一下,也算是初入门径. 然后就整理了一 ...

  9. 谈谈redis缓存击穿透和缓存击穿的区别,雪崩效应

    面试经历 在很长的一段时间里,我以为缓存击穿和缓存穿透是一个东西,直到最近去腾讯面试,面试官问我缓存击穿和穿透的区别:我回答它俩是一样的,面试官马上抬起头用他那细长的单眼皮眼睛瞪着我说:"你 ...

随机推荐

  1. java 线程并发(生产者、消费者模式)

    线程并发协作(生产者/消费者模式) 多线程环境下,我们经常需要多个线程的并发和协作.这个时候,就需要了解一个重要的多线程并发协作模型“生产者/消费者模式”. Ø 什么是生产者? 生产者指的是负责生产数 ...

  2. OpenCvSharp 图像旋转

    /// <summary> /// 图像旋转 /// </summary> private Mat MatRotate(Mat src, float angle) { Mat ...

  3. interface Part4(接口中的多态)

    使用接口实现多态 需要满足以下两个条件. 定义接口并使用类实现了接口中的成员. 创建接口的实例指向不同的实现类对象. 假设接口名称为 ITest,分别定义两个实现类来实现接口的成员,示例代码如下. i ...

  4. RCS版本控制

    RCS(Revision Control System)衍生品有两个 SCCS(Source Code Control System) CVS(Concurrent Versions System)是 ...

  5. sql server存储过程解密

    解密存储过程: USE [RYTreasureDB] GO /****** Object: StoredProcedure [dbo].[sp__windbi$decrypt] Script Date ...

  6. python之迭代器、生成器及列表推导式

    一.迭代器 迭代器就是迭代的工具,迭代是一个重复的过程,每次重复都是一次迭代并且每次迭代的结果都是下次迭代的初始值. lst=[1,2,3,4,5] count=0 while count<le ...

  7. HTML的BODY内标签介绍

    一.基本标签 <body> <b>加粗</b> <i>斜体</i> <u>下划线</u> <s>删除线& ...

  8. mysql(单表查询,多表查询,MySQl创建用户和授权,可视化工具Navicat的使用)

    单表查询 语法: 一.单表查询的语法 SELECT 字段1,字段2... FROM 表名 WHERE 条件 GROUP BY field HAVING 筛选 ORDER BY field LIMIT ...

  9. Centos 6.x开机启动流程

    Centos 6.x开机启动流程 BIOS(COMS)检查 加载Bios,bios包含所有硬件信息(CPU,内存,硬盘,时钟,鼠标键盘等等) 读MBR 硬盘上第0磁道第一个扇区被称为MBR(maste ...

  10. webpack中如何编写一个plugin

    loader和plugin有什么区别呢?什么是loader,什么是plugin. 当我们在源代码里面去引入一个新的js文件或者一个其他格式的文件的时候,这个时候,我们可以借助loader去帮我们处理引 ...