title: 学习笔记:cache 和spring cache 技术---本地缓存-分布式缓存,缓存穿透,雪崩,和热点key的问题 author: Eric liu tags: [] categories:html
将数据缓存在JVM中,使用Map或者Guava的Table来保存数据。redis
考虑因素: 使用内存缓存时,需考虑缓存数据消耗多大内存。spring
优点:sql
场景:数据库
将数据缓存在缓存中间件中,例如,redis、memcached。后端
推荐使用redis:缓存
(1)memcached:键值对 redis 还能够支持更多形式tomcat
(2)一样是内存数据库,redis 能够持久化,虽然redis是基于内存的存储系统,可是他自己是支持内存数据的持久化,并且主要提供两种主要的持久化策略,RDB快照和AOF日志,memcache不能bash
(3)性能 :redis 单线程io复用,,只有IO操做来讲,性能好,也有一些计算,如排序聚合,可是计算的时候影响吞吐量网络
memcached 多线程,非阻塞io复用有对全局变量加锁 性能有损耗,
(4)内存管理机制不一样。
memcached 是提早将 分配的内存切分红规定大小的块,而后使用的使用 用多少分配多少,有一个空闲列表进行统计。 不会用内存碎片,可是存在内存浪费
redis,会把剩余内存大小存在内存块中,Redis使用现场申请内存的方式来存储数据,会在必定程度上存在内存碎片。
在redis中,并非全部的数据都一一直存储在内存中的,这是和memcached相比最大的一个区别
Redis只会缓存全部的key端的信息,若是redis发现内存的使用量超过某一个值,将触发swap的操做,redis根据相应的表达式计算出那些key对应value须要swap到磁盘,而后再将这些这些key对应的value持久化到磁盘中,同时再内存清除。同时因为redis将内存中的数据swap到磁盘的时候,提供服务的主线程和进行swap操做的子进程会共享这部份内存,因此若是更新须要swap的数据,redis将阻塞这个操做,直到子线程完成swap操做后才能够进行修改
https://www.cnblogs.com/hanfei-1005/p/5692455.html
复制代码
(5)数据一致性 memcached 有cas 保证,redis 提供了事务
参考文档:http://blog.csdn.net/u013256816/article/details/51146314
在jvm以及redis中均缓存数据,服务优先从jvm获取,miss后从redis中获取
目前ugc 使用 本地缓存 guava 和 redis 分布式缓存。
使用spring cache的注解使用,经过名字区分指定使用哪一个缓存。 g- 开头为 使用本地缓存,在guava的建立方法里判断 若是非g-开头 return null, 而后去redis 缓存中建立缓存
针对失效时间没有作特殊处理 如失效后加锁 或失效前预处理等,由于量级不大 且没有 某时刻的大流量。
ugc 业务中 查询固定的 和不太常改变的 使用本地缓存,文章等放在分布式缓存中。
复制代码
在缓存初始化时,缓存中是没有任何缓存数据的,需先将数据缓存后,缓存服务才算彻底启动。预热方式:
当用户发生数据变动时,优先更新数据库数据。在更新数据库数据成功后,再更新缓存中数据。尽可能避免缓存数据与数据库数据不一致的状况。
当数据库数据发生变动后,将变动后的key值放入到异步刷新缓存队列中。后台线程根据队列中数据,刷新缓存数据。
先刷缓存会形成
缺点:
在高并发下,异步队列会对下游系统产生压力。例如,10K的客户端同时请求服务端,单个客户端的请求QPS是200,且每次请求的key不一样及缓存中不存在数据,则每次都将key写入到数据库中,则数据库扛不住。所以,先将数据写入本地中,先本地幂等,而后在异步的写入到数据库中及缓存中。所以,每次入异步队列的时候,都查询redis中是否已经将这个key放入异步刷新队列中。若是已经放入待刷新队列中,则再也不再次入队列。
问题:
缘由:代码问题, 爬虫,攻击,大量空命中
场景:查询某个文章,给了一个错误的文章id。一直查询不到。
缓存空对象
能够缓存到本地内存中,空对想用一个静态变量。这样不会形成 形成占用内存。
问题:热点key问题,这里指 缓存层直接失效的问题。
方法:集群,隔离组件 把重要资源隔离。让每种资源都单独运行在本身的线程池中。
而Hystrix 是解决依赖隔离的利器
问题: 热点key 缓存过时或者失效 形成段时间大量访问数据库
缘由:
通常使用,缓存 + 过时时间的策略,加速接口的访问速度,减小了后端负载,同时保证功能的更新
可是有两个问题:
(1) 这个key是一个热点key(例如一个重要的新闻,一个热门的八卦新闻等等),因此这种key访问量可能很是大。
(2) 缓存的构建是须要必定时间的。(多是一个复杂计算,例如复杂的sql、屡次IO、多个依赖(各类接口)等等)
从而在缓存失效的瞬间,有大量线程来构建缓存
热点key 问题解决 一:如何解决失效时 大量并发
1.加锁
(1)单机,synchronized ,spring cache 有sync 关键字
(2)分布式,分布式加锁(redis,添加一个key_mutex , "1" , 若是添加上了 至关于获取锁,若是这个存在说明其余人在用锁,获取失败
String get(String key) {
String value = redis.get(key);
if (value == null) {
if (redis.setnx(key_mutex, "1")) {
// 3 min timeout to avoid mutex holder crash
redis.expire(key_mutex, 3 * 60)
value = db.get(key);
redis.set(key, value);
redis.delete(key_mutex);
} else {
//其余线程休息50毫秒后重试
Thread.sleep(50);
get(key);
}
}
}
复制代码
缺点:挤满线程池
2.不过时
redis 设置物理不过时,
异步-逻辑过时:存值中设置timeout,若是发现timeout 过时,后台异步线程构建缓存
缺点:于性能很是友好,惟一不足的就是构建缓存时候,其他线程(非构建缓存的线程)可能访问的是老数据,
String get(final String key) {
V v = redis.get(key);
String value = v.getValue();
long timeout = v.getTimeout();
if (v.timeout <= System.currentTimeMillis()) {
// 异步更新后台异常执行
threadPool.execute(new Runnable() {
public void run() {
String keyMutex = "mutex:" + key;
if (redis.setnx(keyMutex, "1")) {
// 3 min timeout to avoid mutex holder crash
redis.expire(keyMutex, 3 * 60);
String dbValue = db.get(key);
redis.set(key, dbValue);
redis.delete(keyMutex);
}
}
});
}
return value;
}
复制代码
(1)
在value内部设置1个超时值(proTimeout), proTimeout比实际的redis timeout小。当从cache读取到proTimeout发现它已通过期时候. 而后 加分布式锁,设置一个短暂的过时时间。保证有一个线程在刷缓存,其余的正常使用。
若是这个线程刷缓存出了问题没成功,短暂的过时时间 事后 锁解开,下一个线程会机型刷缓存。
原创 伪代码
String get(String key) {
String v = redis.get(key);
if (v == null) {
if (redis.setnx(key_mutex, "1")) {
// 3 min timeout to avoid mutex holder crash
redis.expire(key_mutex, 1 * 60)
value = db.get(key);
redis.set(key, value);
redis.delete(key_mutex);
} else {
//其余线程休息50毫秒后重试
Thread.sleep(50);
get(key);
}
}
else{
if (v.get(timeout) <= now()) {
if ( redis.setnx(key_mutex, "1") ) {
// 1 min timeout to avoid mutex holder crash
redis.expire(key_mutex, 1 * 60)
value = db.get(key);
redis.set(key, value);
redis.delete(key_mutex);
}
}
//若是没到提早的时间 或者有线程在刷,则继续取
return v.get(value);
}
}
复制代码
缓存数据的设计 也能够是多值,
好比key是aaa,设置失效时间为30s,则另外一个key为expire_aaa,失效时间为25s。
好比一个key是aaa,失效时间是30s。查询DB在1s内。
若是是冷数据,30秒都没有人访问,那么数据会过时。
若是是热门数据,一直有大流量访问,那么数据就是一直热的,并且数据一直不会过时。
(2)其余失效前 刷缓存的方式
a.按期从DB里查询数据,再刷到redis 里 有点扯,不适用 常变化的 缓存
b.缓存失效 加锁查