# OpenResty学习指南（二）

2019/04/10 10:10

## 数据结构table

table并没有区分开数组、哈希、集合等概念，而是揉在了一起。

``````local color = {first = "red", "blue", third = "green", "yellow"}
print(color["first"])                 --> output: red
print(color[1])                         --> output: blue
print(color["third"])                --> output: green
print(color[2])                         --> output: yellow
print(color[3])                         --> output: nil
``````

### table 库函数

#### 获取元素个数

``````\$ resty -e 'local t = { 1, 2, 3 }
print(table.getn(t)) ' # 返回3
``````

``````\$ resty -e 'local t = { 1, a = 2 }
print(#t) ' #返回1
``````

``````local nkeys = require "table.nkeys"

print(nkeys({}))  -- 0
print(nkeys({ "a", nil, "b" }))  -- 2
print(nkeys({ dog = 3, cat = 4, bird = nil }))  -- 2
print(nkeys({ "a", dog = 3, cat = 4 }))  -- 3
``````

#### 删除指定元素

``````\$ resty -e 'local color = {first = "red", "blue", third = "green", "yellow"}
table.remove(color, 1)
for k, v in pairs(color) do
print(v)
end'
``````

#### 元素拼接函数

table.concat 以按照下标，把 table 中的元素拼接起来。只能拼接数组部分

``````\$ resty -e 'local color = {first = "red", "blue", third = "green", "yellow"}
print(table.concat(color, ", "))'
``````

#### 插入一个元素

table.insert 函数，可以下标插入一个新的元素，自然，影响的还是 table 的数组部分。

``````\$ resty -e 'local color = {first = "red", "blue", third = "green", "yellow"}
table.insert(color, 1,  "orange")
print(color[1])
'
``````

color 的第一个元素变为了 orange。当然，你也可以不指定下标，这样就会默认插入队尾。

### 优化

#### 预先生成数组

``````local new_tab = require "table.new"
local t = new_tab(100, 0)
for i = 1, 100 do
t[i] = i
end
``````

#### 循环使用单个 table

table.clear 函数它会把数组中的所有数据清空，但数组的大小不会变。 如下：

``````local local_plugins = {}

core.table.clear(local_plugins)

local local_conf = core.config.local_conf()
local plugin_names = local_conf.plugins

local processed = {}
for _, name in ipairs(plugin_names) do
if processed[name] == nil then
processed[name] = true
insert_tab(local_plugins, name)
end
end

return local_plugins
``````

local_plugins 这个数组，是 plugin 这个模块的 top level 变量。在 load 这个加载插件函数的开始位置， table 就会被清空，然后根据当前的情况生成新的插件列表。

#### table 池

lua-tablepool，可以用缓存池的方式来保存多个 table，以便随用随取。

``````local tablepool = require "tablepool"
local tablepool_fetch = tablepool.fetch
local tablepool_release = tablepool.release

local pool_name = "some_tag"
local function do_sth()
local t = tablepool_fetch(pool_name, 10, 0)
-- -- using t for some purposes
tablepool_release(pool_name, t)
end
``````

## 缓存

OpenResty 中有两个缓存的组件：shared dict 缓存和 lru 缓存。前者只能缓存字符串对象，缓存的数据有且只有一份，每一个 worker 都可以进行访问，所以常用于 worker 之间的数据通信。后者则可以缓存所有的 Lua 对象，但只能在单个 worker 进程内访问，有多少个 worker，就会有多少份缓存数据。

### shared dict

``````\$ resty --shdict='dogs 1m' -e 'local dict = ngx.shared.dogs
dict:set("Tom", 56)
print(dict:get("Tom"))'
``````

``````resty --shdict='dogs 1m' -e 'local dict = ngx.shared.dogs
dict:set("Tom", 56, 0.01)
ngx.sleep(0.02)
local val, flags, stale = dict:get_stale("Tom")
print(val)'
``````

### lru 缓存

lru 缓存的接口只有 5 个：new、set、get、delete 和 flush_all。

``````resty -e 'local lrucache = require "resty.lrucache"
local cache, err = lrucache.new(200)
cache:set("dog", 32, 0.01)
ngx.sleep(0.02)
local data, stale_data = cache:get("dog")
print(stale_data)'
``````

### lua-resty-mlcache

lua-resty-mlcache用 shared dict 和 lua-resty-lrucache ，实现了多层缓存机制。

``````local mlcache = require "resty.mlcache"

local cache, err = mlcache.new("cache_name", "cache_dict", {
lru_size = 500,    -- size of the L1 (Lua VM) cache
ttl = 3600,   -- 1h ttl for hits
neg_ttl  = 30,     -- 30s ttl for misses
})
if not cache then
error("failed to create mlcache: " .. err)
end
``````

``````local function fetch_user(id)
return db:query_user(id)
end

local id = 123
local user , err = cache:get(id , nil , fetch_user , id)
if err then
ngx.log(ngx.ERR , "failed to fetch user: ", err)
return
end

if user then
print(user.id) -- 123
end
``````

L1 缓存就是 lua-resty-lrucache。每一个 worker 中都有自己独立的一份，有 N 个 worker，就会有 N 份数据，自然也就存在数据冗余。

L2 缓存是 shared dict。所有的 worker 共用一份缓存数据，在 L1 缓存没有命中的情况下，就会来查询 L2 缓存。

L3 则是在 L2 缓存也没有命中的情况下，需要执行回调函数去外部数据库等数据源查询后，再缓存到 L2 中。

1. 首先会去查询 worker 内的 L1 缓存，如果 L1 命中就直接返回。
2. 如果 L1 没有命中或者缓存失效，就会去查询 worker 间的 L2 缓存。如果 L2 命中就返回，并把结果缓存到 L1 中。
3. 如果 L2 也没有命中或者缓存失效，就会调用回调函数，从数据源中查到数据，并写入到 L2 缓存中，这也就是 L3 数据层的功能。

``````local mlcache = require "resty.mlcache"

local cache, err = mlcache.new("my_mlcache", "cache_shm", {
l1_serializer = function(i)
return i + 2
end,
})

local function callback()
return 123456
end

local data = assert(cache:get("number", nil, callback))
assert(data == 123458)
``````

0
0 收藏

0 评论
0 收藏
0