主从系统的实现
这是GPRC+ETCD服务发现的一种变种实现
在服务端代码的封装上有很大的区别,一个简单的服务发现系统中,服务端之间的状态互不影响
在主从系统中,主需要知道从的IP来向从复制数据(或者反过来从需要知道主的IP来拉取数据),当主挂了,需要选举一台从来切换成主
服务发现的变种实现
在我的实现中,将这种服务发现设计为selecter类,该类是没有grpc代码的,是对ETCD的一个封装
该类有两个作用,首先是不间断的将自己的地址注册在ETCD上,其次是将ETCD的各种节点变化事件变为主从事件,通过管道发送给调用方
1 首先看第一个作用,将自己的地址注册在ETCD上,并且让客户端能够明显的鉴别哪个是主
优雅的实现是利用ETCD的自增节点,将一系列IP注册在自增节点的内容中,哪个自增节点最小,哪个就是主
当主挂了,次小的那个就应该是主了
由于ETCD不像zookeeper封装了自动刷新节点,因此需要自己实现
这部分可以用状态机来实现,分为INIT,REFRESH,OFFLINE三种状态
INIT状态进行节点创建,成功进入REFRESH状态,失败进入OFFLINE状态
REFRESH状态不断刷新节点,失败进入OFFLINE状态
OFFLINE状态进入INIT状态
2 第二个作用,首先需要设计有事件的结构
1 | type Event struct { |
这是事件的状态码和事件变化的节点
大致有如下几种
1 | INIT_MASTER |
这里分为INIT_MASTER和CHANGE_TO_MASTER,是为了考虑到初始化可能要做一些工作
具体实现是建立一个ETCD watcher,任何的节点变化事件都会触发watcher的回调
对所有节点排序来获取新的主和从,来与历史记录的主从进行比较
从而生成主从事件和变化节点列表
这里有个注意点是创建这个watcher前,需要先调用一遍watcher的回调,否则调用方无法初始化主从状态
而这个主动调用,然后创建watcher的过程可以做成一个函数,任何失败发生(例如网络超时),就重新调用这个函数
服务端的实现
服务端我设计为sync_manager类,该类使用设计好的grpc master和grpc slave类,生成master server和slave server实例
master server有对外部开放的读写接口
而slave server只有对内部开放的同步接口
而后启动一个后台进程housekeeper,该进程监听selecter类的各种事件
切换主从状态,当slave新加或者删除,对应建立或者销毁相对的grpc slave client
外部调用master的write接口时,如果是强一致性协议,就需要写从成功后返回
客户端的实现
客户端需要连接一组或者多组服务端
例如服务端将路径注册在/server/0001这样的节点时
客户端就需要监听/server下所有的节点变化事件,当新的一组主从0002加入时,必须有所响应
这部分功能基本没什么难点了,只是封装上可以实现成两种方式
1 | s = service.NewClientManager("etcdURL+Path") |
1 | s = service.NewClientManager("etcdURL+Path") |
方式一的问题在于如果有个服务注册在ETCD上,但是他有BUG,有可能出错,重复调用还是有可能调用到这个重复的上门
方式二的调用方可以自己管理客户端实例,遍历可用的客户端来进行访问
补充
ETCD的自增节点有个小坑。监听路径进行watcher并且设置Recursive为true
如果用set创建孙子节点,不会触发watcher
而如果用CreateInOrder创建孙子节点,则会触发watcher
所以需要对watcher触发的路径值resp.Node.Key进行判断,如果是孙子节点,那么屏蔽