成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專(zhuān)欄INFORMATION COLUMN

使用 Nginx 優(yōu)化面向側(cè)面的架構(gòu)

remcarpediem / 1133人閱讀

摘要:從主關(guān)注點(diǎn)中分離出橫切關(guān)注點(diǎn)是面向側(cè)面的程序設(shè)計(jì)的核心概念。最終我們采用的是通過(guò)的模塊來(lái)將以面向側(cè)面的思路耦合。原文使用優(yōu)化面向側(cè)面的架構(gòu)

面向側(cè)面的程序設(shè)計(jì)(aspect-oriented programming,AOP),通過(guò)將解決特定領(lǐng)域問(wèn)題的代碼從業(yè)務(wù)邏輯中獨(dú)立出來(lái),從而提高代碼的可維護(hù)性。

從主關(guān)注點(diǎn)中分離出橫切關(guān)注點(diǎn)是面向側(cè)面的程序設(shè)計(jì)的核心概念。分離關(guān)注點(diǎn)使得解決特定領(lǐng)域問(wèn)題的代碼從業(yè)務(wù)邏輯中獨(dú)立出來(lái),業(yè)務(wù)邏輯的代碼中不再含有針對(duì)特定領(lǐng)域問(wèn)題代碼的調(diào)用,業(yè)務(wù)邏輯同特定領(lǐng)域問(wèn)題的關(guān)系通過(guò)側(cè)面來(lái)封裝、維護(hù),這樣原本分散在在整個(gè)應(yīng)用程序中的變動(dòng)就可以很好的管理起來(lái)。 - 維基百科


示例是根據(jù)最近正在負(fù)責(zé)的 APP 后端項(xiàng)目簡(jiǎn)化版,需求簡(jiǎn)單說(shuō)如下:

APP 端會(huì)對(duì)所有請(qǐng)求進(jìn)行加密,服務(wù)器端要對(duì)加密結(jié)果進(jìn)行校驗(yàn),確保正確以及未篡改;

通過(guò)手機(jī)號(hào)來(lái)登錄,采用基本的 token 機(jī)制驗(yàn)證登錄;

有企業(yè)、小組以及員工的層級(jí)關(guān)系,后期必須考慮根據(jù)公司來(lái)分表/集群;

提供涉及到權(quán)限的 REST 風(fēng)格的接口(某種程度上類(lèi)似 Postgrest,但是進(jìn)行了拓展,后面會(huì)有專(zhuān)門(mén)文章介紹)

Version 1st.

思路:首先整個(gè)目前項(xiàng)目的主關(guān)注點(diǎn) (core concern) 是 REST 風(fēng)格的資源服務(wù)器 —— 即通過(guò)約定俗成的風(fēng)格來(lái)對(duì)應(yīng)具體的數(shù)據(jù)/資源操作。在這個(gè)功能外,需要完成的其他關(guān)注點(diǎn)包括:

所有請(qǐng)求加密校驗(yàn)

登錄驗(yàn)證

資源的權(quán)限管理以及獲取

于是,在 Sinatra 中,可以通過(guò) extensions 的方式將請(qǐng)求加密校驗(yàn)完成,配合 before 來(lái)進(jìn)行統(tǒng)一處理:

require "sinatra/base"

module Sinatra

  module RequestHeadersVerify

    module Helpers
      def headers_valid?
        # 此處省略真實(shí)業(yè)務(wù)代碼
        false
      end
    end

    def self.registered(app)
      app.helpers RequestHeadersVerify::Helpers

      app.before do
        unless headers_valid?
          halt 400, json(ResponseErrror::InvalidHeadersError.new)
        end
      end
    end
  end

  register RequestHeadersVerify
end

最終采用"中間件"的方式,在請(qǐng)求的最前面一層(橫切關(guān)注點(diǎn) crosscutting concerns)將非法請(qǐng)求進(jìn)行攔截。

于是緊接著第二個(gè)流程,驗(yàn)證用戶是否登錄,與獲取當(dāng)前聯(lián)系人所在的公司、小組、以及其管理的小組信息一樣,這里最快速/方便的做法就是通過(guò) helpers 來(lái)實(shí)現(xiàn):

require "sinatra/base"

module Sinatra 
  module UserSessionHelpers
    HTTP_USER_TOKEN_KEY = "HTTP_AUTH_TOKEN"

    def current_user
      @current_user ||= (
        user = User.first token: env[HTTP_USER_TOKEN_KEY]
        halt 400, json(ResponseErrror::InvalidTokenError.new) unless user
        user
      )
    end
  end

  module OrganizationHelpers
    # 這里省略掉相關(guān) helpers 代碼
  end

  helpers UserSessionHelpers
  helpers OrganizationHelpers
end

最終在 REST 相關(guān)的構(gòu)建代碼中,就不需要去考慮用戶請(qǐng)求加密的內(nèi)容,也不需要去考慮用戶是否登錄(因?yàn)槿绻枰褂玫接脩粜畔⒌怯脩魶](méi)有登錄,會(huì)直接拋出錯(cuò)誤返回)。只需要按照約定的設(shè)計(jì)風(fēng)格,把請(qǐng)求的內(nèi)容在校驗(yàn)了內(nèi)容和權(quán)限后,轉(zhuǎn)成對(duì)應(yīng)的數(shù)據(jù)庫(kù)操作,最終再按照約定的內(nèi)容返回。

Version 2nd.

第一版已經(jīng)盡可能的考慮到 解決特定領(lǐng)域問(wèn)題的代碼從業(yè)務(wù)邏輯中獨(dú)立出來(lái)。但是現(xiàn)實(shí)開(kāi)發(fā)里面經(jīng)常會(huì)涉及到多人開(kāi)發(fā)、跨語(yǔ)言合作、更快速的迭代等等的問(wèn)題,最終需要把他們拆成獨(dú)立的低耦合度的 Server。于是隨之而來(lái)的是如何在服務(wù)間進(jìn)行通訊/共享數(shù)據(jù)。

這里的方案選擇通常會(huì)根據(jù)實(shí)際業(yè)務(wù)以及難易程度來(lái)權(quán)衡,例如最快捷的 webServer 的方式內(nèi)部通信,稍微復(fù)雜點(diǎn)的基于 TCP 的 RPC 通信方案(例如 thrift),或者某些特殊的情景,例如是生產(chǎn)者/消費(fèi)者關(guān)系的話,則可能通過(guò) MQ 來(lái)進(jìn)行通信。最終我們采用的是通過(guò) Nginx 的 lua 模塊來(lái)將 server 以面向側(cè)面的思路耦合。

首先,Nginx 的 Lua 模塊可以做什么?如果可以,單純 nginx 和 lua 就可以完成完整的 web 服務(wù)。可以連接 redis、memcache、postgresql 等等,同時(shí)可以取得請(qǐng)求的所有內(nèi)容,可以設(shè)置返回的頭部、正文。配合 lua 的對(duì)數(shù)據(jù)處理能力,基本功能都可以實(shí)現(xiàn)。同時(shí) nginx 的 lua 模塊整體都是異步,所以性能也相對(duì)較好。當(dāng)然也可以通過(guò) lua 腳本來(lái)控制權(quán)限,如果驗(yàn)證通過(guò)則繼續(xù)下面的操作,例如是 proxy_pass 代理,簡(jiǎn)單的示例如下文:

location = /foo {
    access_by_lua_block {
        -- check the client IP address is in our black list
        if ngx.var.remote_addr == "132.5.72.3" then
            ngx.exit(ngx.HTTP_FORBIDDEN)
        end

        -- check if the URI contains bad words
        if ngx.var.uri and
            string.match(ngx.var.request_body, "evil")
        then
            return ngx.redirect("/terms_of_use.html")
        end

        -- tests passed
    }

    proxy_pass http://blah.blah.com;
 }

不過(guò),我們這里將用到的主要還是 proxy_pass, 和 ngx.location.capture,基本代碼如下:


if string.sub(ngx.var.uri, 2, 2) == "_" then
  ngx.exit(404)
end

local cjson = require "cjson"

local custom_header_prefix = "V-"
local request_args = ngx.req.get_uri_args(64)
local request_body = ngx.req.read_body()
local request_path = ngx.var.uri
local request_method = ngx["HTTP_"..ngx.req.get_method()]

for header, _ in pairs(ngx.req.get_headers()) do
  if string.upper(string.sub(header, 1, 2)) == crm_header_prefix then
    ngx.req.clear_header(header);
  end
end

function res_with_json(body, status)
  ngx.header["Content-Type"] = "application/json"
  ngx.print(body)
  return ngx.exit(status)
end

function request_to_server(uri)
  
  res = ngx.location.capture(uri..request_path, {
    body = request_body,
    args = request_args,
    method = request_method,
  })
  local json_response = cjson.decode(res.body)

  
  if not json_response.next == true then
    res_with_json(res.body, res.status)
  end
  for key, value in json_response.params do
     ngx.req.set_header(custom_header_prefix..string.upper(key), value)
  end
  return false
end

上面的代碼主完成了清理用戶惡意提交的請(qǐng)求頭,以及 request_to_server 的代碼,實(shí)現(xiàn)了將原請(qǐng)求內(nèi)容轉(zhuǎn)發(fā)給另一個(gè)接口并獲得請(qǐng)求后的內(nèi)容。得到請(qǐng)求結(jié)果后,驗(yàn)證請(qǐng)求的參數(shù)。

同時(shí)在 nginx 里面通過(guò) stream 和 proxy_pass 的方式來(lái)配置多個(gè)內(nèi)部地址:

upstream authentication-server {
    server 192.168.21.1:6011;
    server 192.168.21.2:6011;
}

server {
    location /_authentication {
      proxy_redirect off;
      proxy_set_header Host      $host;
      proxy_set_header X-Real-IP $remote_addr;

      rewrite  ^/_authentication/(.*)  /$1 break;
      proxy_pass http://authentication-server;
    }
}

于是,將兩個(gè)結(jié)合起來(lái),就可以實(shí)現(xiàn)通過(guò) lua 腳本把原請(qǐng)求的所有參數(shù),包括頭部、正文、請(qǐng)求地址、請(qǐng)求方法都帶過(guò)去,請(qǐng)求另一個(gè)地址(和 proxy_pass 類(lèi)似),并且可以得到最終返回的結(jié)果處理。

擁有這個(gè)能力后,便是本文的重點(diǎn)了:在 Sinatra 的第一版本中,最終都是 ruby 代碼不斷調(diào)用方法,來(lái)完成整個(gè)請(qǐng)求的流程。那如果我們把整個(gè)流程的打通交給 Nginx 的話該如何實(shí)現(xiàn)呢?

當(dāng)一個(gè)請(qǐng)求進(jìn)入后,通過(guò) request_to_server 的能力,把請(qǐng)求依次轉(zhuǎn)發(fā)給負(fù)責(zé) 橫切關(guān)注點(diǎn) 的服務(wù),例如用戶請(qǐng)求校驗(yàn)以及登錄校驗(yàn)服務(wù)、用戶的組織架構(gòu)服務(wù),最終再去調(diào)用主關(guān)注點(diǎn),即本文中的資源服務(wù)器;

每次請(qǐng)求完后,根據(jù)前一個(gè)流程的返回值決定是否進(jìn)入下一個(gè)流程,例如示例中的 lua 腳本是通過(guò)返回的 json 里面的 next 參數(shù)來(lái)決定是否繼續(xù)往下走。如果沒(méi)有這個(gè)參數(shù)則直接返回當(dāng)前服務(wù)的返回值不再繼續(xù)請(qǐng)求下去;

如果出現(xiàn)了 next: true 這個(gè)關(guān)鍵字,則將返回值中的其他內(nèi)容以請(qǐng)求頭的形式傳遞給下一個(gè)服務(wù),且每個(gè)服務(wù)都會(huì)完全信賴這些請(qǐng)求頭(所以請(qǐng)求剛進(jìn)來(lái)的時(shí)候需要做一些請(qǐng)求頭和請(qǐng)求地址處理)。

如果到這里都沒(méi)有太大問(wèn)題,你應(yīng)該可以理解我的意圖了。即 Nginx 通過(guò) Lua 腳本來(lái)依次請(qǐng)求 橫切關(guān)注點(diǎn)服務(wù)器,如果一路順暢(每次都有 next: true),最終會(huì)把攜帶有 橫切關(guān)注點(diǎn)服務(wù)返回的內(nèi)容的 headers 帶給主關(guān)注點(diǎn)服務(wù)。

于是,在本需求里面,為了保證可拓展性和低耦合性,最終分為了三個(gè)服務(wù):

負(fù)責(zé)請(qǐng)求加密鑒權(quán),用戶登錄、密碼修改的用戶驗(yàn)證服務(wù)

負(fù)責(zé)管理企業(yè)結(jié)構(gòu)、獲取用戶權(quán)限的組織架構(gòu)服務(wù)

負(fù)責(zé)具體的 REST 請(qǐng)求處理的資源服務(wù)

當(dāng)一個(gè)用戶登錄的請(qǐng)求過(guò)來(lái),因?yàn)槊艽a錯(cuò)誤或者加密鑒權(quán)失敗,會(huì)在用戶驗(yàn)證服務(wù)就直接返回錯(cuò)誤。因?yàn)榉祷貎?nèi)容中沒(méi)有 next: true 字段,所以直接返回結(jié)果;

當(dāng)一個(gè)用戶發(fā)起了一個(gè)發(fā)送短信驗(yàn)證碼的服務(wù),這個(gè)只是用戶驗(yàn)證服務(wù)的職能,沒(méi)有必要繼續(xù)向下走,于是返回了一個(gè)沒(méi)有 next: true 字段的返回值,于是 Nginx 直接返回結(jié)果;

當(dāng)用戶登錄的時(shí)候,雖然通過(guò)了用戶驗(yàn)證服務(wù)的校驗(yàn),但是該服務(wù)無(wú)法獲取更多的用戶信息,于是把該請(qǐng)求繼續(xù)傳遞到組織架構(gòu)服務(wù),組織架構(gòu)服務(wù)在請(qǐng)求頭中拿到了手機(jī)號(hào)信息,于是直接返回了該手機(jī)號(hào)所對(duì)應(yīng)的詳細(xì)信息;

現(xiàn)在發(fā)起了一個(gè)資源操作的請(qǐng)求,因?yàn)橛脩趄?yàn)證服務(wù)無(wú)法識(shí)別,所以只返回了手機(jī)號(hào)給 nginx,nginx 繼續(xù)請(qǐng)求組織架構(gòu)服務(wù),因?yàn)榻M織架構(gòu)服務(wù)也不能處理,所以繼續(xù)返回了詳細(xì)的個(gè)人信息給 nginx,nginx 最終拿到這些信息,都通過(guò)頭部請(qǐng)求了資源服務(wù),然后因?yàn)檫@里是主關(guān)注點(diǎn),也是流程里面最后一個(gè)節(jié)點(diǎn),所以通過(guò) proxy_pass 給了資源服務(wù)。


最終,這樣做的優(yōu)勢(shì):

利用 Nginx 異步的優(yōu)勢(shì)來(lái)彌補(bǔ) ruby 服務(wù)先天性 IO 處理的不足;

目前只實(shí)現(xiàn)了第一條線,即從用戶驗(yàn)證 -> 組織信息 -> 資源服務(wù)器的順序,后面如果有需要,可以隨時(shí)實(shí)現(xiàn)其他順序,而只需要按照在請(qǐng)求頭里面加上相應(yīng)的參數(shù)即可,減低耦合性;

三個(gè)模塊都有各自的業(yè)務(wù)和特點(diǎn),可以針對(duì)模塊去設(shè)計(jì)緩存方案,而且可以分模塊去設(shè)計(jì)集群方案;

對(duì)于開(kāi)發(fā)者而言,更容易完成單個(gè)服務(wù)的測(cè)試用例,而不需要過(guò)多在開(kāi)發(fā)過(guò)程中關(guān)注聯(lián)調(diào)。

原文: 使用 Nginx 優(yōu)化面向側(cè)面的架構(gòu)

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/39194.html

相關(guān)文章

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<