说明

RulesEngine 是 C# 写的一个规则引擎类库,读者可以从这些地方了解它:

仓库地址:

https://github.com/microsoft/RulesEngine

使用方法:

https://microsoft.github.io/RulesEngine

文档地址:

https://github.com/microsoft/RulesEngine/wiki

什么是规则引擎?

照搬 https://github.com/microsoft/RulesEngine/wiki/Introduction#what-is-the-rules-engine

在企业项目中,关键或核心部分总是业务逻辑或业务规则,也就是 CRUD,这些系统都有一个共同的特征是,某个模块中的一些或许多规则或策略总会发生变化,例如购物网站的顾客折扣、物流企业的运价计算等。随着这些变化而来的是大量的重复工作,如果系统没有足够的抽象,那么每当增加一种规则时,开发者需要在规则、回归测试、性能测试等方面的变化中编写代码。

在 RulesEngine 中,微软对规则进行了抽象,这样核心逻辑总是得到稳定的、易于维护的,而规则的更改可以以一种简单的方式生成,而不需要更改代码库。此外,系统的输入本质上是动态的,因此不需要在系统中定义模型,而是可以作为扩展对象或任何其他类型的对象作为输入,系统经过预定义的规则处理后,输出结果。

它有以下特性:

  • Json based rules definition (基于 Json 的规则定义)
  • Multiple input support (多输入支持)
  • Dynamic object input support (动态对象输入支持)
  • C# Expression support (C # 表达式支持)
  • Extending expression via custom class/type injection (通过自定义类/类型注入扩展表达式)
  • Scoped parameters (范围参数)
  • Post rule execution actions (发布规则执行操作)

说人话就是,业务逻辑的输出结果受到多个因子影响,但是这些影响有一定规律的,那么适合将这些部分抽象出来,接着使用规则引擎处理,例如购物的各种优惠卷叠加之后的最终折扣价、跨区运输的不同类型的包裹运价计算等。

笔者认为这个规则引擎主要由两部分构成:

  • 规则验证系统,例如根据规则验证字段、执行函数验证当前流程、输出执行结果;
  • 动态代码引擎,能够将字符串转换为动态代码,利用表达式树这些完成;

当然,这样说起来其实很抽象的,还得多撸代码,才能明白这个 RulesEngine 到底是干嘛的。

安装

新建项目后,nuget 直接搜索 RulesEngine 即可安装,在 nuget 介绍中可以看到 RulesEngine 的依赖:

FluentValidation 是一个用于构建强类型验证规则的 .NET 库,在 ASP.NET Core 项目中,我们会经常使用模型验证,例如必填字段使用 [Required]、字符串长度使用 [MaxLength] 等;但是因为是特性注解,也就是难以做到很多需要经过动态检查的验证方式,使用 FluentValidation 可以为模型类构建更加丰富的验证规则。

而 FluentValidation 用在 RulesEngine 上,也是相同的用途,RulesEngine 最常常用做规则验证,检查模型类或业务逻辑的验证结果,利用 FluentValidation 中丰富的验证规则,可以制作各种方便的表达式树,构建动态代码。

怎么使用

我们通过 RulesEngine 检查模型类的字段是否符合规则,来了解 RulesEngine 的使用方法。

创建一个这样的模型类:

public class Buyer
{
public int Id { get; set; }
public int Age { get; set; }
// 是否为已认证用户
public bool Authenticated { get; set; }
}

场景是这样的,用户下单购买商品,后台需要判断此用户是否已经成年是否通过了认证

正常来看代码应该这样写:

if(Authenticated == true && Age > 18)

但是如果年龄调为 16 岁呢?如果最近公司搞活动,不需要上传身份证就能购买商品呢?

当然定义变量存储到数据库也行,但是如果后面又新增了几个条件,那么我们就需要修改代码了,大佬说,这样不好,我们要 RulesEngine 。

好的,那我们来研究一下这个东西。

前面提到的 if(Authenticated == true && Age > 18),这么一个完整的验证过程,在 RulesEngine 称为 Workflow,每个 Workflow 下有多个 Rule。

if(Authenticated == true && Age > 18) => Workflow
Authenticated == true => Rule
Age > 18 => Rule

在 RulesEngine 中,有两种方法定义这些 Workflow 和 Rule,一种是使用代码,一种是 JSON,官方是推荐使用 JSON 的,因为 JSON 可以动态生成,可以实现真正的动态。

下面我们来看看如何使用 JSON 和代码,分别定义 if(Authenticated == true && Age > 18) 这个验证过程。

JSON 定义:

[
{
"WorkflowName": "Test",
"Rules": [
{
"RuleName": "CheckAuthenticated",
"Expression": "Authenticated == true"
},
{
"RuleName": "CheckAge",
"Expression": "Age >= 18"
}
]
}
]
        var rulesStr = "[{... ...}]" // JSON
var workflows = JsonConvert.DeserializeObject<List<Workflow>>(rulesStr);

C# 代码:

        var workflows = new List<Workflow>();
List<Rule> rules = new List<Rule>(); Workflow exampleWorkflow = new Workflow();
exampleWorkflow.WorkflowName = "Test";
exampleWorkflow.Rules = rules;
workflows.Add(exampleWorkflow); Rule authRule = new Rule();
authRule.RuleName = "CheckAuthenticated";
authRule.Expression = "Authenticated == true";
rules.Add(authRule); Rule ageRule = new Rule();
ageRule.RuleName = "CheckAuthenticated";
ageRule.Expression = "Authenticated == true";
rules.Add(ageRule);

两种方式都是一样的,每个 Workflow 下有多个 Rule,可以定义多个 Workflow。

当前我们有两个地方要了解:

        "RuleName": "CheckAuthenticated",
"Expression": "Authenticated == true"

RuleName:规则名称;

Expression: 真实的代码,必须是符合 C# 语法的代码;

定义好 Workflow 和 Rule 后,我们需要生成规则引擎,直接 new RulesEngine.RulesEngine() 即可:

        var bre = new RulesEngine.RulesEngine(workflows.ToArray());

生成引擎是需要一些时间的。

生成引擎后,我们通过名称指定调用一个 Workflow,并获取每个 Rule 的验证结果:

        List<RuleResultTree> resultList = await bre.ExecuteAllRulesAsync("Test", new Buyer
{
Id = 666,
Age = 17,
Authenticated = false
});

完整代码示例如下:

    static async Task Main()
{
// 定义
var rulesStr = ... ...// JSON
// 生成 Workflow[ Rule[] ]
var workflows = JsonConvert.DeserializeObject<List<Workflow>>(rulesStr)!;
var bre = new RulesEngine.RulesEngine(workflows.ToArray()); // 调用指定的 Workflow,并传递参数,获取每个 Rule 的处理结果
List<RuleResultTree> resultList = await bre.ExecuteAllRulesAsync("Test", new Buyer
{
Id = 666,
Age = 17,
Authenticated = false
}); // 打印输出
foreach (var item in resultList)
{
Console.WriteLine("规则名称:{0}, 验证结果:{1}", item.Rule.RuleName, item.IsSuccess);
}
}

多参数

如果商品需要 VIP 才能购买呢?

这里我们再定义一个模型类,表示一个用户是否为 VIP。

public class VIP
{
public int Id { get; set; }
public bool IsVIP { get; set; }
}

那么这个时候就需要处理两个模型类了,为了能够在 Rule 中使用所有的模型类,我们需要为每个模型类定义 RuleParameter

        var rp1 = new RuleParameter("buyer", new Buyer
{
Id = 666,
Age = 20,
Authenticated = true
}); var rp2 = new RuleParameter("vip", new VIP
{
Id = 666,
IsVIP = false
});

相当于表达式树:

            ParameterExpression rp1 = Expression.Parameter(typeof(Buyer), "buyer");
ParameterExpression rp2 = Expression.Parameter(typeof(VIP), "vip");

可以参考笔者的表达式树系列文章:https://ex.whuanle.cn/

然后重新设计 JSON,增加一个 Rule:

[{
"WorkflowName": "Test",
"Rules": [{
"RuleName": "CheckAuthenticated",
"Expression": "buyer.Authenticated == true"
},
{
"RuleName": "CheckAge",
"Expression": "buyer.Age >= 18"
},
{
"RuleName": "CheckVIP",
"Expression": "vip.IsVIP == true"
}
]
}]

然后执行此 Workflow:

List<RuleResultTree> resultList = await bre.ExecuteAllRulesAsync("Test", rp1, rp2);

完整代码:

    static async Task Main()
{
// 定义
var rulesStr = ... ... // JSON
var workflows = JsonConvert.DeserializeObject<List<Workflow>>(rulesStr)!;
var bre = new RulesEngine.RulesEngine(workflows.ToArray()); var rp1 = new RuleParameter("buyer", new Buyer
{
Id = 666,
Age = 20,
Authenticated = true
}); var rp2 = new RuleParameter("vip", new VIP
{
Id = 666,
IsVIP = false
}); List<RuleResultTree> resultList = await bre.ExecuteAllRulesAsync("Test", rp1, rp2); foreach (var item in resultList)
{
Console.WriteLine("规则名称:{0}, 验证结果:{1}", item.Rule.RuleName, item.IsSuccess);
}
}

全局参数、本地参数

全局参数

在 Workflow 中可以定义全局参数,参数对 Workflow 内的所有 Rule 起效,所有 Rule 都可以使用它。

定义示例:

	"WorkflowName": "Test",
"GlobalParams": [{
"Name": "age",
"Expression": "buyer.Age"
}],

参数的值,可以定义为常量,也可以来源于传入的参数。

修改上一个小节的示例,在 Rule CheckAge 中,使用这个全局参数。

[{
"WorkflowName": "Test",
"GlobalParams": [{
"Name": "age",
"Expression": "buyer.Age"
}],
"Rules": [{
"RuleName": "CheckAuthenticated",
"Expression": "buyer.Authenticated == true"
},
{
"RuleName": "CheckAge",
"Expression": "age >= 18"
},
{
"RuleName": "CheckVIP",
"Expression": "vip.IsVIP == true"
}
]
}]

本地参数

本地参数在 Rule 内定义,只对当前 Rule 起效。

[{
"WorkflowName": "Test",
"Rules": [{
"RuleName": "CheckAuthenticated",
"LocalParams": [{
"Name": "age",
"Expression": "buyer.Age"
}],
"Expression": "buyer.Authenticated == true"
},
{
"RuleName": "CheckAge",
"Expression": "age >= 18"
},
{
"RuleName": "CheckVIP",
"Expression": "vip.IsVIP == true"
}
]
}]

在定义参数时,参数的值可以通过执行函数来获取:

      "LocalParams":[
{
"Name":"mylocal1",
"Expression":"myInput.hello.ToLower()"
}
],

LocalParams 可以使用 GlobalParams 的参数再次生成新的变量。

  "GlobalParams":[
{
"Name":"myglobal1"
"Expression":"myInput.hello"
}
],
"Rules":[
{
"RuleName": "checkGlobalAndLocalEqualsHello",
"LocalParams":[
{
"Name": "mylocal1",
"Expression": "myglobal1.ToLower()"
}
]
},

定义验证成功、失败行为

可以为每个 Rule 定义验证成功和失败后执行一些代码。

格式示例:

        "Actions": {
"OnSuccess": {
"Name": "OutputExpression",
"Context": {
"Expression": "input1.TotalBilled * 0.8"
}
},
"OnFailure": {
"Name": "EvaluateRule",
"Context": {
"WorkflowName": "inputWorkflow",
"ruleName": "GiveDiscount10Percent"
}
}
}

OutputExpression 里面定义了执行代码:

              "Name": "OutputExpression",
"Context": {
"Expression": "input1.TotalBilled * 0.8"
}

EvaluateRule 定义了执行另一个 Workflow 的 Rule,

               "Name": "EvaluateRule",
"Context": {
"WorkflowName": "inputWorkflow",
"ruleName": "GiveDiscount10Percent"
}

OnSuccessOnFailure 里面,内部结构如下所示:

              "Name": "OutputExpression",  //Name of action you want to call
"Context": { //This is passed to the action as action context
"Expression": "input1.TotalBilled * 0.8"
} "Name": "EvaluateRule",
"Context": {
"WorkflowName": "inputWorkflow",
"ruleName": "GiveDiscount10Percent"
}

Name:{xxx} 中的 {xxx} 是一个具体的执行器名称,不是随便定义的,OutputExpressionEvaluateRule 都是自带的执行器,所谓的执行器就是一个 Func<ActionBase>,在后面的 自定义执行器 中,可以了解更多。

Context 里面的内容,是一个字典,这些 Key/Value 会被当做参数传递给执行器,每个执行器要求设置的 Context 是不一样的。

另外每个 Rule 都可以定义以下三个字段:

      "SuccessEvent": "10",
"ErrorMessage": "One or more adjust rules failed.",
"ErrorType": "Error",

ErrorType 有两个选项,WarnError,如果这个 Rule 的表达式错误,那么是否弹出异常。如果设置为 Warn, Rule 有问题,验证结果则会是 false,而不会报异常;如果是 Error,那么这个 Rule 会中止 Workflow 的执行,程序会报错。

SuccessEventErrorMessage 对应,只是成功、失败的提示消息。

计算折扣

前面提到的都是验证规则,接下来我们将会使用 RulesEngine 实现规则计算。

这里规定,基础折扣为 1.0,如果用户小于 18 岁,打 9 折,如果用户是 VIP,打 9 折,两个规则独立。

如果是小于 18岁,则 1.0 * 0.9
如果是 VIP, 则 1.0 * 0.9

定义一个模型类,用于传递折扣基值。

// 折扣
public class Discount
{
public double Value
{
get; set;
}
}

定义三个参数:

        var rp1 = new RuleParameter("buyer", new Buyer
{
Id = 666,
Age = 16,
}); var rp2 = new RuleParameter("vip", new VIP
{
Id = 666,
IsVIP = true
}); var rp3 = new RuleParameter("discount", new Discount
{
Value = 1.0
});

定义规则计算,每个规则计算的是自己的折扣:

[{
"WorkflowName": "Test",
"GlobalParams": [{
"Name": "value",
"Expression": "discount.Value"
}],
"Rules": [{
"RuleName": "CheckAge",
"Expression": "buyer.age < 18",
"Actions": {
"OnSuccess": {
"Name": "OutputExpression",
"Context": {
"Expression": "value * 0.9"
}
}
}
},
{
"RuleName": "CheckVIP",
"Expression": "vip.IsVIP == true",
"Actions": {
"OnSuccess": {
"Name": "OutputExpression",
"Context": {
"Expression": "value * 0.9"
}
}
}
}
]
}]

完整代码:

    static async Task Main()
{
// 定义
var rulesStr = ... ... // JSON
var workflows = JsonConvert.DeserializeObject<List<Workflow>>(rulesStr)!;
var bre = new RulesEngine.RulesEngine(workflows.ToArray()); var rp1 = new RuleParameter("buyer", new Buyer
{
Id = 666,
Age = 16,
}); var rp2 = new RuleParameter("vip", new VIP
{
Id = 666,
IsVIP = true
}); var rp3 = new RuleParameter("discount", new Discount
{
Value = 1.0
}); List<RuleResultTree> resultList = await bre.ExecuteAllRulesAsync("Test", rp1, rp2, rp3);
var discount = 1.0;
foreach (var item in resultList)
{
if (item.ActionResult != null && item.ActionResult.Output != null)
{
Console.WriteLine($"{item.Rule.RuleName} 折扣优惠:{item.ActionResult.Output}");
discount = discount * (double)item.ActionResult.Output;
}
}
Console.WriteLine($"最终折扣:{discount}");
}

笔者这里的示例是,每个规则只计算自己的折扣,也就是每个 Rule 都是独立的,下一个 Rule 不会在上一个 Rule 结果上计算。

< 18 : 0.9
VIP : 0.9

如果是折扣可以叠加,那么就是 0.9*0.9 ,最终可以拿到 0.81 的折扣。

如果折扣不能叠加,只能选择最佳的优惠,那么就是 0.9

使用自定义函数

自定义函数有两种静态函数和实例函数两种,我们可以在 Expression 中调用预先写好的函数。

下面讲解如何在 Rule 中调用自定义的函数。

静态函数

自定义静态函数:

    public static bool CheckAge(int age)
{
return age >= 18;
}

注册类型:

        ReSettings reSettings = new ReSettings
{
CustomTypes = new[] { typeof(Program) }
}; var bre = new RulesEngine.RulesEngine(Workflows: workflows.ToArray(), reSettings: reSettings);

使用静态函数:

[{
"WorkflowName": "Test",
"Rules": [{
"RuleName": "CheckAge",
"Expression": "Program.CheckAge(buyer.Age) == true"
}]
}]

完整代码:

    static async Task Main()
{
// 定义
var rulesStr = "[{\"WorkflowName\":\"Test\",\"Rules\":[{\"RuleName\":\"CheckAge\",\"Expression\":\"Program.CheckAge(buyer.Age) == true\"}]}]";
var workflows = JsonConvert.DeserializeObject<List<Workflow>>(rulesStr)!; ReSettings reSettings = new ReSettings
{
CustomTypes = new[] { typeof(Program) }
}; var bre = new RulesEngine.RulesEngine(Workflows: workflows.ToArray(), reSettings: reSettings);
List<RuleResultTree> resultList = await bre.ExecuteAllRulesAsync("Test", new Buyer
{
Age = 16
}); foreach (var item in resultList)
{
Console.WriteLine("规则名称:{0}, 验证结果:{1}", item.Rule.RuleName, item.IsSuccess);
}
} public static bool CheckAge(int age)
{
return age >= 18;
}

实例函数

定义实例函数:

    public bool CheckAge(int age)
{
return age >= 18;
}

通过 RuleParameter 参数的方式,传递实例:

        var rp1 = new RuleParameter("p", new Program());

通过参数的名称调用函数:

[{
"WorkflowName": "Test",
"Rules": [{
"RuleName": "CheckAge",
"Expression": "p.CheckAge(buyer.Age) == true"
}]
}]

完整代码:

    static async Task Main()
{
// 定义
var rulesStr = "[{\"WorkflowName\":\"Test\",\"Rules\":[{\"RuleName\":\"CheckAge\",\"Expression\":\"p.CheckAge(buyer.Age) == true\"}]}]";
var workflows = JsonConvert.DeserializeObject<List<Workflow>>(rulesStr)!; var rp1 = new RuleParameter("p", new Program()); var bre = new RulesEngine.RulesEngine(Workflows: workflows.ToArray());
List<RuleResultTree> resultList = await bre.ExecuteAllRulesAsync("Test", new Buyer
{
Age = 16
}, rp1); foreach (var item in resultList)
{
Console.WriteLine("规则名称:{0}, 验证结果:{1}", item.Rule.RuleName, item.IsSuccess);
}
} public bool CheckAge(int age)
{
return age >= 18;
}

自定义执行器

自定义执行器就是 OnSuccessOnFailure 这部分的自定义执行代码,相比静态函数、实例函数,使用自定义执行器,可以获取 Rule 的一些数据。

		"Actions": {
"OnSuccess": {
"Name": "MyCustomAction",
"Context": {
"customContextInput": "0.9"
}
}
}

自定义一个执行器,执行器需要继承 ActionBase

public class MyCustomAction : ActionBase
{
public override async ValueTask<object> Run(ActionContext context, RuleParameter[] ruleParameters)
{
var customInput = context.GetContext<string>("customContextInput");
return await ValueTask.FromResult(new object());
}
}

定义 ReSettings,并在构建规则引擎时,传递进去:

        var b = new Buyer
{
Age = 16
};
var reSettings = new ReSettings
{
CustomActions = new Dictionary<string, Func<ActionBase>>
{
{"MyCustomAction", () => new MyCustomAction() }
}
}; var bre = new RulesEngine.RulesEngine(workflows.ToArray(), reSettings); List<RuleResultTree> resultList = await bre.ExecuteAllRulesAsync("Test", b);

定义 JSON 规则:

[{
"WorkflowName": "Test",
"Rules": [{
"RuleName": "CheckAge",
"Expression": "Age <= 18 ",
"Actions": {
"OnSuccess": {
"Name": "MyCustomAction",
"Context": {
"customContextInput": "0.9"
}
}
}
}]
}]

C# RulesEngine 规则引擎:从入门到看懵的更多相关文章

  1. 【Drools-开源业务规则引擎】入门实例(含源码)

    该实例转自:http://blog.csdn.net/quzishen/article/details/6163012 便于理解的应用实例1: 现在我们模拟一个应用场景:网站伴随业务产生而进行的积分发 ...

  2. Node.js躬行记(15)——活动规则引擎

    在日常的业务开发中,会包含许多的业务规则,一般就是用if-else硬编码的方式实现,这样就会增加逻辑的维护成本,若无注释,可能都无法理解规则意图. 因为一旦规则有所改变,那么就需要修改代码再发布代码, ...

  3. .NET RulesEngine(规则引擎)

    一次偶然的机会,让我拿出RulesEngine去完成一个业务,对于业务来说主要是完成一个可伸缩性(不确定的类型,以及不确定的条件,条件的变动可能是持续增加修改的)的业务判断.比如说完成一个成就系统,管 ...

  4. Drools 规则引擎应用 看这一篇就够了

    1 .场景 1.1需求 商城系统消费赠送积分 100元以下, 不加分 100元-500元 加100分 500元-1000元 加500分 1000元 以上 加1000分 ...... 1.2传统做法 1 ...

  5. Drools规则引擎入门指南(一)

    最近项目需要增加风控系统,在经过一番调研以后决定使用Drools规则引擎.因为项目是基于SpringCloud的架构,所以此次学习使用了SpringBoot2.0版本结合Drools7.14.0.Fi ...

  6. 规则引擎以及blaze 规则库的集成初探之三——Blaze规则引擎和SRL

    原文地址:http://jefferson.iteye.com/blog/68604 在上面介绍利用JSR94的api使用的章节中,我们使用的具体引擎的实现是一个商业产品,如果想了解Drools的使用 ...

  7. jboss规则引擎KIE Drools 6.3.0 Final 教程(1)

    前言 目前世面上中文的KIE DROOLS Workbench(JBOSS BRMS)的教程几乎没有,有的也只有灵灵碎碎的使用机器来翻译的(翻的不知所云)或者是基于老版本的JBOSS Guvnor即5 ...

  8. [z]规则引擎

    https://www.ibm.com/developerworks/cn/java/j-drools/ 使用声明性编程方法编写程序的业务逻辑 使用规则引擎可以通过降低实现复杂业务逻辑的组件的复杂性, ...

  9. 规则引擎之easyRules

    规则引擎听起来是蛮高深的一个词语,但透过现象看本质,Martin Fowler 有如下言: You can build a simple rules engine yourself. All you ...

随机推荐

  1. 论文翻译:2020_Lightweight Online Noise Reduction on Embedded Devices using Hierarchical Recurrent Neural Networks

    论文地址:基于分层递归神经网络的嵌入式设备轻量化在线降噪 引用格式:Schröter H, Rosenkranz T, Zobel P, et al. Lightweight Online Noise ...

  2. 简单概述因特网(Internet)

    学习目的 了解 Internet 的概念,区别因特网与互联网. 了解 Internet 的基本结构. 了解 Internet 的发展历史. Internet 概念 因特网(Internet)是全球性的 ...

  3. Spark基础入门(01)—RDD

    1,基本概念 RDD(Resilient Distributed Dataset) :弹性分布式数据集 它是Spark中最基本的数据抽象,是编写Spark程序的基础.简单的来讲,一个Spark程序可以 ...

  4. idea中无法在@Test 之下使用Scanner

    //如何解决idea中无法在 @Test 之下使用Scanner@Testpublic void testInsert(){ Scanner scanner = new Scanner(System. ...

  5. Android Notification使用

    一 Notification的类别 1.状态栏和抽屉式通知 //获取NotificationManager对象 val notificationManager = getSystemService(N ...

  6. Linux 配置ODBC连接Oracle

    在使用kdb_database_link 扩展插件连接Oracle数据库时,必须先配置ODBC,确保通过ODBC能连接Oracle数据库.以下是配置ODBC的过程. 一.安装ODBC 1.安装 [ro ...

  7. java8 新特性 -Optional的常见用法

    1. Optional 一. 简介 Opitonal是java8引入的一个新类,目的是为了解决空指针异常问题.本质上,这是一个包含有可选值的包装类,这意味着 Optional 类既可以含有对象也可以为 ...

  8. docker学习笔记一-docker安装与卸载

    环境查看 # 1 查询当前centOS的版本,官方要求版本为7以上 uname -r 查询系统内核 cat /etc/os-release 系统版本 安装 # 1.卸载旧版本 yum remove d ...

  9. C#/VB.NET 如何在Excel中使用条件格式设置交替行颜色

    说起高亮数据行,不让人想起了交替颜色行,有的人把交替颜色行也都设置成高亮,不仅不美观,而且对阅读还是个干扰.隔行交替的颜色是为了阅读不串行,这些行只是环境,数据才是主体.那么如何通过C#/VB.NET ...

  10. SkyWalking 6.x 的架构图

    可以看到主要由四部分组成: Agent(也叫Probe):代理或者探针,集成在被监测的应用中(SDK形式或者动态注入),采集应用的数据发送给后端(OAP). UI:自带的Web页面. OAP:后端,接 ...