• Nacos2 .Net SDK源码阅读 牧羊人


    创建webapi项目

    var builder = WebApplication.CreateBuilder(args);
    // 引入Nacos
    builder.Host.ConfigureAppConfiguration((context, builder) => {
        var configuration = builder.Build();
        builder.AddNacosV2Configuration(configuration.GetSection("NacosConfig"));
    });
    builder.Build().Run()
    

    拷贝官方的配置

    {
      "NacosConfig": {
        "Listeners": [//配置监听,这里会在初始化的时候,
          {
            "Optional": true,
            "DataId": "demo",
            "Group": "DEFAULT_GROUP"
          }
        ],
        "Namespace": "", // 注意这里要用的是命名空间的ID,如果是空的用的是public命名空间
        "ServerAddresses": [ "http://localhost:8848/", "http://localhost:8849/", "http://localhost:8850/"],
        "UserName": "nacos",
        "Password": "nacos",
        "AccessKey": "",
        "SecretKey": "",
        "EndPoint": "acm.aliyun.com",
        "ConfigFilterAssemblies": [ "YouPrefix.AssemblyName" ],
        "ConfigFilterExtInfo": "some ext infomation"
      }
    }
    

    看一下builder.AddNacosV2Configuration()方法的实现

     public static IConfigurationBuilder AddNacosV2Configuration(
               this IConfigurationBuilder builder,
               IConfiguration configuration,
               INacosConfigurationParser parser = null,
               Action<ILoggingBuilder> logAction = null)
     {
         if (builder == null)
         {
             throw new ArgumentNullException(nameof(builder));
         }
    
         if (configuration == null)
         {
             throw new ArgumentNullException(nameof(configuration));
         }
    	 //新建configsource
         var source = new NacosV2ConfigurationSource();
         configuration.Bind(source);
         //这里配置的格式默认是JSON的,如果调用AddNacosV2Configuration方法没指定parser,如果配置数据用了其他格式的,就会抛异常
         source.NacosConfigurationParser = parser ?? DefaultJsonConfigurationStringParser.Instance;
         source.LoggingBuilder = logAction;
    
         return builder.Add(source);
     }
    

    NacosV2ConfigurationSource实现了IConfigurationSource

    public class NacosV2ConfigurationSource : Nacos.V2.NacosSdkOptions, IConfigurationSource
    

    实现了Build方法

    public IConfigurationProvider Build(IConfigurationBuilder builder)
    {
        return new NacosV2ConfigurationProvider(this);
    }
    

    继续看NacosV2ConfigurationProvider构造方法

    public NacosV2ConfigurationProvider(NacosV2ConfigurationSource configurationSource)
    {
        _configurationSource = configurationSource;
        _parser = configurationSource.NacosConfigurationParser;
        _configDict = new ConcurrentDictionary<string, string>(StringComparer.OrdinalIgnoreCase);
        _listenerDict = new Dictionary<string, MsConfigListener>();
    	// 读取全部配置文件的信息
        var options = Options.Create(new NacosSdkOptions()
                                     {
                                         ServerAddresses = configurationSource.ServerAddresses,
                                         Namespace = configurationSource.GetNamespace(),
                                         AccessKey = configurationSource.AccessKey,
                                         ContextPath = configurationSource.ContextPath,
                                         EndPoint = configurationSource.EndPoint,
                                         DefaultTimeOut = configurationSource.DefaultTimeOut,
                                         SecretKey = configurationSource.SecretKey,
                                         Password = configurationSource.Password,
                                         UserName = configurationSource.UserName,
                                         ListenInterval = 20000,
                                         ConfigUseRpc = configurationSource.ConfigUseRpc,
                                         ConfigFilterAssemblies = configurationSource.ConfigFilterAssemblies,
                                         ConfigFilterExtInfo = configurationSource.ConfigFilterExtInfo,
                                     });
    
        var nacosLoggerFactory = Nacos.Microsoft.Extensions.Configuration.NacosLog.NacosLoggerFactory.GetInstance(configurationSource.LoggingBuilder);
        _logger = nacosLoggerFactory.CreateLogger<NacosV2ConfigurationProvider>();
        _client = new NacosConfigService(nacosLoggerFactory, options);
        if (configurationSource.Listeners != null && configurationSource.Listeners.Any())
        {
            var tasks = new List<Task>();
    
            foreach (var item in configurationSource.Listeners)
            {
                // 默认初始化的是MsConfigListener
                var listener = new MsConfigListener(item.DataId, item.Group, item.Optional, this, _logger);
    
                tasks.Add(_client.AddListener(item.DataId, item.Group, listener));
    
                _listenerDict.Add($"{item.DataId}#{item.Group}", listener);
            }
    
            Task.WaitAll(tasks.ToArray());
        }
        else
        {
            //这里就是为什么配置文件没有配置任何监听器就抛异常的原因了
            throw new Nacos.V2.Exceptions.NacosException("Listeners is empty!!");
        }
    }
    

    看一下MsConfigListener的实现,主要看ReceiveConfigInfo方法

    public void ReceiveConfigInfo(string configInfo)
    {
        _logger?.LogDebug("MsConfigListener Receive ConfigInfo 【{0}】", configInfo);
        try
        {
            _provider._configDict[_key] = configInfo;
    		//
            var nData = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
    
            foreach (var listener in _provider._configurationSource.Listeners)
            {
                var key = $"{_provider._configurationSource.GetNamespace()}#{listener.Group}#{listener.DataId}";
                if (_provider._configDict[key] == null)
                {
                    continue;
                }
    			//这里注意provider的parser的格式器,如果格式不匹配,这里会抛异常,默认是JSON的
                var data = _provider._parser.Parse(_provider._configDict[key]);
    
                foreach (var item in data)
                {
                    nData[item.Key] = item.Value;
                }
            }
    
            _provider.Data = nData;
            //重新加载配置
            _provider.OnReload();
        }
        catch (Exception ex)
        {
            _logger?.LogWarning(ex, $"call back reload config error");
            if (!_optional)
            {
                throw;
            }
        }
    }
    

    那ReceiveConfigInfo是怎么触发的呢?

    CacheData这个类里面

    public void CheckListenerMd5()
    {
        foreach (var wrap in Listeners)
        {
            // 检查MD5,如果不一致,通知更新配置
            if (!wrap.LastCallMd5.Equals(Md5))
            {
                SafeNotifyListener(DataId, Group, Content, Type, Md5, EncryptedDataKey, wrap);
            }
        }
    }
    private void SafeNotifyListener(string dataId, string group, string content, string type,
                                    string md5, string encryptedDataKey, ManagerListenerWrap wrap)
    {
        var listener = wrap.Listener;
    
        ConfigResponse cr = new ConfigResponse();
        cr.SetDataId(dataId);
        cr.SetGroup(group);
        cr.SetContent(content);
        cr.SetEncryptedDataKey(encryptedDataKey);
        ConfigFilterChainManager.DoFilter(null, cr);
    
        // after filter, such as decrypted value
        string contentTmp = cr.GetContent();
    
        wrap.LastContent = content;
        wrap.LastCallMd5 = md5;
    
        // should pass the value after filter
        listener.ReceiveConfigInfo(contentTmp);
    }
    

    再往上看ConfigRpcTransportClient,因为用的是RPC调用,实现了AbstConfigTransportClient抽象类,如果是http,那么对应的类是ConfigHttpTransportClient

    private async Task RefreshContentAndCheck(CacheData cacheData, bool notify)
    {
        try
        {
            var resp = await GetServerConfig(cacheData.DataId, cacheData.Group, cacheData.Tenant, 3000L, notify).ConfigureAwait(false);
            cacheData.SetContent(resp.GetContent());
    
            if (resp.GetConfigType().IsNotNullOrWhiteSpace()) cacheData.Type = resp.GetConfigType();
    
            cacheData.CheckListenerMd5();
        }
        catch (Exception ex)
        {
            _logger?.LogError(ex, "refresh content and check md5 fail ,dataid={0},group={1},tenant={2} ", cacheData.DataId, cacheData.Group, cacheData.Tenant);
        }
    }
    
    protected async override Task ExecuteConfigListen()
    {
        //删了很多代码
        var rpcClient = EnsureRpcClient(taskId);
    
        var configChangeBatchListenResponse = (ConfigChangeBatchListenResponse)(await RequestProxy(rpcClient, request).ConfigureAwait(false));
    
        if (configChangeBatchListenResponse != null && configChangeBatchListenResponse.IsSuccess())
        {
            var changeKeys = new HashSet<string>();
    
            if (configChangeBatchListenResponse.ChangedConfigs != null && configChangeBatchListenResponse.ChangedConfigs.Any())
            {
                foreach (var item in configChangeBatchListenResponse.ChangedConfigs)
                {
                    var changeKey = GroupKey.GetKeyTenant(item.DataId, item.Group, item.Tenant);
    
                    changeKeys.Add(changeKey);
    
                    await RefreshContentAndCheck(changeKey, true).ConfigureAwait(false);
                }
            }
        //删了很多代码
        }
    
    protected override void StartInner()
    {
        InitSecurityProxy();
    
        Task.Factory.StartNew(async () =>
                              {
                                  while (true)
                                  {
                                      try
                                      {
                                          // block 5000ms
                                          if (_listenExecutebell.TryTake(out _, 5000))
                                          {
                                              await ExecuteConfigListen().ConfigureAwait(false);
                                          }
                                          else
                                          {
                                              continue;
                                          }
                                      }
                                      catch (Exception ex)
                                      {
                                          _logger?.LogError(ex, "[ rpc listen execute ] [rpc listen] exception");
                                      }
                                  }
                              });
    }
    
    protected override Task NotifyListenConfig()
    {
        _listenExecutebell.Add(_bellItem);
        return Task.CompletedTask;
    }
    
    private void InitHandlerRpcClient(RpcClient rpcClientInner)
    {
        //注册到
        rpcClientInner.RegisterServerPushResponseHandler(new ConfigRpcServerRequestHandler(_logger, _cacheMap, NotifyListenConfig));
        rpcClientInner.RegisterConnectionListener(new ConfigRpcConnectionEventListener(_logger, rpcClientInner, _cacheMap, _listenExecutebell));
        rpcClientInner.Init(new ConfigRpcServerListFactory(_serverListManager));
    }
    
    private RpcClient EnsureRpcClient(string taskId)
    {
        lock (_obj)
        {
            Dictionary<string, string> labels = GetLabels();
            Dictionary<string, string> newlabels = new Dictionary<string, string>(labels);
            newlabels["taskId"] = taskId;
    
            RpcClient rpcClient = RpcClientFactory
                .CreateClient($"{uuid}_config-{taskId}", RemoteConnectionType.GRPC, newlabels);
    
            if (rpcClient.IsWaitInited())
            {
                InitHandlerRpcClient(rpcClient);
                rpcClient.SetTenant(GetTenant());
                rpcClient.SetClientAbilities(InitAbilities());
                rpcClient.Start();
            }
    
            return rpcClient;
        }
    }
    

    GrpcClient里

    public void RegisterServerPushResponseHandler(IServerRequestHandler serverRequestHandler)
    {
        this.serverRequestHandlers.Add(serverRequestHandler);
    }
    

    while (await call.ResponseStream.MoveNext(cts.Token).ConfigureAwait(false))这里通过RPC的steam的特性,客户端阻塞等待nacos服务端把数据写入到流中,然后触发了上面一系列的调用,最终执行了IListener的ReceiveConfigInfo方法。

    这是GRPC官方的说明

    • A bidirectional streaming RPC where both sides send a sequence of messages using a read-write stream. The two streams operate independently, so clients and servers can read and write in whatever order they like: for example, the server could wait to receive all the client messages before writing its responses, or it could alternately read a message then write a message, or some other combination of reads and writes. The order of messages in each stream is preserved. You specify this type of method by placing the stream keyword before both the request and the response.

      // Accepts a stream of RouteNotes sent while a route is being traversed,
      // while receiving other RouteNotes (e.g. from other users).
      rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}
      

      这是nacos_grpc_service.proto定义的方法

      service BiRequestStream {
          // Sends a commonRequest
          rpc requestBiStream (stream Payload) returns (stream Payload) {
          }
      }
      
    private Grpc.Core.AsyncDuplexStreamingCall<Nacos.Payload, Nacos.Payload> BindRequestStream(Nacos.BiRequestStream.BiRequestStreamClient client, GrpcConnection grpcConn)
            {
                var call = client.requestBiStream();
    
                System.Threading.Tasks.Task.Factory.StartNew(
                   async () =>
                   {
                       var cts = new System.Threading.CancellationTokenSource();
    
                       while (await call.ResponseStream.MoveNext(cts.Token).ConfigureAwait(false))
                       {
                           var current = call.ResponseStream.Current;
    
                           var parseBody = GrpcUtils.Parse(current);
    
                           var request = (CommonRequest)parseBody;
                           if (request != null)
                           {
                               try
                               {
                                   //这里会循环调用上一步RegisterServerPshResponseHandler()的方法
                                   var response = HandleServerRequest(request);
                                   response.RequestId = request.RequestId;
                                   await call.RequestStream.WriteAsync(GrpcUtils.Convert(response)).ConfigureAwait(false);
                               }
                               catch (Exception)
                               {
                                   throw;
                               }
                           }
                       }
    
                       if (IsRunning() && !grpcConn.IsAbandon())
                       {
                           logger?.LogInformation(" Request Stream onCompleted ,switch server ");
    
                           if (System.Threading.Interlocked.CompareExchange(ref rpcClientStatus, RpcClientStatus.UNHEALTHY, RpcClientStatus.RUNNING) == RpcClientStatus.RUNNING)
                           {
                               SwitchServerAsync();
                           }
                       }
                       else
                       {
                           logger?.LogInformation("client is not running status ,ignore complete  event ");
                       }
                   }, System.Threading.Tasks.TaskCreationOptions.LongRunning);
    
                return call;
    }
    

    循环调用上一步RegisterServerPshResponseHandler()的方法

    protected CommonResponse HandleServerRequest(CommonRequest request)
    {
        foreach (var serverRequestHandler in serverRequestHandlers)
        {
            var response = serverRequestHandler.RequestReply(request);
            if (response != null) return response;
        }
        return null;
    }
    

    关于流双工通信,nacos做了一下封装,下面这个例子说明一下

    protobuf文件定义

    syntax = "proto3";
    
    option csharp_namespace = "GrpcStreamServer";
    
    package grpcstream;
    
    service StreamGrpc {
      rpc CommitStream (stream Request) returns (stream Reply);
    }
    
    message Request {
      string name = 1;
    }
    
    message Reply {
      string message = 1;
    }
    

    服务端实现代码

    namespace GrpcStreamServer
    {
        public class GrpcStreamService :StreamGrpc.StreamGrpcBase
        {
            public override async Task CommitStream(IAsyncStreamReader<Request> requestStream, IServerStreamWriter<Reply> responseStream, ServerCallContext context)
            {
                if (requestStream.MoveNext().GetAwaiter().GetResult())
                {
                    Request req = requestStream.Current;
                    await responseStream.WriteAsync(new Reply() { Message = $"Hi {req.Name},Here is GrpcStream  For .NET" });
                }
            }
        }
    }
    

    客户端

    GrpcChannel channel = GrpcChannel.ForAddress("https://localhost:7196");
    var client = new GrpcStreamClient.StreamGrpc.StreamGrpcClient(channel);
    var call = client.CommitStream();
    
    await call.RequestStream.WriteAsync(new Request() { Name = "Aming" });
    
    while (await call.ResponseStream.MoveNext())
    {
        Reply reply = call.ResponseStream.Current;
        Console.WriteLine(reply.Message);
    }
    Console.ReadLine();
    
    

    这个例子,在WriteAsync之后才会收到消息

  • 相关阅读:
    利用if else判断几点是什么时间段
    【UML】活动图介绍
    【UML】类图介绍
    jQuery Ajax跨域问题简易解决方案
    ASP.NET MVC @Html.Label的问题
    Mysql Show ProcessList命令
    【ASP.NET MVC 学习笔记】- 20 ASP.NET Web API
    【ASP.NET MVC 学习笔记】- 19 REST和RESTful Web API
    【ASP.NET MVC 学习笔记】- 18 Bundle(捆绑)
    【ASP.NET MVC 学习笔记】- 17 Model验证
  • 原文地址:https://www.cnblogs.com/hunter2014/p/16382233.html
Copyright © 2020-2023  润新知