防緩存穿透設計

這裏首先要弄清楚什麼是緩存穿透,緩存雪崩,緩存擊穿,簡單說就是緩存裏面查詢不到,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

相關文章
相關標籤/搜索