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。在複雜性完全相加(不太可能)的絕對最壞情況下,這將導致機器的複雜性為 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()
)是同步的,因此在任何時間點都可能只有一個執行緒更新機器。