思路
每个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操做至关与互斥锁,在并发状况下只有一个线程能获取到锁,杜绝了大量并发击穿缓存请求到数据库的问题
流程图redis
代码演示数据库
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; }