背景
公司后端是基于ROR的,应用服务器是unicorn,搞活动高峰期,缺乏一种动态改变配置后,就可以控制程序行为的能力。
受限于Rails和Unicorn的多进程模型,初步的方案就是往Cache里扔一个值,接口每次都检查这个值,但这样就多了一次网络IO,不太Geek。
深入思考了一下,觉得可以做一个配置中心,unicorn启动的时候,建立长连接到配置中心获取数据,配置发生改变的时候通过长连接通知到unicorn,从而动态改变了所有服务器上的本地内存。然后又在考虑,是每个unicorn worker都保持一个连接?还是只有master保持连接,通过一些进程间通信技术通知到worker?又或者单独做一个agent,让agent去保持连接,然后写到unix socket里,unicorn读这个socket?…
最终由于改不动unicorn的代码, 放弃了。
后来转念一想,这不就是etcd解决的问题!只不过详细的方案还需要基于etcd自己设计。
设计
服务有一个唯一id,id是调用方和提供方都知道的。
大致流程:
- 提供方: 往etcd里put一个键值对,key是服务id,值是对应的配置
- 掉用方: 根据key向etcd获取配置,并进行watch
- 配置改变时,调用方可以通过watch得知
实践
根据官方文档装好etcd,https://coreos.com/etcd/docs/latest/dl_build.html
1 | git clone https://github.com/coreos/etcd.git |
写一个简单的服务注册1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36package main
import (
"context"
"github.com/coreos/etcd/clientv3"
"log"
"time"
)
type ServiceInfo struct {
id string
address string
}
var info = ServiceInfo{"/service/b", "10.1.1.60:8081"}
func main() {
cli, err := clientv3.New(clientv3.Config{
Endpoints: []string{"localhost:2379"},
DialTimeout: 5 * time.Second,
})
if err != nil {
log.Fatal(err)
}
defer cli.Close()
if _, err := cli.Put(context.TODO(), info.id, info.address); err != nil {
log.Fatal(err)
}
if resp, err := cli.Get(context.TODO(), info.id); err != nil {
log.Fatal(err)
} else {
log.Println("resp: ", resp)
}
}
写个简单的go服务a, 通过etcd来发现服务b的地址
1 | package main |
结果是, a服务可以获取到b的地址,在/service/b
这个key改变时,也可以通过watch得到最新的值。
服务挂掉的处理
以上的例子可能有点不太恰当,因为偏向于一个动态的配置中心,我们不需要监测服务是否存活,因为etcd本身就是服务。
对于一般的服务,如果挂了,以上的方式是无法处理的。解决方式是给key设置一个ttl,每隔一段时间刷新ttl。通过这种类似心跳的方式来实现。