聊一聊基於Nacos的metadata完成服務間的AB測試


背景

在很多時候,產品同學或其他 boss 會有一些想法,或好或壞,都會想放到線上環境去驗證,看看能不能帶來更好的效果。

這其實就是一個提出假設和驗證假設的過程,而 AB 測試,是驗證假設的好方法。

對於服務之間的調用,這一塊其實也是相當符合的。

舉幾個例子吧

  1. A -> B,B 進行了重構
  2. A -> B,B 進行了算法模型的調整
  3. A -> B,B 加入了新特性
  4. ...

對於這幾個例子,正常的邏輯都是會讓小部分用戶或流量流進新的 B,觀察一段時間的數據,是否達到預期,再決策 B 是否真的可以上線。

在引入注冊中心 Nacos 之后我們對服務之間調用這一塊可以怎么做到呢?

答案就是 metadata(元數據)!!!

每個應用的實例基本信息比較少,但是 metadata 是可以很豐富的。

我們在向 Nacos Server 進行服務注冊的時候往往會附加一些 metadata ,可以參考官方文檔中 Dubbo 融合 Nacos 成為注冊中心 章節。

對於上述的被調用方新版 B 而已,完全可以把相關內容放進 metadata 中,好比說版本號,特性名等等。

調用方 A 就可以根據當前的用戶來判斷是否要走那個版本的被調用方 B。 當然這一步很多公司都會有相應的系統去管理,好比體驗用戶。

也可以看看下面這個流程圖,基本涉及到了。

接下來就根據上面的這個,做一個簡單的例子。

示例

首先是准備兩個被調用方 B。

帶特性的:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddNacosAspNet(builder.Configuration);

var app = builder.Build();

app.MapGet("/", () =>
{
    return Results.Ok("OK - feature");
});

app.Run("http://*:9885");
{
  "nacos": {
    "ServerAddresses": [ "http://localhost:8848" ],
    "DefaultTimeOut": 15000,
    "Namespace": "cs",
    "ListenInterval": 1000,
    "ServiceName": "providerb",
    "PreferredNetworks": "192.168",
    "GroupName": "DEFAULT_GROUP",
    "ClusterName": "DEFAULT",
    "Weight": 100,
    "Metadata": {
      "version": "1.0",
      "feature": "true"
    }
  }
}

正常的:

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddNacosAspNet(builder.Configuration);

var app = builder.Build();

app.MapGet("/", () =>
{
    return Results.Ok("OK - normal");
});

app.Run("http://*:9886");
{
  "nacos": {
    "ServerAddresses": [ "http://localhost:8848" ],
    "DefaultTimeOut": 15000,
    "Namespace": "cs",
    "ListenInterval": 1000,
    "ServiceName": "providerb",
    "PreferredNetworks": "192.168",
    "GroupName": "DEFAULT_GROUP",
    "ClusterName": "DEFAULT",
    "Weight": 100,
    "Metadata": {
      "version": "1.0",
      "feature": "false"
    }
  }
}

啟動這兩個被調用方,然后可以看到 Nacos 的服務詳情頁大致如下:

后面就是比較關鍵的調用方了。

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddNacosV2Naming(x =>
{
    x.ServerAddresses = new List<string> { "http://localhost:8848/" };
    x.Namespace = "cs";
});

var app = builder.Build();

app.MapGet("/req/{id}", Call);

app.Run("http://*:9884");

async Task<IResult> Call(ILoggerFactory loggerFactory, INacosNamingService svc, IHttpClientFactory factory, int id)
{
    var logger = loggerFactory.CreateLogger(nameof(Call));

    var allIns = await svc.GetAllInstances("providerb", "DEFAULT_GROUP", new List<string> { "DEFAULT" });

    // 按照對應的邏輯做對應的地址獲取方式
    // 這里是:id 小於 100 的走新特性
    string address = GetAddress(allIns, id < 100);

    var client = factory.CreateClient();

    var res = await client.GetStringAsync(address);

    logger.LogInformation("user={id},url={url},result={res}", id, address, res);

    return Results.Ok($"caller ------ {res}");
}

string GetAddress(List<Instance> instances, bool isFeature)
{
    var str = isFeature ? "true" : "false";

    var ins = instances
        .Where(x => x.Healthy 
        && x.Enabled 
        && x.Metadata.TryGetValue("feature", out var feature) 
        && feature.Equals(str))
        .OrderBy(x=>Guid.NewGuid())
        .FirstOrDefault();

    return ins != null 
        ? $"http://{ins.Ip}:{ins.Port}" 
        : throw new Exception("Can not find out ins");
}

啟動調用方程序,訪問並指定小於100和大於100的兩個用戶,可以看到調用的雖然是同一個服務,但是一個是訪問的 feature,另一個訪問的是 normal。

到這里我們已經可以做到根據不同的邏輯,將用戶導向到相同服務的不同版本上面了。

寫在最后

充分利用好服務實例的 metadata ,可以衍生出許多有意思的實踐。

nacos-sdk-csharp 的地址 :https://github.com/nacos-group/nacos-sdk-csharp

本文示例代碼的地址 :https://github.com/catcherwong-archive/2021/tree/main/ABTestWithNacos


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM