加速读写
cpu l1/l2/l3 cache、浏览器缓存、ehcache缓存数据库结果
降低后端负载
后端服务器通过前端缓存降低负载:业务端使用redis降低后端mysql的负载
数据不一致:缓存层和数据层有时间窗口不一致问题,和更新策略有关
代码维护成本:多了一层缓存逻辑
降低后端负载
对高消耗的sql:join结果集/分组统计结果缓存
加速请求响应
利用redis/memcache优化io响应时间
大量写合并为批量写
入计数器先redis累加再批量写db
###1.lru等算法剔除:例如 maxmemory-policy
淘汰策略 | 含义 |
---|---|
noeviction | 当内存使用达到阈值的时候,所有引起申请内存的命令会报错 |
allkeys-lru | 在主键空间中,优先移除最近未使用的key |
volatile-lru | 在设置了过期时间的键空间中,优先移除最近未使用的key |
allkeys-random | 最主键空间中,随机移除某个key |
volatile-random | 在设置了过期的键空间中,随机移除某个key |
volatile-ttl | 在设置了过期时间的键空间中,具有更早过期时间的key优先移除 |
###3.主动更新:开发控制生命周期
低一致性数据:最大内存和淘汰策略
高一致性:超时剔除和主动更新结合,最大内存和淘汰策略兜底
通用性:全量属性更好
占用空间:部分属性更好
代码维护:表面上全量属性更好
含义:查询一个不存在的数据,由于缓存是不命中时需要从数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询
产生原因
业务代码自身问题
恶意攻击、爬虫
发现问题
业务的响应时间,受到恶意攻击时,普遍请求被打到存储层,必会引起响应时间提高,可通过监控发现。
业务本身问题
相关指标:总调用数、缓存层命中数、存储层命中数
###解决方法1:缓存空对象(设置过期时间)
含义:当存储层查询不到数据后,往cache层中存储一个null,后期再被查询时,可以通过cache返回null。
缺点
cache层需要存储更多的key
缓存层和数据层数据“短期”不一致
示例代码
public string getpassthrough(string key) {
string cachevalue = cache.get(key);
if( stringutils.isempty(cachevalue) ) {
string storagevalue = storage.get(key);
cache.set(key , storagevalue);
if( stringutils.isempty(storagevalue) ) {
cache.expire(key , 60 * 5 );
}
return storagevalue;
} else {
return cachevalue;
}
}
###解决方法2:布隆过滤器拦截(适合固定的数据)
将所有可能存在的数据哈希到一个足够大的bitmap中,一个不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力
含义:由于cache服务器承载大量的请求,当cache服务异常脱机,流量直接压向后端组件,造成级联故障。或者缓存集中在一段时间内失效,发生大量的缓存穿透
做到缓存多节点、多机器、甚至多机房。
redis cluster、redis sentinel
做二级缓存
使用hystrix做服务降级
问题描述:添加机器时,客户端的性能不但没提升,反而下降
问题关键点
更多的机器 != 更高的性能
更多的机器 = 数据增长与水平扩展
批量接口需求:一次mget随着机器增多,网络节点访问次数更多。网络节点的时间复杂度由o(1) -> o(node)
优化io的方法
命令本身优化:减少慢查询命令:keys、hgetall、查询bigkey并进行优化
减少网络通信次数
mget由o(keys),升级为o(node),o(max_slow(node)) , 甚至是o(1)
降低接入成本:例如客户端长连接/连接池、nio等
热点key(访问量比较大) + 较长的重建时间(重建过程中的api或者接口比较费时间)
导致的问题:有大量的线程会去查询数据源并重建缓存,对存储层造成了巨大的压力,响应时间会变得很慢
减少重建缓存的次数
数据尽可能一致
减少潜在危险:例如死锁、线程池大量被hang住(悬挂)
互斥锁(分布式锁)
第一个线程需要重建时候,对这个key的重建加入分布式锁,重建完成后进行解锁
这个方法避免了大量的缓存重建与存储层的压力,但是还是会有大量线程的阻塞
jedis.set(lockkey, requestid, set_if_not_exist, set_with_expire_time, expiretime)
string get(string key) {
string set_if_not_exist = "nx";
string set_with_expire_time = "px";
string value = jedis.get(key);
if( null == value ) {
string lockkey = "lockkey:" + key;
if( "ok".equals(jedis.set(lockkey , "1" , set_if_not_exist ,
set_with_expire_time , 180)) ) {
value = db.get(key);
jedis.set(key , value);
jedis.delete(lockkey);
} else {
thread.sleep(50);
get(key);
}
}
return value;
}
永远不过期
缓存层面:不设置过期时间(不使用expire)
功能层面:为每个value添加逻辑过期时间,单发现超过逻辑过期时间后,会使用单独的线程去重建缓存。
还存在一个数据不一致的情况。可以将逻辑过期时间相对实际过期时间相对减小
加速读写
cpu l1/l2/l3 cache、浏览器缓存、ehcache缓存数据库结果
降低后端负载
后端服务器通过前端缓存降低负载:业务端使用redis降低后端mysql的负载
数据不一致:缓存层和数据层有时间窗口不一致问题,和更新策略有关
代码维护成本:多了一层缓存逻辑
降低后端负载
对高消耗的sql:join结果集/分组统计结果缓存
加速请求响应
利用redis/memcache优化io响应时间
大量写合并为批量写
入计数器先redis累加再批量写db
###1.lru等算法剔除:例如 maxmemory-policy
淘汰策略 | 含义 |
---|---|
noeviction | 当内存使用达到阈值的时候,所有引起申请内存的命令会报错 |
allkeys-lru | 在主键空间中,优先移除最近未使用的key |
volatile-lru | 在设置了过期时间的键空间中,优先移除最近未使用的key |
allkeys-random | 最主键空间中,随机移除某个key |
volatile-random | 在设置了过期的键空间中,随机移除某个key |
volatile-ttl | 在设置了过期时间的键空间中,具有更早过期时间的key优先移除 |
###3.主动更新:开发控制生命周期
低一致性数据:最大内存和淘汰策略
高一致性:超时剔除和主动更新结合,最大内存和淘汰策略兜底
通用性:全量属性更好
占用空间:部分属性更好
代码维护:表面上全量属性更好
含义:查询一个不存在的数据,由于缓存是不命中时需要从数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询
产生原因
业务代码自身问题
恶意攻击、爬虫
发现问题
业务的响应时间,受到恶意攻击时,普遍请求被打到存储层,必会引起响应时间提高,可通过监控发现。
业务本身问题
相关指标:总调用数、缓存层命中数、存储层命中数
###解决方法1:缓存空对象(设置过期时间)
含义:当存储层查询不到数据后,往cache层中存储一个null,后期再被查询时,可以通过cache返回null。
缺点
cache层需要存储更多的key
缓存层和数据层数据“短期”不一致
示例代码
public string getpassthrough(string key) {
string cachevalue = cache.get(key);
if( stringutils.isempty(cachevalue) ) {
string storagevalue = storage.get(key);
cache.set(key , storagevalue);
if( stringutils.isempty(storagevalue) ) {
cache.expire(key , 60 * 5 );
}
return storagevalue;
} else {
return cachevalue;
}
}
###解决方法2:布隆过滤器拦截(适合固定的数据)
将所有可能存在的数据哈希到一个足够大的bitmap中,一个不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力
含义:由于cache服务器承载大量的请求,当cache服务异常脱机,流量直接压向后端组件,造成级联故障。或者缓存集中在一段时间内失效,发生大量的缓存穿透
做到缓存多节点、多机器、甚至多机房。
redis cluster、redis sentinel
做二级缓存
使用hystrix做服务降级
问题描述:添加机器时,客户端的性能不但没提升,反而下降
问题关键点
更多的机器 != 更高的性能
更多的机器 = 数据增长与水平扩展
批量接口需求:一次mget随着机器增多,网络节点访问次数更多。网络节点的时间复杂度由o(1) -> o(node)
优化io的方法
命令本身优化:减少慢查询命令:keys、hgetall、查询bigkey并进行优化
减少网络通信次数
mget由o(keys),升级为o(node),o(max_slow(node)) , 甚至是o(1)
降低接入成本:例如客户端长连接/连接池、nio等
热点key(访问量比较大) + 较长的重建时间(重建过程中的api或者接口比较费时间)
导致的问题:有大量的线程会去查询数据源并重建缓存,对存储层造成了巨大的压力,响应时间会变得很慢
减少重建缓存的次数
数据尽可能一致
减少潜在危险:例如死锁、线程池大量被hang住(悬挂)
互斥锁(分布式锁)
第一个线程需要重建时候,对这个key的重建加入分布式锁,重建完成后进行解锁
这个方法避免了大量的缓存重建与存储层的压力,但是还是会有大量线程的阻塞
jedis.set(lockkey, requestid, set_if_not_exist, set_with_expire_time, expiretime)
string get(string key) {
string set_if_not_exist = "nx";
string set_with_expire_time = "px";
string value = jedis.get(key);
if( null == value ) {
string lockkey = "lockkey:" + key;
if( "ok".equals(jedis.set(lockkey , "1" , set_if_not_exist ,
set_with_expire_time , 180)) ) {
value = db.get(key);
jedis.set(key , value);
jedis.delete(lockkey);
} else {
thread.sleep(50);
get(key);
}
}
return value;
}
永远不过期
缓存层面:不设置过期时间(不使用expire)
功能层面:为每个value添加逻辑过期时间,单发现超过逻辑过期时间后,会使用单独的线程去重建缓存。
还存在一个数据不一致的情况。可以将逻辑过期时间相对实际过期时间相对减小
如对本文有疑问, 点击进行留言回复!!
express+mongoose实现对mongodb增删改查操作详解
修复 Mac brew 安装 mongodb 报 Error: No available formula with the name ‘mongodb’ 问题详解
网友评论