kong的代码运行于nginx的worker进程中。kong对数据的修改会在一个worker中进行,修改后需通知给本机其他worker进程和其他机器上的worker进程。
kong 会将数据库记录缓存起来,这里只是缓存kong自身数据比如插件, service, route 数据, 自定义插件的数据表数据并未缓存, 例如: rate limit 插件的数据也要自己通过kong.cache来缓存, 通过/etc/kong/kong.conf 配置db_update_frequency确定更新route, service, plugins等数据的缓存时间。
kong使用的进程间通信主要方式有:
- 本机间通信-共享内存
- 跨机器通信-数据库
数据共享
kong 数据存在数据库,同时在缓存中保留一份。
当数据库数据被修改,需发出事件通知其他worker。其他worker收到事件后,删除缓存中对应的数据。
事件
1.本地事件: 通知本机上的worker, 由 lua-resty-worker-events 来处理
2.集群事件: 通知在多台机器上的worker, 由 cluster_events.lua 来处理
本地事件(共享内存)
本地事件: 通过共享内存实现。
kong实现了一套基于nginx共享内存的事件发布-订阅机制,源码见仓库https://github.com/Kong/lua-resty-worker-events。
该包提供post_local方法在worker进程内进行事件发布
提供post方法在本机worker进程间进行事件发布
这2个方法需要指定source, event来确定事件源。
kong的数据访问层dao.lua封装了insert、update和delete三个对数据操作的方法。
这三个方法分别会使用post_local发出
source为: dao:crud
event为insert、delete、update 事件。
事件的数据格式如下:
1 | { |
worker进程启动的时候会在init_worker阶段注册这些事件的订阅方法,见/usr/local/share/lua/5.1/kong/runloop/handler.lua:worker_events.register。
订阅方法中把所有的dao:crud事件按表名称使用post_local再进行分发。
集群事件(数据库)
集群事件通过数据库实现。数据库表cluster_events存放用于集群间分发的事件.
Column | Type | Collation | Nullable | Default
```
-----------+--------------------------+-----------+----------+---------
id | uuid | | not null |
node_id | uuid | | not null |
at | timestamp with time zone | | not null |
nbf | timestamp with time zone | | |
expire_at | timestamp with time zone | | not null |
channel | text | | |
data | text | | |
channel的类型有:
- invalidations, 表示路由规则、插件配置的变更
- balancer:targets, 表示负载均衡的targets列表发生变更
- balancer:upstreams, 表示upstream对象发生变更
- balancer:post_health, 表示target的健康状态发生变更。由于被动健康检查拉出实例后,kong不会在对该实例进行自动拉入,需要通过该事件来拉入实例。
./runloop/handler.lua
调用 cluster_events.lua 代码中的 _M:broadcast(channel, data, nbf)方法会往cluster_events表中新增一条记录。
在init_worker阶段通过调用cluster_events:subscribe开启一个定时器,定时查询出cluster_events表中新增的记录。注意: 本机只会有一个worker进程会对数据库进行查询(通过加锁实现,代码见cluster_events:get_lock),查询出来后再通过共享内存的方式通知给本机其他worker.
/etc/kong/kong.conf#db_update_frequency 配置参数db_update_frequency 用于确定查询数据库的间隔. 数据范围根据at字段是否落在(起始时间, 结束时间]确定。起始时间第一次设置在init_worker阶段,调用ngx.now()获取当前时间(精确到毫秒)并放入key为cluster_events:at的共享内存中。之后抢到锁的worker会从共享内存中取出该时间,该时间需要减去db_update_propagation + 0.001来确定起始时间,以防止事件丢失。配置参数db_update_propagation默认为0。结束时间取ngx.now()的值。查询成功后会把结束时间覆盖之前的起始时间,并把该事件分发到本机的其他worker。对于设置了nbf的事件,kong如果发现还没到生效时间,就会通过ngx.timer设置一个定时器延后分发该事件.
详见: /usr/local/share/lua/5.1/kong/init.lua 的 Kong.init_worker() 中:
local cluster_events, err = kong_cluster_events.new {
db = kong.db,
poll_interval = kong.configuration.db_update_frequency,
poll_offset = kong.configuration.db_update_propagation,
}
Kong 通过这两个核心组件来完成集群中各个节点的状态更新。多个 Kong 节点连接到相同数据库时,便构建起了一个可以动态水平扩展的集群。
- 多级缓存 lua-resty-mlcache https://github.com/thibaultcha/lua-resty-mlcache
- worker 间事件通讯 lua-resty-worker-events
- 避免缓存失效风暴, 加锁策略 https://github.com/openresty/lua-resty-lock
KONG 需关注参数:
kong_db_cache 设置kong缓存大小, 用于缓存数据库数据
lua_max_running_timers 4096;
lua_max_pending_timers 16384;
lua_code_cache
http://cyukang.com/2017/07/22/kong-intro-4.html
https://ms2008.github.io/2018/06/11/kong-events-cache/
http://cyukang.com/2017/07/22/kong-intro-4.html
OpenResty-Best-Practices 最佳实践
如何设计可扩展的速率限制算法
https://konghq.com/blog/how-to-design-a-scalable-rate-limiting-algorithm/