Java專案中使用Redis快取案例
快取的目的是為了提高系統的效能,快取中的資料主要有兩種:
1.熱點資料。我們將經常訪問到的資料放在快取中,降低資料庫I/O,同時因為快取的資料的高速查詢,加快整個系統的響應速度,也在一定程度上提高併發量。
2.查詢耗時的資料。如果有一些資料查詢十分耗時,那麼每次請求這些資料時,都去資料庫查詢的話,會使得系統響應速度特別低,資料庫cpu 100%。將這些資料放快取,會極大提高系統響應速度,但同時資料實時性較差。
最近工作中有碰到需要使用快取的情況,場景如下:app端看板統計資料彙總,在開啟app時載入看板資料,彙總資料來源於不同的庫,各個資料的生成介面已經寫好,只需要去呼叫介面整合資料返回即可。
具體我們來看看是怎麼實現的吧。
第一步,取mysql中查詢各個介面的資訊:
getPanelInfo.java
/* service程式碼略*/
List<PanelDto> panels = panelService.getAllPanels(); //得到介面的名稱,介面的url
第二步,根據拿到的資訊生成請求引數:
getPanelInfo.java
WrapResponseModel resonseModel = new WrapResponseModel(); Map<String, String> headers = new HashMap<>(); headers.put("username", username); headers.put("token",token); List<String> content = new ArrayList<String>(); for(PanelDto panelDto:panel){ //傳送http請求 content.add(HttpRequestUtils.get(panelDto.getUrl(), headers)); } // 返回格式 responseModel.setCode(SUCCESS_CODE); responseModel.setData(content);
第三步,傳送http請求呼叫介面:
HttpRequestUtils.java
public static String get(String url, Map<String, String> headers) { RequestConfig config = RequestConfig.custom().setConnectTimeout(TIME_OUT).setConnectionRequestTimeout(TIME_OUT).setSocketTimeout(TIME_OUT).build(); String ret = null; //建立HttpClient物件 CloseableHttpClient closeHttpClient = HttpClients.createDefault(); CloseableHttpResponse httpResponse = null; //傳送get請求 HttpGet httpGet = new HttpGet(url); try { // add header if (Objects.nonNull(headers)) { Set<String> keys = headers.keySet(); for (Iterator<String> i = keys.iterator(); i.hasNext(); ) { String key = i.next(); httpGet.addHeader(key, headers.get(key)); } } httpGet.setConfig(config); //執行Get請求 得到Response物件 httpResponse = closeHttpClient.execute(httpGet); //httpResponse.getStatusLine() 響應頭資訊 int httpResponseCode = httpResponse.getStatusLine().getStatusCode(); if (200 != httpResponseCode) { logger.error("http返回值異常, httpResponseCode = " + httpResponseCode); } //返回物件 HttpEntity httpEntity = httpResponse.getEntity(); ret = EntityUtils.toString(httpEntity, "UTF-8"); } catch (UnsupportedEncodingException e) { logger.error(e.getMessage(), e); } catch (ClientProtocolException e) { logger.error(e.getMessage(), e); } catch (IOException e) { //logger.error(e.getMessage(), e); } finally { if (httpResponse != null) { try { httpResponse.close(); } catch (IOException e) { logger.error(e.getMessage(), e); } } if (closeHttpClient != null) { try { closeHttpClient.close(); } catch (IOException e) { logger.error(e.getMessage(), e); } } } return ret; }
第四步,查詢資料set進redis,之後返回查詢的資料:
getPanelInfo.java
if (!Objects.equals(redisCache, "false")) {
//redisttl過期時間 redisProxyHandler.set(redisKey, JSON.toJSONString(responseModel), REDIS_TTL); logger.error("set succeed!!!!!!!!!!!!!!!!"); }
redisHandler.java
public void set(String key, String value, int seconds) { redisCacheProvider.set(key, value, seconds); }
redisProvider.java
@Autowired
private JedisPool jedisPool;
public Jedis getJedis() {
Jedis jedis = this.jedisPool.getResource();
// 使用index為2的database
jedis.select(2);
return jedis;
}
public void set(String key, String value, int seconds) {
Jedis jedis = null;
try {
jedis = getJedis();
jedis.setex(key, seconds, value);
Long exp = jedis.ttl(key);
if (exp < 0) {
throw new RuntimeException("data time out!");19 }
} catch (Throwable e) {
logger.error(e.getMessage(), e);
throw new TokenException(e.getMessage());
} finally {
if(jedis != null){jedis.close;}
}
}
第五步,請求介面的時候,先請求redis快取,如果命中則返回命中資料,否則,將執行上面的傳送http請求在拼湊資料返回的程式碼:
getPanelInfo.java
String panelInfo = redisProxyHandler.get(redisKey);
Long expire = redisProxyHandler.getExpire(redisKey);
//命中才返回,否則會去發http請求
if (Objects.nonNull(panelInfo) && (expire > 0) && expire <REDIS_TTL) {
responseModel = JSON.parseObject(panelInfo, WrapResponseModel.class);
return responseModel;
}
redisHandler.java
public String get(String key) return redisCacheProvider.get(key); }
redisProvider.java
public String get(String key) {
String value = null;
Jedis jedis = null;
try {
jedis = getJedis();
value = jedis.get(key);
} catch (Throwable e) {
logger.error(e.getMessage(), e);
throw new TokenException(e.getMessage());
} finally {
if(jedis != null){
jedis.close();
}
}
return value;
}
redis相關配置檔案如下
applicationContext.xml
<!-- 讀取配置檔案資訊 -->
<context:property-placeholder location="classpath:redis.properties" file-encoding="UTF-8" ignore-unresolvable="true"/>
<!-- Jedis 連線池配置 -->
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxTotal" value="${redis.pool.maxTotal}"/>
<property name="maxIdle" value="${redis.pool.maxIdle}"/>
<property name="maxWaitMillis" value="${redis.pool.maxWaitMillis}"/>
<property name="testOnBorrow" value="${redis.pool.testOnBorrow}"/>
</bean>
<bean id="jedisPool" class="redis.clients.jedis.JedisPool">
<constructor-arg ref="jedisPoolConfig"/>
<constructor-arg value="${jedis.host}" type="java.lang.String"/>
<constructor-arg type="int" value="${jedis.port}"/>
</bean>
redis.properties
# 控制一個pool可分配多少個jedis例項
redis.pool.maxTotal=1000
# 控制一個pool最多有多少個狀態為idle(空閒)的jedis例項
redis.pool.maxIdle=200
# 表示當borrow一個jedis例項時,最大的等待時間,如果超過等待時間,則直接丟擲JedisConnectionException
redis.pool.maxWaitMillis=2000
#在borrow一個jedis例項時,是否提前進行validate操作;如果為true,則得到的jedis例項均是可用的
redis.pool.testOnBorrow=true
# redis 單機
# 單機 host
jedis.host=127.0.0.1
# 單機 port
jedis.port=6379