简单地实现NodeJS内存缓存过期

在一些业务中,要从redis获取一个超大的字符串。为了避免频繁从redis获取数据,需要在内存中实现对数据的缓存。

实现

1. 从内存缓存读取数据,没有发现数据,则从redis获取数据并设置过期时间。

2. 从内存缓存读取数据,发现数据而没有过期,直接返回。

3. 从内存缓存读取数据,发现数据过期,重新从redis获取数据并设置过期时间。注意:需要比较从redis的数据时候和缓存数据一致,不一致才替换缓存数据,否则会出现2份内容一样的数据。即之前拿过的数据和内存缓存的数据是内容一样的,由于redis的数据没有变化,但是内存缓存的数据过期,重新创建一个一模一样的数据。

4. 定时检查过期数据,发现数据过期,直接从内存缓存中删除。防止缓存数据一直在内存中。

5. 从内存缓存读取数据,需要加锁,防止异步读取redis数据时,Nodejs再次切换函数,再次发现数据不存在或者数据过期,再次异步读取redis数据。避免频繁读取redis数据。这里需要使用async-lock,保证读取redis是按队列一个个读取,即判断数据不存在或者数据过期,需要之前读取redis并设置到内存缓存后,才能去判断数据不存在或者数据过期的情况。

/**
 * 简单地通过计数来实现共享缓存对象
 */
import { DeviceValidator } from './device_validator'
import * as AsyncLock from 'async-lock'
import { isDeepStrictEqual } from 'util';

interface CacheInfo {
  data: any
  expire: number
}
const cache = new Map<string, CacheInfo>();
// 定时删除过期的键。
setInterval(() => {
  let count = 0;
  const now = Date.now()
  for (const [key, info] of cache) {
    // 发现过期要清理
    if (info.expire <= now) {
      cache.delete(key);
      count++;
      // 每次只删除20个键,减少出现undefined的情况。
      if (count >= 20) {
        break;
      }
    }
  }
}, 5000);

function getOrCreateCacheInfo(key: string, expire: number) {
  let cacheInfo = cache.get(key);
  if (!cacheInfo) {
    cacheInfo = { data: undefined, expire };
    cache.set(key, cacheInfo);
  }
  return cacheInfo
}

export function deleteCache(key: string) {
  cache.delete(key);
}

export function updateCacheExpire(key: string, expire: number) {
  const cacheInfo = cache.get(key);
  if (cacheInfo) {
    cacheInfo.expire = expire;
  }
}

const lock = new AsyncLock({maxPending: 100000000})

export function devtypeCacheKey(owner: string, devtype: string) {
  return `${owner}.${devtype}`
}

export async function getDevtypeCache(owner: string, devtype: string) {
  const key = devtypeCacheKey(owner, devtype);
  // 虽然JS是单线程,但是await后,getDevtypeCache会调用多次。这里需要锁住代码
  return lock.acquire<any>(key, async () => {
    const now = Date.now();
    const expire = now + 30000;
    const cacheInfo = getOrCreateCacheInfo(key, expire);
    if (cacheInfo.data != undefined && cacheInfo.expire > now) {
      // 这个data指向的引用没有变化,后续拿的对象都是指向同一个对象。
      return cacheInfo.data;
    }
    // 设备类型变化不大,不使用redis的订阅发布来监听设备类型的变化。
    const devTypeInfo = await DeviceValidator.getDevtype(owner, devtype);
    if (!isDeepStrictEqual(devTypeInfo, cacheInfo.data)) {
      cacheInfo.data = devTypeInfo;
    }
    cacheInfo.expire = expire;
    return cacheInfo.data;
  })
}

export function deviceCacheKey(owner: string, devtype: string, devid: string) {
  return `${owner}.${devtype}.${devid}`
}

export async function getDeviceCache(owner: string, devtype: string, devid: string) {
  const key = deviceCacheKey(owner, devtype, devid);
  return lock.acquire<any>(key, async () => {
    const now = Date.now();
    const expire = now + 10000;
    const cacheInfo = getOrCreateCacheInfo(key, expire)
    if (cacheInfo.data != undefined && cacheInfo.expire > now) {
      return cacheInfo.data;
    }
    const deviceInfo = await DeviceValidator.getDevice(owner, devtype, devid);
    if (!isDeepStrictEqual(deviceInfo, cacheInfo.data)) {
      cacheInfo.data = deviceInfo;
    }
    cacheInfo.expire = expire;
    return cacheInfo.data;
  })
}

  • 10
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值