Skip to main content

hapi - caching

Dev
Table of Contents

HAPI 内置了緩存系統。緩存也分很多種。爲什麽用緩存。如果資料不需要實時更新,其實它可以緩存到内存、或者其它儲存設備。

例如電視節目表,可以緩存幾小時或者長達一天的資料,不需要每次訪問都詢問資料庫。

當然,速度來講非 redis、memcache 莫屬,内存目前還是最快的媒體,所以首推這兩款。詳細請看 catbox 的 API 文檔

偽代碼
#

手動寫 cache 就是大概這樣……譬如用 redis。接下來的是偽代碼:

  1. const key = redis.get('key:xxxx')
  2. 找到 key,return res(...)
  3. 沒找到 key
    redis.set('key:xxxx')
    redis.expire('key:xxxx') // 不會緩存幾年吧
    return res(...)
    

當然有 error 錯誤也好處理一下。

可以讀讀,這篇文章 Caching a MongoDB Database with Redis

客戶端緩存
#

這個很明顯,客戶端單方面做簡單的緩存。伺服器加了 Cache-Control 這個 header。

HTTP Header
#

server.route({
  path: "/hapi",
  method: "GET",
  handler: function (request, reply) {
    reply({ be: "hapi" });
  },
  config: {
    cache: {
      expiresIn: 30 * 1000, // 30 seconds
      privacy: "private",
    },
  },
});

相等于:Cache-Control:max-age=30, private

除了用 expiresIn 之外,也可以用 expiresAt:每天固定时间过期( HH:MM 格式)。

Last-Modified
#

reply(result).header("Last-Modified", lastModified.toUTCString());

游覽器的 request 順便帶著 If-Modified-Since 這個 HTTP header,伺服器收到請求后比較時間,再作出回應是否 304.

Etag
#

Etag 其實跟 Last-Modified 差不多~~,不是用時間來對比而已~~ 。Etag 需要校驗(checksum)計算,有點浪費資源。不推薦這方法。

reply(result).etag("xxxxxxxxx");

伺服器緩存
#

配置緩存政策,暫且叫做 redisCache。

// server.js
const Hapi = require("hapi");

const server = new Hapi.Server({
  cache: [
    {
      name: "redisCache",
      engine: require("catbox-redis"),
      host: "127.0.0.1",
      partition: "cache",
    },
  ],
});

server.connection({
  port: 8000,
});

配置 cache 時,如果沒寫用什麽 engine,catbox 就會用 catbox-memory。默認限制 100MB。用來本地測試、dev 伺服器就算了,production 還是用 redis 或者 memcache。

// route.js
const add = function (a, b, next) {
  return next(null, Number(a) + Number(b));
};

const sumCache = server.cache({
  cache: "redisCache",
  expiresIn: 20 * 1000,
  segment: "customSegment",
  generateFunc: function (id, next) {
    add(id.a, id.b, next);
  },
  generateTimeout: 100,
});

server.route({
  path: "/add/{a}/{b}",
  method: "GET",
  handler: function (request, reply) {
    const id = request.params.a + ":" + request.params.b;
    sumCache.get(
      { id: id, a: request.params.a, b: request.params.b },
      (err, result) => {
        if (err) {
          return reply(err);
        }
        reply(result);
      }
    );
  },
});

sumCache.get(xxx) 嘗試讀取這個緩存。如果沒有緩存,generateFunc 也提供了,它就會自動生成新的緩存。

segment 是命名空間。用來隔離緩存的 cache key,不會搞亂其它資料。

  • 如果在插件裏面調用這個 cache。Segment 值就是 !pluginName
  • Server Method 調用則是:#methodName

Server method
#

Server method 是 HAPI 很重要的 API。用來分享函數。

const add = function (a, b, next) {
  return next(null, Number(a) + Number(b));
};

server.method("sum", add, {
  cache: {
    cache: "redisCache",
    expiresIn: 30 * 1000,
    generateTimeout: 100,
  },
});

server.route({
  path: "/add/{a}/{b}",
  method: "GET",
  handler: function (request, reply) {
    server.methods.sum(request.params.a, request.params.b, (err, result) => {
      if (err) {
        return reply(err);
      }
      reply(result);
    });
  },
});
  • expiresIn:cache 的存活時間。單位:ms
  • generateTimeout: 給伺服器多長時間生成這個緩存。單位:ms
    • 如果你需要下載遠程東西,或者 db 搞很長時間,考慮提高這個數值到三十秒或者一分鐘。否則 hapi 返回 504 timeout。

上面的例子來説,Hapi 自動創建 segment: '#sum' 這個 cache key。如果有參數,參數也會附加到這個 cache key。自動創建支持 String,Number,Boolean 這三個類型。其它類型的需要你自己寫 generateKey 生成自己的 cache key。

客戶端和服務器緩存
#

繼續上面的例子。如果調用 server.methods,提供了額外兩個參數 cachedreport

(err, result, cached, report) => xxx

// some-route.js
server.route({
  path: "/add/{a}/{b}",
  method: "GET",
  handler: function (request, reply) {
    server.methods.sum(
      request.params.a,
      request.params.b,
      (err, result, cached, report) => {
        if (err) {
          return reply(err);
        }
        const lastModified = cached ? new Date(cached.stored) : new Date();
        return reply(result).header(
          "last-modified",
          lastModified.toUTCString()
        );
      }
    );
  },
});

cached.stored 是時間戳,這樣就可以在 reply 添加 last-modified 這個 header。

HAPI 緩存是智能的,錯誤當然是不會保存的。

題外話

HAPI 是配置形的伺服器(config-based),重複無聊的緩存代碼是不需要自己寫,有用的功能都内置了,配置好就可以用了,花時間處理其它事吧。

擴展閲讀
#