Redis缓存的具体使用场景

根据文章的总结:http://blog.csdn.net/qq_18860653/article/details/54893095

Redis用作缓存,减少了对数据库的压力。一般如果有一个请求,那么或者时时调用接口查询,或者从缓存中查找。下面我们讨论redis的具体使用场景。

准备工作JeidsPool:

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

/**
 * Created by TangXW on 2017/7/6.
 */
public final class RedisUtil {
    private static String HOST = "127.0.0.1";

    private static int PORT = 6379;

    // 最大连接数
    // 默认为8,-1表示无限制
    private static int MAX_ACTIVE =  100;

    // 空闲的jedis实例,默认为8
    private static int MAX_IDLE = 10;

    // 等待可用连接的最大时间,-1无限制,单位毫秒
    private static int MAX_WAIT = 10000;

    private static int TIMEOUT = 10000;

    // 在borrow一个jedis实例时,是否提前进行validate操作,如果为true,则得到的jedis实例均是可用的
    private static boolean TEST_ON_BORROW = true;

    private static JedisPool jedisPool = null;

    /**
     * 初始化jedispool
     */
    static{
        try{
            JedisPoolConfig config = new JedisPoolConfig();
            config.setMaxTotal(MAX_ACTIVE);
            config.setMaxIdle(MAX_IDLE);
            config.setMaxWaitMillis(MAX_WAIT);
            config.setTestOnBorrow(TEST_ON_BORROW);
            jedisPool = new JedisPool(config, HOST, PORT, TIMEOUT);
        }catch (Exception e){
            e.printStackTrace();;
        }
    }

    /**
     * 获取Jedis实例
     * @return
     */
    public synchronized static Jedis getJedis(){
        try {
            if(jedisPool != null){
                Jedis resource = jedisPool.getResource();
                return resource;
            }else{
                return null;
            }
        }catch (Exception e){
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 释放jedis资源
     * @param jedis
     */
    public static void returnResource(final Jedis jedis){
        if(jedis != null){
            jedisPool.returnResource(jedis);
        }
    }
}

模拟时时查询接口:

/**
 * Created by TangXW on 2017/7/6.
 */
public class BaseService {
    public String query(String req){
        return req;
    }
}
然后我们模拟5个请求,并发执行。

/**
 * Created by TangXW on 2017/7/6.
 */
public class RedisCacheTest {

    public static void main(String[] args) {
        for(int i = 0; i < 5; i++){
            new Thread() {
                @Override
                public void run() {
                    runQuery();
                }
            }.start();
        }
    }

    /**
     * 查询方法
     */
    public static void runQuery(){
        BaseService bs = new BaseService();
        Jedis jedis = RedisUtil.getJedis();
        String req = "requesttest";
        String result = "";
        // 如果缓存中没有,则时时查询
        if(jedis.get(req) == null){
            System.out.println("接口查询...");
            result = bs.query(req);
            jedis.set(req, result);
        }else{
            System.out.println("缓存查询...");
            result = jedis.get(req);
        }
        System.out.println(result);
        RedisUtil.returnResource(jedis);
    }
}

贴出查询结果:

接口查询...
requesttest
缓存查询...
requesttest
缓存查询...
缓存查询...
requesttest
requesttest
缓存查询...
requesttest
可以看到,第一次查询因为没有缓存,所以调用接口查询,后面缓存中有了,直接从缓存中取。因为这里模拟接口调用的速度非常快,日常中接口调用还要从数据库查数据,速度不会这么快,那么我们在接口中让其等待1秒,更贴近真实场景。

/**
 * Created by TangXW on 2017/7/6.
 */
public class BaseService {
    public String query(String req){
        try{
            Thread.sleep(1000);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        return req;
    }
}

清空一下缓存

127.0.0.1:6379> flushdb
OK
127.0.0.1:6379>

重新测试,贴出结果:

接口查询...
接口查询...
接口查询...
接口查询...
接口查询...
requesttest
requesttest
requesttest
requesttest
requesttest

发现结果变成了这样。很合理。因为等待并发执行,等待一秒,还没来得及往缓存中存数据,线程就执行好了。那么怎么处理可以既能接口又能缓存。我们加锁看一下。

  public static void main(final String[] args) {
        for(int i = 0; i < 5; i++){
            new Thread() {
                @Override
                public void run() {
                    synchronized (args){
                        runQuery();
                    }
                }
            }.start();
        }
    }
flushdb,重新运行:

接口查询...
requesttest
缓存查询...
requesttest
缓存查询...
requesttest
缓存查询...
requesttest
缓存查询...
requesttest
理想结果,因为加锁,每次只能允许一个请求。
但是又有一个问题,就是现在缓存是持久化的,如果缓存有一定的缓存时间,也就是当缓存失效时,那么查询就会卡顿一下,为了保证流畅,我们再缓存存在的时间里去异步更新缓存。

public static void runQuery(){
        final BaseService bs = new BaseService();
        final Jedis jedis = RedisUtil.getJedis();
        final String req = "requesttest";
        String result = "";
        // 如果缓存中没有,则时时查询
        if(jedis.get(req) == null){
            System.out.println("接口查询...");
            result = bs.query(req);
            jedis.setex(req, 100, result); // 100秒的缓存时间
        }else{
            System.out.println("缓存查询...");
            System.out.println("缓存剩余时间 = " + jedis.ttl(req));
            result = jedis.get(req);
            if(jedis.ttl(req) < 90){  // 每当缓存过了十秒,就异步更新
                new Thread(){
                    @Override
                    public void run(){
                        System.out.println("异步更新数据...");
                        String syresult = bs.query(req);
                        jedis.setex(req, 100, syresult);
                    }
                }.start();
            }
        }
        System.out.println(result);
        RedisUtil.returnResource(jedis);
    }

flushdb, 运行结果:

缓存查询...
缓存剩余时间 = 84
requesttest
异步更新数据...
缓存查询...
缓存剩余时间 = 84
requesttest
缓存查询...
异步更新数据...
缓存剩余时间 = 84
requesttest
异步更新数据...
缓存查询...
缓存剩余时间 = 84
requesttest
异步更新数据...
缓存查询...
缓存剩余时间 = 84
requesttest
异步更新数据...
结果很合理,缓存过了十秒,小于90了,就去异步更新数据。

还有一个问题,5个线程同时运行时,都发现缓存小于90,都去异步更新,为了保证只更新一次,我们加锁处理。

public static void runQuery(){
        final BaseService bs = new BaseService();
        final Jedis jedis = RedisUtil.getJedis();
        final String req = "requesttest";
        String result = "";
        // 如果缓存中没有,则时时查询
        if(jedis.get(req) == null){
            System.out.println("接口查询...");
            result = bs.query(req);
            jedis.setex(req, 100, result); // 100秒的缓存时间
        }else{
            System.out.println("缓存查询...");
            System.out.println("缓存剩余时间 = " + jedis.ttl(req));
            result = jedis.get(req);
            if(jedis.ttl(req) < 90){
                new Thread(){
                    @Override
                    public void run(){
                        Jedis j = RedisUtil.getJedis();
                        Long incr = j.incr("timeout"); // 赋值1
                        j.expire("timeout", 5);  // key 5秒失效倒计时
                        if(incr == 1){  // 保证5秒内只更新一次
                            System.out.println("异步更新数据...");
                            String syresult = bs.query(req);
                            j.setex(req, 100, syresult);
                        }
                    }
                }.start();
            }
        }
        System.out.println(result);
        RedisUtil.returnResource(jedis);
    }

注意上面异步更新run方法中要获取一个新的jedis实例,否则会因为多个线程(这里是2个)共用一个jedis实例,抛出java.lang.Long cannot be cast to [B的异常。
flushdb重新运行:

缓存查询...
缓存剩余时间 = 88
requesttest
缓存查询...
异步更新数据...
缓存剩余时间 = 88
requesttest
缓存查询...
缓存剩余时间 = 88
requesttest
缓存查询...
缓存剩余时间 = 88
requesttest
缓存查询...
缓存剩余时间 = 88
requesttest

可以看到,当缓存剩余88秒时我们再次运行,发现在5秒的时间里只异步更新缓存一次。
以上就是这次的总结。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值