《转》基于redis的缓存机制的思考和优化

news/2024/7/10 6:00:54 标签: redis, 缓存机制, 优化, 慢查询缓存

不错的文章,转给大家看看。

原文地址:http://blog.csdn.net/qq_18860653/article/details/54893095。再次感谢原博主。

相对我们对于redis的使用场景都已经想当的熟悉。对于大量的数据,为了缓解接口(数据库)的压力,我们对查询的结果做了缓存的策略。一开始我们的思路是这样的。

1.执行查询

2.缓存中存在数据 -> 查询缓存 

3.缓存中不存在数据 -> 查询实时接口


对此,我简单模拟了我们的缓存机制

这是一个查询实时的服务

[java]  view plain  copy
  1. <span style="font-size:14px;">package yyf.Jedis.toolsByRedis.cacheCacheTools;  
  2.   
  3. /** 
  4.  * 模拟服务 
  5.  * @author yuyufeng 
  6.  * 
  7.  */  
  8. public class BaseService {  
  9.     public String query(String req) {  
  10.           
  11.         return "hello:" + req;  
  12.     }  
  13. }  
  14. </span>  

从代码中我们可以看到,这个服务反应应该是非常快的。

[java]  view plain  copy
  1. <span style="font-size:14px;">package yyf.Jedis.toolsByRedis.cacheCacheTools;  
  2.   
  3. import redis.clients.jedis.Jedis;  
  4. import redis.clients.jedis.JedisPool;  
  5. import redis.clients.jedis.JedisPoolConfig;  
  6.   
  7. public class CacheCacheToolTest {  
  8.     static JedisPool jedisPool;  
  9.     static {  
  10.         JedisPoolConfig config = new JedisPoolConfig();  
  11.         config.setMaxTotal(100);  
  12.         config.setMaxIdle(5);  
  13.         config.setMaxWaitMillis(1000);  
  14.         config.setTestOnBorrow(false);  
  15.   
  16.         jedisPool = new JedisPool(config, "127.0.0.1"63791000);  
  17.         Jedis jedis = jedisPool.getResource();  
  18.         jedisPool.returnResource(jedis);  
  19.   
  20.     }  
  21.   
  22.     public static void main(String[] args) {  
  23.         for (int i = 0; i < 5; i++) {  
  24.             new Thread(){@Override  
  25.             public void run() {  
  26.                 //执行查询  
  27.                 query();  
  28.             }}.start();  
  29.               
  30.         }  
  31.   
  32.     }  
  33.   
  34.     public static void query() {  
  35.         BaseService bs = new BaseService();  
  36.         Jedis jedis = jedisPool.getResource();  
  37.         String req = "test123";  
  38.         String res;  
  39.         if (jedis.get(req) == null) {  
  40.             System.out.println("##查询接口服务");  
  41.             res = bs.query(req);  
  42.             jedis.setex(req, 10, res);  
  43.         } else {  
  44.             System.out.println("##查询缓存");  
  45.             res = jedis.get(req);  
  46.         }  
  47.         System.out.println(res);  
  48.         jedisPool.returnResource(jedis);  
  49.     }  
  50.   
  51. }  
  52. </span>  

当5个并发进来的时候,第一个查询实时服务,其余的查询缓存。

[java]  view plain  copy
  1. <span style="font-size:14px;">##查询接口服务  
  2. hello:test123  
  3. ##查询缓存  
  4. ##查询缓存  
  5. ##查询缓存  
  6. hello:test123  
  7. hello:test123  
  8. hello:test123  
  9. ##查询缓存  
  10. hello:test123  
  11. </span>  

看到结果,我们似乎觉得这个查询非常的合理,当时当我们的实时接口查询速度很慢的时候,就暴露出问题来了。

[java]  view plain  copy
  1. <span style="font-size:14px;">package yyf.Jedis.toolsByRedis.cacheCacheTools;  
  2.   
  3. /** 
  4.  * 模拟服务 
  5.  * @author yuyufeng 
  6.  * 
  7.  */  
  8. public class BaseService {  
  9.     public String query(String req) {  
  10.         try {  
  11.             Thread.sleep(1000);  
  12.         } catch (InterruptedException e) {  
  13.             e.printStackTrace();  
  14.         }  
  15.         return "hello:" + req;  
  16.     }  
  17. }  
  18. </span>  

[java]  view plain  copy
  1. <span style="font-size:14px;">##查询接口服务  
  2. ##查询接口服务  
  3. ##查询接口服务  
  4. ##查询接口服务  
  5. ##查询接口服务  
  6. hello:test123  
  7. hello:test123  
  8. hello:test123  
  9. hello:test123  
  10. hello:test123  
  11. </span>  
结果是,全部都查询的接口服务。这样会导致并发一高,缓存就相当于作用非常小了。


如果在查询实时过程时,对于相同的请求,能够让其等待,那么效率会有大大的提升:(为了模拟,加锁处理)

[java]  view plain  copy
  1. public static void main(String[] args) {  
  2.         beginTime = System.currentTimeMillis();  
  3.         for (int i = 0; i < 5; i++) {  
  4.             new Thread(){@Override  
  5.             public void run() {  
  6.                 //执行查询  
  7.                 synchronized (args) {  
  8.                     query();  
  9.                 }  
  10.                   
  11.                 //System.out.println(System.currentTimeMillis()-beginTime);  
  12.             }}.start();  
  13.               
  14.         }  
  15.   
  16.     }  

[java]  view plain  copy
  1. ##查询缓存  
  2. hello:test123  
  3. ##查询缓存  
  4. hello:test123  
  5. ##查询缓存  
  6. hello:test123  
  7. ##查询缓存  
  8. hello:test123  
  9. ##查询缓存  
  10. hello:test123  

现在就都是查询缓存了。其实对于查询并发这样做是比好的。打个比方:

一堆人需要从一个出口出去,这个出口有一个小门已经可以通过,还有一个大门未打开,需要从小门出去打开。这个大门非常大(redis查询速度非常快)。如果大批的人同时出去(高并发),那么必然在小门挤很长的时间。此时,如果现有一个人去把大门先打开,那么后面的人(包括本来要挤小门的人)可以直接从大门出去,效率肯定是后面的划算。


对于查询实时一次比较慢的情况下,可以先让一个线程进去。让其它线程等待。


当然,这样并不完美。当缓存失效,那么查询就会卡顿一下。为了保证用户能一直流畅的查询,我有如下两种方案:

1.在缓存存在的时间里的进行异步查询去更新缓存。

2.使用二级缓存,并且当一级缓存失效的时候,会去读取二级缓存,二级缓存异步更新。(二级缓存的时间可以很长)


下面是第一种策略的代码模拟:

[java]  view plain  copy
  1. public static void query() {  
  2.         BaseService bs = new BaseService();  
  3.         Jedis jedis = jedisPool.getResource();  
  4.         String req = "test123";  
  5.         String res;  
  6.         if (jedis.get(req) == null) {  
  7.             System.out.println("##查询接口服务");  
  8.             res = bs.query(req);  
  9.             jedis.setex(req, 100, res);  
  10.         } else {  
  11.             System.out.println("##查询缓存");  
  12.             res = jedis.get(req);  
  13.             System.out.println("缓存剩余时间:"+jedis.ttl(req));  
  14.             // 当时间超过10秒,异步更新数据到缓存  
  15.             if (jedis.ttl(req) < 90) {  
  16.                 //模拟得到推送,接受推送,执行  
  17.                 new Thread() {  
  18.                     @Override  
  19.                     public void run() {  
  20.                         String res = bs.query(req);  
  21.                         jedis.setex(req, 100, res);  
  22.                         System.out.println("异步更新数据:"+req);  
  23.                     }  
  24.                 }.start();  
  25.   
  26.             }  
  27.   
  28.         }  
  29.         System.out.println(res);  
  30.         jedisPool.returnResource(jedis);  
  31.     }  


运行结果:
[java]  view plain  copy
  1. ##查询缓存  
  2. 缓存剩余时间:67  
  3. hello:test123  
  4. ##查询缓存  
  5. 缓存剩余时间:67  
  6. hello:test123  
  7. ##查询缓存  
  8. 缓存剩余时间:67  
  9. hello:test123  
  10. ##查询缓存  
  11. 缓存剩余时间:67  
  12. hello:test123  
  13. ##查询缓存  
  14. 缓存剩余时间:67  
  15. hello:test123  
  16. 异步更新数据:test123  
  17. 异步更新数据:test123  
  18. 异步更新数据:test123  
  19. 异步更新数据:test123  
  20. 异步更新数据:test123  

为了保证一段时间内,更新一个缓存只执行一次,做如下锁

[java]  view plain  copy
  1. public static void main(String[] args) {  
  2.         beginTime = System.currentTimeMillis();  
  3.         for (int i = 0; i < 5; i++) {  
  4.             new Thread() {  
  5.                 @Override  
  6.                 public void run() {  
  7.                     // 执行查询  
  8.                         query();  
  9.                     // System.out.println(System.currentTimeMillis()-beginTime);  
  10.                 }  
  11.             }.start();  
  12.   
  13.         }  
  14.   
  15.     }  
  16.   
  17.     public static void query() {  
  18.         BaseService bs = new BaseService();  
  19.         Jedis jedis = jedisPool.getResource();  
  20.         String req = "test123";  
  21.         String res;  
  22.         System.out.println(jedis.get(req));  
  23.         if (jedis.get(req) == null) {  
  24.             System.out.println("##查询接口服务");  
  25.             res = bs.query(req);  
  26.             jedis.setex(req, 100, res);  
  27.         } else {  
  28.             System.out.println("##查询缓存");  
  29.             res = jedis.get(req);  
  30.             System.out.println("缓存剩余时间:"+jedis.ttl(req));  
  31.             // 当时间超过10秒,异步更新数据到缓存  
  32.             if (jedis.ttl(req) < 90) {  
  33.                 //模拟得到推送,接受推送,执行  
  34.                 new Thread() {  
  35.                     @Override  
  36.                     public void run() {  
  37.                           
  38.                         //保证5秒内,一条数据只更新一次  
  39.                         Long incr = jedis.incr("incr-flag-"+req);  
  40.                         jedis.expire("incr-flag-"+req, 5);  
  41.                           
  42.                         if(1 == incr){  
  43.                             String resT = bs.query(req);  
  44.                             jedis.setex(req, 100, resT);  
  45.                             System.out.println("异步更新数据:"+req);  
  46.                         }  
  47.                     }  
  48.                 }.start();  
  49.   
  50.             }  
  51.   
  52.         }  
  53.         jedisPool.returnResource(jedis);  
  54.     }  

运行两次,间隔10秒。运行结果:

[java]  view plain  copy
  1. hello:test123  
  2. ##查询缓存  
  3. hello:test123  
  4. hello:test123  
  5. hello:test123  
  6. hello:test123  
  7. ##查询缓存  
  8. ##查询缓存  
  9. ##查询缓存  
  10. ##查询缓存  
  11. 异步更新数据:test123  


这样,即可保证一次查询比较耗时的情况下,用户能流畅的查询。用户体验大大提升



http://www.niftyadmin.cn/n/1712085.html

相关文章

java乐观锁实现案例

简单说说乐观锁。乐观锁是相对于悲观锁而言。悲观锁认为&#xff0c;这个线程&#xff0c;发生并发的可能性极大&#xff0c;线程冲突几率大&#xff0c;比较悲观。一般用synchronized实现&#xff0c;保证每次操作数据不会冲突。乐观锁认为&#xff0c;线程冲突可能性小&#…

致java初学者:理解每一句java代码,给出一个简单实例。

工作或学习中&#xff0c;我们写过很多代码&#xff0c;但往往我们并不了解每行代码都干了些什么。以下给你个简单的实例&#xff0c;你是否会有所触动。 注释里是对代码的理解。 package com.vcredit.jdev.base;public class Test { public static void main(String[] args) {…

Linux命令详解之—less命令

原文地址&#xff1a;https://www.cnblogs.com/waitig/archive/2016/09/28/5916338.htmlLinux下还有一个与more命令非常类似的命令--less命令&#xff0c;相比于more命令&#xff0c;less命令更加灵活强大一些&#xff0c;今天就给大家介绍下Linux下的less命令。更多Linux命令详…

mybatis plus介绍

简介 Mybatis-Plus&#xff08;简称MP&#xff09;是一个 Mybatis 的增强工具&#xff0c;在 Mybatis 的基础上只做增强不做改变&#xff0c;为简化开发、提高效率而生。我们的愿景是成为Mybatis最好的搭档&#xff0c;就像 Contra Game 中的1P、2P&#xff0c;基友搭配&#x…

hashCode

在前面三篇博文中LZ讲解了&#xff08;HashMap、HashSet、HashTable&#xff09;&#xff0c;在其中LZ不断地讲解他们的put和get方法&#xff0c;在这两个方法中计算key的hashCode应该是最重要也是最精华的部分&#xff0c;所以下面LZ揭开hashCode的“神秘”面纱。 hashCode的作…

mysql like查询 使用索引

在使用MySQL的查询语句时&#xff0c;可能要利用到like语句。通常情况下&#xff0c;是看不出来语句的差异性的&#xff0c;因为数据量比较小&#xff0c;但是当数据量达到千万级以上的时候&#xff0c;sql语句的执行效率就显得非常重要了。LZ写like语句的时候&#xff0c;一般…

分享一个设计模式专栏

之前看了一些设计模式相关的东西&#xff0c;看到一个专栏很不错&#xff0c;分享给大家。 专栏链接&#xff1a;https://blog.csdn.net/column/details/pattern.html?&page1

maven引入本地或者第三方jar(scope 为 system 的 jar 包)

由于项目中需要引入第三方jar包&#xff0c;但是由于是外部jar&#xff0c;不太适合放入公司内部maven仓库。故采用本地jar包形式引入&#xff0c;或者称之为第三方jar包。方法如下&#xff1a; 1.放入若需jar包&#xff0c;放在工程src目录同级就行。 2.pom.xml引入 <depen…