一、SharedDict 核心定位与前置配置
1.1 什么是 SharedDict?
SharedDict(共享字典)是 NJS 基于 NGINX 共享内存区实现的键值存储,核心特性:
- 跨进程共享:所有 NGINX Worker 进程可读写同一 SharedDict 数据,突破单进程内存隔离限制;
- 类型约束 :支持
string/number两种数据类型,需在配置时指定; - 自动过期:可设置全局或单个键的超时时间,实现缓存自动失效;
- 内存可控:通过配置固定内存容量,避免无限制占用系统资源。
1.2 必选配置:js_shared_dict_zone 指令
使用 ngx.shared 前,需在 NGINX 配置文件(nginx.conf)中通过 js_shared_dict_zone 指令定义共享字典,支持 http 或 stream 块级配置:
nginx
# http 块中定义共享字典
http {
# 定义名为 "my_cache" 的字符串类型共享字典,容量 10MB,全局超时 60 秒
js_shared_dict_zone zone=my_cache:10m type=string timeout=60s;
# 定义名为 "request_counter" 的数值类型共享字典,容量 1MB,无全局超时
js_shared_dict_zone zone=request_counter:1m type=number;
server {
listen 80;
# 其他配置...
}
}
配置参数说明:
| 参数 | 说明 |
|---|---|
zone=NAME:SIZE |
定义字典名称(NAME)和内存容量(SIZE,如 10m=10MB、1k=1KB); |
type=string/number |
指定字典存储的值类型,必选(string 存储字符串,number 仅存储整数); |
timeout=TIME |
全局超时时间(如 60s、5m),可选,未指定则键永久有效; |
二、SharedDict 核心 API 详解
定义好共享字典后,可通过 ngx.shared.字典名称 访问实例,例如 ngx.shared.my_cache、ngx.shared.request_counter。以下按功能分类讲解核心 API。
2.1 基础操作:增删改查
2.1.1 新增键值:add()
仅当键不存在时新增,返回布尔值(成功 true/已存在 false),支持自定义超时时间(0.8.5+):
javascript
// 操作 "my_cache" 字符串字典
const cacheDict = ngx.shared.my_cache;
// 新增键 "user_1001",值 "zhangsan",超时 30 秒(覆盖全局 60 秒)
const isAdded = cacheDict.add("user_1001", "zhangsan", 30000); // 超时单位:毫秒
if (isAdded) {
console.log("键新增成功");
} else {
console.log("键已存在,新增失败");
}
⚠️ 异常场景:
- 内存不足时抛出
SharedMemoryError; - 值类型与字典定义不符时抛出
TypeError(如给 number 字典存字符串)。
2.1.2 修改键值:replace()/set()
replace(key, value):仅当键存在时替换值,返回布尔值;set(key, value [,timeout]):无论键是否存在都设置值,支持自定义超时,返回字典实例(可链式调用)。
javascript
// 替换已存在的键值
const isReplaced = cacheDict.replace("user_1001", "lisi");
// 强制设置键值,超时 10 秒
cacheDict.set("user_1002", "wangwu", 10000).set("user_1003", "zhaoliu"); // 链式调用
2.1.3 查询键值:get()/has()
get(key):获取键对应值,不存在返回undefined;has(key):检查键是否存在,返回布尔值。
javascript
// 查询值
const userName = cacheDict.get("user_1001"); // 输出:lisi
// 检查键是否存在
const exists = cacheDict.has("user_1004"); // 输出:false
2.1.4 删除键值:delete()/pop()
delete(key):删除键,返回布尔值(成功true/不存在false);pop(key):删除键并返回对应值,不存在返回undefined。
javascript
// 删除键,返回是否成功
const isDeleted = cacheDict.delete("user_1002");
// 删除键并获取值
const deletedValue = cacheDict.pop("user_1003"); // 输出:zhaoliu
2.2 数值专用:incr()
仅适用于 type=number 的字典,实现数值递增/递减,支持初始化值和自定义超时:
javascript
// 操作 "request_counter" 数值字典
const counterDict = ngx.shared.request_counter;
// 场景1:键不存在时,初始化为 0 并递增 1(最终值 1)
const count1 = counterDict.incr("total_requests", 1); // 输出:1
// 场景2:键已存在,递增 1(最终值 2)
const count2 = counterDict.incr("total_requests", 1); // 输出:2
// 场景3:键不存在,初始化为 10 并递减 2(最终值 8),超时 5 分钟
const count3 = counterDict.incr("failed_requests", -2, 10, 300000); // 输出:8
参数说明:
delta:递增/递减值(正数增,负数减);init:可选,键不存在时的初始值(默认 0);timeout:可选,超时时间(毫秒,0.8.5+)。
2.3 字典信息:容量、大小、类型
获取字典的元信息,辅助监控和内存管理:
javascript
// 字符串字典示例
const cacheDict = ngx.shared.my_cache;
console.log("字典名称:", cacheDict.name); // 输出:my_cache
console.log("字典类型:", cacheDict.type); // 输出:string
console.log("总容量(字节):", cacheDict.capacity); // 输出:10485760(10MB)
console.log("空闲内存(字节):", cacheDict.freeSpace()); // 输出:剩余空闲字节数
console.log("当前键数量:", cacheDict.size()); // 输出:字典中现有键的数量
2.4 批量操作:keys()/items()
批量获取键或键值对,支持指定最大数量(默认 1024):
javascript
// 获取前 10 个键
const keys = cacheDict.keys(10); // 输出:["user_1001", ...]
// 获取前 5 个键值对(数组元素为 [key, value])
const items = cacheDict.items(5); // 输出:[["user_1001", "lisi"], ...]
2.5 清空字典:clear()
删除字典中所有键值,无返回值:
javascript
// 清空缓存字典
cacheDict.clear();
console.log("清空后键数量:", cacheDict.size()); // 输出:0
三、实战场景:基于 SharedDict 的限流与缓存
3.1 场景1:接口访问限流(数值字典)
实现单 IP 每分钟最多访问 10 次接口,超出则拒绝:
步骤1:NGINX 配置
nginx
http {
js_shared_dict_zone zone=ip_limit:1m type=number;
js_import limit.js; # 导入 NJS 脚本
server {
listen 80;
location /api {
js_access limit.ipAccessLimit; # 访问前执行限流校验
proxy_pass http://backend;
}
}
}
步骤2:NJS 脚本(limit.js)
javascript
function ipAccessLimit(r) {
const limitDict = ngx.shared.ip_limit;
const clientIP = r.remoteAddress; // 获取客户端 IP
const limitCount = 10; // 每分钟限制 10 次
const timeout = 60000; // 60 秒超时
try {
// 递增 IP 访问次数,不存在则初始化为 0
const currentCount = limitDict.incr(clientIP, 1, 0, timeout);
if (currentCount > limitCount) {
r.return(429, "Too Many Requests"); // 超出限制,返回 429
} else {
r.next(); // 正常放行
}
} catch (e) {
console.error("限流校验失败:", e.message);
r.return(500, "Internal Server Error");
}
}
export default { ipAccessLimit };
3.2 场景2:接口响应缓存(字符串字典)
缓存热点接口响应,减少后端服务压力:
步骤1:NGINX 配置
nginx
http {
js_shared_dict_zone zone=api_cache:10m type=string timeout=30s;
js_import cache.js;
server {
listen 80;
location /api/hot-data {
js_content cache.hotDataCache;
}
}
}
步骤2:NJS 脚本(cache.js)
javascript
// 模拟后端接口请求
function fetchHotData() {
// 实际场景中可通过 r.subrequest 调用后端接口
return JSON.stringify({
id: 1,
content: "热点数据内容",
updateTime: new Date().toISOString()
});
}
function hotDataCache(r) {
const cacheDict = ngx.shared.api_cache;
const cacheKey = "hot_data";
// 先查缓存
const cachedData = cacheDict.get(cacheKey);
if (cachedData) {
r.headersOut['Content-Type'] = 'application/json';
r.return(200, cachedData); // 缓存命中,直接返回
return;
}
// 缓存未命中,请求后端并写入缓存
const freshData = fetchHotData();
cacheDict.set(cacheKey, freshData, 20000); // 缓存 20 秒
r.headersOut['Content-Type'] = 'application/json';
r.return(200, freshData);
}
export default { hotDataCache };
四、使用注意事项
- 类型严格匹配 :
string字典只能存字符串,number字典只能存整数,否则抛出TypeError; - 内存管理 :当共享字典内存不足时,新增/修改操作会抛出
SharedMemoryError,建议通过freeSpace()监控空闲内存; - 超时单位 :API 中自定义超时的单位是毫秒 ,而 NGINX 配置中
timeout单位是秒/分钟(如 60s),注意转换; - 性能考量 :SharedDict 基于共享内存实现,读写性能高,但批量操作(
keys()/items())会遍历所有键,高频调用可能影响性能; - 版本兼容 :
timeout自定义参数仅 0.8.5+ 支持,items()仅 0.8.1+ 支持,使用前需通过njs.version检查版本。
五、总结
ngx.shared是 NJS 实现跨 Worker 进程数据共享的核心能力,需先通过js_shared_dict_zone配置字典名称、类型和容量;- 核心 API 分为基础增删改查(
add()/set()/get()/delete())、数值递增(incr())、元信息查询(capacity/size())和批量操作(keys()/items()); - 典型应用场景包括接口限流(number 字典)、响应缓存(string 字典)、跨进程计数器等,是 NGINX 网关层轻量化数据共享的最优方案。