Yukang's Page

Kong集群Left Cluster Node问题

问题

Kong在实践中会有一些疑惑的地方,这里记录一下。注意这里记录的Kong集群部署的问题是0.10.3版本的,最新Kong版本已经不是通过serf来管理不同节点之间的配置同步问题。

在Kong多节点部署的时候,有时候某个节点停掉后,我们在后台可以看到left的信息,而且这个left信息会保留一段不短的时间。类似于如下:

kong-left

分析

管理后台Konga是通过api获取的节点信息,在kong/kong/api/routes/cluster.lua文件里可以看到如下路由处理逻辑:

GET = function(self, dao_factory, helpers)
local members, err = singletons.serf:members()
if err then
return responses.send_HTTP_INTERNAL_SERVER_ERROR(err)
end
local result = {data = {}}
for _, v in pairs(members) do
if not self.params.status or (self.params.status and v.status == self.params.status) then
table_insert(result.data, {
name = v.name,
address = v.addr,
status = v.status
})
end
end
result.total = #result.data
return responses.send_HTTP_OK(result)
end,

具体serf:members()的实现在serf.lua里面可以看到,就是执行了serf cluster members命令获取结果然后返回JSON。所以我们在服务器上执行这个命令其实也可以看到类似的结果:

kong-left-cmd

那么问题的根源当然是在Serf本身里面,通过看文档发现原来确实是有一定延迟的。

Serf keeps the state of dead nodes around for a set amount of time, so that when full syncs are requested, the requester also receives information about dead nodes. Because SWIM doesn’t do full syncs, SWIM deletes dead node state immediately upon learning that the node is dead. This change again helps the cluster converge more quickly.

参考serf文档»

serf的具体实现

接着稍微看了一下Serf的代码,果然Go的项目代码直观好读。在Serf这个结构体里面保存了一个leftMembers的状态列表,每次收到left事件的时候处理逻辑是:

// handleNodeLeaveIntent is called when an intent to leave is received.
func (s *Serf) handleNodeLeaveIntent(leaveMsg *messageLeave) bool {
..................
// State transition depends on current state
switch member.Status {
case StatusAlive:
member.Status = StatusLeaving
member.statusLTime = leaveMsg.LTime
return true
case StatusFailed:
member.Status = StatusLeft
member.statusLTime = leaveMsg.LTime
// Remove from the failed list and add to the left list. We add
// to the left list so that when we do a sync, other nodes will
// remove it from their failed list.
s.failedMembers = removeOldMember(s.failedMembers, member.Name)
s.leftMembers = append(s.leftMembers, member)
................
return true
default:
return false
}
}

通过索引变量发现这个列表会定时通过handleReap函数更新,逻辑如下:

// handleReap periodically reaps the list of failed and left members, as well
// as old buffered intents.
func (s *Serf) handleReap() {
for {
select {
case <-time.After(s.config.ReapInterval):
s.memberLock.Lock()
now := time.Now()
s.failedMembers = s.reap(s.failedMembers, now, s.config.ReconnectTimeout)
s.leftMembers = s.reap(s.leftMembers, now, s.config.TombstoneTimeout)
reapIntents(s.recentIntents, now, s.config.RecentIntentTimeout)
s.memberLock.Unlock()
case <-s.shutdownCh:
return
}
}
}

所以看起来这里相关的Timeout是s.config.TombstoneTimeout, 接着需要看看reap到底做了什么,这里果然是把到了一定时间间隔的节点删掉了:

// reap is called with a list of old members and a timeout, and removes
// members that have exceeded the timeout. The members are removed from
// both the old list and the members itself. Locking is left to the caller.
func (s *Serf) reap(old []*memberState, now time.Time, timeout time.Duration) []*memberState {
n := len(old)
for i := 0; i < n; i++ {
m := old[i]
// Skip if the timeout is not yet reached
if now.Sub(m.leaveTime) <= timeout {
continue
}
// Delete from the list
old[i], old[n-1] = old[n-1], nil
old = old[:n-1]
n--
i--
..........
}
return old
}

那么这个时间间隔是多久呢,在serf/config.go有一个默认配置:

TombstoneTimeout: 24 * time.Hour,

其他

serf这个软件值得好好分析一下,节点的状态同步和事件处理都是分布式软件的基础,后续继续看看这个gossip protocol based on SWIM的具体实现。另外hashicorp这个公司的开源代码和文档都非常好,值得学习一番。

发表评论

Docker compose初始化失败问题

问题今天在Docker Postgresql用户名和密码授权的问题上花了一些时间,问题是: psql: FATAL: password authentication failed for user "postgres" admin的用户名和密码是可以在docker-compose.y .....
阅读全文

使用overcommit生成git hooks

git hooks很方便地可以在git操作流程的各个阶段加入hooks,比如执行一些脚本来检查代码风格、跑单元测试、做代码静态检查等。git hooks的试用方法是在.git/hooks目录下写各种脚本,但是.git目录的这些脚本是不会checkin到repo里的,所以如果一个代码如果被 .....
阅读全文

Nginx https too many redirect

Http请求在经过多层Nginx的时候,通常强制http跳转到https的时候会这样配置: return 302 https://$host$request_uri; ## 需要注意这里是request_uri而不是uri,否则会引起安全问题 但是如果是多层Nginx,前面的Nginx .....
阅读全文

Linux 当前 shell 执行命令

Linux shell 执行脚本的时候一般是会 fork 出一个子 shell,这样在有的时候就不方便了,比如要unset 当前shell 的环境变量等, #!/usr/bin/env zshif [ -z $http_proxy ]; then echo "not using p .....
阅读全文

Ruby的 open 函数导致命令执行

说明首先看看 open 函数的文档说明, https://apidock.com/ruby/v1_9_3_392/Kernel/open/class: If path starts with a pipe character, a subprocess is created, con .....
阅读全文

BuckleScript and Reason

BuckleScript虽然我不是前端工程师,不过因为喜欢 OCaml,所以偶尔关注 BuckleScript 有一段时间了,今天又花时间看了看文档和代码。BuckleScript 是张宏波主导开发的开源项目,『有希望成为第一个完全由国人设计主导实现并被世界各地广泛使用的编译器』,不过是 .....
阅读全文

《深度工作-如何有效使用每一点脑力》读后

深度工作这本书主要讲解了一些时间和精力管理方面的东西,人到了一定年龄就会觉得时间不够用,日子过得太快,每天觉得都没干什么就过去了。工作几年后这种感觉时不时袭来。反而是如果某段时间一直有一个阶段性的目标,就会觉得很踏实,进度和效率也可以。那种完全沉浸在思考中的状态真的也并不是累,相反所得到 .....
阅读全文

Kong的0.11.0版本

Kong发布了新的版本0.11.0,从这里开始区分了社区版本和商业版。这次改动比较大的是丢弃了serf,这样整个Kong节点之间的缓存同步方式变化了。开发者给出的理由如下: 依赖serf,serf并不属于Nginx/OpenResty 这种依赖相互间通信来同步的机制对于deploym .....
阅读全文

Lua时间处理

我需要用Lua处理一个与时间相关的问题,比如我们在配置文件里面配置一个日期(北京时间),然后在Openresty里面判断当前时间是否在这个日期之前或者之后来做对应的逻辑。 Lua的时间处理还有点麻烦,主要是自带的相关库函数比较少。 os.time() &l .....
阅读全文
Prev Next