/// <summary> /// HttpProxy 配置,主要配置那些微服务可以提供外部api服务 /// </summary> /// <param name="proxyName">proxyName</param> /// <param name="updateAction">updateAction</param> /// <returns>HttpProxy</returns> public static Configuration GetHttpProxy(string proxyName, Action <Configuration> updateAction) { switch (configCenter.ConfigType) { case ConfigType.Local: case ConfigType.HttpFile: return(config); // 直接返回本地配置 case ConfigType.Zookeeper: Action <Configuration> callBack = (Configuration cfgValue) => { GetHttpProxy(proxyName, updateAction); // 调用一次方法,挂载回调 updateAction(cfgValue); // 重连之后要执行回调,做变更 }; callBackList[proxyName] = callBack; // 断线重连之后,要把当前方法封装起来,作为回调 var router = new ConfigWatcher(); router.OnChange += (string path) => { if (updateAction != null && !string.IsNullOrEmpty(path)) { var proxyStr = GrantZookeeperManager.GetNodeData(path, router); if (string.IsNullOrEmpty(proxyStr)) { return; } var httpProxy = Newtonsoft.Json.JsonConvert.DeserializeObject <Configuration>(proxyStr); config.HttpProxy = httpProxy.HttpProxy; updateAction(config); } }; string p = GrantZookeeperManager.getConfigPath(proxyName); var cfg = GrantZookeeperManager.GetNodeData(p + "/" + GrantHttpProxy.HttpProxy, router); if (string.IsNullOrEmpty(cfg)) { logger.LogWarning("获取代理层配置为空,这个代理层将无法提供代理服务 ServerSetting.GetHttpProxy is Null"); } else { var proxy = Newtonsoft.Json.JsonConvert.DeserializeObject <Configuration>(cfg); config.HttpProxy = proxy.HttpProxy; } return(config); } return(null); }
/// <summary> /// 注册zookeeper使用 /// </summary> /// <param name="serverName"></param> /// <param name="ip"></param> /// <param name="port"></param> /// <param name="enable"></param> /// <param name="timeout"></param> public static void RegisterRouter(string serverName, string ip, int port, bool enable, int timeout) { if (configCenter.ConfigType != ConfigType.Zookeeper) { return; // 本地配置不走zookeeper; } string path = GrantZookeeperManager.SetRouter(serverName, ip, port, enable, timeout); var cfgWatcher = new ConfigWatcher(); cfgWatcher.OnChange += (string p) => { var changeData = GrantZookeeperManager.GetNodeData(path, cfgWatcher); if (!string.IsNullOrEmpty(changeData)) { try { var clientItem = Newtonsoft.Json.JsonConvert.DeserializeObject <ClientItem>(changeData); logger.LogWarning($"我的配置被修改为:{changeData}"); if (clientItem != null && !clientItem.Enable) { // 我被下线了,更新本地配置"Enable": false,下一次重启需要带上这个状态 logger.LogInformation($"我被管理员下线了,哼。。。。serverName={serverName},ip={ip},port = {port}"); } else { logger.LogInformation($"我被管理员上线了,哈哈哈哈。。。。serverName={serverName},ip={ip},port = {port}"); } // 在这里修改本地配置快照 if (clientItem == null || string.IsNullOrEmpty(clientItem.Ip) || clientItem.Port < 1) { return; } config.GrantConfig.RpcService.Ip = clientItem.Ip; config.GrantConfig.RpcService.Port = clientItem.Port; config.GrantConfig.RpcService.Pool = clientItem.Pool; config.GrantConfig.RpcService.Enable = clientItem.Enable; config.GrantConfig.RpcService.TimeOut = clientItem.TimeOut; //copyConfig(); 暂时注掉,这里还没思考好 } catch (Exception e) { logger.LogCritical(e, $"RegisterRouter.cfgWatcher.OnChange.Error,serverName={serverName},ip={ip},port = {port}"); } } }; GrantZookeeperManager.GetNodeData(path, cfgWatcher); // 监控自己router节点的内容,有可能被置为下线; }
private static void initlizeData(string appName) { // 检查标准配置,第一次可能zk是空 // 检查标准配置节点,帮助初始化 var standConfig = getStandConfig(); GrantZookeeperManager.CheckConfig(appName, standConfig); // 拉取当前AppName的配置,需要注册watcher var dataWatcher = new ConfigWatcher(); dataWatcher.OnChange += (string path) => { string configData = GrantZookeeperManager.GetNodeData(path, dataWatcher); if (string.IsNullOrEmpty(configData)) { return; } UpdateZookeeper(path, configData); }; List <string> childrens = GrantZookeeperManager.GetConfigChildren(appName, null); // 配置是整个获取节点,分别获取配置和分别增加watcher if (childrens != null && childrens.Count > 0) { string root = GrantZookeeperManager.getConfigPath(appName); if (string.IsNullOrEmpty(root)) { return; } foreach (var item in childrens) { // 需要根据节点路径来判断是哪个节点变化了 string path = root + "/" + item; string configData = GrantZookeeperManager.GetNodeData(path, dataWatcher); UpdateZookeeper(path, configData); } } }
private static RpcClients getRouters(string appName, Watcher serviceRouterWatcher) { List <string> nodeList = GrantZookeeperManager.GetRouterChildren(appName, serviceRouterWatcher); // 路由是整个获取节点整个跟节点监控,因为子节点是虚拟的 if (nodeList == null || nodeList.Count < 1) { string msg = $"你代码里面调用了 {appName} ,但是从zookeeper中取不到这个服务的路由信息,GetAppClient.appName={appName}.GetChildrenNode==null"; logger.LogWarning(msg); return(null); } RpcClients rpcClients = new RpcClients(); rpcClients.Clients = new List <Client>(); Client client = new Client() { RouterType = RouterType.Random, ServerName = appName }; client.Items = new List <ClientItem>(); rpcClients.Clients.Add(client); string p = GrantZookeeperManager.getRouterPath(appName); if (string.IsNullOrEmpty(p)) { return(null); } foreach (var item in nodeList) { string nodeData = GrantZookeeperManager.GetNodeData(p + "/" + item, serviceRouterWatcher); if (string.IsNullOrEmpty(nodeData)) { continue; } ClientItem clientItem = Newtonsoft.Json.JsonConvert.DeserializeObject <ClientItem>(nodeData); client.Items.Add(clientItem); } config.RpcClients = rpcClients; return(rpcClients); }
/// <summary> /// RpcClinet 客户端初始化信息,路由 /// </summary> /// <param name="appName">appName</param> /// <returns>XElement</returns> public static Configuration GetAppClient(string appName, Action <Configuration> updateAction) { switch (configCenter.ConfigType) { case ConfigType.Local: case ConfigType.HttpFile: return(config); // 直接返回本地配置 case ConfigType.Zookeeper: // 去远程拉取,因为初始化的时候,是初始化的当前服务的配置, // 引用客户端的配置需要分别拉取 // 因为zk没有提供拉节点数据和子节点的接口,只能先拉到Node然后在依次拉子节点的数据 var serviceRouterWatcher = new ServiceRouterWatcher(); serviceRouterWatcher.OnChange += (string path) => { if (updateAction != null && !string.IsNullOrEmpty(path)) { config.RpcClients = getRouters(appName, serviceRouterWatcher); updateAction(config); } }; Action <Configuration> callBack = (Configuration cfgValue) => { GetAppClient(appName, updateAction); // 调用一次方法,挂载回调 updateAction(config); // 重连之后要执行回调,做变更 }; callBackList[appName] = callBack; // 断线重连之后,要把当前方法封装起来,作为回调 config.RpcClients = getRouters(appName, serviceRouterWatcher); GrantZookeeperManager.SetRelation(appName, _appName); // 设置调用关系 return(config); default: return(null); } }
/// <summary> /// 注册服务 /// </summary> /// <param name="server">服务配置</param> public void RpcServerRegister(GrantServerConfig server) { try { distributer = new GrantRpcDistributer(server); // 这里会初始化serverSetting和zk连接以及其他配置 // 注册zk登记自己(包括serverName,ip,port,pool) server.ServerName = distributer.ShortName; // 兼容集中配置 if (server.PortList != null && server.PortList.ContainsKey(server.ServerName)) { server.Port = server.PortList[server.ServerName]; ServerSetting.UpdateRpcPort(server.Port); } bool IsExit = false; // 要先初始化系统相关组件之后才能注册Rpc端口,要不请·求上来了,还没有初始化其他的,会有问题 // 去zk注册自己 if (ServerSetting.ConfigCenter.ConfigType == ConfigType.Zookeeper) { Task.Run(() => { Random r = new Random(); // rpc注册socket是阻塞的,只能提前通过线程注册, 等待1s 再注册路由,这个过程中,如果rpc端口不成功,将会撤销资源退出,这个注册也就失败了,防止先注册路由,然后再rpc注册异常,导致路由瞬间抖动,造成瞬间无效广播; Thread.Sleep(r.Next(1500, 2500)); // 随机等待在1s--2s之间吧,防止集群一瞬间同时重启,容易形成广播风暴,形成雪崩; if (IsExit) { return; } ServerSetting.RegisterRouter(server.ServerName, server.Ip, server.Port, ServerSetting.GetRpcServer().Enable, server.TimeOut); logger.LogInformation($"\r\n服务名:{server.ServerName},开始监听Ip是:{server.Ip},端口是:{server.Port}\r\n*******************▄︻┻┳══━一zookeeper路由注册成功,系统启动成功!▄︻┻┳══━一*******************"); }); } else { Task.Run(() => { Thread.Sleep(1500); if (IsExit) { return; } logger.LogInformation($"\r\n服务名:{server.ServerName},开始监听Ip是:{server.Ip},端口是:{server.Port}\r\n*******************▄︻┻┳══━一启用本地配置,系统启动成功!▄︻┻┳══━一*******************"); }); } ServerRegister(server); // 底层通讯和业务进行绑定 // 只有socket才会阻塞 if (server.ServerType == ServerType.Thrift || server.ServerType == ServerType.Grpc) { IsExit = true; // 通知所有注册方,停止注册,系统要撤销所有资源了,防止其他异常,优雅一点点,不能太暴力 logger.LogWarning($"\r\n rpc端口监听异常退出:{server.ServerName}{server.Port}{server.AssemblyPath}" + " \r\n time=" + DateTime.Now.ToString("yy-MM-dd HH:mm:ss:ffff")); if (ServerSetting.ConfigCenter.ConfigType == ConfigType.Zookeeper) { GrantZookeeperManager.ClearRouter(server.ServerName, server.Ip); } // rpc监听是hold住当前线程的,因为底层吞掉异常了,跑到这里来就说明异常了,要彻底释放 // 让他等一下,把日志写完 System.Threading.Thread.Sleep(1000); // 如果启动失败,一定要彻底清理退出,因为在线程中,只能这样,要不只是线程退出,主程序还运行 Environment.Exit(0); } } catch (Exception ex) { logger.LogCritical(ex, "\r\n Error 服务异常退出 " + DateTime.Now.ToString("yy-MM-dd HH:mm:ss:ffff")); // 让他等一下,把日志写完 System.Threading.Thread.Sleep(1000); // 如果启动失败,一定要彻底清理退出,因为在线程中,只能这样,要不只是线程退出,主程序还运行 Environment.Exit(0); } }
/// <summary> /// RpcServer 服务端 /// 根据配置加载配置信息,凡是后端微服务不用自己初始化, /// 直接注册RpcServer就可以(RpcServer里面已经初始化了),如果也要注册RpcClient,RpcClient的注册要放在后面 /// 在前端代理层,需要先初始化Initlize() /// </summary> /// <param name="appName">appName</param> /// <param name="pool">pool</param> public static void Initlize(string appName, int pool) { try { checkInitlize(); ServerSetting._appName = appName; ServerSetting._pool = pool; switch (configCenter.ConfigType) { default: case ConfigType.Local: case ConfigType.HttpFile: UpdateLocal(config); // 本地的配置,一次就加载完整了 break; case ConfigType.Zookeeper: // 根据appName拉取信息,注册zk,这里需要注意热更新的问题 // 初始化连接,并注册对连接的监听 ZKConnectionWatcher connectionWatcher = new ZKConnectionWatcher(); connectionWatcher.OnChange += (string path) => { GrantZookeeperManager.reConnection(new KeeperException.SessionExpiredException(), () => { try { // Initlize(appName, pool); // 更新了config initlizeData(appName); } catch (Exception e) // 重连的就不能抛异常了 { logger.LogError(e, $"reConnection.Initlize.Error"); } var list = callBackList.Values.ToArray(); for (int i = 0; i < list.Length; i++) { try { list[i]?.Invoke(config); // callBackList[callBackList.Keys[i]]?.Invoke(config); // 依次执行回调链,保证更新及时 } catch (Exception e) { logger.LogError(e, $"reConnection.callBack.Error"); } } // 断线重连,注册自己 RegisterRouter(ServerSetting.AppName, ServerSetting.Config.GrantConfig.RpcService.Ip, ServerSetting.Config.GrantConfig.RpcService.Port, ServerSetting.Config.GrantConfig.RpcService.Enable, ServerSetting.Config.GrantConfig.RpcService.TimeOut); }); }; GrantZookeeperManager.Initlize( configCenter.Ip, configCenter.SessionTimeout, connectionWatcher); initlizeData(appName); break; } } catch (Exception ex) { logger.LogCritical(ex, "ServerSetting.Initlize.Error"); throw ex; } }