日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

第六章 Web开发实战1——HTTP服务

發(fā)布時(shí)間:2024/9/20 编程问答 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 第六章 Web开发实战1——HTTP服务 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

此處我說的HTTP服務(wù)主要指如訪問京東網(wǎng)站時(shí)我們看到的熱門搜索、用戶登錄、實(shí)時(shí)價(jià)格、實(shí)時(shí)庫存、服務(wù)支持、廣告語等這種非Web頁面,而是在Web頁面中異步加載的相關(guān)數(shù)據(jù)。這些服務(wù)有個(gè)特點(diǎn)即訪問量巨大、邏輯比較單一;但是如實(shí)時(shí)庫存邏輯其實(shí)是非常復(fù)雜的。在京東這些服務(wù)每天有幾億十幾億的訪問量,比如實(shí)時(shí)庫存服務(wù)曾經(jīng)在沒有任何IP限流、DDos防御的情況被刷到600多萬/分鐘的訪問量,而且能輕松應(yīng)對(duì)。支撐如此大的訪問量就需要考慮設(shè)計(jì)良好的架構(gòu),并很容易實(shí)現(xiàn)水平擴(kuò)展。

?

架構(gòu)

此處介紹下我曾使用過Nginx+JavaEE的架構(gòu)。

?

1、單DB架構(gòu)

早期架構(gòu)可能就是Nginx直接upstream請(qǐng)求到后端Tomcat,擴(kuò)容時(shí)基本是增加新的Tomcat實(shí)例,然后通過Nginx負(fù)載均衡upstream過去。此時(shí)數(shù)據(jù)庫還不是瓶頸。當(dāng)訪問量到一定級(jí)別,數(shù)據(jù)庫的壓力就上來了,此處單純的靠單個(gè)數(shù)據(jù)庫可能扛不住了,此時(shí)可以通過數(shù)據(jù)庫的讀寫分離或加緩存來實(shí)現(xiàn)。

?

?

2、DB+Cache/數(shù)據(jù)庫讀寫分離架構(gòu)


?

此時(shí)就通過使用如數(shù)據(jù)庫讀寫分離或者Redis這種緩存來支撐更大的訪問量。使用緩存這種架構(gòu)會(huì)遇到的問題諸如緩存與數(shù)據(jù)庫數(shù)據(jù)不同步造成數(shù)據(jù)不一致(一般設(shè)置過期時(shí)間),或者如Redis掛了,此時(shí)會(huì)直接命中數(shù)據(jù)庫導(dǎo)致數(shù)據(jù)庫壓力過大;可以考慮Redis的主從或者一致性Hash 算法做分片的Redis集群;使用緩存這種架-構(gòu)要求應(yīng)用對(duì)數(shù)據(jù)的一致性要求不是很高;比如像下訂單這種要落地的數(shù)據(jù)不適合用Redis存儲(chǔ),但是訂單的讀取可以使用緩存。

?

3、Nginx+Lua+Local Redis+Mysql集群架構(gòu)

首先Nginx通過Lua讀取本機(jī)Redis緩存,如果不命中才回源到后端Tomcat集群;后端Tomcat集群再讀取Mysql數(shù)據(jù)庫。Redis都是安裝到和Nginx同一臺(tái)服務(wù)器,Nginx直接讀本機(jī)可以減少網(wǎng)絡(luò)延時(shí)。Redis通過主從方式同步數(shù)據(jù),Redis主從一般采用樹的方式實(shí)現(xiàn):

在葉子節(jié)點(diǎn)可以做AOF持久化,保證在主Redis掛時(shí)能進(jìn)行恢復(fù);此處假設(shè)對(duì)Redis很依賴的話,可以考慮多主Redis架構(gòu),而不是單主,來防止單主掛了時(shí)數(shù)據(jù)的不一致和擊穿到后端Tomcat集群。這種架構(gòu)的缺點(diǎn)就是要求Redis實(shí)例數(shù)據(jù)量較小,如果單機(jī)內(nèi)存不足以存儲(chǔ)這么多數(shù)據(jù),當(dāng)然也可以通過如尾號(hào)為1的在A服務(wù)器,尾號(hào)為2的在B服務(wù)器這種方式實(shí)現(xiàn);缺點(diǎn)也很明顯,運(yùn)維復(fù)雜、擴(kuò)展性差。

?

4、Nginx+Lua+ Redis集群+Mysql集群架構(gòu)?

和之前架構(gòu)不同的點(diǎn)是此時(shí)我們使用一致性Hash算法實(shí)現(xiàn)Redis集群而不是讀本機(jī)Redis,保證其中一臺(tái)掛了,只有很少的數(shù)據(jù)會(huì)丟失,防止擊穿到數(shù)據(jù)庫。Redis集群分片可以使用Twemproxy;如果 Tomcat實(shí)例很多的話,此時(shí)就要考慮Redis和Mysql鏈接數(shù)問題,因?yàn)榇蟛糠諶edis/Mysql客戶端都是通過連接池實(shí)現(xiàn),此時(shí)的鏈接數(shù)會(huì)成為瓶頸。一般方法是通過中間件來減少鏈接數(shù)。

Twemproxy與Redis之間通過單鏈接交互,并Twemproxy實(shí)現(xiàn)分片邏輯;這樣我們可以水平擴(kuò)展更多的Twemproxy來增加鏈接數(shù)。

?

此時(shí)的問題就是Twemproxy實(shí)例眾多,應(yīng)用維護(hù)配置困難;此時(shí)就需要在之上做負(fù)載均衡,比如通過LVS/HAProxy實(shí)現(xiàn)VIP(虛擬IP),可以做到切換對(duì)應(yīng)用透明、故障自動(dòng)轉(zhuǎn)移;還可以通過實(shí)現(xiàn)內(nèi)網(wǎng)DNS來做其負(fù)載均衡。

本文沒有涉及Nginx之上是如何架構(gòu)的,對(duì)于Nginx、Redis、Mysql等的負(fù)載均衡、資源的CDN化不是本文關(guān)注的點(diǎn),有興趣可以參考

很早的Taobao CDN架構(gòu)

Nginx/LVS/HAProxy負(fù)載均衡軟件的優(yōu)缺點(diǎn)詳解?

?

實(shí)現(xiàn)

接下來我們來搭建一下第四種架構(gòu)。

以獲取如京東商品頁廣告詞為例,如下圖

假設(shè)京東有10億商品,那么廣告詞極限情況是10億;所以在設(shè)計(jì)時(shí)就要考慮:

1、數(shù)據(jù)量,數(shù)據(jù)更新是否頻繁且更新量是否很大;

2、是K-V還是關(guān)系,是否需要批量獲取,是否需要按照規(guī)則查詢。

?

而對(duì)于本例,廣告詞更新量不會(huì)很大,每分鐘可能在幾萬左右;而且是K-V的,其實(shí)適合使用關(guān)系存儲(chǔ);因?yàn)閺V告詞是商家維護(hù),因此后臺(tái)查詢需要知道這些商品是哪個(gè)商家的;而對(duì)于前臺(tái)是不關(guān)心商家的,是KV存儲(chǔ),所以前臺(tái)顯示的可以放進(jìn)如Redis中。 即存在兩種設(shè)計(jì):

1、所有數(shù)據(jù)存儲(chǔ)到Mysql,然后熱點(diǎn)數(shù)據(jù)加載到Redis;

2、關(guān)系存儲(chǔ)到Mysql,而數(shù)據(jù)存儲(chǔ)到如SSDB這種持久化KV存儲(chǔ)中。

?

基本數(shù)據(jù)結(jié)構(gòu):商品ID、廣告詞、所屬商家、開始時(shí)間、結(jié)束時(shí)間、是否有效。

?

后臺(tái)邏輯

1、商家登錄后臺(tái);

2、按照商家分頁查詢商家數(shù)據(jù),此處要按照商品關(guān)鍵詞或商品類目查詢的話,需要走商品系統(tǒng)的搜索子系統(tǒng),如通過Solr或elasticsearch實(shí)現(xiàn)搜索子系統(tǒng);

3、進(jìn)行廣告詞的增刪改查;

4、增刪改時(shí)可以直接更新Redis緩存或者只刪除Redis緩存(第一次前臺(tái)查詢時(shí)寫入緩存);

?

前臺(tái)邏輯

1、首先Nginx通過Lua查詢Redis緩存;

2、查詢不到的話回源到Tomcat,Tomcat讀取數(shù)據(jù)庫查詢到數(shù)據(jù),然后把最新的數(shù)據(jù)異步寫入Redis(一般設(shè)置過期時(shí)間,如5分鐘);此處設(shè)計(jì)時(shí)要考慮假設(shè)Tomcat讀取Mysql的極限值是多少,然后設(shè)計(jì)降級(jí)開關(guān),如假設(shè)每秒回源達(dá)到100,則直接不查詢Mysql而返回空的廣告詞來防止Tomcat應(yīng)用雪崩。

?

為了簡單,我們不進(jìn)行后臺(tái)的設(shè)計(jì)實(shí)現(xiàn),只做前端的設(shè)計(jì)實(shí)現(xiàn),此時(shí)數(shù)據(jù)結(jié)構(gòu)我們簡化為[商品ID、廣告詞]。另外有朋友可能看到了,可以直接把Tomcat部分干掉,通過Lua直接讀取Mysql進(jìn)行回源實(shí)現(xiàn)。為了完整性此處我們還是做回源到Tomcat的設(shè)計(jì),因?yàn)槿绻壿嫳容^復(fù)雜的話或一些限制(比如使用Java特有協(xié)議的RPC)還是通過Java去實(shí)現(xiàn)更方便一些。

?

項(xiàng)目搭建

項(xiàng)目部署目錄結(jié)構(gòu)。

Java代碼??
  • /usr/chapter6??
  • ??redis_6660.conf??
  • ??redis_6661.conf??
  • ??nginx_chapter6.conf??
  • ??nutcracker.yml??
  • ??nutcracker.init??
  • ??webapp??
  • WEB-INF??
  • ???lib??
  • ???classes??
  • ???web.xml???
  • ?

    Redis+Twemproxy配置

    此處根據(jù)實(shí)際情況來決定Redis大小,此處我們已兩個(gè)Redis實(shí)例(6660、6661),在Twemproxy上通過一致性Hash做分片邏輯。

    ?

    安裝

    之前已經(jīng)介紹過Redis和Twemproxy的安裝了。

    ?

    Redis配置redis_6660.conf和redis_6661.conf????

    Java代碼??
  • #分別為6660?6661??
  • port?6660??
  • #進(jìn)程ID?分別改為redis_6660.pid?redis_6661.pid??
  • pidfile?"/var/run/redis_6660.pid"??
  • #設(shè)置內(nèi)存大小,根據(jù)實(shí)際情況設(shè)置,此處測試僅設(shè)置20mb??
  • maxmemory?20mb??
  • #內(nèi)存不足時(shí),按照過期時(shí)間進(jìn)行LRU刪除??
  • maxmemory-policy?volatile-lru??
  • #Redis的過期算法不是精確的而是通過采樣來算的,默認(rèn)采樣為3個(gè),此處我們改成10??
  • maxmemory-samples?10??
  • #不進(jìn)行RDB持久化??
  • save?“”??
  • #不進(jìn)行AOF持久化??
  • appendonly?no???
  • 將如上配置放到redis_6660.conf和redis_6661.conf配置文件最后即可,后邊的配置會(huì)覆蓋前邊的。??

    ?

    Twemproxy配置nutcracker.yml?

    Java代碼??
  • server1:??
  • ??listen:?127.0.0.1:1111??
  • ??hash:?fnv1a_64??
  • ??distribution:?ketama??
  • ??redis:?true??
  • ??timeout:?1000??
  • ??servers:??
  • ???-?127.0.0.1:6660:1?server1??
  • ???-?127.0.0.1:6661:1?server2??
  • ?復(fù)制nutcracker.init到/usr/chapter6下,并修改配置文件為/usr/chapter6/nutcracker.yml。

    ?

    啟動(dòng)

    Java代碼??
  • nohup?/usr/servers/redis-2.8.19/src/redis-server??/usr/chapter6/redis_6660.conf?&??
  • nohup?/usr/servers/redis-2.8.19/src/redis-server??/usr/chapter6/redis_6661.conf?&??
  • /usr/chapter6/nutcracker.init?start??
  • ps?-aux?|?grep?-e?redis??-e?nutcracker??
  • ??

    Mysql+Atlas配置

    Atlas類似于Twemproxy,是Qihoo 360基于Mysql Proxy開發(fā)的一個(gè)Mysql中間件,據(jù)稱每天承載讀寫請(qǐng)求數(shù)達(dá)幾十億,可以實(shí)現(xiàn)分表、分庫(sharding版本)、讀寫分離、數(shù)據(jù)庫連接池等功能,缺點(diǎn)是沒有實(shí)現(xiàn)跨庫分表功能,需要在客戶端使用分庫邏輯,目前Atlas不活躍。另一個(gè)選擇是使用如阿里的TDDL,它是在客戶端完成之前說的功能。到底選擇是在客戶端還是在中間件根據(jù)實(shí)際情況選擇。

    ?

    此處我們不做Mysql的主從復(fù)制(讀寫分離),只做分庫分表實(shí)現(xiàn)。

    ?

    Mysql初始化

    ?

    為了測試我們此處分兩個(gè)表。

    ?

    Java代碼??
  • CREATE?DATABASE?chapter6?DEFAULT?CHARACTER?SET?utf8;??
  • use?chapter6;??
  • CREATE?TABLE??chapter6.ad_0(??
  • ??????sku_id?BIGINT,??
  • ??????content?VARCHAR(4000)??
  • )?ENGINE=InnoDB??DEFAULT?CHARSET=utf8;??
  • CREATE?TABLE??chapter6.ad_1??
  • ??????sku_id?BIGINT,??
  • ??????content?VARCHAR(4000)??
  • )?ENGINE=InnoDB??DEFAULT?CHARSET=utf8;??
  • ?

    Atlas安裝

    Java代碼??
  • cd?/usr/servers/??
  • wget?https://github.com/Qihoo360/Atlas/archive/2.2.1.tar.gz?-O?Atlas-2.2.1.tar.gz??
  • tar?-xvf?Atlas-2.2.1.tar.gz??
  • cd?Atlas-2.2.1/??
  • #Atlas依賴mysql_config,如果沒有可以通過如下方式安裝??
  • apt-get?install?libmysqlclient-dev??
  • #安裝Lua依賴??
  • wget?http://www.lua.org/ftp/lua-5.1.5.tar.gz??
  • tar?-xvf?lua-5.1.5.tar.gz??
  • cd?lua-5.1.5/??
  • make?linux?&&?make?install??
  • #安裝glib依賴??
  • apt-get?install?libglib2.0-dev??
  • #安裝libevent依賴??
  • apt-get?install?libevent????
  • #安裝flex依賴??
  • apt-get?install?flex??
  • #安裝jemalloc依賴??
  • apt-get?install?libjemalloc-dev??
  • #安裝OpenSSL依賴??
  • apt-get?install?openssl??
  • apt-get?install?libssl-dev??
  • ?apt-get?install?libssl0.9.8??
  • ??
  • ./configure?--with-mysql=/usr/bin/mysql_config??
  • ./bootstrap.sh??
  • make?&&?make?install??
  • ?

    Atlas配置?

    Java代碼??
  • vim?/usr/local/mysql-proxy/conf/chapter6.cnf??
  • Java代碼??
  • [mysql-proxy]??
  • #Atlas代理的主庫,多個(gè)之間逗號(hào)分隔??
  • proxy-backend-addresses?=?127.0.0.1:3306??
  • #Atlas代理的從庫,多個(gè)之間逗號(hào)分隔,格式ip:port@weight,權(quán)重默認(rèn)1??
  • #proxy-read-only-backend-addresses?=?127.0.0.1:3306,127.0.0.1:3306??
  • #用戶名/密碼,密碼使用/usr/servers/Atlas-2.2.1/script/encrypt?123456加密??
  • pwds?=?root:/iZxz+0GRoA=??
  • #后端進(jìn)程運(yùn)行??
  • daemon?=?true??
  • #開啟monitor進(jìn)程,當(dāng)worker進(jìn)程掛了自動(dòng)重啟??
  • keepalive?=?true??
  • #工作線程數(shù),對(duì)Atlas的性能有很大影響,可根據(jù)情況適當(dāng)設(shè)置??
  • event-threads?=?64??
  • #日志級(jí)別??
  • log-level?=?message??
  • #日志存放的路徑??
  • log-path?=?/usr/chapter6/??
  • #實(shí)例名稱,用于同一臺(tái)機(jī)器上多個(gè)Atlas實(shí)例間的區(qū)分??
  • instance?=?test??
  • #監(jiān)聽的ip和port??
  • proxy-address?=?0.0.0.0:1112??
  • #監(jiān)聽的管理接口的ip和port??
  • admin-address?=?0.0.0.0:1113??
  • #管理接口的用戶名??
  • admin-username?=?admin??
  • #管理接口的密碼??
  • admin-password?=?123456??
  • #分表邏輯??
  • tables?=?chapter6.ad.sku_id.2??
  • #默認(rèn)字符集??
  • charset?=?utf8???
  • 因?yàn)楸纠龥]有做讀寫分離,所以讀庫proxy-read-only-backend-addresses沒有配置。分表邏輯即:數(shù)據(jù)庫名.表名.分表鍵.表的個(gè)數(shù),分表的表名格式是table_N,N從0開始。

    ?

    Atlas啟動(dòng)/重啟/停止

    Java代碼??
  • /usr/local/mysql-proxy/bin/mysql-proxyd?chapter6?start??
  • /usr/local/mysql-proxy/bin/mysql-proxyd?chapter6?restart??
  • /usr/local/mysql-proxy/bin/mysql-proxyd?chapter6?stop???
  • 如上命令會(huì)自動(dòng)到/usr/local/mysql-proxy/conf目錄下查找chapter6.cnf配置文件。?

    ?

    Atlas管理

    通過如下命令進(jìn)入管理接口

    Java代碼??
  • mysql?-h127.0.0.1?-P1113??-uadmin?-p123456???
  • 通過執(zhí)行SELECT * FROM help查看幫助。還可以通過一些SQL進(jìn)行服務(wù)器的動(dòng)態(tài)添加/移除。

    ?

    Atlas客戶端

    通過如下命令進(jìn)入客戶端接口

    Java代碼??
  • mysql?-h127.0.0.1?-P1112??-uroot?-p123456??
  • Java代碼??
  • use?chapter6;??
  • insert?into?ad?values(1?'測試1);??????
  • insert?into?ad?values(2,?'測試2');??????
  • insert?into?ad?values(3?'測試3);??????
  • select?*?from?ad?where?sku_id=1;??
  • select?*?from?ad?where?sku_id=2;??
  • #通過如下sql可以看到實(shí)際的分表結(jié)果??
  • select?*?from?ad_0;??
  • select?*?from?ad_1;???
  • ?

    此時(shí)無法執(zhí)行select * from ad,需要使用如“select * from ad where sku_id=1”這種SQL進(jìn)行查詢;即需要帶上sku_id且必須是相等比較;如果是范圍或模糊是不可以的;如果想全部查詢,只能挨著遍歷所有表進(jìn)行查詢。即在客戶端做查詢-聚合。

    ?

    此處實(shí)際的分表邏輯是按照商家進(jìn)行分表,而不是按照商品編號(hào),因?yàn)槲覀兒笈_(tái)查詢時(shí)是按照商家維度的,此處是為了測試才使用商品編號(hào)的。

    ?

    到此基本的Atlas就介紹完了,更多內(nèi)容請(qǐng)參考如下資料:

    Mysql主從復(fù)制

    http://369369.blog.51cto.com/319630/790921/

    Mysql中間件介紹

    http://www.guokr.com/blog/475765/

    Atlas使用

    http://www.0550go.com/database/mysql/mysql-atlas.html

    Atlas文檔

    https://github.com/Qihoo360/Atlas/blob/master/README_ZH.md

    ?

    Java+Tomcat安裝

    Java安裝? ?

    Java代碼??
  • cd?/usr/servers/??
  • #首先到如下網(wǎng)站下載JDK??
  • #http://www.oracle.com/technetwork/cn/java/javase/downloads/jdk7-downloads-1880260.html??
  • #本文下載的是?jdk-7u75-linux-x64.tar.gz。??
  • tar?-xvf?jdk-7u75-linux-x64.tar.gz??
  • vim?~/.bashrc??
  • 在文件最后添加如下環(huán)境變量??
  • export?JAVA_HOME=/usr/servers/jdk1.7.0_75/??
  • export?PATH=$JAVA_HOME/bin:$JAVA_HOME/jre/bin:$PATH??
  • export?CLASSPATH=$CLASSPATH:.:$JAVA_HOME/lib:$JAVA_HOME/jre/lib??
  • ??
  • #使環(huán)境變量生效??
  • source?~/.bashrc??
  • ??

    Tomcat安裝?

    Java代碼??
  • cd?/usr/servers/??
  • wget?http://ftp.cuhk.edu.hk/pub/packages/apache.org/tomcat/tomcat-7/v7.0.59/bin/apache-tomcat-7.0.59.tar.gz??
  • tar?-xvf?apache-tomcat-7.0.59.tar.gz??
  • cd?apache-tomcat-7.0.59/??
  • #啟動(dòng)???
  • /usr/servers/apache-tomcat-7.0.59/bin/startup.sh???
  • #停止??
  • /usr/servers/apache-tomcat-7.0.59/bin/shutdown.sh??
  • #刪除tomcat默認(rèn)的webapp??
  • rm?-r?apache-tomcat-7.0.59/webapps/*??
  • #通過Catalina目錄發(fā)布web應(yīng)用??
  • cd?apache-tomcat-7.0.59/conf/Catalina/localhost/??
  • vim?ROOT.xml??
  • ROOT.xml

    Java代碼??
  • <!--?訪問路徑是根,web應(yīng)用所屬目錄為/usr/chapter6/webapp?-->??
  • <Context?path=""?docBase="/usr/chapter6/webapp"></Context>??
  • Java代碼??
  • #創(chuàng)建一個(gè)靜態(tài)文件隨便添加點(diǎn)內(nèi)容??
  • vim?/usr/chapter6/webapp/index.html??
  • #啟動(dòng)??
  • /usr/servers/apache-tomcat-7.0.59/bin/startup.sh????
  • ?

    訪問如http://192.168.1.2:8080/index.html能處理內(nèi)容說明配置成功。

    ?

    Java代碼??
  • #變更目錄結(jié)構(gòu)??
  • cd?/usr/servers/??
  • mv?apache-tomcat-7.0.59?tomcat-server1??
  • #此處我們創(chuàng)建兩個(gè)tomcat實(shí)例??
  • cp?–r?tomcat-server1?tomcat-server2??
  • vim?tomcat-server2/conf/server.xml??????
  • Java代碼??
  • #如下端口進(jìn)行變更??
  • 8080--->8090??
  • 8005--->8006??
  • ?????

    啟動(dòng)兩個(gè)Tomcat

    Java代碼??
  • /usr/servers/tomcat-server1/bin/startup.sh???
  • /usr/servers/tomcat-server2/bin/startup.sh???
  • 分別訪問,如果能正常訪問說明配置正常。

    ?

    http://192.168.1.2:8080/index.html

    http://192.168.1.2:8090/index.html

    ?

    如上步驟使我們在一個(gè)服務(wù)器上能啟動(dòng)兩個(gè)tomcat實(shí)例,這樣的好處是我們可以做本機(jī)的Tomcat負(fù)載均衡,假設(shè)一個(gè)tomcat重啟時(shí)另一個(gè)是可以工作的,從而不至于不給用戶返回響應(yīng)。

    ?

    Java+Tomcat邏輯開發(fā)

    搭建項(xiàng)目

    我們使用Maven搭建Web項(xiàng)目,Maven知識(shí)請(qǐng)自行學(xué)習(xí)。

    ?

    項(xiàng)目依賴

    本文將最小化依賴,即僅依賴我們需要的servlet、mysql、druid、jedis。?

    Java代碼??
  • <dependencies>??
  • ??<dependency>??
  • ????<groupId>javax.servlet</groupId>??
  • ????<artifactId>javax.servlet-api</artifactId>??
  • ????<version>3.0.1</version>??
  • ????<scope>provided</scope>??
  • ??</dependency>??
  • ??<dependency>??
  • ????<groupId>mysql</groupId>??
  • ????<artifactId>mysql-connector-java</artifactId>??
  • ????<version>5.1.27</version>??
  • ??</dependency>??
  • ??<dependency>??
  • ????<groupId>com.alibaba</groupId>??
  • ????<artifactId>druid</artifactId>??
  • ????<version>1.0.5</version>??
  • ??</dependency>??
  • ??<dependency>??
  • ????<groupId>redis.clients</groupId>??
  • ????<artifactId>jedis</artifactId>??
  • ????<version>2.5.2</version>??
  • ??</dependency>??
  • </dependencies>??
  • ?

    核心代碼

    com.github.zhangkaitao.chapter6.servlet.AdServlet

    Java代碼??
  • public?class?AdServlet?extends?HttpServlet?{??
  • ????@Override??
  • ????protected?void?doGet(HttpServletRequest?req,?HttpServletResponse?resp)?throws?ServletException,?IOException?{??
  • ????????String?idStr?=?req.getParameter("id");??
  • ????????Long?id?=?Long.valueOf(idStr);??
  • ????????//1、讀取Mysql獲取數(shù)據(jù)??
  • ????????String?content?=?null;??
  • ????????try?{??
  • ????????????content?=?queryDB(id);??
  • ????????}?catch?(Exception?e)?{??
  • ????????????e.printStackTrace();??
  • ????????????resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);??
  • ????????????return;??
  • ????????}??
  • ????????if(content?!=?null)?{??
  • ????????????//2.1、如果獲取到,異步寫Redis??
  • ????????????asyncSetToRedis(idStr,?content);??
  • ????????????//2.2、如果獲取到,把響應(yīng)內(nèi)容返回??
  • ????????????resp.setCharacterEncoding("UTF-8");??
  • ????????????resp.getWriter().write(content);??
  • ????????}?else?{??
  • ????????????//2.3、如果獲取不到,返回404狀態(tài)碼??
  • ????????????resp.setStatus(HttpServletResponse.SC_NOT_FOUND);??
  • ????????}??
  • ????}??
  • ??
  • ????private?DruidDataSource?datasource?=?null;??
  • ????private?JedisPool?jedisPool?=?null;??
  • ??
  • ????{??
  • ????????datasource?=?new?DruidDataSource();??
  • ????????datasource.setUrl("jdbc:mysql://127.0.0.1:1112/chapter6?useUnicode=true&characterEncoding=utf-8&autoReconnect=true");??
  • ????????datasource.setUsername("root");??
  • ????????datasource.setPassword("123456");??
  • ????????datasource.setMaxActive(100);??
  • ??
  • ????????GenericObjectPoolConfig?poolConfig?=?new?GenericObjectPoolConfig();??
  • ????????poolConfig.setMaxTotal(100);??
  • ????????jedisPool?=?new?JedisPool(poolConfig,?"127.0.0.1",?1111);??
  • ????}??
  • ??
  • ????private?String?queryDB(Long?id)?throws?Exception?{??
  • ????????Connection?conn?=?null;??
  • ????????try?{??
  • ????????????conn?=?datasource.getConnection();??
  • ????????????String?sql?=?"select?content?from?ad?where?sku_id?=??";??
  • ????????????PreparedStatement?psst?=?conn.prepareStatement(sql);??
  • ????????????psst.setLong(1,?id);??
  • ????????????ResultSet?rs?=?psst.executeQuery();??
  • ????????????String?content?=?null;??
  • ????????????if(rs.next())?{??
  • ????????????????content?=?rs.getString("content");??
  • ????????????}??
  • ????????????rs.close();??
  • ????????????psst.close();??
  • ????????????return?content;??
  • ????????}?catch?(Exception?e)?{??
  • ????????????throw?e;??
  • ????????}?finally?{??
  • ????????????if(conn?!=?null)?{??
  • ????????????????conn.close();??
  • ????????????}??
  • ????????}??
  • ????}??
  • ??
  • ????private?ExecutorService?executorService?=?Executors.newFixedThreadPool(10);??
  • ????private?void?asyncSetToRedis(final?String?id,?final?String?content)?{??
  • ????????executorService.submit(new?Runnable()?{??
  • ????????????@Override??
  • ????????????public?void?run()?{??
  • ????????????????Jedis?jedis?=?null;??
  • ????????????????try?{??
  • ????????????????????jedis?=?jedisPool.getResource();??
  • ????????????????????jedis.setex(id,?5?*?60,?content);//5分鐘??
  • ????????????????}?catch?(Exception?e)?{??
  • ????????????????????e.printStackTrace();??
  • ????????????????????jedisPool.returnBrokenResource(jedis);??
  • ????????????????}?finally?{??
  • ????????????????????jedisPool.returnResource(jedis);??
  • ????????????????}??
  • ??
  • ????????????}??
  • ????????});??
  • ????}??
  • }??
  • 整個(gè)邏輯比較簡單,此處更新緩存一般使用異步方式去更新,這樣不會(huì)阻塞主線程;另外此處可以考慮走Servlet異步化來提示吞吐量。 web.xml配置 Java代碼??
  • <servlet>??
  • ????<servlet-name>adServlet</servlet-name>??
  • ????<servlet-class>com.github.zhangkaitao.chapter6.servlet.AdServlet</servlet-class>??
  • </servlet>??
  • <servlet-mapping>??
  • ????<servlet-name>adServlet</servlet-name>??
  • ????<url-pattern>/ad</url-pattern>??
  • </servlet-mapping>??
  • ?

    打WAR包?

    Java代碼??
  • cd?D:\workspace\chapter6??
  • mvn?clean?package??
  • 此處使用maven命令打包,比如本例將得到chapter6.war,然后將其上傳到服務(wù)器的/usr/chapter6/webapp,然后通過unzip chapter6.war解壓。

    ?

    測試?

    啟動(dòng)Tomcat實(shí)例,分別訪問如下地址將看到廣告內(nèi)容:

    Java代碼??
  • http://192.168.1.2:8080/ad?id=1??
  • http://192.168.1.2:8090/ad?id=1??
  • ?

    nginx配置

    vim /usr/chapter6/nginx_chapter6.conf?

    Java代碼??
  • upstream?backend?{??
  • ????server?127.0.0.1:8080?max_fails=5?fail_timeout=10s?weight=1?backup=false;??
  • ????server?127.0.0.1:8090?max_fails=5?fail_timeout=10s?weight=1?backup=false;??
  • ????check?interval=3000?rise=1?fall=2?timeout=5000?type=tcp?default_down=false;??
  • ????keepalive?100;??
  • }??
  • server?{??
  • ????listen???????80;??
  • ????server_name??_;??
  • ??
  • ????location?~?/backend/(.*)?{??
  • ????????keepalive_timeout???30s;??
  • ????????keepalive_requests??100;??
  • ??
  • ????????rewrite?/backend(/.*)?$1?break;??
  • ????????#之后該服務(wù)將只有內(nèi)部使用,ngx.location.capture??
  • ????????proxy_pass_request_headers?off;??
  • ????????#more_clear_input_headers?Accept-Encoding;??
  • ????????proxy_next_upstream?error?timeout;??
  • ????????proxy_pass?http://backend;??
  • ????}??
  • }??
  • upstream配置:http://nginx.org/cn/docs/http/ngx_http_upstream_module.html。

    ? server:指定上游到的服務(wù)器, weight:權(quán)重,權(quán)重可以認(rèn)為負(fù)載均衡的比例; ?fail_timeout+max_fails:在指定時(shí)間內(nèi)失敗多少次認(rèn)為服務(wù)器不可用,通過proxy_next_upstream來判斷是否失敗。

    ? check:ngx_http_upstream_check_module模塊,上游服務(wù)器的健康檢查,interval:發(fā)送心跳包的時(shí)間間隔,rise:連續(xù)成功rise次數(shù)則認(rèn)為服務(wù)器up,fall:連續(xù)失敗fall次則認(rèn)為服務(wù)器down,timeout:上游服務(wù)器請(qǐng)求超時(shí)時(shí)間,type:心跳檢測類型(比如此處使用tcp)更多配置請(qǐng)參考https://github.com/yaoweibin/nginx_upstream_check_module和http://tengine.taobao.org/document_cn/http_upstream_check_cn.html。

    ? keepalive:用來支持upstream server http keepalive特性(需要上游服務(wù)器支持,比如tomcat)。默認(rèn)的負(fù)載均衡算法是round-robin,還可以根據(jù)ip、url等做hash來做負(fù)載均衡。更多資料請(qǐng)參考官方文檔。

    ?

    tomcat keepalive配置:?http://tomcat.apache.org/tomcat-7.0-doc/config/http.html。

    ??maxKeepAliveRequests:默認(rèn)100;

    ??keepAliveTimeout:默認(rèn)等于connectionTimeout,默認(rèn)60秒;

    ?

    location proxy配置:http://nginx.org/cn/docs/http/ngx_http_proxy_module.html。

    ? rewrite:將當(dāng)前請(qǐng)求的url重寫,如我們請(qǐng)求時(shí)是/backend/ad,則重寫后是/ad。

    ? proxy_pass:將整個(gè)請(qǐng)求轉(zhuǎn)發(fā)到上游服務(wù)器。

    ??proxy_next_upstream:什么情況認(rèn)為當(dāng)前upstream server失敗,需要next upstream,默認(rèn)是連接失敗/超時(shí),負(fù)載均衡參數(shù)。

    ??proxy_pass_request_headers:之前已經(jīng)介紹過了,兩個(gè)原因:1、假設(shè)上游服務(wù)器不需要請(qǐng)求頭則沒必要傳輸請(qǐng)求頭;2、ngx.location.capture時(shí)防止gzip亂碼(也可以使用more_clear_input_headers配置)。

    ? keepalive:keepalive_timeout:keepalive超時(shí)設(shè)置,keepalive_requests:長連接數(shù)量。此處的keepalive(別人訪問該location時(shí)的長連接)和upstream keepalive(nginx與上游服務(wù)器的長連接)是不一樣的;此處注意,如果您的服務(wù)是面向客戶的,而且是單個(gè)動(dòng)態(tài)內(nèi)容就沒必要使用長連接了。

    ?

    vim /usr/servers/nginx/conf/nginx.conf

    Java代碼??
  • include?/usr/chapter6/nginx_chapter6.conf;??
  • #為了方便測試,注釋掉example.conf??
  • #include?/usr/example/example.conf;??
  • ?

    重啟nginx?

    /usr/servers/nginx/sbin/nginx -s reload ? ? ?

    ?

    訪問如192.168.1.2/backend/ad?id=1即看到結(jié)果。可以kill掉一個(gè)tomcat,可以看到服務(wù)還是正常的。

    ?

    vim /usr/chapter6/nginx_chapter6.conf?

    Java代碼??
  • location?~?/backend/(.*)?{??
  • ????internal;??
  • ????keepalive_timeout???30s;??
  • ????keepalive_requests??1000;??
  • ????#支持keep-alive??
  • ????proxy_http_version?1.1;??
  • ????proxy_set_header?Connection?"";??
  • ??
  • ????rewrite?/backend(/.*)?$1?break;??
  • ????proxy_pass_request_headers?off;??
  • ????#more_clear_input_headers?Accept-Encoding;??
  • ????proxy_next_upstream?error?timeout;??
  • ????proxy_pass?http://backend;??
  • }??
  • 加上internal,表示只有內(nèi)部使用該服務(wù)。?

    ?

    Nginx+Lua邏輯開發(fā)

    核心代碼?

    /usr/chapter6/ad.lua?

    Java代碼??
  • local?redis?=?require("resty.redis")??
  • local?cjson?=?require("cjson")??
  • local?cjson_encode?=?cjson.encode??
  • local?ngx_log?=?ngx.log??
  • local?ngx_ERR?=?ngx.ERR??
  • local?ngx_exit?=?ngx.exit??
  • local?ngx_print?=?ngx.print??
  • local?ngx_re_match?=?ngx.re.match??
  • local?ngx_var?=?ngx.var??
  • ??
  • local?function?close_redis(red)??
  • ????if?not?red?then??
  • ????????return??
  • ????end??
  • ????--釋放連接(連接池實(shí)現(xiàn))??
  • ????local?pool_max_idle_time?=?10000?--毫秒??
  • ????local?pool_size?=?100?--連接池大小??
  • ????local?ok,?err?=?red:set_keepalive(pool_max_idle_time,?pool_size)??
  • ??
  • ????if?not?ok?then??
  • ????????ngx_log(ngx_ERR,?"set?redis?keepalive?error?:?",?err)??
  • ????end??
  • end??
  • local?function?read_redis(id)??
  • ????local?red?=?redis:new()??
  • ????red:set_timeout(1000)??
  • ????local?ip?=?"127.0.0.1"??
  • ????local?port?=?1111??
  • ????local?ok,?err?=?red:connect(ip,?port)??
  • ????if?not?ok?then??
  • ????????ngx_log(ngx_ERR,?"connect?to?redis?error?:?",?err)??
  • ????????return?close_redis(red)??
  • ????end??
  • ??
  • ????local?resp,?err?=?red:get(id)??
  • ????if?not?resp?then??
  • ????????ngx_log(ngx_ERR,?"get?redis?content?error?:?",?err)??
  • ????????return?close_redis(red)??
  • ????end??
  • ????????--得到的數(shù)據(jù)為空處理??
  • ????if?resp?==?ngx.null?then??
  • ????????resp?=?nil??
  • ????end??
  • ????close_redis(red)??
  • ??
  • ????return?resp??
  • end??
  • ??
  • local?function?read_http(id)??
  • ????local?resp?=?ngx.location.capture("/backend/ad",?{??
  • ????????method?=?ngx.HTTP_GET,??
  • ????????args?=?{id?=?id}??
  • ????})??
  • ??
  • ????if?not?resp?then??
  • ????????ngx_log(ngx_ERR,?"request?error?:",?err)??
  • ????????return??
  • ????end??
  • ??
  • ????if?resp.status?~=?200?then??
  • ????????ngx_log(ngx_ERR,?"request?error,?status?:",?resp.status)??
  • ????????return??
  • ????end??
  • ??
  • ????return?resp.body??
  • end??
  • ??
  • ??
  • --獲取id??
  • local?id?=?ngx_var.id??
  • ??
  • --從redis獲取??
  • local?content?=?read_redis(id)??
  • ??
  • --如果redis沒有,回源到tomcat??
  • if?not?content?then??
  • ???ngx_log(ngx_ERR,?"redis?not?found?content,?back?to?http,?id?:?",?id)??
  • ????content?=?read_http(id)??
  • end??
  • ??
  • --如果還沒有返回404??
  • if?not?content?then??
  • ???ngx_log(ngx_ERR,?"http?not?found?content,?id?:?",?id)??
  • ???return?ngx_exit(404)??
  • end??
  • ??
  • --輸出內(nèi)容??
  • ngx.print("show_ad(")??
  • ngx_print(cjson_encode({content?=?content}))??
  • ngx.print(")")??
  • 將可能經(jīng)常用的變量做成局部變量,如local ngx_print = ngx.print;使用jsonp方式輸出,此處我們可以將請(qǐng)求url限定為/ad/id方式,這樣的好處是1、可以盡可能早的識(shí)別無效請(qǐng)求;2、可以走nginx緩存/CDN緩存,緩存的key就是URL,而不帶任何參數(shù),防止那些通過加隨機(jī)數(shù)穿透緩存;3、jsonp使用固定的回調(diào)函數(shù)show_ad(),或者限定幾個(gè)固定的回調(diào)來減少緩存的版本。

    ?

    vim /usr/chapter6/nginx_chapter6.conf?

    Java代碼??
  • location?~?^/ad/(\d+)$?{??
  • ????default_type?'text/html';??
  • ????charset?utf-8;??
  • ????lua_code_cache?on;??
  • ????set?$id?$1;??
  • ????content_by_lua_file?/usr/chapter6/ad.lua;??
  • }??
  • ?

    重啟nginx

    Java代碼??
  • /usr/servers/nginx/sbin/nginx?-s?reload??
  • ?

    訪問如http://192.168.1.2/ad/1即可得到結(jié)果。而且注意觀察日志,第一次訪問時(shí)不命中Redis,回源到Tomcat;第二次請(qǐng)求時(shí)就會(huì)命中Redis了。

    ?

    第一次訪問時(shí)將看到/usr/servers/nginx/logs/error.log輸出類似如下的內(nèi)容,而第二次請(qǐng)求相同的url不再有如下內(nèi)容:

    Java代碼??
  • redis?not?found?content,?back?to?http,?id?:?2??
  • ?

    ?

    到此整個(gè)架構(gòu)就介紹完了,此處可以直接不使用Tomcat,而是Lua直連Mysql做回源處理;另外本文只是介紹了大體架構(gòu),還有更多業(yè)務(wù)及運(yùn)維上的細(xì)節(jié)需要在實(shí)際應(yīng)用中根據(jù)自己的場景自己摸索。后續(xù)如使用LVS/HAProxy做負(fù)載均衡、使用CDN等可以查找資料學(xué)習(xí)。

    ?

    • chapter6.zip?(2.8 MB)
    • 下載次數(shù): 294

    來源:http://jinnianshilongnian.iteye.com/blog/2188113

    總結(jié)

    以上是生活随笔為你收集整理的第六章 Web开发实战1——HTTP服务的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。