整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          JavaScript 后端開發者必須跨的 10 道門檻

          要:在IT行業中,后端開發者發揮著至關重要的作用。尤其是在技術變革日新月異的時代,更需要后端開發者不斷提升自我的技能,但是,無論技術迭代如何迅速,后端開發人員的“基本功”都不可以落下。本文展開討論成為JavaScript堆棧后端開發人員,必須經歷的那些事……

          原文鏈接:

          https://billypentester.medium.com/ultimate-10-stages-to-master-backend-development-f6f65f22a327

          聲明:本文為CSDN翻譯,轉載請注明來源。

          作者 | Bilal Ahmad

          譯者 | 朱珂欣 責編 | 屠敏

          出品 | CSDN(ID:CSDNnews)

          隨著時代的發展,越來越多編程語言和開發工具的出現,讓開發者能夠方便且快捷地開發和部署應用程序。其中,后端開發多是指服務器端編程,開發人員通過創建應用程序體系結構和業務邏輯,以此處理和存儲數據并輸出有用的信息。

          毋庸置疑,成為后端開發人員必然有門檻,掌握Web和操作系統的基礎知識、了解數據庫、計算機網絡協議、數據結構和算法、開發工具等,逐漸成為后端開發人員的入門技能。后端編程語言、框架和堆棧也很多,例如JavaScript、Python、PHP、Java、Ruby等。那么,成為JavaScript堆棧后端開發人員,需要面臨那幾個必須經歷的階段呢?


          第1階段:掌握Web基礎知識

          在開發人員的初始階段,學習Web的基礎知識是至關重要的。因為擁有Web概念可以幫助開發人員更好地了解互聯網的工作原理,例如使用哪些協議、如何傳遞消息等。

          首當其沖的是了解Web中的一些基本概念,例如HTTP的概念、HTTP的基本方面、HTTP 請求的類型及作用、客戶端和服務器模型、OSI 模型、DNS 工作原理等。


          第2階段:了解操作系統知識
          操作系統,管理著計算機上的所有軟件和硬件。它執行內存和進程管理,處理輸入和輸出等。通過運行操作系統,進一步運行完整 Web 應用的服務器。因此,開發人員有必要了解操作系統整體實現,以最大限度地提高性能并優化內存。
          在此階段,開發需要了解操作系統體系結構、文件系統、基本CLI命令、內存和進程管理、操作系統線程等。

          第3階段:學習后端框架和語言

          在開發者學習要使用的后端框架和語言的過程中,第一步是學習JavaScript。學習JavaScript的基礎知識,例如數據類型、循環、選擇、函數、對象、類、數組、字符串、正則表達式等。
          JavaScript 由ECMAScript、文檔對象模型(DOM)和瀏覽器對象模型(BOM)三個部分組成。ECMAScript是JavaScript所基于的腳本語言規范。它引入了新的JavaScript功能,如箭頭函數,異步等待,模板文本等,這些功能可以對有效的編碼很有幫助。
          Node.js是一個開源的、跨平臺的、后端的JavaScript運行時環境,它在JavaScript引擎上運行并執行JavaScript代碼。開發人員可以學習基本的節點JavaScript模塊,例如fs、path、os等。

          第4階段:使用服務器框架和API開發

          首先,服務器端框架作為后端開發中的主要角色,它是前端和后端(節點應用)之間的通信網關,使編寫、維護和擴展Web應用更加容易。
          服務器端框架提供工具和庫來實現簡單、常見的開發任務,包括路由處理,數據庫交互,會話支持和用戶驗證,格式化輸出,提高安全性應對網絡攻擊,也能使開發人員完全控制傳入和傳出的請求/響應,提供端點,執行中間件功能等。
          其次,API開發作為一種實踐階段,開發人員將學習如何使用Express JS和節點JS創建簡單的REST API并執行CRUD操作。使用快速中間件驗證傳入請求,使用路由器處理端點等。
          Postman 在測試 API 端點方面很有幫助。添加更多內容并了解 swagger 和 Open API。

          第5階段:使用模板引擎

          通常,前端(React應用程序)和后端(快速應用程序)之間的通信是使用MERN堆棧中的API調用完成的。客戶端從服務器請求數據,服務器將請求的數據發送回客戶端。
          但是,如果開發人員的項目很小,并且對 React 或其他前端框架不了解,可以使用模板引擎。
          模板引擎可幫助開發人員創建靜態HTML模板文件,并在運行時將模板文件中的變量替換為實際值,這使其成為動態網站。模板引擎與Express JS的集成一起工作。它從數據庫獲取數據值,并可以通過HTML 模板文件呈現這些值。


          第6階段:了解數據庫

          數據庫在后端開發中發揮著巨大的作用。數據庫用于以結構化方式存儲數據以及訪問和維護數據。

          后端應用程序可以通過使用MongoDB,MySQL等第三方npm包查詢數據庫來直接與數據庫交互。如果開發人員不了解SQL或數據庫語言,則需要學習任何ORM/ODM作為中間件,將對象方法轉換為復雜的數據庫查詢,這些中間件為開發人員提供了一個名為模型的完整結構,用于在將數據輸入數據庫之前驗證數據。此外,它在對象模型之上提供了一個額外的安全層。


          第7階段:關注安全性

          安全性是企業級 Web 應用程序的一大關注點。單個漏洞會導致數據泄露或應用程序故障。Web 應用中應有適當的驗證、身份驗證和授權。

          因此,開發人員在該階段可能會涉及促進應用更安全的一些技術,例如:
          • JWT:使用令牌防止對受保護資源的意外訪問;
          • Cookie:用于存儲有關訪問網頁的信息;
          • 會話:用于維護與服務器的安全連接;
          • CORS:使用CORS從一個網站向另一個網站發出請求;
          • 哈希:使用中間件通過salt對密碼進行哈希處理;
          • 加密/解密:使用中間件加密和解密重要數據;
          • SSL:使用SSL認證來保護事務;
          • 限制:使用中間件應用請求限制,防止DDOS攻擊。


          第8階段:使用消息代理

          現代應用程序非常復雜,由于時間復雜度大,CPU密集型操作、大量數據處理以及多個服務之間的API通信存在耗時的問題,會帶來糟糕的用戶體驗。

          消息代理成為幫助克服其中一些困難的解決方案之一消息代理由 3 個組件組成:

          • 制作人:生產者發送有關主題的消息。一個或多個發布者可以針對同一主題發布內容;

          • 消費者:訂閱者訂閱主題,發布到該主題的所有消息都將由該主題的所有訂閱者接收;

          • 隊列/主題:隊列保存由生產者發布并由消費者訪問的消息。


          第9階段:測試完整的API

          在實時服務器上部署Web應用程序之前,為了減少單個錯誤的輸入或缺少數據導致正在運行的服務器出現故障,需要進行測試。

          測試可以根據多個標準在多個級別上進行,開發人員可以檢查Web應用是否返回了正確的狀態代碼、錯誤消息、數據等。

          在這個階段,開發人員的主要任務是使用Jest測試完整的API。Jest是一個著名的單元測試框架,用于測試Web應用程序和API。它提供了多種方法來自動化測試過程,檢查對錯誤或缺失數據的響應等。

          第10階段:實時服務器/云上部署Web應用程序

          在部署Web應用之前,開發人員需要學習Git、GitHub的操作以及版本控制工具的使用,以使部署靈活且可擴展。

          Git可以用于版本控制,通過創建多個分支,跟蹤源代碼中的更改,并使多個開發人員能夠協同工作。GitHub是一個用于版本控制和協作的代碼托管平臺,可以允許開發人員和其他人共同處理項目。GitHub操作可幫助您自動執行軟件開發工作流。單個操作是可重用的代碼段,可用于在 GitHub上生成、測試、打包或部署項目。它還可用于自動執行工作流程的任何步驟。部署完整的Web應用非常復雜。開發者還需要一個專用服務器來響應HTTP請求,并使用在線數據庫。

          總的來說,從應用程序開發最初階段的掌握Web基礎知識,到最后階段在實時服務器/云上部署Web應用程序。JavaScript堆棧后端開發者在點滴的積累中,學習和掌握了與應用開發相關的實踐經驗。然而,無論是對于后端開發的初學者還是對有經驗工程師,無論邁過多少個階段,學習之路都不會中斷。

          是3y,一年CRUD經驗用十年的markdown程序員?常年被譽為優質八股文選手

          花了幾天搭了個后臺管理頁面,今天分享下我的搭建過程,全文非技術向,就當跟大家吹吹水吧。

          1、我的前端技術

          老讀者可能知道我是上了大學以后,才了解什么是編程。在這之前,我對編程一無所知,甚至報考了計算機專業之后也未曾了解過它是做什么的。

          在大一的第一個學期,我印象中只開了一門C++的編程課(其他的全是數學)。嗯,理所當然,我是聽不懂的,也不知道用來干什么。

          剛進大學的時候,我對一切充滿了未知,在那時候順其自然地就想要進幾個社團玩玩。但在眾多社團里都找不到我擅長的領域,等快到截止時間了。我又不想大學期間什么社團都沒有參加,最后報了兩個:乒乓球社團和計算機協會

          這個計算機協會絕大多數的人員都來自于計算機專業,再后來才發現這個協會的主要工作就是給人「重裝系統」,不過這是后話啦。

          當時加入計算機協會還需要滿足一定的條件:師兄給了一個「網站」我們這群人,讓我們上去學習,等到國慶回來后看下我們的學習進度再來決定是否有資格加入。

          那個網站其實就是對HTML/CSS/JavaScript入門教程,是一個國外的網站,具體的地址我肯定是忘了。不過那時候,我國慶閑著也沒事干,于是就開始學起來了。我當時的進度應該是學到CSS,能簡單的頁面布局和展示圖片啥的

          剛開始的時候,覺得蠻有趣的:我改下這個代碼,字體的顏色就變了,圖片就能展示出來了。原來我平時上網的網站是這樣弄出來的啊!(比什么C++有趣多了)

          國慶后回來發現:考核啥的并不重要,只要報名了就都通過了。

          有了基本的認知后,我對這個也并不太上心,沒有持續地學下去。再后來,我實在是太無聊,就開始想以后畢業找工作的事了,自己也得在大學充實下自己,于是我開始在知乎搜各種答案「如何入門編程」。

          在知乎搜了各種路線并浪費了大量時間以后,我終于開始看視頻入門。我熬完了JavaSE基礎之后,我記得我是看方立勛老師入門的JavaWeb,到前端的課程以后,我覺得前端HTML/CSS/JavaScript啥的都要補補,于是又去找資源學習(那時候信奉著技多不壓身)。

          印象中是看韓順平老師的HTML/CSS/JavaScript,那時候還手打代碼的階段,把我看得一愣一愣的(IDE都不需要的)。隨著學習,發現好像還得學AJAX/jQuery,于是我又去找資源了,不過我已經忘了看哪個老師的AJAXjQuery課程。

          在這個學習的過程中,我曾經用純HTML/CSS/JavaScript跟著視頻仿照過某某網站,在jQuery的學習時候做過各種的輪播圖動畫。還理解了marginpadding的區別。臨近畢業的時候,也會點BootStrap來寫個簡單的頁面(丑就完事了)

          等我進公司了以后,技術架構前后端是分離的,雖然我拉了前端的代碼,但我看不懂,期間我也沒學。以至于我兩年多是沒碰過前端的,我對前端充滿著敬畏(剛畢業那段時間,前端在飛速發展

          2、austin前端選型

          從我籌劃要寫austin項目的時候,我就知道我肯定要寫一個「后臺管理頁面」,但我遲遲沒下手。一方面是我認為「后端」才是我的賽道,另一方面我「前端」確實菜,不想動手。

          我有想過要不找個小伙伴幫我寫,但是很快就被我自己否定了:還得給小伙伴提需求,算了

          當我要面臨前端的時,我第一時間就想到:肯定是有什么框架能夠快速搭建出一個管理頁面的。我自己不知道,但是,我的朋友圈肯定是有人知道的啊。于是,我果斷求助:

          我被安利了很多框架,簡單列舉下出場率比較高的。

          :大多數我只是粗略看了下,沒有仔細研究。若有錯誤可以在評論區留言,輕噴

          2.1 renren-fast

          官網文檔:https://www.renren.io/guide#getdoc

          它這個框架是前后端分離的,后端還可以生成對應的CRUD代碼,前端基于vueelement-ui開發。

          當時其實我有點想選它的,但考慮到我要再部署個后端,還得學點vue,我就擱置了

          2.2 RuoYi

          官方文檔:http://doc.ruoyi.vip/ruoyi/

          RuoYi給我安利的也很多,這個貌似最近非常火?感覺我被推薦了以后,到處都能看到它的身影。

          我簡單刷了下文檔,感覺他做的事比renren-fast要多,文檔也很齊全,但是沒找到我想要的東西:我打開一個文檔,我希望能看到它的系統架構,系統之間的交互或者架構層面上的東西,但我沒快速找到。

          項目齊全和復雜對我來說或許并不是一件好事,很可能意味著我的學習成本可能會更大。于是,我也擱置著。

          2.3 Vue相關

          vue-element-admin

          官方文檔:https://panjiachen.github.io/vue-element-admin-site/zh/guide/

          Vue Antd Admin

          官方文檔:https://iczer.gitee.io/vue-antd-admin-docs/start/use.html#%E5%87%86%E5%A4%87

          Ant Design Pro

          官方文檔:https://pro.antdv.com/docs/getting-started

          這幾個項目被推薦率也是極高的,從第一行介紹我基本就知道需要去學Vue的語法,奈何我太懶了,擱置著。

          2.4 layui

          有好幾小伙伴們聽說我會jQuery,于是給我推薦了layui。我以前印象中好像聽過這個框架,但一直沒了解過他。但是,當我搜到它的時候,它已經不維護了

          GitHub地址:https://github.com/sentsin/layui

          我簡單瀏覽下文檔,其實它也有對應的一套”語法“,需要一定的學習成本,但不高。

          第一感覺有點類似我以前寫過的BootStrap,我對這不太感冒,感覺如果要接入可能還是需要自己寫比較多的代碼。

          2.5 其他

          還有些小伙伴推薦或者我看到的文章推薦:x-admin/D2admin/smartchart/JEECG-BOOT/Dcat-admin/iview-admin等等等,在這里面還有些依賴著PHP/Python

          總的來說,我還是覺得這些框架有一定的學習成本(我真的是懶出天際了)。可能需要我去部署后端,也可能需要我學習前端的框架語法,也可能讓我學Vue

          看到這里,可能你們很好奇我最后選了什么作為austin的前端,都已經被我篩了這么多了。在公布之前,我想說的是:如果想要頁面好看靈活性高還是得學習Vue。從上面我被推薦的框架中,好多都是在Vue的基礎上改動的,并且我敢肯定:還有很多基于Vue且好用的后臺是我不知道的。

          :我這里指代跟我一樣不懂前端的(如果本身就已經懂前端,你說啥都對)

          3、amis框架

          我最后選擇了amis作為austin的前端。這個框架在我朋友圈只有一個小伙伴推薦,我第一次打開文檔的時候,確實驚艷到我了

          文檔地址:https://baidu.gitee.io/amis/zh-CN/docs/index

          它是一個低代碼前端框架:amis 的渲染過程是將 json 轉成對應的 React 組件

          我花了半天粗略地刷了下文檔,大概知道了JSON的結構(說實話,他這個文檔寫得挺可以的),然后我去GitHub找了一份模板,就直接開始動手了,readme十分簡短。

          GitHub:https://github.com/aisuda/amis-admin

          這個前端低代碼工具還有個好處就是可以通過可視化編輯器拖拉生成JSON代碼,將生成好的代碼直接往自己本地一貼,就完事了,確實挺方便的。

          可視化編輯器的地址:https://aisuda.github.io/amis-editor-demo/

          4、使用感受

          其實沒什么好講的,無非就是在頁面上拖拉得到一個頁面,然后調用API的時候看下文檔的姿勢。

          在這個過程中我也去看了下這個框架的評價,發現百度內部很多系統就用的這個框架來搭建頁面的,也看到Bigo也有在線上使用這個框架來搭建后臺。有一線/二線公司都在線上使用該框架了,我就認為問題不大了。

          總的來說,我這次搭建austin后臺實際編碼時間沒多少,都在改JSON配置和查文檔。我周六下午2點到的圖書館,新建了GitHub倉庫,在6點閉館前就已經搭出個大概頁面了,然后在周日空閑時間里再完善了幾下,感覺可以用了

          austin-amis倉庫地址:https://github.com/ZhongFuCheng3y/austin-admin

          在搭建的過程中,amis低代碼框架還是有地方可吐槽的,就是它的靈活性太低。我們的接口返回值需要迎合它的主體結構,當我們如果有嵌套JSON這種就變得異常難處理,表單無法用表達式進行回顯等等。

          它并不完美,很可能需要我用些奇怪的姿勢妥協,不要問我接口返回的時候為啥轉了一層Map

          不管怎么說,這不妨礙我花了極短的時間就能搭出一個能看的后臺管理頁面(CRUD已齊全)

          5、總結

          目前搭好的前端能用,也只能用一點點,后面會逐漸完善它的配置和功能的。我后面有鏈路追蹤的功能,肯定要在后臺這把清洗后的數據提供給后臺進行查詢,但也不會花比較長的篇幅再來聊前端這事了。

          我一直定位是在后端的代碼上,至于前端我能學,但我又不想學。怎么說呢,利益最大化吧。我把學前端的時間花在學后端上,或許可能對我有更大的受益。現在基本前后端分離了,在公司我也沒什么機會寫前端。

          下一篇很有可能是聊分布式定時任務框架上,我發現我的進度可以的,這個季度拿個4.0應該問題不大了。

          都看到這里了,點個贊一點都不過分吧?我是3y,下期見。

          關注我的微信公眾號【Java3y】除了技術我還會聊點日常,有些話只能悄悄說~ 【對線面試官+從零編寫Java項目】 持續高強度更新中!求star!!原創不易!!求三連!!

          austin項目源碼Gitee鏈接:gitee.com/austin

          austin項目源碼GitHub鏈接:github.com/austin


          來源:https://www.cnblogs.com/Java3y/p/16020226.html


          本文主要總結了在ICBU的核心溝通場景下服務端在此次性能優化過程中做的工作,供大家參考討論。


          一、背景與效果


          ICBU的核心溝通場景有了10年的“積累”,核心場景的界面響應耗時被拉的越來越長,也讓性能優化工作提上了日程,先說結論,經過這一波前后端齊心協力的優化努力,兩個核心界面90分位的數據,FCP平均由2.6s下降到1.9s,LCP平均由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 + ";charset=UTF-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. 注意事項


          此次優化的應用與界面本身歷史包袱很重,在進行流式改造的過程中,我們遇到了不少的阻力與挑戰,在解決問題的過程也學到了很多東西,這部分主要對遇到的問題進行整理。

          1. 二方包或自定義的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;
          
          }
          1. 谷歌瀏覽器禁止跨域名寫入cookie,我們的應用界面會以iframe的形式嵌入其他界面,谷歌瀏覽器正在逐步禁止跨域名寫cookie,如下所示:

          為了確保cookie能正常寫入,需要指定cookie的SameSite=None。

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

          1. 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'
          1. ThreadLocal與StreamingResponseBody

          在開始,我們使用StreamingResponseBody來實現的分塊傳輸:

          @GetMapping("/chunked")
          
          public ResponseEntity<StreamingResponseBody> 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.
          
           *
          
           * <p><strong>Note:</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
          
           */
          
          @FunctionalInterface
          
          public 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);
          
              }
          
          }

          四、措施三:數據直出


          1. 靜態圖片直出,頁面上有靜態的loge圖片,原本為cdn地址,在瀏覽器渲染時,需要建聯并會搶占線程,對于這類不會發生發生變化的圖片,可以直接替換為base64的形式,js可以直接加載。
          2. 加載數據直出,這部分需要根據具體業務來分析,部分業務數據是瀏覽器運行js腳本在本地二次請求加載的,由于低端機以及本地瀏覽器的能力限制,如果需要加載的數據很多,就很導致js線程的擠占,拖慢整體的時間,因此,可以考慮在服務器將部分數據預先加載好,隨http請求一起給瀏覽器,減少這部分的卡點。

          數據直出有利有弊,對于頁面的加載性能有正向影響的同時,也會同時導致HTTP的response增大以及服務端RT的升高。數據直出與流式分塊傳輸相結合的效果可能會更好,當服務端分塊響應HTTP請求時,本身的response就被切割成多塊,單次大小得到了控制,流式分塊傳輸下,服務端分批執行數據準備的策略也能很好的緩沖RT增長的問題。

          五、措施四:本地緩存


          以我們遇到的一個問題為例,我們的云盤文件列表需要在后端準備好文件所屬人的昵稱,這是在后端服務器由用戶id調用會員的rpc接口實時查詢的。分析這個場景,我們不難發現,同一時間,IM場景下的文件所屬人往往是其中歸屬在聊天的幾個人名下的,因此,可以利用HashMap作為緩存rpc查詢到的會員昵稱,避免重復的查詢與調用。

          六、措施五:下線歷史債務


          針對有歷史包袱的應用,歷史債務導致的額外耗時往往很大,這些歷史代碼可能包括以下幾類:

          • 未下線的實驗或者分流接口調用;
            • 時間線拉長,這部分的代碼殘骸在所難免,而且積少成多,累計起來往往有幾十上百毫秒的資源浪費,再加上業務開發時,大家往往沒有額外資源去評估這部分的很多代碼是否可以下線,因此可以借助性能優化的契機進行治理。
          • 已經廢棄的vm變量與重復變量治理。
            • 對vm變量的盤點過程中發現有很多之前在使用但現在已經廢棄的變量。當然,這部分變量的需要前后端同學共同梳理,防止下線線上依舊依賴的變量。

          作者:樹塔

          來源-微信公眾號:阿里云開發者

          出處:https://mp.weixin.qq.com/s/06eND-fUGQ7Y6gwJxmvwQQ


          主站蜘蛛池模板: 国产成人精品一区二三区在线观看| 久久亚洲中文字幕精品一区四| 日韩av无码一区二区三区| 另类国产精品一区二区| 国产综合一区二区在线观看| 国产精品成人一区二区三区| 一区二区在线视频观看| 国产主播在线一区| 中文字幕一区二区三匹| 亚洲av午夜福利精品一区| 日韩人妻无码一区二区三区久久| 色老板在线视频一区二区| 日韩精品无码一区二区中文字幕 | 亚洲乱码国产一区网址| 91香蕉福利一区二区三区| 一区二区三区电影在线观看| 亚洲Av永久无码精品一区二区| 亚洲爽爽一区二区三区| 国产一区二区草草影院| 538国产精品一区二区在线| 精品一区二区三区四区在线播放| 国产对白精品刺激一区二区| 91福利视频一区| 日本一道一区二区免费看| 大伊香蕉精品一区视频在线| 中文字幕一区二区三区久久网站| 在线精品视频一区二区| 中文字幕一区二区三区精华液| 无码人妻精品一区二区三区夜夜嗨| 国产香蕉一区二区三区在线视频| 精品亚洲一区二区三区在线观看 | 国产丝袜美女一区二区三区| 精品无码一区二区三区爱欲九九| 成人在线观看一区| 无码AV天堂一区二区三区| 一区视频免费观看| 日韩成人一区ftp在线播放| 中文无码精品一区二区三区| 亚洲综合色一区二区三区| 中文字幕一区在线观看| 亚洲一区二区三区高清不卡|