我们一起聊聊前端接口容灾

开篇【我们一起聊聊前端接口容灾】你说,万一接口挂了会怎么样?
还能咋样,白屏呗 。
有没有不白屏的方案?
有?。?还挺简单的 。
容我细细细细分析 。
原因就是接口挂了,拿不到数据了 。那把数据储存起来就可以解决问题 。
思考存哪里?第一时间反应浏览器本地存储,想起了四兄弟 。
选型对比 特性
cookie
localStorage
sessionStorage
indexDB
数据生命周期
服务器或者客户端都可以设置、有过期时间
一直存在
关闭页面就清空
一直存在
数据储存大小
4KB
5MB
5MB
动态,很大
大于250MB
与服务器通信
每次都带在header中
不带
不带
不带
兼容性
都支持
都支持
都支持
IE不支持,其他主流都支持
考虑到需要存储的数据量 , 5MB 一定不够的 , 所以选择了 IndexDB 。
考虑新用户或者长时间未访问老用户 , 会取不到缓存数据与陈旧的数据 。
因此准备上云 , 用阿里云存储 , 用 CDN 来保障 。
总结下:线上 CDN、线下 IndexDB 。
整体方案整体流程图

我们一起聊聊前端接口容灾

文章插图
图片
CDN先讲讲线上 CDN 。
通常情况下可以让后端支撑,本质就是更新策略问题,这里不细说 。
我们讲讲另外一种方案,单独启个 Node 服务更新 CDN 数据 。
流程图
我们一起聊聊前端接口容灾

文章插图
图片
劫持逻辑劫持所有接口,判断接口状态与缓存标识 。从而进行更新数据、获取数据、缓存策略三种操作
通过配置白名单来控制接口存与取
axIOS.interceptors.response.use(async (resp) => {const { config } = respconst { url } = config// 是否有缓存tag,用于更新CDN数据 。目前是定时服务在跑,访问页面带上tagif (this.hasCdnTag() && this.isWhiteApi(url)) {this.updateCDN(config, resp)}return resp;},async (err) => {const { config } = errconst { 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 += 1localStorage.setItem(CACHE_ERROR_COUNT_KEY, `${this.errorCount}`)if (this.errorCount > this.maxCount) {this.expiresStamp = +new Date() + this.expiresSeconds * 1000this.errorCount = 0localStorage.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 = https://www.isolves.com/it/cxkf/qd/2023-11-29/''if (method === 'get') {rawData = params}if (method === 'post') {rawData = JSON.parse(data)}// 返回拼接keyreturn `${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 < list.length; i++) {await list[i].click();}}await browser.close();ctx.logger.info('browser --finish', domain);return true;};


推荐阅读