缓存策略 #

一、缓存概述 #

1.1 Hapi缓存系统 #

Hapi使用Catbox作为缓存系统,支持多种缓存后端:

  • 内存缓存
  • Redis
  • Memcached
  • MongoDB
  • 自定义适配器

1.2 缓存类型 #

类型 说明
服务端缓存 服务器端数据缓存
方法缓存 服务器方法结果缓存
客户端缓存 HTTP缓存头控制

二、配置缓存 #

2.1 内存缓存 #

javascript
const Hapi = require('@hapi/hapi');

const server = Hapi.server({
    port: 3000,
    cache: [{
        name: 'memory',
        provider: {
            constructor: require('@hapi/catbox-memory'),
            options: {
                maxByteSize: 100 * 1024 * 1024
            }
        }
    }]
});

2.2 Redis缓存 #

bash
npm install @hapi/catbox-redis
javascript
const CatboxRedis = require('@hapi/catbox-redis');

const server = Hapi.server({
    port: 3000,
    cache: [{
        name: 'redis',
        provider: {
            constructor: CatboxRedis,
            options: {
                partition: 'my-app',
                host: 'localhost',
                port: 6379,
                password: 'secret'
            }
        }
    }]
});

2.3 多缓存配置 #

javascript
const server = Hapi.server({
    port: 3000,
    cache: [
        {
            name: 'memory',
            provider: {
                constructor: require('@hapi/catbox-memory'),
                options: { maxByteSize: 50 * 1024 * 1024 }
            }
        },
        {
            name: 'redis',
            provider: {
                constructor: require('@hapi/catbox-redis'),
                options: {
                    partition: 'my-app',
                    host: 'localhost',
                    port: 6379
                }
            }
        }
    ]
});

三、方法缓存 #

3.1 基本缓存 #

javascript
server.method('getUser', async (id) => {
    return await User.findById(id);
}, {
    cache: {
        expiresIn: 60 * 1000,
        generateTimeout: 100
    }
});

3.2 缓存选项 #

选项 说明
cache 缓存名称
expiresIn 过期时间(毫秒)
staleIn 过期前刷新时间
staleTimeout 刷新超时
generateTimeout 生成超时

3.3 高级缓存配置 #

javascript
server.method('getProduct', async (id) => {
    return await Product.findById(id);
}, {
    cache: {
        cache: 'redis',
        expiresIn: 60 * 60 * 1000,
        staleIn: 30 * 60 * 1000,
        staleTimeout: 100,
        generateTimeout: 1000
    },
    generateKey: (id) => `product:${id}`
});

3.4 缓存键生成 #

javascript
server.method('search', async (query, filters) => {
    return await SearchService.search(query, filters);
}, {
    cache: {
        expiresIn: 60 * 1000
    },
    generateKey: (query, filters) => {
        return `search:${query}:${JSON.stringify(filters)}`;
    }
});

四、缓存操作 #

4.1 手动缓存 #

javascript
const cache = server.cache({
    cache: 'redis',
    segment: 'users',
    expiresIn: 60 * 60 * 1000
});

server.route({
    method: 'GET',
    path: '/users/{id}',
    handler: async (request, h) => {
        const key = `user:${request.params.id}`;
        
        let user = await cache.get(key);
        
        if (!user) {
            user = await User.findById(request.params.id);
            await cache.set(key, user);
        }
        
        return user;
    }
});

4.2 删除缓存 #

javascript
server.route({
    method: 'PUT',
    path: '/users/{id}',
    handler: async (request, h) => {
        const user = await User.update(request.params.id, request.payload);
        
        const key = `user:${request.params.id}`;
        await cache.drop(key);
        
        return user;
    }
});

4.3 清空缓存 #

javascript
server.route({
    method: 'POST',
    path: '/admin/clear-cache',
    handler: async (request, h) => {
        await cache.drop();
        return { message: 'Cache cleared' };
    }
});

五、客户端缓存 #

5.1 Cache-Control #

javascript
server.route({
    method: 'GET',
    path: '/static/{path*}',
    handler: {
        directory: {
            path: './public'
        }
    },
    options: {
        cache: {
            expiresIn: 30 * 24 * 60 * 60 * 1000,
            privacy: 'public'
        }
    }
});

5.2 ETag #

javascript
server.route({
    method: 'GET',
    path: '/api/data',
    handler: (request, h) => {
        const data = { value: 'data' };
        return h.response(data)
            .etag('abc123');
    }
});

5.3 Last-Modified #

javascript
server.route({
    method: 'GET',
    path: '/article/{id}',
    handler: async (request, h) => {
        const article = await Article.findById(request.params.id);
        
        return h.response(article)
            .header('Last-Modified', article.updatedAt.toUTCString());
    }
});

六、缓存策略 #

6.1 分层缓存 #

javascript
server.method('getData', async (key) => {
    return await fetchData(key);
}, {
    cache: {
        cache: 'memory',
        expiresIn: 30 * 1000
    }
});

server.method('getDataLongTerm', async (key) => {
    return await fetchData(key);
}, {
    cache: {
        cache: 'redis',
        expiresIn: 60 * 60 * 1000
    }
});

6.2 条件缓存 #

javascript
server.method('getUser', async (id) => {
    return await User.findById(id);
}, {
    cache: {
        expiresIn: 60 * 1000,
        generateTimeout: 100
    },
    generateKey: (id) => {
        if (id === 'admin') {
            return null;
        }
        return `user:${id}`;
    }
});

6.3 缓存预热 #

javascript
server.events.on('start', async () => {
    const users = await User.findAll();
    
    for (const user of users) {
        await server.methods.getUser.cache.set(
            `user:${user.id}`,
            user
        );
    }
    
    console.log('Cache warmed up');
});

七、缓存失效 #

7.1 基于时间 #

javascript
server.method('getSettings', async () => {
    return await Settings.findAll();
}, {
    cache: {
        expiresIn: 60 * 1000,
        staleIn: 30 * 1000,
        staleTimeout: 100
    }
});

7.2 基于事件 #

javascript
server.event('userUpdated');

server.events.on('userUpdated', async (userId) => {
    await server.methods.getUser.cache.drop(userId);
});

server.route({
    method: 'PUT',
    path: '/users/{id}',
    handler: async (request, h) => {
        const user = await User.update(request.params.id, request.payload);
        server.events.emit('userUpdated', request.params.id);
        return user;
    }
});

7.3 标签缓存 #

javascript
const cache = server.cache({
    segment: 'products',
    expiresIn: 60 * 60 * 1000
});

server.route({
    method: 'GET',
    path: '/products/{id}',
    handler: async (request, h) => {
        const key = `product:${request.params.id}`;
        let product = await cache.get(key);
        
        if (!product) {
            product = await Product.findById(request.params.id);
            await cache.set(key, product, 0, ['products']);
        }
        
        return product;
    }
});

八、缓存监控 #

8.1 缓存统计 #

javascript
server.method('getCacheStats', async () => {
    const stats = await cache.stats();
    return {
        hits: stats.hits,
        misses: stats.misses,
        sets: stats.sets,
        deletes: stats.deletes
    };
});

8.2 缓存健康检查 #

javascript
server.route({
    method: 'GET',
    path: '/health/cache',
    handler: async (request, h) => {
        try {
            await cache.set('health-check', 'ok', 1000);
            const value = await cache.get('health-check');
            
            return {
                status: value === 'ok' ? 'healthy' : 'unhealthy'
            };
        } catch (error) {
            return {
                status: 'unhealthy',
                error: error.message
            };
        }
    }
});

九、完整示例 #

9.1 缓存服务 #

javascript
const Hapi = require('@hapi/hapi');
const CatboxRedis = require('@hapi/catbox-redis');

const init = async () => {
    const server = Hapi.server({
        port: 3000,
        cache: [{
            name: 'redis',
            provider: {
                constructor: CatboxRedis,
                options: {
                    partition: 'my-app',
                    host: 'localhost',
                    port: 6379
                }
            }
        }]
    });
    
    const userCache = server.cache({
        cache: 'redis',
        segment: 'users',
        expiresIn: 60 * 60 * 1000
    });
    
    server.method('getUser', async (id) => {
        return await User.findById(id);
    }, {
        cache: {
            cache: 'redis',
            expiresIn: 60 * 60 * 1000,
            generateTimeout: 100
        },
        generateKey: (id) => `user:${id}`
    });
    
    server.route([
        {
            method: 'GET',
            path: '/users/{id}',
            handler: async (request, h) => {
                return await server.methods.getUser(request.params.id);
            }
        },
        {
            method: 'PUT',
            path: '/users/{id}',
            handler: async (request, h) => {
                const user = await User.update(request.params.id, request.payload);
                await server.methods.getUser.cache.drop(request.params.id);
                return user;
            }
        }
    ]);
    
    await server.start();
    console.log('Server running on %s', server.info.uri);
};

init();

十、总结 #

缓存策略要点:

功能 说明
内存缓存 @hapi/catbox-memory
Redis缓存 @hapi/catbox-redis
方法缓存 server.method cache
客户端缓存 Cache-Control, ETag
缓存失效 drop, 事件

下一步,让我们学习安全最佳实践!

最后更新:2026-03-28