<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Node開(kāi)發(fā)實(shí)踐總結(jié)-定時(shí)腳本的設(shè)計(jì)與實(shí)現(xiàn)

          共 3858字,需瀏覽 8分鐘

           ·

          2021-01-05 10:39


          作者:騰訊IMWeb前端團(tuán)隊(duì)

          原文地址:https://mp.weixin.qq.com/s/a_kIRvJUuICtw0CG7EFAtg

          前言

          作為Node語(yǔ)言的初學(xué)者去實(shí)踐后端開(kāi)發(fā)時(shí),不僅僅有見(jiàn)獵心喜,也有一些忐忑,好在大家都很open,給予了很多建議和分享,到目前為止,也成功建立了三個(gè)基于Node.js + TypeScript + IMServer?1?的工程,也是時(shí)候?qū)⒆约鹤罱膶W(xué)習(xí)過(guò)程進(jìn)行總結(jié),下面就以一個(gè)小小的開(kāi)發(fā)任務(wù)為載體分享下我的成長(zhǎng)過(guò)程。

          需求

          在完成Node工程的搭建之后,我接受到第一個(gè)Node后臺(tái)開(kāi)發(fā)任務(wù):定時(shí)將企業(yè)微信的組織架構(gòu)信息拉取到業(yè)務(wù)數(shù)據(jù)庫(kù)系統(tǒng)中,并且提供手機(jī)號(hào)查詢用戶查詢接口。一開(kāi)始對(duì)這個(gè)任務(wù)還是比較樂(lè)觀的,信心滿滿的去開(kāi)發(fā)了。

          初步方案

          經(jīng)過(guò)方案設(shè)計(jì)之后形成了上述的方案:

          1. 在服務(wù)器部署初始化時(shí)(init.ts初始啟動(dòng)文件中)啟動(dòng)node-schedule的定時(shí)任務(wù),讀取數(shù)據(jù)庫(kù)中的企業(yè)微信的企業(yè)配置,然后并行啟動(dòng)若干企業(yè)的組織架構(gòu)更新進(jìn)程。

          2. 企業(yè)微信提供了獲取部門成員的詳情,因此只需并行更新每個(gè)部門的信息,并且寫入mysql數(shù)據(jù)庫(kù)中。


          3. 當(dāng)查詢接口到達(dá)服務(wù)器后,首先從數(shù)據(jù)庫(kù)中查詢?cè)撌謾C(jī)號(hào)對(duì)應(yīng)的成員,若不存在則從企業(yè)微信側(cè)調(diào)用手機(jī)號(hào)獲取userid API,然后通過(guò)獲取用戶信息API獲取最新的用戶信息,避免定時(shí)更新帶來(lái)的更新時(shí)間gap;若存在則直接返回?cái)?shù)據(jù)庫(kù)中的信息。


          開(kāi)發(fā)過(guò)程中的踩雷

          整體業(yè)務(wù)邏輯并不復(fù)雜,調(diào)試和部署的過(guò)程中遇到許多問(wèn)題,這里給大家一一列舉下:

          1. 訪問(wèn)頻率受限
            企業(yè)微信官方規(guī)定同一時(shí)間對(duì)同一份資源的請(qǐng)求數(shù)不可超過(guò)一定數(shù)值(60),由于部門詳情的請(qǐng)求接口采用的并行模式,因此超過(guò)了閾值,測(cè)試過(guò)程中被官方封禁了IP。

          1. 過(guò)多進(jìn)程導(dǎo)致SQL慢查詢
            沒(méi)有考慮多地部署(3地 * 5服務(wù)器 * 8 worker)導(dǎo)致同時(shí)存在了120個(gè)更新進(jìn)程,進(jìn)而導(dǎo)致數(shù)據(jù)庫(kù)mysql的讀寫混亂,也消耗了大量性能,導(dǎo)致數(shù)據(jù)庫(kù)讀寫壓力比較大時(shí),出現(xiàn)了部分慢查詢的情況

          1. 無(wú)效手機(jī)號(hào)不可調(diào)用企業(yè)微信api
            企業(yè)微信對(duì)手機(jī)號(hào)獲取userid的接口,具有以下限制:當(dāng)查詢中出現(xiàn)一定數(shù)量的無(wú)效手機(jī)號(hào)時(shí),會(huì)觸發(fā)企業(yè)微信官方IP封禁。但是業(yè)務(wù)系統(tǒng)中存在大量離職后的無(wú)效手機(jī)號(hào),因此當(dāng)檢查到數(shù)據(jù)庫(kù)中不存在時(shí),頻繁調(diào)用上述接口則會(huì)觸發(fā)封禁。

          2. 數(shù)據(jù)庫(kù)讀寫沖突
            由于存在多臺(tái)服務(wù)器同時(shí)讀寫數(shù)據(jù)庫(kù),導(dǎo)致數(shù)據(jù)庫(kù)出現(xiàn)了部分重復(fù)、缺少的情況。

          3. 網(wǎng)絡(luò)環(huán)境導(dǎo)致讀寫鎖的平衡性失效,產(chǎn)生衍生問(wèn)題
            為了優(yōu)化上述部分,引入的任務(wù)讀寫鎖,保證單一進(jìn)程更新。但是未考慮各地服務(wù)網(wǎng)絡(luò)情況,導(dǎo)致內(nèi)網(wǎng)服務(wù)器一直持有讀寫鎖,失去了均衡負(fù)載的設(shè)計(jì)有效性。也導(dǎo)致在配置預(yù)上線環(huán)境時(shí),預(yù)上線環(huán)境由于網(wǎng)絡(luò)環(huán)境良好一直持有讀寫鎖,進(jìn)而影響線上的實(shí)時(shí)數(shù)據(jù)。

          4. 未考慮失敗情況進(jìn)行報(bào)警和恢復(fù)

          深度優(yōu)化設(shè)計(jì)

          下面介紹下如何解決這些問(wèn)題和思路和方案。

          1、訪問(wèn)頻率受限

          這里針對(duì)“部門成員信息API“的并行請(qǐng)求,改造成基于有效頻率值的串行發(fā)送機(jī)制,設(shè)計(jì)成10個(gè)/每秒的調(diào)用速度。

          2、過(guò)多進(jìn)程導(dǎo)致SQL慢查詢

          這個(gè)解決方案比較明確,就是減少啟動(dòng)定時(shí)任務(wù)的進(jìn)程數(shù)。


          由于后端服務(wù)一般分為測(cè)試環(huán)境、預(yù)上線環(huán)境、正式環(huán)境,不同的環(huán)境中是否需要啟動(dòng)各個(gè)定時(shí)器腳本可以通過(guò)部署時(shí)(以SKTE為例),設(shè)置環(huán)境變量“SCHEDULE_ENV”來(lái)管理。


          每臺(tái)服務(wù)器會(huì)啟動(dòng)8個(gè)worker進(jìn)程,每個(gè)worker使用process.env.IMSERVER_WORKER_ID變量進(jìn)行標(biāo)識(shí),因此可以設(shè)計(jì)只有“worker1”進(jìn)程來(lái)進(jìn)行定時(shí)任務(wù)的啟動(dòng);

          3、無(wú)效手機(jī)號(hào)不可調(diào)用企業(yè)微信api

          這個(gè)是在技術(shù)調(diào)研中沒(méi)能發(fā)現(xiàn)的情況,發(fā)現(xiàn)前期技術(shù)調(diào)研的工作疏忽。

          首先是業(yè)務(wù)調(diào)用方是無(wú)法得知手機(jī)號(hào)是否有效,且也不應(yīng)該去關(guān)心這個(gè)限制,因此原先為了解決部分新紀(jì)錄更新不及時(shí)的問(wèn)題,而引入的實(shí)時(shí)查詢機(jī)制是不合理的。

          實(shí)時(shí)查詢機(jī)制:“對(duì)于數(shù)據(jù)庫(kù)中不存在的手機(jī)號(hào),通過(guò)企業(yè)微信官方api進(jìn)行實(shí)時(shí)查詢來(lái)返回結(jié)果”

          因此移除了這個(gè)機(jī)制,并且提供了一個(gè)基于企業(yè)微信官方API的實(shí)時(shí)查詢接口,每次業(yè)務(wù)方調(diào)用時(shí),也將結(jié)果同步更新到組織架構(gòu)中。

          4、數(shù)據(jù)庫(kù)讀寫沖突

          引入redis任務(wù)鎖機(jī)制,保證同一時(shí)間內(nèi)只有一個(gè)進(jìn)程能夠進(jìn)行數(shù)據(jù)庫(kù)更新操作。

          其次是企業(yè)之間的更新采用并行機(jī)制,由于相互之間是互不沖突的,因此不會(huì)引起同一條記錄的讀寫沖突,也可以提升其更新速度。

          5、網(wǎng)絡(luò)環(huán)境導(dǎo)致讀寫鎖的平衡性失效,產(chǎn)生衍生問(wèn)題

          在最初的設(shè)計(jì)中,我希望服務(wù)器之間能夠根據(jù)自身的負(fù)載情況來(lái)進(jìn)行公平競(jìng)爭(zhēng)任務(wù)鎖,但是實(shí)際情況是由于多地部署,其中穩(wěn)定的內(nèi)網(wǎng)環(huán)境可以一直優(yōu)先獲取到任務(wù)鎖,就是沒(méi)有所謂的公平性了。

          特別是當(dāng)壓測(cè)需要部署預(yù)上線環(huán)境時(shí),如果沒(méi)有設(shè)置只讀db賬號(hào)并且沒(méi)有設(shè)置啟動(dòng)定時(shí)任務(wù)環(huán)境變量,這兩個(gè)失誤會(huì)導(dǎo)致某一次的組織架構(gòu)更新邏輯調(diào)整的代碼更新到線上時(shí),線上一直是舊的邏輯在執(zhí)行,經(jīng)過(guò)一系列排查我們發(fā)現(xiàn)預(yù)上線環(huán)境一直獲取了讀寫鎖,使用舊的邏輯更新數(shù)據(jù)庫(kù)。

          因此增加環(huán)境變量來(lái)控制定時(shí)任務(wù)啟動(dòng)、對(duì)于壓測(cè)的環(huán)境的中的數(shù)據(jù)庫(kù)權(quán)限進(jìn)行了區(qū)分,增加了只讀模式。

          6、報(bào)警和錯(cuò)誤恢復(fù)

          這里有一點(diǎn)前端思維定勢(shì)的影響了,這一部分是同樣重要的。

          報(bào)警方面

          則是接入IMLog的Node SDK,通過(guò) Kibana 和 Grafana 的系統(tǒng)配置,可以有效監(jiān)控組織架構(gòu)的更新情況。


          錯(cuò)誤恢復(fù)方面

          這里的錯(cuò)誤主要是發(fā)生在企業(yè)微信API的access_token過(guò)期的情況,常發(fā)生于以下兩種情況:

          1. 企業(yè)微信官方主動(dòng)使access_token過(guò)期

          2. 在組織架構(gòu)更新過(guò)程中,access_token剛好失效情況,也就是http傳輸?shù)狡髽I(yè)微信剛好失效的情況

          以上的情況是無(wú)法避免的。這里使用中間件對(duì)node.fetch進(jìn)行封裝,增加對(duì)response的返回值的校驗(yàn),如果企業(yè)微信api的返回值是?WX_CODE.INVALIDE_TOKEN?則進(jìn)行預(yù)警和重置accessToken。

          export default (app) => {
          ?const { utils: { imlogHelper } } = app;
          ?const wrapperLogFetch = (originFetch, {
          ? ?traceId,
          ? ?header,
          ? ?client_ip,
          ?}) => async (...args) => {
          ? ?const res = await originFetch(...args);
          ? ?if (res.errcode === WX_CODE.INVALIDE_TOKEN) {
          ? ? ?// 進(jìn)行更新邏輯
          ? ? ?wxService.clearAllRedisKey();
          ? ? ?imlogHelper({
          ? ? ? ?cmd: url,
          ? ? ? ?message: 'accessToken_update_warning',
          ? ? ? ?body: JSON.stringify(res),
          ? ? ? ?trace_id: traceId,
          ? ? ? ?retcode,
          ? ? ? ?headers: header,
          ? ? ?});
          ? ?}
          ? ?return res;
          ?};
          ? ?// 覆蓋context.fetch方法
          ?return async (ctx, next) => {
          ? ?if (!ctx.logFetch) {
          ? ? ?const originFetch = ctx.fetch;
          ? ? ?const { traceId, ip: client_ip } = ctx.request;
          ? ? ?const header = JSON.stringify(ctx.request.header);
          ? ? ?const logFetch = wrapperLogFetch(originFetch, {
          ? ? ? ?traceId,
          ? ? ? ?header,
          ? ? ? ?client_ip,
          ? ? ?});
          ? ? ?ctx.logFetch = logFetch;
          ? ?}
          ? ?if (ctx.fetch !== ctx.logFetch) {
          ? ? ?ctx.fetch = ctx.logFetch;
          ? ?}
          ? ?await next();
          ?};
          }

          總結(jié)

          經(jīng)過(guò)重新設(shè)計(jì)和驗(yàn)證后形成以上的設(shè)計(jì)方案,具有以下優(yōu)化點(diǎn):

          1. 首先通過(guò)基于redis setnx實(shí)現(xiàn)的任務(wù)鎖,來(lái)實(shí)現(xiàn)同一時(shí)間單進(jìn)程更新數(shù)據(jù)庫(kù);

          2. 通過(guò)部署時(shí)設(shè)置定時(shí)任務(wù)啟動(dòng)環(huán)境變量和數(shù)據(jù)庫(kù)讀寫賬號(hào)設(shè)置,來(lái)保證不同環(huán)境的分離;

          3. 通過(guò)企業(yè)并行,部門數(shù)據(jù)拉取接口串行的模式,最大化性能和避免API調(diào)用封禁;

          4. 完善錯(cuò)誤恢復(fù)機(jī)制和報(bào)警,實(shí)時(shí)查看運(yùn)行狀況。

          ??愛(ài)心三連擊

          1.看到這里了就點(diǎn)個(gè)在看支持下吧,你的點(diǎn)贊在看是我創(chuàng)作的動(dòng)力。

          2.關(guān)注公眾號(hào)程序員成長(zhǎng)指北,回復(fù)「1」加入高級(jí)前端交流群!「在這里有好多 前端?開(kāi)發(fā)者,會(huì)討論?前端 Node 知識(shí),互相學(xué)習(xí)」!

          3.也可添加微信【ikoala520】,一起成長(zhǎng)。

          “在看轉(zhuǎn)發(fā)”是最大的支持


          瀏覽 14
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲人操逼视频 | 人妻日韩精品中文字幕 | 青青啪啪啪 | 6080yy精品久久久久久 | 美女靠逼在线观看 |