大型网站架构师方案—屌爆了!这种优化方案让我网站响应速度提高了近80倍


作者:空白

1. 网站性能的指标

博客网站的页面打开时间一旦超过10s就会容易让用户关闭网站,从而增加跳出率。但实际情况是,大部分用户对于网页打开时间超过3s的可能都无法接受。

网站性能指标

对于网站的性能指标,最重要的是响应时间、吞吐量、并发数。 响应时间是浏览器发出请求到收到响应并最终渲染到界面上的总时间,能很直观的反映系统的快慢。 吞吐量是单位时间处理的请求数,也称为TPS,能体现系统容量。 并发数是指系统同时能处理的请求数,例如双十一,上亿用户在线,而且短时间内都访问系统,这时候网站就需要极高的并发能力支持。

关联 其实细细思考,这几个网站的性能指标,它们之间是有关联关系的。 就拿高速公路收费站来类比,响应时间(RT)就是收费窗口处理收费事件的时间,吞吐量(TPS)是单位时间内收费站窗口总共经过了多少辆车,而并发量(P)就是总共有多少个收费通道。

TPS = p*(1/RT)

但是这篇文章核心内容是谈谈如何优化网站的RT:响应时间。 通过降低网站RT时间来提高网站的吞吐量,提高用户的体验。

2. 网站性能检测方法

从输入url,到页面渲染结束,全过程可大致分为以下几个阶段:

  1. 从浏览器输入url,DNS域名解析,查找到域名绑定的id地址。
  2. 建立TCP连接:客户端浏览器和web服务器建立TCP连接(传输控制协议),三次握手。
  3. 发送HTTP request请求:客户端浏览器向对应的ip地址web服务器发送http请求。
  4. web服务器反向代理:Nginx反向代理,解析域名转发到对应的应用服务器上。
  5. 服务器业务逻辑处理:业务接口逻辑,访问数据库或访问缓存,数据计算等过程。
  6. 视图解析:拿第五步得到业务数据结合视图模板进行解析,得到html文件。
  7. 客户端浏览器渲染:渲染响应页面,客户端浏览器下载数据,解析HTML,完成页面的排版。
  8. 响应完成,断开tcp连接。

在这8个阶段,其中对于网站响应优化最明显的地方就是第五、六阶段,对于绝大多数博客网站来说,网页都是相对静态的,很少有数据的变化。因此通过将博客网页静态化处理,可以省去响应过程最耗时的这两个阶段,能大大降低响应时间。

过滤器统计响应时间

SpringBoot的API过滤器:ApiAccessFilter。使用注解的开发方式,如下代码所示。@WebFilter修饰实现了Filter接口的类,参数filterName为拦截器名称,urlPatterns设置拦截的url模式。Api响应时间计算逻辑在doFilter方法中实现。

@WebFilter(filterName = "ApiAccessFilter", urlPatterns = "/*")
public class ApiAccessFilter implements Filter { 
  
  @Override
  public void init(FilterConfig filterConfig) { 
  }
  
  @Override
  public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
    FilterChain filterChain) throws IOException, ServletException {
  
    HttpServletRequest request = (HttpServletRequest) servletRequest;
  
//    Long requestId = IdGenUtils.nextIdByMem(); // 请求ID,这个是我业务中的id,大家可自行决定是否需要
    long start = System.currentTimeMillis(); // 请求进入时间
  
    log.info("[Api Access] start. uri: {}, method: {}, client: {}",
    request.getRequestURI(), request.getMethod(), getIP(request));
    filterChain.doFilter(servletRequest, servletResponse);
    log.info("[Api Access]  end. uri: {}, method: {}, client: {}, duration: {}ms",
    request.getRequestURI(), request.getMethod(), getIP(request),System.currentTimeMillis() - start); 
  }
  
  @Override
  public void destroy() {
  
  }
  
  /**
   * 获取IP地址
   *
   * @param request 请求
   * @return request发起客户端的IP地址
   */
  private String getIP(HttpServletRequest request) {
    if (request == null) {
      return "0.0.0.0";
    }
  
    String Xip = request.getHeader("X-Real-IP");
    String XFor = request.getHeader("X-Forwarded-For");
  
    String UNKNOWN_IP = "unknown";
    if (!StringUtils.isEmpty(XFor) && !UNKNOWN_IP.equalsIgnoreCase(XFor)) {
      //多次反向代理后会有多个ip值,第一个ip才是真实ip
      int index = XFor.indexOf(",");
      if (index != -1) {
        return XFor.substring(0, index);
      } else {
        return XFor;
      }
    }
  
    XFor = Xip;
    if (!StringUtils.isEmpty(XFor) && !UNKNOWN_IP.equalsIgnoreCase(XFor)) {
      return XFor;
    }
  
    if (StringUtils.isEmpty(XFor) || UNKNOWN_IP.equalsIgnoreCase(XFor)) {
      XFor = request.getHeader("Proxy-Client-IP");
    }
    if (StringUtils.isEmpty(XFor) || UNKNOWN_IP.equalsIgnoreCase(XFor)) {
      XFor = request.getHeader("WL-Proxy-Client-IP");
    }
    if (StringUtils.isEmpty(XFor) || UNKNOWN_IP.equalsIgnoreCase(XFor)) {
      XFor = request.getHeader("HTTP_CLIENT_IP");
    }
    if (StringUtils.isEmpty(XFor) || UNKNOWN_IP.equalsIgnoreCase(XFor)) {
      XFor = request.getHeader("HTTP_X_FORWARDED_FOR");
    }
    if (StringUtils.isEmpty(XFor) || UNKNOWN_IP.equalsIgnoreCase(XFor)) {
      XFor = request.getRemoteAddr();
    }
    return XFor;
  }
}

有了这个filter,我们就能在日志中查看所有url的响应时间,然后对比网站静态化前后的响应时间变化。

3. 优化方案

静态化处理 每个页面请求到来,拦截在执行controller接口逻辑之前,判断该请求是否有存在的html文件,如果有直接重定向,跳过数据库访问、数据计算、模板解析等过程。

如果不存在,则不拦截,正常走业务逻辑、视图解析等正常处理流程。

大型新闻网站的静态化方案

alt

可以看到,大型网站一般都是将访问的每个url都有直接关联到的静态html,而静态html的维护是由另外一套系统来做。这种方式比较繁琐,系统也很复杂,下面来一起看看我设计的轻量静态化方案。

轻量的网页静态HTML化方案

轻量的静态化方案,是基于注解开发的,对业务代码没有侵入性。拦截器在执行controller接口方法之前先执行注解切面逻辑,通过spring的切面编程方式,可以把这部分逻辑作为增强逻辑放到切面模块。

那controller接口方法的代码不用做任何更改,做到了对业务代码零入侵。接下来就来谈谈具体实现方案。

4. 方案落地开发

废话不多说,直接上代码,博客网站首页的controller接口业务代码,看看是如何启动页面静态化的。

 /**
     * 首页
     *
     * @param vo
     * @param model
     * @return
     */
    @RequestMapping("/")
    @BussinessLog(value = "进入首页", platform = PlatformEnum.WEB)
    @StaticPage(enable=true)
    public ModelAndView home(ArticleConditionVO vo, Model model) {
        model.addAttribute("url", INDEX_URL);
        loadIndexPage(vo, model);

        return ResultUtil.view(INDEX_URL);
    }

是的,一个自定义注解@StaticPage直接开启首页接口页面的静态html化,做到了业务代码的零入侵!

切面增强逻辑

  1. 切面逻辑这块的开发,是直接获取url请求的request对象,并获取html的path,计算得到html的相对路径。
  2. 创建file对象,判断是否存在html文件,存在则重定向到html,结束接口后续响应。
  3. 文件不存在,则不中断响应,响应线程走正常执行业务逻辑、解析模板等过程。

如何维护静态html文件

这里设计的方案是,系统根据url所带的秘钥参数来决定执行逻辑,静态化请求都由博客网站后台发出,这是更新html文件的管理方法。如果秘钥正确,则只生成html静态文件,如果秘钥不存在,则重定向到html或生成html后再重定向。

自定义ViewClass

SpringBoot项目的解析器ViewResolver可配置自定义ViewClass类,如下代码所示。

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    
    @Bean
    public FreeMarkerViewResolver freeMarkerViewResolver() {
        FreeMarkerViewResolver resolver = new FreeMarkerViewResolver();
        resolver.setViewClass(MyFreeMarkerView.class);
        resolver.setPrefix("");
        resolver.setSuffix(".ftl");
        resolver.setContentType("text/html; charset=UTF-8");
        resolver.setRequestContextAttribute("rc");
        return resolver;
    }
    
}

在执行完controller接口后、解析视图模板之前执行ViewClass中的doRender方法逻辑,如下代码的位置,这块逻辑来判断是否继续执行模板解析逻辑。

public class MyFreeMarkerView extends FreeMarkerView{  
	

	private static final Logger log = LoggerFactory.getLogger(MyFreeMarkerView.class);

   
    @Override  
    protected void doRender(Map model,  
            HttpServletRequest request, HttpServletResponse response)  
            throws Exception { 
            ......在业务逻辑执行之后 、 模板解析之前的逻辑。
            }

5. 优化成果

使用springmvc的filter过滤器来统计网站接口的响应时间,具体原理就不在这里细说了,直接看运行的数据结果。

开启静态化之前,接口响应时间是duration: 12236ms

20:50:26.374 [http-nio-8081-exec-1] INFO  c.r.f.f.ApiAccessFilter - [doFilter,39] - [Api Access]  end. uri: /article/127, method: GET, client: 127.0.0.1, duration: 12236ms

开启静态化之后,接口响应时间是duration: 15ms

20:43:32.276 [http-nio-8081-exec-7] INFO  c.r.f.f.ApiAccessFilter - [doFilter,39] - [Api Access]  end. uri: /article/127, method: GET, client: 127.0.0.1, duration: 15ms

静态化开启前后,接口响应的时间降低了12236/15≈815.73。速度提高了800多倍。

6. 网站性能还能提升吗?

其实,在前面分析的响应过程里,html下载(html文件的网络传输)也是非常消耗时间的阶段,静态化html文件的存放位置还能进一步优化,在这个项目中我们存放在服务这一侧,其实还可以放在网站接入层nginx服务器。

对于静态html文件,可以直接上传到与用户在同一个城市的网络运营商CDN服务器,这样文件的网络传输过程就大大缩减了,网页打开速度会更快。现在很多新闻网站、博客网站都是在使用运营商CDN服务的,甚至视频网站也会将最新视频上传到CDN,以加快用户响应速度。

完整的demo项目,请关注公众号“前沿科技bot“并发送""获取。

alt

扫码或搜索:前沿科技
发送 290992
即可立即永久解锁本站全部文章