程序员过关斩将--为微服务撸一个简约而不简单的配置中心 (2)

抽象出这个操作接口之后,至于具体怎么样实现,可以是基于ETCD,可以是基于Consul,甚至可以基于DB,可见面向接口编程是多么的正确。

在网络故障等情况下需要能继续工作

在互联网应用中,始终存在一个真理:网络是不可靠的。配置中心作为公司的一个核心系统来说,要尽可能的保证能提供服务。但是,也要做好了预防措施,以防在配置中心短暂不可用的期间,不影响适用方。对于使用client端来说,既然服务端保证不了高可用,那就需要在本地动手脚:可以把获取到的配置信息在本地做存储,信息并随着watch机制做持久化。这样在配置中心网络不可用的情况下,尽量保证客户端程序可用。至于本地存储的方式,无所谓了,就算了文本文件,还是sqllite都可以。

性能要高

配置中心最显著的一个业务特点是变化不频繁,但是客户端使用频繁。所以我们可以把配置信息加载在内存中,内存中的数据随着watch机制改变而改变,这样就做到了内存数据和服务端数据高度一致。

以上啰嗦了那么多,相信你们也看累了,空谈理论谁都会,落地代码才是真理。

客户端配置中心接口 /// <summary> /// 配置中心客户端,应用,子应用,环境、版本、灰度、 /// </summary> public interface IConfigCenterClient { /// <summary> /// 配置信息发生变化时候的事件,参数:key/new value /动作(put /delete), 是etcd /consul watch的事件。每个key 的value发生变化都会触发,每个key会触发一条 /// </summary> event Action<string, string, string> ConfigValueChangedEvent; /// <summary> /// 发生异常时候的事件 /// </summary> event Action<Exception> ErrorOccurredEvent; /// <summary> /// 获取相应的配置 /// </summary> /// <param>配置的名称</param> /// <param>版本号</param> /// <returns></returns> string GetConfig(string configKey); } /// <summary> /// 环境的定义 /// </summary> public enum EnvEnum { /// <summary> /// 本地环境 /// </summary> Local=1, /// <summary> /// 开发环境 /// </summary> DEV, /// <summary> /// 仿真环境 /// </summary> UAT, /// <summary> /// 生产环境 /// </summary> PRO } public class ConfigCenterETCDProvider : ConfigCenterBaseProvider, IConfigCenterClient { //配置发生改变的通知事件 public event Action<string, string, string> ConfigValueChangedEvent; //有异常发生的时候的事件 public override event Action<Exception> ErrorOccurredEvent; //etcd的配置 private ConfigCenterETCDOption option { get; set; } private EtcdClient client = null; internal ConfigCenterETCDProvider(ConfigCenterBaseOption _option) : base(_option) { if (_option == null) { throw new Exception("config is null!"); } option = _option as ConfigCenterETCDOption; if (option == null) { throw new Exception("option type is wrong!"); } if (string.IsNullOrWhiteSpace(option.ConnectionString)) { //默认地址 option.ConnectionString = "http://127.0.0.1"; } client = new EtcdClient(option.ConnectionString, option.Port, option.Username, option.Password, option.CaCert, option.ClientCert, option.ClientKey, option.PublicRootCa); client.WatchRange(WatchPath, ETCDWatchEvent, exceptionHandler: e => { ErrorOccurredEvent?.Invoke(e); Thread.Sleep(1000); }); //读取本地文件只初始化读取一次 InitConfigMapFromLocal(); } #region 实现的接口方法 //设置某个配置的值 public bool SetConfig(string configKey, string configValue) { string key = FillConfigKey(configKey); try { var putRet = client.Put(key, configValue); } catch (Exception e) { ErrorOccurredEvent?.Invoke(e); return false; } return true; } //删除某个配置的值 public bool DeleteConfig(string configKey) { string key = FillConfigKey(configKey); try { client.Delete(key); } catch (Exception e) { ErrorOccurredEvent?.Invoke(e); return false; } return true; } #endregion #region 基类 方法 //etcd 组织key protected override string FillConfigKey(string configKey) { return $"{WatchPath}/{configKey}"; } //etcd 获取远程key protected override (bool isSuccess, string value) GetValueFromServer(string configKey) { try { var value = client.GetVal(configKey); return (true, value); } catch (Exception e) { ErrorOccurredEvent?.Invoke(e); return (false, null); } } #endregion #region 私有方法 //监控value 变化的事件 private void ETCDWatchEvent(WatchEvent[] response) { lock (ConfigCenterBaseProvider.ObjLock) { foreach (WatchEvent e in response) { if (e.Type == Mvccpb.Event.Types.EventType.Delete) { if (ConfigMap.ContainsKey(e.Key)) { ConfigMap.Remove(e.Key); } } else { ConfigMap[e.Key] = e.Value; } Console.WriteLine(JsonConvert.SerializeObject(ConfigMap)); if (ConfigValueChangedEvent != null) { ConfigValueChangedEvent.Invoke(e.Key, e.Value, e.Type.ToString()); } } if (response != null && response.Any()) { //把最新的值写会本地文件 FlushLocalFileFromMap(); } } } #endregion }

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpzjjj.html