接口挂了拿不到数据怎么办?如何容灾?

大家好,我是若川。我持续组织了近一年的源码共读活动,感兴趣的可以 点此扫码加我微信 lxchuan12 参与,每周大家一起学习200行左右的源码,共同进步。同时极力推荐订阅我写的《学习源码整体架构系列》 包含20余篇源码文章。历史面试系列。另外:目前建有江西|湖南|湖北|河南籍前端群,可加我微信进群。公众号回复【相亲】关键词可以获取男生、女生的菜单~欢迎大家投稿"相亲"小栏目,或者原创文章。

a8b0a811ef46c609bb8d3f37491b0422.png

开篇

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

还能咋样,白屏呗。

有没有不白屏的方案?

有啊,还挺简单的。

容我细细细细分析。

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

思考

存哪里?

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

选型对比

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

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

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

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

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

整体方案

整体流程图

28b65197e2208206c05866390b7915d8.jpeg

CDN

先讲讲线上 CDN。

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

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

流程图

09e436f39e14af2fe0dd0d7bfed7611e.jpeg

劫持逻辑

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

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

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,整个页面正常展示

87cb3e2707608c61115006e5c9d18739.jpeg

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,甚至在某些场景下都不会有用户反馈。

<think>嗯,用户想了解Excel中的VLOOKUP函数的用法和参数含义。首先,我需要回忆一下VLOOKUP的基本结构。VLOOKUP是垂直查找函数,用于在表格的首列查找指定的值,然后返回该行中指定列的数据。它的四个参数分别是查找值、表格范围、列索引号和匹配模式。 接下来,我得确认每个参数的具体作用。第一个参数是lookup_value,也就是用户要查找的值,比如产品ID或者姓名。第二个参数是table_array,即查找的范围,这里要注意的是,查找值必须位于这个范围的第一列,否则函数无法正确工作。第三个参数是col_index_num,也就是用户想要返回的数据在表格范围中的第几列,这个列数是从表格范围的第一列开始算起的,不是整个工作表的列。第四个参数是range_lookup,决定是精确匹配还是近似匹配,通常用FALSE或0来指定精确匹配,避免错误。 然后,用户可能需要一个具体的例子来理解如何使用。比如,假设有一个产品价格表,用户想根据产品ID查找对应的价格。这时候,VLOOKUP就可以在ID列查找,然后返回价格列的数据。同时,要提醒用户注意表格范围最好使用绝对引用,比如$A$2:$C$100,这样在拖动公式时范围不会变化。 可能遇到的问题也需要考虑。比如,如果查找值不在表格的第一列,或者列索引号超过了表格的列数,函数会返回错误。此外,数据格式不一致也可能导致问题,比如查找值是数字,而表格中是文本格式的数字,这时候需要统一格式。还有,当出现#N/A错误时,可能是查找值不存在,或者匹配模式设置不正确,需要检查参数是否正确。 用户可能还会问到如何避免常见的错误,比如部分数据匹配出错的情况。这时候需要检查是否有多余的空格,或者数据格式不一致,或者表格范围没有锁定导致拖动时偏移。此外,XLOOKUP作为VLOOKUP的升级版,可能在某些情况下更方便,比如可以向左查找或者返回多列数据,这也是可以提及的内。 最后,整理步骤,从基础语法到参数说明,再到具体案例,逐步引导用户理解如何使用VLOOKUP函数。同时,提供常见错误和解决方法,帮助用户在实际应用中避免问题。如果有必要,还可以比较VLOOKUP和其他查找函数的区别,比如HLOOKUP或XLOOKUP,但根据用户的问题,主要集中在VLOOKUP本身即可。</think>### Excel VLOOKUP函数详解 #### 一、基础语法与参数说明 VLOOKUP函数的语法为: $$=VLOOKUP(lookup\_value, table\_array, col\_index\_num, [range\_lookup])$$ 包含4个参数: 1. **lookup_value**(必填):要查找的值(如单元格引用或具体值) 2. **table_array**(必填):包含数据的表格范围(必须包含查找列和返回列) 3. **col_index_num**(必填):返回值所在列的序号(从table_array第一列开始计数) 4. **range_lookup**(可选):匹配类型 - `TRUE`/`1`:近似匹配(默认值,需数据升序排列) - `FALSE`/`0`:精确匹配(常用选项) [^1][^2] #### 二、使用步骤演示(工资表查询案例) 假设需要根据员工编号查询工资: 1. 建立查询单元格(如`B12`) 2. 输入公式: ```excel =VLOOKUP(A12, $A$2:$D$100, 4, 0) ``` - `A12`:待查询的员工编号 - `$A$2:$D$100`:锁定数据区域(绝对引用) - `4`:返回第4列(工资列) - `0`:精确匹配 [^2][^3] #### 三、常见错误与解决方法 | 错误现象 | 原因 | 解决方案 | |---------|------|---------| | #N/A | 查找值不存在 | 检查数据源或改用`IFERROR`错 | | #REF! | 列序号超出范围 | 确认col_index_num ≤ 表格列数 | | 部分匹配失败 | 数据格式不一致 | 统一数值/文本格式 | | 结果错位 | 表格未锁定 | 使用`$`符号固定区域引用 | [^3][^4] #### 四、进阶技巧 1. **多条件查询**: 使用辅助列合并多个条件字段 ```excel =VLOOKUP(A2&B2, $D$2:$F$100, 3, 0) ``` 2. **通配符匹配**: `"*"`匹配任意字符,`"?"`匹配单个字符 ```excel =VLOOKUP("张*", $A$2:$C$100, 3, 0) ``` 3. **跨表查询**: 引用其他工作表数据 ```excel =VLOOKUP(A2, Sheet2!$A$2:$D$100, 4, 0) ``` [^1][^4]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值