比尔云BierYun--阿里云最新优惠活动
阿里云优惠码丨阿里云代金券

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

基于redis的缓存机制的思考和优化http://www.bieryun.com/1718.html

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

1.执行查询

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

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

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

这是一个查询实时的服务

[java] view plain copy

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

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

[java] view plain copy

  1. <span style=“font-size:14px;”>package yyf.Jedis.toolsByRedis.cacheCacheTools;
  2. import redis.clients.jedis.Jedis;
  3. import redis.clients.jedis.JedisPool;
  4. import redis.clients.jedis.JedisPoolConfig;
  5. public class CacheCacheToolTest {
  6.     static JedisPool jedisPool;
  7.     static {
  8.         JedisPoolConfig config = new JedisPoolConfig();
  9.         config.setMaxTotal(100);
  10.         config.setMaxIdle(5);
  11.         config.setMaxWaitMillis(1000);
  12.         config.setTestOnBorrow(false);
  13.         jedisPool = new JedisPool(config, “127.0.0.1”63791000);
  14.         Jedis jedis = jedisPool.getResource();
  15.         jedisPool.returnResource(jedis);
  16.     }
  17.     public static void main(String[] args) {
  18.         for (int i = 0; i < 5; i++) {
  19.             new Thread(){@Override
  20.             public void run() {
  21.                 //执行查询
  22.                 query();
  23.             }}.start();
  24.         }
  25.     }
  26.     public static void query() {
  27.         BaseService bs = new BaseService();
  28.         Jedis jedis = jedisPool.getResource();
  29.         String req = “test123”;
  30.         String res;
  31.         if (jedis.get(req) == null) {
  32.             System.out.println(“##查询接口服务”);
  33.             res = bs.query(req);
  34.             jedis.setex(req, 10, res);
  35.         } else {
  36.             System.out.println(“##查询缓存”);
  37.             res = jedis.get(req);
  38.         }
  39.         System.out.println(res);
  40.         jedisPool.returnResource(jedis);
  41.     }
  42. }
  43. </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.  * @author yuyufeng
  5.  *
  6.  */
  7. public class BaseService {
  8.     public String query(String req) {
  9.         try {
  10.             Thread.sleep(1000);
  11.         } catch (InterruptedException e) {
  12.             e.printStackTrace();
  13.         }
  14.         return “hello:” + req;
  15.     }
  16. }
  17. </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.                 //System.out.println(System.currentTimeMillis()-beginTime);
  11.             }}.start();
  12.         }
  13.     }

 

[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.         System.out.println(res);
  28.         jedisPool.returnResource(jedis);
  29.     }

运行结果:

[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.     public static void query() {
  15.         BaseService bs = new BaseService();
  16.         Jedis jedis = jedisPool.getResource();
  17.         String req = “test123”;
  18.         String res;
  19.         System.out.println(jedis.get(req));
  20.         if (jedis.get(req) == null) {
  21.             System.out.println(“##查询接口服务”);
  22.             res = bs.query(req);
  23.             jedis.setex(req, 100, res);
  24.         } else {
  25.             System.out.println(“##查询缓存”);
  26.             res = jedis.get(req);
  27.             System.out.println(“缓存剩余时间:”+jedis.ttl(req));
  28.             // 当时间超过10秒,异步更新数据到缓存
  29.             if (jedis.ttl(req) < 90) {
  30.                 //模拟得到推送,接受推送,执行
  31.                 new Thread() {
  32.                     @Override
  33.                     public void run() {
  34.                         //保证5秒内,一条数据只更新一次
  35.                         Long incr = jedis.incr(“incr-flag-“+req);
  36.                         jedis.expire(“incr-flag-“+req, 5);
  37.                         if(1 == incr){
  38.                             String resT = bs.query(req);
  39.                             jedis.setex(req, 100, resT);
  40.                             System.out.println(“异步更新数据:”+req);
  41.                         }
  42.                     }
  43.                 }.start();
  44.             }
  45.         }
  46.         jedisPool.returnResource(jedis);
  47.     }

运行两次,间隔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

 

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

未经允许不得转载:比尔云 » 基于redis的缓存机制的思考和优化
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

强烈推荐

高性能SSD云服务器ECS抗攻击,高可用云数据库RDS