คุณเคยได้ยินว่า Redis มีภาษาสคริปต์ฝังตัว แต่ยังไม่ได้ลองใช้เลยใช่ไหม นี่คือทัวร์ชมสิ่งที่คุณต้องเข้าใจเพื่อใช้พลังของ Lua กับเซิร์ฟเวอร์ Redis ของคุณ
สวัสดี ลุย!
สคริปต์ Redis Lua ตัวแรกของเราเพิ่งคืนค่าโดยไม่ต้องโต้ตอบกับ Redis ในทางที่มีความหมาย:
local msg = "Hello, world!"
return msg
นี้เป็นเรื่องง่ายตามที่ได้รับ บรรทัดแรกตั้งค่าตัวแปรภายในเครื่องด้วยข้อความของเรา และบรรทัดที่สองคืนค่าจากเซิร์ฟเวอร์ Redis ไปยังไคลเอ็นต์ บันทึกไฟล์นี้ในเครื่องเป็น hello.lua
และเรียกใช้ดังนี้:
redis-cli --eval hello.lua
มีปัญหาการเชื่อมต่อหรือไม่
redis-cli
นี้ ตัวอย่างถือว่าคุณกำลังเรียกใช้ Redisserver ในเครื่อง หากคุณกำลังทำงานกับเซิร์ฟเวอร์ระยะไกลเช่น RedisGreen คุณจะต้องระบุข้อมูลโฮสต์และพอร์ต ค้นหา เชื่อมต่อ ปุ่มบนแดชบอร์ด RedisGreen ของคุณเพื่อคัดลอกข้อมูลการเข้าสู่ระบบสำหรับเซิร์ฟเวอร์ของคุณอย่างรวดเร็ว
ดูเพิ่มเติม:ไม่สามารถเชื่อมต่อกับ Redis ที่ 127.0.0.1:6379:การเชื่อมต่อถูกปฏิเสธ
การดำเนินการนี้จะพิมพ์ว่า "สวัสดีชาวโลก!" อาร์กิวเมนต์แรกของEVAL
เป็นสคริปต์ lua ที่สมบูรณ์ — ที่นี่เราใช้ cat
คำสั่งอ่านสคริปต์จากไฟล์ อาร์กิวเมนต์ที่สองคือจำนวน Rediskeys ที่สคริปต์จะเข้าถึง สคริปต์ "Hello World" แบบง่ายของเราไม่สามารถเข้าถึงคีย์ใดๆ ได้ ดังนั้นเราจึงใช้ 0
.
การเข้าถึงคีย์และอาร์กิวเมนต์
สมมติว่าเรากำลังสร้างตัวย่อ URL ทุกครั้งที่มี URL เข้ามา เราต้องการจัดเก็บและส่งคืนหมายเลขเฉพาะที่สามารถใช้เข้าถึง URL ได้ในภายหลัง
เราจะใช้สคริปต์ Lua เพื่อรับ ID ที่ไม่ซ้ำจาก Redis โดยใช้ INCR
และเก็บ URL ไว้ในแฮชที่เข้ารหัสด้วย ID เฉพาะทันที:
local link_id = redis.call("INCR", KEYS[1])
redis.call("HSET", KEYS[2], link_id, ARGV[1])
return link_id
เรากำลังเข้าถึง Redis เป็นครั้งแรกที่นี่ โดยใช้ call()
ฟังก์ชั่น.call()
อาร์กิวเมนต์ของคือคำสั่งที่จะส่งไปยัง Redis:ก่อนอื่นเรา INCR <key>
แล้วเรา HSET <key> <field> <value>
. คำสั่งทั้งสองนี้จะทำงานตามลำดับ — Redis จะไม่ดำเนินการใดๆ ในขณะที่สคริปต์นี้ทำงาน และจะรันอย่างรวดเร็วมาก
เรากำลังเข้าถึงตาราง Lua สองตาราง KEYS
และ ARGV
. ตารางเป็นอาร์เรย์ที่เชื่อมโยงกัน และเป็นกลไกเดียวของ Lua สำหรับการจัดโครงสร้างข้อมูล เพื่อจุดประสงค์ของเรา คุณสามารถมองว่าพวกมันเทียบเท่ากับอาร์เรย์ในภาษาใดก็ตามที่คุณสะดวกใจที่สุด แต่โปรดทราบว่า Lua-isms สองสิ่งนี้ที่พาดพิงถึงภาษาใหม่:
-
ตารางเป็นแบบหนึ่งเดียว นั่นคือ การสร้างดัชนีเริ่มต้นที่ 1 ดังนั้นองค์ประกอบแรกใน
mytable
คือmytable[1]
ที่สองคือmytable[2]
, ฯลฯ -
ตารางไม่สามารถเก็บค่าศูนย์ได้ หากการดำเนินการจะให้ตารางของ
[ 1, nil, 3, 4 ]
ผลลัพธ์จะเป็น[ 1 ]
. แทน — ตารางถูกตัดออก ที่ค่าศูนย์แรก
เมื่อเราเรียกใช้สคริปต์นี้ เราจำเป็นต้องส่งต่อค่าสำหรับ KEYS
และ ARGV
ตาราง ในโปรโตคอล Redis แบบ raw คำสั่งจะมีลักษณะดังนี้:
EVAL $incrset.lua 2 links:counter links:url https://malcolmgladwellbookgenerator.com/
เมื่อโทร EVAL
หลังจากสคริปต์เราให้ 2
เป็นจำนวนKEYS
ที่จะเข้าถึงได้ จากนั้นเราจะแสดงรายการ KEYS
. ของเรา และสุดท้าย เราก็ให้ค่าสำหรับ ARGV
.
โดยปกติเมื่อเราสร้างแอปด้วยสคริปต์ Redis Lua ไลบรารี Redisclient จะดูแลการระบุจำนวนคีย์ codeblock ด้านบนจะแสดงเพื่อความสมบูรณ์ แต่นี่เป็นวิธีที่ง่ายกว่าในการดำเนินการนี้ที่บรรทัดคำสั่ง:
redis-cli --eval incrset.lua links:counter links:urls , https://malcolmgladwellbookgenerator.com/
เมื่อใช้ --eval
ดังข้างบนนี้ เครื่องหมายจุลภาคคั่น KEYS[]
จาก ARGV[]
รายการ
เพื่อให้ชัดเจน นี่คือสคริปต์ดั้งเดิมของเราอีกครั้ง คราวนี้มีKEYS
และ ARGV
ขยาย:
local link_id = redis.call("INCR", "links:counter")
redis.call("HSET", "links:urls", link_id, "https://malcolmgladwellbookgenerator.com")
return link_id
เมื่อเขียนสคริปต์ Lua สำหรับ Redis ทุกคีย์ที่เข้าถึงได้ควรเข้าถึงโดย KEYS
เท่านั้น โต๊ะ. ARGV
ตารางใช้สำหรับส่งผ่านพารามิเตอร์ นี่คือค่าของ URL ที่เราต้องการจัดเก็บ
ลอจิกแบบมีเงื่อนไข:increx และ hincrex
ตัวอย่างด้านบนของเราบันทึกลิงก์สำหรับตัวย่อ URL แต่เรายังต้องติดตามจำนวนครั้งที่ URL ถูกเข้าถึง ในการทำเช่นนั้น เราจะทำการโต้ตอบกันในแฮชใน Redis เมื่อผู้ใช้มาพร้อมกับตัวระบุลิงก์ เราจะตรวจสอบเพื่อดูว่ามีอยู่หรือไม่ และเพิ่มตัวนับสำหรับมันหากมี:
if redis.call("HEXISTS", KEYS[1], ARGV[1]) == 1 then
return redis.call("HINCRBY", KEYS[1], ARGV[1], 1)
else
return nil
end
ทุกครั้งที่มีคนคลิกลิงก์สั้น เราจะเรียกใช้สคริปต์นี้เพื่อติดตามว่ามีการแชร์ลิงก์อีกครั้ง เราเรียกใช้สคริปต์โดยใช้ EVAL
และส่งต่อlinks:visits
สำหรับคีย์เดียวของเราและตัวระบุลิงก์ที่ส่งคืนจากสคริปต์ก่อนหน้าของเราเป็นอาร์กิวเมนต์เดียว
สคริปต์จะดูเกือบจะเหมือนกันโดยไม่มีแฮช นี่คือ ascript ซึ่งเพิ่มคีย์ Redis มาตรฐานเฉพาะเมื่อมีอยู่แล้ว:
if redis.call("EXISTS",KEYS[1]) == 1 then
return redis.call("INCR",KEYS[1])
else
return nil
end
โหลดสคริปต์และ EVALSHA
โปรดจำไว้ว่าเมื่อ Redis เรียกใช้สคริปต์ Lua จะไม่เรียกใช้อย่างอื่น สคริปต์ที่ดีที่สุดเพียงแค่ขยายคำศัพท์ Redis ที่มีอยู่ของการดำเนินการดาต้าอะตอมมิกขนาดเล็กด้วยตรรกะที่น้อยที่สุดที่จำเป็น ข้อบกพร่องในสคริปต์ Lua สามารถล็อกเซิร์ฟเวอร์ Redis ได้ทั้งหมด ดีที่สุดคือทำให้ทุกอย่างสั้นและง่ายต่อการแก้ไข
แม้ว่าโดยปกติแล้วจะค่อนข้างสั้น แต่เราไม่จำเป็นต้องระบุสคริปต์ Lua แบบเต็มในแต่ละครั้งที่เราต้องการเรียกใช้ ในแอปพลิเคชันจริง คุณจะลงทะเบียนสคริปต์ Lua แต่ละรายการกับ Redis แทนเมื่อแอปพลิเคชันบูท (หรือเมื่อคุณปรับใช้) จากนั้นเรียกใช้สคริปต์ในภายหลังโดยใช้ตัวระบุ SHA-1 ที่ไม่ซ้ำกัน
redis-cli SCRIPT LOAD "return 'hello world'"
# "5332031c6b470dc5a0dd9b4bf2030dea6d65de91"
redis-cli EVALSHA 5332031c6b470dc5a0dd9b4bf2030dea6d65de91 0
# "hello world"
การเรียกที่ชัดเจนไปยัง SCRIPT LOAD
มักจะไม่จำเป็นในแอปพลิเคชันสดเนื่องจาก EVAL
โหลดสคริปต์ที่ส่งไปโดยปริยาย แอปพลิเคชันสามารถพยายาม EVALSHA
มองโลกในแง่ดีและถอยกลับไปสู่ EVAL
เฉพาะในกรณีที่ไม่พบสคริปต์
หากคุณเป็นโปรแกรมเมอร์ Ruby ลองดู Wolverine ของ Shopify ซึ่งช่วยลดความยุ่งยากในการโหลดและจัดเก็บสคริปต์ Lua สำหรับแอป Ruby สำหรับโปรแกรมเมอร์ PHP Predis รองรับการเพิ่ม Luascripts เพื่อเรียกเหมือนกับว่าเป็นคำสั่ง Redis ปกติ หากคุณใช้เครื่องมือเหล่านี้หรือเครื่องมืออื่นๆ เพื่อสร้างมาตรฐานการโต้ตอบกับ Lua โปรดแจ้งให้เราทราบ— ฉันสนใจที่จะค้นหาว่ามีอะไรอีกบ้าง
ควรใช้ Lua เมื่อใด
Redis รองรับ Lua ทับซ้อนกับ WATCH
/MULTI
/EXEC
บล็อก ซึ่งกลุ่มการดำเนินงานจึงดำเนินการร่วมกัน แล้วคุณจะเลือกใช้อันใดอันหนึ่งแทนอันอื่นได้อย่างไร แต่ละการดำเนินการใน MULTI
บล็อกต้องเป็นอิสระ แต่สำหรับ Lua การดำเนินการในภายหลังขึ้นอยู่กับผลลัพธ์ของการดำเนินการก่อนหน้านี้ การใช้สคริปต์ Lua ยังสามารถหลีกเลี่ยงสภาวะที่อาจทำให้ไคลเอ็นต์ต้องอดอาหารช้าเมื่อ WATCH
ถูกนำมาใช้
จากสิ่งที่เราเห็นที่ RedisGreen แอปส่วนใหญ่ที่ใช้ Lua จะใช้ MULTI/EXEC ด้วยเช่นกัน แต่ไม่ใช่ในทางกลับกัน สคริปต์ Lua ที่ประสบความสำเร็จส่วนใหญ่นั้นมีขนาดเล็ก และเพียงแค่ปรับใช้คุณลักษณะเดียวที่แอปของคุณต้องการ แต่ไม่ได้เป็นส่วนหนึ่งของ Redisvocabulary
การเยี่ยมชมห้องสมุด
ล่าม Redis Lua โหลดไลบรารีเจ็ดไลบรารี:ฐาน ตาราง สตริง คณิตศาสตร์ ดีบัก cjson และ cmsgpack ประการแรกคือไลบรารีมาตรฐานที่ให้คุณดำเนินการพื้นฐานที่คุณต้องการจากภาษาใดก็ได้ สองข้อสุดท้ายทำให้ Redis เข้าใจ JSON และ MessagePack ซึ่งเป็นฟีเจอร์ที่มีประโยชน์อย่างยิ่ง และฉันก็ยังสงสัยว่าทำไมฉันถึงไม่เห็นมันใช้บ่อยขึ้น
เว็บแอปที่มี API สาธารณะมักจะมี JSON วางอยู่ทั่ว ดังนั้น บางทีคุณอาจมี JSON blobs จำนวนมากที่จัดเก็บไว้ในคีย์ Redis ปกติ และคุณต้องการเข้าถึงค่าบางอย่างภายใน ราวกับว่าคุณได้จัดเก็บไว้เป็นแฮช ด้วยการสนับสนุน Redis JSON นั่นเป็นเรื่องง่าย:
if redis.call("EXISTS", KEYS[1]) == 1 then
local payload = redis.call("GET", KEYS[1])
return cjson.decode(payload)[ARGV[1]]
else
return nil
end
ที่นี่เราตรวจสอบเพื่อดูว่ามีคีย์อยู่หรือไม่และคืนค่าศูนย์อย่างรวดเร็วหากไม่มี จากนั้นนำค่า JSON ออกจาก Redis แยกวิเคราะห์ด้วย cjson.decode()
และคืนค่าที่ร้องขอ
redis-cli set apple '{ "color": "red", "type": "fruit" }'
# OK
redis-cli --eval json-get.lua apple , type
# "fruit"
การโหลดสคริปต์นี้ลงในเซิร์ฟเวอร์ Redis ของคุณทำให้คุณสามารถจัดการกับค่า JSON ที่จัดเก็บใน Redis ได้เหมือนกับว่าเป็นแฮช หากออบเจ็กต์ของคุณมีขนาดเล็กพอสมควร การดำเนินการนี้ค่อนข้างเร็ว แม้ว่าเราต้องแยกวิเคราะห์ค่าในการเข้าถึงแต่ละครั้ง
หากคุณกำลังทำงานกับ API ภายในสำหรับระบบที่ต้องการประสิทธิภาพ คุณมักจะเลือก MessagePack แทน JSON เนื่องจากมีขนาดเล็กกว่าและเร็วกว่า โชคดีกับ Redis (เช่นเดียวกับในสถานที่ส่วนใหญ่) MessagePack ค่อนข้างจะแทนที่ สำหรับ JSON:
if redis.call("EXISTS", KEYS[1]) == 1 then
local payload = redis.call("GET", KEYS[1])
return cmsgpack.unpack(payload)[ARGV[1]]
else
return nil
end
การกระทืบตัวเลข
Lua และ Redis มีระบบประเภทต่างๆ ดังนั้นจึงควรทำความเข้าใจว่าค่าต่างๆ อาจเปลี่ยนแปลงไปอย่างไรเมื่อข้ามพรมแดน Redis-Lua เมื่อตัวเลขมาจาก Lua กลับไปที่ไคลเอนต์ Redis ตัวเลขนั้นจะกลายเป็นเลขจำนวนเต็ม — ตัวเลขใดๆ ที่เลยจุดทศนิยมจะถูกทิ้ง:
local indiana_pi = 3.2
return indiana_pi
เมื่อคุณเรียกใช้สคริปต์นี้ Redis จะคืนค่าจำนวนเต็ม 3 — คุณจะสูญเสียส่วนที่น่าสนใจของ pi ดูเหมือนง่ายพอสมควร แต่สิ่งต่างๆ จะยุ่งยากขึ้นเล็กน้อยเมื่อคุณเริ่มโต้ตอบกับ Redis กลางสคริปต์ ตัวอย่าง:
local indiana_pi = 3.2
redis.call("SET", "pi", indiana_pi)
return redis.call("GET", "pi")
ค่าผลลัพธ์ที่นี่คือ astring:"3.2"
ทำไม Redis ไม่มีประเภทตัวเลขเฉพาะ เมื่อเรา SET
. ครั้งแรก ค่า Redis จะบันทึกเป็นสตริงโดยสูญเสียบันทึกทั้งหมดเกี่ยวกับข้อเท็จจริงที่ Lua คิดในตอนแรกว่าค่านี้เป็นทศนิยม เมื่อเราดึงค่าออกมาทีหลัง มันก็ยังคงเป็นสตริง
ค่าใน Redis ที่เข้าถึงได้ด้วย GET
/SET
ควรถูกมองว่าเป็นสตริง ยกเว้นเมื่อการดำเนินการที่เป็นตัวเลข เช่น INCR
และ DECR
กำลังวิ่งต่อต้านพวกเขา การดำเนินการตัวเลขพิเศษเหล่านี้จะส่งคืนการตอบกลับจำนวนเต็ม (และจัดการค่าที่เก็บไว้ตามกฎทางคณิตศาสตร์) แต่ "ประเภท" ของค่าที่เก็บไว้ใน Redis ยังคงเป็นค่าสตริง
Gotchas:สรุป
นี่คือข้อผิดพลาดทั่วไปที่เราเห็นเมื่อทำงานกับ Lua ใน Redis:
-
ตารางเป็นแบบหนึ่งเดียวใน Lua ซึ่งแตกต่างจากภาษายอดนิยมส่วนใหญ่ องค์ประกอบแรกในตาราง KEYS คือ
KEYS[1]
ที่สองคือKEYS[2]
ฯลฯ -
ค่าศูนย์ยุติตารางใน Lua ดังนั้น
[ 1, 2, nil, 3 ]
จะกลายเป็น[1, 2]
. โดยอัตโนมัติ . อย่าใช้ค่าศูนย์ในตาราง -
redis.call
จะทำให้เกิดข้อผิดพลาด Lua สไตล์ข้อยกเว้นในขณะที่redis.pcall
จะดักจับข้อผิดพลาดโดยอัตโนมัติและส่งคืน astable ที่สามารถตรวจสอบได้ -
ตัวเลข Lua จะถูกแปลงเป็นจำนวนเต็มเมื่อส่งไปยัง Redis — ทุกอย่างที่ผ่านมาจุดทศนิยมจะหายไป แปลงตัวเลขทศนิยมให้เป็นสตริงก่อนส่งคืน
-
อย่าลืมระบุคีย์ทั้งหมดที่คุณใช้ในสคริปต์ Lua ของคุณใน
KEYS
ไม่เช่นนั้นสคริปต์ของคุณอาจจะใช้งานไม่ได้ใน Redis เวอร์ชันต่อๆ ไป -
สคริปต์ Lua นั้นเหมือนกับการดำเนินการอื่นๆ ใน Redis:ไม่มีสิ่งใดทำงานในขณะที่ดำเนินการอยู่ คิดว่าสคริปต์เป็นวิธีขยายคำศัพท์ของเซิร์ฟเวอร์ Redis ให้สั้นและตรงประเด็น
อ่านเพิ่มเติม
มีแหล่งข้อมูลที่ยอดเยี่ยมมากมายสำหรับ Lua และ Redis ทางออนไลน์ — นี่คือ Iuse บางส่วน:
- เอกสาร EVAL
- ไลบรารี Lua Script ของ RedisGreen
- คู่มืออ้างอิงลัวะ
- ไดเรกทอรี Lua Tutorial