当前位置: 首页 > news >正文

荥阳网站开发中国工程交易中心网

荥阳网站开发,中国工程交易中心网,建筑效果图,中国住房和城乡建设部网站首页一、背景与效果 ICBU的核心沟通场景有了10年的“积累”#xff0c;核心场景的界面响应耗时被拉的越来越长#xff0c;也让性能优化工作提上了日程#xff0c;先说结论#xff0c;经过这一波前后端齐心协力的优化努力#xff0c;两个核心界面90分位的数据#xff0c;FCP平…一、背景与效果 ICBU的核心沟通场景有了10年的“积累”核心场景的界面响应耗时被拉的越来越长也让性能优化工作提上了日程先说结论经过这一波前后端齐心协力的优化努力两个核心界面90分位的数据FCP平均由2.6s下降到1.9sLCP平均由2.8s下降到2s。 本文主要着眼于服务端在此次性能优化过程中做的工作供大家参考讨论。 二、措施一流式分块传输核心 2.1. HTTP分块传输介绍 分块传输编码Chunked Transfer Encoding是一种HTTP/1.1协议中的数据传输机制它允许服务器在不知道整个内容大小的情况下就开始传输动态生成的内容。这种机制特别适用于生成大量数据或者由于某种原因数据大小未知的情况。 在分块传输编码中数据被分为一系列的“块”chunk。每一个块都包括一个长度标识以十六进制格式表示和紧随其后的数据本身然后是一个CRLF即\r\n代表回车和换行来结束这个块。块的长度标识会告诉接收方这个块的数据部分有多长使得接收方可以知道何时结束这一块并准备好读取下一块。 当所有数据都发送完毕时服务器会发送一个长度为零的块表明数据已经全部发送完毕。零长度块后面可能会跟随一些附加的头部信息尾部头部然后再用一个CRLF来结束整个消息体。 我们可以借助分块传输协议完成对切分好的vm进行分块推送从而达到整体HTML界面流式渲染的效果在实现时只需要对HTTP的header进行改造即可 public void chunked(HttpServletRequest request, HttpServletResponse response) { try (PrintWriter writer response.getWriter()) { // 设置响应类型和编码 oriResponse.setContentType(MediaType.TEXT_HTML_VALUE ;charsetUTF-8); oriResponse.setHeader(Transfer-Encoding, chunked); oriResponse.addHeader(X-Accel-Buffering, no); // 第一段 Context modelMain getmessengerMainContext(request, response, aliId); flushVm(/velocity/layout/Main.vm, modelMain, writer);// 第二段 Context modelSec getmessengerSecondContext(request, response, aliId, user); flushVm(/velocity/layout/Second.vm, modelSec, writer);// 第三段 Context modelThird getmessengerThirdContext(request, response, user); flushVm(/velocity/layout/Third.vm, modelThird, writer);} catch (Exception e) { // logger}} private void flushVm(String templateName, Context model, PrintWriter writer) throws Exception { StringWriter tmpWri new StringWriter(); // vm渲染 engine.mergeTemplate(templateName, UTF-8, model, tmpWri); // 数据写出 writer.write(tmpWri.toString()); writer.flush();} 2.2. 页面流式分块传输优化方案 我们现在的大部分应用都是springmvc架构浏览器发起请求后端服务器进行数据准备与vm渲染之后返回html给浏览器。 从请求到达服务端开始计算一次HTML请求到页面加载完全要经过网络请求、网络传输与前端资源渲染三个阶段 HTML流式输出思路是对HTML界面进行拆分之后由服务器分批进行推送这样做有两个好处 服务端分批进行数据准备可以减少首次需要准备的数据量极大缩短准备时间。 浏览器分批接收数据当接收到第一部分的数据时可以立刻进行js渲染提升其利用率。 这个思路对需要加载资源较多的页面有很明显的效果在我们此次的界面优化中页面的FCP与LCP均有300ms-400ms的性能提升在进行vm界面的数据拆分时有以下几个技巧 注意界面资源加载的依赖关系前序界面不能依赖后序界面的变量。 将偏静态与核心的资源前置后端服务器可以快速完成数据准备并返回第一段html供前端加载。 2.3. 注意事项 此次优化的应用与界面本身历史包袱很重在进行流式改造的过程中我们遇到了不少的阻力与挑战在解决问题的过程也学到了很多东西这部分主要对遇到的问题进行整理。 二方包或自定义的HTTP请求 filter 会改写 response 的 header导致分块传输失效。如果应用中有这种情况我们在进行流式推送时可以获取到最原始的response防止被其他filter影响 /** * 防止filter或者其他代理包装了response并开启缓存 * 这里获取到真实的response * * param response * return */private static HttpServletResponse getResponse(HttpServletResponse response) { ServletResponse resp response; while (resp instanceof ServletResponseWrapper) { ServletResponseWrapper responseWrapper (ServletResponseWrapper) resp; resp responseWrapper.getResponse(); } return (HttpServletResponse) resp;} 谷歌浏览器禁止跨域名写入cookie我们的应用界面会以iframe的形式嵌入其他界面谷歌浏览器正在逐步禁止跨域名写cookie如下所示 为了确保cookie能正常写入需要指定cookie的SameSiteNone。 VelocityEngine模板引擎的自定义tool。 我们的项目中使用的模板引擎为VelocityEngine在流式分块传输时需要手动渲染vm private void flushVm(String templateName, Context model, PrintWriter writer) throws Exception { StringWriter tmpWri new StringWriter(); // vm渲染 engine.mergeTemplate(templateName, UTF-8, model, tmpWri); // 数据写出 writer.write(tmpWri.toString()); writer.flush();} 需要注意的是VelocityEngine模板引擎支持自定义tool在vm文件中是如下的形式当vm引擎渲染到对应位置时会调用配置好的方法进行解析 title$tool.do(xx, $!{arg})/title 如果用注解的形式进行vm渲染框架本身会帮我们自动做tools的初始化。但如果我们想手动渲染vm那么需要将这些tools初始化到context中 /** * 初始化 toolbox.xml 中的工具 */private Context initContext(HttpServletRequest request, HttpServletResponse response) { ViewToolContext viewToolContext null; try { ServletContext servletContext request.getServletContext(); viewToolContext new ViewToolContext(engine, request, response, servletContext); VelocityToolsRepository velocityToolsRepository VelocityToolsRepository.get(servletContext); if (velocityToolsRepository ! null) { viewToolContext.putAll(velocityToolsRepository.getTools()); } } catch (Exception e) { LOGGER.error(createVelocityContext error, e); return null; }} 对于比较古老的应用VelocityToolsRepository需要将二方包版本进行升级而且需要注意velocity-spring-boot-starter升级后可能存在tool.xml文件失效的问题建议可以采用注解的形式实现tool并且注意tool对应java类的路径。 DefaultKey(assetsVersion)public class AssertsVersionTool extends SafeConfig { public String get(String key) { return AssetsVersionUtil.get(key); }} Nginx 的 location 配置 server { location ~ ^/chunked { add_header X-Accel-Buffering no; proxy_http_version 1.1; proxy_cache off; # 关闭缓存 proxy_buffering off; # 关闭代理缓冲 chunked_transfer_encoding on; # 开启分块传输编码 proxy_pass http://backends; } } ngnix配置本身可能存在对流式输出的不兼容这个问题是很难枚举的我们遇到的问题是如下配置需要将SC_Enabled关闭。 SC_Enabled on;SC_AppName gangesweb;SC_OldDomains //b.alicdn.com;SC_NewDomains //b.alicdn.com;SC_OldDomains //bg.alicdn.com;SC_NewDomains //bg.alicdn.com;SC_FilterCntType text/html;SC_AsyncVariableNames asyncResource;SC_MaxUrlLen 1024; 详见https://github.com/dinic/styleCombine3 ngnix缓冲区大小在我们优化的过程中某个应用并没有指定缓冲区大小取的默认值我们的改造导致http请求的header变大了导致报错upstream sent too big header while reading response header from upstream proxy_buffers 128 32k;proxy_buffer_size 64k;proxy_busy_buffers_size 128k;client_header_buffer_size 32k;large_client_header_buffers 4 16k; 如果页面在浏览器上有问题时可以通过curl命令在服务器上直接访问排查是否为ngnix的问题 curl --trace - http://127.0.0.1:7001/chunked \-H cookie: xxx ThreadLocal与StreamingResponseBody 在开始我们使用StreamingResponseBody来实现的分块传输 GetMapping(/chunked)public ResponseEntityStreamingResponseBody streamChunkedData() { StreamingResponseBody stream outputStream - { // 第一段 Context modelMain getmessengerMainContext(request, response, aliId); flushVm(/velocity/layout/Main.vm, modelMain, writer); // 第二段 Context modelSec getmessengerSecondContext(request, response, aliId, user); flushVm(/velocity/layout/Second.vm, modelSec, writer); // 第三段 Context modelThird getmessengerThirdContext(request, response, user); flushVm(/velocity/layout/Third.vm, modelThird, writer); } }; return ResponseEntity.ok() .contentType(MediaType.TEXT_HTML) .body(stream); }} 但是我们在运行时发现vm的部分变量会渲染失败卡点了不少时间后面在排查过程中发现应用在处理http请求时会在ThreadLocal中进行用户数据、request数据与部分上下文的存储而后续vm数据准备时有一部分数据是直接从中读取或者间接依赖的而StreamingResponseBody本身是异步的可以看如下的代码注释这就导致新开辟的线程读不到原线程ThreadLocal的数据进而渲染错误 /** * A controller method return value type for asynchronous request processing * where the application can write directly to the response {code OutputStream} * without holding up the Servlet container thread. * * pstrongNote:/strong when using this option it is highly recommended to * configure explicitly the TaskExecutor used in Spring MVC for executing * asynchronous requests. Both the MVC Java config and the MVC namespaces provide * options to configure asynchronous handling. If not using those, an application * can set the {code taskExecutor} property of * {link org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter * RequestMappingHandlerAdapter}. * * author Rossen Stoyanchev * since 4.2 */FunctionalInterfacepublic interface StreamingResponseBody {/** * A callback for writing to the response body. * param outputStream the stream for the response body * throws IOException an exception while writing */ void writeTo(OutputStream outputStream) throws IOException; } 三、措施二非流量中间件优化 在性能优化过程中我们发现在流量高峰期某个服务接口的平均耗时会显著升高结合arths分析发现是由于在流量高峰期对于配置中心的调用被限流了。原因是配置中心的使用不规范每次都是调用getConfig方法从配置中心服务端拉取的数据。 在读取配置中心的配置时更标准的使用方法是由配置中心主动推送变更客户端监听配置信息缓存到本地这样每次读取配置其实读取的是机器的本地缓存可以参考如下的方式 public static void registerDynamicConfig(final String dataIdKey, final String groupName) { IOException initError null;try { String e Diamond.getConfig(dataIdKey, groupName, DEFAULT_TIME_OUT); if(e ! null) { getGroup(groupName).put(dataIdKey, e); }logger.info(Diamond config init: dataId dataIdKey , groupName groupName ; initValue e); } catch (IOException e) { logger.error(Diamond config init error: dataId dataIdKey, e); initError e; }Diamond.addListener(dataIdKey, groupName, new ManagerListener() { Override public Executor getExecutor() { return null; }Override public void receiveConfigInfo(String s) { String oldValue (String)DynamicConfig.getGroup(groupName).get(dataIdKey); DynamicConfig.getGroup(groupName).put(dataIdKey, s); DynamicConfig.logger.warn( Receive config update: dataId dataIdKey , newValue s , oldValue oldValue); } }); if(initError ! null) { throw new RuntimeException(Diamond config init error: dataId dataIdKey, initError); }} 四、措施三数据直出 静态图片直出页面上有静态的loge图片原本为cdn地址在浏览器渲染时需要建联并会抢占线程对于这类不会发生发生变化的图片可以直接替换为base64的形式js可以直接加载。 加载数据直出这部分需要根据具体业务来分析部分业务数据是浏览器运行js脚本在本地二次请求加载的由于低端机以及本地浏览器的能力限制如果需要加载的数据很多就很导致js线程的挤占拖慢整体的时间因此可以考虑在服务器将部分数据预先加载好随http请求一起给浏览器减少这部分的卡点。 数据直出有利有弊对于页面的加载性能有正向影响的同时也会同时导致HTTP的response增大以及服务端RT的升高。数据直出与流式分块传输相结合的效果可能会更好当服务端分块响应HTTP请求时本身的response就被切割成多块单次大小得到了控制流式分块传输下服务端分批执行数据准备的策略也能很好的缓冲RT增长的问题。 五、措施四本地缓存 以我们遇到的一个问题为例我们的云盘文件列表需要在后端准备好文件所属人的昵称这是在后端服务器由用户id调用会员的rpc接口实时查询的。分析这个场景我们不难发现同一时间IM场景下的文件所属人往往是其中归属在聊天的几个人名下的因此可以利用HashMap作为缓存rpc查询到的会员昵称避免重复的查询与调用。 六、措施五下线历史债务 针对有历史包袱的应用历史债务导致的额外耗时往往很大这些历史代码可能包括以下几类 未下线的实验或者分流接口调用 时间线拉长这部分的代码残骸在所难免而且积少成多累计起来往往有几十上百毫秒的资源浪费再加上业务开发时大家往往没有额外资源去评估这部分的很多代码是否可以下线因此可以借助性能优化的契机进行治理。 已经废弃的vm变量与重复变量治理。 对vm变量的盘点过程中发现有很多之前在使用但现在已经废弃的变量。当然这部分变量的需要前后端同学共同梳理防止下线线上依旧依赖的变量。
http://www.w-s-a.com/news/24244/

相关文章:

  • 毕业设计做网站怎样做特别一点在线网页制作软件
  • html网站代码上海这边敲墙拆旧做啥网站的比较多
  • 微网站怎么用在线crm管理系统
  • 中国城乡建设部人力网站首页如何利用某个软件做一个网站
  • 个人承接网站建设wordpress editor
  • 建站主机 wordpress专业的菏泽网站建设公司
  • 网站响应时间 标准网站建设色调的
  • 网站开发的合同网站建设 设计
  • 网站开发设置网页端口申请免费个人网站空间
  • 制作广告网站的步骤云服务器做网站
  • ipv6可以做网站吗东莞网站建站推广
  • 注册功能的网站怎么做做网站容易还是编程容易
  • wordpress建立目录seo编辑培训
  • 网站怎么群发广州现在可以正常出入吗
  • 微信有网站开发吗多语种网站
  • 深圳网站设计 建设首选深圳市室内设计公司排名前50
  • 上海网站建设 觉策动力wordpress接口开发
  • 网站建设服务器的选择方案小型视频网站建设
  • 江宁做网站价格扬州立扬计算机培训网站建设怎么样
  • 手表网站背景开发新客户的十大渠道
  • 定制网站设计wordpress写的网站
  • p2p网站建设公司排名成都装饰公司
  • 网站被k怎么恢复wordpress缓存类
  • 做外贸有哪些网站平台最近文章 wordpress
  • joomla网站模板一个人做网站的swot
  • 南京建设网站需要多少钱深圳专业网站建设制作价格
  • 天河建网站装修公司线上推广方式
  • 超市网站怎么做的目前最流行的拓客方法
  • 做文字logo的网站贵阳商城网站开发
  • 沧州有没有做网站的中国建筑设计