《转》基于redis的缓存机制的思考和优化
2018-01-03 14:32
405 查看
不错的文章,转给大家看看。
原文地址:http://blog.csdn.net/qq_18860653/article/details/54893095。再次感谢原博主。
相对我们对于redis的使用场景都已经想当的熟悉。对于大量的数据,为了缓解接口(数据库)的压力,我们对查询的结果做了缓存的策略。一开始我们的思路是这样的。
1.执行查询
2.缓存中存在数据 -> 查询缓存
3.缓存中不存在数据 -> 查询实时接口
对此,我简单模拟了我们的缓存机制 。
这是一个查询实时的服务
[java] view
plain copy
<span style="font-size:14px;">package yyf.Jedis.toolsByRedis.cacheCacheTools;
/**
* 模拟服务
* @author yuyufeng
*
*/
public class BaseService {
public String query(String req) {
return "hello:" + req;
}
}
</span>
从代码中我们可以看到,这个服务反应应该是非常快的。
[java] view
plain copy
<span style="font-size:14px;">package yyf.Jedis.toolsByRedis.cacheCacheTools;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
public class CacheCacheToolTest {
static JedisPool jedisPool;
static {
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(100);
config.setMaxIdle(5);
config.setMaxWaitMillis(1000);
config.setTestOnBorrow(false);
jedisPool = new JedisPool(config, "127.0.0.1", 6379, 1000);
Jedis jedis = jedisPool.getResource();
jedisPool.returnResource(jedis);
}
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
new Thread(){@Override
public void run() {
//执行查询
query();
}}.start();
}
}
public static void query() {
BaseService bs = new BaseService();
Jedis jedis = jedisPool.getResource();
String req = "test123";
String res;
if (jedis.get(req) == null) {
System.out.println("##查询接口服务");
res = bs.query(req);
jedis.setex(req, 10, res);
} else {
System.out.println("##查询缓存");
res = jedis.get(req);
}
System.out.println(res);
jedisPool.returnResource(jedis);
}
}
</span>
当5个并发进来的时候,第一个查询实时服务,其余的查询缓存。
[java] view
plain copy
<span style="font-size:14px;">##查询接口服务
hello:test123
##查询缓存
##查询缓存
##查询缓存
hello:test123
hello:test123
hello:test123
##查询缓存
hello:test123
</span>
看到结果,我们似乎觉得这个查询非常的合理,当时当我们的实时接口查询速度很慢的时候,就暴露出问题来了。
[java] view
plain copy
<span style="font-size:14px;">package yyf.Jedis.toolsByRedis.cacheCacheTools;
/**
* 模拟服务
* @author yuyufeng
*
*/
public class BaseService {
public String query(String req) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "hello:" + req;
}
}
</span>
[java] view
plain copy
<span style="font-size:14px;">##查询接口服务
##查询接口服务
##查询接口服务
##查询接口服务
##查询接口服务
hello:test123
hello:test123
hello:test123
hello:test123
hello:test123
</span>
结果是,全部都查询的接口服务。这样会导致并发一高,缓存就相当于作用非常小了。
如果在查询实时过程时,对于相同的请求,能够让其等待,那么效率会有大大的提升:(为了模拟,加锁处理)
[java] view
plain copy
public static void main(String[] args) {
beginTime = System.currentTimeMillis();
for (int i = 0; i < 5; i++) {
new Thread(){@Override
public void run() {
//执行查询
synchronized (args) {
query();
}
//System.out.println(System.currentTimeMillis()-beginTime);
}}.start();
}
}
[java] view
plain copy
##查询缓存
hello:test123
##查询缓存
hello:test123
##查询缓存
hello:test123
##查询缓存
hello:test123
##查询缓存
hello:test123
现在就都是查询缓存了。其实对于查询并发这样做是比好的。打个比方:
一堆人需要从一个出口出去,这个出口有一个小门已经可以通过,还有一个大门未打开,需要从小门出去打开。这个大门非常大(redis查询速度非常快)。如果大批的人同时出去(高并发),那么必然在小门挤很长的时间。此时,如果现有一个人去把大门先打开,那么后面的人(包括本来要挤小门的人)可以直接从大门出去,效率肯定是后面的划算。
对于查询实时一次比较慢的情况下,可以先让一个线程进去。让其它线程等待。
当然,这样并不完美。当缓存失效,那么查询就会卡顿一下。为了保证用户能一直流畅的查询,我有如下两种方案:
1.在缓存存在的时间里的进行异步查询去更新缓存。
2.使用二级缓存,并且当一级缓存失效的时候,会去读取二级缓存,二级缓存异步更新。(二级缓存的时间可以很长)
下面是第一种策略的代码模拟:
[java] view
plain copy
public static void query() {
BaseService bs = new BaseService();
Jedis jedis = jedisPool.getResource();
String req = "test123";
String res;
if (jedis.get(req) == null) {
System.out.println("##查询接口服务");
res = bs.query(req);
jedis.setex(req, 100, res);
} else {
System.out.println("##查询缓存");
res = jedis.get(req);
System.out.println("缓存剩余时间:"+jedis.ttl(req));
// 当时间超过10秒,异步更新数据到缓存
if (jedis.ttl(req) < 90) {
//模拟得到推送,接受推送,执行
new Thread() {
@Override
public void run() {
String res = bs.query(req);
jedis.setex(req, 100, res);
System.out.println("异步更新数据:"+req);
}
}.start();
}
}
System.out.println(res);
jedisPool.returnResource(jedis);
}
运行结果:
[java] view
plain copy
##查询缓存
缓存剩余时间:67
hello:test123
##查询缓存
缓存剩余时间:67
hello:test123
##查询缓存
缓存剩余时间:67
hello:test123
##查询缓存
缓存剩余时间:67
hello:test123
##查询缓存
缓存剩余时间:67
hello:test123
异步更新数据:test123
异步更新数据:test123
异步更新数据:test123
异步更新数据:test123
异步更新数据:test123
为了保证一段时间内,更新一个缓存只执行一次,做如下锁
[java] view
plain copy
public static void main(String[] args) {
beginTime = System.currentTimeMillis();
for (int i = 0; i < 5; i++) {
new Thread() {
@Override
public void run() {
// 执行查询
query();
// System.out.println(System.currentTimeMillis()-beginTime);
}
}.start();
}
}
public static void query() {
BaseService bs = new BaseService();
Jedis jedis = jedisPool.getResource();
String req = "test123";
String res;
System.out.println(jedis.get(req));
if (jedis.get(req) == null) {
System.out.println("##查询接口服务");
res = bs.query(req);
jedis.setex(req, 100, res);
} else {
System.out.println("##查询缓存");
res = jedis.get(req);
System.out.println("缓存剩余时间:"+jedis.ttl(req));
// 当时间超过10秒,异步更新数据到缓存
if (jedis.ttl(req) < 90) {
//模拟得到推送,接受推送,执行
new Thread() {
@Override
public void run() {
//保证5秒内,一条数据只更新一次
Long incr = jedis.incr("incr-flag-"+req);
jedis.expire("incr-flag-"+req, 5);
if(1 == incr){
String resT = bs.query(req);
jedis.setex(req, 100, resT);
System.out.println("异步更新数据:"+req);
}
}
}.start();
}
}
jedisPool.returnResource(jedis);
}
运行两次,间隔10秒。运行结果:
[java] view
plain copy
hello:test123
##查询缓存
hello:test123
hello:test123
hello:test123
hello:test123
##查询缓存
##查询缓存
##查询缓存
##查询缓存
异步更新数据:test123
这样,即可保证一次查询比较耗时的情况下,用户能流畅的查询。用户体验大大提升
原文地址:http://blog.csdn.net/qq_18860653/article/details/54893095。再次感谢原博主。
相对我们对于redis的使用场景都已经想当的熟悉。对于大量的数据,为了缓解接口(数据库)的压力,我们对查询的结果做了缓存的策略。一开始我们的思路是这样的。
1.执行查询
2.缓存中存在数据 -> 查询缓存
3.缓存中不存在数据 -> 查询实时接口
对此,我简单模拟了我们的缓存机制 。
这是一个查询实时的服务
[java] view
plain copy
<span style="font-size:14px;">package yyf.Jedis.toolsByRedis.cacheCacheTools;
/**
* 模拟服务
* @author yuyufeng
*
*/
public class BaseService {
public String query(String req) {
return "hello:" + req;
}
}
</span>
从代码中我们可以看到,这个服务反应应该是非常快的。
[java] view
plain copy
<span style="font-size:14px;">package yyf.Jedis.toolsByRedis.cacheCacheTools;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
public class CacheCacheToolTest {
static JedisPool jedisPool;
static {
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(100);
config.setMaxIdle(5);
config.setMaxWaitMillis(1000);
config.setTestOnBorrow(false);
jedisPool = new JedisPool(config, "127.0.0.1", 6379, 1000);
Jedis jedis = jedisPool.getResource();
jedisPool.returnResource(jedis);
}
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
new Thread(){@Override
public void run() {
//执行查询
query();
}}.start();
}
}
public static void query() {
BaseService bs = new BaseService();
Jedis jedis = jedisPool.getResource();
String req = "test123";
String res;
if (jedis.get(req) == null) {
System.out.println("##查询接口服务");
res = bs.query(req);
jedis.setex(req, 10, res);
} else {
System.out.println("##查询缓存");
res = jedis.get(req);
}
System.out.println(res);
jedisPool.returnResource(jedis);
}
}
</span>
当5个并发进来的时候,第一个查询实时服务,其余的查询缓存。
[java] view
plain copy
<span style="font-size:14px;">##查询接口服务
hello:test123
##查询缓存
##查询缓存
##查询缓存
hello:test123
hello:test123
hello:test123
##查询缓存
hello:test123
</span>
看到结果,我们似乎觉得这个查询非常的合理,当时当我们的实时接口查询速度很慢的时候,就暴露出问题来了。
[java] view
plain copy
<span style="font-size:14px;">package yyf.Jedis.toolsByRedis.cacheCacheTools;
/**
* 模拟服务
* @author yuyufeng
*
*/
public class BaseService {
public String query(String req) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "hello:" + req;
}
}
</span>
[java] view
plain copy
<span style="font-size:14px;">##查询接口服务
##查询接口服务
##查询接口服务
##查询接口服务
##查询接口服务
hello:test123
hello:test123
hello:test123
hello:test123
hello:test123
</span>
结果是,全部都查询的接口服务。这样会导致并发一高,缓存就相当于作用非常小了。
如果在查询实时过程时,对于相同的请求,能够让其等待,那么效率会有大大的提升:(为了模拟,加锁处理)
[java] view
plain copy
public static void main(String[] args) {
beginTime = System.currentTimeMillis();
for (int i = 0; i < 5; i++) {
new Thread(){@Override
public void run() {
//执行查询
synchronized (args) {
query();
}
//System.out.println(System.currentTimeMillis()-beginTime);
}}.start();
}
}
[java] view
plain copy
##查询缓存
hello:test123
##查询缓存
hello:test123
##查询缓存
hello:test123
##查询缓存
hello:test123
##查询缓存
hello:test123
现在就都是查询缓存了。其实对于查询并发这样做是比好的。打个比方:
一堆人需要从一个出口出去,这个出口有一个小门已经可以通过,还有一个大门未打开,需要从小门出去打开。这个大门非常大(redis查询速度非常快)。如果大批的人同时出去(高并发),那么必然在小门挤很长的时间。此时,如果现有一个人去把大门先打开,那么后面的人(包括本来要挤小门的人)可以直接从大门出去,效率肯定是后面的划算。
对于查询实时一次比较慢的情况下,可以先让一个线程进去。让其它线程等待。
当然,这样并不完美。当缓存失效,那么查询就会卡顿一下。为了保证用户能一直流畅的查询,我有如下两种方案:
1.在缓存存在的时间里的进行异步查询去更新缓存。
2.使用二级缓存,并且当一级缓存失效的时候,会去读取二级缓存,二级缓存异步更新。(二级缓存的时间可以很长)
下面是第一种策略的代码模拟:
[java] view
plain copy
public static void query() {
BaseService bs = new BaseService();
Jedis jedis = jedisPool.getResource();
String req = "test123";
String res;
if (jedis.get(req) == null) {
System.out.println("##查询接口服务");
res = bs.query(req);
jedis.setex(req, 100, res);
} else {
System.out.println("##查询缓存");
res = jedis.get(req);
System.out.println("缓存剩余时间:"+jedis.ttl(req));
// 当时间超过10秒,异步更新数据到缓存
if (jedis.ttl(req) < 90) {
//模拟得到推送,接受推送,执行
new Thread() {
@Override
public void run() {
String res = bs.query(req);
jedis.setex(req, 100, res);
System.out.println("异步更新数据:"+req);
}
}.start();
}
}
System.out.println(res);
jedisPool.returnResource(jedis);
}
运行结果:
[java] view
plain copy
##查询缓存
缓存剩余时间:67
hello:test123
##查询缓存
缓存剩余时间:67
hello:test123
##查询缓存
缓存剩余时间:67
hello:test123
##查询缓存
缓存剩余时间:67
hello:test123
##查询缓存
缓存剩余时间:67
hello:test123
异步更新数据:test123
异步更新数据:test123
异步更新数据:test123
异步更新数据:test123
异步更新数据:test123
为了保证一段时间内,更新一个缓存只执行一次,做如下锁
[java] view
plain copy
public static void main(String[] args) {
beginTime = System.currentTimeMillis();
for (int i = 0; i < 5; i++) {
new Thread() {
@Override
public void run() {
// 执行查询
query();
// System.out.println(System.currentTimeMillis()-beginTime);
}
}.start();
}
}
public static void query() {
BaseService bs = new BaseService();
Jedis jedis = jedisPool.getResource();
String req = "test123";
String res;
System.out.println(jedis.get(req));
if (jedis.get(req) == null) {
System.out.println("##查询接口服务");
res = bs.query(req);
jedis.setex(req, 100, res);
} else {
System.out.println("##查询缓存");
res = jedis.get(req);
System.out.println("缓存剩余时间:"+jedis.ttl(req));
// 当时间超过10秒,异步更新数据到缓存
if (jedis.ttl(req) < 90) {
//模拟得到推送,接受推送,执行
new Thread() {
@Override
public void run() {
//保证5秒内,一条数据只更新一次
Long incr = jedis.incr("incr-flag-"+req);
jedis.expire("incr-flag-"+req, 5);
if(1 == incr){
String resT = bs.query(req);
jedis.setex(req, 100, resT);
System.out.println("异步更新数据:"+req);
}
}
}.start();
}
}
jedisPool.returnResource(jedis);
}
运行两次,间隔10秒。运行结果:
[java] view
plain copy
hello:test123
##查询缓存
hello:test123
hello:test123
hello:test123
hello:test123
##查询缓存
##查询缓存
##查询缓存
##查询缓存
异步更新数据:test123
这样,即可保证一次查询比较耗时的情况下,用户能流畅的查询。用户体验大大提升
相关文章推荐
- 基于redis的缓存机制的思考和优化
- 【Redis缓存机制】11.Java连接Redis_Jedis_测试联通
- 彻底弄懂 Http 缓存机制 - 基于缓存策略三要素分解法
- \t\t基于C#的缓存优化
- 基于Redis分析sql实现主动缓存
- UI基础第十四弹:UITableviewcell的性能优化和缓存机制
- 【Redis缓存机制】5.List链表类型介绍和操作
- Android之ListView异步加载网络图片(优化缓存机制)
- 【Redis缓存机制】7.SortSet排序集合类型操作
- Django- DRF redis缓存机制
- 【Redis缓存机制】快照持久化和AOF持久化
- Redis之利用锁机制来防止缓存过期产生的惊群现象
- Semaphore机制与TCp通信中的应用以及双缓存优化
- 分布式系统基于缓存机制的实时开关系统——可将一个指令同时推送给N个主机
- H5 缓存机制浅析 移动端 Web 加载性能优化
- Android 中加载网络资源时的优化 缓存和异步机制
- shiro安全框架扩展教程--如何扩展实现我们的缓存机制(第三方容器redis,memcached)
- Android之ListView异步加载网络图片(优化缓存机制)
- 彻底弄懂 Http 缓存机制 - 基于缓存策略三要素分解法