緩存的目的是為了提高系統的性能,緩存中的數據主要有兩種:
1.熱點數據。我們將經常訪問到的數據放在緩存中,降低數據庫I/O,同時因為緩存的數據的高速查詢,加快整個系統的響應速度,也在一定程度上提高並發量。
2.查詢耗時的數據。如果有一些數據查詢十分耗時,那么每次請求這些數據時,都去數據庫查詢的話,會使得系統響應速度特別低,數據庫cpu 100%。將這些數據放緩存,會極大提高系統響應速度,但同時數據實時性較差。
最近工作中有碰到需要使用緩存的情況,場景如下:app端看板統計數據匯總,在打開app時加載看板數據,匯總數據來源於不同的庫,各個數據的生成接口已經寫好,只需要去調用接口整合數據返回即可。
具體我們來看看是怎么實現的吧。
第一步,取mysql中查詢各個接口的信息:
getPanelInfo.java
1 /* service代碼略*/ 2 List<PanelDto> panels = panelService.getAllPanels(); //得到接口的名稱,接口的url
第二步,根據拿到的信息生成請求參數:
getPanelInfo.java
1 WrapResponseModel resonseModel = new WrapResponseModel(); 2 Map<String, String> headers = new HashMap<>(); 3 headers.put("username", username); 4 headers.put("token",token); 5 List<String> content = new ArrayList<String>(); 6 for(PanelDto panelDto:panel){ 7 //發送http請求 8 content.add(HttpRequestUtils.get(panelDto.getUrl(), headers)); 9 } 10 // 返回格式 11 responseModel.setCode(SUCCESS_CODE); 12 responseModel.setData(content);
第三步,發送http請求調用接口:
HttpRequestUtils.java
1 public static String get(String url, Map<String, String> headers) { 2 RequestConfig config = RequestConfig.custom().setConnectTimeout(TIME_OUT).setConnectionRequestTimeout(TIME_OUT).setSocketTimeout(TIME_OUT).build(); 3 String ret = null; 4 //創建HttpClient對象 5 CloseableHttpClient closeHttpClient = HttpClients.createDefault(); 6 CloseableHttpResponse httpResponse = null; 7 //發送get請求 8 HttpGet httpGet = new HttpGet(url); 9 try { 10 // add header 11 if (Objects.nonNull(headers)) { 12 Set<String> keys = headers.keySet(); 13 for (Iterator<String> i = keys.iterator(); i.hasNext(); ) { 14 String key = i.next(); 15 httpGet.addHeader(key, headers.get(key)); 16 } 17 } 18 19 httpGet.setConfig(config); 20 //執行Get請求 得到Response對象 21 httpResponse = closeHttpClient.execute(httpGet); 22 //httpResponse.getStatusLine() 響應頭信息 23 int httpResponseCode = httpResponse.getStatusLine().getStatusCode(); 24 25 if (200 != httpResponseCode) { 26 logger.error("http返回值異常, httpResponseCode = " + httpResponseCode); 27 } 28 29 //返回對象 30 HttpEntity httpEntity = httpResponse.getEntity(); 31 ret = EntityUtils.toString(httpEntity, "UTF-8"); 32 } catch (UnsupportedEncodingException e) { 33 logger.error(e.getMessage(), e); 34 } catch (ClientProtocolException e) { 35 logger.error(e.getMessage(), e); 36 } catch (IOException e) { 37 //logger.error(e.getMessage(), e); 38 } finally { 39 if (httpResponse != null) { 40 try { 41 httpResponse.close(); 42 } catch (IOException e) { 43 logger.error(e.getMessage(), e); 44 } 45 } 46 if (closeHttpClient != null) { 47 try { 48 closeHttpClient.close(); 49 } catch (IOException e) { 50 logger.error(e.getMessage(), e); 51 } 52 } 53 } 54 return ret; 55 }
第四步,查詢數據set進redis,之后返回查詢的數據:
getPanelInfo.java
1 if (!Objects.equals(redisCache, "false")) {
2 //redisttl過期時間 3 redisProxyHandler.set(redisKey, JSON.toJSONString(responseModel), REDIS_TTL); 4 logger.error("set succeed!!!!!!!!!!!!!!!!"); 5 }
redisHandler.java
1 public void set(String key, String value, int seconds) { 2 redisCacheProvider.set(key, value, seconds); 3 }
redisProvider.java
1 @Autowired 2 private JedisPool jedisPool; 3 4 public Jedis getJedis() { 5 Jedis jedis = this.jedisPool.getResource(); 6 // 使用index為2的database 7 jedis.select(2); 8 return jedis; 9 } 10 11 public void set(String key, String value, int seconds) { 12 Jedis jedis = null; 13 try { 14 jedis = getJedis(); 15 jedis.setex(key, seconds, value); 16 Long exp = jedis.ttl(key); 17 if (exp < 0) { 18 throw new RuntimeException("data time out!");19 } 20 } catch (Throwable e) { 21 logger.error(e.getMessage(), e); 22 throw new TokenException(e.getMessage()); 23 } finally { 24 if(jedis != null){jedis.close;} 25 } 26 }
第五步,請求接口的時候,先請求redis緩存,如果命中則返回命中數據,否則,將執行上面的發送http請求在拼湊數據返回的代碼:
getPanelInfo.java
1 String panelInfo = redisProxyHandler.get(redisKey); 2 Long expire = redisProxyHandler.getExpire(redisKey); 3 //命中才返回,否則會去發http請求 4 if (Objects.nonNull(panelInfo) && (expire > 0) && expire <REDIS_TTL) {
5 responseModel = JSON.parseObject(panelInfo, WrapResponseModel.class);
6 return responseModel;
7 }
redisHandler.java
1 public String get(String key) 2 return redisCacheProvider.get(key); 3 }
redisProvider.java
1 public String get(String key) { 2 String value = null; 3 Jedis jedis = null; 4 try { 5 jedis = getJedis(); 6 value = jedis.get(key); 7 } catch (Throwable e) { 8 logger.error(e.getMessage(), e); 9 throw new TokenException(e.getMessage()); 10 } finally { 11 if(jedis != null){ 12 jedis.close(); 13 } 14 } 15 return value; 16 }
redis相關配置文件如下
applicationContext.xml
1 <!-- 讀取配置文件信息 --> 2 <context:property-placeholder location="classpath:redis.properties" file-encoding="UTF-8" ignore-unresolvable="true"/> 3 4 <!-- Jedis 連接池配置 --> 5 <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig"> 6 <property name="maxTotal" value="${redis.pool.maxTotal}"/> 7 <property name="maxIdle" value="${redis.pool.maxIdle}"/> 8 <property name="maxWaitMillis" value="${redis.pool.maxWaitMillis}"/> 9 <property name="testOnBorrow" value="${redis.pool.testOnBorrow}"/> 10 </bean> 11 12 <bean id="jedisPool" class="redis.clients.jedis.JedisPool"> 13 <constructor-arg ref="jedisPoolConfig"/> 14 <constructor-arg value="${jedis.host}" type="java.lang.String"/> 15 <constructor-arg type="int" value="${jedis.port}"/> 16 </bean>
redis.properties
1 # 控制一個pool可分配多少個jedis實例 2 redis.pool.maxTotal=1000 3 # 控制一個pool最多有多少個狀態為idle(空閑)的jedis實例 4 redis.pool.maxIdle=200 5 # 表示當borrow一個jedis實例時,最大的等待時間,如果超過等待時間,則直接拋出JedisConnectionException 6 redis.pool.maxWaitMillis=2000 7 #在borrow一個jedis實例時,是否提前進行validate操作;如果為true,則得到的jedis實例均是可用的 8 redis.pool.testOnBorrow=true 9 # redis 單機 10 # 單機 host 11 jedis.host=127.0.0.1 12 # 單機 port 13 jedis.port=6379
看了上面的代碼,我們知道一般的緩存是怎么使用的,在這個案例中,每個redisKey是根據請求的用戶名拼接特定的字符串生成的,每個請求用戶對應的key只在redis中保存一定的時間,過了指定的過期時間REDIS_TTL,數據將會被清除掉。