您的位置:首页 > 数据库 > Redis

《转》基于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  

这样,即可保证一次查询比较耗时的情况下,用户能流畅的查询。用户体验大大提升
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息