@@ -31,6 +31,7 @@ local ngx_timer_at = ngx.timer.at
3131local ngx_timer_every = ngx .timer .every
3232local log = core .log
3333local json_delay_encode = core .json .delay_encode
34+ local process = require (" ngx.process" )
3435local ngx_worker_id = ngx .worker .id
3536local exiting = ngx .worker .exiting
3637local thread_spawn = ngx .thread .spawn
@@ -42,16 +43,28 @@ local null = ngx.null
4243local type = type
4344local 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+
4651local default_service
4752local default_weight
4853local sort_type
4954local skip_service_map = core .table .new (0 , 1 )
5055local dump_params
5156
52- local events
53- local events_list
5457local 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
5669local default_skip_services = {" consul" }
5770local 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
7995end
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
99132end
100133
101134
102135local 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 " )
116170end
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" )
154215end
155216
156217
157218local 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
612674function _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
673732
674733
675734function _M .dump_data ()
676- return {config = local_conf .discovery .consul , services = all_services }
735+ return {config = local_conf .discovery .consul , services = _M . all_nodes () }
677736end
678737
679738
0 commit comments