|
前端接口容灾
399 开篇你说,万一接口挂了会怎么样?还能咋样,白屏呗。有没有不白屏的方案?有啊,还挺简单的。容我细细细细分析。原因就是接口挂了,拿不到数据了。那把数据储存起来就可以解决问题。思考存哪里?第一时间反应浏览器本地存储,想起了四兄弟。选型对比特性cookielocalStoragesessionStorageindexDB数据生命周期服务器或者客户端都可以设置、有过期时间一直存在关闭页面就清空一直存在数据储存大小4KB5MB5MB动态,很大大于250MB与服务器通信每次都带在header中不带不带不带兼容性都支持都支持都支持IE不支持,其他主流都支持考虑到需要存储的数据量,5MB 一定不够的,所以选择了 IndexDB。考虑新用户或者长时间未访问老用户,会取不到缓存数据与陈旧的数据。因此准备上云,用阿里云存储,用 CDN 来保障。总结下:线上 CDN、线下 IndexDB。整体方案整体流程图CDN先讲讲线上 CDN。通常情况下可以让后端支撑,本质就是更新策略问题,这里不细说。我们讲讲另外一种方案,单独启个 Node 服务更新 CDN 数据。流程图劫持逻辑劫持所有接口,判断接口状态与缓存标识。从而进行更新数据、获取数据、缓存策略三种操作通过配置白名单来控制接口存与取axios.interceptors.response.use( async (resp) => { const { config } = resp const { url } = config // 是否有缓存tag,用于更新CDN数据。目前是定时服务在跑,访问页面带上tag if (this.hasCdnTag() & this.isWhiteApi(url)) { this.updateCDN(config, resp) } return resp; }, async (err) => { const { config } = err const { url } = config // 是否命中缓存策略 if (this.isWhiteApi(url) & this.useCache()) { return this.fetchCDN(config).then(res => { pushLog(`cdn缓存数据已命中,请处理`, SentryTypeEnum.error) return res }).catch(()=>{ pushLog(`cdn缓存数据未同步,请处理`, SentryTypeEnum.error) }) } } );缓存策略累计接口异常发生 maxCount 次,打开缓存开关,expiresSeconds 秒后关闭。缓存开关用避免网络波动导致命中缓存,设置了阀值。/** 缓存策略*/useCache = () => { if (this.expiresStamp > +new Date()) { const d = new Date(this.expiresStamp) console.warn(` --------------------------------------- --------------------------------------- 启用缓存中 关闭时间:${d.getHours()}{d.getMinutes()}{d.getSeconds()} --------------------------------------- --------------------------------------- `) return true } this.errorCount += 1 localStorage.setItem(CACHE_ERROR_COUNT_KEY, `${this.errorCount}`) if (this.errorCount > this.maxCount) { this.expiresStamp = +new Date() + this.expiresSeconds * 1000 this.errorCount = 0 localStorage.setItem(CACHE_EXPIRES_KEY, `${this.expiresStamp}`) localStorage.removeItem(CACHE_ERROR_COUNT_KEY) return true } return false}唯一标识根据 method、url、data 三者来标识接口,保证接口的唯一性带动态标识,譬如时间戳等可以手动过滤/** * 生成接口唯一键值*/generateCacheKey = (config) => { // 请求方式,参数,请求地址, const { method, url, data, params } = config; let rawData = '' if (method === 'get') { rawData = params } if (method === 'post') { rawData = JSON.parse(data) } // 返回拼接key return `${encodeURIComponent([method, url, stringify(rawData)].join('_'))}.json`;};更新数据/** * 更新cdn缓存数据*/updateCDN = (config, data) => { const fileName = this.generateCacheKey(config) const cdnUrl = `${this.prefix}/${fileName}` axios.post(`${this.nodeDomain}/cdn/update`, { cdnUrl, data })}Node定时任务构建定时任务,用 puppeteer 去访问、带上缓存标识,去更新 CDN 数据import schedule from 'node-schedule';const scheduleJob = {};export const xxxJob = (ctx) => { const { xxx } = ctx.config; ctx.logger.info(xxx, 'xxx'); const { key, url, rule } = xxx; if (scheduleJob[key]) { scheduleJob[key].cancel(); } scheduleJob[key] = schedule.scheduleJob(rule, async () => { ctx.logger.info(url, new Date()); await browserIndex(ctx, url); });};export const browserIndex = async (ctx, domain) => { ctx.logger.info('browser --start', domain); if (!domain) { ctx.logger.error('domain为空'); return false; } const browser = await puppeteer.launch({ args: [ '--use-gl=egl', '--disable-gpu', '--no-sandbox', '--disable-setuid-sandbox', ], executablePath: process.env.CHROMIUM_PATH, headless: true, timeout: 0, }); const page = await browser.newPage(); await page.goto(`${domain}?${URL_CACHE_KEY}`); await sleep(10000); // 访问首页所有查询接口 const list = await page.$('.po-tabs__item'); if (list?.length) { for (let i = 0; i { const { config } = resp const { url } = config // 是否有缓存tag,用于更新CDN数据。目前是定时服务在跑,访问页面带上tag if (this.hasCdnTag() & this.isWhiteApi(url)) { this.updateCDN(config, resp) } if(this.isIndexDBWhiteApi(url)){ this.updateIndexDB(config, resp) } return resp; }, async (err) => { const { config } = err const { url } = config // 是否命中缓存策略 if (this.isWhiteApi(url) & this.useCache()) { return this.fetchCDN(config).then(res => { pushLog(`cdn缓存数据已命中,请处理`, SentryTypeEnum.error) return res }).catch(()=>{ pushLog(`cdn缓存数据未同步,请处理`, SentryTypeEnum.error) if(this.isIndexDBWhiteApi(url)){ return this.fetchIndexDB(config).then(res => { pushLog(`IndexDB缓存数据已命中,请处理`, SentryTypeEnum.error) return res }).catch(()=>{ pushLog(`IndexDB缓存数据未同步,请处理`, SentryTypeEnum.error) }) } }) } } );总结总结下,优点包括不入侵业务代码,不影响现有业务,随上随用,尽可能避免前端纯白屏的场景,成本低。劣势包括使用局限,不适合对数据实效性比较高的业务场景,不支持 IE 浏览器。接口容灾我们也是刚弄不久,有许多细节与不足,欢迎沟通交流。接口容灾本意是预防发生接口服务挂了的场景,我们不会很被动。原来是P0的故障,能被它降低为 P2、P3,甚至在某些场景下都不会有用户反馈。,Base 杭州,一个富有激情和技术匠心精神的成长型团队。前端团队,一个年轻富有激情和创造力的前端团队。团队现有 80 余个前端小伙伴,平均年龄 27 岁,近 4 成是全栈工程师,妥妥的青年风暴团。成员构成既有来自于阿里、网易的“老”兵,也有浙大、中科大、杭电等校的应届新人。团队在日常的业务对接之外,还在物料体系、工程平台、搭建平台、智能化平台、性能体验、云端应用、数据分析、错误监控及可视化等方向进行技术探索和实战,推动并落地了一系列的内部技术产品,持续探索前端技术体系的新边界。如果你想改变一直被事折腾,希望开始能折腾事;如果你想改变一直被告诫需要多些想法,却无从破局;如果你想改变你有能力去做成那个结果,却不需要你;如果你想改变你想做成的事需要一个团队去支撑,但没你带人的位置;如果你想改变本来悟性不错,但总是有那一层窗户纸的模糊… 如果你相信相信的力量,相信平凡人能成就非凡事,相信能遇到更好的自己。如果你希望参与到随着业务腾飞的过程,亲手推动一个有着深入的业务理解、完善的技术体系、技术创造价值、影响力外溢的前端团队的成长历程,我觉得我们该聊聊。任何时间,等着你写点什么,发给 ZooTeam@cai-inc.com
|
|