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 มี 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" } ]
}
การจับคู่แบบ Equals-ignore-case จะใช้ได้กับฟิลด์ค่าสตริงเท่านั้น
{
"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
primitive ของ Java ซึ่งใช้มาตรฐาน 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" } ]
}
}
ตามตัวอย่างข้างต้นแสดง ไม้บรรทัดจะพิจารณากฎที่จะจับคู่หากฟิลด์ ทั้งหมด ที่มีชื่อในกฎตรงกัน และจะพิจารณาฟิลด์ที่จะจับคู่หากค่าฟิลด์ ใด ๆ ที่ให้มาตรงกัน กล่าวคือ ไม้บรรทัดได้ใช้ตรรกะ "และ" ในทุกฟิลด์โดยค่าเริ่มต้น โดยไม่ต้องใช้ "And" ดั้งเดิม
มีสองวิธีในการเข้าถึงเอฟเฟกต์ "หรือ":
ค่าดั้งเดิม "$or" เพื่อให้ลูกค้าอธิบายความสัมพันธ์ "Or" ระหว่างฟิลด์ต่างๆ ในกฎได้โดยตรง
ไม้บรรทัดจะรับรู้ความสัมพันธ์ "หรือ" ก็ต่อ เมื่อกฎตรงตามเงื่อนไขด้านล่าง ทั้งหมด :
/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" ] }
]
}
}
"Or" มี "และ" อยู่ข้างใน
// 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" ]
}
}
อ้างถึง /src/test/data/normalRulesWithOrWording.json
สำหรับตัวอย่างเพิ่มเติมที่ Ruler แยกวิเคราะห์ "$or" เป็นชื่อฟิลด์ปกติ
คำหลัก "$or" เป็นความสัมพันธ์แบบ "Or" ไม่ควรได้รับการออกแบบให้เป็นช่องปกติทั้งในเหตุการณ์และกฎ Ruler รองรับกฎเดิมโดยที่ "$or" ถูกแยกวิเคราะห์เป็นชื่อฟิลด์ปกติเพื่อรักษาความเข้ากันได้แบบย้อนหลัง และให้เวลาสำหรับทีมในการโยกย้ายการใช้งาน "$or" แบบเดิมออกจากกิจกรรมและกฎของพวกเขาเป็นชื่อไฟล์ปกติ การใช้ "$or" แบบผสมเป็น "Or" ดั้งเดิม และ "$or" เป็นชื่อฟิลด์ปกติไม่ได้รับการสนับสนุนโดยเจตนาโดย Ruler เพื่อหลีกเลี่ยงไม่ให้เกิดความคลุมเครือที่น่าอึดอัดใจอย่างยิ่งใน "$or"
มีสองวิธีในการใช้ไม้บรรทัด คุณสามารถคอมไพล์กฎหลายข้อลงใน "Machine" จากนั้นใช้เมธอด rulesForEvent()
หรือเมธอด rulesForJSONEvent()
อย่างใดอย่างหนึ่งเพื่อตรวจสอบว่ากฎข้อใดตรงกับเหตุการณ์ใดๆ ความแตกต่างระหว่างสองวิธีนี้มีการอธิบายไว้ด้านล่าง การสนทนานี้จะใช้ rulesForEvent()
โดยทั่วไป ยกเว้นในกรณีที่ความแตกต่างมีความสำคัญ
หรือคุณสามารถใช้วิธีการบูลีนแบบคงที่วิธีเดียวเพื่อพิจารณาว่าแต่ละเหตุการณ์ตรงกับกฎเฉพาะหรือไม่
มีเมธอดบูลีนแบบคงที่เดียว Ruler.matchesRule(event, rule)
- อาร์กิวเมนต์ทั้งสองระบุเป็นสตริง JSON
หมายเหตุ: มีวิธีที่เลิกใช้แล้วอีกวิธีหนึ่งที่เรียกว่า Ruler.matches(event, rule)
ซึ่งไม่ควรใช้เนื่องจากผลลัพธ์ไม่สอดคล้องกับ rulesForJSONEvent()
และ rulesForEvent()
ดูเอกสารประกอบเกี่ยวกับ Ruler.matches(event, rule)
สำหรับรายละเอียด
เวลาจับคู่ไม่ขึ้นอยู่กับจำนวนกฎ นี่เป็นตัวเลือกที่ดีที่สุดหากคุณมีกฎที่เป็นไปได้หลายกฎที่คุณต้องการเลือก และโดยเฉพาะอย่างยิ่งหากคุณมีวิธีจัดเก็บเครื่องที่คอมไพล์แล้ว
เวลาที่ตรงกันจะได้รับผลกระทบจากระดับของการไม่กำหนดซึ่งเกิดจากกฎตัวแทนและกฎอื่นๆ ยกเว้นไวด์การ์ด ประสิทธิภาพลดลงเมื่อคำนำหน้ากฎตัวแทนมีจำนวนเพิ่มมากขึ้นซึ่งตรงกับเหตุการณ์กรณีที่เลวร้ายที่สุดทางทฤษฎี เพื่อหลีกเลี่ยงปัญหานี้ กฎไวด์การ์ดที่เกี่ยวข้องกับฟิลด์เหตุการณ์เดียวกันควรหลีกเลี่ยงคำนำหน้าทั่วไปที่นำไปสู่อักขระไวด์การ์ดตัวแรก หากจำเป็นต้องใช้คำนำหน้าทั่วไป ให้ใช้จำนวนอักขระตัวแทนขั้นต่ำและจำกัดลำดับอักขระซ้ำที่เกิดขึ้นหลังจากอักขระตัวแทน MachineComplexityEvaluator สามารถใช้เพื่อประเมินเครื่องจักรและกำหนดระดับของการไม่กำหนด หรือ "ความซับซ้อน" (เช่น จำนวนคำนำหน้ากฎไวด์การ์ดที่ตรงกับเหตุการณ์กรณีที่แย่ที่สุดทางทฤษฎี) ต่อไปนี้คือจุดข้อมูลบางส่วนที่แสดงประสิทธิภาพโดยทั่วไปที่ลดลงสำหรับการเพิ่มคะแนนความซับซ้อน
สิ่งสำคัญคือต้องจำกัดความซับซ้อนของเครื่องจักรเพื่อปกป้องแอปพลิเคชันของคุณ มีกลยุทธ์ที่แตกต่างกันอย่างน้อยสองกลยุทธ์ในการจำกัดความซับซ้อนของเครื่องจักร อันไหนที่สมเหตุสมผลมากกว่าอาจขึ้นอยู่กับใบสมัครของคุณ
กลยุทธ์ #1 มีความเหมาะสมมากกว่าในการวัดความซับซ้อนที่แท้จริงของเครื่องจักรที่มีกฎทั้งหมด เมื่อเป็นไปได้ควรใช้กลยุทธ์นี้ ข้อเสียคือ สมมติว่าคุณมีระนาบควบคุมที่อนุญาตให้สร้างกฎได้ครั้งละหนึ่งกฎ จนถึงจำนวนมากก็ได้ จากนั้นสำหรับการดำเนินการควบคุม Plane แต่ละรายการ คุณต้องโหลดกฎที่มีอยู่ทั้งหมดเพื่อทำการตรวจสอบ นี่อาจมีราคาแพงมาก นอกจากนี้ยังเสี่ยงต่อสภาพการแข่งขันอีกด้วย ยุทธศาสตร์ที่ 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
Machine เป็นเพียง GenericMachine
API อ้างถึงประเภททั่วไปว่า "ชื่อ" ซึ่งสะท้อนถึงประวัติ: เวอร์ชัน String ถูกสร้างขึ้นก่อน และสตริงที่จัดเก็บและส่งคืนถือเป็นชื่อกฎ
เพื่อความปลอดภัย ประเภทที่ใช้กับกฎ "ชื่อ" ควรจะไม่เปลี่ยนรูป หากคุณเปลี่ยนเนื้อหาของวัตถุในขณะที่ถูกใช้เป็นชื่อกฎ อาจทำให้การทำงานของไม้บรรทัดหยุดลง
ตัวสร้าง GenericMachine และ Machine สามารถเลือกยอมรับอ็อบเจ็กต์ GenericMachineConfiguration ได้ ซึ่งจะแสดงตัวเลือกการกำหนดค่าต่อไปนี้
ค่าเริ่มต้น: false โดยปกติแล้ว NameStates จะถูกนำมาใช้ซ้ำสำหรับลำดับย่อยและรูปแบบคีย์ที่กำหนด หากมีการเพิ่มลำดับย่อยและรูปแบบคีย์นี้ไว้ก่อนหน้านี้ หรือหากมีการเพิ่มรูปแบบสำหรับลำดับย่อยของคีย์ที่กำหนดแล้ว ดังนั้น ตามค่าเริ่มต้น การใช้ NameState ซ้ำจึงถือเป็นโอกาส แต่ด้วยการตั้งค่าแฟล็กนี้เป็นจริง การใช้ NameState ซ้ำจะถูกบังคับให้ใช้ลำดับย่อยของคีย์ ซึ่งหมายความว่ารูปแบบแรกที่ถูกเพิ่มสำหรับลำดับย่อยของคีย์จะใช้ NameState อีกครั้ง หากมีการเพิ่มลำดับย่อยของคีย์นั้นมาก่อน ความหมายแต่ละลำดับย่อยของคีย์มี NameState เดียว วิธีนี้จะปรับปรุงการใช้งานหน่วยความจำแบบทวีคูณในบางกรณี แต่จะส่งผลให้มีการจัดเก็บกฎย่อยเพิ่มเติมใน NameStates แต่ละรายการ ซึ่งบางครั้ง Ruler จะวนซ้ำ ซึ่งอาจทำให้เกิดการถดถอยของประสิทธิภาพรันไทม์เล็กน้อย ค่าเริ่มต้นนี้เป็นเท็จสำหรับความเข้ากันได้แบบย้อนหลัง แต่มีแนวโน้มว่าแอปพลิเคชันทั้งหมดยกเว้นที่มีความอ่อนไหวต่อเวลาแฝงมากที่สุดจะได้รับประโยชน์จากการตั้งค่านี้ให้เป็นจริง
นี่เป็นตัวอย่างง่ายๆ พิจารณา:
machine . addRule ( "0" , "{"key1": ["a", "b", "c"]}" ) ;
รูปแบบ "a" จะสร้าง NameState จากนั้นถึงแม้จะมี moreNameStateReuse=false รูปแบบที่สอง ("b") และรูปแบบที่สาม ("c") จะนำ NameState เดียวกันนั้นกลับมาใช้ใหม่ แต่ให้พิจารณาสิ่งต่อไปนี้แทน:
machine . addRule ( "0" , "{"key1": ["a"]}" ) ;
machine . addRule ( "1" , "{"key1": ["b"]}" ) ;
machine . addRule ( "2" , "{"key1": ["c"]}" ) ;
ตอนนี้ ด้วย moreNameStateReuse=false เราจะได้ NameState สามรายการ เนื่องจากรูปแบบแรกที่พบสำหรับลำดับย่อยของคีย์ในการเพิ่มกฎแต่ละรายการจะสร้าง NameState ใหม่ ดังนั้น "a", "b" และ "c" ต่างก็มี NameStates เป็นของตัวเอง อย่างไรก็ตาม ด้วย moreNameStateReuse=true "a" จะสร้าง NameState ใหม่ จากนั้น "b" และ "c" จะใช้ NameState เดียวกันนี้ซ้ำ ซึ่งทำได้โดยการจัดเก็บว่าเรามี NameState สำหรับลำดับคีย์ย่อย "key1" อยู่แล้ว
โปรดทราบว่าไม่สำคัญว่าแต่ละ addRule จะใช้ชื่อกฎที่แตกต่างกันหรือชื่อกฎเดียวกัน
ทุกรูปแบบของเมธอดนี้มีอาร์กิวเมนต์แรกเหมือนกัน นั่นคือ String ซึ่งระบุชื่อของ Rule และส่งคืนโดย rulesForEvent()
อาร์กิวเมนต์ที่เหลือระบุคู่ชื่อ/ค่า อาจมีการระบุไว้ใน JSON ตามตัวอย่างด้านบน (ผ่าน String, Reader, InputStream หรือ byte[]
) หรือเป็น Map
โดยที่คีย์คือชื่อฟิลด์และ ค่าคือรายการรายการที่ตรงกันที่เป็นไปได้ จากตัวอย่างข้างต้น จะมีคีย์ชื่อ detail.state
ซึ่งค่าจะเป็นรายการที่มี "initializing"
และ "running"
หมายเหตุ: เมธอดนี้ (และ deleteRule()
) ได้รับการซิงโครไนซ์ ดังนั้นอาจมีเพียงเธรดเดียวเท่านั้นที่สามารถอัปเดตเครื่องได้ตลอดเวลา
คุณสามารถเรียก addRule()
ได้หลายครั้งด้วยชื่อเดียวกัน แต่มีรูปแบบชื่อ/ค่าที่แตกต่างกันหลายรูปแบบ ดังนั้นจึงบรรลุความสัมพันธ์ "หรือ" rulesForEvent()
จะส่งคืนชื่อนั้นหากมีรูปแบบใดที่ตรงกัน
ตัวอย่างเช่น สมมติว่าคุณเรียก addRule()
ด้วยชื่อกฎเป็น "R1" และเพิ่มรูปแบบต่อไปนี้:
{
"detail" : {
"c-count" : [ { "numeric" : [ ">" , 0 , "<=" , 5 ] } ]
}
}
จากนั้นให้คุณเรียกมันอีกครั้งด้วยชื่อเดียวกันแต่มีรูปแบบต่างกัน:
{
"detail" : {
"x-limit" : [ { "numeric" : [ "=" , 3.018e2 ] } ]
}
}
หลังจากนี้ rulesForEvent()
จะส่งกลับ "R1" สำหรับค่า c-count
เป็น 2 หรือ ค่า x-limit
301.8
นี่คือภาพสะท้อนของ addRule()
; ในแต่ละกรณี อาร์กิวเมนต์แรกคือชื่อกฎ ซึ่งกำหนดเป็นสตริง อาร์กิวเมนต์ที่ตามมาจะระบุชื่อและค่า และอาจกำหนดในลักษณะเดียวกับ addRule()
หมายเหตุ: เมธอดนี้ (และ addRule()
) ได้รับการซิงโครไนซ์ ดังนั้นอาจมีเธรดเดียวเท่านั้นที่สามารถอัปเดตเครื่องได้ตลอดเวลา
การทำงานของ API นี้อาจเป็นเรื่องละเอียดอ่อน เครื่องจักรจะรวบรวมการแมปรูปแบบชื่อ/ค่ากับชื่อกฎให้เป็นระบบอัตโนมัติที่มีขอบเขตจำกัด แต่จำไม่ได้ว่ารูปแบบใดบ้างที่แมปกับชื่อกฎที่กำหนด ดังนั้นจึงไม่มีข้อกำหนดว่ารูปแบบใน deleteRule()
จะตรงกันทุกประการกับรูปแบบใน addRule()
ที่สอดคล้องกัน ไม้บรรทัดจะค้นหาการจับคู่กับรูปแบบชื่อ/ค่า และดูว่าการจับคู่กับกฎที่มีชื่อที่ให้มาหรือไม่ และหากเป็นเช่นนั้น ให้ลบออก โปรดทราบว่าในขณะที่ทำการเรียก deleteRule()
ที่ไม่ตรงกับการเรียก addRule()
ที่เกี่ยวข้องจะไม่ล้มเหลวและจะไม่ปล่อยให้เครื่องอยู่ในสถานะที่ไม่สอดคล้องกัน อาจทำให้เกิด "ขยะ" สะสมอยู่ในเครื่องได้
ผลที่ตามมาโดยเฉพาะก็คือ หากคุณเรียก addRule()
หลายครั้งด้วยชื่อเดียวกันแต่มีรูปแบบต่างกัน ดังที่แสดงไว้ด้านบนในส่วน กฎและชื่อกฎ คุณจะต้องเรียก deleteRule()
เป็นจำนวนครั้งเท่ากัน โดยที่เหมือนกัน รูปแบบที่เกี่ยวข้อง เพื่อลบการอ้างอิงทั้งหมดไปยังชื่อกฎนั้นออกจากเครื่อง
เมธอดนี้ส่งคืน List
สำหรับ Machine (และ List
สำหรับ GenericMachine) ซึ่งมีชื่อของกฎที่ตรงกับเหตุการณ์ที่ให้ไว้ เหตุการณ์อาจถูกจัดเตรียมให้กับวิธีใดวิธีหนึ่งเป็น String
เดียวที่แสดงถึงรูปแบบ JSON
เหตุการณ์ยังอาจมอบให้กับ rulesForEvent()
ในรูปแบบชุดของสตริงที่สลับชื่อและค่าของฟิลด์ และต้องจัดเรียงคำศัพท์ตามชื่อฟิลด์ นี่อาจเป็น List
หรือ String[]
การระบุเหตุการณ์ใน JSON เป็นแนวทางที่แนะนำและมีข้อดีหลายประการ ประการแรก การเติมรายการสตริงหรืออาร์เรย์ด้วยจำนวนชื่อ/ค่าที่สลับกันตามลำดับที่จัดเรียงตามชื่อนั้นยุ่งยาก และ Ruler ก็ไม่ได้ช่วยอะไร เพียงแต่ทำงานไม่ถูกต้องหากรายการมีโครงสร้างที่ไม่เหมาะสม นอกจากนี้ การแสดงค่าฟิลด์ที่ระบุเป็นสตริงยังต้องเป็นไปตามกฎไวยากรณ์ JSON ดูด้านล่างใต้ การจับคู่ข้อความ JSON
สุดท้ายนี้ เวอร์ชันรายการ/อาร์เรย์ของเหตุการณ์ทำให้ Ruler ไม่สามารถจดจำโครงสร้างอาร์เรย์และให้การจับคู่ที่สอดคล้องกับอาร์เรย์ได้ ตามที่อธิบายไว้ด้านล่างในเอกสารนี้ rulesForEvent(String eventJSON)
API เลิกใช้แล้วเพื่อสนับสนุน rulesForJSONEvent()
โดยเฉพาะ เนื่องจากไม่รองรับการจับคู่ที่สอดคล้องกับอาร์เรย์
rulesForJSONEvent()
ยังมีข้อได้เปรียบตรงที่โค้ดที่เปลี่ยนรูปแบบ JSON ของเหตุการณ์ให้เป็นรายการที่เรียงลำดับนั้นได้รับการจัดทำโปรไฟล์และปรับให้เหมาะสมอย่างกว้างขวาง
ประสิทธิภาพของ rulesForEvent()
และ rulesForJSONEvent()
ไม่ได้ขึ้นอยู่กับจำนวนกฎที่เพิ่มด้วย addRule()
โดยทั่วไป rulesForJSONEvent()
จะเร็วกว่าเนื่องจากการประมวลผลเหตุการณ์ที่ปรับให้เหมาะสม หากคุณดำเนินการประมวลผลเหตุการณ์ของคุณเองและเรียก rulesForEvent()
พร้อมกับรายการชื่อและค่าที่เรียงลำดับไว้ล่วงหน้า นั่นก็จะยังเร็วกว่า แต่คุณอาจไม่สามารถเตรียมรายการเขตข้อมูลได้เร็วเท่ากับที่ rulesForJSONEvent()
ทำ
วิธีนี้จะประมาณจำนวนวัตถุภายในเครื่อง ค่าจะแตกต่างกันไปเมื่อมีการเพิ่มหรือลบกฎเท่านั้น สิ่งนี้มีประโยชน์ในการระบุเครื่องขนาดใหญ่ที่อาจต้องใช้หน่วยความจำจำนวนมาก เนื่องจากวิธีนี้ขึ้นอยู่กับจำนวนของอ็อบเจ็กต์ภายใน จำนวนนี้อาจเปลี่ยนแปลงได้เมื่อมีการเปลี่ยนแปลงภายในไลบรารีของไม้บรรทัด วิธีการนี้จะทำการคำนวณทั้งหมด ณ รันไทม์เพื่อหลีกเลี่ยงการใช้หน่วยความจำและทำให้ผลกระทบของเครื่องกฎขนาดใหญ่แย่ลง การคำนวณนั้นไม่ได้ตั้งใจที่จะปลอดภัยต่อเธรดเพื่อหลีกเลี่ยงการบล็อกการประเมินกฎและการเปลี่ยนแปลงเครื่อง หมายความว่าหากกระบวนการแบบขนานเพิ่มหรือลบออกจากเครื่อง คุณอาจได้รับผลลัพธ์ที่แตกต่างออกไปเมื่อเปรียบเทียบกับเมื่อกระบวนการแบบขนานดังกล่าวเสร็จสมบูรณ์ นอกจากนี้ เนื่องจากไลบรารีทำการปรับปรุงประสิทธิภาพภายในสำหรับบางรูปแบบ (ดู ShortcutTransition.java
สำหรับรายละเอียดเพิ่มเติม) คุณอาจได้รับผลลัพธ์ที่แตกต่างกันไป ขึ้นอยู่กับลำดับที่กฎถูกเพิ่มหรือลบ
หากคุณคิดว่าเหตุการณ์ของคุณเป็นคู่ชื่อ/ค่า แทนที่จะเป็นเอกสารสไตล์ JSON ที่ซ้อนกัน คลาส Patterns
(และคลาสย่อย Range
) อาจมีประโยชน์ในการสร้างกฎ วิธีการคงที่ต่อไปนี้มีประโยชน์
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 );
เมื่อคุณสร้างตัวจับคู่ Patterns
ที่เหมาะสมด้วยวิธีการเหล่านี้แล้ว คุณสามารถใช้วิธีการต่อไปนี้เพื่อเพิ่มหรือลบออกจากเครื่องของคุณได้:
public void addPatternRule ( final String name , final Map < String , List < Patterns >> namevals );
public void deletePatternRule ( final String name , final Map < String , List < Patterns >> namevals );
หมายเหตุ: ข้อควรระวังที่แสดงอยู่ใน deleteRule()
ใช้กับ deletePatternRule()
ด้วยเช่นกัน
ต้องระบุค่าฟิลด์ในกฎในการแสดง JSON กล่าวคือ ค่าสตริงต้องอยู่ใน "เครื่องหมายคำพูด" อนุญาตให้ใช้ค่าที่ไม่มีเครื่องหมายคำพูด เช่น ตัวเลข ( -3.0e5
) และตัวอักษรเฉพาะของ JSON บางตัว ( true
, false
และ null
)
สิ่งนี้สามารถถูกเพิกเฉยได้ทั้งหมด หากมีการระบุกฎให้กับ addRule()
() ในรูปแบบ JSON หรือหากคุณกำลังทำงานกับรูปแบบแทนที่จะเป็นสตริงตามตัวอักษร แต่ถ้าคุณระบุกฎเป็นคู่ชื่อ/ค่า และคุณต้องการระบุว่าฟิลด์ "xyz" ตรงกับสตริง "true" จะต้องแสดงเป็น "xyz", ""true""
ในทางกลับกัน "xyz", "true"
จะจับคู่เฉพาะตัวอักษร JSON เท่านั้น true
Ruler รองรับการจับคู่กฎสำหรับเหตุการณ์ที่มีอาร์เรย์ แต่เฉพาะเมื่อมีการระบุเหตุการณ์ในรูปแบบ JSON เท่านั้น เมื่อเป็นรายการฟิลด์ที่จัดเรียงไว้ล่วงหน้า โครงสร้างอาร์เรย์ในเหตุการณ์จะสูญหายไป ลักษณะการทำงานยังขึ้นอยู่กับว่าคุณใช้ rulesForEvent()
หรือ rulesForJSONEvent
พิจารณาเหตุการณ์ต่อไปนี้
{
"employees" : [
{ "firstName" : "John" , "lastName" : "Doe" } ,
{ "firstName" : "Anna" , "lastName" : "Smith" } ,
{ "firstName" : "Peter" , "lastName" : "Jones" }
]
}
จากนั้นกฎนี้จะตรงกับ:
{ "employees" : { "firstName" : [ "Anna" ] } }
กล่าวคือ โครงสร้างอาร์เรย์ถูก "บดอัด" ออกจากรูปแบบกฎ และออบเจ็กต์ใดๆ ที่มีอยู่จะถูกปฏิบัติเสมือนว่าเป็นค่าของฟิลด์พาเรนต์ สิ่งนี้ใช้ได้กับอาร์เรย์หลายระดับด้วย:
{
"employees" : [
[
{ "firstName" : "John" , "lastName" : "Doe" } ,
{ "firstName" : "Anna" , "lastName" : "Smith" }
] ,
[
{ "firstName" : "Peter" , "lastName" : "Jones" }
]
]
}
ใน Ruler เวอร์ชันก่อนหน้า วิธีการจับคู่แบบเครื่องจักรเพียงอย่างเดียวคือ rulesForEvent()
ซึ่งน่าเสียดายที่จะตรงกับกฎต่อไปนี้ด้วย:
{ "employees" : { "firstName" : [ "Anna" ] , "lastName" : [ "Jones" ] } }
ในการแก้ไข Ruler ได้แนะนำ rulesForJSONEvent()
ซึ่งตามชื่อที่แนะนำ จะจับคู่เฉพาะเหตุการณ์ที่ระบุในรูปแบบ JSON เท่านั้น rulesForJsonEvent()
จะ ไม่ ตรงกับกฎ "Anna"/"Jones" ด้านบน
อย่างเป็นทางการ: rulesForJSONEvent()
จะปฏิเสธที่จะรับรู้การจับคู่ใดๆ ที่มีสองฟิลด์ใดๆ อยู่ภายในออบเจ็กต์ JSON ที่อยู่ในองค์ประกอบที่แตกต่างกันของอาร์เรย์เดียวกัน ในทางปฏิบัติ นี่หมายความว่าจะเป็นไปตามสิ่งที่คุณคาดหวัง
มีคลาสที่รองรับ com.amazon.fsm.ruler.RuleCompiler
ประกอบด้วยเมธอดชื่อ check()
ซึ่งยอมรับข้อกำหนดกฎ JSON และส่งกลับค่าสตริง ซึ่งหากเป็นค่าว่าง หมายความว่ากฎนั้นถูกต้องตามหลักไวยากรณ์ หากค่าที่ส่งคืนไม่ใช่ค่า Null จะมีข้อความแสดงข้อผิดพลาดที่มนุษย์สามารถอ่านได้ซึ่งอธิบายปัญหา
เพื่อความสะดวก ยังมีเมธอดชื่อ compile()
ซึ่งทำงานเหมือนกับ check()
แต่ส่งสัญญาณข้อผิดพลาดโดยการโยน IOException และเมื่อสำเร็จก็จะส่งคืน Map
ในรูปแบบที่ addRule()
ของเครื่อง addRule()
วิธีการคาดหวัง เนื่องจากคลาส Machine ใช้สิ่งนี้ภายใน วิธีการนี้อาจช่วยประหยัดเวลาได้
เมื่อ Ruler คอมไพล์คีย์ มันจะใช้จุด ( .
) เป็นอักขระในการรวม ซึ่งหมายความว่าจะรวบรวมกฎสองข้อต่อไปนี้เพื่อเป็นตัวแทนภายในเดียวกัน
## has no dots in keys
{ "detail" : { "state" : { "status" : [ "running" ] } } }
## has dots in keys
{ "detail" : { "state.status" : [ "running" ] } }
นอกจากนี้ยังหมายความว่ากฎเหล่านี้จะจับคู่กับสองเหตุการณ์ต่อไปนี้:
## has no dots in keys
{ "detail" : { "state" : { "status" : "running" } } }
## has dots in keys
{ "detail" : { "state.status" : "running" } }
ลักษณะการทำงานนี้อาจเปลี่ยนแปลงไปในเวอร์ชันต่อๆ ไป (เพื่อหลีกเลี่ยงความสับสน) และไม่ควรเชื่อถือ
เราวัดประสิทธิภาพของ Ruler โดยการรวบรวมกฎหลายข้อไว้ในเครื่องและจับคู่เหตุการณ์ที่ระบุเป็นสตริง JSON
เกณฑ์มาตรฐานที่ประมวลผลเหตุการณ์ JSON 213,068 รายการที่มีขนาดเฉลี่ยประมาณ 900 ไบต์เทียบกับ 5 รายการแต่ละรายการที่ตรงกันทุกประการ การจับคู่คำนำหน้า การจับคู่ส่วนต่อท้าย การจับคู่เท่ากับละเว้นการจับคู่ตัวพิมพ์ใหญ่ การจับคู่ไวด์การ์ด การจับคู่ตัวเลข และการจับคู่อะไรก็ได้ยกเว้นการจับคู่ กฎและนับการแข่งขันทำให้ได้ผลลัพธ์ต่อไปนี้บน MacBook ปี 2019:
เหตุการณ์ได้รับการประมวลผลที่มากกว่า 220K/วินาที ยกเว้น:
คำแนะนำบางประการเกี่ยวกับกฎการประมวลผลและกิจกรรม:
จากการพิจารณาประสิทธิภาพ Ruler จะละเอียดอ่อนในรายการด้านล่าง ดังนั้น เมื่อคุณออกแบบสคีมาของเหตุการณ์และกฎของคุณ ต่อไปนี้เป็นคำแนะนำบางส่วน:
ดูการมีส่วนร่วมสำหรับข้อมูลเพิ่มเติม
โครงการนี้ได้รับอนุญาตภายใต้ใบอนุญาต Apache-2.0 ดูใบอนุญาตสำหรับข้อมูลเพิ่มเติม