Skip to content

Commit a055282

Browse files
fix: use shdict instead of events module for nodes data exchange (#13066)
1 parent 7ccbfda commit a055282

5 files changed

Lines changed: 137 additions & 62 deletions

File tree

apisix/cli/ops.lua

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -767,6 +767,16 @@ Please modify "admin_key" in conf/config.yaml .
767767

768768
end
769769

770+
-- inject consul discovery shared dict
771+
if enabled_discoveries["consul"] then
772+
if not sys_conf["discovery_shared_dicts"] then
773+
sys_conf["discovery_shared_dicts"] = {}
774+
end
775+
776+
local consul_conf = yaml_conf.discovery["consul"]
777+
sys_conf["discovery_shared_dicts"]["consul"] = consul_conf.shared_size or "10m"
778+
end
779+
770780
-- fix up lua path
771781
sys_conf["extra_lua_path"] = get_lua_path(yaml_conf.apisix.extra_lua_path)
772782
sys_conf["extra_lua_cpath"] = get_lua_path(yaml_conf.apisix.extra_lua_cpath)

apisix/discovery/consul/init.lua

Lines changed: 120 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ local ngx_timer_at = ngx.timer.at
3131
local ngx_timer_every = ngx.timer.every
3232
local log = core.log
3333
local json_delay_encode = core.json.delay_encode
34+
local process = require("ngx.process")
3435
local ngx_worker_id = ngx.worker.id
3536
local exiting = ngx.worker.exiting
3637
local thread_spawn = ngx.thread.spawn
@@ -42,16 +43,28 @@ local null = ngx.null
4243
local type = type
4344
local next = next
4445

45-
local all_services = core.table.new(0, 5)
46+
local consul_dict = ngx.shared.consul
47+
if not consul_dict then
48+
error("lua_shared_dict \"consul\" not configured")
49+
end
50+
4651
local default_service
4752
local default_weight
4853
local sort_type
4954
local skip_service_map = core.table.new(0, 1)
5055
local dump_params
5156

52-
local events
53-
local events_list
5457
local consul_services
58+
-- Per-worker LRU cache: avoids shared dict access on every request.
59+
-- neg_ttl caches unknown services. invalid_stale ensures expired
60+
-- entries are refreshed from the shared dict instead of re-cached.
61+
local nodes_cache = core.lrucache.new({
62+
ttl = 1,
63+
count = 1024,
64+
invalid_stale = true,
65+
neg_ttl = 1,
66+
neg_count = 64,
67+
})
5568

5669
local default_skip_services = {"consul"}
5770
local default_random_range = 5
@@ -66,53 +79,94 @@ local _M = {
6679
}
6780

6881

69-
local function discovery_consul_callback(data, event, source, pid)
70-
all_services = data
71-
log.notice("update local variable all_services, event is: ", event,
72-
"source: ", source, "server pid:", pid,
73-
", all services: ", json_delay_encode(all_services, true))
74-
end
82+
local function fetch_node_from_shdict(service_name)
83+
local value = consul_dict:get(service_name)
84+
if not value then
85+
return nil, "consul service not found: " .. service_name
86+
end
7587

88+
local nodes, err = core.json.decode(value)
89+
if not nodes then
90+
return nil, "failed to decode nodes for service: "
91+
.. service_name .. ", error: " .. (err or "")
92+
end
7693

77-
function _M.all_nodes()
78-
return all_services
94+
return nodes
7995
end
8096

8197

82-
function _M.nodes(service_name)
83-
if not all_services then
84-
log.error("all_services is nil, failed to fetch nodes for : ", service_name)
85-
return
98+
function _M.all_nodes()
99+
local keys = consul_dict:get_keys(0)
100+
local services = core.table.new(0, #keys)
101+
for i, key in ipairs(keys) do
102+
local value = consul_dict:get(key)
103+
if value then
104+
local nodes, err = core.json.decode(value)
105+
if nodes then
106+
services[key] = nodes
107+
else
108+
log.error("failed to decode nodes for service: ", key, ", error: ", err)
109+
end
110+
end
111+
112+
if i % 100 == 0 then
113+
ngx.sleep(0)
114+
end
86115
end
116+
return services
117+
end
87118

88-
local resp_list = all_services[service_name]
89119

90-
if not resp_list then
91-
log.error("fetch nodes failed by ", service_name, ", return default service")
120+
function _M.nodes(service_name)
121+
local nodes, err = nodes_cache(service_name, nil,
122+
fetch_node_from_shdict, service_name)
123+
if not nodes then
124+
log.error("fetch nodes failed by ", service_name, ", error: ", err)
92125
return default_service and {default_service}
93126
end
94127

95-
log.info("process id: ", ngx_worker_id(), ", all_services[", service_name, "] = ",
96-
json_delay_encode(resp_list, true))
128+
log.info("process id: ", ngx_worker_id(), ", [", service_name, "] = ",
129+
json_delay_encode(nodes, true))
97130

98-
return resp_list
131+
return nodes
99132
end
100133

101134

102135
local function update_all_services(consul_server_url, up_services)
103-
-- clean old unused data
136+
-- write new/updated values first so readers never see a missing service
137+
local i = 0
138+
for k, v in pairs(up_services) do
139+
local content, err = core.json.encode(v)
140+
if content then
141+
local ok, set_err, forcible = consul_dict:set(k, content)
142+
if not ok then
143+
log.error("failed to set nodes for service: ", k, ", error: ", set_err,
144+
", please consider increasing lua_shared_dict consul size")
145+
elseif forcible then
146+
log.warn("consul shared dict is full, forcibly evicting items while ",
147+
"setting nodes for service: ", k,
148+
", please consider increasing lua_shared_dict consul size")
149+
end
150+
else
151+
log.error("failed to encode nodes for service: ", k, ", error: ", err)
152+
end
153+
i = i + 1
154+
if i % 100 == 0 then
155+
ngx.sleep(0)
156+
end
157+
end
158+
159+
-- then delete keys that are no longer present
104160
local old_services = consul_services[consul_server_url] or {}
105161
for k, _ in pairs(old_services) do
106-
all_services[k] = nil
162+
if not up_services[k] then
163+
consul_dict:delete(k)
164+
end
107165
end
108-
core.table.clear(old_services)
109166

110-
for k, v in pairs(up_services) do
111-
all_services[k] = v
112-
end
113167
consul_services[consul_server_url] = up_services
114168

115-
log.info("update all services: ", json_delay_encode(all_services, true))
169+
log.info("update all services to shared dict")
116170
end
117171

118172

@@ -149,14 +203,30 @@ local function read_dump_services()
149203
return
150204
end
151205

152-
all_services = entity.services
153-
log.info("load dump file into memory success")
206+
for k, v in pairs(entity.services) do
207+
local content, json_err = core.json.encode(v)
208+
if content then
209+
consul_dict:set(k, content)
210+
else
211+
log.error("failed to encode dump service: ", k, ", error: ", json_err)
212+
end
213+
end
214+
log.info("load dump file into shared dict success")
154215
end
155216

156217

157218
local function write_dump_services()
219+
-- build services from the privileged agent's in-memory tracking table
220+
-- to avoid a full shared dict scan + JSON decode via _M.all_nodes()
221+
local services = core.table.new(0, 8)
222+
for _, svcs in pairs(consul_services) do
223+
for k, v in pairs(svcs) do
224+
services[k] = v
225+
end
226+
end
227+
158228
local entity = {
159-
services = all_services,
229+
services = services,
160230
last_update = ngx.time(),
161231
expire = dump_params.expire, -- later need handle it
162232
}
@@ -556,14 +626,6 @@ function _M.connect(premature, consul_server, retry_delay)
556626

557627
update_all_services(consul_server.consul_server_url, up_services)
558628

559-
--update events
560-
local post_ok, post_err = events:post(events_list._source,
561-
events_list.updating, all_services)
562-
if not post_ok then
563-
log.error("post_event failure with ", events_list._source,
564-
", update all services error: ", post_err)
565-
end
566-
567629
if dump_params then
568630
ngx_timer_at(0, write_dump_services)
569631
end
@@ -611,35 +673,32 @@ end
611673

612674
function _M.init_worker()
613675
local consul_conf = local_conf.discovery.consul
676+
dump_params = consul_conf.dump
614677

615-
if consul_conf.dump then
616-
local dump = consul_conf.dump
617-
dump_params = dump
618-
619-
if dump.load_on_init then
620-
read_dump_services()
621-
end
622-
end
623-
624-
events = require("apisix.events")
625-
events_list = events:event_list(
626-
"discovery_consul_update_all_services",
627-
"updating"
628-
)
629-
630-
if 0 ~= ngx_worker_id() then
631-
events:register(discovery_consul_callback, events_list._source, events_list.updating)
632-
return
633-
end
634-
635-
log.notice("consul_conf: ", json_delay_encode(consul_conf, true))
636678
default_weight = consul_conf.weight
637679
sort_type = consul_conf.sort_type
638680
-- set default service, used when the server node cannot be found
639681
if consul_conf.default_service then
640682
default_service = consul_conf.default_service
641683
default_service.weight = default_weight
642684
end
685+
686+
if process.type() ~= "privileged agent" then
687+
return
688+
end
689+
690+
-- flush stale data that may persist across reloads,
691+
-- since consul_services is re-initialized empty
692+
consul_dict:flush_all()
693+
694+
if consul_conf.dump then
695+
if consul_conf.dump.load_on_init then
696+
read_dump_services()
697+
end
698+
end
699+
700+
log.notice("consul_conf: ", json_delay_encode(consul_conf, true))
701+
643702
if consul_conf.skip_services then
644703
skip_service_map = core.table.new(0, #consul_conf.skip_services)
645704
for _, v in ipairs(consul_conf.skip_services) do
@@ -673,7 +732,7 @@ end
673732

674733

675734
function _M.dump_data()
676-
return {config = local_conf.discovery.consul, services = all_services }
735+
return {config = local_conf.discovery.consul, services = _M.all_nodes()}
677736
end
678737

679738

apisix/discovery/consul/schema.lua

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ return {
2424
type = "string",
2525
}
2626
},
27+
shared_size = {
28+
type = "string",
29+
pattern = [[^[1-9][0-9]*m$]],
30+
default = "1m",
31+
},
2732
token = {type = "string", default = ""},
2833
fetch_interval = {type = "integer", minimum = 1, default = 3},
2934
keepalive = {

t/APISIX.pm

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,7 @@ lua {
294294
lua_shared_dict standalone-config 10m;
295295
lua_shared_dict status-report 1m;
296296
lua_shared_dict nacos 10m;
297+
lua_shared_dict consul 10m;
297298
lua_shared_dict upstream-healthcheck 10m;
298299
}
299300
_EOC_

t/discovery/consul_dump.t

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ GET /hello
230230
--- error_code: 503
231231
--- error_log
232232
connect consul
233-
fetch nodes failed
233+
consul service not found
234234
failed to set upstream
235235
236236

0 commit comments

Comments
 (0)