前端接口容灾

开篇

你说,万一接口挂了会怎么样?

还能咋样,白屏呗。

有没有不白屏的方案?

有啊,还挺简单的。

容我细细细细分析。

原因就是接口挂了,拿不到数据了。那把数据储存起来就可以解决问题。

思考

存哪里?

第一时间反应浏览器本地存储,想起了四兄弟。

选型对比

特性 cookie localStorage sessionStorage indexDB
数据生命周期 服务器或者客户端都可以设置、有过期时间 一直存在 关闭页面就清空 一直存在
数据储存大小 4KB 5MB 5MB 动态,很大 大于250MB
与服务器通信 每次都带在header中 不带 不带 不带
兼容性 都支持 都支持 都支持 IE不支持,其他主流都支持

考虑到需要存储的数据量,5MB 一定不够的,所以选择了 IndexDB。

考虑新用户或者长时间未访问老用户,会取不到缓存数据与陈旧的数据。

因此准备上云,用阿里云存储,用 CDN 来保障。

总结下:线上 CDN、线下 IndexDB。

整体方案

整体流程图

CDN

先讲讲线上 CDN。

通常情况下可以让后端支撑,本质就是更新策略问题,这里不细说。

我们讲讲另外一种方案,单独启个 Node 服务更新 CDN 数据。

流程图

劫持逻辑

劫持所有接口,判断接口状态与缓存标识。从而进行更新数据、获取数据、缓存策略三种操作

通过配置白名单来控制接口存与取

kotlin 复制代码
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 秒后关闭。

缓存开关用避免网络波动导致命中缓存,设置了阀值。

javascript 复制代码
/*
* 缓存策略
*/
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
}

唯一标识

根据 methodurldata 三者来标识接口,保证接口的唯一性

带动态标识,譬如时间戳等可以手动过滤

ini 复制代码
​
/**
 * 生成接口唯一键值
*/
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`;
};

更新数据

javascript 复制代码
/**
 * 更新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 数据

javascript 复制代码
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;
};

效果

手动 block 整个 domain,整个页面正常展示

IndexDB

线上有 CDN 保证了,线下就轮到 IndexDB 了,基于业务简单的增删改查,选用 localForage 三方库足矣。

kotlin 复制代码
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)
        }
        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,甚至在某些场景下都不会有用户反馈。

推荐阅读

ARM架构下部署StarRocks3

Spring Validation实践及其实现原理

ThreadPoolExecutor杂谈

浅谈软件架构

你是否真的需要实现一个3D地图

招贤纳士

政采云技术团队(Zero),Base 杭州,一个富有激情和技术匠心精神的成长型团队。规模 500 人左右,在日常业务开发之外,还分别在云原生、区块链、人工智能、低代码平台、中间件、大数据、物料体系、工程平台、性能体验、可视化等领域进行技术探索和实践,推动并落地了一系列的内部技术产品,持续探索技术的新边界。此外,团队还纷纷投身社区建设,目前已经是 google flutter、scikit-learn、Apache Dubbo、Apache Rocketmq、Apache Pulsar、CNCF Dapr、Apache DolphinScheduler、alibaba Seata 等众多优秀开源社区的贡献者。

如果你想改变一直被事折腾,希望开始折腾事;如果你想改变一直被告诫需要多些想法,却无从破局;如果你想改变你有能力去做成那个结果,却不需要你;如果你想改变你想做成的事需要一个团队去支撑,但没你带人的位置;如果你想改变本来悟性不错,但总是有那一层窗户纸的模糊......如果你相信相信的力量,相信平凡人能成就非凡事,相信能遇到更好的自己。如果你希望参与到随着业务腾飞的过程,亲手推动一个有着深入的业务理解、完善的技术体系、技术创造价值、影响力外溢的技术团队的成长过程,我觉得我们该聊聊。任何时间,等着你写点什么,发给 zcy-tc@cai-inc.com

微信公众号

文章同步发布,政采云技术团队公众号,欢迎关注

相关推荐
whisperrr.19 分钟前
【JavaWeb12】数据交换与异步请求:JSON与Ajax的绝妙搭配是否塑造了Web的交互革命?
前端·ajax·json
烂蜻蜓1 小时前
前端已死?什么是前端
开发语言·前端·javascript·vue.js·uni-app
谢尔登2 小时前
Vue 和 React 的异同点
前端·vue.js·react.js
祈澈菇凉7 小时前
Webpack的基本功能有哪些
前端·javascript·vue.js
小纯洁w7 小时前
Webpack 的 require.context 和 Vite 的 import.meta.glob 的详细介绍和使用
前端·webpack·node.js
想睡好7 小时前
css文本属性
前端·css
qianmoQ7 小时前
第三章:组件开发实战 - 第五节 - Tailwind CSS 响应式导航栏实现
前端·css
zhoupenghui1688 小时前
golang时间相关函数总结
服务器·前端·golang·time
White graces8 小时前
正则表达式效验邮箱格式, 手机号格式, 密码长度
前端·spring boot·spring·正则表达式·java-ee·maven·intellij-idea
庸俗今天不摸鱼8 小时前
Canvas进阶-4、边界检测(流光,鼠标拖尾)
开发语言·前端·javascript·计算机外设