這裏首先要弄清楚什麼是緩存穿透,緩存雪崩,緩存擊穿,簡單說就是緩存裏面查詢不到,db裏面也查詢不到,耗幹服務器資源或緩存失效了,外面的請求這時直接訪問的是db不是cache,那麼流量一加大,就會把數據庫頂爆html
若是還不清楚,請參考這裏 緩存穿透,緩存擊穿,緩存雪崩解決方案分析java
我這裏拿以前寫過的java-redis緩存jsp頁面作例子,redis
限流可用的技術有數據庫
a,Guava官方文檔-RateLimiter類,apache
b,Semaphore,緩存
c,netflix的hystrix服務器
這裏用到的是java 裏面的 Semaphore作限制app
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); } }
若有不足之處,請諸位大神指出jsp