摘自:http://www.daileinote.com/computer/openresty/11

通过 lua_shared_dict 指令可以声明一个共享内存区域,可以在多个 worker 进程间共享,单位支持 k、m,然后配合 ngx.shared.DICT api函数来操作。nginx -s reload 后共享内存的数据还在。

这个共享内存功能非常有用,极大的便利了worker 进程间的通信和协作,而且还提供了类似 redis 的数据结构,可以当做一个简易的数据库,而且没有通信开销。

共享内存在 Openresty 里用途很广,基于它可以实现流量统计、缓存、进程锁等高级功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
http {
lua_shared_dict dogs 10m;
server {
location /set {
content_by_lua_block {
local dogs = ngx.shared.dogs
dogs:set("Jim", 8)
ngx.say("STORED")
}
}
location /get {
content_by_lua_block {
local dogs = ngx.shared.dogs
ngx.say(dogs:get("Jim"))
}
}
}
}
[root@192 ~]# curl localhost/set
STORED
[root@192 ~]# curl localhost/get
8
[root@192 ~]# openresty -p /var/openresty -s reload
[root@192 ~]# curl localhost/get
8

下面将介绍一些操作共享内存的函数,这些函数都是原子操作,只要在同一个内存区域,在多个worker 进程间并发都是安全的。

ngx.shared.DICT.set

1
syntax: success, err, forcible = ngx.shared.DICT:set(key, value, exptime?, flags?)

无条件设置一个共享内存的值,返回3个值:

success:布尔值,是否成功
err:错误信息
forcible:布尔值,是否由于内存不足把其他有效的值挤出去了

value 值可以是 布尔值、数字、字符串、nil,同过get返回的保留这些类型。
exptime 可选值用来设置过期时间(秒),精度为 0.001s。默认值 0 代表不过期。
flags 可选值,一个标记,无符号32位整形,默认为0,通过 get 也可以返回。

当内存不够分配,那么会强制移除最老的项根据过期时间排序,如果移除了还是由于某种原因不能新增数据,那么 success 为fasle,错误信息存入 err。

1
2
local cats = ngx.shared.cats
local succ, err, forcible = cats.set(cats, "Marry", "it is a nice cat!")

也可以是下面这种方式

1
2
local cats = ngx.shared.cats
local succ, err, forcible = cats:set("Marry", "it is a nice cat!")

ngx.shared.DICT.safe_set

1
syntax: ok, err = ngx.shared.DICT:safe_set(key, value, exptime?, flags?)

同上,只是当内存不足时不会强制移除没过期的项目来腾出空间,直接返回 nil 和 错误信息。

ngx.shared.DICT.add

1
syntax: success, err, forcible = ngx.shared.DICT:add(key, value, exptime?, flags?)

同 ngx.shared.DICT.set,只是如果 key 已经存在且没过期,返回 fasle,err 设置为 "exists"。

ngx.shared.DICT.safe_add

1
syntax: ok, err = ngx.shared.DICT:safe_add(key, value, exptime?, flags?)

同上,只是当内存不足时不会强制移除没过期的项目来腾出空间,直接返回 nil 和 错误信息。

ngx.shared.DICT.get

1
syntax: value, flags = ngx.shared.DICT:get(key)

获取共享内存的值,如果 key 不存在或者过期,返回 nil。出错返回 nil 和 错误信息。

1
2
local sh_dog = ngx.shared.dogs
local value,flags = sh_dog.get(sh_dog, "Jim")

另一种获取方式

1
2
local sh_dog = ngx.shared.dogs
local value,flags = sh_dog:get("Jim")

ngx.shared.DICT.get_stale

1
syntax: value, flags, stale = ngx.shared.DICT:get_stale(key)

同上,就算过期了也返回(只要没清除)。stale 代表是否过期了。

ngx.shared.DICT.replace

1
syntax: success, err, forcible = ngx.shared.DICT:replace(key, value, exptime?, flags?)

类似 set,只是 key 必须存在且没过期才会替换成功,否则返回 false,err为 "not found"。

ngx.shared.DICT.delete

1
syntax: ngx.shared.DICT:delete(key)

无条件删除,等同于

1
ngx.shared.DICT:set(key, nil)

ngx.shared.DICT.incr

1
syntax: newval, err, forcible? = ngx.shared.DICT:incr(key, value, init?, init_ttl?)

将 key 里面的数字值增加 value,并返回增加后的数值。value 和 init 可以是正负整数,正负浮点数。

如果 key 里面原来的值不是数字,返回 nil,err设置为 "not a number"。

如果 key 不存在或者过期:

1.如果提供了 init,那么将会新增 key,并初始化值为 init+value,如果内存不足,还是会挤掉最老的值,如果挤掉了,那么 forcible 就为 true。init_ttl 代表新增的 key 的过期时间,默认为 0,不过期。
2.如果没提供,返回 nil,err设置为 "not found"。

操作列表

ngx.shared.DICT.lpush

1
syntax: length, err = ngx.shared.DICT:lpush(key, value)

从key对应的列表的最左边插入 value,返回插入后列表的元素个数。如果内存不足,直接返回 nil,err设置为 "no memory"。

如果 key 不存在,那么会新建空列表后再插入,如果 key 已经存在但是不是列表,返回 nil,err为 "value not a list"。

ngx.shared.DICT.rpush

1
syntax: length, err = ngx.shared.DICT:rpush(key, value)

同上,只是从右边插入。

ngx.shared.DICT.lpop

1
syntax: val, err = ngx.shared.DICT:lpop(key)

移除由key指定的列表最左边的值并返回,key 不存在返回 nil,key存在但是不是列表,返回nil,err为 "value not a list"。

ngx.shared.DICT.rpop

1
syntax: val, err = ngx.shared.DICT:rpop(key)

同上,只是从右边移除。

ngx.shared.DICT.llen

1
syntax: len, err = ngx.shared.DICT:llen(key)

返回指定 key 列表的元素个数,key 不存在返回0,存在但是不是列表返回 nil,err 为 "value not a list"。

ngx.shared.DICT.ttl

1
2
3
syntax: ttl, err = ngx.shared.DICT:ttl(key)

requires: resty.core.shdict or resty.core

返回指定 key 的过期时间(秒),精度为 0.001,返回 0 代表永不过期。如果 key 不存在或者过期,返回 nil,err为 "not found"。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
lua_shared_dict dogs 10m;
server {
location /set {
content_by_lua_block {
local dogs = ngx.shared.dogs
dogs:set("Jim", 8, 100)
ngx.say("STORED")
}
}
location /get {
content_by_lua_block {
require "resty.core"
local dogs = ngx.shared.dogs
local ttl,err = dogs:ttl("Jim")

ngx.say(ttl)
}
}
}
[root@192 ~]# curl localhost/set
STORED

[root@192 ~]# curl localhost/get
98.622
[root@192 ~]# curl localhost/get
96.542
[root@192 ~]# curl localhost/get
95.744

ngx.shared.DICT.expire

1
2
3
syntax: success, err = ngx.shared.DICT:expire(key, exptime)

requires: resty.core.shdict or resty.core

更新 key 的过期时间单位秒,精度0.001,0代表永不过期。如果 key 不存在返回 nil,err为 "not found"。

ngx.shared.DICT.flush_all

1
syntax: ngx.shared.DICT:flush_all()

标记所有的key为过期。

ngx.shared.DICT.flush_expired

1
syntax: flushed = ngx.shared.DICT:flush_expired(max_count?)

将过期的key删除并释放内存,max_count 限制删除的数量,默认为0,代表不限制。

ngx.shared.DICT.get_keys

1
syntax: keys = ngx.shared.DICT:get_keys(max_count?)

从字典里获取key,默认只会返回前 1024 个,如果指定了 max_count,那么返回 max_count 个,如果 max_count 为0,返回全部。

注意:因为该操作会锁住共享内存,如果 key 很多不建议使用该函数,因为会让需要用到该共享内存的 worker 进程阻塞。

ngx.shared.DICT.capacity

1
2
3
syntax: capacity_bytes = ngx.shared.DICT:capacity()

requires: resty.core.shdict or resty.core

返回共享内存的容量(由 lua_shared_dict 指定),单位字节。

ngx.shared.DICT.free_space

1
2
3
syntax: free_page_bytes = ngx.shared.DICT:free_space()

requires: resty.core.shdict or resty.core

获取共享内存的空闲空间,由于内部 nginx 是用 slab 分配器分配的内存,返回0可能还是有空间来存储新的值。

简单的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
     lua_shared_dict dogs 10m;
server {
location /set {
content_by_lua_block {
local dogs = ngx.shared.dogs
dogs:set("Jim", 8, 100)
dogs:lpush("list_1", 3)
dogs:lpush("list_1", 2)
dogs:lpush("list_1", 1)
dogs:rpush("list_1", 4)

dogs:incr("Jim", 4)
}
}
location /get {
content_by_lua_block {
require "resty.core"
local dogs = ngx.shared.dogs
local ttl,err = dogs:ttl("Jim")
local l = dogs:get("list_1")
local val1,err = dogs:lpop("list_1")
local val2,err = dogs:lpop("list_1")


ngx.say("ttl:",ttl)
ngx.say("get list type:",type(l))
ngx.say("lpop1:", val1)
ngx.say("lpop2:", val2)
}
}
}
[root@192 ~]# curl localhost/set
[root@192 ~]# curl localhost/get
ttl:98.44
get list type:nil
lpop1:1
lpop2:2