最近2018中文字幕在日韩欧美国产成人片_国产日韩精品一区二区在线_在线观看成年美女黄网色视频_国产精品一区三区五区_国产精彩刺激乱对白_看黄色黄大色黄片免费_人人超碰自拍cao_国产高清av在线_亚洲精品电影av_日韩美女尤物视频网站

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營(yíng)銷解決方案
Rails微服務(wù)架構(gòu)

Rails 應(yīng)用有各種類型,規(guī)模也各有不同。有的是一個(gè)獨(dú)立的龐大的應(yīng)用,全部應(yīng)用都在同一個(gè)位置(包括管理界面、API、前端部分以及所有需要的模塊)。另一些應(yīng)用則是劃分成一系列的微服務(wù),服務(wù)之間互相通信,這樣可以把整個(gè)應(yīng)用切分成更易管理的部分。

興賓網(wǎng)站制作公司哪家好,找創(chuàng)新互聯(lián)!從網(wǎng)頁(yè)設(shè)計(jì)、網(wǎng)站建設(shè)、微信開發(fā)、APP開發(fā)、成都響應(yīng)式網(wǎng)站建設(shè)公司等網(wǎng)站項(xiàng)目制作,到程序開發(fā),運(yùn)營(yíng)維護(hù)。創(chuàng)新互聯(lián)于2013年開始到現(xiàn)在10年的時(shí)間,我們擁有了豐富的建站經(jīng)驗(yàn)和運(yùn)維經(jīng)驗(yàn),來保證我們的工作的順利進(jìn)行。專注于網(wǎng)站建設(shè)就選創(chuàng)新互聯(lián)。

這種微服務(wù)的架構(gòu)被稱為面向服務(wù)的架構(gòu)( SOA )。雖然我見到過的 Rails 應(yīng)用通常都傾向于成為獨(dú)立的程序,不過開發(fā)者也完全可以選擇讓多個(gè) Rails 程序,以及與其他語(yǔ)言或者框架編寫的服務(wù)一起工作來完成任務(wù)。

獨(dú)立的程序不意味著一定寫的不好,但是寫的差的獨(dú)立程序被拆成微服務(wù)后大多也是很糟糕的。有多種方式可以讓你寫出清晰的(更容易測(cè)試的)代碼,同時(shí)在需要拆分應(yīng)用的時(shí)候也更輕松。

使用微服務(wù)架構(gòu)的 Rails 應(yīng)用的用例

本文會(huì)討論如何實(shí)現(xiàn)一個(gè) CMS 的網(wǎng)站??梢约僭O(shè)是一家大的報(bào)紙或者博客,有很多作者負(fù)責(zé)投稿,用戶可以按主題訂閱內(nèi)容。

Martin Fowler 有一篇很不錯(cuò)的文章,介紹了為什么編輯和發(fā)布應(yīng)該分成兩個(gè)不同的系統(tǒng)。我們的用例與此類似,另外我們還要添加兩個(gè)模塊:通知和訂閱。

我們的 CMS 現(xiàn)在有四個(gè)主要的模塊:

CMS 編輯器:作者和編輯用來創(chuàng)建、編輯和發(fā)布文章。

公共的網(wǎng)站:對(duì)外提供服務(wù),瀏覽已發(fā)布的文章。

通知:通知訂閱者有新發(fā)布的文章。

訂閱:管理用戶賬號(hào)和訂閱。

Rails 應(yīng)用需要支持 SOA 嗎?

是選擇獨(dú)立程序還是構(gòu)建成微服務(wù)?這里沒有對(duì)和錯(cuò)之分,不過下面的問題能幫你做出決定。

團(tuán)隊(duì)的組織結(jié)構(gòu)是怎樣的?

是否選擇支持 SOA 通常與技術(shù)無關(guān),而是在于開發(fā)團(tuán)隊(duì)的組織結(jié)構(gòu)。

由四個(gè)團(tuán)隊(duì)分別負(fù)責(zé)一個(gè)主要的模塊,比所有人在整個(gè)系統(tǒng)上一起工作要靠譜一些。如果你只有一個(gè)團(tuán)隊(duì)或者少數(shù)幾個(gè)開發(fā)人員,一開始就決定采用微服務(wù)架構(gòu)實(shí)際上會(huì)減慢開發(fā)的速度,這是因?yàn)樾枰獮樗膫€(gè)不同的組件直接的通信以及部署增加開發(fā)量。

不同的模塊規(guī)模不一樣?

對(duì)于本文的例子,有一個(gè)問題提現(xiàn)的很好,對(duì)外提供服務(wù)的公共網(wǎng)站肯定要比作者和編輯使用的 CMS 編輯器的訪問壓力要大很多。

如果這些模塊都部署成分離的系統(tǒng),我們就可以單獨(dú)的控制它們的規(guī)模,為系統(tǒng)中不同的部分采用不同的緩存技術(shù)。你當(dāng)然還是可以堅(jiān)持采用單一的系統(tǒng),但是那樣的話你就只能為整個(gè)系統(tǒng)一次性確定其規(guī)模,而不是對(duì)不同的組件分開處理。

不同的模塊使用不同的技術(shù)?

對(duì)于 CMS 編輯器,你也許想使用 Single Page Application (SPA),采用 React 或者 Angular 技術(shù)。而對(duì)外的網(wǎng)站,會(huì)使用更傳統(tǒng)一些的服務(wù)端渲染的 Rails 應(yīng)用(為了支持 SEO)。也許通知模塊更適合 Elixir,因?yàn)檫@個(gè)語(yǔ)言對(duì)并發(fā)和并行處理支持不錯(cuò)。

模塊的分離,使得你可以為每個(gè)模塊選擇最適合的編程語(yǔ)言。

邊界定義

現(xiàn)在最重要的事情是定義好系統(tǒng)中模塊之間的邊界。

系統(tǒng)中的某個(gè)部分可能是某個(gè)外部
Server

Client。使用方法調(diào)用還是基于 HTTP 都不重要,它只需要知道它需要與系統(tǒng)中的其他部分進(jìn)行通信。

為此我們需要定義清晰的邊界。

當(dāng)一篇文章發(fā)布時(shí),會(huì)發(fā)生兩件事:

首先會(huì)把文章的發(fā)布版本發(fā)送給對(duì)外的網(wǎng)站,它會(huì)返回一個(gè)發(fā)布后的 URL。

然后我們把剛創(chuàng)建的公開的 URL、話題、標(biāo)題發(fā)送到通知模塊,后者會(huì)通知到所有對(duì)話題感興趣的訂閱者。這一步可以是異步的,因?yàn)橥ǔ?huì)耗費(fèi)一些時(shí)間來通知到每一個(gè)用戶,并且這個(gè)通知是不會(huì)有反饋的。

例如,下面的代碼用來發(fā)布一篇文章。文章本身不會(huì)關(guān)心服務(wù)是通過方法調(diào)用還是 HTTP 來調(diào)用的。

class Publisher attr_reader :article, :service def initialize(article, service) @article = article @service = service end def publish mark_as_published call_service article end private def call_service service.new( author: article.author, title: article.title, slug: article.slug, category: article.category, body: article.body ).call end def mark_as_published(published_url) article.published_at = Time.zone.now article.published_url = published_url end end

這種方式也可以讓我們方便測(cè)試 Publisher 類的功能,我們可以使用 TestPublisherService 來做測(cè)試,它會(huì)返回預(yù)定義的應(yīng)答。

require "rails_helper" RSpec.describe Publisher, type: :model do let(:article) { OpenStruct.new({ author: 'Carlos Valderrama', title: 'My Hair Secrets', slug: 'my-hair-secrets', category: 'Soccer', body: "# My Hair Secrets\\nHow hair was the secret to my soccer success." }) } class TestPublisherService < PublisherService def call "http://www.website.com/article/#{slug}" end end describe 'publishes an article to public website' do subject { Publisher.new(article, TestPublisherService) } it 'sets published url' do published_article = subject.publish expect(published_article.published_url).to eq('http://www.website.com/article/my-hair-secrets') end it 'sets published at' do published_article = subject.publish expect(published_article.published_at).to be_a(Time) end end end

實(shí)際上 PublisherService 的具體實(shí)現(xiàn)還沒有完成,但是這不妨礙我們?yōu)榭蛻舳耍ù颂幨?Publisher)編寫測(cè)試用例來保證其按預(yù)期工作。

class PublisherService attr_reader :author, :title, :slug, :category, :body def initialize(author:, title:, slug:, category:, body:) @author = author @title = title @slug = slug @category = category @body = body end def call # coming soon end end

服務(wù)間通信

服務(wù)之間需要能夠互相通信。對(duì)此作為 Ruby 程序員應(yīng)該是很熟悉了,即使之前沒有做過微服務(wù)的程序。

調(diào)用某個(gè)對(duì)象的方法,只需要給它發(fā)送消息,例如調(diào)用 Time.send(:now) 就可以改變 Time.now。不管是通過方法調(diào)用還是基于 HTTP 進(jìn)行通信,原理是一樣的。我們要做的是給系統(tǒng)的其他部分發(fā)送消息,通常還需要有回應(yīng)。

使用 HTTP 協(xié)議和微服務(wù)通訊

當(dāng)你的應(yīng)用需要一個(gè)來自服務(wù)端的立即響應(yīng)才能繼續(xù)執(zhí)行的時(shí)候,使用 HTTP 協(xié)議來交互將是不二的選擇。

當(dāng)你需要一個(gè)立即響應(yīng)的時(shí)候,HTTP 協(xié)議通訊將是不二的選擇。

在下面的例子中,PublisherService 類實(shí)現(xiàn)了使用 HTTP Post 方法來和后端的 Faraday 服務(wù)模塊進(jìn)行通訊。

class PublisherService < HttpService attr_reader :author, :title, :slug, :category, :body def initialize(author:, title:, slug:, category:, body:) @author = author @title = title @slug = slug @category = category @body = body end def call post["published_url"] end private def conn Faraday.new(url: Cms::PUBLIC_WEBSITE_URL) end def post resp = conn.post '/articles/publish', payload if resp.success? JSON.parse resp.body else raise ServiceResponseError end end def payload {author: author, title: title, slug: slug, category: category, body: body} end end

這段代碼簡(jiǎn)單來說就是構(gòu)造了一個(gè)需要發(fā)送給后端的數(shù)據(jù),然后通過 HTTP Post 發(fā)送到后端,并且處理從后端的返回的數(shù)據(jù)。但后端返回了正確的數(shù)據(jù),程序?qū)⒔忉屵@個(gè)數(shù)據(jù),否則程序?qū)伋鲆粋€(gè)異常。在后面我們將對(duì)這個(gè)代碼進(jìn)行詳細(xì)地解釋。

在代碼中,后端服務(wù)程序的地址保存在常量 Cms::PUBLIC_WEBSITE_URL中,這個(gè)常量的值是通過初始化代碼設(shè)置的。這樣做的好處就是允許我們使用環(huán)境變量,根據(jù)部署環(huán)境的不同(比如開發(fā)環(huán)境或者生產(chǎn)環(huán)境)來給它配置不同的值。

Cms::PUBLIC_WEBSITE_URL = ENV['PUBLIC_WEBSITE_URL'] || 'http://localhost:3000'

測(cè)試我們的服務(wù)

現(xiàn)在讓我們來測(cè)試 PublisherService 類,看看它是否正常工作。

在這個(gè)測(cè)試中,由于我們是在開發(fā)環(huán)境中做測(cè)試,所以并不能保證后端服務(wù)一直可用,因此我們將使用 WebMock 模塊來模擬到后端的 HTTP 請(qǐng)求,并返回需要的數(shù)據(jù)。

RSpec.describe PublisherService, type: :model do let(:article) { OpenStruct.new({ author: 'Carlos Valderrama', title: 'My Hair Secrets', slug: 'my-hair-secrets', category: 'Soccer', body: "# My Hair Secrets\\nHow hair was the secret to my soccer success." }) } describe 'call the publisher service' do subject { PublisherService.new( author: article.author, title: article.title, slug: article.slug, category: article.category, body: article.body ) } let(:post_url) { "#{Cms::PUBLIC_WEBSITE_URL}/articles/publish" } let(:payload) { {published_url: 'http://www.website.com/article/my-hair-secrets'}.to_json } it 'parses response for published url' do stub_request(:post, post_url).to_return(body: payload) expect(subject.call).to eq('http://www.website.com/article/my-hair-secrets') end it 'raises exception on failure' do stub_request(:post, post_url).to_return(status: 500) expect{subject.call}.to raise_error(PublisherService::ServiceResponseError) end end end

處理調(diào)用失敗

在系統(tǒng)使用過程中,有一件事情是絕對(duì)不可避免的,那就是對(duì)于服務(wù)端的調(diào)用可能失敗(服務(wù)暫時(shí)不可用或者網(wǎng)絡(luò)通信超市),我們的代碼應(yīng)該要能夠正確處理這些異常。

當(dāng)遠(yuǎn)端服務(wù)不可用的時(shí)候,系統(tǒng)應(yīng)該如何響應(yīng)完全取決于開發(fā)者。在我們的 CMS 應(yīng)用中,當(dāng)遠(yuǎn)端服務(wù)不可用的時(shí)候,用戶仍然可以創(chuàng)建和編輯文章,只是不能發(fā)布任何文章。

在上面的測(cè)試?yán)又?,代碼包含了對(duì) HTTP Status Code 500 (服務(wù)段出現(xiàn)異常)的處理。當(dāng)測(cè)試代碼收到 500 Status Code 的時(shí)候,代碼將拋出 PublisherService::ServiceResponseError 這個(gè)異常。 ServiceResponseError 這個(gè)異常類繼承自 Error 類,目前這個(gè)類并沒有對(duì)外提供任何有用的信息,僅僅表示發(fā)生了一個(gè)錯(cuò)誤。下面是這個(gè)類的相關(guān)代碼。

class HttpService class Error < RuntimeError end class ServiceResponseError < Error end end

在 Martin Fowler 的一篇文章中,提出了另外一種處理服務(wù)不可用的方法(在他的文章中,他把這種方法叫做 CircuitBreaker 模式)。簡(jiǎn)單來說,這個(gè)模式的任務(wù)就是通過某種方式檢測(cè)遠(yuǎn)端服務(wù)是否運(yùn)作正常。如果運(yùn)作不正常,它將阻止對(duì)響應(yīng)遠(yuǎn)端服務(wù)的調(diào)用。

我們也可以通過讓我們的應(yīng)用感知遠(yuǎn)端服務(wù)的狀態(tài)并且做出適當(dāng)?shù)姆磻?yīng)來讓我們的應(yīng)用更強(qiáng)壯。這種系統(tǒng)行為的改變,我們既可以通過類似 CircuitBreaker 的模式來自動(dòng)實(shí)現(xiàn),也可以通過用戶手動(dòng)關(guān)閉系統(tǒng)的某些功能來實(shí)現(xiàn)。

在我們的例子中,如果我們可以在現(xiàn)實(shí) Publish 按鈕之前檢查一下遠(yuǎn)端 Publish 服務(wù)是否可用,那么我們就可以直接避免對(duì)不可用服務(wù)的調(diào)用。

使用隊(duì)列進(jìn)行通信

HTTP 并非是與其他服務(wù)通信的唯一方式。隊(duì)列是不同的服務(wù)之間傳遞異步消息的很好的選擇。如果對(duì)于要做的事情不需要消息接收者立刻反饋,那就非常適合這種方式(例如發(fā)送郵件)。

我們的 CMS 應(yīng)用中,文章發(fā)布后,訂閱文章的主題的用戶會(huì)被通知到(通過郵件,或者網(wǎng)站通知或者推送消息),告知他們有感興趣的文章被發(fā)布。我們的程序并不需要
Notifier服務(wù)的反饋,只需要把消息發(fā)給它就行了。

使用 Rails 的隊(duì)列

之前的一篇文章,我介紹了如何使用ActiveJob,Rails 自帶的,用來處理這種后臺(tái)或者異步處理的任務(wù)。

ActiveJob 要求接收代碼也需要運(yùn)行在 Rails 環(huán)境,不過它確實(shí)是一種很好的選擇,簡(jiǎn)單易用。

使用 RabbitMQ

RabbitMQ是 Rails(以及 Ruby)之外的另一個(gè)選擇,可以作為不同的服務(wù)之間的一個(gè)通用的消息處理系統(tǒng)。通過 RabbitMQ 也可以處理遠(yuǎn)程方法調(diào)用(RPC),不過更多的是使用 RabbitMQ 向其他服務(wù)方式異步消息。這里有很好的 Ruby 的使用教程。

下面的類用于向
Notifier服務(wù)發(fā)送消息,通知有新文章發(fā)布。

class NotifierService attr_reader :category, :title, :published_url def initialize(category, title, published_url) @category = category @title = title @published_url = published_url end def call publish payload end private def publish(data) channel.default_exchange.publish(data, routing_key: queue.name) connection.close end def payload {category: category, title: title, published_url: published_url}.to_json end def connection @conn ||= begin conn = Bunny.new conn.start end end def channel @channel ||= connection.create_channel end def queue @queue ||= channel.queue 'notifier' end end

代碼可以這樣調(diào)用:

NotifierService.new("Soccer", "My Hair Secrets", "http://localhost:3000/article/my-hair-secrets").call

總結(jié)

微服務(wù)并不可怕,不過確實(shí)需要仔細(xì)的處理。它會(huì)帶來很多好處。我的建議是從一個(gè)有著清晰邊界的小系統(tǒng)開始,這樣你可以很容易的劃分服務(wù)。

更多的服務(wù)意味著更多的開發(fā)運(yùn)維工作(你不再只是部署一個(gè)單獨(dú)的程序,而是需要部署多個(gè)小服務(wù)),這時(shí)你也許有興趣看一下我寫的如何部署到 Docker 容器。


名稱欄目:Rails微服務(wù)架構(gòu)
本文來源:http://fisionsoft.com.cn/article/chddic.html