Event Ruler(为简洁起见,在文档的其余部分中称为 Ruler)是一个 Java 库,允许将规则与事件进行匹配。事件是字段列表,可以以名称/值对或 JSON 对象的形式给出。规则将事件字段名称与可能值的列表相关联。使用标尺有两个原因:
内容:
通过例子来解释是最简单的。
事件是一个 JSON 对象。这是一个例子:
{
"version" : "0" ,
"id" : "ddddd4-aaaa-7777-4444-345dd43cc333" ,
"detail-type" : "EC2 Instance State-change Notification" ,
"source" : "aws.ec2" ,
"account" : "012345679012" ,
"time" : "2017-10-02T16:24:49Z" ,
"region" : "us-east-1" ,
"resources" : [
"arn:aws:ec2:us-east-1:123456789012:instance/i-000000aaaaaa00000"
] ,
"detail" : {
"c-count" : 5 ,
"d-count" : 3 ,
"x-limit" : 301.8 ,
"source-ip" : "10.0.0.33" ,
"instance-id" : "i-000000aaaaaa00000" ,
"state" : "running"
}
}
您还可以将其视为一组名称/值对。为简洁起见,我们仅提供一个示例。 Ruler 具有用于以 JSON 形式和名称/值对的形式提供事件的 API:
+--------------+------------------------------------------+
| name | value |
|--------------|------------------------------------------|
| source | "aws.ec2" |
| detail-type | "EC2 Instance State-change Notification" |
| detail.state | "running" |
+--------------+------------------------------------------+
JSON 形式的事件可以以原始 JSON 字符串或解析的 Jackson JsonNode 的形式提供。
本节中的规则均与上面的示例事件匹配:
{
"detail-type" : [ "EC2 Instance State-change Notification" ] ,
"resources" : [ "arn:aws:ec2:us-east-1:123456789012:instance/i-000000aaaaaa00000" ] ,
"detail" : {
"state" : [ "initializing" , "running" ]
}
}
这会将任何事件与为resource
、 detail-type
和detail.state
值提供的值进行匹配,忽略事件中的任何其他字段。如果detail.state
的值是"initializing"
它也会匹配。
规则中的值始终以数组形式提供,并且如果事件中的值是数组中提供的值之一,则匹配。对resources
的引用表明,如果事件中的值也是数组,则如果事件数组和规则数组之间的交集非空,则规则匹配。
{
"time" : [ { "prefix" : "2017-10-02" } ]
}
前缀匹配仅适用于字符串值字段。
{
"source" : [ { "prefix" : { "equals-ignore-case" : "EC2" } } ]
}
前缀 equals-ignore-case 匹配仅适用于字符串值字段。
{
"source" : [ { "suffix" : "ec2" } ]
}
后缀匹配仅适用于字符串值字段。
{
"source" : [ { "suffix" : { "equals-ignore-case" : "EC2" } } ]
}
后缀 equals-ignore-case 匹配仅适用于字符串值字段。
{
"source" : [ { "equals-ignore-case" : "EC2" } ]
}
等于忽略大小写匹配仅适用于字符串值字段。
{
"source" : [ { "wildcard" : "Simple*Service" } ]
}
通配符匹配仅适用于字符串值字段。单个值可以包含零到多个通配符,但不允许连续的通配符。为了专门匹配星号字符,可以使用反斜杠转义通配符。两个连续的反斜杠(即用反斜杠转义的反斜杠)代表实际的反斜杠字符。不允许使用反斜杠转义星号或反斜杠以外的任何字符。
Anything-but 匹配顾名思义:匹配除规则中提供的内容之外的任何内容。
Anything-but 适用于单个字符串和数值或列表,其中必须完全包含字符串或完全数字。它还可以应用于字符串或字符串列表的前缀、后缀或等于忽略大小写匹配。
单个任何内容(字符串,然后是数字):
{
"detail" : {
"state" : [ { "anything-but" : "initializing" } ]
}
}
{
"detail" : {
"x-limit" : [ { "anything-but" : 123 } ]
}
}
除了列表之外的任何内容(字符串):
{
"detail" : {
"state" : [ { "anything-but" : [ "stopped" , "overloaded" ] } ]
}
}
除了列表(数字)之外的任何内容:
{
"detail" : {
"x-limit" : [ { "anything-but" : [ 100 , 200 , 300 ] } ]
}
}
除了前缀之外的任何内容:
{
"detail" : {
"state" : [ { "anything-but" : { "prefix" : "init" } } ]
}
}
任何前缀列表(字符串):
{
"detail" : {
"state" : [ { "anything-but" : { "prefix" : [ "init" , "error" ] } } ]
}
}
除了后缀之外的任何内容:
{
"detail" : {
"instance-id" : [ { "anything-but" : { "suffix" : "1234" } } ]
}
}
任何后缀列表(字符串):
{
"detail" : {
"instance-id" : [ { "anything-but" : { "suffix" : [ "1234" , "6789" ] } } ]
}
}
任何情况但忽略大小写:
{
"detail" : {
"state" : [ { "anything-but" : { "equals-ignore-case" : "Stopped" } } ]
}
}
任何但忽略大小写的列表(字符串):
{
"detail" : {
"state" : [ { "anything-but" : { "equals-ignore-case" : [ "Stopped" , "OverLoaded" ] } } ]
}
}
除通配符外的任何内容:
{
"detail" : {
"state" : [ { "anything-but" : { "wildcard" : "*/bin/*.jar" } } ]
}
}
除通配符之外的任何内容列表(字符串):
{
"detail" : {
"state" : [ { "anything-but" : { "wildcard" : [ "*/bin/*.jar" , "*/bin/*.class" ] } } ]
}
}
{
"detail" : {
"c-count" : [ { "numeric" : [ ">" , 0 , "<=" , 5 ] } ] ,
"d-count" : [ { "numeric" : [ "<" , 10 ] } ] ,
"x-limit" : [ { "numeric" : [ "=" , 3.018e2 ] } ]
}
}
上面,对c-count
、 d-count
和x-limit
的引用说明了数字匹配,并且仅适用于 JSON 数字的值。数字匹配支持与 Java 的double
原语相同的精度和范围,后者实现 IEEE 754 binary64
标准。
{
"detail" : {
"source-ip" : [ { "cidr" : "10.0.0.0/24" } ]
}
}
这也适用于 IPv6 地址。
存在匹配适用于 JSON 事件中是否存在字段。
下面的规则将匹配任何具有detail.c-count 字段的事件。
{
"detail" : {
"c-count" : [ { "exists" : true } ]
}
}
下面的规则将匹配任何没有detail.c-count 字段的事件。
{
"detail" : {
"c-count" : [ { "exists" : false } ]
}
}
注意Exists
匹配仅适用于叶节点。它不适用于中间节点。
作为一个例子,上面的例子中的exists : false
将匹配下面的事件:
{
"detail-type" : [ "EC2 Instance State-change Notification" ] ,
"resources" : [ "arn:aws:ec2:us-east-1:123456789012:instance/i-000000aaaaaa00000" ] ,
"detail" : {
"state" : [ "initializing" , "running" ]
}
}
但也会匹配下面的事件,因为c-count
不是叶节点:
{
"detail-type" : [ "EC2 Instance State-change Notification" ] ,
"resources" : [ "arn:aws:ec2:us-east-1:123456789012:instance/i-000000aaaaaa00000" ] ,
"detail" : {
"state" : [ "initializing" , "running" ]
"c-count" : {
"c1" : 100
}
}
}
{
"time" : [ { "prefix" : "2017-10-02" } ] ,
"detail" : {
"state" : [ { "anything-but" : "initializing" } ] ,
"c-count" : [ { "numeric" : [ ">" , 0 , "<=" , 5 ] } ] ,
"d-count" : [ { "numeric" : [ "<" , 10 ] } ] ,
"x-limit" : [ { "anything-but" : [ 100 , 200 , 300 ] } ] ,
"source-ip" : [ { "cidr" : "10.0.0.0/8" } ]
}
}
如上面的示例所示,如果规则中指定的所有字段都匹配,则 Ruler 认为该规则匹配;如果提供的任何字段值匹配,则认为该字段匹配,也就是说 Ruler 应用了“与”逻辑默认情况下,所有字段都不需要“And”原语。
有两种方法可以达到“或”效果:
“$or”原语允许客户直接描述规则中字段之间的“或”关系。
仅当规则满足以下所有条件时,标尺才识别“或”关系:
/src/main/software/amazon/event/ruler/Constants.java#L38
中的 RESERVED_FIELD_NAMES_IN_OR_RELATIONSHIP 例如,以下规则将不会被解析为“或者”关系,因为“数字”和“前缀”是标尺保留关键字。 {
"$or": [ {"numeric" : 123}, {"prefix": "abc"} ]
}
否则,Ruler 仅将“$or”视为普通字段名,与规则中的其他字符串相同。
正常的“或”:
// Effect of "source" && ("metricName" || "namespace")
{
"source" : [ "aws.cloudwatch" ] ,
"$or" : [
{ "metricName" : [ "CPUUtilization" , "ReadLatency" ] } ,
{ "namespace" : [ "AWS/EC2" , "AWS/ES" ] }
]
}
并行“或”:
// Effect of ("metricName" || "namespace") && ("detail.source" || "detail.detail-type")
{
"$or" : [
{ "metricName" : [ "CPUUtilization" , "ReadLatency" ] } ,
{ "namespace" : [ "AWS/EC2" , "AWS/ES" ] }
] ,
"detail" : {
"$or" : [
{ "source" : [ "aws.cloudwatch" ] } ,
{ "detail-type" : [ "CloudWatch Alarm State Change" ] }
]
}
}
“或”里面有一个“与”
// Effect of ("source" && ("metricName" || ("metricType && "namespace") || "scope"))
{
"source" : [ "aws.cloudwatch" ] ,
"$or" : [
{ "metricName" : [ "CPUUtilization" , "ReadLatency" ] } ,
{
"metricType" : [ "MetricType" ] ,
"namespace" : [ "AWS/EC2" , "AWS/ES" ]
} ,
{ "scope" : [ "Service" ] }
]
}
嵌套“或”和“与”
// Effect of ("source" && ("metricName" || ("metricType && "namespace" && ("metricId" || "spaceId")) || "scope"))
{
"source" : [ "aws.cloudwatch" ] ,
"$or" : [
{ "metricName" : [ "CPUUtilization" , "ReadLatency" ] } ,
{
"metricType" : [ "MetricType" ] ,
"namespace" : [ "AWS/EC2" , "AWS/ES" ] ,
"$or" : [
{ "metricId" : [ 1234 ] } ,
{ "spaceId" : [ 1000 ] }
]
} ,
{ "scope" : [ "Service" ] }
]
}
“$or”可能已经在某些应用程序中用作普通键(尽管可能很少见)。对于这些情况,Ruler 尽力保持向后兼容性。只有当上述 3 个条件出现时,统治者才会改变行为,因为它假设您的规则确实需要一个 OR,并且直到今天才配置错误。例如,下面的规则将继续作为正常规则工作,并将“$or”视为规则和事件中的正常字段名称:
{
"source" : [ "aws.cloudwatch" ] ,
"$or" : {
"metricType" : [ "MetricType" ] ,
"namespace" : [ "AWS/EC2" , "AWS/ES" ]
}
}
有关“$or”被 Ruler 解析为普通字段名称的更多示例,请参阅/src/test/data/normalRulesWithOrWording.json
。
作为“Or”关系原语的关键字“$or”不应被设计为事件和规则中的普通字段。 Ruler 支持旧规则,其中“$or”被解析为普通字段名称,以保持向后兼容性,并为团队提供时间将旧的“$or”用法从事件和规则中迁移为普通字段名称。 Ruler 故意不支持混合使用“$or”作为“Or”原语和“$or”作为普通字段名称,以避免“$or”上出现超级尴尬的歧义。
有两种使用标尺的方法。您可以将多个规则编译为“机器”,然后使用其rulesForEvent()
方法或rulesForJSONEvent()
方法来检查哪些规则与任何事件匹配。下面讨论这两种方法之间的区别。本讨论将一般使用rulesForEvent()
除非差异很重要。
或者,您可以使用单个静态布尔方法来确定单个事件是否与特定规则匹配。
有一个静态布尔方法Ruler.matchesRule(event, rule)
- 两个参数都以 JSON 字符串形式提供。
注意:还有另一种已弃用的方法,称为Ruler.matches(event, rule)
,不应使用该方法,因为其结果与rulesForJSONEvent()
和rulesForEvent()
不一致。有关详细信息,请参阅有关Ruler.matches(event, rule)
的文档。
匹配时间不依赖于规则的数量。如果您有多个可能的规则想要从中进行选择,特别是如果您有办法存储已编译的 Machine,那么这是最佳选择。
匹配时间受到通配符和非通配符规则引起的不确定性程度的影响。随着越来越多的通配符规则前缀与理论上的最坏情况事件匹配,性能会下降。为了避免这种情况,与同一事件字段相关的通配符规则应避免导致第一个通配符的公共前缀。如果需要公共前缀,则使用最少数量的通配符,并限制通配符后面出现的重复字符序列。 MachineComplexityEvaluator 可用于评估机器并确定不确定性或“复杂性”的程度(即有多少通配符规则前缀与理论上的最坏情况事件匹配)。以下是一些数据点,显示随着复杂性分数的增加,性能通常会下降。
限制机器复杂性以保护您的应用程序非常重要。至少有两种不同的策略来限制机器的复杂性。哪一种更有意义可能取决于您的应用程序。
策略#1 更理想,因为它测量包含所有规则的机器的实际复杂性。如果可能,应使用此策略。缺点是,假设您有一个控制平面,允许一次创建一个规则,最多可以创建一个非常大的数量。然后,对于每个控制平面操作,您必须加载所有现有规则来执行验证。这可能非常昂贵。它还容易出现竞争条件。策略#2 是一种妥协。策略 #2 使用的阈值将低于策略 #1,因为它是每个规则的阈值。假设您希望添加所有规则后机器的复杂性不超过 300。例如,使用策略 #2,您可以将每个单规则机器的复杂性限制为 10,并允许包含通配符模式的 30 条规则。在复杂性完全相加(不太可能)的绝对最坏情况下,这将导致机器的复杂性为 300。缺点是复杂性不太可能完全相加,因此包含通配符的规则的数量将可能会受到不必要的限制。
对于策略#2,根据规则的存储方式,可能需要向规则添加附加属性以指示哪些规则是不确定的(即包含通配符模式),以便限制包含通配符的规则的数量。
下面的代码片段说明了如何限制给定模式的复杂性,例如策略#2。
public class Validate {
private void validate ( String pattern , MachineComplexityEvaluator machineComplexityEvaluator ) {
// If we cannot compile, then return exception.
List < Map < String , List < Patterns >>> compilationResult = Lists . newArrayList ();
try {
compilationResult . addAll ( JsonRuleCompiler . compile ( pattern ));
} catch ( Exception e ) {
InvalidPatternException internalException =
EXCEPTION_FACTORY . invalidPatternException ( e . getLocalizedMessage ());
throw ExceptionMapper . mapToModeledException ( internalException );
}
// Validate wildcard patterns. Look for wildcard patterns out of all patterns that have been used.
Machine machine = new Machine ();
int i = 0 ;
for ( Map < String , List < Patterns >> rule : compilationResult ) {
if ( containsWildcard ( rule )) {
// Add rule to machine for complexity evaluation.
machine . addPatternRule ( Integer . toString (++ i ), rule );
}
}
// Machine has all rules containing wildcard match types. See if the complexity is under the limit.
int complexity = machine . evaluateComplexity ( machineComplexityEvaluator );
if ( complexity > MAX_MACHINE_COMPLEXITY ) {
InvalidPatternException internalException = EXCEPTION_FACTORY . invalidPatternException ( "Rule is too complex" );
throw ExceptionMapper . mapToModeledException ( internalException );
}
}
private boolean containsWildcard ( Map < String , List < Patterns >> rule ) {
for ( List < Patterns > fieldPatterns : rule . values ()) {
for ( Patterns fieldPattern : fieldPatterns ) {
if ( fieldPattern . type () == WILDCARD || fieldPattern . type () == ANYTHING_BUT_WILDCARD ) {
return true ;
}
}
}
return false ;
}
}
您将与之交互的主类实现基于状态机的规则匹配。有趣的方法是:
addRule()
- 向机器添加新规则deleteRule()
- 从机器中删除规则rulesForEvent()
/ rulesForJSONEvent()
- 查找机器中与事件匹配的规则有两种风格: Machine
和GenericMachine<T>
。 Machine 就是GenericMachine<String>
。 API 将泛型类型称为“名称”,这反映了历史:首先构建了 String 版本,它存储和返回的字符串被视为规则名称。
为了安全起见,用于“命名”规则的类型应该是不可变的。如果在将对象用作规则名称时更改对象的内容,则可能会破坏标尺的操作。
GenericMachine 和 Machine 构造函数可以选择接受 GenericMachineConfiguration 对象,该对象公开以下配置选项。
默认值: false 通常,如果先前已添加给定键子序列和模式,或者已经为给定键子序列添加了模式,则 NameState 会重新用于给定键子序列和模式。因此,默认情况下,NameState 重用是机会主义的。但通过将此标志设置为 true,将强制对关键子序列重用 NameState。这意味着,如果之前已添加过该键子序列,则为该键子序列添加的第一个模式将重新使用 NameState。这意味着每个键子序列都有一个 NameState。在某些情况下,这会以指数方式提高内存利用率,但确实会导致更多的子规则存储在各个 NameState 中,Ruler 有时会对其进行迭代,这可能会导致运行时性能适度下降。为了向后兼容,此值默认为 false,但很可能,除了对延迟最敏感的应用程序之外,所有应用程序都可以从将其设置为 true 中受益。
这是一个简单的例子。考虑:
machine . addRule ( "0" , "{"key1": ["a", "b", "c"]}" ) ;
模式“a”创建一个NameState,然后,即使additionalNameStateReuse=false,第二个模式(“b”)和第三个模式(“c”)也会重复使用相同的NameState。但请考虑以下几点:
machine . addRule ( "0" , "{"key1": ["a"]}" ) ;
machine . addRule ( "1" , "{"key1": ["b"]}" ) ;
machine . addRule ( "2" , "{"key1": ["c"]}" ) ;
现在,如果additionalNameStateReuse=false,我们最终会得到三个NameState,因为每个规则添加中关键子序列遇到的第一个模式将创建一个新的NameState。因此,“a”、“b”和“c”都有自己的 NameState。然而,当additionalNameStateReuse=true时,“a”将创建一个新的NameState,然后“b”和“c”将重用这个相同的NameState。这是通过存储我们已经拥有键子序列“key1”的 NameState 来完成的。
请注意,每个 addRule 使用不同的规则名称或相同的规则名称并不重要。
此方法的所有形式都具有相同的第一个参数,即提供规则名称并由rulesForEvent()
返回的字符串。其余参数提供名称/值对。它们可以像上面的示例一样以 JSON 形式提供(通过 String、Reader、InputStream 或byte[]
),或者以Map<String, List<String>>
形式提供,其中键是字段名称和值是可能匹配的列表;使用上面的示例,将有一个名为detail.state
的键,其值将是包含"initializing"
和"running"
的列表。
注意:此方法(以及deleteRule()
)是同步的,因此在任何时间点都可能只有一个线程更新机器。