Redis缓存热点数据案例
缓存的目的是为了提高系统的性能,缓存中的数据主要有两种:
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 //redis过期时间 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,数据将会被清除掉。