Recently, I wrote an interface to get a file directory using Node when I was doing a project. In order to reduce the pressure on the server, I thought of using Redis to do temporary cache, but because I have to change the server recently and I don’t bother to install it, and the amount of data to be stored on my personal website is not too much, can I maintain a cache object by myself?

An idea struck me:

When we start the node server.js service, the service itself runs in memory, so we can write an object that manages itself. Of course, the disadvantages are obvious: 1. The performance is certainly not as good as Redis itself, 2. When the service goes down, the data cache goes away.

Redis V8
Implementation language c c++
perform C Direct operating system Compile js code for machine code execution

Which is faster and more efficient, C or V8(C ++)? Ahem, don’t do that. Don’t circle it. If you are interested, you can search for relevant articles.

Features of Redis:

  • Store the data
  • To get the data
  • With cache time
  • Delete after expiration

Self implemented functions:

  1. Data cache, set expiration time;
  2. Limit the maximum memory. If the memory exceeds the limit, clean up.
  3. Reduce the frequency of program execution, data brushing.

Implement a simple cache object

Defining the cache object

const length = Symbol('_length');
const data = Symbol('data');

const cache = {
  // Set data to type Symbol to avoid directly viewing/adding/deleting/modifying attributes
  [data]: {
    // key: { createTime: 1627001616489, value: 'data', overTime: 1000, count: 0 }
    [length]: 0
  },
  // Get the length of the entire cache object
  length() {
    return this[data][length]; }}Copy the code

If you want to use it on the browser side, replace the data object with Storage, and the idea is the same. Implement a storage with an expiration time

Start with a utility function: number generator

function *createNum() {
  let n = 0
  while (true) {
    yieldn; n++; }}const iter = createNum();
iter.next().value;  / / -- > 0
iter.next().value;  / / -- > 1
iter.next().value;  / / -- > 2
Copy the code

Storage data format

key type instructions
createTime Date.now() Data storage time
value any Store the object
overTime number Expiration time
count number Record the index. It is possible to store a lot of data at the same time. The smaller the index, the earlier it was stored (use the above utility functions).

Store & Get

const cache = {
  [data]: {
    [length]: 0
  },
  // Store the data
  set(key, value, overTime) {
    if (key === null || key === undefined || key === ' ') return;
    this[data][length] ++;
    this[data][key] = {
      createTime: Date.now(),  // Create time
      value,  // Store the data
      overTime: overTime,  // Expiration time
      count: iter.next().value,  // Index to record the data storage order}},// Get the data
  get(key) {
    if (!this[data][key]) return;
    const time = Date.now();
    const obj = this[data][key];
    time - obj.createTime > obj.overTime && this.delete(key);
    return this[data][key]? .value;/ /? . Prevent error when storing value === undefined}}Copy the code

Attach the full code

cache.js
const { 'log': c, warn } = console;
const length = Symbol('_length');
const data = Symbol('data');

function *createNum() {
  let n = 0
  while (true) {
    yieldn; n++; }}const iter = createNum();

const cache = {
  // Set data to type Symbol to avoid directly viewing/adding/deleting/modifying attributes
  [data]: {
    [length]: 0
  },
  /** * Set cache data *@param {string | symbol} Key If you do not want to override this attribute, set the key to type Symbol *@param {*} value 
   * @param {number} OverTime Expiration time. Unit: ms */
  set(key, value, overTime) {
    if (key === null || key === undefined || key === ' ') return;
    this[data][length] ++;
    this[data][key] = {
      createTime: Date.now(),
      value,
      overTime: overTime,
      count: iter.next().value,  // It is possible to store a lot of data in the same millisecond, record an index, the smaller it is, the earlier it is stored}},// Get the data
  get(key) {
    if (!this[data][key]) return;
    const time = Date.now();
    const obj = this[data][key];
    time - obj.createTime > obj.overTime && this.delete(key);
    return this[data][key]? .value;/ /? . Prevent error when storing value === undefined
  },
  // Delete the data
  delete(key) {
    if (!this[data][key]) return;
    delete this[data][key];
    this[data][length] --;
  },
  // Delete all data
  clear() {
    this[data] = {}
  },
  // data.length
  length() {
    return this[data][length];
  },
  gainAll() {
    return this[data];
  },
  // Get the size of the data in bytes and calculate it roughly (without differentiating between Chinese and English and symbol type key.length)
  size() {
    const syms = Object.getOwnPropertySymbols(this[data]);
    let symSize = 0;
    syms.forEach(val= > {
      const symValue = this[data][val];
      symSize += JSON.stringify(symValue).length
    })
    const objSize = JSON.stringify(this[data]).length;
    returnsymSize + objSize; }}export default cache;
Copy the code
import cache from './cache.js';
const { 'log': c } = console;

cache.set('the east emperor'.'Can't play'.1000);
cache.set('ying zheng'.'No money to buy'.1000);
cache.set('cloud with'.'the thief slipped'.1213);
cache.set('the best'.'Two shots for one.'.500);
cache.set('Chung Wo-yan'.'Take my hammer.'.43);

cache.set('cloud sakura'.'the thief slipped'.1213);
cache.set('the best'.'Two shots for one.'.400);
setTimeout(() = > {
  cache.get('cloud sakura');
  cache.get('the best');
  c(cache.gainAll());
  c(cache.size());
}, 600)
Copy the code

You think that’s the end of it? No, the core features haven’t been implemented yet. The above code only in the data storage to check the data is not expired, that storage is not also should clean up the next memory! Also, limit the size of memory and delete outdated data and the oldest data. The waves behind the Yangtze river rush the waves before, and the new generation changes the old on earth! ^v^

Limit size and clean up memory

import cache from './cache.js';

export default class Redis {
  constructor(maxCache) {
    this.maxCache = maxCache || 1024 * 1024 * 2;  // The maximum number of caches is 2M by default
  }
  
  /** * store data. If it already exists and is not expired, you will get it directly *@param {string} Key Specifies the key for storing data@param {*} The value suggestion type is a function (which must return data) that can request data and does not perform * when cached@param {date} OverTime Expiration time *@returns Returns the stored value */
  async deposit(key, value, overTime) {
    const data = cache.get(key)
    if (data) {
      return data;
    } else {
      typeof value === 'function' ? value = await value() : value;
      cache.set(key, value, overTime);  // Save data before clearing memory to prevent overflow
      
      const size = cache.size();  // Obtain the memory size of the data stored in the container
      // If the actual number of cached data is larger than the set number, clear the data
      size > this.maxCache && this.clearCache();

      returnvalue; }}// Clean up data (expired, old)
  clearCache() {
    this.deleteOverValue();  // Clear expired data

    const size = cache.size();
    if (size < this.maxCache) return;  // Determine the size of the memory again, if the memory exceeds, do the following operations

    const obj = cache.gainAll();
    const arr = [];
    for (const prop of Object.entries(obj)) {
      arr.push({key: prop[0], ...prop[1]});
    }
    const newArr = choiceSort(arr);
    this.deleteFristValue(newArr);
  }
  
  // Delete expired data
  deleteOverValue() {
    const obj = cache.gainAll();
    const curTime = Date.now();
    for (const prop of Object.entries(obj)) {
      const createTime = prop[1].createTime;
      const overTime = prop[1].overTime;
      if (curTime - createTime > overTime) {
        cache.delete(prop[0]); }}}// Delete the earliest cached data
  deleteFristValue(arr) {
    const key = arr[0].key;
    arr.shift();
    cache.delete(key);

    const size = cache.size();
    if (size <= this.maxCache) return;
    this.deleteFristValue(arr);  // If the memory of the container is still larger than the specified memory, continue to delete}}// Sort the array
function choiceSort(arr) {
  const len = arr.length;
  if (arr == null || len == 0) return [];
  for (let i = 0; i < len - 1; i++) {
    for (let j = 0; j < len - 1; j++) {
      const minValue = arr[j];
      if (minValue.count > arr[j + 1].count) {
        [arr[j], arr[j + 1]] = [arr[j + 1], minValue];  [a, b] --> [b, a]}}}return arr;
}
Copy the code

Knowledge supplement: parameter replacement

var a = 1, b = 2;

// 1. This is what we did before ES6
var c = b;
b = a;
a = c;
console.log(a, b);  / / -- > 2 to 1

// 2. ES6+
[a, b] = [b, a];
console.log(a, b);  / / -- > 2 to 1
Copy the code

Testing:

import cache from './cache.js';
import Redis from './redis.js';
const redis = new Redis(500);  // Limit the memory size
const { 'log': c } = console;

redis.deposit('the east emperor'.'Can't play'.1000);
redis.deposit('ying zheng'.'No money to buy'.1000);
redis.deposit('Hidden in the Ming Dynasty'.'Have a plate'.2000);
redis.deposit('the sable cicada'.'Don't love Lu Bu'.2000);
redis.deposit('Yang Jian'.() = > 'Set the dog on you'.1000);
redis.deposit('Milady'.() = > ['1' creeps.'batman 2'.'batman 3'].600);
redis.deposit(Symbol('AKe'), () = > 'Can't see me'.1000);
redis.deposit('the monkey'.async() = > {return await 'who'
}, 600);
redis.deposit('yuan song'.() = >[{name: 'I could be anyone'},].600);
redis.deposit('Master Lu Ban'.() = > 'Lu Ban no. 7 Father'.1000);
redis.deposit('cloud with'.() = > "Come with me to the Dali Temple.".300);
setTimeout(async () => {
  redis.deposit('Luban No. 7'.() = > 'Short legs, fast frequency'.1000);
  c(cache.gainAll());
  c('size:', cache.size());

  const monkey = await redis.deposit('the monkey');
  c('the monkey:', monkey)
}, 500)
Copy the code

In node, I’m using KOA

app.get('/label'.async (ctx, next) => {
  const time = 1000 * 60 * 20;
  const data = await redis.deposit('label'.async() = > {return await getFileCatalogue();
  }, time);
  ctx.body = data;
  next();
})
Copy the code