焦 宇,李 民,王 歡,余開朝
(昆明理工大學(xué)機(jī)電工程學(xué)院,云南昆明 650500)
隨著互聯(lián)網(wǎng)技術(shù)的迅速發(fā)展,人們與網(wǎng)絡(luò)的關(guān)系日益密切,互聯(lián)網(wǎng)帶給人們一種全新的生活方式,如網(wǎng)上購(gòu)物、滴滴打車、學(xué)習(xí)網(wǎng)課等都為人們的生活帶來(lái)了全新的體驗(yàn),而近些年的網(wǎng)上購(gòu)物更是發(fā)展得如火如荼[1]。網(wǎng)購(gòu)系統(tǒng)的出現(xiàn)極大地改善了人們的購(gòu)物體驗(yàn),雖然不能完全替代線下購(gòu)物,但已逐漸成為人們主流的購(gòu)物方式。由此,對(duì)網(wǎng)購(gòu)系統(tǒng)設(shè)計(jì)要求隨之提高,如遇到某些購(gòu)物活動(dòng),大量客戶進(jìn)入系統(tǒng)進(jìn)行購(gòu)物,傳統(tǒng)的單機(jī)部署式網(wǎng)購(gòu)系統(tǒng)已經(jīng)無(wú)法承受如此高的并發(fā)量,會(huì)發(fā)生提取數(shù)據(jù)緩慢甚至宕機(jī)的情況,影響人們的購(gòu)物體驗(yàn)。因此,對(duì)網(wǎng)購(gòu)系統(tǒng)進(jìn)行分布式拓展及緩存優(yōu)化具有重要意義。
分布式計(jì)算技術(shù)最早由OMG(Open Management Group)組織于1992 年提出,這一技術(shù)的出現(xiàn)很大程度上提高了分布式系統(tǒng)的開發(fā)效率。隨著互聯(lián)網(wǎng)與網(wǎng)絡(luò)技術(shù)的迅速發(fā)展和廣泛應(yīng)用,Sun 公司和Microsoft 公司分別推出了應(yīng)用于B/S 架構(gòu)的J2EE 開發(fā)應(yīng)用平臺(tái)和面向B/S 應(yīng)用的.NET 開發(fā)應(yīng)用平臺(tái)[2-3]。
相比于傳統(tǒng)單機(jī)部署的企業(yè)級(jí)項(xiàng)目,分布式部署項(xiàng)目方式的出現(xiàn)能夠極大地幫助企業(yè)提高系統(tǒng)的穩(wěn)定性、擴(kuò)展性和并發(fā)性。人們接觸的架構(gòu)通常是從簡(jiǎn)單到復(fù)雜、從單一到復(fù)合不斷改進(jìn)的過(guò)程。隨著互聯(lián)網(wǎng)技術(shù)的不斷發(fā)展,可以在分布式架構(gòu)中加入負(fù)載均衡算法、Redis 數(shù)據(jù)庫(kù)等技術(shù)以實(shí)現(xiàn)對(duì)項(xiàng)目的優(yōu)化。李效利等[4]通過(guò)分布式網(wǎng)絡(luò)架構(gòu)提高了監(jiān)測(cè)平臺(tái)的一體化管理;陳鵬煒[5]運(yùn)用open?resty 及其負(fù)載均衡策略設(shè)計(jì)了一個(gè)集群實(shí)名鑒權(quán)系統(tǒng),相比傳統(tǒng)系統(tǒng)提高了性能;李曉東[6]采用ssm 框架和Nginx 負(fù)載均衡策略緩解了Mysql 數(shù)據(jù)庫(kù)的讀取壓力;孔祥真等[7]為了解決網(wǎng)絡(luò)服務(wù)器中高流量不穩(wěn)定的問(wèn)題,部署Nginx和tomcat 服務(wù)器以提供一個(gè)高性能的服務(wù)器解決方案。這些學(xué)者在運(yùn)用Nginx 負(fù)載均衡時(shí)僅僅考慮了應(yīng)對(duì)并發(fā)時(shí)的服務(wù)器壓力問(wèn)題,面對(duì)數(shù)據(jù)存儲(chǔ)效率問(wèn)題時(shí)也只是簡(jiǎn)單地采用本地Mysql 數(shù)據(jù)庫(kù),而面對(duì)大量人群訪問(wèn)數(shù)據(jù)時(shí),其數(shù)據(jù)提取性能并沒有得到有效改善。
在分布式集群中加入Redis 緩存的方式同樣可以提高系統(tǒng)性能。李彥辰等[8]通過(guò)設(shè)計(jì)Redis 集群的分布式搜索方法提高了連接分析性能;陳清[9]為了應(yīng)對(duì)大型機(jī)電設(shè)備數(shù)據(jù)交換時(shí)出現(xiàn)的問(wèn)題,將Redis 緩存運(yùn)用其中,提高了數(shù)據(jù)查詢速度;Li等[10]運(yùn)用改進(jìn)的一致性哈希算法進(jìn)行過(guò)濾系統(tǒng)設(shè)計(jì),提高了Redis 集群的可用性等性能。這些研究通過(guò)Redis 集群或者算法優(yōu)化方式提高了數(shù)據(jù)提取性能,然而卻忽略了大量人群訪問(wèn)服務(wù)器時(shí)的壓力問(wèn)題,當(dāng)服務(wù)器因?yàn)閴毫^(guò)大發(fā)生宕機(jī)問(wèn)題時(shí),數(shù)據(jù)獲取將變得十分困難。
針對(duì)以上不足,本文提出了一種新的分布式高可用集群,對(duì)之前單一的Nginx 策略或者Redis 集群進(jìn)行改進(jìn),將Nginx 負(fù)載均衡策略、Redis 哨兵集群相結(jié)合,并且加入最新的Nginx lua 緩存技術(shù),改善了之前在實(shí)現(xiàn)高可用時(shí)數(shù)據(jù)提取效率問(wèn)題,以及在數(shù)據(jù)提取時(shí)所忽略的服務(wù)器壓力過(guò)大問(wèn)題,既保證了集群在高并發(fā)情況下的正常運(yùn)作,又提升了數(shù)據(jù)提取性能。最后以傳統(tǒng)本地部署的網(wǎng)購(gòu)系統(tǒng)為背景,結(jié)合新建的集群進(jìn)行試驗(yàn)驗(yàn)證,通過(guò)得到的參數(shù)證明該集群可提高數(shù)據(jù)提取能力和抗并發(fā)能力。
OpenResty 是一個(gè)基于Nginx 與Lua 的高性能Web 平臺(tái),其內(nèi)部有精良的Lua 庫(kù)、第三方模塊等依賴項(xiàng)。它可以用來(lái)搭建能夠處理高并發(fā)、擴(kuò)展性極高的動(dòng)態(tài)Web 應(yīng)用、Web 服務(wù)和動(dòng)態(tài)網(wǎng)關(guān)[11-13]。Nginx 是一款高性能的HTTP 服務(wù)器/反向代理服務(wù)器及電子郵件(IMAP/POP3)代理服務(wù)器,最高可以承受5 萬(wàn)的并發(fā)量,而對(duì)CPU、內(nèi)存等資源消耗卻非常低,運(yùn)行十分穩(wěn)定[14-16]。此外,Nginx 在作為Web 服務(wù)器、動(dòng)態(tài)分離服務(wù)器,以及反向代理服務(wù)器時(shí)都發(fā)揮著重要作用。在反向代理方面,Nginx 實(shí)現(xiàn)的負(fù)載均衡算法主要有輪詢、輪詢權(quán)值、ip_hash、url_hash(第三方)、fail(第三方)等。Nginx lua 是基于Nginx 協(xié)程機(jī)制的一種緩存方式,針對(duì)用戶想要獲取的內(nèi)容,使用lua 腳本的方式在Nginx 上完成對(duì)應(yīng)業(yè)務(wù)代碼的處理邏輯,可以避免訪問(wèn)Java服務(wù)器。
Redis 是一個(gè)開源的高性能鍵值對(duì)(key-value)數(shù)據(jù)庫(kù),根據(jù)官方測(cè)試得出50 個(gè)并發(fā)執(zhí)行10 000 個(gè)請(qǐng)求,讀的速度是11 000 次/s,寫的速度是81 000 次/s[17],且Redis 可以提供多種鍵值數(shù)據(jù)類型如:字符串類型、哈希類型、列表類型等。Redis 是一個(gè)緩存的數(shù)據(jù)庫(kù)中間件,可以將它設(shè)置為數(shù)據(jù)刷新到磁盤中的策略,可以支持當(dāng)系統(tǒng)對(duì)一定數(shù)量key 產(chǎn)生set 操作變化時(shí)即刷新一次磁盤,也可以設(shè)置每個(gè)1s 或2s 輪詢的方式刷新對(duì)應(yīng)的磁盤。因此,Redis 磁盤具備了存儲(chǔ)數(shù)據(jù)庫(kù)的能力,但會(huì)丟失一定數(shù)量的數(shù)據(jù),可知Redis 也是一個(gè)易失性的數(shù)據(jù)存儲(chǔ)。Redis 除讀寫高效外,還可以利用其搭建多節(jié)分布式集群從而提高數(shù)據(jù)讀取效率,同時(shí)也極大地提高了系統(tǒng)性能。
目前,網(wǎng)購(gòu)系統(tǒng)功能趨于穩(wěn)定,對(duì)客戶而言主要以訂單功能、登錄功能以及商品詳情頁(yè)模塊為主,可以將其拆分放在不同的服務(wù)器中,降低在同一時(shí)間多客戶訪問(wèn)時(shí)的訪問(wèn)壓力;其次可以降低耦合度,有利于后期工程師對(duì)網(wǎng)購(gòu)系統(tǒng)的功能維護(hù)及功能添加。
項(xiàng)目垂直拆分是指按照項(xiàng)目的不同應(yīng)用功能進(jìn)行拆分,將系統(tǒng)不同的功能拆分到不同的服務(wù)器中,各功能之間獨(dú)自運(yùn)行,互不影響,同時(shí)提高系統(tǒng)抗并發(fā)能力,具體部署如圖1所示。
Fig.1 Vertical split圖1 垂直拆分
項(xiàng)目水平拆分是指為了提高后期項(xiàng)目維護(hù)效果,按照業(yè)務(wù)對(duì)系統(tǒng)進(jìn)行拆分。例如,本系統(tǒng)的業(yè)務(wù)代碼層可以分為Controller 層、dao層、service 層、dataobject 層、model層、接口層,從而降低代碼之間的耦合度,如圖2所示。
Fig.2 Horizontal split圖2 水平拆分
系統(tǒng)優(yōu)化主要是在高并發(fā)優(yōu)化及數(shù)據(jù)提取效率方面,采用Openresty 平臺(tái)結(jié)合Redis數(shù)據(jù)庫(kù)搭建集群框架。該網(wǎng)購(gòu)系統(tǒng)采用前后端分離的方式進(jìn)行設(shè)計(jì),前端運(yùn)用html5標(biāo)準(zhǔn)進(jìn)行編寫,因此首先將Nginx 作為靜態(tài)的Web 服務(wù)器使用,然后將其作為動(dòng)靜分離的服務(wù)器,對(duì)應(yīng)的Nginx 作反向業(yè)務(wù)代理后,可以將對(duì)應(yīng)的靜態(tài)請(qǐng)求依舊路由在本地的html 文件中,以靜態(tài)資源請(qǐng)求的方式返回給前端,之后依賴反向代理服務(wù),將動(dòng)態(tài)請(qǐng)求返回到后端,以完成對(duì)應(yīng)的動(dòng)態(tài)請(qǐng)求代理,以Ajax 請(qǐng)求的方式返回給前端固定的Json參數(shù),實(shí)現(xiàn)動(dòng)靜分離的服務(wù)器使用。Nginx 采用輪詢的負(fù)載均衡方式,將客戶端的請(qǐng)求發(fā)送給Tomcat 服務(wù)器。例如,用戶在獲取商品詳情頁(yè)并提取數(shù)據(jù)時(shí),會(huì)將其請(qǐng)求發(fā)送到某一臺(tái)Tomcat 上,首先從Redis 中提取數(shù)據(jù),若Redis中無(wú)數(shù)據(jù),則從Mysql 數(shù)據(jù)庫(kù)中進(jìn)行提取,并且查出數(shù)據(jù)后將數(shù)據(jù)緩存到Redis 中,這樣用戶在進(jìn)行第二次查詢時(shí)便可從Redis 緩存中直接查詢數(shù)據(jù),提高了用戶對(duì)數(shù)據(jù)的查詢效率,如圖3所示。
Fig.3 Distributed high availability cluster圖3 分布式高可用集群
單機(jī)部署項(xiàng)目通常部署的是單機(jī)版的Redis,其弊端在于對(duì)應(yīng)Redis 的單點(diǎn)問(wèn)題瓶頸難以處理,若對(duì)應(yīng)單機(jī)節(jié)點(diǎn)的Redis 中斷,則所有的業(yè)務(wù)操作都會(huì)消失,且單機(jī)版有容量上限,無(wú)法滿足高效存儲(chǔ)要求。采取sentinel 的哨兵模式可以很好地解決該問(wèn)題:引入Redis sentinel 哨兵機(jī)制,假設(shè)有兩臺(tái)Redis 服務(wù)器,將Redis2 作為Redis1 的從機(jī),Redis 支持主從同步模式,Redis1 可以將數(shù)據(jù)同步給Redis2。理想情況下,當(dāng)網(wǎng)購(gòu)系統(tǒng)服務(wù)器探測(cè)到Redis1 出問(wèn)題時(shí),可以自動(dòng)切換到Redis2。然而探測(cè)Redis1 是一個(gè)繁雜的過(guò)程,因?yàn)閷?duì)應(yīng)的分布式環(huán)境十分復(fù)雜,無(wú)法明確Redis1 是否為宕機(jī)狀態(tài),并且它需要知道切換到哪一臺(tái)備機(jī)上。因此,引入哨兵機(jī)制,它與Redis1、Redis2 都建立了長(zhǎng)連接,并且是一個(gè)心跳機(jī)制,Redis sentinel 清楚地知道Redis1 和Redis2 是處于哪種狀態(tài),當(dāng)項(xiàng)目啟動(dòng)時(shí)無(wú)需感知Redis1 和Redis2,只需詢問(wèn)Redis sentinel 即可知道需要連接哪一臺(tái)服務(wù)器。Redis sentinel 是一個(gè)單點(diǎn)機(jī)器,因此可以完全確定Redis1 和Redis2 哪一臺(tái)是主,它可以將Redis1指定為master,將Redis2 指定為slave,一旦確定Redis1 位于master 后,項(xiàng)目服務(wù)器就會(huì)連接Redis1 做一個(gè)get、set 操作,當(dāng)Redis1 產(chǎn)生異常后,心跳機(jī)制就會(huì)被破壞掉,Redis sentinel將立刻做一次切換,將Redis2指定為master,Redis1指定為slave,此時(shí)Redis sentinel 會(huì)通知項(xiàng)目服務(wù)器產(chǎn)生了變化,所部屬項(xiàng)目重新觸發(fā)詢問(wèn)流程,會(huì)將get、set 操作改變到Redis2 去。哨兵機(jī)制很好地解決了當(dāng)用戶提取數(shù)據(jù)時(shí),一臺(tái)Redis 服務(wù)器出現(xiàn)問(wèn)題時(shí),用戶無(wú)法獲取信息的問(wèn)題。本文采取的是開啟2 臺(tái)Redis 主機(jī)、兩臺(tái)Redis 從機(jī)的方式實(shí)現(xiàn)Redis集群,如圖4所示。
Fig.4 Redis cluster圖4 Redis集群
盡管采用了Redis 集群部署,但是面對(duì)大量訪問(wèn)涌入時(shí),服務(wù)器壓力仍然較大,若將Nginx 與Redis 集群聯(lián)系起來(lái),則對(duì)數(shù)據(jù)緩存性能有所提升。例如,當(dāng)用戶發(fā)起訪問(wèn)商品詳情頁(yè)請(qǐng)求時(shí),Nginx 收到請(qǐng)求后不會(huì)直接鏈路到后端部署的兩臺(tái)項(xiàng)目服務(wù)器,它直接連到Redis 從機(jī)上,進(jìn)行只讀不寫的操作,同時(shí)分擔(dān)了Redis 主機(jī)的負(fù)載,若Redis中沒有指定數(shù)據(jù),就回源到項(xiàng)目服務(wù)器上,項(xiàng)目服務(wù)器也對(duì)Redis 中的數(shù)據(jù)進(jìn)行判斷,若還是沒有,則回源到Mysql數(shù)據(jù)庫(kù)中進(jìn)行數(shù)據(jù)提取,并且放入Redis 中,則下一次客戶將對(duì)應(yīng)請(qǐng)求發(fā)送到Nginx 上時(shí)就可以直接通過(guò)Redis 進(jìn)行“讀”的操作。將Lua 緩存Redis 主從搭配方式相結(jié)合,可以從容應(yīng)對(duì)大流量的數(shù)據(jù),提高數(shù)據(jù)提取效率。
本文將網(wǎng)購(gòu)系統(tǒng)及Nginx、Redis 集群部署到多臺(tái)虛擬機(jī)Linux 系統(tǒng)中,開啟訪問(wèn)權(quán)限和防火墻,運(yùn)用Xshell 文件實(shí)現(xiàn)本地Windows 系統(tǒng)與Linux 系統(tǒng)的通信連接,從而可以進(jìn)行對(duì)項(xiàng)目的運(yùn)行操作,如圖5所示。
Fig.5 Project server cluster deployment圖5 項(xiàng)目服務(wù)器集群部署
將本地的網(wǎng)購(gòu)項(xiàng)目打成jar 包,分別傳輸至2 臺(tái)Vm?ware 虛擬機(jī)中。將兩臺(tái)服務(wù)器靜態(tài)IP 地址分別設(shè)置為192.168.157.138 和192.168.157.139,對(duì)應(yīng)端口號(hào)都設(shè)置為8 090 端口;再將Openresty 部署到第三臺(tái)虛擬機(jī)中,設(shè)置靜態(tài)地址為192.168.157.140,點(diǎn)開nginx.conf 文件完成監(jiān)聽端口號(hào)、域名ip、負(fù)載均衡輪詢方式等一系列設(shè)置。Redis 集群采用2 主2 從的方式,將其部署到4 臺(tái)虛擬機(jī)中,分別設(shè)置好靜態(tài)IP 地址,并且開啟端口分別為6 500、6 501、6 502和6 503,通過(guò)測(cè)試,集群可以正常啟動(dòng)。再進(jìn)入Openresty服務(wù)器中,新建itemredis.lua 文件,在其中設(shè)置lua 調(diào)用Re?dis 的腳本,同時(shí)修改nginx.conf 文件,使其運(yùn)行時(shí)可以啟動(dòng)相應(yīng)的腳本。部署完所有項(xiàng)目后開啟項(xiàng)目進(jìn)行壓力測(cè)試。
本次測(cè)試主要驗(yàn)證項(xiàng)目?jī)?yōu)化后的抗并發(fā)能力及數(shù)據(jù)提取能力,采用jmeter 壓測(cè)工具進(jìn)行測(cè)試。實(shí)驗(yàn)測(cè)試環(huán)境為:i7 10 代/16G 內(nèi)存、Windows10 系統(tǒng)、JDK1.8、Redis4.0、Tomcat8.5、Mysql5.7。
Jmeter 是基于Java 的壓力測(cè)試工具,可以用來(lái)測(cè)試服務(wù)器在不同壓力下的強(qiáng)度與性能[18-19]。使用該測(cè)試工具,分別設(shè)置測(cè)試用戶在1 000、1 500、2 000 時(shí)的并發(fā)量;啟動(dòng)線程時(shí)間為10s,循環(huán)次數(shù)15 次,進(jìn)行壓力測(cè)試。具體通過(guò)觀察數(shù)據(jù)提取的平均值、中位數(shù)、90%線位、錯(cuò)誤率以及吞吐量對(duì)比出原始網(wǎng)購(gòu)系統(tǒng)與優(yōu)化后網(wǎng)購(gòu)系統(tǒng)的性能。圖6——圖8 分別表示在1 000、1 500、2 000 并發(fā)量時(shí)的壓測(cè)結(jié)果。
Fig.6 1 000 concurrency comparison results圖6 1 000并發(fā)量對(duì)比結(jié)果
Fig.7 1 500 concurrency comparison results圖7 1 500并發(fā)量對(duì)比結(jié)果
Fig.8 2 000 concurrency comparison results圖8 2 000并發(fā)量對(duì)比結(jié)果
測(cè)試結(jié)果表明,與傳統(tǒng)本地部署的網(wǎng)購(gòu)系統(tǒng)相比,優(yōu)化后的網(wǎng)購(gòu)系統(tǒng)在吞吐量、數(shù)據(jù)響應(yīng)時(shí)間上有明顯性能提升,在并發(fā)量為1 000 的情況下,吞吐量相比增加624/s、平均響應(yīng)時(shí)間減少72ms;并發(fā)量為1 500的情況下,吞吐量相比增加1 274/s、平均響應(yīng)時(shí)間減少151ms;并發(fā)量為2 000的情況下,吞吐量相比增加1 062/s、平均響應(yīng)時(shí)間減少135ms。錯(cuò)誤率方面,并發(fā)量為1 000、1 500 時(shí)錯(cuò)誤率都為0,在并發(fā)量為2 000 時(shí),優(yōu)化后系統(tǒng)的錯(cuò)誤率仍然低于之前的錯(cuò)誤率。
本文針對(duì)傳統(tǒng)本地部署的網(wǎng)購(gòu)系統(tǒng)進(jìn)行了項(xiàng)目?jī)?yōu)化及重構(gòu),通過(guò)將項(xiàng)目部署到虛擬機(jī)服務(wù)器中,并且運(yùn)用Nginx、Redis 集群及Lua 緩存等技術(shù),構(gòu)建出一個(gè)分布式高可用的集群以支撐該系統(tǒng)。通過(guò)壓力測(cè)試驗(yàn)證所建集群在控制錯(cuò)誤率的情況下,該系統(tǒng)的并發(fā)能力及數(shù)據(jù)處理效率都有一定提高,可以更好應(yīng)對(duì)高并發(fā)情況下產(chǎn)生的問(wèn)題。然而,本文并沒有考慮如何應(yīng)對(duì)類似“雙十一”活動(dòng)的瞬時(shí)并發(fā)問(wèn)題,這有待進(jìn)一步研究解決。