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 指令定义共享字典,支持 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 };

四、使用注意事项

  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 网关层轻量化数据共享的最优方案。
相关推荐
lpfasd1232 天前
WinSW在Win7上失败真相-实测与修复
windows·nginx
lpfasd1232 天前
nginx服务化五套方案原理解剖与选型
运维·nginx
ue星空2 天前
ALS V4 中文拆解手册
运维·nginx
吴声子夜歌3 天前
Nginx应用与运维——Nginx集群负载与配置管理(二)
运维·nginx
cpolar技术支持3 天前
外网测试机的异常传不回内网?自托管 Sentry,用 cpolar 打通错误上报链路
python·nginx·docker·cpolar·sentry
吴声子夜歌3 天前
Nginx应用与运维——Nginx在Kubernetes中的应用(一)
运维·nginx·kubernetes
螺蛳粉 螺蛳粉3 天前
第一篇:Keepalived 高可用实战:VIP 漂移与 Nginx 主备切换完整指南
运维·nginx·负载均衡·keepalived·高可用
吴声子夜歌3 天前
Nginx应用与运维——Nginx在Kubernetes中的应用(三)
运维·nginx·kubernetes
吴声子夜歌3 天前
Nginx应用与运维——Nginx在Kubernetes中的应用(二)
运维·nginx·kubernetes
Wang's Blog3 天前
Java 项目实战: 外卖平台优化-Nginx配置文件结构与块层级
java·开发语言·nginx