zoukankan      html  css  js  c++  java
  • 聊一聊基于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

    如果您认为这篇文章还不错或者有所收获,可以点击右下角的【推荐】按钮,因为你的支持是我继续写作,分享的最大动力!
    声明: 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。如果您发现博客中出现了错误,或者有更好的建议、想法,请及时与我联系!!如果想找我私下交流,可以私信或者加我微信。
  • 相关阅读:
    nginx配置zabbix下setup.php(web页面)无法显示,浏览器无法打开
    CentOS release 6.5下jdk1.7升级到1.8
    tcp流量控制
    图像处理服务器
    muduo rpc protobuf 实现学习
    p2p nat 穿透原理
    博客-livevent-stl-cpp-nginx
    使用eventfd创建一个用于事件通知的文件描述符
    多线程设计的类的思考!
    ftp协议服务器与tinyhttp服务demo
  • 原文地址:https://www.cnblogs.com/catcher1994/p/15678360.html
Copyright © 2011-2022 走看看