NJS 共享字典(ngx.shared)全解析:跨 Worker 进程的数据共享方案

一、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 指令定义共享字典,支持 httpstream 块级配置:

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_cachengx.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 };

四、使用注意事项

  1. 类型严格匹配string 字典只能存字符串,number 字典只能存整数,否则抛出 TypeError
  2. 内存管理 :当共享字典内存不足时,新增/修改操作会抛出 SharedMemoryError,建议通过 freeSpace() 监控空闲内存;
  3. 超时单位 :API 中自定义超时的单位是毫秒 ,而 NGINX 配置中 timeout 单位是秒/分钟(如 60s),注意转换;
  4. 性能考量 :SharedDict 基于共享内存实现,读写性能高,但批量操作(keys()/items())会遍历所有键,高频调用可能影响性能;
  5. 版本兼容timeout 自定义参数仅 0.8.5+ 支持,items() 仅 0.8.1+ 支持,使用前需通过 njs.version 检查版本。

五、总结

  1. ngx.shared 是 NJS 实现跨 Worker 进程数据共享的核心能力,需先通过 js_shared_dict_zone 配置字典名称、类型和容量;
  2. 核心 API 分为基础增删改查(add()/set()/get()/delete())、数值递增(incr())、元信息查询(capacity/size())和批量操作(keys()/items());
  3. 典型应用场景包括接口限流(number 字典)、响应缓存(string 字典)、跨进程计数器等,是 NGINX 网关层轻量化数据共享的最优方案。
相关推荐
xifangge20258 小时前
PHP 错误日志在哪里看?Apache / Nginx / PHP-FPM 一次讲清
nginx·php·apache
鸠摩智首席音效师8 小时前
如何安装和配置 Nginx 反向代理服务器 ?
运维·nginx
星光不问赶路人11 小时前
Nginx 的 location 路径匹配语法详解
nginx·api
GDAL11 小时前
深入理解 NJS 全局对象:掌控运行时的核心工具
nginx·njs
GDAL12 小时前
精通 NJS HTTP 请求对象:全方位掌控 NGINX 请求生命周期
nginx·njs
飞翔沫沫情13 小时前
Nginx运维维护规范及全配置详解【持续更新】
nginx·nginx 配置·nginx 操作手册·nginx 使用规范·nginx 日志规范·nginx 配置文件说明
deriva13 小时前
nginx如何将某域名/二级站点/代理到二级站点?以ChirpStack实战为例
运维·nginx
睡不醒的猪儿1 天前
nginx常见的优化配置
运维·nginx
root666/1 天前
【后端开发-nginx】proxy_pass和proxy_redirect参数作用
运维·nginx