解决redis缓存穿透设计:加锁控制(nodejs版本)

46 阅读1分钟

为了防止多热点,消耗大的内容被频繁请求,会大量占用服务器资源。导致服务器响应慢或者卡死的问题。控制访问数量解决此类问题。

在获取时先判断是否存在正在请求相同的缓存。存在(拿不到锁)则进行等待,定时轮询(次数可以限制次数和时长直接进行返回)。不存在(拿到锁)则进行查询,查询结束解锁(需捕获异常防止卡死)。

附上代码:

const redis = require('redis')
// 设置集合锁
global.REDIS_LOCKS = {}
// 允许的并发数量,同个请求允许有2个并发, 可以自行配置
global.REDIS_LOCKS_MAX = 2

class Cache {
  /**
   * 构造缓存实例
   * @param {Integer} db 0-15 缓存库名
   */
  constructor (db) {
    this.options = { url: `${global.REDIS_CONFIG}/${db}`, retry_strategy: retryStrategy }
  }
  
  /**
   * 试读取内容
   * @param {Array} args Key值,可以是某个函数的参数数组arguments,也可以是字符串
   * @param {Function} callback 缓存中不存在的内容,则执行某个过程,将返回值存入缓存
   * @returns {Object} callback的返回值
   */
  async tryGetContent (args, callback, expire = defaultExpireTime) {
    let key = this._args2Key(args)
    let value = await this.getContent(key)
    if (value) {
      return JSON.parse(value)
    } else {
      // 加锁
      if (REDIS_LOCKS[key] && (REDIS_LOCKS[key] >= REDIS_LOCKS_MAX)) {
        // 等待1秒后再查询
        await new Promise(resolve => setTimeout(resolve, 1000))
        return await this.tryGetContent(args, callback, expire)
      } else {
        REDIS_LOCKS[key] = REDIS_LOCKS[key] ? REDIS_LOCKS[key] + 1 : 1
      }
      let result
      // 捕获错误解锁,防止异常无法解锁
      try {
        result = await callback()
        if (result || result === 0) {
          await this.setContent(key, JSON.stringify(result), expire)
        }
      } catch (e) {
        console.log('tryGetContent', e)
      }
      // 解锁
      REDIS_LOCKS[key] = REDIS_LOCKS[key] - 1
      //  else {
      //   console.error('获取内容不存在,id为:', key)
      // }
      return result
    }
  }
}

module.exports = Cache