前端接口容灾

5,719 阅读5分钟

文章顶部.png 小歪.png

开篇

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

还能咋样,白屏呗。

有没有不白屏的方案?

有啊,还挺简单的。

容我细细细细分析。

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

思考

存哪里?

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

选型对比

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

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

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

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

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

整体方案

整体流程图

image.png

CDN

先讲讲线上 CDN。

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

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

流程图

image (1).png

劫持逻辑

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

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

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
}

唯一标识

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

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

​
/**
 * 生成接口唯一键值
*/
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 < list.length; i++) {
      await list[i].click();
    }
  }
  await browser.close();
  ctx.logger.info('browser --finish', domain);
  return true;
};

效果

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

image (2).png

IndexDB

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

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

微信公众号

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