• 微服务:Etcd服务注册与服务发现


    为什么?

    为什么会有服务注册和服务发现?在它以前我们是怎么做的?

    举个例子:
    比如我们做MySQL读写分离,就在本地配置一个文件,然后程序读取这个配置文件里的数据进行数据库读写分离的设置。
    但是随着业务发展迅速,业务模块越来越多,数据也越来越多,MySQL数据库也越来越多,需要读取MySQL服务的业务模块也越来越多。每次增加MySQL实例,每个业务模块都要手动去写一次本地配置。

    想一想这里有什么问题?可以改进吗?
    每次手动写配置,是不是很浪费时间,能不能做到自动一些?能不能做到只配置一次,业务模块就可以自动读取更新后的配置?于是我们就开始动脑筋了,就开始琢磨解决的办法了。

    在计算机领域,没有什么是增加一个中间层解决不了的问题,有的话,“那就在增加一层”,对,最后那句是我说的- -!。

    于是就想:能不能把所有的配置集中存放,程序模块的配置更新由各个程序自动的来配置中心读取,更新配置。而不是每次手动的增加每个程序模块的配置。
    于是我们就增加了一个“中间层”- 配置中心,集中存储,管理分散在各个程序中的配置。配置中心就向外面提供配置服务。

    不过Etcd应该属于服务配置中心,属于更加细化的配置中心。
    还有更加专注的配置中心,比如Apollo,Nacos,disconf等等。

    是什么?

    什么是服务注册和服务发现?

    • 服务注册 :就是向配置中心注册一个配置的功能。
    • 服务发现:就是发现配置中心增加、修改、删除了配置的功能。发现配置的异动情况,让更新配置。

    会遇到哪些问题?

    服务发现和服务注册怎么解决问题以及会遇到哪些问题?

    1. 怎么标识一个服务?
      在计算机里,标识一个计算机中运行的程序可以用ip+端口形式来标识,配置服务也可以用这种方式。
    2. 怎么发现一个配置服务的异动?
      配置更新,删除(下线),增加了,怎么发现配置的这些异动情况?一种是长轮询,一种是心跳,每隔多长时间检查一次,还有发布订阅模式。
    3. 服务是否可用?
      这种就要检查服务的可用情况了,一般是健康检查。

    ... ...

    等等问题

    怎么解决

    业内主要的几个解决方案ZooKeeper,Etcd,Consul ,等。我们介绍etcd的使用。

    etcd介绍

    etcd是CoreOS团队于2013年6月发起的开源项目,它的目标是构建一个高可用的分布式键值(key-value)数据库。etcd内部采用raft协议作为一致性算法,etcd基于Go语言实现。

    前面博客文章也有一些它的使用情况。

    它是一个kv键值的数据库:
    所以可以存储数据,那么就可以存储配置数据。

    它有watch功能:
    所以可以发现配置的异动情况。变化时候可以起到通知作用。

    它有key TTL功能:
    etcdv3可以通过lease租约,设置服务生存周期TTL,让key自动过期,通过KeepAlive定期续租,避免过期。

    当然,它还可以做分布式锁、分布式队列等等其他功能,你可以google去学习它的其他功能。

    例子

    • 注册服务和获取服务
    // PutValueDemo 插入值demo,配置里面put相当于注册一个服务(服务注册),get相当于获取一个服务(服务发现)
    func (demo EtcdDemo) PutValueDemo(client *clientv3.Client) {
        fmt.Println("
    ...put value demo...")
        fmt.Println("put value: ", demo)
        _, err := demo.PutValue(client)
        if err != nil {
            fmt.Println("failed to put value, err: ", err)
            return
        }
    
        getVal, err := demo.GetValue(client)
        if err != nil {
            fmt.Println("get val err: ", err)
            return
        }
        fmt.Println("get key:",string(getVal.Kvs[0].Key),",value:", string(getVal.Kvs[0].Value), ", Revision: ", getVal.Header.Revision)
    }
    

    如果你单独在main函数里测试PutValueDemo(),

    client, err := clientv3.New(clientv3.Config{
            Endpoints:  []string{"127.0.0.1:2379"},
            DialTimeout: time.Second * 5,
            })
     if err != nil {
            fmt.Println("failed to connect etcd: ", err)
            return
     }
     defer client.Close()
    
     demo := EtcdDemo{Key: "test1", Val: "val1"}
     demo.PutValueDemo(client)
    

    你会发现:
    Revision 的值,每次都是增加的。

    get val: val1 ,key: test1 , Revision:  1
    get val: val1 ,key: test1 , Revision:  2
    这也是etcd的一个特性,可以用个来做分布式锁。

    • watch 监测服务变化
    // watchDemo 监听key的变化
    func (demo EtcdDemo) WatchDemo(client *clientv3.Client) {
        fmt.Println("
    ...watch demo...")
        stopChan := make(chan interface{}) // 是否停止信号
        go func() {
            watchChan := client.Watch(context.TODO(), demo.Key, clientv3.WithPrefix())
            for {
                select {
                case result := <- watchChan:
                    for _, event := range result.Events {
                        fmt.Printf("%s %q : %q
    ", event.Type, event.Kv.Key, event.Kv.Value)
                    }
                    case <-stopChan:
                        fmt.Println("stop watching...")
                        return
                }
            }
        }()
    
        for i := 0; i < 5; i++ {
            var demo EtcdDemo
            demo.Key = fmt.Sprintf("key_%02d", i)
            demo.Val = strconv.Itoa(i)
            demo.PutValue(client)
        }
        time.Sleep(time.Second * 1)
    
        stopChan <- 1 //停止watch,在插入就不会监听到了
    }
    

    运行后:

    ...watch demo...
    putResp Revision:  13
    PUT "key_00" : "0"
    PUT "key_01" : "1"
    putResp Revision:  14
    PUT "key_02" : "2"
    putResp Revision:  15
    PUT "key_03" : "3"
    putResp Revision:  16
    PUT "key_04" : "4"
    putResp Revision:  17
    stop watching...

    Revision单调递增变化的。
    watch 可以监测到put变化的情况。

    • 租约lease
    // LeaseDemo 租约
    func (demo EtcdDemo) LeaseDemo(client *clientv3.Client) {
        fmt.Println("
    ...lease demo...")
    
        lease, err := client.Grant(context.TODO(), 2) //创建一个租约
        if err != nil {
            fmt.Println("grant err: ", err)
            return
        }
    
        testKey := "testleasekey"
        // 给这个testkey一个 2秒的TTL租约
        client.Put(context.TODO(), testKey, "testvalue", clientv3.WithLease(lease.ID))
        getVal, err := client.Get(context.TODO(), testKey)
        if err != nil {
            fmt.Println("get val err: ", err)
            return
        }
        vallen := len(getVal.Kvs)
        fmt.Println("before time sleep, val len: ", vallen)
    
        fmt.Println("sleep 4 seconds")
        time.Sleep(4 * time.Second) //睡眠4秒,让租约过期
    
        getVal, _ = client.Get(context.TODO(), testKey)
        vallen = len(getVal.Kvs)
        fmt.Println("after 4 seconds, val len: ", vallen)
    }
    

    运行:

    ...lease demo...
    before time sleep, val len:  1
    sleep 4 seconds
    after 4 seconds, val len:  0 //这里租约到期,删掉了这个key和val

    完整代码在这里github

    参考

  • 相关阅读:
    SQL2005的CTE
    分列顯示
    2005数据库结构显示
    十进制/十八进制的互转换(此方法应用于所有进制与10进制的转换)
    2005自动生成数据库的清空脚本
    阻塞分析
    通过在 Web 表单中维持对象的 ViewState (视图状态)
    cs文件调用aspx页面js函数
    Repeater绑定后格式化某字段
    Fireworks中制作炫光效果
  • 原文地址:https://www.cnblogs.com/jiujuan/p/13200898.html
Copyright © 2020-2023  润新知