Event Ruler (der Kürze halber im Rest des Dokuments Ruler genannt) ist eine Java-Bibliothek, die den Abgleich von Regeln mit Ereignissen ermöglicht. Ein Ereignis ist eine Liste von Feldern, die als Name/Wert-Paare oder als JSON-Objekt angegeben werden können. Eine Regel verknüpft Ereignisfeldnamen mit Listen möglicher Werte. Es gibt zwei Gründe, Ruler zu verwenden:
Inhalt:
Am einfachsten lässt es sich anhand eines Beispiels erklären.
Ein Ereignis ist ein JSON-Objekt. Hier ist ein Beispiel:
{
"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"
}
}
Sie können dies auch als eine Reihe von Name/Wert-Paaren sehen. Der Kürze halber präsentieren wir nur eine Auswahl. Ruler verfügt über APIs zum Bereitstellen von Ereignissen sowohl im JSON-Format als auch als Name/Wert-Paare:
+--------------+------------------------------------------+
| name | value |
|--------------|------------------------------------------|
| source | "aws.ec2" |
| detail-type | "EC2 Instance State-change Notification" |
| detail.state | "running" |
+--------------+------------------------------------------+
Ereignisse im JSON-Formular können in Form eines rohen JSON-Strings oder eines geparsten Jackson JsonNode bereitgestellt werden.
Die Regeln in diesem Abschnitt entsprechen alle dem obigen Beispielereignis:
{
"detail-type" : [ "EC2 Instance State-change Notification" ] ,
"resources" : [ "arn:aws:ec2:us-east-1:123456789012:instance/i-000000aaaaaa00000" ] ,
"detail" : {
"state" : [ "initializing" , "running" ]
}
}
Dadurch wird jedes Ereignis mit den bereitgestellten Werten für die Werte resource
, detail-type
und detail.state
abgeglichen, wobei alle anderen Felder im Ereignis ignoriert werden. Es würde auch übereinstimmen, wenn der Wert von detail.state
"initializing"
gewesen wäre.
Werte in Regeln werden immer als Arrays bereitgestellt und stimmen überein, wenn der Wert im Ereignis einer der im Array bereitgestellten Werte ist. Der Verweis auf resources
zeigt, dass die Regel zutrifft, wenn der Wert im Ereignis ebenfalls ein Array ist, wenn die Schnittmenge zwischen dem Ereignisarray und dem Regelarray nicht leer ist.
{
"time" : [ { "prefix" : "2017-10-02" } ]
}
Präfixübereinstimmungen funktionieren nur bei Feldern mit Zeichenfolgenwerten.
{
"source" : [ { "prefix" : { "equals-ignore-case" : "EC2" } } ]
}
Übereinstimmungen mit dem Präfix „equals-ignore-case“ funktionieren nur bei Feldern mit Zeichenfolgenwerten.
{
"source" : [ { "suffix" : "ec2" } ]
}
Suffixübereinstimmungen funktionieren nur bei Feldern mit Zeichenfolgenwerten.
{
"source" : [ { "suffix" : { "equals-ignore-case" : "EC2" } } ]
}
Suffix-Equals-Ignore-Case-Übereinstimmungen funktionieren nur bei Feldern mit Zeichenfolgenwerten.
{
"source" : [ { "equals-ignore-case" : "EC2" } ]
}
Gleichheits-Ignorieren-Groß-/Kleinschreibung-Übereinstimmungen funktionieren nur bei Feldern mit Zeichenfolgenwerten.
{
"source" : [ { "wildcard" : "Simple*Service" } ]
}
Platzhalterübereinstimmungen funktionieren nur bei Feldern mit Zeichenfolgenwerten. Ein einzelner Wert kann null bis viele Platzhalterzeichen enthalten, aufeinanderfolgende Platzhalterzeichen sind jedoch nicht zulässig. Um das Sternchen gezielt zuzuordnen, kann ein Platzhalterzeichen mit einem Backslash maskiert werden. Zwei aufeinanderfolgende Backslashes (also ein mit einem Backslash maskierter Backslash) repräsentieren das eigentliche Backslash-Zeichen. Ein Backslash, der ein anderes Zeichen als ein Sternchen oder einen Backslash ersetzt, ist nicht zulässig.
„Anything-but-Matching“ macht das, was der Name verspricht: passt alles außer dem, was in der Regel vorgesehen ist.
Anything-but funktioniert mit einzelnen Zeichenfolgen und numerischen Werten oder Listen, die ausschließlich Zeichenfolgen oder ausschließlich numerische Werte enthalten müssen. Es kann auch auf ein Präfix, Suffix oder eine Gleichheits-Groß-/Kleinschreibungsübereinstimmung einer Zeichenfolge oder einer Liste von Zeichenfolgen angewendet werden.
Einzelnes Alles-außer (Zeichenfolge, dann numerisch):
{
"detail" : {
"state" : [ { "anything-but" : "initializing" } ]
}
}
{
"detail" : {
"x-limit" : [ { "anything-but" : 123 } ]
}
}
Alles andere als Liste (Strings):
{
"detail" : {
"state" : [ { "anything-but" : [ "stopped" , "overloaded" ] } ]
}
}
Alles andere als Liste (Zahlen):
{
"detail" : {
"x-limit" : [ { "anything-but" : [ 100 , 200 , 300 ] } ]
}
}
Alles andere als Präfix:
{
"detail" : {
"state" : [ { "anything-but" : { "prefix" : "init" } } ]
}
}
Alles andere als Präfixliste (Strings):
{
"detail" : {
"state" : [ { "anything-but" : { "prefix" : [ "init" , "error" ] } } ]
}
}
Alles andere als Suffix:
{
"detail" : {
"instance-id" : [ { "anything-but" : { "suffix" : "1234" } } ]
}
}
Alles andere als Suffixliste (Strings):
{
"detail" : {
"instance-id" : [ { "anything-but" : { "suffix" : [ "1234" , "6789" ] } } ]
}
}
Alles andere als den Fall ignorieren:
{
"detail" : {
"state" : [ { "anything-but" : { "equals-ignore-case" : "Stopped" } } ]
}
}
Alles-außer-Fall-Ignorieren-Liste (Zeichenfolgen):
{
"detail" : {
"state" : [ { "anything-but" : { "equals-ignore-case" : [ "Stopped" , "OverLoaded" ] } } ]
}
}
Alles andere als ein Platzhalter:
{
"detail" : {
"state" : [ { "anything-but" : { "wildcard" : "*/bin/*.jar" } } ]
}
}
Alles andere als Platzhalterliste (Strings):
{
"detail" : {
"state" : [ { "anything-but" : { "wildcard" : [ "*/bin/*.jar" , "*/bin/*.class" ] } } ]
}
}
{
"detail" : {
"c-count" : [ { "numeric" : [ ">" , 0 , "<=" , 5 ] } ] ,
"d-count" : [ { "numeric" : [ "<" , 10 ] } ] ,
"x-limit" : [ { "numeric" : [ "=" , 3.018e2 ] } ]
}
}
Oben veranschaulichen die Verweise auf c-count
, d-count
und x-limit
den numerischen Abgleich und funktionieren nur mit Werten, die JSON-Zahlen sind. Der numerische Abgleich unterstützt die gleiche Genauigkeit und den gleichen Bereich wie double
von Java, das binary64
-Standard IEEE 754 implementiert.
{
"detail" : {
"source-ip" : [ { "cidr" : "10.0.0.0/24" } ]
}
}
Dies funktioniert auch mit IPv6-Adressen.
Der Exists-Abgleich hängt davon ab, ob ein Feld im JSON-Ereignis vorhanden ist oder nicht.
Die folgende Regel gilt für jedes Ereignis, bei dem ein detail.c-count-Feld vorhanden ist.
{
"detail" : {
"c-count" : [ { "exists" : true } ]
}
}
Die folgende Regel gilt für alle Ereignisse, die kein detail.c-count-Feld haben.
{
"detail" : {
"c-count" : [ { "exists" : false } ]
}
}
Hinweis: Die Übereinstimmung „ Exists
“ funktioniert nur auf den Blattknoten. Es funktioniert nicht auf Zwischenknoten.
Als Beispiel würde das obige Beispiel für exists : false
mit dem folgenden Ereignis übereinstimmen:
{
"detail-type" : [ "EC2 Instance State-change Notification" ] ,
"resources" : [ "arn:aws:ec2:us-east-1:123456789012:instance/i-000000aaaaaa00000" ] ,
"detail" : {
"state" : [ "initializing" , "running" ]
}
}
würde aber auch mit dem folgenden Ereignis übereinstimmen, da c-count
kein Blattknoten ist:
{
"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" } ]
}
}
Wie die obigen Beispiele zeigen, betrachtet Ruler eine Regel als übereinstimmend, wenn alle in der Regel genannten Felder übereinstimmen, und er betrachtet ein Feld als übereinstimmend, wenn einer der bereitgestellten Feldwerte übereinstimmt, d. h. Ruler hat die „Und“-Logik angewendet standardmäßig auf alle Felder ohne „Und“-Grundelement erforderlich .
Es gibt zwei Möglichkeiten, die „Oder“-Effekte zu erreichen:
Das Grundelement „$or“ ermöglicht es dem Kunden, die „Oder“-Beziehung zwischen Feldern in der Regel direkt zu beschreiben.
Das Lineal erkennt die „Oder“-Beziehung nur , wenn die Regel alle folgenden Bedingungen erfüllt:
/src/main/software/amazon/event/ruler/Constants.java#L38
Die folgende Regel wird nicht analysiert als „ Oder“-Beziehung, da „numerisch“ und „Präfix“ vom Ruler reservierte Schlüsselwörter sind. {
"$or": [ {"numeric" : 123}, {"prefix": "abc"} ]
}
Andernfalls behandelt Ruler das „$or“ einfach als normalen Dateinamen genauso wie andere Zeichenfolgen in der Regel.
Normales „Oder“:
// Effect of "source" && ("metricName" || "namespace")
{
"source" : [ "aws.cloudwatch" ] ,
"$or" : [
{ "metricName" : [ "CPUUtilization" , "ReadLatency" ] } ,
{ "namespace" : [ "AWS/EC2" , "AWS/ES" ] }
]
}
Paralleles „Oder“:
// 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" ] }
]
}
}
„Oder“ enthält ein „Und“.
// Effect of ("source" && ("metricName" || ("metricType && "namespace") || "scope"))
{
"source" : [ "aws.cloudwatch" ] ,
"$or" : [
{ "metricName" : [ "CPUUtilization" , "ReadLatency" ] } ,
{
"metricType" : [ "MetricType" ] ,
"namespace" : [ "AWS/EC2" , "AWS/ES" ]
} ,
{ "scope" : [ "Service" ] }
]
}
Verschachtelte „Oder“ und „Und“
// 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“ wird in einigen Anwendungen möglicherweise bereits als normaler Schlüssel verwendet (obwohl dies wahrscheinlich selten vorkommt). In diesen Fällen versucht Ruler sein Bestes, die Abwärtskompatibilität aufrechtzuerhalten. Nur wenn die drei oben genannten Bedingungen erfüllt sind, ändert sich das Verhalten des Herrschers, da davon ausgegangen wird, dass Ihre Regel wirklich ein ODER wollte und bis heute falsch konfiguriert wurde. Die folgende Regel funktioniert beispielsweise weiterhin wie eine normale Regel und behandelt „$or“ als normalen Feldnamen in der Regel und im Ereignis:
{
"source" : [ "aws.cloudwatch" ] ,
"$or" : {
"metricType" : [ "MetricType" ] ,
"namespace" : [ "AWS/EC2" , "AWS/ES" ]
}
}
Weitere Beispiele dafür, wie „$or“ von Ruler als normaler Feldname analysiert wird, finden Sie unter /src/test/data/normalRulesWithOrWording.json
.
Das Schlüsselwort „$or“ als „Or“-Beziehungsprimitiv sollte nicht als normales Feld sowohl in Ereignissen als auch in Regeln entworfen werden. Ruler unterstützt die alten Regeln, bei denen „$or“ als normaler Feldname geparst wird, um die Abwärtskompatibilität aufrechtzuerhalten und dem Team Zeit zu geben, die alte „$or“-Verwendung von ihren Ereignissen und Regeln als normalen Dateinamen zu migrieren. Die gemischte Verwendung von „$or“ als „Or“-Primitiv und „$or“ als normaler Feldname wird von Ruler absichtlich nicht unterstützt, um das Auftreten der äußerst unangenehmen Mehrdeutigkeiten bei „$or“ zu vermeiden.
Es gibt zwei Möglichkeiten, Ruler zu verwenden. Sie können mehrere Regeln in einer „Maschine“ kompilieren und dann entweder die rulesForEvent()
Methode oder rulesForJSONEvent()
Methode verwenden, um zu prüfen, welche der Regeln mit einem Ereignis übereinstimmen. Der Unterschied zwischen diesen beiden Methoden wird im Folgenden erläutert. In dieser Diskussion wird rulesForEvent()
generisch verwendet, außer dort, wo der Unterschied wichtig ist.
Alternativ können Sie eine einzelne statische boolesche Methode verwenden, um zu bestimmen, ob ein einzelnes Ereignis einer bestimmten Regel entspricht.
Es gibt eine einzige statische boolesche Methode Ruler.matchesRule(event, rule)
– beide Argumente werden als JSON-Strings bereitgestellt.
HINWEIS: Es gibt eine weitere veraltete Methode namens Ruler.matches(event, rule)
die nicht verwendet werden sollte, da ihre Ergebnisse nicht mit rulesForJSONEvent()
und rulesForEvent()
übereinstimmen. Weitere Informationen finden Sie in der Dokumentation zu Ruler.matches(event, rule)
.
Die Übereinstimmungszeit hängt nicht von der Anzahl der Regeln ab. Dies ist die beste Wahl, wenn Sie mehrere mögliche Regeln zur Auswahl haben und insbesondere, wenn Sie die kompilierte Maschine speichern können.
Die Übereinstimmungszeit wird durch den Grad des Nichtdeterminismus beeinflusst, der durch Wildcard- und Alles-außer-Wildcard-Regeln verursacht wird. Die Leistung nimmt ab, wenn immer mehr Platzhalterregelpräfixe mit einem theoretischen Worst-Case-Ereignis übereinstimmen. Um dies zu vermeiden, sollten Platzhalterregeln, die sich auf dasselbe Ereignisfeld beziehen, gemeinsame Präfixe vor dem ersten Platzhalterzeichen vermeiden. Wenn ein gemeinsames Präfix erforderlich ist, verwenden Sie die Mindestanzahl an Platzhalterzeichen und begrenzen Sie sich wiederholende Zeichenfolgen, die nach einem Platzhalterzeichen auftreten. MachineComplexityEvaluator kann verwendet werden, um eine Maschine zu bewerten und den Grad des Nichtdeterminismus oder der „Komplexität“ zu bestimmen (dh wie viele Wildcard-Regelpräfixe mit einem theoretischen Worst-Case-Ereignis übereinstimmen). Hier sind einige Datenpunkte, die einen typischen Leistungsabfall bei steigenden Komplexitätswerten zeigen.
Es ist wichtig, die Komplexität der Maschine zu begrenzen, um Ihre Anwendung zu schützen. Zur Begrenzung der Maschinenkomplexität gibt es mindestens zwei unterschiedliche Strategien. Was sinnvoller ist, hängt möglicherweise von Ihrer Anwendung ab.
Strategie Nr. 1 ist idealer, da sie die tatsächliche Komplexität der Maschine misst, die alle Regeln enthält. Wenn möglich, sollte diese Strategie verwendet werden. Der Nachteil besteht darin, dass Sie beispielsweise über eine Steuerungsebene verfügen, die die Erstellung einer Regel nach der anderen, bis zu einer sehr großen Anzahl, ermöglicht. Anschließend müssen Sie für jede dieser Steuerungsebenenoperationen alle vorhandenen Regeln laden, um die Validierung durchzuführen. Das könnte sehr teuer werden. Es ist auch anfällig für Rennbedingungen. Strategie Nr. 2 ist ein Kompromiss. Der von Strategie Nr. 2 verwendete Schwellenwert ist niedriger als der von Strategie Nr. 1, da es sich um einen Schwellenwert pro Regel handelt. Nehmen wir an, Sie möchten, dass die Komplexität einer Maschine mit allen hinzugefügten Regeln nicht mehr als 300 beträgt. Dann könnten Sie mit Strategie Nr. 2 beispielsweise jede Maschine mit einer einzigen Regel auf eine Komplexität von 10 begrenzen und 30 Regeln mit Platzhaltermustern zulassen . Im absolut schlimmsten Fall, in dem die Komplexität perfekt additiv ist (unwahrscheinlich), würde dies zu einer Maschine mit einer Komplexität von 300 führen. Der Nachteil besteht darin, dass es unwahrscheinlich ist, dass die Komplexität perfekt additiv ist, und die Anzahl der Regeln, die Platzhalter enthalten, daher auch wahrscheinlich unnötig eingeschränkt werden.
Für Strategie Nr. 2 muss je nach Art der Speicherung der Regeln möglicherweise ein zusätzliches Attribut zu den Regeln hinzugefügt werden, um anzugeben, welche Regeln nicht deterministisch sind (d. h. Platzhaltermuster enthalten), um die Anzahl der Regeln, die Platzhalter enthalten, zu begrenzen.
Das Folgende ist ein Codeausschnitt, der veranschaulicht, wie die Komplexität für ein bestimmtes Muster begrenzt werden kann, z. B. für Strategie Nr. 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 ; } }
Die Hauptklasse, mit der Sie interagieren, implementiert den Regelabgleich auf Zustandsmaschinenbasis. Die interessanten Methoden sind:
addRule()
– fügt der Maschine eine neue Regel hinzudeleteRule()
– löscht eine Regel vom ComputerrulesForEvent()
/ rulesForJSONEvent()
– findet die Regeln in der Maschine, die einem Ereignis entsprechen Es gibt zwei Varianten: Machine
und GenericMachine
. Maschine ist einfach GenericMachine
. Die API bezeichnet den generischen Typ als „Name“, was den Verlauf widerspiegelt: Die String-Version wurde zuerst erstellt und die von ihr gespeicherten und zurückgegebenen Strings wurden als Regelnamen betrachtet.
Aus Sicherheitsgründen sollte der zum „Benennen“ von Regeln verwendete Typ unveränderlich sein. Wenn Sie den Inhalt eines Objekts ändern, während es als Regelname verwendet wird, kann dies zu Funktionsstörungen von Ruler führen.
Die Konstruktoren GenericMachine und Machine akzeptieren optional ein GenericMachineConfiguration-Objekt, das die folgenden Konfigurationsoptionen verfügbar macht.
Standard: false Normalerweise werden NameStates für eine bestimmte Schlüsseluntersequenz und ein bestimmtes Muster wiederverwendet, wenn diese Schlüsseluntersequenz und dieses Muster zuvor hinzugefügt wurden oder wenn für die angegebene Schlüsseluntersequenz bereits ein Muster hinzugefügt wurde. Daher ist die Wiederverwendung von NameState standardmäßig opportunistisch. Wenn Sie dieses Flag jedoch auf „true“ setzen, wird die Wiederverwendung von NameState für eine Schlüsseluntersequenz erzwungen. Dies bedeutet, dass das erste Muster, das für eine Schlüsseluntersequenz hinzugefügt wird, einen NameState wiederverwendet, wenn diese Schlüsseluntersequenz zuvor hinzugefügt wurde. Das bedeutet, dass jede Schlüsseluntersequenz einen einzelnen NameState hat. Dies verbessert in einigen Fällen die Speicherauslastung exponentiell, führt jedoch dazu, dass mehr Unterregeln in einzelnen NameStates gespeichert werden, die Ruler manchmal durchläuft, was zu einer geringfügigen Leistungseinbuße bei der Laufzeit führen kann. Aus Gründen der Abwärtskompatibilität ist dieser Wert standardmäßig auf „false“ eingestellt, aber wahrscheinlich würden alle bis auf die latenzempfindlichsten Anwendungen von der Einstellung auf „true“ profitieren.
Hier ist ein einfaches Beispiel. Halten:
machine . addRule ( "0" , "{"key1": ["a", "b", "c"]}" ) ;
Das Muster „a“ erstellt einen NameState, und selbst mit „additionalNameStateReuse=false“ verwenden das zweite Muster („b“) und das dritte Muster („c“) denselben NameState erneut. Aber bedenken Sie stattdessen Folgendes:
machine . addRule ( "0" , "{"key1": ["a"]}" ) ;
machine . addRule ( "1" , "{"key1": ["b"]}" ) ;
machine . addRule ( "2" , "{"key1": ["c"]}" ) ;
Mit „additionalNameStateReuse=false“ erhalten wir nun drei NameStates, da das erste Muster, das bei jeder Regelergänzung für eine Schlüsselteilsequenz angetroffen wird, einen neuen NameState erstellt. „a“, „b“ und „c“ erhalten also alle ihre eigenen NameStates. Mit „additionalNameStateReuse=true“ erstellt „a“ jedoch einen neuen NameState und „b“ und „c“ verwenden dann denselben NameState wieder. Dies wird dadurch erreicht, dass wir speichern, dass wir bereits einen NameState für die Schlüsseluntersequenz „key1“ haben.
Beachten Sie, dass es keine Rolle spielt, ob jede addRule einen anderen Regelnamen oder denselben Regelnamen verwendet.
Alle Formen dieser Methode haben das gleiche erste Argument, einen String, der den Namen der Regel angibt und von rulesForEvent()
zurückgegeben wird. Die restlichen Argumente stellen die Name/Wert-Paare bereit. Sie können wie in den obigen Beispielen in JSON bereitgestellt werden (über einen String, einen Reader, einen InputStream oder byte[]
) oder als Map
, wobei die Schlüssel die Feldnamen und die sind Werte sind die Liste möglicher Übereinstimmungen; Im obigen Beispiel gäbe es einen Schlüssel mit dem Namen detail.state
, dessen Wert die Liste mit "initializing"
und "running"
wäre.
Hinweis: Diese Methode (und auch deleteRule()
) ist synchronisiert, sodass zu jedem Zeitpunkt möglicherweise nur ein Thread die Maschine aktualisiert.
Sie können addRule()
mehrmals mit demselben Namen, aber mehreren unterschiedlichen Namens-/Wertmustern aufrufen und so eine „oder“-Beziehung herstellen; rulesForEvent()
gibt diesen Namen zurück, wenn eines der Muster übereinstimmt.
Angenommen, Sie rufen addRule()
mit dem Regelnamen „R1“ auf und fügen das folgende Muster hinzu:
{
"detail" : {
"c-count" : [ { "numeric" : [ ">" , 0 , "<=" , 5 ] } ]
}
}
Dann rufen Sie es erneut mit demselben Namen, aber einem anderen Muster auf:
{
"detail" : {
"x-limit" : [ { "numeric" : [ "=" , 3.018e2 ] } ]
}
}
Danach gibt rulesForEvent()
„R1“ entweder für einen c-count
Wert von 2 oder einen x-limit
-Wert von 301,8 zurück.
Dies ist ein Spiegelbild von addRule()
; In jedem Fall ist das erste Argument der Regelname, angegeben als String. Nachfolgende Argumente stellen die Namen und Werte bereit und können auf die gleiche Weise wie bei addRule()
angegeben werden.
Hinweis: Diese Methode (und auch addRule()
) ist synchronisiert, sodass zu jedem Zeitpunkt möglicherweise nur ein Thread die Maschine aktualisiert.
Die Funktionsweise dieser API kann subtil sein. Die Maschine kompiliert die Zuordnung von Namens-/Wertmustern zu Regelnamen in einem endlichen Automaten, merkt sich jedoch nicht, welche Muster einem bestimmten Regelnamen zugeordnet sind. Daher ist es nicht erforderlich, dass das Muster in einer deleteRule()
genau mit dem in der entsprechenden addRule()
übereinstimmt. Ruler sucht nach Übereinstimmungen mit den Namens-/Wertmustern und prüft, ob sie eine Übereinstimmung mit einer Regel mit dem angegebenen Namen ergeben, und entfernt sie gegebenenfalls. Bedenken Sie, dass bei der Ausführung deleteRule()
-Aufrufen, die nicht exakt mit den entsprechenden addRule()
Aufrufen übereinstimmen, kein Fehler auftritt und die Maschine nicht in einem inkonsistenten Zustand zurückbleibt, sie jedoch dazu führen können, dass sich „Müll“ in der Maschine ansammelt.
Eine konkrete Konsequenz besteht darin, dass Sie, wenn Sie addRule()
mehrmals mit demselben Namen, aber unterschiedlichen Mustern aufgerufen haben, wie oben im Abschnitt „Regeln und Regelnamen“ dargestellt, deleteRule()
genauso oft und mit demselben Aufruf aufrufen müssen Zugehörige Muster, um alle Verweise auf diesen Regelnamen von der Maschine zu entfernen.
Diese Methode gibt eine List
für Machine (und List
für GenericMachine) zurück, die die Namen der Regeln enthält, die mit dem bereitgestellten Ereignis übereinstimmen. Das Ereignis kann jeder Methode als einzelner String
bereitgestellt werden, der deren JSON-Form darstellt.
Das Ereignis kann rulesForEvent()
auch als Sammlung von Zeichenfolgen bereitgestellt werden, die Feldnamen und -werte abwechseln, und muss lexikalisch nach Feldnamen sortiert werden. Dies kann ein List
oder String[]
sein.
Die Bereitstellung des Ereignisses in JSON ist der empfohlene Ansatz und hat mehrere Vorteile. Erstens ist es schwierig, die String-Liste oder das Array mit abwechselnden Namens-/Wertmengen in einer nach Namen sortierten Reihenfolge zu füllen, und Ruler hilft nicht weiter, sondern funktioniert nur nicht richtig, wenn die Liste nicht richtig strukturiert ist. Erschwerend kommt hinzu, dass die Darstellung von Feldwerten, die als Zeichenfolgen bereitgestellt werden, den JSON-Syntaxregeln entsprechen muss – siehe unten unter JSON-Textabgleich .
Schließlich macht es die Listen-/Array-Version eines Ereignisses für Ruler unmöglich, Array-Strukturen zu erkennen und einen Array-konsistenten Abgleich bereitzustellen, wie weiter unten in diesem Dokument beschrieben. Die API rulesForEvent(String eventJSON)
ist zugunsten von rulesForJSONEvent()
veraltet, insbesondere weil sie keinen Array-konsistenten Abgleich unterstützt.
rulesForJSONEvent()
hat außerdem den Vorteil, dass der Code, der die JSON-Form des Ereignisses in eine sortierte Liste umwandelt, umfassend profiliert und optimiert wurde.
Die Leistung von rulesForEvent()
und rulesForJSONEvent()
hängt nicht von der Anzahl der mit addRule()
hinzugefügten Regeln ab. rulesForJSONEvent()
ist aufgrund der optimierten Ereignisverarbeitung im Allgemeinen schneller. Wenn Sie Ihre eigene Ereignisverarbeitung durchführen und rulesForEvent()
mit einer vorsortierten Liste von Namen und Werten aufrufen, ist das noch schneller; Sie können die Feldlistenvorbereitung jedoch möglicherweise nicht so schnell durchführen wie rulesForJSONEvent()
.
Diese Methode bestimmt ungefähr die Anzahl der Objekte innerhalb der Maschine. Der Wert variiert nur, wenn Regeln hinzugefügt oder entfernt werden. Dies ist nützlich, um große Maschinen zu identifizieren, die möglicherweise viel Speicher benötigen. Da diese Methode von der Anzahl der internen Objekte abhängt, kann sich diese Anzahl ändern, wenn die Interna der Linealbibliothek geändert werden. Die Methode führt alle Berechnungen zur Laufzeit durch, um zu vermeiden, dass Speicher beansprucht wird und die Auswirkungen großer Regelmaschinen noch schlimmer werden. Seine Berechnung ist absichtlich NICHT Thread-sicher, um das Blockieren von Regelauswertungen und Maschinenänderungen zu vermeiden. Das bedeutet, dass Sie beim Hinzufügen oder Entfernen von Daten zur Maschine durch einen parallelen Prozess möglicherweise andere Ergebnisse erhalten als nach Abschluss solcher paralleler Prozesse. Da die Bibliothek außerdem für einige Muster Optimierungen an ihren Interna vornimmt (weitere Einzelheiten finden Sie unter ShortcutTransition.java
), erhalten Sie möglicherweise auch unterschiedliche Ergebnisse, abhängig von der Reihenfolge, in der Regeln hinzugefügt oder entfernt wurden.
Wenn Sie Ihre Ereignisse als Name/Wert-Paare und nicht als verschachtelte Dokumente im JSON-Stil betrachten, kann die Klasse Patterns
(und ihre Unterklasse Range
“) beim Erstellen von Regeln hilfreich sein. Die folgenden statischen Methoden sind nützlich.
public static ValuePatterns exactMatch ( final String value );
public static ValuePatterns prefixMatch ( final String prefix );
public static ValuePatterns prefixEqualsIgnoreCaseMatch ( final String prefix );
public static ValuePatterns suffixMatch ( final String suffix );
public static ValuePatterns suffixEqualsIgnoreCaseMatch ( final String suffix );
public static ValuePatterns equalsIgnoreCaseMatch ( final String value );
public static ValuePatterns wildcardMatch ( final String value );
public static AnythingBut anythingButMatch ( final String anythingBut );
public static AnythingBut anythingButMatch ( final Set < String > anythingButs );
public static AnythingBut anythingButMatch ( final double anythingBut );
public static AnythingBut anythingButNumberMatch ( final Set < Double > anythingButs );
public static AnythingButValuesSet anythingButPrefix ( final String prefix );
public static AnythingButValuesSet anythingButPrefix ( final Set < String > anythingButs );
public static AnythingButValuesSet anythingButSuffix ( final String suffix );
public static AnythingButValuesSet anythingButSuffix ( final Set < String > anythingButs );
public static AnythingButValuesSet anythingButIgnoreCaseMatch ( final String anythingBut );
public static AnythingButValuesSet anythingButIgnoreCaseMatch ( final Set < String > anythingButs );
public static AnythingButValuesSet anythingButWildcard ( final String value );
public static AnythingButValuesSet anythingButWildcard ( final Set < String > anythingButs );
public static ValuePatterns numericEquals ( final double val );
public static Range lessThan ( final double val );
public static Range lessThanOrEqualTo ( final double val );
public static Range greaterThan ( final double val );
public static Range greaterThanOrEqualTo ( final double val );
public static Range between ( final double bottom , final boolean openBottom , final double top , final boolean openTop );
Sobald Sie mit diesen Methoden geeignete Patterns
Matcher erstellt haben, können Sie die folgenden Methoden zum Hinzufügen oder Löschen von Ihrem Computer verwenden:
public void addPatternRule ( final String name , final Map < String , List < Patterns >> namevals );
public void deletePatternRule ( final String name , final Map < String , List < Patterns >> namevals );
HINWEIS: Die in deleteRule()
aufgeführten Vorsichtsmaßnahmen gelten auch für deletePatternRule()
.
Die Feldwerte in Regeln müssen in ihren JSON-Darstellungen bereitgestellt werden. Das heißt, Zeichenfolgenwerte müssen in „Anführungszeichen“ eingeschlossen werden. Werte ohne Anführungszeichen sind zulässig, z. B. Zahlen ( -3.0e5
) und bestimmte JSON-spezifische Literale ( true
, false
und null
).
Dies kann vollständig ignoriert werden, wenn Regeln für addRule()
() in JSON-Form bereitgestellt werden oder wenn Sie mit Mustern statt mit Literalzeichenfolgen arbeiten. Wenn Sie jedoch Regeln als Name/Wert-Paare bereitstellen und angeben möchten, dass das Feld „xyz“ mit der Zeichenfolge „true“ übereinstimmt, muss diese als "xyz", ""true""
ausgedrückt werden. Andererseits würde "xyz", "true"
nur mit dem JSON-Literal true
übereinstimmen.
Ruler unterstützt den Regelabgleich für Ereignisse, die Arrays enthalten, jedoch nur, wenn das Ereignis in JSON-Form bereitgestellt wird – wenn es sich um eine Liste vorsortierter Felder handelt, geht die Array-Struktur im Ereignis verloren. Das Verhalten hängt auch davon ab, ob Sie rulesForEvent()
oder rulesForJSONEvent
verwenden.
Betrachten Sie das folgende Ereignis.
{
"employees" : [
{ "firstName" : "John" , "lastName" : "Doe" } ,
{ "firstName" : "Anna" , "lastName" : "Smith" } ,
{ "firstName" : "Peter" , "lastName" : "Jones" }
]
}
Dann passt diese Regel:
{ "employees" : { "firstName" : [ "Anna" ] } }
Das heißt, die Array-Struktur wird aus dem Regelmuster „herausgelöst“ und alle enthaltenen Objekte werden so behandelt, als wären sie der Wert des übergeordneten Felds. Dies funktioniert auch für mehrstufige Arrays:
{
"employees" : [
[
{ "firstName" : "John" , "lastName" : "Doe" } ,
{ "firstName" : "Anna" , "lastName" : "Smith" }
] ,
[
{ "firstName" : "Peter" , "lastName" : "Jones" }
]
]
}
In früheren Versionen von Ruler war rulesForEvent()
die einzige maschinenbasierte Abgleichsmethode, die leider auch die folgende Regel abgleicht:
{ "employees" : { "firstName" : [ "Anna" ] , "lastName" : [ "Jones" ] } }
Als Lösung führte Ruler rulesForJSONEvent()
ein, die, wie der Name schon sagt, nur Ereignisse abgleicht, die im JSON-Format bereitgestellt werden. rulesForJsonEvent()
stimmt nicht mit der oben genannten Regel „Anna“/„Jones“ überein.
Formal: rulesForJSONEvent()
erkennt keine Übereinstimmungen, bei denen sich zwei beliebige Felder in JSON-Objekten befinden, die sich in verschiedenen Elementen desselben Arrays befinden. In der Praxis bedeutet dies, dass es ungefähr das tut, was Sie erwarten würden.
Es gibt eine unterstützende Klasse com.amazon.fsm.ruler.RuleCompiler
. Es enthält eine Methode namens check()
die eine JSON-Regeldefinition akzeptiert und einen String-Wert zurückgibt, der, wenn er null ist, bedeutet, dass die Regel syntaktisch gültig war. Wenn der Rückgabewert nicht Null ist, enthält er eine für Menschen lesbare Fehlermeldung, die das Problem beschreibt.
Der Einfachheit halber enthält es auch eine Methode namens compile()
, die genau wie check()
funktioniert, aber einen Fehler durch Auslösen einer IOException signalisiert und bei Erfolg eine Map
in der Form zurückgibt, die der addRule()
der Maschine entspricht addRule()
-Methode erwartet. Da die Machine-Klasse dies intern verwendet, kann diese Methode zeitsparend sein.
Wenn Ruler Schlüssel kompiliert, verwendet es den Punkt ( .
) als Verbindungszeichen. Dies bedeutet, dass die folgenden beiden Regeln in derselben internen Darstellung kompiliert werden
## has no dots in keys
{ "detail" : { "state" : { "status" : [ "running" ] } } }
## has dots in keys
{ "detail" : { "state.status" : [ "running" ] } }
Dies bedeutet auch, dass diese Regeln für die folgenden zwei Ereignisse gelten:
## has no dots in keys
{ "detail" : { "state" : { "status" : "running" } } }
## has dots in keys
{ "detail" : { "state.status" : "running" } }
Dieses Verhalten kann sich in zukünftigen Versionen ändern (um Verwirrungen zu vermeiden) und sollte nicht als verlässlich angesehen werden.
Wir messen die Leistung von Ruler, indem wir mehrere Regeln in einer Maschine kompilieren und Ereignisse abgleichen, die als JSON-Strings bereitgestellt werden.
Ein Benchmark, der 213.068 JSON-Ereignisse mit einer durchschnittlichen Größe von etwa 900 Byte verarbeitet, gegenüber jeweils 5 exakten Übereinstimmungen, Präfix-Übereinstimmungen, Suffix-Übereinstimmungen, Gleichheits-Ignorieren-Fall-Übereinstimmungen, Wildcard-Übereinstimmungen, numerischen Übereinstimmungen und alles andere als Übereinstimmungen Regeln und Zählen der Übereinstimmungen ergeben auf einem 2019 MacBook Folgendes:
Ereignisse werden mit über 220.000/Sekunde verarbeitet, mit Ausnahme von:
Hier einige Vorschläge zu Verarbeitungsregeln und Ereignissen:
Aus Leistungsgründen reagiert Ruler auf die folgenden Elemente. Wenn Sie also das Schema Ihres Ereignisses und Ihrer Regel entwerfen, finden Sie hier einige Vorschläge:
Weitere Informationen finden Sie unter BEITRAGEN.
Dieses Projekt ist unter der Apache-2.0-Lizenz lizenziert. Weitere Informationen finden Sie unter LIZENZ.