Event Ruler (для краткости называемая Ruler в остальной части документа) — это библиотека Java, которая позволяет сопоставлять правила с событиями . Событие — это список полей, которые могут быть заданы как пары имя/значение или как объект JSON. Правило связывает имена полей событий со списками возможных значений. Есть две причины использовать Ruler:
Содержание:
Проще всего объяснить на примере.
Событие — это объект 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 имеет API для предоставления событий как в форме JSON, так и в виде пар имя/значение:
+--------------+------------------------------------------+
| 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" } } ]
}
Соответствие префиксного равенства-игнорирования регистра работает только с полями со строковыми значениями.
{
"source" : [ { "suffix" : "ec2" } ]
}
Сопоставления суффиксов работают только с полями со строковыми значениями.
{
"source" : [ { "suffix" : { "equals-ignore-case" : "EC2" } } ]
}
Соответствие суффиксу «равно-игнорировать-регистр» работает только с полями со строковыми значениями.
{
"source" : [ { "equals-ignore-case" : "EC2" } ]
}
Сопоставления «равно-игнорировать-регистр» работают только с полями со строковыми значениями.
{
"source" : [ { "wildcard" : "Simple*Service" } ]
}
Соответствия по подстановочным знакам работают только с полями со строковыми значениями. Одно значение может содержать от нуля до нескольких символов подстановки, но последовательные символы подстановки не допускаются. Чтобы точно соответствовать символу звездочки, подстановочный знак можно экранировать обратной косой чертой. Две последовательные обратные косые черты (т. е. обратная косая черта, экранированная обратной косой чертой) представляют собой фактический символ обратной косой черты. Обратная косая черта, экранирующая любой символ, кроме звездочки или обратной косой черты, не допускается.
Все, кроме сопоставления, делает то, что указано в названии: соответствует всему, кроме того, что указано в правиле.
Что угодно, но работает с одиночными строковыми и числовыми значениями или списками, которые должны содержать полностью строки или полностью числовые значения. Его также можно применять к префиксу, суффиксу или совпадению строки или списка строк без учета регистра.
Одиночное что угодно, но (строковое, затем числовое):
{
"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. Числовое сопоставление поддерживает ту же точность и диапазон, что и double
примитив Java, который реализует стандарт IEEE binary64
.
{
"detail" : {
"source-ip" : [ { "cidr" : "10.0.0.0/24" } ]
}
}
Это также работает с адресами IPv6.
Сопоставление Exists работает при наличии или отсутствии поля в событии 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 применил логику «И». для всех полей по умолчанию без примитива «И» .
Есть два способа достичь эффекта «ИЛИ»:
Примитив «$или», позволяющий клиенту напрямую описать связь «или» между полями в правиле.
Линейка распознает отношение «ИЛИ» только в том случае, если правило удовлетворяет всем нижеперечисленным условиям:
/src/main/software/amazon/event/ruler/Constants.java#L38
например, приведенное ниже правило не будет анализироваться как « Или" связь, потому что "числовой" и "префикс" являются ключевыми словами, зарезервированными линейкой. {
"$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 изо всех сил старается поддерживать обратную совместимость. Только при соблюдении трех условий, упомянутых выше, правитель изменит свое поведение, поскольку он предполагает, что ваше правило действительно требовало ИЛИ и до сегодняшнего дня было неправильно настроено. Например, приведенное ниже правило будет продолжать работать как обычное правило, рассматривая «$or» как обычное имя поля в правиле и событии:
{
"source" : [ "aws.cloudwatch" ] ,
"$or" : {
"metricType" : [ "MetricType" ] ,
"namespace" : [ "AWS/EC2" , "AWS/ES" ]
}
}
Дополнительные примеры того, как «$or» анализируется Ruler как обычное имя поля, см. в файле /src/test/data/normalRulesWithOrWording.json
Ключевое слово «$or» в качестве примитива связи «Или» не должно проектироваться как обычное поле как в Событиях, так и в Правилах. Ruler поддерживает устаревшие правила, в которых «$or» анализируется как обычное имя поля, чтобы сохранить обратную совместимость и дать команде время перенести устаревшее использование «$or» из своих событий и правил в качестве обычного имени файла. Совместное использование «$or» в качестве примитива «Or» и «$or» в качестве обычного имени поля не поддерживается Ruler намеренно, чтобы избежать возникновения неудобных двусмысленностей в «$or».
Есть два способа использования линейки. Вы можете скомпилировать несколько правил в «Машину», а затем использовать либо ее метод rulesForEvent()
, либо методы rulesForJSONEvent()
чтобы проверить, какое из правил соответствует какому-либо событию. Разница между этими двумя методами обсуждается ниже. В этом обсуждении rulesForEvent()
будут использоваться в общем, за исключением тех случаев, когда разница имеет значение.
Альтернативно вы можете использовать один статический логический метод, чтобы определить, соответствует ли отдельное событие определенному правилу.
Существует единственный статический логический метод Ruler.matchesRule(event, rule)
— оба аргумента предоставляются в виде строк JSON.
ПРИМЕЧАНИЕ. Существует еще один устаревший метод Ruler.matches(event, rule)
, который не следует использовать, поскольку его результаты несовместимы с rulesForJSONEvent()
и rulesForEvent()
. Подробности смотрите в документации Ruler.matches(event, rule)
.
Время сопоставления не зависит от количества правил. Это лучший выбор, если у вас есть несколько возможных правил, из которых вы хотите выбрать, и особенно если у вас есть способ сохранить скомпилированную машину.
На время сопоставления влияет степень недетерминированности, вызванная правилами подстановочных знаков и правилами, отличными от подстановочных знаков. Производительность ухудшается, поскольку все большее число префиксов правил с подстановочными знаками соответствует теоретическому наихудшему событию. Чтобы избежать этого, в правилах подстановочных знаков, относящихся к одному и тому же полю события, следует избегать общих префиксов, ведущих к их первому подстановочному знаку. Если требуется общий префикс, используйте минимальное количество подстановочных знаков и ограничьте повторяющиеся последовательности символов, которые следуют за подстановочным знаком. 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 была создана первой, а строки, которые она сохраняла и возвращала, считались именами правил.
В целях безопасности тип, используемый для «именования» правил, должен быть неизменяемым. Если вы измените содержимое объекта, пока он используется в качестве имени правила, это может нарушить работу Ruler.
Конструкторы GenericMachine и Machine дополнительно принимают объект GenericMachineConfiguration, который предоставляет следующие параметры конфигурации.
По умолчанию: false Обычно NameStates повторно используются для данной ключевой подпоследовательности и шаблона, если эта ключевая подпоследовательность и шаблон были добавлены ранее или если шаблон уже был добавлен для данной ключевой подпоследовательности. Следовательно, по умолчанию повторное использование NameState является оппортунистическим. Но если для этого флага установлено значение true, повторное использование NameState будет принудительно для ключевой подпоследовательности. Это означает, что первый шаблон, добавляемый для ключевой подпоследовательности, будет повторно использовать NameState, если эта ключевая подпоследовательность была добавлена ранее. Это означает, что каждая ключевая подпоследовательность имеет одно состояние NameState. В некоторых случаях это экспоненциально улучшает использование памяти, но приводит к тому, что в отдельных NameStates сохраняется больше подправил, которые Ruler иногда перебирает, что может вызвать умеренное снижение производительности во время выполнения. По умолчанию это значение равно false для обратной совместимости, но, вероятно, все приложения, кроме наиболее чувствительных к задержкам, выиграют от установки этого значения в значение true.
Вот простой пример. Учитывать:
machine . addRule ( "0" , "{"key1": ["a", "b", "c"]}" ) ;
Шаблон «a» создает NameState, а затем, даже если extraNameStateReuse=false, второй шаблон («b») и третий шаблон («c») повторно используют тот же самый NameState. Но вместо этого рассмотрим следующее:
machine . addRule ( "0" , "{"key1": ["a"]}" ) ;
machine . addRule ( "1" , "{"key1": ["b"]}" ) ;
machine . addRule ( "2" , "{"key1": ["c"]}" ) ;
Теперь, при дополнительном NameStateReuse=false, мы получаем три NameState, поскольку первый шаблон, встречающийся для ключевой подпоследовательности при каждом добавлении правила, создаст новый NameState. Итак, «a», «b» и «c» получают свои собственные состояния имен. Однако при дополнительном NameStateReuse=true «a» создаст новый NameState, затем «b» и «c» будут повторно использовать этот же NameState. Это достигается за счет сохранения того, что у нас уже есть NameState для ключевой подпоследовательности «key1».
Обратите внимание, что не имеет значения, использует ли каждый addRule другое имя правила или одно и то же имя правила.
Все формы этого метода имеют один и тот же первый аргумент — строку, которая содержит имя правила и возвращается rulesForEvent()
. Остальные аргументы предоставляют пары имя/значение. Они могут быть предоставлены в формате JSON, как в примерах выше (через String, Reader, InputStream или byte[]
) или в виде Map<String, List<String>>
, где ключами являются имена полей и значения — это список возможных совпадений; в приведенном выше примере будет ключ с именем detail.state
, значение которого будет списком, содержащим "initializing"
и "running"
.
Примечание. Этот метод (а также deleteRule()
) синхронизирован, поэтому в любой момент времени только один поток может обновлять машину.