根据文章的总结: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秒的时间里只异步更新缓存一次。
以上就是这次的总结。