0%

简单的源码阅读

链接: https://github.com/coreos/etcd/tree/release-2.3

Main

  1. 检查运行 arch
  2. parse 启动配置参数
  3. 设置日志级别(参考 admin guide debug part,可以打开 debug 级别日志,亦可单独打开特定 package 的 debug 日志)
  4. 声明停止通道 var stopped <-chan struct{}
  5. 打印 etcd 版本 / GitSHA / 编译时的 Go VERSION / 运行架构信息

检查数据目录是否存在,若存在日志提示 (只有 data-dir 存在,就会打印如下日志)

1
the server is already initialized as member before, starting as etcd member...

不管数据目录存在与否,均如此启动

1
stopped, err = startEtcd(cfg)

最后获取到停止信号停止

1
2
// etcd process stpo
<-stoped

startEtcd

首先会从 initial-cluster 中初始化 peer 信息,利用这些 peer 信息新建 peer listener,主要方法为 rafthttp.NewListener

接下来使用 listen-client-url 新建 client listener,这块直接使用 net.Listen 方法,开启 tcp 服务;然后判断系统文件描述符限制,如果 < 150,则 panic,然后限制 listener 的数量为 系统描述符限制 - 150

初始化 net.Listen 的 keepAliveListener

初始化 etcd server config

初始化 etcdserver

启动 etcdserver


注册操作系统终止回调函数 osutil.RegisterInterruptHandler(s.Stop)


使用 cors 初始化 clientHandler

使用 etcdhttp 初始化 peerHandler

goroutine 为每个 peer listener 开启 http 服务 处理请求,5 min read timeout

goroutine 为每个 client listener 开启 http 服务 处理请求,0 min read timeout // 与 golang 的一个 bug 有关

etcdhttp/peer.go

etcdhttp/client.go

其实是 http server,待阅读


最后,返回停止通道及错误

etcdserver.NewServer

初始化 etcdserver

集群信息存在 /0

key 信息存在 /1

检查数据目录版本,并更新数据目录

判断以何种方式启动

(1) !haveWAL && !cfg.NewCluster

没有 wal 目录且 new-cluster == false,此时通过从启动参数 initial-token 及 initial-cluster 配置的集群信息来访问集群(会将自己的 peer url 排除),获取到当前存在的集群信息

校验 启动参数中配置的集群信息与获取到存在的集群信息,并为 local member 设置 id

此时成员信息从 remote peer 中获取,当前 server 的 cluster id 被设置为 remote peer 中获取到 cluster id

打印启动参数 cfg.Print()

启动 raftnode

1
id, n, s, w = startNode(cfg, cl, nil)

简单的源码阅读

链接: https://github.com/coreos/etcd/tree/release-2.3

Main

  • 检查运行 arch
  • parse 启动配置参数
  • 设置日志级别(参考 admin guide debug part,可以打开 debug 级别日志,亦可单独打开特定 package 的 debug 日志)
  • 声明停止通道 var stopped <-chan struct{}
  • 打印 etcd 版本 / GitSHA / 编译时的 Go VERSION / 运行架构信息

检查数据目录是否存在,若存在日志提示 (只有 data-dir 存在,就会打印如下日志)

1
the server is already initialized as member before, starting as etcd member...

不管数据目录存在与否,均如此启动

1
stopped, err = startEtcd(cfg)

最后获取到停止信号停止

1
2
// etcd process stpo
<-stoped

startEtcd

首先会从 initial-cluster 中初始化 peer 信息,利用这些 peer 信息新建 peer listener,主要方法为 rafthttp.NewListener

接下来使用 listen-client-url 新建 client listener,这块直接使用 net.Listen 方法,开启 tcp 服务;然后判断系统文件描述符限制,如果 < 150,则 panic,然后限制 listener 的数量为 系统描述符限制 - 150

初始化 net.Listen 的 keepAliveListener

初始化 etcd server config

初始化 etcdserver

启动 etcdserver


注册操作系统终止回调函数 osutil.RegisterInterruptHandler(s.Stop)


使用 cors 初始化 clientHandler

使用 etcdhttp 初始化 peerHandler

goroutine 为每个 peer listener 开启 http 服务 处理请求,5 min read timeout

goroutine 为每个 client listener 开启 http 服务 处理请求,0 min read timeout // 与 golang 的一个 bug 有关

etcdhttp/peer.go

etcdhttp/client.go

其实是 http server,待阅读


etcdserver.NewServer

初始化 etcdserver

集群信息存在 /0

key 信息存在 /1

检查数据目录版本,并更新数据目录

判断以何种方式启动

(1) !haveWAL && !cfg.NewCluster

没有 wal 目录且 new-cluster == false,此时通过从启动参数 initial-token 及 initial-cluster 配置的集群信息来访问集群(会将自己的 peer url 排除),获取到当前存在的集群信息

校验 启动参数中配置的集群信息与获取到存在的集群信息,并为 local member 设置 id

此时成员信息从 remote peer 中获取,当前 server 的 cluster id 被设置为 remote peer 中获取到 cluster id

打印启动参数

1
cfg.Print()

启动 raftnode

1
id, n, s, w = startNode(cfg, cl, nil)

(2) !haveWAL && cfg.NewCluster

没有 wal 目录且 NewCluster == true

判断 isMemberBootstrapped,从 remote peer 中获取集群信息,如果 server id 已经存在集群中且其 client url 不为空,则表示已经被启动过了,返回错误

1
member XXX has already been bootstrapped

打印启动参数,并启动 raftnode

1
2
cfg.PrintWithInitial()
id, n, s, w = startNode(cfg, cl, cl.MemberIDs())

可见

  • initial advertise peer url
  • intial cluster

参数是用来 bootstrap 成员用的

(3) haveWAL

有 WAL 目录的情况

data-dir/member

data-dir/member/wal

data-dir/member/snap

load snapshot 文件

从 snapshot 文件中恢复

最后启动 raftNode

1
2
3
4
5
6
7
8
cfg.Print()
if !cfg.ForceNewCluster {
id, cl, n, s, w = restartNode(cfg, snapshot)
} else {
id, cl, n, s, w = restartAsStandaloneNode(cfg, snapshot)
}
cl.SetStore(st)
cl.Recover()

注意备份文件恢复时,一定要使用 force-new-start 的原理就在这里了

判断完了以何种方式启动 raftnode 之后,开始初始化 EtcdServer,并且初始化 rafthttp.Transport,启动 rafthttp.Transport,将从 remote peer 获取到的 peer url 信息全部加入 tr.AddRemote,AddRemote 中会判断是否已经存在 peer 或者 remote 中,已存在则不再加入;将 cluster 中的 member 信息全部加入 tr.AddPeer

当然都是除了自己,加入之前判断了 id,如果 id == 本身则不加入

不过我还是不太明白 remote 和 peer 有啥区别,目前

得看看 remote.go 和 peer.go 都干了什么

EtcdServer.Start

  1. EtcdServer.start()
  2. goroutine publish
  3. goroutine purge file
  4. goroutine monit file descriptor
  5. goroutine monitor versions

EtcdServer.publish

注册 server 信息到 cluster 中,并更新 server 的静态 client url

publish 的过程比较直接,调用 pb.Request 方法,将其 attributes 即 name 和 clientURLs 写入集群中

即写入 key / value 中 (v2 的存储)

key 的格式为 /0/members/id/attributes

实践中,当成员无法 publish 至集群时,一般发生的错误为超时;超时后会重试,毕竟是个 for loop,在 publish 成功或者成员停止时直接 return 结束 for loop 否则会持续 publish

EtcdServer.purge

启了个 goroutine 删除超出 threshold 的 snap 和 wal 文件

EtcdServer.monitorFileDescriptor

启了个 goroutine 检查系统当前使用的 fd 数量是否超过了 limit 的 80%

EtcdServer.monitorVersions

启了个 goroutine 检查集群 version

EtcdServer.start

设置默认 snapshotCount,初始化 done / stop 通道,打印集群版本信息,goroutine run

EtcdServer.run

raftnode start

待阅读

startRemote

newPeerStatus(to)

这个结构体中提供了 activate / deactivate 方法

即日志中常见的 the connection with [Member ID] became active 信息

随后初始化了 remote 结构体

remote 结构体中初始化了 pipeline

sync.waitGroup 初始化为 4

启了 4 个 goroutine 跑 handle

handle 从 msgc 通道中获取 raftpb.Message,并使用 post 方法发送出去

waitGroup 在 stop 方法内调用 wait,确保 handle goroutine 均已关闭

startPeer

newPeerStatus(to)

初始化 peer 结构体

初始化 msgAppV2Writer: msgAppV2Writer / writer: startStreamWriter

初始化 pipeline: newPipeline / snapSender: newSnapshotSender


goroutine startPeer 从 recvc 通道中获取 message 交由底层 raft 处理


goroutine startPeer 从 propc 通道中获取 message 交由底层 raft 处理


初始化 p.msgAppV2Reader = startStreamReader

初始化 p.msgAppReader = startStreamReader

1
2
3
4
5
6
7
8
9
10
11
// peer is the representative of a remote raft node. Local raft node sends
// messages to the remote through peer.
// Each peer has two underlying mechanisms to send out a message: stream and
// pipeline.
// A stream is a receiver initialized long-polling connection, which
// is always open to transfer messages. Besides general stream, peer also has
// a optimized stream for sending msgApp since msgApp accounts for large part
// of all messages. Only raft leader uses the optimized stream to send msgApp
// to the remote follower node.
// A pipeline is a series of http clients that send http requests to the remote.
// It is only used when the stream has not been established.

peer 是 remote raft node 的代表,本地 raft node 通过 peer 发送 message 到 remote

stream and pipeline

pipeline 是一系列 http clients,用于向 remote 发送 http 请求,它只有在 stream 没有建立起来时使用

stream 是接收者 long-polling 链接,用于传递 message;另外 raft leader 使用优化过的 stream 发送 msgApp 信息

stream run 起来之后,尝试去 dial 远端,dial 未返回错误后,将 peerStatus 设置为 active,即此时日志中会打印 the connection with [Member ID] became active

dial 如果返回 errUnsupportedStreamType 亦或是 err := cr.decodeLoop(rc, t) 返回的 err 不是 EOF 或者链接被关闭,则 peerStatus 被设置为 inactive

stream 每 100 ms 会重新尝试 dial remote peer,如果出现 request sent was ignored (cluster ID mismatch: remote[remote member id]=X-Etcd-Cluster-ID in http header, local=local cluster id) 错误的话,那么这个错误日志的打印频率将会很高,需要及时处理

stream 将获取到的 raftpb.Message 放入相应的通道 recvc / propc

Summary

相比 k8s 的复杂来说,etcd 的代码阅读算是还能摸得着头的了

etcd v3.1.9

从 etcd 启动参数中生成 Cluster ID 和 Member ID

1
cl, err = membership.NewClusterFromURLsMap(cfg.InitialClusterToken, cfg.InitialPeerURLsMap)

上述方法从 –initial-cluster-token and –initial-cluster 这个两个启动参数中生成 Cluster ID 和各个 Member ID

NewClusterFromURLsMap 这个方法中调用 NewMember 生成 Member ID

首先来看 NewMember 方法

1
func NewMember(name string, peerURLs types.URLs, clusterName string, now *time.Time) *Member

核心思路

1
2
3
b []byte: peerUrls + clusterName + time
hash := sha1.Sum(b)
memberID: binary.BigEndian.Uint64(hash[:8])

Member ID 根据 peerUrls / clusterName / current_time 的 sha1 sum 值,取其前 8 个 bytes,为 16 位的 hex 数

回到 NewClusterFromURLsMap 方法中的 NewMember(代码如下)可见最后一个参数为 nil,即不加入时间因素,因此 NewClusterFromURLsMap 生成的 Member ID 是固定的

1
m := NewMember(name, urls, token, nil)

Member Add 生成的 Member ID

直接从 server 端看起 —— etcdserver/api/v3rpc/member.go 中的 MemberAdd 方法

可见如下代码

1
2
3
4
5
6
7
8
9
urls, err := types.NewURLs(r.PeerURLs)
if err != nil {
return nil, rpctypes.ErrGRPCMemberBadURLs
}
now := time.Now()
m := membership.NewMember("", urls, "", &now)
if err = cs.server.AddMember(ctx, *m); err != nil {
return nil, togRPCError(err)
}

m := membership.NewMember(“”, urls, “”, &now) 加入了当前时间,因此 Member ID 是不确定的

总结

cluster ID 仅生成一次,此后不会变化

通过 etcd 启动参数生成 (initial-cluster) 的 Member ID 固定

通过 Member add 生成的 Member ID 不确定

Member add 的时候,没有传递 member 的 name,因此 member add 成功时,member list 出来的 member item,新加入的 member 其 name 为空,且没有 client url,因该 member 尚未 publish 其 client url 到集群中

etcd v3.1.9

什么时候生成

etcd 启动时设置了参数 –snapshot-count 即 index 变化 达到该值时,会生成 .snap 文件

相关代码如下

1
2
3
4
5
6
7
8
func (s *EtcdServer) triggerSnapshot(ep *etcdProgress) {
if ep.appliedi-ep.snapi <= s.snapCount {
return
}
plog.Infof("start to snapshot (applied: %d, lastsnap: %d)", ep.appliedi, ep.snapi)
s.snapshot(ep.appliedi, ep.confState)
ep.snapi = ep.appliedi
}

文件名规则

snap 的全称呢 snapshot,是快照的意思,在 etcd v3 里面呢,是把 v2 的内存数据存储到磁盘上,生成 [Term]-[Index].snap 一类的文件

snap 文件名生成规则

fname := fmt.Sprintf(“%016x-%016x%s”, snapshot.Metadata.Term, snapshot.Metadata.Index, snapSuffix)

是否可以删除

说到 v2 的存储,全部在内存中,而且是一个非常直接的实现,看着就是个多叉树

那么是否 .snap 文件就可以删除掉了呢

  1. 如果存储了 v2 的数据,显然不能删除,否则 etcd 重启之后,就无法从 .snap 文件中恢复出 v2 的数据了;当然恢复也不会是全量的数据,因为有 —snapshot-count 控制,会丢这个数的 Index
  2. 如果没存储 v2 的数据,都是存的 v3 的数据,这种情况下,能否删除?
    这就要看 .snap 文件除了存储 v2 的数据还存了什么东东;以及存的这东东,还有什么其他重要的用途了

看了 etcd v3 的 restore 代码之后,我们知道 restore 会在 snap 文件下生成 v3 的存储 db 文件,以及一个 .snap 文件,这个 .snap 文件存储了啥东西嘞

相关代码如下

1
2
3
4
5
6
7
8
9
10
11
// first snapshot
raftSnap := raftpb.Snapshot{
Data: b,
Metadata: raftpb.SnapshotMetadata{
Index: commit,
Term: term,
ConfState: raftpb.ConfState{
Nodes: nodeIDs,
},
},
}

其中的 Data 并不重要,只是存储了两个 namespace,其实就是两路径

1
st := store.New(etcdserver.StoreClusterPrefix, etcdserver.StoreKeysPrefix)

重要的是 Metadata 中的 Index 和 Term

显然也不能删除

etcd 在启动时,会读取 .snap 文件,获取其中的 Metadata.Index,使用这个值去搜索应该从哪个 wal 文件开始继续处理

回忆一下 .wal 文件名的第二段,正是当时 wal 存储中的 index

wal 的搜索代码如下

1
2
3
4
nameIndex, ok := searchIndex(names, snap.Index)
if !ok || !isValidSeq(names[nameIndex:]) {
return nil, ErrFileNotFound
}

从最旧的 wal 搜索到最新的 wal sort.Strings(names),直到搜索到

1
2
3
if index >= curIndex {
return i, true
}

如果 .snap 文件不存在,那么会从 index = 0 开始搜索 wal 文件,也就是说 .snap 文件不存在的时候,必须存在 0000000000000000-0000000000000000.wal 文件,否则 etcd 启动时会报如下错误

1
2017-10-02 13:49:08.313573 C | etcdserver: open wal error: wal: file not found

wal 中存储了 raft MemoryStorage 的 entries / raft HardState,etcd member id 和集群 id

构造一个异常情况

purge 保留 1,snapshot count 设置的超大,重启 etcd 会发生什么?

1
./bin/etcd --max-snapshots '1' --max-wals '1' --snapshot-count '20000000'

这个异常意义在于 snapshot 文件并未生成,而此时 wal 被 purge 之后,第一个 wal 被删掉了,那么重启 etcd 后会出现前述 wal: file not found 的错误。

持续往 etcd 写入数据,直到生成新的 wal 文件,然而不幸的是,并没有观察到 purge 的动作。 那么问题来了,etcd 是在哪儿做了保护?

查看 purge 的代码发现了如下的轨迹

1
2
3
4
l, err := TryLockFile(f, os.O_WRONLY, PrivateFileMode)
if err != nil {
break
}

也就是说 TryLockFile 成功才可以被 purge 掉,那么我们可以进一步推测没生成 .snap 文件之前,etcd 不会释放 LockFile,阻止仍然有用的 wal 文件被 purge 掉

为了验证我们的猜想,查看 wal/wal.go 的 ReleaseLockTo 方法,直接贴该方法的注释吧

1
2
ReleaseLockTo releases the locks, which has smaller index than the given index except the largest one among them.
For example, if WAL is holding lock 1,2,3,4,5,6, ReleaseLockTo(4) will release lock 1,2 but keep 3. ReleaseLockTo(5) will release 1,2,3 but keep 4.

继续看,ReleaseLockTo 方法被谁调用,即什么时候释放 wal 文件的 lock,什么时候允许 wal 被 purge

okay,答案符合我们的预期。ReleaseLockTo 在 etcdserver/storage.go 的 SaveSnap 方法中被调用,还是直接贴该方法的注释吧

1
SaveSnap saves the snapshot to disk and release the locked wal files since they will not be used.

忙了一周 + 1 天,又是一个全员加班的周六

从上周六开始,独立维护了一套平台环境,验证服务升级部署

只能说进一步熟悉了业务

支撑了另外一个部门的服务部署,对面那哥们问我来多久了,我曰一个半月,对方回感觉都要成 xx 专家了,我曰怎么会,术业有专攻,对方回之前在哪儿待的,我曰啊,在学校呀,对方回天将降大任于斯人也

工作呀,对我来说,最需要成就感,哎,果然是个骄傲的不行的人,老师评价的不错,是得改改了,没必要和自己过不去

刚看了一个文档,终于理解了 etcd 的神逻辑。Disaster recovery 的时候,需要使用旧的数据 force-start-new 一个单节点集群,该集群正常启动后,将其 peer url 更新为对外 url,后继可正常添加成员

在 cluster-health 的情况下才可以 Member add。Member add 之前,删除其 data-dir,避免数据不一致

从 snapshot 恢复时,不用 force-start-new,因为 snapshot 没有集群 metadata,只有 key 数据

如果是数据从 v2 迁移至 v3,对 data-dir 执行 migrate 之后只是将 key data 转成了 mvcc 格式,而 cluster meta data 还是旧的,因此需要走 force new start 流程

如果是全新的集群启动,则所有 etcd 以 new 状态启动即可

这样就覆盖了所有流程

终于要去见来杭的 Gloria 了,据说为了见人,还特意剪了个发型,不过被剪成 SB 了,笑,等会儿看看有多惊艳

杭州应该进入了梅雨季节,一言不合就下雨,天气略闷热,而我的工作略忙

这篇在地铁,公交上写完

2017年06月11日12:53:16 杭州

这次不文艺风了,吐槽篇

昨晚第一次刷夜完成了一次安装,哦对了,是提心吊胆,一脸懵逼的完成了

这么复杂一个系统,那么多组件,随便崩一个,修复起来都够呛,对于新手如我,简单问题能弄就直接弄了,不能也只能一遍一遍的重装。现在才理解“概率性”失败的含义,囧,总之就是因为事情太复杂,偶尔一些未知因素叠加在一起,就导致了异常的情况,然后就概率性失败了,重试的时候可能好可能不好。

这些天好混乱,各种事情堆着,这个事儿还没做出个结果,又有其他事情也说要了结一下,总感觉这样子赶鸭子上架,没有准备和思考时间,对自己能力提升有限,更别说底下的工作效率了。

最近基本都和程序打交道,别提遇到坑爹的领路人。我遇到的问题,去咨询他,blabla 胡乱给我解释一通,老哥,我也是写过代码的好不好,那么容易忽悠么,总之,好几次最后都是我和他说了正确的解释。哎初来乍到,就当供着你吧,谁让我也是好人。

本来这周末是部门出游的,部门其他组都去的差不多的,就我们组只去了零星几个,可见强度。特别是看着群里的两位小伙伴(hcl, yy)还在端午假期中,无限感慨呀。

2017年06月04日12:44:32 杭州

昨晚团队第一次聚餐,喝了点儿小酒,仍然是不胜酒力呀,一杯就脸红,囧。

怎么样才可以更成熟些,感觉自己面对人还是很不自然。又被各种问有没有对象了,并不想回答,奈何 leader 也总问,真是个忧伤的故事。还被大家期待讲故事,呃,看起来这么像有故事的么,其实,我是个学霸,想做个好人。

工作上的事情么,最近在研究 etcd,经过好几天的熟悉摸索,现在总算把框架写好了,后继往上加功能即可。反思是开工之前,先好好研究文档吧,直接看别人的代码,听人讲思路,还是理解不能。

新接了一个 issue,还没看懂啥意思呢,节后再问老司机到底啥情况了,先把 etcd 那个搞完吧,毕竟思路还顺着,一鼓作气。

最近还是疯狂加班,工作效率低么。主因语言还不太熟悉,有点重回大一那个时候,编译通过,都有振臂高呼的兴奋心情,哈哈,有点夸张了。另外就是部署测试效率低,自己很想研究下相关自动化的工具,无奈担心不误正业,只能手工一遍一遍的操作了。

杭州呢,还是保持着一星期下一次雨的节奏,天气并不是特别炎热的感觉,毕竟早出晚归,没机会体验。昨晚聚餐,本地同事开车捎去的,本地人都好低调,朴朴素素的,然而车库一看奔驰 SUV,厉害了我的哥。

来到感情的事儿了,这次自己觉得的很完美了,勇敢、付出、不伤害、不打扰、互相成长,一句,“不会的, 祝幸福”,云淡风轻,也许茫茫人海,丢掉一个人的感觉很无奈,不过仍然会“带着这份温暖和善意永远走下去”。

一学弟和学妹近期就要来杭州实习了,杭帮又壮大了;六月份也是毕业季,准备又可以见到新同事了,同样是满怀期待。

这个端午屋里好热闹,来了室友A男票、室友B表妹。先写到这儿了,周末研究家常菜系列又开始了。

Windy Hill

2017年05月28日10:54:39 杭州

最近好像天气变热了不少,然而早出晚归的我,几乎没有感受到夏天的炎热。早上七点起床,收拾一会儿就出发,在天气好的时候,自然是能披一道阳光在身上了。晚上不时十一点才回来,路上偶尔还能遇到几个不知何原因同样晚归的路人,不过有时候也会有些害怕就是了,毕竟只有大马路上来往的车辆,才提醒你还有人烟的存在,要是阴暗处杀出个神魔鬼怪,还是挺担心的。所以如果当天穿着运动装的话,就一路小跑回去了,好在也只是 1.7 公里而已。

这周又到了月末周六上班的时候,因为下周就是端午节假期了,好吧,想想又是一个六天工作日的一个星期。昨天早上起来的时候,还一直纳闷闹钟怎么还没有响,忍不住去看了一眼时间,结果早过点了。原来闹钟只定了工作日,这月末周六不按常理出牌,当然闹钟没动静了。话说回来,其实很少被闹钟叫醒,几乎都是在闹钟时间之前就有意识的醒了,可能还是微微有些压力,或者是 passion?passion 多一些,有时工作时间解决不了的问题,回到家洗个澡,悠闲的看着自己的电脑,查查资料,很快就想明白了,第二天到公司验证,果然解决了。

对公司业务、用到的技术有了更多了解,开始能解决一些问题了;写了点儿代码,合并到主干去了,昨晚已经转测试了。然而还有很多不懂的东西,经常去请教老员工或者是比我来的稍微早一些的同事就是了,不懂就要问么。问题都是带着目的性的,比如问这个问题,了解这个问题之后,我可以完成什么任务。解决问题之后,自己也会思考着总结一下,写写 wiki。感觉自己不太能记东西,就是别人说一遍,非得理解清楚背后因果关系,或者自己实践一下,才能掌握。

上周比较无语的一个事情是和一个异地团队联合调试,踩了各种坑,同时也填了各种坑。事情背景:我团队需集成 Ta 团队提供的一个 SDK 到产品中。事情过程:本来是个很简单的操作,然而由于该 SDK 他们也在开发中,版本升级没提前知会;一开始我使用的旧版本,输出结果总是不对;问主管找到他们的咨询人员,联系后被告知在开会,回有时间帮忙看看;一等等一上午,到下午两点多的时候,才说版本升级了,用 xx 版本的试试;用了 xx 版本之后,总算解决问题了;到此以为问题完了,才不是,简直噩梦的开始,后来验证过程中发现对方 SDK 中有几句错误代码,导致运行不正常,代码截图发他们的咨询人员看,对方还强调说他们没加这个代码,说再去确认一下;反正我是无语了,SDK 就你们给的,而且还在里面发现了这个代码,那这代码是谁加的呀。不管怎么说,这个集成任务完成了,不过后续和对方团队的联调,感觉还是困难重重。

感触就是工作中,很多事情不是自己一个人能解决的,等待、沟通、扯皮(笑)都需要时间,慢慢习惯咯。

这次文字拖的比较晚,有点忙 ~ 坐在客厅的沙发上,闻着邻居家的菜香,写了此篇。看会儿菜谱出去买菜去了,还要买一周的水果。

最后,最近只能听的下纯音,推一些纯音好了

美丽的神话
最浪漫的事
斯卡布罗集市
绿野仙踪
高雅~メインテーマ

遥祝你们一切都好。

2017年05月21日11:26:59 杭州

童话镇

除了工作还是工作呢。

挺有意思的,刚来的一波新人,还啥都不懂呢,就被拉上去做 support 了,囧,能 support 什么,也是得找各种直接负责人一起定位问题。可能也是被找来帮老员工挡问题的吧。

毕竟是底层基础架构的,每天问题单雪片似的飞来。分布式软件真是不好调试,加上系统复杂之后,引入的额外负担,更别提多小组多部门的各种推诿了。

go 语言好么,目前觉得看的很难受,接口非常松散,不太容易搞清楚调用关系,经常看着看着就晕了。

上周我是被派了一个 issue,本来以为要写代码的,后来仔细看了,并不是一个 bug。和 leader 沟通后,转成了一个通过代码整理文档的任务,工作量之大,不可小觑。

转接了一个 ticket,本来以为要写代码,后来和开发沟通后,又确认了并不是一个 bug。和提 ticket 的同事解释了很久,对方终于接受了。

今天是刚接了一个升级任务,晚上刚想到环境上面去测试,结果环境崩了,正逢周末,得拖到下周才能验证了。时运不济呀。

挺累的,身心俱疲。

该给自己的身心减减负了,let it be。

2017年05月12日23:53:45 杭州

这星期过的好快,因为只有四天。

杭州最近是一星期得两三天下雨,还挺凉快。

回想起来这星期并没有做很多事情,当然不是我偷懒了,培训就占用了两天时间,讲些泛泛的概念,各种名词,听不懂。还是做成了一件事的,为了把 kubernetes 要求的网络环境弄通,真是花了自己的十八般武艺加上各种请教人,最后解决的时候,还是很喜欢解决问题和合作的感觉的。也没多少技术含量,就是使用工具加思考定位问题而已了。

总之下周要上一个小项目,五个人,两个技术核心 + 三小弟(我是其中之一),有点小压力。工作的事情不多说啦,总之我很努力就是了。

想想学弟们的近况,反正更是厉害了,不想说有 2 G家,10 A家 … 吓人,实验室下一届大概有 50 人的样子。

上周末去了一学弟家做客,绍兴的诸暨,诸暨的乡下好美丽,感觉挺富足的,五泄风景区很漂亮,顾名思义,其实就是有五个小瀑布,那个地方还有个小水库,我们还坐船玩儿了会儿。学弟爸爸问,五泄怎么样呀,我觉得我词穷了,简直觉得读书少,对答不上了,只能说很美呀,一车人都笑了。好喜欢乡里乡亲人与人之间亲密的联系,中午在学弟家吃的,晚上去了他一亲戚家吃,叔叔阿姨都很热情。

一天都玩的挺开心的,就是回程的时候不太顺利:室友手机看剧,我也看了会儿手机没注意时间,等看到时间的时候,只剩两分钟就开车了,走到检票口的时候,公告牌都显示停止检票了。这还是第一次这么愚蠢的错过火车来着 sigh。

先写到这里咯,回来看这乱七八糟写的都是什么呀。

遥祝你们一切都好。

2017年05月05日21:59:01 杭州