前言
- nameko 默认的启动文件格式yaml 或者直接命令行输入某些参数
- 是否可以 自定义nameko的配置格式呢,比如利用json格式 或者改为直接读取consul 的远程配置
nameko 的配置文件加载过程
从nameko源码 可以看到 加载yaml 大概如下(比如执行 nameko run --config foo.yaml service ):
- 启动参数传到命令Run 然后调用 nameko.cli.run.main 方法
- 利用yaml依赖 解析 foo.xml文件生成 config(一个字典类型)配置
- 调用 nameko.cli.run.run 继续初始化 ( add_service 然后 start)
ServiceRunner 中用到config
- init 方法 获取 container_cls 时候是从 config 读取 ‘SERVICE_CONTAINER_CLS’
- add_service 中根据 container_cls 创建 container 对象需要传入config
因为 ServiceRunner 默认是不支持 自定义的,所以 目前还是必须存在 一个 yaml配置文件.
nameko 添加 consul 远程配置
为什么需要这个呢?
- 可以把微服务的配置统一到 一个地方管理,这样如果改配置 不用更新线上所有配置?这样为什么 不用redis 或者 mongo 存储配置呢? 其实也是可以的?我这里举出使用consul 的两点好处
- consul 提供了 kv 的后台管理
- 通过我们的实现 我们可以实现 程序内部准实时感知配置变化,本地访问配置更高效
基于consul kv 实现nameko 插件
方案一
上面 已经分析过 配置 在 serviceRunner 会传入 container 实例,而且我们从上面 知道 servicecontainer 是可以 重写的,通过在foo.yaml 中定义 SERVICE_CONTAINER_CLS 我们用自己实现的 servicecontainer 替换 默认的 servicecontainer,这样后面的拓展都可以 用到最新 的 consul kv 配置.
- 我们 可以自定义实现 servicecontainer 在初始化时候 直接 把远程 consul kv 配置 和本地的 config 合并
- 远程 consul kv k可以用 servicename 命名 比如(k=lyconf/ranklkist) v 存储json 文本
但是我不建议这么做,因为 consul kv 一般是 动态配置更新的,但是 foo.yaml 一般是重启才会更新的,这样合并配置 到 servicecontainer.config 反而会让人混淆 的感觉. 我更推荐 讲consul kv 和 foo.yaml 分开配置,下面按照方案二说明.
方案二
我们写一个拓展, 将远程 consul kv 映射伟本地 的lyconf 并且实现动态更新.
- 当我们需要动态更新的配置时候 可以把配置 放在lyconf,当然一些静态的也可使用(还是有一个好处的,就是配置统一,更新配置,可以不更新线上代码)
- 按照第一点讨论,我建议 可以 一个微服务 可以在 consul kv 配置多个文件,如果文件最后一层 以 static_ 则 不动态更新,需要重启 服务才会更新本地服务配置,否则会动态更新本地服务配置.
最终我们的设计 是这样的:
假设我们有个微服务叫做 mshello
- consul kv 远程 key 名称 是 root dir 是 /lyconf/mshello/ ,我们 可以在该目录下创建多个配置文件(不再识别文件夹,没必要搞这么复杂)
- nameko 编写consul kv 拓展,从而 可以读取 /lyconf/mshello/ 下的文件配置, 我们 为了处理起来简单,改成 只读取 该目录下的,指定文件名,auto 和 static. static 文件 不做动态更新,只在服务启动时候,加载 一次;auto 实现本地动态更新.
源码初版实现如下:
# -*- coding:utf-8 -*-
import json
import consul
from eventlet import Event
from nameko.extensions import DependencyProvider, SharedExtension
from consul_lib.consul_lib import LyConf
from ly_nameko.constants import LYCONF, LYCONF_LOOP_INTERVAL, LYCONSUL_KEY
class NamekoConsul(SharedExtension):
def setup(self):
consul_conf = self.container.config.get(LYCONSUL_KEY, {})
if not consul_conf:
consul_conf.update({'host': '127.0.0.1', 'port': 8500})
self.consul_client = consul.Consul(**consul_conf)
class ConsulConf(object):
'''
读取远程 consul kv 的配置
'''
def __init__(self, consul_key, consul_client):
self.consul_client = consul_client
self._conf = {}
self.consul_key = consul_key
self.consul_key_index = None
def __getitem__(self, item):
return self._conf.get(item)
def reload_conf(self):
curr_index = self.consul_key_index
index, val = self.consul_client.kv.get(self.consul_key, curr_index)
print('val', val)
if curr_index != index:
self.consul_key_index = index
if val:
body = val.get('Value')
if body is not None:
conf = json.loads(body)
self._conf = conf
return True
def get_conf(self):
self._conf.copy()
class LyConfExtension(SharedExtension):
nameko_consul = NamekoConsul()
auto = 'auto'
static = 'static'
config_names = [auto, static]
def setup(self):
self.key_prefix = '/LYCONF/%s/' % self.container.service_name
self.my_conf = self.container.config.get(LYCONF, {})
self.loop_interval = self.my_conf.get(LYCONF_LOOP_INTERVAL)
self.cache = {}
self.should_stop = Event()
self.auto_config = None
self.static_config = None
def start(self):
self.consul_client = self.nameko_consul.consul_client
self.init_conf()
self.container.spawn_managed_thread(self.run)
def init_conf(self):
_cache = {}
if self.auto_config is None:
k = self.key_prefix + LyConf.auto
c = ConsulConf(k, self.consul_client)
if c.reload_conf():
_cache.update(c.get_conf())
self.auto_config = c
if self.static_config is None:
k = self.key_prefix + LyConf.static
c = ConsulConf(k, self.consul_client)
if c.reload_conf():
_cache.update(c.get_conf())
self.static_config = c
if _cache:
self.cache = _cache
def _sync_conf(self):
if self.auto_config.reload_conf():
_cache = {}
_cache.update(self.auto_config.get_conf())
_cache.update(self.static.get_conf())
if _cache:
self.cache = _cache
def run(self):
while True:
# sleep for `sleep_time`, unless `should_stop` fires, in which
# case we leave the while loop and stop entirely
with Timeout(self.loop_interval, exception=False):
self.should_stop.wait()
break
self._sync_conf()
def stop(self):
self.should_stop.send()
def get_conf(self):
return self.cache.copy()
class LyConf(DependencyProvider):
lyConfExtension = LyConfExtension()
def get_dependency(self, worker_ctx):
return self.container.config.copy().update(
self.lyConfExtension.get_conf())
源码中的问题:
- LyConfExtension 依赖于 NamekoConsul NamekoConsul初始化 放在 setup 而 LyConfExtension 初始化 在start .还没找到更好的初始化顺序
- 代码第一版没有考虑并发问题 ,貌似 好像不用考虑这个问题