防缓存穿透设计
这里首先要弄清楚什么是缓存穿透,缓存雪崩,缓存击穿,简单说就是缓存里面查询不到,db里面也查询不到,耗干服务器资源或缓存失效了,外面的请求这时直接访问的是db不是cache,那么流量一加大,就会把数据库顶爆
如果还不清楚,请参考这里 缓存穿透,缓存击穿,缓存雪崩解决方案分析
我这里拿之前写过的java-redis缓存jsp页面做例子,
限流可用的技术有
b,Semaphore,
c,netflix的hystrix
这里用到的是java 里面的 Semaphore做限制
import com.xxxxxx.platform.common.redis.RedisUtil; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.concurrent.Semaphore; public class RedisPageCacheFilter implements Filter { private static final Logger log = LoggerFactory.getLogger(RedisPageCacheFilter.class); private final static String FILTER_URL_PATTERNS = "patterns"; private static String[] cacheURLs; private static int timeOut; //加入信号量防止缓存穿透,缓存失效时只能同时1个用户访问数据库,防止数据库被顶爆 private static final Semaphore semp = new Semaphore(1); @Override public void init(FilterConfig config) throws ServletException { String patterns = config.getInitParameter(FILTER_URL_PATTERNS); timeOut = Integer.parseInt(config.getInitParameter("expireTime")); cacheURLs = StringUtils.split(patterns, ","); } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletResponse resp = (HttpServletResponse) servletResponse; HttpServletRequest request = (HttpServletRequest) servletRequest; String url = request.getRequestURI(); boolean flag = false; if (cacheURLs != null && cacheURLs.length > 0) { for (String cacheURL : cacheURLs) { if (url.contains(cacheURL.trim()) || url.matches(cacheURL)) { flag = true; break; } } } // 如果包含我们要缓存的url 就缓存该页面,否则执行正常的页面转向 if (flag) { String query = request.getQueryString(); if (query != null) { query = "?" + query; } final String key = "REDISCACHE:" + url + query; log.info("当前请求缓存为:" + url + query); // 从缓存中得到主页html String html = getHtmlFromCache(key); if (null == html) { try { semp.acquire();
//这里做第二道redis读缓存操作,因为同时等待不只一个请求,第一个请求读完db,就把数据写到redis里面了,那么第二个进来就不用读db了
html = getHtmlFromCache(key); if(StringUtils.isEmpty(html)) { // 截取生成的html并放入缓存 log.info("缓存不存在,生成缓存1"); ResponseWrapper wrapper = new ResponseWrapper(resp); filterChain.doFilter(servletRequest, wrapper); // 放入缓存 html = wrapper.getResult(); putIntoCache(key, html); } else{ log.info("缓存已经生成,直接用缓存2"); } } catch (InterruptedException e) { e.printStackTrace(); } finally { // 访问完后,释放 semp.release(); } } // 返回响应 resp.setContentType("text/html; charset=utf-8"); resp.getWriter().print(html); } else { filterChain.doFilter(servletRequest, resp); return; } } @Override public void destroy() { } private String getHtmlFromCache(String redisKey) { return RedisUtil.get(redisKey); } private void putIntoCache(String redisKey, String html) { RedisUtil.set(redisKey, html, timeOut * 60); } }
如有不足之处,请诸位大神指出