consul:啥?我被優化沒了?AgileConfig+Yarp替代Ocelot+Consul實現服務發現和自動網關配置

現在軟件就業環境不景氣,各行各業都忙着裁員優化。作爲一個小開發,咱也不能光等着別人來優化咱,也得想辦法優化下自己。就拿手頭上的工作來說吧,我發現我的微服務應用裏,既有AgileConfig這個配置中心組件,又有一個Consul 服務發現組件。本來吧他倆也沒啥事,各幹個的。但是,我在操作AgileConfig的時候發現了一個事image
然後我又一百度發現了這個AgileConfig 1.6.0 發佈 - 支持服務註冊與發現 - Agile.Zhou - 博客園 (cnblogs.com),有點意思。稍微一思索,我們現在的微服務解決方案裏網關用的ocelot+consul 作爲HTTP api網關,同時 還是用了 yarp做 grpc的網關,明顯可以看出來有一套多餘的網關在這裏。基於目前的情況,我是一直想優化掉 ocelot+consul這個組合。改用 agileConfig+yarp,奈何前期對微服務機制不是很熟悉,有堆坑要填。現在看到agileconfig的服務列表,又勾起了我這個優化的想法。說幹就幹,我理想的目標是可以直接從agileconfig上獲取到所有註冊的服務,然後用代碼來動態給yarp添加代理配置。這樣既可以優化掉一個consul服務,又可以免去每次服務部署時繁瑣的網關配置。

首先第一個任務就是解決yarp如何用代碼實現動態配置的問題。bing 裏搜索 yarp 動態配置 ,優先看博客園的博主發的文章,事實上我也就只看了這一篇Welcome to YARP - 2.2 配置功能 - 配置提供者(Configuration Providers) - coding-y - 博客園 (cnblogs.com) 。完美,問題解決。下面就是代碼時間。

通過上面兩篇文章我們知道,agileconfig會提供一個IDiscoveryService 接口來供程序獲取註冊的服務信息。同時 yarp也提供了從內存中提供配置的 InMemoryConfigProvider ,那我們只需要在agileconfig 註冊之後 通過 IDisconverService接口 獲取所有已註冊服務,然後再讓yarp應用上內存中的配置即可實現服務註冊後自動配置代理的需求。

下面我們動動手指頭 按下 ctrl c ,ctrl v實現如下代碼:代碼不具備通用性 需要進一步優化 建議已給出。

using AgileConfig.Client;
using AgileConfig.Client.RegisterCenter;
using Newtonsoft.Json.Linq;

using Yarp.ReverseProxy.Configuration;
using Yarp.ReverseProxy.Transforms;

namespace Microsoft.Extensions.DependencyInjection
{
    public static class AgileConfigProxyConfigProviderExtend
    {
        const string NotProxyStr = "notProxy";
        const string TransformsStr = "Transforms";
        static readonly ILogger _Logger = LoggerFactory.Create(b => { }).CreateLogger("AgileConfigProxyConfigProviderExtend");
        public static RouteConfig[] GetRoutes(this IDiscoveryService discoveryService)
        {
            var routes = new List<RouteConfig>();
            foreach (var item in discoveryService.Services)
            {
                if (item.MetaData.Any(r=>r.Equals(NotProxyStr, StringComparison.OrdinalIgnoreCase)))
                {
                    continue;
                }
                var route = new RouteConfig
                {
                    RouteId = item.ServiceId,
                    ClusterId = item.ServiceName,
                    Match = new RouteMatch
                    {
                        Path = $"/{item.ServiceName}/{{**all}}"
                    }
                };
                //.WithTransformPathRouteValues(pattern: new PathString("/{**all}"))
                try
                {
                    var transformStr = item.MetaData.FirstOrDefault(r => r.StartsWith(TransformsStr));
                    if (transformStr is not null)
                    {
                        var jobj = JObject.Parse(transformStr.Split(':')[1]);
                        foreach (var k in jobj)
                        {
                            route.WithTransform(d => d.Add(k.Key, k.Value?.ToString() ?? ""));
                        }
                    }
                }
                catch (Exception e)
                {
                    _Logger.LogError(e,"生成路由【轉換】配置時出錯");
                }

                routes.Add(route);
                _Logger.LogTrace("添加路由{RouteId}", route.RouteId);
            }
            return routes.ToArray();
        }
        public static ClusterConfig[] GetClusters(this IDiscoveryService discoveryService)
        {
            var clusters = new List<ClusterConfig>();
            var proxyServices = discoveryService.Services
                                    .Where(r => !r.MetaData.Any(r => r.Equals(NotProxyStr, StringComparison.OrdinalIgnoreCase)))
                                    .GroupBy(p => p.ServiceName);

            foreach (var item in proxyServices)
            {
                var destinations = new Dictionary<string, DestinationConfig>(StringComparer.OrdinalIgnoreCase);
                foreach (var service in item)
                {
                    destinations.Add(service.ServiceId, new DestinationConfig() {
                        Address=service.AsHttpHost()
                    });
                }

                clusters.Add(new ClusterConfig
                {
                    ClusterId = item.Key,
                    Destinations = destinations
                });
            }

            return clusters.ToArray();
        }
        //可以再加一個重載 支持傳入一個委託 來自定義構造配置。
        public static IReverseProxyBuilder LoadFromAgileConfigByInMemoryConfigProvider(this IReverseProxyBuilder builder, ConfigClient client)
        {
            var discoveryService = client.DiscoveryService();
            discoveryService ??= new DiscoveryService(client, LoggerFactory.Create(b => b.SetMinimumLevel(LogLevel.Information)));

            var configProvider = new InMemoryConfigProvider(discoveryService.GetRoutes(), discoveryService.GetClusters());

            builder.Services.AddSingleton(configProvider);
            builder.Services.AddSingleton<IProxyConfigProvider>(configProvider);

            discoveryService.ReLoaded += () =>
            {
                configProvider.Update(discoveryService.GetRoutes(), discoveryService.GetClusters());
            };
            return builder;
        }
    }
}

現在只需要調用如下代碼,即可給原有的yarp服務加上自動生成代理配置的功能了。

builder.Services.AddReverseProxy()//添加ReverseProxy相關服務到DI

.LoadFromAgileConfigByInMemoryConfigProvider((ConfigClient)client);

注意:註冊AgileConfig時候請使用 UseAgileConfig()方法註冊。Addxxx方法會導致無法獲取到agileConfig上的已註冊服務對信息。
代碼已傳gitee:https://gitee.com/dotnetfans/yarp-auto-proxy.-agile-config
同時也個給agileConfig 提交了合併請求。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章