歡迎訪問我的個人網站O(∩_∩)O哈哈~希望大佬們能給個star,個人網站網址:http://www.wenzhihuai.com,個人網站代碼地址:https://github.com/Zephery/newblog。
洋洋灑灑的買了兩個服務器,用來學習分布式、集群之類的東西,整來整去,感覺分布式這種東西沒人指導一下真的是太抽象了,先從網站的分布式部署一步一步學起來吧,雖然網站本身的訪問量不大==。
nginx負載均衡
一般情況下,當單實例無法支撐起用戶的請求時,就需要就行擴容,部署的服務器可以分機房、分地域。而分地域會導致請求分配到太遠的地區,比如:深圳的用戶卻訪問到了北京的節點,然后還得從北京返回處理之后的數據,光是來回就至少得30ms。這部分可以通過智能DNS(就近訪問)解決。而分機房,需要將請求合理的分配到不同的服務器,這部分就是我們所需要處理的。
通常,負載均衡分為硬件和軟件兩種,硬件層的比較牛逼,將4-7層負載均衡功能做到一個硬件里面,如F5,梭子魚等。目前主流的軟件負載均衡分為四層和七層,LVS屬於四層負載均衡,工作在tcp/ip協議棧上,通過修改網絡包的ip地址和端口來轉發, 由於效率比七層高,一般放在架構的前端。七層的負載均衡有nginx, haproxy, apache等,雖然nginx自1.9.0版本后也開始支持四層的負載均衡,但是暫不討論(我木有硬件條件)。下圖來自張開濤的《億級流量網站架構核心技術》
本站並沒有那么多的服務器,目前只有兩台,搭建不了那么大型的架構,就簡陋的用兩台服務器來模擬一下負載均衡的搭建。下圖是本站的簡單架構:
其中服務器A(119.23.46.71)為深圳節點,服務器B(47.95.10.139)為北京節點,搭建Nginx之后流量是這么走的:user->A->B-A->user或者user->A->user,第一條中A將請求轉發給B,然后B返回的是其運行結果的靜態資源。因為這里僅僅是用來學習,所以請不要考慮因為地域導致延時的問題。。。。下面是過程。
1.1 Nginx的安裝
可以選擇tar.gz、yum、rpm安裝等,這里,由於編譯、nginx配置比較復雜,要是沒有把握還是使用rpm來安裝吧,比較簡單。從https://pkgs.org/download/nginx可以找到最新的rpm包,然后rpm -ivh 文件,然后在命令行中輸入nginx即可啟動,可以使用netstat檢查一下端口。
啟動后頁面如下:
記一下常用命令
啟動nginx,由於是采用rpm方式,所以環境變量什么的都配置好了。
[root@beijingali ~]# nginx #啟動nginx
[root@beijingali ~]# nginx -s reload #重啟nginx
[root@beijingali ~]# nginx -t #校驗nginx配置文件
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
1.2 Nginx的配置
1.2.1 負載均衡算法
Nginx常用的算法有:
(1)round-robin:輪詢,nginx默認的算法,從詞語上可以看出,輪流訪問服務器,也可以通過weight來控制訪問次數。
(2)ip_hash:根據訪客的ip,一個ip地址對應一個服務器。
(3)hash算法:hash算法常用的方式有根據uri、動態指定的consistent_key兩種。
使用hash算法的缺點是當添加服務器的時候,只有少部分的uri能夠被重新分配到新的服務器。這里,本站使用的是hash uri的算法,將不同的uri分配到不同的服務器,但是由於是不同的服務器,tomcat中的session是不一致,解決辦法是tomcat session的共享。額。。。可惜本站目前沒有什么能夠涉及到登陸什么session的問題。
http{
...
upstream backend {
hash $uri;
# 北京節點
server 47.95.10.139:8080;
# 深圳節點
server 119.23.46.71:8080;
}
server {
...
location / {
root html;
index index.html index.htm;
proxy_pass http://backend;
...
}
...
1.2.2 日志格式
之前有使用過ELK來跟蹤日志,所以將日志格式化成了json的格式,這里貼一下吧
...
log_format main '{"@timestamp":"$time_iso8601",'
'"host":"$server_addr",'
'"clientip":"$remote_addr",'
'"size":$body_bytes_sent,'
'"responsetime":$request_time,'
'"upstreamtime":"$upstream_response_time",'
'"upstreamhost":"$upstream_addr",'
'"http_host":"$host",'
'"url":"$uri",'
'"xff":"$http_x_forwarded_for",'
'"referer":"$http_referer",'
'"agent":"$http_user_agent",'
'"status":"$status"}';
access_log logs/access.log main;
...
1.2.3 HTTP反向代理
配置完上流服務器之后,需要配置Http的代理,將請求的端口轉發到proxy_pass設定的上流服務器,即當我們訪問http://wwww.wenzhihuai.com的時候,請求會被轉發到backend中配置的服務器,此處為http://47.95.10.139:8080或者http://119.23.46.71:8080。但是,仔細注意之后,我們會發現,tomcat中的訪問日志ip來源都是127.0.0.1,相當於本地訪問自己的資源。由於后台中有處理ip的代碼,對客戶端的ip、訪問uri等記錄下來,所以需要設置nginx來獲取用戶的實際ip,參考nginx 配置。參考文中的一句話:經過反向代理后,由於在客戶端和web服務器之間增加了中間層,因此web服務器無法直接拿到客戶端的ip,通過$remote_addr變量拿到的將是反向代理服務器的ip地址”。nginx是可以獲得用戶的真實ip的,也就是說nginx使用$remote_addr變量時獲得的是用戶的真實ip,如果我們想要在web端獲得用戶的真實ip,就必須在nginx這里作一個賦值操作,如下:
location / {
root html;
index index.html index.htm;
proxy_pass http://backend;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_set_header REMOTE-HOST $remote_addr;
}
(1)proxy_set_header X-real-ip $remote_addr;
其中這個X-real-ip是一個自定義的變量名,名字可以隨意取,這樣做完之后,用戶的真實ip就被放在X-real-ip這個變量里了,然后,在web端可以這樣獲取:
request.getAttribute("X-real-ip")
(2)proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
X-Forwarded-For:squid開發的,用於識別通過HTTP代理或負載平衡器原始IP一個連接到Web服務器的客戶機地址的非rfc標准,這個不是默認有的,其經過代理轉發之后,格式為client1, proxy1, proxy2,如果想通過這個變量來獲取用戶的ip,那么需要和$proxy_add_x_forwarded_for一起使用。
$proxy_add_x_forwarded_for:現在的$proxy_add_x_forwarded_for變量,X-Forwarded-For部分包含的是用戶的真實ip,$remote_addr部分的值是上一台nginx的ip地址,於是通過這個賦值以后現在的X-Forwarded-For的值就變成了“用戶的真實ip,第一台nginx的ip”。
1.2.4 HTTPS
HTTPS(全稱:Hyper Text Transfer Protocol over Secure Socket Layer),是以安全為目標的HTTP通道,簡單講是HTTP的安全版。即HTTP下加入SSL層,HTTPS的安全基礎是SSL,因此加密的詳細內容就需要SSL。一般情況下,能通過服務器的ssh來生成ssl證書,但是如果使用是自己的,一般瀏覽器(谷歌、360等)都會報證書不安全的錯誤,正常用戶都不敢訪問吧==,所以現在使用的是騰訊跟別的機構頒發的:
首先需要下載證書,放在nginx.conf相同目錄下,nginx上的配置也需要有所改變,在nginx.conf中設置listen 443 ssl;開啟https。然后配置證書和私鑰:
ssl_certificate 1_www.wenzhihuai.com_bundle.crt; #主要文件路徑
ssl_certificate_key 2_www.wenzhihuai.com.key;
ssl_session_timeout 5m; # 超時時間
ssl_protocols TLSv1 TLSv1.1 TLSv1.2; #按照這個協議配置
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;#按照這個套件配置
ssl_prefer_server_ciphers on;
至此,可以使用https來訪問了。https帶來的安全性(保證信息安全、識別釣魚網站等)是http遠遠不能比擬的,目前大部分網站都是實現全站https,還能將http自動重定向為https,此處,需要在server中添加rewrite ^(.*) https://$server_name$1 permanent;即可
1.2.5 失敗重試
配置好了負載均衡之后,如果有一台服務器掛了怎么辦?nginx中提供了可配置的服務器存活的識別,主要是通過max_fails失敗請求次數,fail_timeout超時時間,weight為權重,下面的配置的意思是當服務器超時10秒,並失敗了兩次的時候,nginx將認為上游服務器不可用,將會摘掉上游服務器,fail_timeout時間后會再次將該服務器加入到存活上游服務器列表進行重試
upstream backend_server {
server 10.23.46.71:8080 max_fails=2 fail_timeout=10s weight=1;
server 47.95.10.139:8080 max_fails=2 fail_timeout=10s weight=1;
}
session共享
分布式情況下難免會要解決session共享的問題,目前推薦的方法基本上都是使用redis,網上查找的方法目前流行的有下面四種,參考自tomcat 集群中 session 共:
1.使用 filter 方法存儲。(推薦,因為它的服務器使用范圍比較多,不僅限於tomcat ,而且實現的原理比較簡單容易控制。)
2.使用 tomcat sessionmanager 方法存儲。(直接配置即可)
3.使用 terracotta 服務器共享。(不知道,不了解)
4.使用spring-session。(spring的一個小項目,其原理也和第一種基本一致)
本站使用spring-session,畢竟是spring下的子項目,學習下還是挺好的。參考Spring-Session官網。官方文檔提供了spring-boot、spring等例子,可以參考參考。目前最新版本是2.0.0,不同版本使用方式不同,建議看官網的文檔吧。
首先,添加相關依賴
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
<version>1.3.1.RELEASE</version>
<type>pom</type>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>${jedis.version}</version>
</dependency>
新建一個session.xml,然后在spring的配置文件中添加該文件,然后在session.xml中添加:
<!-- redis -->
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
</bean>
<bean id="jedisConnectionFactory"
class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<property name="hostName" value="${host}" />
<property name="port" value="${port}" />
<property name="password" value="${password}" />
<property name="timeout" value="${timeout}" />
<property name="poolConfig" ref="jedisPoolConfig" />
<property name="usePool" value="true" />
</bean>
<bean id="redisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate">
<property name="connectionFactory" ref="jedisConnectionFactory" />
</bean>
<!-- 將session放入redis -->
<bean id="redisHttpSessionConfiguration"
class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration">
<property name="maxInactiveIntervalInSeconds" value="1800" />
</bean>
然后我們需要保證servlet容器(tomcat)針對每一個請求都使用springSessionRepositoryFilter來攔截
<filter>
<filter-name>springSessionRepositoryFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSessionRepositoryFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>ERROR</dispatcher>
</filter-mapping>
配置完成,使用RedisDesktopManager查看結果:
測試:
訪問http://www.wenzhihuai.com
tail -f localhost_access_log.2017-11-05.txt查看日志,然后清空一下當前記錄
訪問技術雜談頁面,此時nginx將請求轉發到119.23.46.71服務器,session為28424f91-5bc5-4bba-99ec-f725401d7318。
點擊生活筆記頁面,轉發到的服務器為47.95.10.139,session為28424f91-5bc5-4bba-99ec-f725401d7318,與上面相同。session已保持一致。
值得注意的是:同一個瀏覽器,在沒有關閉的情況下,即使通過域名訪問和ip訪問得到的session是不同的。
歡迎訪問我的個人網站O(∩_∩)O哈哈~希望能給個star
個人網站網址:http://www.wenzhihuai.com
個人網站代碼地址:https://github.com/Zephery/newblog