package com.pingan.haofang.agent.saas.util.cache.redis;
import com.pingan.haofang.agent.saas.common.utils.Log;
import com.pingan.haofang.agent.saas.common.utils.RedisUtils;
import org.apache.log4j.Logger;
import org.springframework.data.redis.core.RedisTemplate;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
/**
* redis缓存
*
*/
public class RedisCache<K, V> {
private static final Logger log = Logger.getLogger(RedisCache.class);
private static final ExecutorService executor = Executors.newCachedThreadPool();
/**
* 对Null类型的值,用户希望设定的过期时间 ,时间单位为秒
*/
private long nullValueExpiredTime = 10;
/** 对于正常的缓存值,用户希望设定的过期时间,时间单位为秒 */
private long valueExpiredTime = 24 * 60 * 60;
/**
* 对于通过getDataByKey接口获取数据是报错的情况,用户希望Null在本地缓存的过期时间,时间单位为秒
*/
private long errorValueExpiredTime = 10;
/**
* RedisCacheDataCallBack的实现,在创建一个DaemonCache,强制要求用户实现
*/
private RedisCacheDataCallBack<K, V> callback;
private RedisTemplate<String, V> redisTemplate;
private RedisUtils redisUtils;
//private RedisUtils<K,V> redisUtils;
/**
* 需要传入RedisCacheDataCallBack构造
*/
public RedisCache(RedisCacheDataCallBack<K, V> redisCacheDataCallBack, RedisTemplate<String, V> redisTemplate) {
if (redisCacheDataCallBack == null) {
throw new IllegalArgumentException("when create a new RedisCache, please implmement the RedisCacheDataCallBack first!");
}
this.callback = redisCacheDataCallBack;
this.redisTemplate = redisTemplate;
}
/**
* 需要传入RedisCacheDataCallBack构造
*/
public RedisCache(RedisCacheDataCallBack<K, V> redisCacheDataCallBack, RedisUtils redisUtils) {
if (redisCacheDataCallBack == null) {
throw new IllegalArgumentException("when create a new RedisCache, please implmement the RedisCacheDataCallBack first!");
}
this.callback = redisCacheDataCallBack;
this.redisUtils = redisUtils;
}
/**
* 创建RedisCache实例 ,默认本地缓存大小为10万
*
* @param nullValueExpiredTime 空数据过期时间
* @param valueExpiredTime 正常时间期望过期时间
* @param errorValueExpiredTime 数据接口查询错误过期时间
*/
public RedisCache(long valueExpiredTime, long nullValueExpiredTime, long errorValueExpiredTime) {
this.valueExpiredTime = valueExpiredTime;
this.nullValueExpiredTime = nullValueExpiredTime;
this.errorValueExpiredTime = errorValueExpiredTime;
}
/**
* 创建RedisCache实例 ,带有数据层逻辑回调实现,默认本地缓存大小为10万
*
* @param valueExpiredTime 空数据过期时间 ,时间单位为"秒"
* @param nullValueExpiredTime 正常时间期望过期时间,时间单位为"秒"
* @param errorValueExpiredTime 数据接口查询错误过期时间,时间单位为"秒"
* @param callback 数据层逻辑回调实现,实现自RedisCacheDataCallBack<K, V>接口
* 例子:
* RedisCache localCache = new RedisCache<Integer, List<Item>>(
* 60,//nullValueExpiredTime 空数据过期时间
* 60 * 60,//valueExpiredTime 正常时间期望过期时间
* 60,//errorValueExpiredTime 数据接口查询错误过期时间
* new RedisCacheDataCallBack<Integer, List<Item>>() {
* @Override public List<Item> getDataByKey(Integer key) {
* return getTopItemsImpl(key); //这里为业务逻辑实现
* }
* }
* );
*/
public RedisCache(long valueExpiredTime, long nullValueExpiredTime,
long errorValueExpiredTime, RedisCacheDataCallBack<K, V> callback) {
if (callback == null) {
throw new IllegalArgumentException("when create a new RedisCache, please implmement the RedisCacheDataCallBack first!");
}
this.callback = callback;
this.nullValueExpiredTime = nullValueExpiredTime;
this.valueExpiredTime = valueExpiredTime;
this.errorValueExpiredTime = errorValueExpiredTime;
}
/**
* 获取默认的RedisCache,过期时间默认为都为10s , 默认本地缓存大小为10万
*
* @param redisCacheDataCallBack 数据层逻辑回调实现,实现自RedisCacheDataCallBack<K, V>接口
* @return RedisCache 默认的实例
*/
public RedisCache<K, V> getDefaultRedisCache(RedisCacheDataCallBack<K, V> redisCacheDataCallBack) {
if (redisCacheDataCallBack == null) {
throw new IllegalArgumentException("when create a new RedisCache, please implmement the RedisCacheDataCallBack first!");
}
this.callback = redisCacheDataCallBack;
return new RedisCache<K, V>(nullValueExpiredTime, valueExpiredTime, errorValueExpiredTime, redisCacheDataCallBack);
}
/**
* 通过key从redis中获取对应的Value l
*
* @param key <K>
* @return V value
*/
public V get(String key, K k) {
return get(key,k,valueExpiredTime);
}
public V get(String key, K k, long expireTime) {
V result = (V) redisUtils.get(key);
if (result != null) {
return result;
}
// -> 没有数据,或者为NULL(NULL数据已经过期)
log.debug("没有数据,或者为NULL(NULL数据已经过期)");
try {
// 先调用数据查询接口(下层Memcache和DB的查询逻辑)
V callBackResult = callback.getRedisDataByKey(k);
if (callBackResult == null) {
// 没有数据
log.debug("5-无数据");
put(key, callBackResult, this.nullValueExpiredTime);
return null;
} else {
// 有数据,填充数据并设定时间(业务数据过期时间)
log.debug("6-有数据,填充数据并设定时间(业务数据过期时间)");
put(key, callBackResult, expireTime);
return callBackResult;
}
} catch (RedisCacheDataCallBackException e) {
e.printStackTrace();
// 数据查询报错,向Cache中回写Null,并设定过期时间(这里的时间可能偏短)
log.debug("7-数据查询报错");
putNULL(key, this.nullValueExpiredTime);
return null;
}
}
/**
* 向localcache中放入Null值
*
* @param key cache的Key
* @param expiredTime Null 值的过期时间
*/
private void putNULL(String key, long expiredTime) {
redisTemplate.opsForValue().set(key, null, expiredTime);
}
/**
* 向Cache中放入key和Value,需要指定过期时间
*
* @param key
* @param value
* @param expiredTime
*/
private void put(String key, V value, long expiredTime) {
redisUtils.set(key, value, expiredTime);
}
/**
* 注入对应的业务回调实例
*
* @param callback 数据层逻辑回调实现,实现自RedisCacheDataCallBack<K, V>接口
*/
public void setCallback(RedisCacheDataCallBack<K, V> callback) {
if (callback == null) {
throw new IllegalArgumentException(
"the callback is null here , please check !");
}
this.callback = callback;
}
/**
*
* @param key
* @param value
* @param expiredTime
*/
public int updateDbAndRedis(String key, V value, long expiredTime) {
//先存db,再删缓存
int result = callback.updateDbFromRedis(value);
redisUtils.delete(key);
//思考:为什么后通知,先更新DB,后更新缓存
//1.先更新DB,后删缓存:
//正常思考:并发查询时,查出的是redis的旧值,产生了1次脏读,但减少对DB的冲击。
//深度思考:此脏读是一时的旧数据,因随后会删redis,代价小,1次脏读。
//2.先删缓存,后更新DB:
//正常思考:并发查询时,因没有缓存,去数据库查询,对其冲击大。好处是可以查到最新的数据,但!
//深度思考:但查到实际不是最新的数据,因为还没有更新DB,会把不是最新的数据会放到redis里,导致长时间脏读,概率大,代价大。
// FutureTask future = new FutureTask(() -> {
//
// //推缓存
// Log.info("redis异步更新数据库", "key:{} data:{}", key, value);
// if (!redisUtils.hasKey(key)) {
// try {
//
// Field idField;
// PropertyDescriptor pd;
//
// if (value.getClass().getName().endsWith("WithBLOBs")) {
// idField = value.getClass().getSuperclass().getDeclaredField("id");
// pd = new PropertyDescriptor(idField.getName(), value.getClass().getSuperclass());
// } else {
// idField = value.getClass().getDeclaredField("id");
// pd = new PropertyDescriptor(idField.getName(), value.getClass());
// }
//
// Method getMethod = pd.getReadMethod();
// Long id = (Long) getMethod.invoke(value);
//
// if (id == null) {
// return null;
// }
// V v = callback.getById(id);
// put(key, v, expiredTime);
// } catch (NoSuchFieldException e) {
// log.error("id 不存在! 或者 调用 getById 失败", e);
// } catch (IllegalAccessException e) {
// log.error("redis异步更新数据库", e);
// } catch (Exception e) {
// log.error("redis异步更新数据库", e);
// }
// }
// return null;
// });
// executor.execute(future);
return result;
}
/**
*
* @param key
* @param value
*/
public int updateDbAndRedis(String key, V value) {
return this.updateDbAndRedis(key,value,valueExpiredTime);
}
public int insertDbAndRedis(String cacheConsants,V value, long expiredTime){
int primaryKey = callback.insertDbFromRedis(value);
// FutureTask<Void> future = new FutureTask(new Callable<Void>() {
// public Void call() {
// //推缓存
// Log.info("redis异步更新数据库","{key}:{data}",cacheConsants + primaryKey, value);
// if (!redisUtils.hasKey(cacheConsants + primaryKey)) {
// put(cacheConsants + primaryKey, value, expiredTime);
// }
// return null;
// }
// });
// executor.execute(future);
return primaryKey;
}
public int insertDbAndRedis(String key,V value){
return this.insertDbAndRedis(key,value,valueExpiredTime);
}
public static void main(String[] args) {
System.out.println("1");
FutureTask<Void> future = new FutureTask(new Callable<Void>() {
public Void call() {
//Thread.sleep(5);
System.out.println("线程执行");
return null;
}
});
executor.execute(future);
System.out.println("2");
}
}
分享到:
相关推荐
被动刷新是比较常用的刷新方式,存入缓存的数据会被设置一个过期时间。查询请求进来时,当缓存中有数据时则从缓存取,缓存中没有数据时,则从数据库查询数据,并更新至缓存。但是,需要注意从数据库查询的逻辑需要...
缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时被动写的,并且出于容错考虑,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。...
缓存穿透是指查询一个缓存和数据库中都没有的数据,由于大部分缓存策略是被动加载的,并且出于容错考虑,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的...
Redis作为一款高性能的键值存储数据库,广泛应用于缓存和数据存储场景,但其性能受到多种因素影响。 一、Redis变慢的原因: 1. 复杂度过高的命令:执行时间较长的操作会导致阻塞其他客户端的请求。 2. 操作bigkey:...
Redis,全称Remote Dictionary Server,是一款高性能的键值存储系统,常被用于缓存、消息队列等场景。在Java开发中,Redis是常见的数据存储和高速访问解决方案。以下是一些关于Redis的面试题及其答案,帮助你深入...
缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时被动写的,并且出于容错考虑,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。...
缓存穿透是指查询一个确定不存在的数据,由于缓存是不命中时被动写入的,并且为了容错,如果从存储层查不到数据,则不会写入缓存。这样一来,该不存在的数据每次请求都会到达存储层,导致失去缓存的意义。特别是在高...
- 被动失效策略则是在缓存空间不足时采用LRU算法来淘汰数据。 - 基于时间戳的失效策略则是为每个缓存条目设置一个有效期,一旦过期则自动删除。 - **缓存命中率优化**: - 缓存命中率是指缓存中数据被有效利用的...
缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时被动写的,并且出于容错考虑,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。...
被动淘汰是指Redis 数据占满了内存,这个时候就会将过期的数据或者很久没有访问的数据删除掉。 定时过期是指每个设置过期时间的 key 都需要创建一个定时器,到过期时间就会立即清除。该策略可以立即清除过期的数据...
这一方案主要结合了本地缓存和分布式缓存,如Redis,以提高系统的响应速度和处理能力。在这种场景下,读操作远超写操作,尤其是对于热卖商品,本地缓存能有效缓解分布式缓存的压力,防止因热点数据导致的单点瓶颈。 ...
- **心跳机制**:客户端定期向服务端发送心跳包,由服务端被动接收并响应;减轻服务端负担,提高灵活性。 #### Java模板引擎对比:Velocity与FreeMarker 在Java开发中,模板引擎是非常重要的工具之一,主要用于将...
在有限的物理资源下,如果实现服务的高可用性与稳定性。主要解决如下一些问题: ...解决数据库主从延迟,数据库与redis缓存,跨系统&跨数据库,本地缓存与redis缓存数据,同库多表冗余数据等问题;实现数据最终一致性。
Redis 是一个广泛使用的开源键值存储系统,常用于缓存和数据持久化。要查找 Redis 数据库中的某个 key 在内存中的位置,首先需要理解 Redis 的内部数据结构和工作原理。 1. **Redis 数据库结构**: Redis 中的每个...
CBase策略被实现在了Redis系统中,Redis是一个开源的内存数据结构存储系统,用作数据库、缓存和消息代理。实验结果显示,CBase能够有效提升内存对象缓存系统的命中率,同时整体上增加系统吞吐量,并且没有明显的系统...
Redis是一个开源的、基于键值存储的数据结构服务器,常被用作数据库、缓存和消息代理。在"interprocess-push-stream"中,Redis被用来作为进程间流事件的通道,允许一个进程发送数据流,而其他进程可以订阅并接收这些...
总的来说,Redis 的主键失效机制结合了主动和被动的删除策略,既保证了缓存的时效性,又尽可能减少了对系统资源的影响。开发者可以根据实际需求调整过期策略,如调整活跃删除的频率或调整过期策略的算法,以优化缓存...
koa2 框架、mysql 数据库、session 会话机制、EJS 后端模板引擎、CSS 样式表、Redis 缓存数据库等。 Node.js 微博系统的特点 基于 Node.js 的微博系统具有轻量型且易部署的特点,该系统适合于想要推出自己的微博...
缓存管理算法包括FIFO(先进先出)、LRU(最近最少使用)和LFU(最不经常使用)等,开源的NoSQL数据库如Redis和Memcached提供了成熟的缓存解决方案。 缓存服务器部署是另一个要考虑的因素。推荐将缓存服务器独立...