防缓存穿透设计

这里首先要弄清楚什么是缓存穿透,缓存雪崩,缓存击穿,简单说就是缓存里面查询不到,db里面也查询不到,耗干服务器资源或缓存失效了,外面的请求这时直接访问的是db不是cache,那么流量一加大,就会把数据库顶爆

如果还不清楚,请参考这里  缓存穿透,缓存击穿,缓存雪崩解决方案分析

我这里拿之前写过的java-redis缓存jsp页面做例子,

限流可用的技术有

a,Guava官方文档-RateLimiter类,

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); } }

 如有不足之处,请诸位大神指出

posted @ 2018-01-15 14:59  yuan.net  阅读(726)  评论(0编辑  收藏  举报