Computer >> คอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Redis

คุณอาจจะคิดเกี่ยวกับ Redis Streams ผิด

ฉันมีความผิดโดยส่วนตัวในการอธิบายสตรีมในทางที่ผิด ฉันได้กำหนดให้มันเป็น "ชุดขององค์ประกอบที่คล้ายแฮชแมป เรียงลำดับตามเวลาภายใต้คีย์เดียว" สิ่งนี้ไม่ถูกต้อง . บิตสุดท้ายเกี่ยวกับเวลาและคีย์ใช้ได้ แต่บิตแรกผิดทั้งหมด

มาดูกันว่าเหตุใดสตรีมจึงเข้าใจผิดและมีพฤติกรรมอย่างไร เราจะประเมินข้อดีและข้อเสียของความเข้าใจผิดนี้และผลกระทบที่มีต่อซอฟต์แวร์ของคุณ สุดท้าย เราจะตรวจสอบรูปแบบที่ไม่ชัดเจนซึ่งใช้ประโยชน์จากคุณสมบัติที่ไม่ค่อยมีใครรู้จักของ Redis Streams

พื้นหลัง

ก่อนอื่น มาดูคำสั่ง XADD กันก่อน เพราะนี่คือจุดเริ่มต้นของความเข้าใจผิด ลายเซ็นคำสั่งตามที่ระบุไว้ในเอกสารทางการของ redis.io มีลักษณะดังนี้:

XADD key ID field value [field value ...]

คีย์ เป็นตัวอธิบาย id เป็นการประทับเวลา/คำสั่งผสมตามลำดับสำหรับรายการใหม่ แต่ในความเป็นจริง แทบจะทุกครั้ง * เพื่อระบุการสร้างอัตโนมัติ รากของความสับสนนี้เริ่มต้นด้วยฟิลด์และมูลค่า หากคุณดูที่ลายเซ็นคำสั่งสำหรับ HSET คุณจะเห็นรูปแบบที่ค่อนข้างคล้ายกัน:

HSET key field value [field value ...]

ลายเซ็นสำหรับสองคำสั่งเป็นเพียงอาร์กิวเมนต์เดียวที่ปิด และอาร์กิวเมนต์ใน XADD นั้นมักจะเป็น * หน้าตาค่อนข้างคล้ายกัน ใช้คำเหมือน ต้องเหมือนกันใช่ไหม

ตกลง. เพื่อดำเนินการต่อปัญหา ให้แยก Redis และดูว่าภาษาโปรแกรมจัดการกับคู่ค่าฟิลด์อย่างไร โดยส่วนใหญ่ ไม่ว่าภาษาจะเป็นแบบใด วิธีการแสดงค่าฟิลด์ที่ชัดเจนที่สุดคือชุด (ไม่มีการซ้ำ) ของฟิลด์ที่สัมพันธ์กับค่า—บางภาษาจะรักษาลำดับของฟิลด์และบางฟิลด์จะไม่ทำ มาดูการเปรียบเทียบข้ามภาษากัน:

แมปนี้จับคู่กับแฮช Redis ได้อย่างดี ทั้งหมดนี้สามารถระบุคุณสมบัติของแฮช ซึ่งไม่มีลำดับและไม่มีการทำซ้ำ อาร์เรย์ PHP, Python dicts และแผนที่ JavaScript สามารถกำหนดลำดับของฟิลด์ได้ แต่ถ้าคุณทำงานกับแฮชใน Redis ก็ไม่สำคัญ คุณเพียงแค่ต้องรู้ว่าคุณไม่สามารถพึ่งพาลำดับนี้ในระดับแอปพลิเคชัน .

สำหรับคนส่วนใหญ่ ข้อสรุปโดยธรรมชาติคือเนื่องจากลายเซ็นคำสั่งของ HSET และ XADD มีความสัมพันธ์กัน จึงอาจมีความสัมพันธ์ที่คล้ายคลึงกันในทางกลับกัน ที่ระดับโปรโตคอลใน RESP2 ทั้งคู่จะถูกส่งกลับเป็นอาร์เรย์ RESP แบบอินเตอร์ลีฟ สิ่งนี้ยังคงดำเนินต่อไปใน RESP3 เวอร์ชันแรกๆ ซึ่งการตอบสนองของ HGETALL และ XREAD เป็นแผนที่ทั้งคู่ (เพิ่มเติมในภายหลัง)

แมลงทำให้ฉันเปลี่ยนใจ

โดยปกติฉันจะเขียนโค้ดใน JavaScript และบางครั้งเป็น Python ในฐานะที่เป็นคนที่สื่อสารเกี่ยวกับ Redis ฉันต้องเข้าถึงผู้คนให้มากที่สุดเท่าที่จะเป็นไปได้ และทั้งสองภาษานี้ค่อนข้างเข้าใจกันดี ดังนั้น เปอร์เซ็นต์ที่สูงของโลกของนักพัฒนาจะเข้าใจถึงตัวอย่างหรือโค้ดตัวอย่างในภาษาใดภาษาหนึ่ง เมื่อเร็ว ๆ นี้ ฉันได้มีโอกาสพูดคุยในการประชุม PHP และจำเป็นต้องแปลงโค้ด Python ที่มีอยู่เป็น PHP ฉันใช้ PHP เปิดและปิดมาเกือบ 20 ปีแล้ว แต่ฉันไม่เคยสนใจมาก่อน การสาธิตเฉพาะนั้นไม่เหมาะกับการดำเนินการตามสไตล์ mod_php ดังนั้นฉันจึงใช้ swoole และการดำเนินการร่วม (ในบันทึกด้านข้าง การรู้สึกสบายใจในโลกของ JavaScript swoole ทำให้ PHP คุ้นเคยมากสำหรับฉัน) ไลบรารี่ล้าสมัยและจำเป็นต้องส่งคำสั่งดิบ Redis โดยไม่ต้องให้ความช่วยเหลือเกี่ยวกับไลบรารีของไคลเอ็นต์จริงในการถอดรหัสการส่งคืนในระดับสูง โดยทั่วไป การส่งคำสั่ง Redis แบบ Raw และถอดรหัสผลลัพธ์จะให้การควบคุมที่มากขึ้นเล็กน้อยและไม่ยุ่งยาก

ดังนั้น เมื่อส่งคำสั่งไปยัง XADD ฉันกำลังสร้างส่วนค่าฟิลด์และมีข้อผิดพลาดแบบแยกส่วน ส่งผลให้ฉันส่งบางอย่างไปโดยไม่ได้ตั้งใจ:

XADD myKey * field0 value1 field0 value2 field2 value3

แทนที่จะส่งฟิลด์และค่าที่สัมพันธ์กัน (field0 เป็น value0 เป็นต้น)

ต่อมาในโค้ด ฉันได้ใส่ผลลัพธ์ของ XREAD ลงในอาร์เรย์ PHP ที่มีอยู่ (ซึ่งเชื่อมโยงกัน) ในลูป โดยกำหนดแต่ละฟิลด์เป็นคีย์โดยมีค่าแต่ละค่า และข้ามสิ่งใดๆ ที่ตั้งค่าไว้แล้ว ดังนั้นฉันจึงเริ่มต้นด้วยอาร์เรย์ดังนี้:

array(1) {
  ["foo"]=>
  string(3) "bar"
}

และลงเอยด้วยอาร์เรย์ดังนี้:

array(3) {
  ["foo"]=>
  string(3) "bar"
  ["field0"]=>
  string(6) "value1"
  ["field2"]=>
  string(6) "value3"
}

ฉันไม่สามารถเข้าใจได้ว่ามันเป็นไปได้อย่างไร ฉันสามารถติดตามข้อผิดพลาดได้อย่างรวดเร็วว่าทำไม value1 ได้รับมอบหมายให้ field0 (ข้อผิดพลาดที่กล่าวถึงข้างต้นใน XADD ของฉัน) แต่ทำไมไม่ใช่ field0 กำหนดเป็น value2 ? ใน HSET ลักษณะการทำงานสำหรับการเพิ่มฟิลด์นั้นโดยทั่วไปแล้ว ปรับปรุง หากมีฟิลด์ ให้อัปเดตฟิลด์ มิฉะนั้น ให้เพิ่มฟิลด์และตั้งค่า

ตรวจสอบบันทึกของ MONITOR และเล่นค่าซ้ำ ฉันรัน XREAD ดังนี้:

> XREAD STREAMS myKey 0
1) 1) "myKey"
   2) 1) 1) "1592409586907-0"
         2) 1) "field0"
            2) "value1"
            3) "field0"
            4) "value2"
            5) "field2"
            6) "value3"

มีการทำซ้ำและบันทึก ไม่เสียใจ นอกจากนี้คำสั่งซื้อจะถูกเก็บรักษาไว้ นี้ไม่เหมือนแฮช!

เมื่อนึกถึงสิ่งนี้โดยใช้ JSON เป็นการประมาณ ฉันคิดว่ารายการสตรีมมีลักษณะดังนี้:

{
  "field1"  : "value",
  "field2"  : "value",
  "field3"  : "value"
}

แต่จริงๆ แล้วหน้าตาเป็นแบบนี้:

[
  ["field1", "value"],
  ["field2", "value"],
  ["field3", "value"]
]

สิ่งนี้หมายความว่าอย่างไร

ข่าวดี

หากคุณมีโค้ดที่ใช้งานได้กับ Streams อยู่ในขณะนี้ และคุณคิดว่ารายการต่างๆ นั้นเหมือนกับแฮชแมป คุณก็ไม่มีปัญหา คุณเพียงแค่ต้องคอยดูจุดบกพร่องที่อาจเกิดขึ้นเกี่ยวกับการใส่ฟิลด์ที่ซ้ำกัน เนื่องจากสิ่งเหล่านี้อาจไม่เป็นไปตามที่คุณคาดหวังในแอปพลิเคชันที่กำหนด สิ่งนี้อาจใช้ไม่ได้ในทุกสถานการณ์หรือไลบรารีไคลเอนต์ แต่แนวทางปฏิบัติที่ดีคือการจัดหาโครงสร้างข้อมูลที่ไม่อนุญาตให้ทำซ้ำ (ดูด้านบน) และทำให้เป็นอนุกรมนี้เป็นอาร์กิวเมนต์เมื่อส่งไปยัง XADD ช่องที่ไม่ซ้ำและช่องที่ไม่ซ้ำ

ข่าวร้าย

ไม่ใช่ทุกไลบรารีและเครื่องมือของไคลเอ็นต์ที่จะทำให้ถูกต้อง ไลบรารีไคลเอนต์ระดับต่ำ (โดยย่อ:node_redis, hiredis) ไม่ได้ทำอะไรมากเท่ากับการเปลี่ยนเอาต์พุตจาก Redis เป็นโครงสร้างภาษา ห้องสมุดระดับสูงกว่าอื่นๆ ทำ สรุปการส่งคืน Redis จริงเป็นโครงสร้างภาษา—คุณควรตรวจสอบเพื่อดูว่าไลบรารีที่คุณเลือกกำลังทำสิ่งนี้อยู่หรือไม่ และมีปัญหาหรือไม่ ไลบรารีระดับสูงกว่าบางอันใช้งานได้ตั้งแต่เริ่มต้น (stackexchange.redis) ดังนั้นความรุ่งโรจน์จึงเกิดขึ้น

ส่วนอื่นที่ค่อนข้างแย่:หากคุณเป็นผู้เริ่มใช้ RESP3 ในช่วงต้น คุณอาจเคยมีประสบการณ์ XREAD / XREADGROUP ส่งคืนประเภทแผนที่ RESP3 จนถึงต้นเดือนเมษายน Redis 6 เวอร์ชันที่ยังไม่พัฒนาได้ส่งคืนแผนที่ซ้ำๆ อย่างสับสนเมื่ออ่าน Streams โชคดีที่สิ่งนี้ได้รับการแก้ไขแล้ว และ Redis 6 เวอร์ชัน GA ซึ่งเป็นครั้งแรกที่คุณควรใช้ RESP3 ในการผลิตจริง ได้รับการจัดส่งพร้อมผลตอบแทนที่เหมาะสมสำหรับ XREAD / XREADGROUP

ส่วนที่สนุก

เนื่องจากฉันได้อธิบายไปแล้วว่าคุณอาจคิดผิดเกี่ยวกับ Streams อย่างไร ลองมาคิดกันสักนิดว่าคุณจะใช้ประโยชน์จากโครงสร้างที่เข้าใจผิดมาก่อนได้อย่างไร

ใช้ความหมายในการสั่งซื้อในรายการสตรีม

คุณมีเวกเตอร์สามตัวให้เล่นในรูปแบบนี้ ลองนึกภาพการจัดเก็บเส้นทางสำหรับกราฟิกแบบเวกเตอร์ รายการสตรีมแต่ละรายการจะเป็นรูปหลายเหลี่ยมหรือเส้นทางที่ไม่ซ้ำกัน และฟิลด์และค่าต่างๆ จะเป็นพิกัด ตัวอย่างเช่น นำส่วนของ SVG นี้:

<polyline points="50,150 50,200 200,200 200,100">

สามารถพูดได้ดังนี้:

> XADD mySVG * 50 150 50 200 200 200 200 100

รูปร่างเพิ่มเติมแต่ละรายการจะเป็นอีกรายการหนึ่งในคีย์เดียวกัน หากคุณพยายามทำสิ่งนี้กับ Redis Hash คุณจะมีเพียงสองพิกัดและไม่มีการรับประกันคำสั่งซื้อ เป็นที่ยอมรับว่าคุณสามารถทำได้กับสิ่งต่าง ๆ เช่นบิตฟิลด์ แต่คุณจะสูญเสียความยืดหยุ่นมากมายในแง่ของความยาวและขนาดพิกัด ด้วย Streams คุณอาจทำสิ่งที่เรียบร้อยด้วยการประทับเวลาเพื่อแสดงชุดของรูปร่างที่ปรากฏเมื่อเวลาผ่านไป

สร้างชุดลำดับเวลาของรายการตามลำดับ

อันนี้ต้องใช้แฮ็คเล็ก ๆ แต่สามารถให้ฟังก์ชั่นมากมาย ลองนึกภาพว่าคุณกำลังเก็บลำดับของข้อมูลที่คล้ายอาร์เรย์ อาร์เรย์ของอาร์เรย์อย่างมีประสิทธิภาพ—ใน JSON คุณสามารถคิดได้ว่ามีลักษณะดังนี้:

[
  ["A New Hope", "The Empire Strikes Back", "Return of the Jedi"],
  ["The Phantom Menace", "Attack of the Clones", "Revenge of the Sith"],
  ["The Force Awakens", "The Last Jedi", "The Rise of Skywalker"]
]

คุณสามารถอธิบายสิ่งนี้เป็นชุดของรายการสตรีมด้วยความแตกต่างเพียงเล็กน้อย:คุณต้องตรวจสอบให้แน่ใจว่าจำนวนองค์ประกอบ (หลอก) ในรายการภายในของคุณไม่แปลก หากเป็นเรื่องแปลก คุณจะต้องบันทึกว่าด้วยวิธีใดวิธีหนึ่ง นี่คือวิธีที่ฉันทำโดยใช้สตริงว่าง:

> XADD starwars * "A New Hope" "The Empire Strikes Back" "Return of the Jedi" ""
"1592427370458-0"
> XADD starwars * "The Phantom Menace" "Attack of the Clones" "Revenge of the Sith" ""
"1592427393492-0"
> XADD starwars * "The Force Awakens" "The Last Jedi" "The Rise of Skywalker" ""
"1592427414475-0"
> XREAD streams starwars 0
1# "starwars" => 
   1) 1) "1592427370458-0"
      2) 1) "A New Hope"
         2) "The Empire Strikes Back"
         3) "Return of the Jedi"
         4) ""
   2) 1) "1592427393492-0"
      2) 1) "The Phantom Menace"
         2) "Attack of the Clones"
         3) "Revenge of the Sith"
         4) ""
   3) 1) "1592427414475-0"
      2) 1) "The Force Awakens"
         2) "The Last Jedi"
         3) "The Rise of Skywalker"
         4) ""

คุณได้รับมากในรูปแบบนี้โดยมีค่าใช้จ่าย (เล็กน้อย) ในการกรองสตริงที่มีความยาว 0 ออก

สตรีมเป็นแคชการแบ่งหน้า

สิ่งที่ยุ่งยากที่คุณเห็นบ่อยคือรายการสินค้าบนเว็บไซต์ (อีคอมเมิร์ซ กระดานข้อความ ฯลฯ) โดยทั่วไปแคชจะถูกแคช แต่ฉันเคยเห็นผู้คนพยายามค้นหาวิธีที่ดีที่สุดในการแคชข้อมูลประเภทนี้ คุณแคชชุดผลลัพธ์ทั้งหมดเป็นชุดที่จัดเรียงแล้วและแบ่งหน้าออกด้วย ZRANGE หรือคุณเก็บทั้งหน้า ที่คีย์สตริง? ทั้งสองวิธีมีข้อดีและข้อเสีย

ผลปรากฏว่า Streams ใช้งานได้จริง ยกตัวอย่าง รายชื่ออีคอมเมิร์ซ คุณมีชุดของรายการ โดยแต่ละรายการมีรหัส รายการเหล่านั้นมีการระบุไว้ในชุดการเรียงลำดับที่แน่นอนซึ่งมักจะมีการกลับรายการ (A-Z, Z-A, ต่ำไปสูง, สูงไปต่ำ, คะแนนสูงสุดไปต่ำสุด, คะแนนต่ำสุดไปสูงสุด ฯลฯ)

ในการสร้างแบบจำลองข้อมูลประเภทนี้ในสตรีม คุณจะต้องกำหนดขนาด "กลุ่ม" ที่เฉพาะเจาะจงและกำหนดให้เป็นรายการ ทำไมชิ้นและไม่เต็มหน้าผลลัพธ์ที่รายการ? วิธีนี้ช่วยให้คุณมีหน้าที่มีขนาดต่างกันในการแบ่งหน้าของคุณ (เช่น 10 รายการต่อหน้าอาจสร้างจาก 2 ชิ้นละ 5 ชิ้น ในขณะที่ 25 ชิ้นต่อหน้าจะเป็น 5 ชิ้นละ 5 ชิ้น) แต่ละรายการจะมีฟิลด์ที่แมปกับรหัสผลิตภัณฑ์ และค่าจะเป็นข้อมูลผลิตภัณฑ์ ดูตัวอย่างแบบง่ายนี้ด้วยขนาดก้อนที่ต่ำเกินจริง:

คุณอาจจะคิดเกี่ยวกับ Redis Streams ผิด

เมื่อคุณต้องการดึงค่าที่แคชไว้ คุณจะต้องเรียกใช้ XRANGE โดยตั้งค่าอาร์กิวเมนต์ COUNT เป็นจำนวนชิ้นที่ประกอบขึ้นเป็นหน้าผลลัพธ์สำหรับอินเทอร์เฟซของคุณ ดังนั้น หากคุณต้องการได้หน้าแรกของสี่รายการ คุณจะเรียกใช้:

> XRANGE listcache - + COUNT 2
1) 1) "0-1"
   2) 1) "123"
      2) "{ \"Red Belt\", ... }"
      3) "456"
      4) "{ \"Yellow Belt\", ... }"
2) 1) "0-2"
   2) 1) "789"
      2) "{ \"Blue Belt\", ... }"
      3) "012"
      4) "{ \"Purple Belt\", ... }"

ในการรับหน้าที่ 2 ของ 4 รายการ คุณจะต้องระบุ Stream ID ขอบเขตล่างที่เพิ่มขึ้น 1 ในกรณีของเรา ขอบเขตล่างจะเป็น 0-2 .

> XRANGE listcache 0-3 + COUNT 2
1) 1) "0-3"
   2) 1) "345"
      2) "{ \"Black Belt\", ... }"
      3) "678"
      4) "{ \"Red Boa\", ... }"
2) 1) "0-4"
   2) 1) "901"
      2) "{ \"Yellow Boa\", ... }"
      3) "234"
      4) "{ \"Green Belt\", ... }"

สิ่งนี้ให้ข้อได้เปรียบด้านความซับซ้อนในการคำนวณมากกว่า Sorted Sets หรือ Lists เนื่องจาก XRANGE มีประสิทธิภาพ O(1) ในการใช้งานนี้ แต่มีบางสิ่งที่ต้องคำนึงถึง:

  • XREVRANGE สามารถใช้การกลับรายการได้ แต่จะกลับลำดับของ "ชิ้น" เท่านั้น ภายในแต่ละส่วน คุณจะต้องย้อนกลับลำดับในตรรกะของแอปพลิเคชัน ซึ่งน่าจะค่อนข้างไม่สำคัญ
  • การค้นหาส่วนต่างๆ ของรายการนั้น “ฟรี” หากคุณตั้งค่า Stream ID แบบเส้นตรงด้วยตนเอง ดังนั้นกลุ่มที่ 1 จึงเป็น Stream ID 0-1 , ชิ้นที่ 2 คือ Stream ID 0-2 และอื่นๆ โปรดทราบว่าคุณต้องเริ่มด้วย 1 แทนที่จะเป็น 0 เนื่องจากคุณไม่สามารถเพิ่มรายการสตรีมที่ 0-0

เช่นเดียวกับคีย์อื่นๆ คุณสามารถใช้การหมดอายุเพื่อจัดการระยะเวลาที่สตรีมจะอยู่ได้ ตัวอย่างวิธีการทำได้คือใน stream-row-cache

ฉันหวังว่าโพสต์นี้จะช่วยให้คุณมีบริบทเพิ่มเติมเกี่ยวกับวิธีการทำงานของ Streams และวิธีที่คุณสามารถใช้ประโยชน์จากคุณสมบัติที่ไม่รู้จักส่วนใหญ่เหล่านี้ของ Streams ในแอปพลิเคชันของคุณ