在Malwarebytes 我們經(jīng)歷了顯著的增長(zhǎng),自從我一年前加入了硅谷的公司,一個(gè)主要的職責(zé)成了設(shè)計(jì)架構(gòu)和開(kāi)發(fā)一些系統(tǒng)來(lái)支持一個(gè)快速增長(zhǎng)的信息安全公司和所有需要的設(shè)施來(lái)支持一個(gè)每天百萬(wàn)用戶使用的產(chǎn)品。我在反病毒和反惡意軟件行業(yè)的不同公司工作了12年,從而我知道由于我們每天處理大量的數(shù)據(jù),這些系統(tǒng)是多么復(fù)雜。
10多年專注成都網(wǎng)站制作,成都定制網(wǎng)頁(yè)設(shè)計(jì),個(gè)人網(wǎng)站制作服務(wù),為大家分享網(wǎng)站制作知識(shí)、方案,網(wǎng)站設(shè)計(jì)流程、步驟,成功服務(wù)上千家企業(yè)。為您提供網(wǎng)站建設(shè),網(wǎng)站制作,網(wǎng)頁(yè)設(shè)計(jì)及定制高端網(wǎng)站建設(shè)服務(wù),專注于成都定制網(wǎng)頁(yè)設(shè)計(jì),高端網(wǎng)頁(yè)制作,對(duì)成都垃圾桶等多個(gè)方面,擁有豐富設(shè)計(jì)經(jīng)驗(yàn)。
有趣的是,在過(guò)去的大約9年間,我參與的所有的web后端的開(kāi)發(fā)通常是通過(guò)Ruby on Rails技術(shù)實(shí)現(xiàn)的。不要錯(cuò)怪我。我喜歡Ruby on Rails,并且我相信它是個(gè)令人驚訝的環(huán)境。但是一段時(shí)間后,你會(huì)開(kāi)始以ruby的方式開(kāi)始思考和設(shè)計(jì)系統(tǒng),你會(huì)忘記,如果你可以利用多線程、并行、快速執(zhí)行和小內(nèi)存開(kāi)銷(xiāo),軟件架構(gòu)本來(lái)應(yīng)該是多么高效和簡(jiǎn)單。很多年期間,我是一個(gè)c/c++、Delphi和c#開(kāi)發(fā)者,我剛開(kāi)始意識(shí)到使用正確的工具可以把復(fù)雜的事情變得簡(jiǎn)單些。
作為首席架構(gòu)師,我不會(huì)很關(guān)心在互聯(lián)網(wǎng)上的語(yǔ)言和框架戰(zhàn)爭(zhēng)。我相信效率、生產(chǎn)力。代碼可維護(hù)性主要依賴于你如何把解決方案設(shè)計(jì)得很簡(jiǎn)單。
問(wèn)題
當(dāng)工作在我們的匿名遙測(cè)和分析系統(tǒng)中,我們的目標(biāo)是可以處理來(lái)自于百萬(wàn)級(jí)別的終端的大量的POST請(qǐng)求。web處理服務(wù)可以接收包含了很多payload的集合的JSON數(shù)據(jù),這些數(shù)據(jù)需要寫(xiě)入Amazon S3中。接下來(lái),map-reduce系統(tǒng)可以操作這些數(shù)據(jù)。
按照習(xí)慣,我們會(huì)調(diào)研服務(wù)層級(jí)架構(gòu),涉及的軟件如下:
Sidekiq
Resque
DelayedJob
Elasticbeanstalk Worker Tier
RabbitMQ
and so on…
搭建了2個(gè)不同的集群,一個(gè)提供web前端,另外一個(gè)提供后端處理,這樣我們可以橫向擴(kuò)展后端服務(wù)的數(shù)量。
但是,從剛開(kāi)始,在 討論階段我們的團(tuán)隊(duì)就知道我們應(yīng)該使用Go,因?yàn)槲覀兛吹竭@會(huì)潛在性地成為一個(gè)非常龐大( large traffic)的系統(tǒng)。我已經(jīng)使用了Go語(yǔ)言大約2年時(shí)間,我們開(kāi)發(fā)了幾個(gè)系統(tǒng),但是很少會(huì)達(dá)到這樣的負(fù)載(amount of load)。
我們開(kāi)始創(chuàng)建一些結(jié)構(gòu),定義從POST調(diào)用得到的web請(qǐng)求負(fù)載,還有一個(gè)上傳到S3 budket的函數(shù)。
type PayloadCollection struct {
WindowsVersion string `json:"version"`
Token string `json:"token"`
Payloads []Payload `json:"data"`
}
type Payload struct {
// [redacted]
}
func (p *Payload) UploadToS3() error {
// the storageFolder method ensures that there are no name collision in
// case we get same timestamp in the key name
storage_path := fmt.Sprintf("%v/%v", p.storageFolder, time.Now().UnixNano())
bucket := S3Bucket
b := new(bytes.Buffer)
encodeErr := json.NewEncoder(b).Encode(payload)
if encodeErr != nil {
return encodeErr
}
// Everything we post to the S3 bucket should be marked 'private'
var acl = s3.Private
var contentType = "application/octet-stream"
return bucket.PutReader(storage_path, b, int64(b.Len()), contentType, acl, s3.Options{})
}
本地Go routines方法
剛開(kāi)始,我們采用了一個(gè)非常本地化的POST處理實(shí)現(xiàn),僅僅嘗試把發(fā)到簡(jiǎn)單go routine的job并行化:
func payloadHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
w.WriteHeader(http.StatusMethodNotAllowed)
return
}
// Read the body into a string for json decoding
var content = PayloadCollection{}
err := json.NewDecoder(io.LimitReader(r.Body, MaxLength)).Decode(content)
if err != nil {
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(http.StatusBadRequest)
return
}
// Go through each payload and queue items individually to be posted to S3
for _, payload := range content.Payloads {
go payload.UploadToS3() // ----- DON'T DO THIS
}
w.WriteHeader(http.StatusOK)
}
對(duì)于中小負(fù)載,這會(huì)對(duì)大多數(shù)的人適用,但是大規(guī)模下,這個(gè)方案會(huì)很快被證明不是很好用。我們期望的請(qǐng)求數(shù),不在我們剛開(kāi)始計(jì)劃的數(shù)量級(jí),當(dāng)我們把第一個(gè)版本部署到生產(chǎn)環(huán)境上。我們完全低估了流量。
上面的方案在很多地方很不好。沒(méi)有辦法控制我們產(chǎn)生的go routine的數(shù)量。由于我們收到了每分鐘1百萬(wàn)的POST請(qǐng)求,這段代碼很快就崩潰了。
再次嘗試
我們需要找一個(gè)不同的方式。自開(kāi)始我們就討論過(guò), 我們需要保持請(qǐng)求處理程序的生命周期很短,并且進(jìn)程在后臺(tái)產(chǎn)生。當(dāng)然,這是你在Ruby on Rails的世界里必須要做的事情,否則你會(huì)阻塞在所有可用的工作 web處理器上,不管你是使用puma、unicore還是passenger(我們不要討論JRuby這個(gè)話題)。然后我們需要利用常用的處理方案來(lái)做這些,比如Resque、 Sidekiq、 SQS等。這個(gè)列表會(huì)繼續(xù)保留,因?yàn)橛泻芏嗟姆桨缚梢詫?shí)現(xiàn)這些。
所以,第二次迭代,我們創(chuàng)建了一個(gè)緩沖channel,我們可以把job排隊(duì),然后把它們上傳到S3。因?yàn)槲覀兛梢钥刂莆覀冴?duì)列中的item最大值,我們有大量的內(nèi)存來(lái)排列job,我們認(rèn)為只要把job在channel里面緩沖就可以了。
var Queue chan Payload
func init() {
Queue = make(chan Payload, MAX_QUEUE)
}
func payloadHandler(w http.ResponseWriter, r *http.Request) {
...
// Go through each payload and queue items individually to be posted to S3
for _, payload := range content.Payloads {
Queue - payload
}
...
}
接下來(lái),我們?cè)購(gòu)年?duì)列中取job,然后處理它們。我們使用類(lèi)似于下面的代碼:
func StartProcessor() {
for {
select {
case job := -Queue:
job.payload.UploadToS3() // -- STILL NOT GOOD
}
}
}
說(shuō)實(shí)話,我不知道我們?cè)谙胧裁?。這肯定是一個(gè)滿是Red-Bulls的夜晚。這個(gè)方法不會(huì)帶來(lái)什么改善,我們用了一個(gè) 有缺陷的緩沖隊(duì)列并發(fā),僅僅是把問(wèn)題推遲了。我們的同步處理器同時(shí)僅僅會(huì)上傳一個(gè)數(shù)據(jù)到S3,因?yàn)閬?lái)到的請(qǐng)求遠(yuǎn)遠(yuǎn)大于單核處理器上傳到S3的能力,我們的帶緩沖channel很快達(dá)到了它的極限,然后阻塞了請(qǐng)求處理邏輯的queue更多item的能力。
我們僅僅避免了問(wèn)題,同時(shí)開(kāi)始了我們的系統(tǒng)掛掉的倒計(jì)時(shí)。當(dāng)部署了這個(gè)有缺陷的版本后,我們的延時(shí)保持在每分鐘以常量增長(zhǎng)。
最好的解決方案
我們討論過(guò)在使用用Go channel時(shí)利用一種常用的模式,來(lái)創(chuàng)建一個(gè)二級(jí)channel系統(tǒng),一個(gè)來(lái)queue job,另外一個(gè)來(lái)控制使用多少個(gè)worker來(lái)并發(fā)操作JobQueue。
想法是,以一個(gè)恒定速率并行上傳到S3,既不會(huì)導(dǎo)致機(jī)器崩潰也不好產(chǎn)生S3的連接錯(cuò)誤。這樣我們選擇了創(chuàng)建一個(gè)Job/Worker模式。對(duì)于那些熟悉Java、C#等語(yǔ)言的開(kāi)發(fā)者,可以把這種模式想象成利用channel以golang的方式來(lái)實(shí)現(xiàn)了一個(gè)worker線程池,作為一種替代。
var (
MaxWorker = os.Getenv("MAX_WORKERS")
MaxQueue = os.Getenv("MAX_QUEUE")
)
// Job represents the job to be run
type Job struct {
Payload Payload
}
// A buffered channel that we can send work requests on.
var JobQueue chan Job
// Worker represents the worker that executes the job
type Worker struct {
WorkerPool chan chan Job
JobChannel chan Job
quit chan bool
}
func NewWorker(workerPool chan chan Job) Worker {
return Worker{
WorkerPool: workerPool,
JobChannel: make(chan Job),
quit: make(chan bool)}
}
// Start method starts the run loop for the worker, listening for a quit channel in
// case we need to stop it
func (w Worker) Start() {
go func() {
for {
// register the current worker into the worker queue.
w.WorkerPool - w.JobChannel
select {
case job := -w.JobChannel:
// we have received a work request.
if err := job.Payload.UploadToS3(); err != nil {
log.Errorf("Error uploading to S3: %s", err.Error())
}
case -w.quit:
// we have received a signal to stop
return
}
}
}()
}
// Stop signals the worker to stop listening for work requests.
func (w Worker) Stop() {
go func() {
w.quit - true
}()
}
我們已經(jīng)修改了我們的web請(qǐng)求handler,用payload創(chuàng)建一個(gè)Job實(shí)例,然后發(fā)到JobQueue channel,以便于worker來(lái)獲取。
func payloadHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
w.WriteHeader(http.StatusMethodNotAllowed)
return
}
// Read the body into a string for json decoding
var content = PayloadCollection{}
err := json.NewDecoder(io.LimitReader(r.Body, MaxLength)).Decode(content)
if err != nil {
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(http.StatusBadRequest)
return
}
// Go through each payload and queue items individually to be posted to S3
for _, payload := range content.Payloads {
// let's create a job with the payload
work := Job{Payload: payload}
// Push the work onto the queue.
JobQueue - work
}
w.WriteHeader(http.StatusOK)
}
在web server初始化時(shí),我們創(chuàng)建一個(gè)Dispatcher,然后調(diào)用Run()函數(shù)創(chuàng)建一個(gè)worker池子,然后開(kāi)始監(jiān)聽(tīng)JobQueue中的job。
dispatcher := NewDispatcher(MaxWorker)
dispatcher.Run()
下面是dispatcher的實(shí)現(xiàn)代碼:
type Dispatcher struct {
// A pool of workers channels that are registered with the dispatcher
WorkerPool chan chan Job
}
func NewDispatcher(maxWorkers int) *Dispatcher {
pool := make(chan chan Job, maxWorkers)
return Dispatcher{WorkerPool: pool}
}
func (d *Dispatcher) Run() {
// starting n number of workers
for i := 0; i d.maxWorkers; i++ {
worker := NewWorker(d.pool)
worker.Start()
}
go d.dispatch()
}
func (d *Dispatcher) dispatch() {
for {
select {
case job := -JobQueue:
// a job request has been received
go func(job Job) {
// try to obtain a worker job channel that is available.
// this will block until a worker is idle
jobChannel := -d.WorkerPool
// dispatch the job to the worker job channel
jobChannel - job
}(job)
}
}
}
注意到,我們提供了初始化并加入到池子的worker的最大數(shù)量。因?yàn)檫@個(gè)工程我們利用了Amazon Elasticbeanstalk帶有的docker化的Go環(huán)境,所以我們常常會(huì)遵守12-factor方法論來(lái)配置我們的生成環(huán)境中的系統(tǒng),我們從環(huán)境變了讀取這些值。這種方式,我們控制worker的數(shù)量和JobQueue的大小,所以我們可以很快的改變這些值,而不需要重新部署集群。
var (
MaxWorker = os.Getenv("MAX_WORKERS")
MaxQueue = os.Getenv("MAX_QUEUE")
)
直接結(jié)果
我們部署了之后,立馬看到了延時(shí)降到微乎其微的數(shù)值,并未我們處理請(qǐng)求的能力提升很大。
Elastic Load Balancers完全啟動(dòng)后,我們看到ElasticBeanstalk 應(yīng)用服務(wù)于每分鐘1百萬(wàn)請(qǐng)求。通常情況下在上午時(shí)間有幾個(gè)小時(shí),流量峰值超過(guò)每分鐘一百萬(wàn)次。
我們一旦部署了新的代碼,服務(wù)器的數(shù)量從100臺(tái)大幅 下降到大約20臺(tái)。
我們合理配置了我們的集群和自動(dòng)均衡配置之后,我們可以把服務(wù)器的數(shù)量降至4x EC2 c4.Large實(shí)例,并且Elastic Auto-Scaling設(shè)置為如果CPU達(dá)到5分鐘的90%利用率,我們就會(huì)產(chǎn)生新的實(shí)例。
總結(jié)
在我的書(shū)中,簡(jiǎn)單總是獲勝。我們可以使用多隊(duì)列、后臺(tái)worker、復(fù)雜的部署設(shè)計(jì)一個(gè)復(fù)雜的系統(tǒng),但是我們決定利用Elasticbeanstalk 的auto-scaling的能力和Go語(yǔ)言開(kāi)箱即用的特性簡(jiǎn)化并發(fā)。
我們僅僅用了4臺(tái)機(jī)器,這并不是什么新鮮事了??赡芩鼈冞€不如我的MacBook能力強(qiáng)大,但是卻處理了每分鐘1百萬(wàn)的寫(xiě)入到S3的請(qǐng)求。
處理問(wèn)題有正確的工具。當(dāng)你的 Ruby on Rails 系統(tǒng)需要更強(qiáng)大的web handler時(shí),可以考慮下ruby生態(tài)系統(tǒng)之外的技術(shù),或許可以得到更簡(jiǎn)單但更強(qiáng)大的替代方案。
本節(jié)介紹golang elastic client參數(shù)詳解,主要包括:
elasticsearch連接地址
elasticsearch賬號(hào)/密碼
監(jiān)控檢查
失敗重試次數(shù)
gzip設(shè)置
我們使用Elasticsearch存儲(chǔ)的文檔數(shù)量接近50億(算上1份復(fù)制,接近
100億文檔),總共10個(gè)數(shù)據(jù)節(jié)點(diǎn)和2個(gè)元數(shù)據(jù)節(jié)點(diǎn)(48GB內(nèi)存,8核心CPU,ES使用內(nèi)存達(dá)到70%),每天的文檔增量大概是3000W條(速度
持續(xù)增加中)。目前來(lái)看,單個(gè)文檔的查詢效率基本處于實(shí)時(shí)狀態(tài);對(duì)于1到2周的數(shù)據(jù)的聚合統(tǒng)計(jì)操作也可以在10秒之內(nèi)返回結(jié)果。
但是,還有提升的空間:
1. 對(duì)于查詢單條數(shù)據(jù)的應(yīng)用場(chǎng)景來(lái)說(shuō),我們可以使用ES的路由機(jī)制,將同一索引內(nèi)的具有相同特征(比如具有相同的userid)的文檔全部存儲(chǔ)于一個(gè)節(jié)點(diǎn)上,這樣我們之后的查詢都可以直接定位到這個(gè)節(jié)點(diǎn)上,而不用將查詢廣播道所有的節(jié)點(diǎn)上;
2. 隨著數(shù)據(jù)節(jié)點(diǎn)的增加,適當(dāng)增加分片數(shù)量,提升系統(tǒng)的分布水平,也可以通過(guò)分而治之的方式優(yōu)化查詢性能;
個(gè)人以為Elasticsearch作為內(nèi)部存儲(chǔ)來(lái)說(shuō)還是不錯(cuò)的,效率也基本能夠滿足,在某些方面替代傳統(tǒng)DB也是可以的,前提是你的業(yè)務(wù)不對(duì)操作的事
性務(wù)有特殊要求;而權(quán)限管理也不用那么細(xì),因?yàn)镋S的權(quán)限這塊還不完善。由于我們對(duì)ES的應(yīng)用場(chǎng)景僅僅是在于對(duì)某段時(shí)間內(nèi)的數(shù)據(jù)聚合操作,沒(méi)有大量的單文
檔請(qǐng)求(比如通過(guò)userid來(lái)找到一個(gè)用戶的文檔,類(lèi)似于NoSQL的應(yīng)用場(chǎng)景),所以能否替代NoSQL還需要各位自己的測(cè)試。如果讓我選擇的話,我
會(huì)嘗試使用ES來(lái)替代傳統(tǒng)的NoSQL,因?yàn)樗臋M向擴(kuò)展機(jī)制太方便了。
你好
logstash 和filebeat 的關(guān)系:
1. logstash和filebeat都是可以作為日志采集的工具,目前日志采集的工具有很多種,如fluentd, flume, logstash,betas等等。甚至最后我決定用filebeat作為日志采集端工具的時(shí)候,還有人問(wèn)我為什么不用flume,logstash等采集工具。
2. logstash出現(xiàn)時(shí)間要比f(wàn)ilebeat早許多,隨著時(shí)間發(fā)展,logstash不僅僅是一個(gè)日志采集工具,它也是可以作為一個(gè)日志搜集工具,有豐富的input|filter|output插件可以使用。常用的ELK日志采集方案中,大部分的做法就是將所有節(jié)點(diǎn)的日志內(nèi)容上送到kafka消息隊(duì)列,然后使用logstash集群讀取消息隊(duì)列內(nèi)容,根據(jù)配置文件進(jìn)行過(guò)濾。上送到elasticsearch。logstash詳細(xì)信息可前往
3. logstash是使用Java編寫(xiě),插件是使用jruby編寫(xiě),對(duì)機(jī)器的資源要求會(huì)比較高,網(wǎng)上有一篇關(guān)于其性能測(cè)試的報(bào)告。之前自己也做過(guò)和filebeat的測(cè)試對(duì)比。在采集日志方面,對(duì)CPU,內(nèi)存上都要比前者高很多。LogStash::Inputs::Syslog 性能測(cè)試與優(yōu)化
4. filebeat也是elastic.公司開(kāi)發(fā)的,其官方的說(shuō)法是為了替代logstash-forward。采用go語(yǔ)言開(kāi)發(fā)。代碼開(kāi)源。elastic/beats filebeat是beats的一個(gè)文件采集工具,目前其官方基于libbeats平臺(tái)開(kāi)發(fā)的還有Packetbeat, Metricbeat, Winlogbeat。filebeat性能非常好,部署簡(jiǎn)單。是一個(gè)非常理想的文件采集工具。我自己采集工具也是基于beats源碼進(jìn)行的二次開(kāi)發(fā)。
head插件源碼在git上先安裝gityum install git獲取源碼elasticsearch-headhead 插件需要node.js 的支持先安裝node.js安裝nodejsyum install -y nodejs使用命令node -v 或者npm -v驗(yàn)證是否安裝成功使用npm安裝grunt命令npm install grunt --save-devnpm install修改配置文件 進(jìn)入elasticsearch-head-master 目錄,執(zhí)行命令vim Gruntfile.js文件增加hostname屬性,設(shè)置為npm run start執(zhí)行完成后就OK了
這里我使用elasticsearch官方給的go語(yǔ)言包(go-elasticsearch)
go-elasticsearch向前兼容,這意味著客戶端支持與更大或同等次要版本的 Elasticsearch 通信。Elasticsearch 語(yǔ)言客戶端僅向后兼容默認(rèn)發(fā)行版,不提供任何保證。
go.mod 文件中添加
或者
新建 es.go 存入 es目錄
新建 main.go
...
...
...
文章名稱:go語(yǔ)言elastic替代,go語(yǔ)言代替java
標(biāo)題來(lái)源:http://jinyejixie.com/article38/dssdcsp.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供建站公司、微信公眾號(hào)、面包屑導(dǎo)航、網(wǎng)站建設(shè)、網(wǎng)站改版、動(dòng)態(tài)網(wǎng)站
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)