etcd 总结

1,092 阅读3分钟

参考

www.jianshu.com/p/3bd041807…

配置获取优化

“微服务协同工作架构中,服务动态添加。随着Docker容器的流行,多种微服务共同协作,构成一个相对功能强大的架构的案例越来越多。透明化的动态添加这些服务的需求也日益强烈。通过服务发现机制,在etcd中注册某个服务名字的目录,在该目录下存储可用的服务节点的IP。在使用服务的过程中,只要从服务目录下查找可用的服务节点去使用即可。”

在微服务的某一个模块的 config 配置中去加载配置,然后将配置项赋值到环境变量中去。

etcd 客户端

首先,封装出一个 python etcd 的客户端。

import etcd


class EtcdConfig(object):

    def __init__(self, host: str = None, port: int = None, **kwargs):
        """
        :param host: etcd server host
        :param port: etcd server port
        :param kwargs:
            srv_domain (str): Domain to search the SRV record for cluster autodiscovery.

            version_prefix (str): Url or version prefix in etcd url (default=/v2).

            read_timeout (int):  max seconds to wait for a read.

            allow_redirect (bool): allow the client to connect to other nodes.

            protocol (str):  Protocol used to connect to etcd.

            cert (mixed):   If a string, the whole ssl client certificate;
                            if a tuple, the cert and key file names.

            ca_cert (str): The ca certificate. If pressent it will enable
                           validation.

            username (str): username for etcd authentication.

            password (str): password for etcd authentication.

            allow_reconnect (bool): allow the client to reconnect to another
                                    etcd server in the cluster in the case the
                                    default one does not respond.

            use_proxies (bool): we are using a list of proxies to which we connect,
                                 and don't want to connect to the original etcd cluster.

            expected_cluster_id (str): If a string, recorded as the expected
                                       UUID of the cluster (rather than
                                       learning it from the first request),
                                       reads will raise EtcdClusterIdChanged
                                       if they receive a response with a
                                       different cluster ID.
            per_host_pool_size (int): specifies maximum number of connections to pool
                                      by host. By default this will use up to 10
                                      connections.
            lock_prefix (str): Set the key prefix at etcd when client to lock object.
                                      By default this will be use /_locks.
        """
        self._etcd = etcd.Client(host=host, port=port, **kwargs)

    def get_config(self, dir: str) -> dict or None:
        """
        returns all key / value mappings recursivly coresponding to key dir
        if the dir is not existed return None
        if value is not a dir returns the value itself
        :param dir: key of a dir
        :return:
        """
        try:
            d = self._etcd.get(dir)
        except etcd.EtcdKeyNotFound:
            return None
        if not d.dir:
            return d.value

        cfg = {}
        for chld in d.children:
            local = chld.key.split("/")[-1]
            cfg[local] = self.get_config(chld.key)

        return cfg

    def get_value(self, key: str) -> str:
        """
        get_value returns the value of a key
        :param key:
        :return:
        """
        return self._etcd.get(key)

    def store(self, key: str, value: str or dict):
        """
        stores value into etcd with key.
        the value must be str or dict
        :param key:
        :param value:
        :return:
        """
        if isinstance(value, dict):
            for k, v in value.items():
                new_key = "/".join([key, k])
                self.store(new_key, v)
        else:
            self._etcd.set(key, value)

该客户端实现了简单的 key-value 存储功能。

在调用的时候,向每一个存储节点发送请求,获取对应节点下的全部的键值映射。这种方法在目的上清楚明了,但是每次获取一个配置,就会发送一次 http 请求,在时间成本上花费较大。

代码简易模型示例:

@log_method_time_usage
def ori_get_config():
    envs = client.get_config(_p(f"quant/{QUANT_TYPE}/env"))
    for k, v in envs.items():
        setattr(this, k, v)

    ft = client.get_config(_p("future"))
    type_it(ft)
    setattr(this, "SERVICE_FUTURE", ft)

    idx = client.get_config(_p("index"))
    type_it(idx)
    setattr(this, "SERVICE_INDEX", idx)

    mq = client.get_config(_p("mq"))
    type_it(mq)
    setattr(this, "SERVICE_MQ", mq)
    # ......

为了改进速度,要尽可能减少发送 http 的请求次数。思路是在不改变构建 etcd 模型的基础上,一次请求 load 下全部结构,然后自行分解取用,set 给相应的环境变量。 代码简易模型示例:

def get_config():

    def _q(key: str) -> str:
        return "/".join(["/jq", QUANT_ENV, key])

    ret = client.get_etcd_result(f"jq/{QUANT_ENV}")
    cfg = defaultdict(dict)

    for c in ret.children:
        c_key = c.key.split("/")[-1]

        if c.key.startswith(_q('future')):
            cfg["SERVICE_FUTURE"].update({c_key: c.value})

        elif c.key.startswith(_q('index')):
            cfg["SERVICE_INDEX"].update({c_key: c.value})

        elif c.key.startswith(_q('mq')):
            cfg["SERVICE_MQ"].update({c_key: c.value})

        elif c.key.startswith(_q('oss')):
            cfg["SERVICE_OSS"].update({c_key: c.value})
        # ......

同时我们需要在客户端实现实现一个 get_etcd_result 方法获取首层的 etcdResult 对象: 代码:

class QuantEtcdConfig(EtcdConfig):
    def __init__(self, host: str = None, port: int = None, **kwargs):
        super().__init__(host, port, **kwargs)

    def get_etcd_result(self, base_dir: str) -> etcd.EtcdResult or None:
        try:
            d = self._etcd.read(base_dir, recursive=True)
        except etcd.EtcdKeyNotFound:
            return
        return d