Event Ruler (chamado Ruler no restante do documento por questões de brevidade) é uma biblioteca Java que permite combinar Rules com Events . Um evento é uma lista de campos, que pode ser fornecida como pares nome/valor ou como um objeto JSON. Uma regra associa nomes de campos de eventos a listas de valores possíveis. Existem dois motivos para usar a Régua:
Conteúdo:
É mais fácil explicar com exemplos.
Um Evento é um objeto JSON. Aqui está um exemplo:
{
"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"
}
}
Você também pode ver isso como um conjunto de pares nome/valor. Por uma questão de brevidade, apresentamos apenas uma amostragem. O Ruler possui APIs para fornecer eventos no formato JSON e como pares nome/valor:
+--------------+------------------------------------------+
| name | value |
|--------------|------------------------------------------|
| source | "aws.ec2" |
| detail-type | "EC2 Instance State-change Notification" |
| detail.state | "running" |
+--------------+------------------------------------------+
Os eventos no formato JSON podem ser fornecidos na forma de uma string JSON bruta ou de um Jackson JsonNode analisado.
Todas as regras nesta seção correspondem ao evento de exemplo acima:
{
"detail-type" : [ "EC2 Instance State-change Notification" ] ,
"resources" : [ "arn:aws:ec2:us-east-1:123456789012:instance/i-000000aaaaaa00000" ] ,
"detail" : {
"state" : [ "initializing" , "running" ]
}
}
Isso corresponderá a qualquer evento com os valores fornecidos para os valores resource
, detail-type
e detail.state
, ignorando quaisquer outros campos no evento. Também corresponderia se o valor de detail.state
tivesse sido "initializing"
.
Os valores nas regras são sempre fornecidos como matrizes e correspondem se o valor no evento for um dos valores fornecidos na matriz. A referência aos resources
mostra que se o valor no evento também for uma matriz, a regra corresponderá se a interseção entre a matriz de eventos e a matriz de regras não estiver vazia.
{
"time" : [ { "prefix" : "2017-10-02" } ]
}
As correspondências de prefixo funcionam apenas em campos com valor de string.
{
"source" : [ { "prefix" : { "equals-ignore-case" : "EC2" } } ]
}
As correspondências de prefixo equals-ignore-case funcionam apenas em campos com valor de string.
{
"source" : [ { "suffix" : "ec2" } ]
}
As correspondências de sufixos funcionam apenas em campos com valor de string.
{
"source" : [ { "suffix" : { "equals-ignore-case" : "EC2" } } ]
}
As correspondências de sufixo equals-ignore-case funcionam apenas em campos com valor de string.
{
"source" : [ { "equals-ignore-case" : "EC2" } ]
}
As correspondências Equals-ignore-case funcionam apenas em campos com valor de string.
{
"source" : [ { "wildcard" : "Simple*Service" } ]
}
As correspondências de curinga funcionam apenas em campos com valor de string. Um único valor pode conter de zero a muitos caracteres curinga, mas caracteres curinga consecutivos não são permitidos. Para corresponder especificamente ao caractere asterisco, um caractere curinga pode ser escapado com uma barra invertida. Duas barras invertidas consecutivas (ou seja, uma barra invertida escapada por uma barra invertida) representam o caractere de barra invertida real. Uma barra invertida escapando de qualquer caractere diferente de asterisco ou barra invertida não é permitida.
A correspondência de qualquer coisa, menos faz o que o nome diz: corresponde a qualquer coisa, exceto ao que é fornecido na regra.
Qualquer coisa, menos funciona com string única e valores ou listas numéricas, que devem conter inteiramente strings ou inteiramente numéricos. Também pode ser aplicado a uma correspondência de prefixo, sufixo ou igual a ignorar maiúsculas e minúsculas de uma string ou lista de strings.
Único, tudo menos (string e depois numérico):
{
"detail" : {
"state" : [ { "anything-but" : "initializing" } ]
}
}
{
"detail" : {
"x-limit" : [ { "anything-but" : 123 } ]
}
}
Qualquer coisa, menos lista (strings):
{
"detail" : {
"state" : [ { "anything-but" : [ "stopped" , "overloaded" ] } ]
}
}
Qualquer coisa, menos lista (números):
{
"detail" : {
"x-limit" : [ { "anything-but" : [ 100 , 200 , 300 ] } ]
}
}
Qualquer coisa, menos prefixo:
{
"detail" : {
"state" : [ { "anything-but" : { "prefix" : "init" } } ]
}
}
Lista de qualquer coisa, menos prefixos (strings):
{
"detail" : {
"state" : [ { "anything-but" : { "prefix" : [ "init" , "error" ] } } ]
}
}
Qualquer coisa, menos sufixo:
{
"detail" : {
"instance-id" : [ { "anything-but" : { "suffix" : "1234" } } ]
}
}
Lista de qualquer coisa, menos sufixos (strings):
{
"detail" : {
"instance-id" : [ { "anything-but" : { "suffix" : [ "1234" , "6789" ] } } ]
}
}
Qualquer coisa, menos ignorar:
{
"detail" : {
"state" : [ { "anything-but" : { "equals-ignore-case" : "Stopped" } } ]
}
}
Lista de casos de qualquer coisa, menos ignorar (strings):
{
"detail" : {
"state" : [ { "anything-but" : { "equals-ignore-case" : [ "Stopped" , "OverLoaded" ] } } ]
}
}
Qualquer coisa, menos curinga:
{
"detail" : {
"state" : [ { "anything-but" : { "wildcard" : "*/bin/*.jar" } } ]
}
}
Qualquer coisa, menos lista de curingas (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 ] } ]
}
}
Acima, as referências a c-count
, d-count
e x-limit
ilustram a correspondência numérica e funcionam apenas com valores que são números JSON. A correspondência numérica suporta a mesma precisão e intervalo que a primitiva double
do Java que implementa o padrão IEEE 754 binary64
.
{
"detail" : {
"source-ip" : [ { "cidr" : "10.0.0.0/24" } ]
}
}
Isso também funciona com endereços IPv6.
A correspondência de existências funciona na presença ou ausência de um campo no evento JSON.
A regra abaixo corresponderá a qualquer evento que tenha um campo detail.c-count presente.
{
"detail" : {
"c-count" : [ { "exists" : true } ]
}
}
A regra abaixo corresponderá a qualquer evento que não possua o campo detail.c-count.
{
"detail" : {
"c-count" : [ { "exists" : false } ]
}
}
Nota A correspondência Exists
funciona apenas nos nós folha. Não funciona em nós intermediários.
Por exemplo, o exemplo acima para exists : false
corresponderia ao evento abaixo:
{
"detail-type" : [ "EC2 Instance State-change Notification" ] ,
"resources" : [ "arn:aws:ec2:us-east-1:123456789012:instance/i-000000aaaaaa00000" ] ,
"detail" : {
"state" : [ "initializing" , "running" ]
}
}
mas também corresponderia ao evento abaixo porque c-count
não é um nó folha:
{
"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" } ]
}
}
Como mostram os exemplos acima, o Ruler considera uma regra correspondente se todos os campos nomeados na regra corresponderem e considera um campo correspondente se algum dos valores de campo fornecidos corresponder, ou seja, o Ruler aplicou a lógica "E" para todos os campos por padrão sem a primitiva "E" é obrigatória .
Existem duas maneiras de alcançar os efeitos "Or":
A primitiva "$or" para permitir que o cliente descreva diretamente o relacionamento "Or" entre os campos da regra.
A régua reconhece o relacionamento "Ou" somente quando a regra atende a todas as condições abaixo:
/src/main/software/amazon/event/ruler/Constants.java#L38
por exemplo, a regra abaixo não será analisada como " Ou" relacionamento porque "numérico" e "prefixo" são palavras-chave reservadas da régua. {
"$or": [ {"numeric" : 123}, {"prefix": "abc"} ]
}
Caso contrário, o Ruler apenas trata o "$or" como um nome de campo normal, da mesma forma que outra string na regra.
Normal "Ou":
// Effect of "source" && ("metricName" || "namespace")
{
"source" : [ "aws.cloudwatch" ] ,
"$or" : [
{ "metricName" : [ "CPUUtilization" , "ReadLatency" ] } ,
{ "namespace" : [ "AWS/EC2" , "AWS/ES" ] }
]
}
Paralelo "Ou":
// 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" ] }
]
}
}
"Ou" tem um "E" dentro
// Effect of ("source" && ("metricName" || ("metricType && "namespace") || "scope"))
{
"source" : [ "aws.cloudwatch" ] ,
"$or" : [
{ "metricName" : [ "CPUUtilization" , "ReadLatency" ] } ,
{
"metricType" : [ "MetricType" ] ,
"namespace" : [ "AWS/EC2" , "AWS/ES" ]
} ,
{ "scope" : [ "Service" ] }
]
}
"Ou" e "E" aninhados
// 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" possivelmente já é usado como chave normal em alguns aplicativos (embora seja provavelmente raro). Para esses casos, o Ruler faz o possível para manter a compatibilidade com versões anteriores. Somente quando as 3 condições mencionadas acima forem atendidas, a régua mudará o comportamento, pois assume que sua regra realmente queria um OR e estava mal configurada até hoje. Por exemplo, a regra abaixo continuará funcionando normalmente, tratando "$or" como nome de campo normal na regra e no evento:
{
"source" : [ "aws.cloudwatch" ] ,
"$or" : {
"metricType" : [ "MetricType" ] ,
"namespace" : [ "AWS/EC2" , "AWS/ES" ]
}
}
Consulte /src/test/data/normalRulesWithOrWording.json
para obter mais exemplos de que "$or" é analisado como nome de campo normal pela Régua.
A palavra-chave "$or" como primitiva de relacionamento "Or" não deve ser projetada como campo normal em Eventos e Regras. O Ruler oferece suporte às regras legadas em que "$or" é analisado como nome de campo normal para manter a compatibilidade com versões anteriores e dar tempo para a equipe migrar seu uso "$or" herdado de seus eventos e regras como nome arquivado normal. O uso misto de "$or" como "Or" primitivo e "$or" como nome de campo normal não é suportado intencionalmente pelo Ruler para evitar a ocorrência de ambiguidades super estranhas em "$or".
Existem duas maneiras de usar a Régua. Você pode compilar várias regras em uma "Máquina" e, em seguida, usar um de seus métodos rulesForEvent()
ou métodos rulesForJSONEvent()
para verificar quais das regras correspondem a qualquer evento. A diferença entre esses dois métodos é discutida abaixo. Esta discussão usará rulesForEvent()
genericamente, exceto onde a diferença for importante.
Como alternativa, você pode usar um único método booleano estático para determinar se um evento individual corresponde a uma regra específica.
Existe um único método booleano estático Ruler.matchesRule(event, rule)
- ambos os argumentos são fornecidos como strings JSON.
NOTA: Existe outro método obsoleto chamado Ruler.matches(event, rule)
que não deve ser usado porque seus resultados são inconsistentes com rulesForJSONEvent()
e rulesForEvent()
. Consulte a documentação em Ruler.matches(event, rule)
para obter detalhes.
O tempo de correspondência não depende do número de regras. Esta é a melhor escolha se você tiver várias regras possíveis para selecionar e, especialmente, se tiver uma maneira de armazenar a máquina compilada.
O tempo de correspondência é afetado pelo grau de não determinismo causado pelas regras curinga e tudo menos curinga. O desempenho se deteriora à medida que um número crescente de prefixos de regras curinga corresponde a um evento teórico de pior caso. Para evitar isso, as regras curinga pertencentes ao mesmo campo de evento devem evitar prefixos comuns que levam ao seu primeiro caractere curinga. Se um prefixo comum for necessário, use o número mínimo de caracteres curinga e limite as sequências de caracteres repetidas que ocorrem após um caractere curinga. MachineComplexityEvaluator pode ser usado para avaliar uma máquina e determinar o grau de não determinismo ou "complexidade" (ou seja, quantos prefixos de regras curinga correspondem a um evento teórico de pior caso). Aqui estão alguns pontos de dados que mostram uma diminuição típica no desempenho para pontuações de complexidade crescentes.
É importante limitar a complexidade da máquina para proteger sua aplicação. Existem pelo menos duas estratégias diferentes para limitar a complexidade da máquina. Qual deles faz mais sentido pode depender da sua aplicação.
A estratégia nº 1 é mais ideal porque mede a complexidade real da máquina que contém todas as regras. Sempre que possível, esta estratégia deve ser utilizada. A desvantagem é que digamos que você tenha um plano de controle que permite a criação de uma regra por vez, até um número muito grande. Então, para cada uma dessas operações do plano de controle, você deverá carregar todas as regras existentes para realizar a validação. Isto pode ser muito caro. Também está sujeito a condições de corrida. A estratégia nº 2 é um compromisso. O limite usado pela estratégia nº 2 será menor que a estratégia nº 1, pois é um limite por regra. Digamos que você queira que a complexidade de uma máquina, com todas as regras adicionadas, não seja superior a 300. Então, com a estratégia nº 2, por exemplo, você poderia limitar cada máquina de regra única a uma complexidade de 10 e permitir 30 regras contendo padrões curinga . No pior caso, onde a complexidade é perfeitamente aditiva (improvável), isso levaria a uma máquina com complexidade de 300. A desvantagem é que é improvável que a complexidade seja perfeitamente aditiva e, portanto, o número de regras contendo caracteres curinga será provavelmente será limitado desnecessariamente.
Para a estratégia nº 2, dependendo de como as regras são armazenadas, pode ser necessário adicionar um atributo adicional às regras para indicar quais são não determinísticas (ou seja, contêm padrões curinga), a fim de limitar o número de regras que contêm curinga.
A seguir está um trecho de código que ilustra como limitar a complexidade para um determinado padrão, como para a estratégia nº 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 ; } }
A classe principal com a qual você interagirá implementa correspondência de regras baseada em máquina de estado. Os métodos interessantes são:
addRule()
– adiciona uma nova regra à máquinadeleteRule()
- exclui uma regra da máquinarulesForEvent()
/ rulesForJSONEvent()
– encontra as regras na máquina que correspondem a um evento Existem dois sabores: Machine
e GenericMachine
. Máquina é simplesmente GenericMachine
. A API refere-se ao tipo genérico como "nome", que reflete a história: a versão String foi construída primeiro e as strings armazenadas e retornadas foram consideradas nomes de regras.
Por segurança, o tipo usado para "nomear" as regras deve ser imutável. Se você alterar o conteúdo de um objeto enquanto ele estiver sendo usado como nome de regra, isso poderá interromper a operação da Régua.
Os construtores GenericMachine e Machine aceitam opcionalmente um objeto GenericMachineConfiguration, que expõe as seguintes opções de configuração.
Padrão: falso Normalmente, NameStates são reutilizados para uma determinada subsequência e padrão de chave se essa subsequência e padrão de chave tiverem sido adicionados anteriormente ou se um padrão já tiver sido adicionado para a subsequência de chave fornecida. Portanto, por padrão, a reutilização do NameState é oportunista. Mas ao definir este sinalizador como verdadeiro, a reutilização de NameState será forçada para uma subsequência de chave. Isso significa que o primeiro padrão adicionado para uma subsequência de chave reutilizará um NameState se essa subsequência de chave tiver sido adicionada antes. Significa que cada subsequência de chave possui um único NameState. Isso melhora exponencialmente a utilização da memória em alguns casos, mas faz com que mais sub-regras sejam armazenadas em NameStates individuais, sobre os quais o Ruler às vezes itera, o que pode causar uma regressão modesta no desempenho do tempo de execução. O padrão é falso para compatibilidade com versões anteriores, mas provavelmente todos os aplicativos, exceto os mais sensíveis à latência, se beneficiariam com a configuração como verdadeiro.
Aqui está um exemplo simples. Considerar:
machine . addRule ( "0" , "{"key1": ["a", "b", "c"]}" ) ;
O padrão "a" cria um NameState e, então, mesmo com adicionalNameStateReuse=false, o segundo padrão ("b") e o terceiro padrão ("c") reutilizam esse mesmo NameState. Mas considere o seguinte:
machine . addRule ( "0" , "{"key1": ["a"]}" ) ;
machine . addRule ( "1" , "{"key1": ["b"]}" ) ;
machine . addRule ( "2" , "{"key1": ["c"]}" ) ;
Agora, com adicionalNameStateReuse=false, terminamos com três NameStates, porque o primeiro padrão encontrado para uma subsequência de chave em cada adição de regra criará um novo NameState. Portanto, "a", "b" e "c" obtêm seus próprios NameStates. No entanto, com adicionalNameStateReuse=true, "a" criará um novo NameState, então "b" e "c" reutilizarão esse mesmo NameState. Isso é feito armazenando que já temos um NameState para a subsequência de chave "key1".
Observe que não importa se cada addRule usa um nome de regra diferente ou o mesmo nome de regra.
Todas as formas deste método possuem o mesmo primeiro argumento, uma String que fornece o nome da Regra e é retornada por rulesForEvent()
. O restante dos argumentos fornece os pares nome/valor. Eles podem ser fornecidos em JSON como nos exemplos acima (por meio de String, Reader, InputStream ou byte[]
) ou como Map
, onde as chaves são os nomes dos campos e o os valores são a lista de possíveis correspondências; usando o exemplo acima, haveria uma chave chamada detail.state
cujo valor seria a lista contendo "initializing"
e "running"
.
Nota: Este método (e também deleteRule()
) é sincronizado, portanto, apenas um thread pode atualizar a máquina a qualquer momento.
Você pode chamar addRule()
várias vezes com o mesmo nome, mas com vários padrões de nome/valor diferentes, alcançando assim um relacionamento "ou"; rulesForEvent()
retornará esse nome se algum dos padrões corresponder.
Por exemplo, suponha que você chame addRule()
com o nome da regra como "R1" e adicione o seguinte padrão:
{
"detail" : {
"c-count" : [ { "numeric" : [ ">" , 0 , "<=" , 5 ] } ]
}
}
Então você chama novamente com o mesmo nome, mas com um padrão diferente:
{
"detail" : {
"x-limit" : [ { "numeric" : [ "=" , 3.018e2 ] } ]
}
}
Depois disso, rulesForEvent()
retornará "R1" para um valor c-count
de 2 ou um valor x-limit
de 301,8.
Esta é uma imagem espelhada de addRule()
; em cada caso, o primeiro argumento é o nome da regra, fornecido como uma String. Os argumentos subsequentes fornecem os nomes e valores e podem ser fornecidos da mesma maneira que addRule()
.
Nota: Este método (e também addRule()
) é sincronizado, portanto, apenas um thread pode atualizar a máquina a qualquer momento.
A operação desta API pode ser sutil. A Máquina compila o mapeamento de padrões de nome/valor para nomes de regras em um autômato finito, mas não lembra quais padrões são mapeados para um determinado nome de regra. Portanto, não há exigência de que o padrão em deleteRule()
corresponda exatamente ao padrão addRule()
. A régua procurará correspondências com os padrões de nome/valor e verá se eles correspondem a uma regra com o nome fornecido e, em caso afirmativo, os removerá. Tenha em mente que ao executar chamadas deleteRule()
que não correspondam exatamente às chamadas addRule()
correspondentes não falharão e não deixarão a máquina em um estado inconsistente, elas podem causar o acúmulo de "lixo" na máquina.
Uma consequência específica é que se você tiver chamado addRule()
diversas vezes com o mesmo nome, mas com padrões diferentes, conforme ilustrado acima na seção Regras e nomes de regras , você teria que chamar deleteRule()
o mesmo número de vezes, com o mesmo padrões associados, para remover todas as referências a esse nome de regra da máquina.
Este método retorna um List
para Machine (e List
para GenericMachine) que contém os nomes das regras que correspondem ao evento fornecido. O evento pode ser fornecido a qualquer método como uma única String
representando seu formato JSON.
O evento também pode ser fornecido para rulesForEvent()
como uma coleção de strings que alternam nomes e valores de campos e devem ser classificados lexicamente por nome de campo. Pode ser um List
ou String[]
.
Fornecer o evento em JSON é a abordagem recomendada e tem diversas vantagens. Primeiro de tudo, preencher a lista ou array de String com quantidades alternadas de nome/valor, em uma ordem classificada por nome, é complicado, e o Ruler não ajuda, apenas falha em funcionar corretamente se a lista estiver estruturada incorretamente. Para aumentar a dificuldade, a representação dos valores dos campos, fornecidos como strings, deve seguir as regras de sintaxe JSON - veja abaixo em Correspondência de texto JSON .
Finalmente, a versão lista/array de um evento torna impossível para o Ruler reconhecer estruturas de array e fornecer correspondência consistente com array, descrita abaixo neste documento. A API rulesForEvent(String eventJSON)
foi descontinuada em favor de rulesForJSONEvent()
especificamente porque não suporta correspondência consistente com array.
rulesForJSONEvent()
também tem a vantagem de que o código que transforma o formato JSON do evento em uma lista ordenada foi amplamente perfilado e otimizado.
O desempenho de rulesForEvent()
e rulesForJSONEvent()
não depende do número de regras adicionadas com addRule()
. rulesForJSONEvent()
geralmente é mais rápido devido ao processamento otimizado de eventos. Se você fizer seu próprio processamento de eventos e chamar rulesForEvent()
com uma lista pré-classificada de nomes e valores, isso será ainda mais rápido; mas talvez você não consiga fazer a preparação da lista de campos tão rápido quanto rulesForJSONEvent()
faz.
Este método calcula aproximadamente o número de objetos dentro da máquina. Seu valor varia apenas conforme as regras são adicionadas ou removidas. Isso é útil para identificar máquinas grandes que potencialmente requerem muita memória. Como este método depende do número de objetos internos, esta contagem pode mudar quando os internos da biblioteca de réguas são alterados. O método realiza todos os seus cálculos em tempo de execução para evitar ocupar memória e piorar o impacto de grandes máquinas de regras. Sua computação NÃO é intencionalmente segura para threads para evitar o bloqueio de avaliações de regras e alterações de máquina. Isso significa que se um processo paralelo for adicionado ou removido da máquina, você poderá obter resultados diferentes em comparação com quando esses processos paralelos forem concluídos. Além disso, como a biblioteca faz otimizações internas para alguns padrões (consulte ShortcutTransition.java
para obter mais detalhes), você também pode obter resultados diferentes dependendo da ordem em que as regras foram adicionadas ou removidas.
Se você pensar em seus eventos como pares nome/valor em vez de documentos aninhados no estilo JSON, a classe Patterns
(e sua subclasse Range
) pode ser útil na construção de regras. Os seguintes métodos estáticos são úteis.
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 );
Depois de construir correspondentes Patterns
apropriados com esses métodos, você pode usar os seguintes métodos para adicionar ou excluir de sua máquina:
public void addPatternRule ( final String name , final Map < String , List < Patterns >> namevals );
public void deletePatternRule ( final String name , final Map < String , List < Patterns >> namevals );
NOTA: Os cuidados listados em deleteRule()
também se aplicam a deletePatternRule()
.
Os valores dos campos nas regras devem ser fornecidos em suas representações JSON. Ou seja, os valores da string devem ser colocados entre "aspas". Valores sem aspas são permitidos, como números ( -3.0e5
) e determinados literais específicos do JSON ( true
, false
e null
).
Isso pode ser totalmente ignorado se regras forem fornecidas para addRule()
() no formato JSON ou se você estiver trabalhando com padrões em vez de strings literais. Mas se você estiver fornecendo regras como pares nome/valor e quiser especificar que o campo "xyz" corresponde à string "true", isso deverá ser expresso como "xyz", ""true""
. Por outro lado, "xyz", "true"
corresponderia apenas ao literal JSON true
.
O Ruler oferece suporte à correspondência de regras para eventos que contêm matrizes, mas somente quando o evento é fornecido no formato JSON - quando é uma lista de campos pré-classificados, a estrutura da matriz no evento é perdida. O comportamento também depende de você usar rulesForEvent()
ou rulesForJSONEvent
.
Considere o seguinte evento.
{
"employees" : [
{ "firstName" : "John" , "lastName" : "Doe" } ,
{ "firstName" : "Anna" , "lastName" : "Smith" } ,
{ "firstName" : "Peter" , "lastName" : "Jones" }
]
}
Então esta regra corresponderá:
{ "employees" : { "firstName" : [ "Anna" ] } }
Ou seja, a estrutura do array é "eliminada" do padrão de regra e quaisquer objetos contidos são tratados como se fossem o valor do campo pai. Isso também funciona para matrizes multiníveis:
{
"employees" : [
[
{ "firstName" : "John" , "lastName" : "Doe" } ,
{ "firstName" : "Anna" , "lastName" : "Smith" }
] ,
[
{ "firstName" : "Peter" , "lastName" : "Jones" }
]
]
}
Nas versões anteriores do Ruler, o único método de correspondência baseado em máquina era rulesForEvent()
que infelizmente também corresponderá à seguinte regra:
{ "employees" : { "firstName" : [ "Anna" ] , "lastName" : [ "Jones" ] } }
Como solução, o Ruler introduziu rulesForJSONEvent()
que, como o nome sugere, corresponde apenas a eventos fornecidos no formato JSON. rulesForJsonEvent()
não corresponderá à regra "Anna"/"Jones" acima.
Formalmente: rulesForJSONEvent()
se recusará a reconhecer qualquer correspondência em que quaisquer dois campos estejam dentro de objetos JSON que estejam em elementos diferentes do mesmo array. Na prática, isso significa que faz o que você esperaria.
Existe uma classe de suporte com.amazon.fsm.ruler.RuleCompiler
. Ele contém um método chamado check()
que aceita uma definição de regra JSON e retorna um valor String que, se nulo, significa que a regra era sintaticamente válida. Se o valor de retorno não for Nulo, ele conterá uma mensagem de erro legível descrevendo o problema.
Por conveniência, ele também contém um método chamado compile()
que funciona exatamente como check()
mas sinaliza um erro lançando uma IOException e, em caso de sucesso, retorna um Map
na forma que addRule()
da máquina addRule()
método espera. Como a classe Machine usa isso internamente, esse método pode economizar tempo.
Quando o Ruler compila chaves, ele usa ponto ( .
) como caractere de união. Isso significa que irá compilar as duas regras a seguir para a mesma representação interna
## has no dots in keys
{ "detail" : { "state" : { "status" : [ "running" ] } } }
## has dots in keys
{ "detail" : { "state.status" : [ "running" ] } }
Isso também significa que essas regras corresponderão aos seguintes dois eventos:
## has no dots in keys
{ "detail" : { "state" : { "status" : "running" } } }
## has dots in keys
{ "detail" : { "state.status" : "running" } }
Este comportamento pode mudar em versões futuras (para evitar confusões) e não deve ser considerado confiável.
Medimos o desempenho do Ruler compilando várias regras em uma máquina e combinando eventos fornecidos como strings JSON.
Um benchmark que processa 213.068 eventos JSON com tamanho médio de cerca de 900 bytes contra 5 de cada correspondência exata, correspondência de prefixo, correspondência de sufixo, correspondência igual a ignorar maiúsculas e minúsculas, correspondência curinga, correspondência numérica e qualquer coisa menos correspondência governa e conta as partidas, produz o seguinte em um MacBook 2019:
Os eventos são processados a mais de 220 mil/segundo, exceto:
Aqui estão algumas sugestões sobre regras de processamento e eventos:
Considerando o desempenho, o Ruler é sensível aos itens abaixo, portanto, ao projetar o esquema do seu evento e regra, aqui estão algumas sugestões:
Consulte CONTRIBUINDO para obter mais informações.
Este projeto está licenciado sob a licença Apache-2.0. Consulte LICENÇA para obter mais informações.