天天看點

Nginx的程序模型及高可用方案(OpenResty)

 1. Nginx 程序模型簡介

  Nginx預設采用多程序工作方式,Nginx啟動後,會運作一個master程序和多個worker程序。其中master充當整個程序組與使用者的互動接口,同時對程序進行監護,管理worker程序來實作重新開機服務、平滑更新、更換日志檔案、配置檔案實時生效等功能。worker用來處理基本的網絡事件,worker之間是平等的,他們共同競争來處理來自用戶端的請求。生産環境一般采用的是多程序+多路複用的形式。這裡可以從伺服器上可以很直覺的看到:

Nginx的程式模型及高可用方案(OpenResty)

  程序模型:

Nginx的程式模型及高可用方案(OpenResty)

  

  1. 在建立master程序時,先建立需要監聽的socket(listenfd),然後從master程序中fork()出多個worker程序,如此一來每個worker程序都可以監聽使用者請求的socket。一般來說,當一個連接配接進來後,所有Worker都會收到通知,但是隻有一個程序可以接受這個連接配接請求,其它的都失敗,這是所謂的驚群現象。nginx提供了一個accept_mutex(互斥鎖),有了這把鎖之後,同一時刻,就隻會有一個程序在accpet連接配接,這樣就不會有驚群問題了。

  2. 先打開accept_mutex選項,隻有獲得了accept_mutex的程序才會去添加accept事件。nginx使用一個叫ngx_accept_disabled的變量來控制是否去競争accept_mutex鎖。ngx_accept_disabled = nginx單程序的所有連接配接總數 / 8 -空閑連接配接數量,當ngx_accept_disabled大于0時,不會去嘗試擷取accept_mutex鎖,ngx_accept_disable越大,讓出的機會就越多,這樣其它程序擷取鎖的機會也就越大。不去accept,每個worker程序的連接配接數就控制下來了,其它程序的連接配接池就會得到利用,這樣,nginx就控制了多程序間連接配接的平衡。

  3.每個worker程序都有一個獨立的連接配接池,連接配接池的大小是worker_connections。這裡的連接配接池裡面儲存的其實不是真實的連接配接,它隻是一個worker_connections大小的一個ngx_connection_t結構的數組。并且,nginx會通過一個連結清單free_connections來儲存所有的空閑ngx_connection_t,每次擷取一個連接配接時,就從空閑連接配接連結清單中擷取一個,用完後,再放回空閑連接配接連結清單裡面。一個nginx能建立的最大連接配接數,應該是worker_connections * worker_processes。當然,這裡說的是最大連接配接數,對于HTTP請求本地資源來說,能夠支援的最大并發數量是worker_connections * worker_processes,而如果是HTTP作為反向代理來說,最大并發數量應該是worker_connections * worker_processes/2。因為作為反向代理伺服器,每個并發會建立與用戶端的連接配接和與後端服務的連接配接,會占用兩個連接配接。

  相關的配置:

worker_processes  1; // 工作程序數,建議設成CPU總核心數。
events { // 多路複用IO模型機制,epoll . select  ....根據作業系統不同來選擇。linux 預設epoll
    use epoll; //io 模型
    worker_connections  1024; // 每個woker程序的最大連接配接數,數值越大,并發量允許越大
}
http{
  sendfile  on;//開啟零拷貝
}
           

2. Nginx 的高可用方案

  Nginx 作為反向代理伺服器,所有的流量都會經過 Nginx,是以 Nginx 本身的可靠性是我們首先要考慮的問題。

keepalived:

   Keepalived 是 Linux 下一個輕量級别的高可用解決方案,Keepalived 軟體起初是專為 LVS 負載均衡軟體設計的,LVS 是 Linux Virtual Server 的縮寫,也就是 Linux 虛拟伺服器,在 linux2.4 核心以後,已經完全内置了 LVS 的各個功能子產品。它是工作在四層的負載均衡,類似于 Haproxy, 主要用于實作對伺服器叢集的負載均衡。用來管理并監控 LVS 叢集系統中各個服務節點的狀态,後來又加入了可以實作高可用的 VRRP 功能。是以,Keepalived 除了能夠管理 LVS 軟體外,還可以作為其他服務(例如:Nginx、Haproxy、MySQL 等)的高可用解決方案軟體。

  Keepalived 軟體主要是通過 VRRP 協定實作高可用功能的。VRRP 是 Virtual Router RedundancyProtocol(虛拟路由器備援協定)的縮寫,VRRP 出現的目的就是為了解決靜态路由單點故障問題的,它能夠保證當個别節點當機時,整個網絡可以不間斷地運作;(簡單來說,vrrp 就是把兩台或多态路由器裝置虛拟成一個裝置,實作主備高可用)。是以,Keepalived 一方面具有配置管理 LVS 的功能,同時還具有對 LVS 下面節點進行健康檢查的功能,另一方面也可實作系統網絡服務的高可用功能

  關于四層負載,我們知道 osi 網絡層次模型的 7 層模模型(應用層、表示層、會話層、傳輸層、網絡層、資料鍊路層、實體層);四層負載就是基于傳輸層,也就是ip+端口的負載;而七層負載就是需要基于 URL 等應用層的資訊來做負載,同時還有二層負載(基于 MAC)、三層負載(IP);常見的四層負載有:LVS、F5; 七層負載有:Nginx、HAproxy; 在軟體層面,Nginx/LVS/HAProxy 是使用得比較廣泛的三種負載均衡軟體。

  keepalived 安裝:

1.tar -zxvf keepalived-2.0.10.tar.gz

2. ./configure --prefix=/data/program/keepalived --sysconf=/etc 我這邊使用預設安裝路徑,執行./configure --sysconf=/etc  會安裝到 /usr/local 下

3.如果缺少依賴庫 安裝 yum -y install pcre-devel zlib-devel openssl openssl-devel,yum install gcc gcc-c++

4.make && make install

5.進入安裝後的路徑 cd /usr/local, 建立軟連接配接: ln -s sbin/keepalived /sbin

6.複制運作指令  cp /mysoft/keepalived-2.0.10/keepalived/etc/init.d/keepalived /etc/init.d/

7.添加到系統服務 chkconfig --add keepalived

8.啟用該服務 systemctl enable keepalived.service

Nginx的程式模型及高可用方案(OpenResty)

 9.啟動  service keepalived start 并檢視狀态,安裝成功。在配置完conf檔案後啟動會有如下資訊

Nginx的程式模型及高可用方案(OpenResty)

keepalived 的配置 vim /etc/keepalived/keepalived.conf,由于keepalive隻作為vrrp協定的實作,負載代理的工作是交給後面的nginx或者haproxy來實作的。是以部署到一個節點上即可。

MASTER:(192.168.254.139)與nginx在同一個機器上

! Configuration File for keepalived
global_defs {
 router_id MASTER_DEVEL #運作 keepalived 伺服器的辨別,在一個網絡内應該是唯一的
}
vrrp_instance VI_1 { #vrrp 執行個體定義部分
 state MASTER #設定 lvs 的狀态,MASTER 和 BACKUP 兩種,必須大寫
 interface  ens33 #設定對外服務的接口
 virtual_router_id 51 #設定虛拟路由标示,這個标示是一個數字,同一個 vrrp 執行個體使用唯一标示
 priority 150 #定義優先級,數字越大優先級越高,在一個 vrrp——instance 下,master 的優先級必須大于 backup
 advert_int 1 #設定 master 與 backup 負載均衡器之間同步檢查的時間間隔,機關是秒
 authentication { #設定驗證類型和密碼
 auth_type PASS
 auth_pass 1111 #驗證密碼,同一個 vrrp_instance 下 MASTER 和 BACKUP 密碼必須相同}
 virtual_ipaddress { #設定虛拟 ip 位址,可以設定多個,每行一個
   192.168.254.111
 }
}
virtual_server 192.168.254.111 80 { #設定虛拟伺服器,需要指定虛拟 ip 和服務端口
 delay_loop 6 #健康檢查時間間隔
 lb_algo rr #負載均衡排程算法
 lb_kind NAT #負載均衡轉發規則
 persistence_timeout 50 #設定會話保持時間
 protocol TCP #指定轉發協定類型,有 TCP 和 UDP 兩種
 real_server 192.168.254.139 80 { #配置伺服器節點 1,需要指定 real server 的真實 IP 位址和端口weight 1 #設定權重,數字越大權重越高
 TCP_CHECK { #realserver 的狀态監測設定部分機關秒
   connect_timeout 3 #逾時時間
   delay_before_retry 3 #重試間隔
   connect_port 80 #監測端口
 }
 }
}      

BACKUP:(192.168.254.137)與nginx在同一個機器上

! Configuration File for keepalived
global_defs {
 router_id BACKUP_DEVEL #運作 keepalived 伺服器的辨別,在一個網絡内應該是唯一的
}
vrrp_instance VI_1 { #vrrp 執行個體定義部分
 state BACKUP #設定 lvs 的狀态,MASTER 和 BACKUP 兩種,必須大寫
 interface  ens33 #設定對外服務的接口
 virtual_router_id 51 #設定虛拟路由标示,這個标示是一個數字,同一個 vrrp 執行個體使用唯一标示
 priority 100 #定義優先級,數字越大優先級越高,在一個 vrrp——instance 下,master 的優先級必須大于 backup
 advert_int 1 #設定 master 與 backup 負載均衡器之間同步檢查的時間間隔,機關是秒
 authentication { #設定驗證類型和密碼
 auth_type PASS
 auth_pass 1111 #驗證密碼,同一個 vrrp_instance 下 MASTER 和 BACKUP 密碼必須相同}
 virtual_ipaddress { #設定虛拟 ip 位址,可以設定多個,每行一個
   192.168.254.111
 }
}
virtual_server 192.168.254.111 80 { #設定虛拟伺服器,需要指定虛拟 ip 和服務端口
 delay_loop 6 #健康檢查時間間隔
 lb_algo rr #負載均衡排程算法
 lb_kind NAT #負載均衡轉發規則
 persistence_timeout 50 #設定會話保持時間
 protocol TCP #指定轉發協定類型,有 TCP 和 UDP 兩種
 real_server 192.168.254.137 80 { #配置伺服器節點 1,需要指定 real server 的真實 IP 位址和端口weight 1 #設定權重,數字越大權重越高
 TCP_CHECK { #realserver 的狀态監測設定部分機關秒
   connect_timeout 3 #逾時時間
   delay_before_retry 3 #重試間隔
   connect_port 80 #監測端口
 }
 }
}      

 通過腳本實作動态切換(與nginx心跳的綁定):

  通過腳本的方式來檢測 nginx 服務是否存活,一旦nginx挂了,那麼可以通過這個機制把 keepalived 關閉,把機會讓給哪些還存活的節點。詳細配置如下:

首先編寫shell 腳本:我這邊是在keepalived.conf 所在目錄下執行:vim vim nginx_service_check.sh,輸入以下資訊并儲存。

#!bin/sh #! /bin/sh 是指此腳本使用/bin/sh 來執行
A=`ps -C nginx --no-header |wc -l`
if [ $A -eq 0 ]
 then
 echo 'nginx server is died'
 service keepalived stop
fi      

  可以通過sh nginx_service_check.sh 來驗證該腳本是否正确。在nginx 沒有啟動的時候要是能輸出如下資訊就說明是OK的:

Nginx的程式模型及高可用方案(OpenResty)

  為給檔案添權重限 : chmod +x nginx_service_check.sh  。

  然後修改 keepalived.conf 檔案:加入以下資訊

global_defs {
   router_id MASTER_DEVEL
   enable_script_security
}

vrrp_script check_nginx_service {
    script "/etc/keepalived/nginx_service_check.sh"
    interval 3
    weight -10
    user root

}
#還有在vrrp_instance VI_1 這個子產品中添加如下資訊
vrrp_instance VI_1 {
    track_script{
       check_nginx_service
    }
}      

  然後啟動叢集,把配置了該監聽腳本的節點的nginx服務停掉,會發現此刻通過浏覽器通路會切到另外一個節點上。說明配置完成,或者在nginx服務沒有啟動的時候去啟動 keepalived 服務,會發現該服務無論如何都是無法啟動的,啟動後檢視狀态一直會是 dead 的狀态。高可用方案到此結束。

3. OpenResty 安裝及使用

  OpenResty 是一個通過 Lua 擴充 Nginx 實作的可伸縮的 Web 平台,内部內建了大量精良的 Lua 庫、第三方子產品以及大多數的依賴項。用于友善地搭建能夠處理超高并發、擴充性極高的動态 Web 應用、Web 服務和動态網關。

安裝:

1. 下載下傳安裝包  https://openresty.org/cn/download.html

2. 安裝軟體包  tar -zxvf openresty-1.13.6.2.tar.gz。cd openrestry-1.13.6.2。./configure [預設會安裝在/usr/local/openresty 目錄] --prefix= 指定路徑。make && make install

3. 可能存在的錯誤,第三方依賴庫沒有安裝的情況下會報錯 yum install readline-devel / pcre-devel /openssl-devel

  安裝過程和 Nginx 是一樣的,因為他是基于 Nginx 做的擴充。開始第一個程式,HelloWorld  cd /usr/local/openresty/nginx/conf 編輯 nginx 配置檔案 nginx.conf 

location / {
 default_type text/html;
 content_by_lua_block {
   ngx.say("helloworld");
 }
}
           

  在 sbin 目錄下執行.nginx 指令就可以運作,通路可以看到 helloworld。

建立工作空間:

  為了不影響預設的安裝目錄,我們可以建立一個獨立的空間來練習,先到在安裝目錄下建立 demo 目錄,安裝目錄為/usr/local/openresty  .mkdir demo。然後在 demo 目錄下建立三個子目錄,一個是 logs 、一個是 conf,一個是 lua。

  進接下去示範一個實作 API網關功能的簡單demo,然後提供一個算法去計算傳入參數a,b的和。入conf 建立配置檔案  vim nginx.conf :

worker_processes 1;
error_log logs/error.log;
events {
   worker_connections 1024;
}
http {
   lua_package_path '$prefix/lua/?.lua'; //這裡的¥prefix在啟動的時候指定
   lua_code_cache off; // lua腳本不需要重新加載
   server {
     listen 80;
     // 正則比對通路路徑
     location ~ ^/api/([-_a-zA-Z0-9]+) {
        // 請求過濾一下
        access_by_lua_file lua/check.lua;
        content_by_lua_file lua/$1.lua;
     }
   }
}
           

  當來到的請求符合   ^/api/([-_a-zA-Z0-9/]  時 會在NGX_HTTP_CONTENT_PHASE HTTP請求内容階段 交給 lua/$1.lua來處理。比如:

/api/addition                    交給 lua/addition.lua 處理。

/api/lua/substraction       交給 lua/substraction .lua 處理。

  接下去建立 三個 lua 腳本:params.lua:

local _M ={} // 定義一個子產品
//定義一個方法
//該方法用于判斷參數是否為數字
function _M.is_number(...)
   local args={...}
   local num;
   for i,v in ipairs(arg) do
     num=tonumber(v);
     if nil ==num then
         return false;
     end
   end
   return true;
end
//将該子產品傳回出去
return _M;
           

  寫一個用于起到網關過濾的檢查腳本check.lua:

//導入子產品
local param=require("params");
//擷取uri的參數
local args=ngx.req.get_uri_args();
//判斷a,b是否為空且是否為數字
if not args.a or not args.b or not param.is_number(args.a,args.b) then
        ngx.exit(ngx.HTTP_BAD_REQUEST);
        return;
end
           

  算法腳本  add.lua:

local args =ngx.req.get_uri_args();
ngx.say(args.a+args.b);
           

  進入nginx的sbin目錄執行:./nginx -p /usr/local/openresty/demo 【-p 主要是指明 nginx 啟動時的配置目錄】,此時會提示一個警告資訊,無需理會,有強迫症把對應配置關了就還了:nginx: [alert] lua_code_cache is off; this will hurt performance in /usr/local/openresty/demo/conf/nginx.conf:12。通過通路http://192.168.254.137/api/add?a=1&b=3 能顯示最後的值:

Nginx的程式模型及高可用方案(OpenResty)

  庫檔案使用:通過上面的案例,我們基本上對 openresty 有了一個更深的認識,其中我們用到了自定義的 lua 子產品。實際上 openresty 提供了很豐富的子產品。讓我們在實作某些場景的時候更加友善。可以在 /openresty/lualib 目錄下看到;比如在 resty 目錄下可以看到 redis.lua、mysql.lua 這樣的操作 redis 和操作資料庫的子產品。更多的庫可以去百度,或者查找相關書籍。

4.什麼是API網關

  從一個房間到另一個房間,必須必須要經過一扇門,同樣,從一個網絡向另一個網絡發送資訊,必須經過一道“關口”,這道關口就是網關。顧名思義,網關(Gateway)就是一個網絡連接配接到另一個網絡的“關口”。那什麼是 api 網關呢?

  在微服務流行起來之前,api 網關就一直存在,最主要的應用場景就是開放平台,也就是 open api; 這種場景大家接觸的一定比較多,比如阿裡的開放平台;當微服務流行起來以後,api 網關就成了上層應用內建的标配元件.

  為什麼需要網關?

  對微服務元件位址進行統一抽象,API 網關意味着你要把 API 網關放到你的微服務的最前端,并且要讓 API 網關變成由應用所發起的每個請求的入口。這樣就可以簡化用戶端實作和微服務應用程式之間的溝通方式.

  當服務越來越多以後,我們需要考慮一個問題,就是對某些服務進行安全校驗以及使用者身份校驗。甚至包括對流量進行控制。 我們會對需要做流控、需要做身份認證的服務單獨提供認證功能,但是服務越來越多以後,會發現很多元件的校驗是重複的。這些東西很明顯不是每個微服務元件需要去關心的事情。微服務元件隻需要負責接收請求以及傳回響應即可。可以把身份認證、流控都放在 API 網關層進行控制。

5. OpenResty 實作灰階釋出功能

  在單一架構中,随着代碼量和業務量不斷擴大,版本疊代會逐漸變成一個很困難的事情,哪怕是一點小的修改,都必須要對整個應用重新部署。 但是在微服務中,各個子產品是是一個獨立運作的元件,版本疊代會很友善,影響面很小。同時,為服務化的元件節點,對于我們去實作灰階釋出(金絲雀釋出:将一部分流量引導到新的版本)來說,也會變的很簡單;是以通過 API 網關,可以對指定調用的微服務版本,通過版本來隔離。如下圖所示

Nginx的程式模型及高可用方案(OpenResty)

  OpenResty 實作 API 網關限流及登入授權

  OpenResty 為什麼能做網關?

  前面我們了解到了網關的作用,通過網關,可以對 api 通路的前置操作進行統一的管理,比如鑒權、限流、負載均衡、日志收集、請求分片等。是以 API 網關的核心是所有用戶端對接後端服務之前,都需要統一接入網關,通過網關層将所有非業務功能進行處理。OpenResty 為什麼能實作網關呢? OpenResty 有一個非常重要的因素是,對于每一個請求,Openresty 會把請求分為不同階段,進而可以讓第三方子產品通過挂載行為來實作不同階段的自定義行為。而這樣的機制能夠讓我們非常友善的設計 api 網關。

Nginx的程式模型及高可用方案(OpenResty)

Nginx 本身在處理一個使用者請求時,會按照不同的階段進行處理,總共會分為 11個階段。而 openresty 的執行指令,就是在這 11 個步驟中挂載 lua 執行腳本實作擴充,我們分别看看每個指令的作用

  init_by_lua : 當 Nginx master 程序加載 nginx 配置檔案時會運作這段 lua 腳本,一般用來注冊全局變量或者預加載 lua 子產品。

  init_woker_by_lua: 每個 Nginx worker 程序啟動時會執行的 lua 腳本,可以用來做健康檢查。

  set_by_lua:設定一個變量。

  rewrite_by_lua:在 rewrite 階段執行,為每個請求執行指定的 lua 腳本。

  access_by_lua:為每個請求在通路階段調用 lua 腳本。

  content_by_lua:前面示範過,通過 lua 腳本生成 content 輸出給 http 響應。

  balancer_by_lua:實作動态負載均衡,如果不是走 contentbylua,則走 proxy_pass,再通過 upstream 進行轉發。

  header_filter_by_lua: 通過 lua 來設定 headers 或者 cookie。

  body_filter_by_lua:對響應資料進行過濾。

  log_by_lua : 在 log 階段執行的腳本,一般用來做資料統計,将請求資料傳輸到後端進行分析。

灰階釋出的實作:

1.跟前面一樣建立一個新的工作空間 mkdir gray。然後在 demo 目錄下建立三個子目錄,一個是 logs 、一個是 conf,一個是 lua。

2.編寫 Nginx 的配置檔案 nginx.conf

worker_processes 1;

error_log logs/error.log;

events{
  worker_connections 1024;
}

http{
 lua_package_path "$prefix/lualib/?.lua;;";
 lua_package_cpath "$prefix/lualib/?.so;;";
 //生産環境
 upstream prod {//tomcat位址
  server 192.168.254.137:8080;
 }
 // 預生産環境
 upstream pre {//tomcat位址
  server 192.168.254.139:8080;
 }

 server {
  listen 80;
  server_name localhost;
  //當通路該路徑就會進入lua腳本
  location / {
   content_by_lua_file lua/gray.lua;
  }
  // 定義變量在lua中會使用
  location @prod {
   proxy_pass http://prod;
  }

  location @pre {
   proxy_pass http://pre;
  }
 }
}
           

3.編寫 gray.lua 檔案

local redis=require "resty.redis";
local red=redis:new();
red:set_timeout(1000);
local ok,err=red:connect("192.168.254.138",6379);
if not ok then
 ngx.say("failed to connect redis",err);
 return;
end
ok, err = red:auth("wuzhenzhao");
local_ip=ngx.var.remote_addr;
local ip_lists=red:get("gray");
if string.find(ip_lists,local_ip) == nil then
 ngx.exec("@prod");
else
 ngx.exec("@pre");
end
local ok,err=red:close();
           

4.

  1.進入sbin目錄 執行指令啟動 nginx: ./nginx -p /usr/local/openresty/gray

  2. 啟動 redis,并設定 set gray 192.168.254.1,由于我這邊是通路虛拟機,是以我本地ip去通路就是這個。

  3. 通過浏覽器運作: http://192.168.254.137/檢視運作結果

  修改 redis gray 的值, 到 redis 中 set gray 192.168.254.2. 再次運作結果,即可看到通路結果已經發生了變化.

轉載于:https://www.cnblogs.com/wuzhenzhao/p/10214336.html