怎樣定位前端線上問題,一直以來,都是很頭疼的問題,因為它發生於用戶的一系列操作之后。錯誤的原因可能源於機型,網絡環境,接口請求,復雜的操作行為等等,在我們想要去解決的時候很難復現出來,自然也就無法解決。 當然,這些問題並非不能克服,讓我們來一起看看如何去監控並定位線上的問題吧。
這是搭建前端監控系統的第五章,主要是介紹如何處理日志高並發上傳的情況,跟着我一步步做,你也能搭建出一個屬於自己的前端監控系統。
如果感覺有幫助,或者有興趣,請關注 or Star Me 。
============================================================================
============================================================================
隨着監控日志搜集的內容越來越多,終於有一天,由於公司的一波推文,導致了日志的瞬間流量達到歷史新高,以至於mysql的連接數過多,系統崩潰。 當然,作為日志上傳的服務器,這個是必然會發生的情況,只是早晚的問題。 既然出現了並發問題,那么我們就着手來處理吧。日志上傳如何緩解高並發的情況呢?我們分為三個小點來處理。
-
增加日志上傳的時間間隔
正如我們所知,日志上傳的時間間隔越長,用戶在這個間隔內離開的幾率就會越大,日志的漏傳量就會增加,然后會導致日志的准確度降低。因為我們的探針是安插在瀏覽器內的,用戶隨時都有可能關掉,所以,理論上講間隔越短越好,但這並不現實。所以這個需要在服務器的承受能力和日志的准確率之間做個權衡。由具體情況而定
-
移除探針代碼里冗余的參數,縮短參數名字的長度
另外一點,每台服務器的硬盤有限,帶寬有限,如果參數名字太長,參數內容冗余,對服務器的硬盤和帶寬都是一種極大的浪費。雖然每條日志都不起眼,但是日志起量了以后,就是會是一筆非常龐大的開銷。
-
Nodejs + RabbitMq 搭建消息隊列,緩解瞬間並發量
對於一個前端來說,要把消息隊列搭建起來還確實費了一番周折。
1) ubantu16 安裝RabbitMQ服務軟件包,很多教程都要求安裝erlang, 但是更新apt以后,直接執行安裝命令,會自動安裝erlang的核心組件的。(erlang始終無法成功安裝,真心累。)
$ apt-get update $ apt-get install rabbitmq-server // 安裝
$ rabbitmq-plugins enable rabbitmq_management // 啟動插件,瀏覽器才能訪問
正常情況下是直接成功的,直接訪問ip端口號就可以打開了 http://IP:15672, 如下圖:
2)現在我們需要一個有效的登錄名和密碼,執行如下命令
$ rabbitmqctl add_user username password // 設置用戶名密碼 $ rabbitmqctl set_user_tags username administrator // 設置為管理員身份 $ rabbitmqctl set_permissions -p / username ".*" ".*" ".*" //為用戶設置讀寫等權限
OK, 現在我們登錄進來就是這樣的界面,如此消息隊列服務我們算是搭建完成了。
3)消息服務啟動了,那么如何存消息,如何取消息呢?如下圖所示:
我能夠接觸到的關於消息隊列的應用場景實在有限,所以不能介紹更復雜的內容,大致的思維邏輯如上圖1:有消息進來,先存入消息隊列里,另一端再從隊列去取出來,完成接下來的工作。從代碼的角度來看如上圖2:就是一個生產者和消費者的模式,生產者不停的向消息隊列里生產消息,消費者在有需要的時候,從消息隊列里取消息, 一旦完成消費,隊列里便移除這個消息。消息的生產者和消費者互相沒有感知,生產者產生過剩的消息都存放在消息隊列里,由消費者慢慢消耗。以此來削峰填谷,達到處理高並發的目的。當然這都是我的淺顯理解,但是也足以滿足目前日志上傳的需求了。
OK、理論說完了,具體如何實現呢?
let amqp = require('amqplib'); module.exports = class RabbitMQ { constructor() { this.hosts = ["amqp://localhost"]; this.index = 0; this.length = this.hosts.length; this.open = amqp.connect(this.hosts[this.index]); } // 消息生產者 sendQueueMsg(queueName, msg, errCallBack) { let self = this; self.open .then(function (conn) { return conn.createChannel(); }) .then(function (channel) { return channel.assertQueue(queueName).then(function (ok) { return channel.sendToQueue(queueName, new Buffer.from(msg), { persistent: true }); }) .then(function (data) { if (data) { errCallBack && errCallBack("success"); channel.close(); } }) .catch(function () { setTimeout(() => { if (channel) { channel.close(); } }, 500) }); }) .catch(function () { // 這里嘗試備用連接,我就一個,所以就處理了 }); }
// 消息消費者 receiveQueueMsg(queueName, receiveCallBack, errCallBack) { let self = this; self.open.then(function (conn) { return conn.createChannel(); }).then(function (channel) { return channel.assertQueue(queueName).then(function (ok) { return channel.consume(queueName, function (msg) { if (msg !== null) { let data = msg.content.toString(); channel.ack(msg); receiveCallBack && receiveCallBack(data); } }).finally(function () { }); }) }) .catch(function (e) { errCallBack(e) }); } }
消息隊列測試:每隔5秒發送一條消息,每隔5秒取出一條消息,成功
var mq = new RabbitMQ() setInterval(function () { mq.sendQueueMsg("queue1", "這是一個隊列消息", function (err) { console.log(err) }) }, 5000) setInterval(function () { mq.receiveQueueMsg("queue1", function (msg) { console.log(msg) }, function (error) { console.log(error) }) }, 5000)
RabbitMq消息隊列使用中遇到的坑:
① var mq = new RabbitMQ() 多次創建RabbitMQ對象,導致connections, channels, memory 暴增,服務器很快掛掉
② 生產者的channel忘記close, 導致channel太多,服務器超負荷
③ 消費者的channel被close掉了,永遠只能接收到一條消息,消息隊列很快爆掉
最后是消息隊列運行的狀態:
OK、經過了這么一番處理,我們的日志上傳應該能夠承受住一定量的並發了,讓我們拭目以待吧。