参考
配置获取优化
“微服务协同工作架构中,服务动态添加。随着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