ส่วนที่ 3: การเพิ่มการรองรับ Pub/Sub
ที่มา:https://github.com/kzeiter/build-redis-in-go
ในส่วนนี้ของซีรีส์โคลน Redis เราจะเพิ่มการรองรับฟังก์ชัน Pub/Sub (เผยแพร่/ติดตาม) Pub/Sub อนุญาตให้ลูกค้าสมัครรับข้อมูลช่องและรับข้อความที่เผยแพร่ไปยังช่องเหล่านั้น โดยทั่วไปฟีเจอร์นี้จะใช้ในแอปพลิเคชันแบบเรียลไทม์ เช่น แอปพลิเคชันแชทและการอัพเดตแบบเรียลไทม์
เราจะใช้คำสั่งใหม่สองคำสั่งสำหรับ Pub/Sub:
SUBSCRIBE channel PUBLISH channel message
คำสั่ง SUBSCRIBE
อนุญาตให้ไคลเอ็นต์สมัครรับข้อมูลช่อง ในขณะที่คำสั่ง PUBLISH
อนุญาตให้ไคลเอ็นต์เผยแพร่ข้อความไปยังช่อง เมื่อมีการเผยแพร่ข้อความไปยังช่อง ลูกค้าที่สมัครรับข้อมูลทั้งหมดจะได้รับข้อความ
การนำ Pub/Sub ไปใช้
หากต้องการใช้ Pub/Sub เราจะต้องแก้ไขโครงสร้าง store
เพื่อติดตามสมาชิกสำหรับแต่ละช่อง เราจะใช้แผนที่โดยที่คีย์คือชื่อช่องและค่าเป็นส่วนของ client
structs โครงสร้าง client
จะเป็นตัวแทนของไคลเอนต์ที่สมัครรับข้อมูลจากช่องและจะมีการอ้างอิงถึงวัตถุ net.Conn
ของลูกค้าซึ่งเราจะใช้ในการส่งข้อความไปยังไคลเอนต์
type store struct { data map[string]string list map[string][]string sets map[string]map[string]bool subs map[string][]client } func (s *store) subscribe(channel string, conn net.Conn) string { client := client{conn: conn} s.subs[channel] = append(s.subs[channel], client) return "SUBSCRIBED" } func (s *store) publish(channel string, message string) { subs, ok := s.subs[channel] if ok { for _, subscriber := range subs { fmt.Fprintf(subscriber.conn, "+%s\n", message) } } }
เรายังจำเป็นต้องกำหนดโครงสร้าง client
:
type client struct { conn net.Conn }
เมื่อลูกค้าสมัครรับข้อมูลจากช่อง เราจะสร้างโครงสร้าง client
ใหม่และเพิ่มลงในรายชื่อสมาชิกของช่อง เมื่อมีการเผยแพร่ข้อความไปยังช่องใดช่องหนึ่ง เราจะวนซ้ำรายชื่อสมาชิกของช่องนั้นและส่งข้อความไปยังแต่ละช่อง
นี่คือ handleCommand
ที่อัปเดตพร้อมฟังก์ชัน Pub/Sub ใหม่:
func (s *store) handleCommand(command string, args []string) string { switch command { case "SET": // Using Join to save the rest of the received data return s.set(args[0], strings.Join(args[1:], " ")) case "GET": return s.get(args[0]) case "DEL": s.del(args[0]) return "DELETED" case "INCR": return fmt.Sprintf("%v", s.incr(args[0])) case "INCRBY": return fmt.Sprintf("%v", s.incrBy(args[0], args[1])) case "DECR": return fmt.Sprintf("%v", s.decr(args[0])) case "DECRBY": return fmt.Sprintf("%v", s.decrBy(args[0], args[1])) case "LPUSH": return fmt.Sprintf("%v", s.lPush(args[0], args[1])) case "RPUSH": return fmt.Sprintf("%v", s.rPush(args[0], args[1])) case "LPOP": return s.lPop(args[0]) case "RPOP": return s.rPop(args[0]) case "LLEN": return fmt.Sprintf("%v", s.lLen(args[0])) case "LINDEX": index, _ := strconv.Atoi(args[1]) return s.lIndex(args[0], index) case "SADD": return fmt.Sprintf("%v", s.sadd(args[0], args[1])) case "SREM": return fmt.Sprintf("%v", s.srem(args[0], args[1])) case "SMEMBERS": members := s.smembers(args[0]) result := "" for _, member := range members { result += fmt.Sprintf("%v ", member) } return strings.TrimSpace(result) case "SISMEMBER": return fmt.Sprintf("%v", s.sismember(args[0], args[1])) case "SUBSCRIBE": return s.subscribe(args[0], conn) case "PUBLISH": s.publish(args[0], strings.Join(args[1:], " ")) return "PUBLISHED" default: return "ERROR" } }
เราได้เพิ่มสองกรณีใหม่ให้กับคำสั่ง switch ในเมธอด handleCommand
เพื่อจัดการคำสั่ง SUBSCRIBE
และ PUBLISH
นอกจากนี้เรายังได้เพิ่มวิธีการ subscribe
และ publish
เพื่อจัดการการสมัครรับข้อมูลช่องและการเผยแพร่ข้อความไปยังช่องตามลำดับ
ในเมธอด subscribe
เราสร้างโครงสร้าง client
ใหม่ด้วยออบเจ็กต์ net.Conn
ของลูกค้า และผนวกเข้ากับรายชื่อสมาชิกสำหรับช่องที่ระบุ
ในวิธี publish
เราจะตรวจสอบก่อนว่ามีสมาชิกสำหรับช่องที่ระบุหรือไม่ หากมี เราจะวนซ้ำรายชื่อสมาชิกและส่งข้อความถึงแต่ละคนโดยใช้อ็อบเจ็กต์ net.Conn
ของลูกค้า
บทสรุป
ในส่วนนี้ของซีรีส์โคลน Redis เราได้เพิ่มการรองรับฟังก์ชัน Pub/Sub เราได้ใช้คำสั่ง SUBSCRIBE
และ PUBLISH
และอัปเดตโครงสร้าง store
ของเราให้รวมแผนที่ subs
ซึ่งจัดเก็บรายชื่อสมาชิกสำหรับแต่ละช่อง
Pub/Sub เป็นรูปแบบการส่งข้อความที่มีประสิทธิภาพซึ่งช่วยให้ผู้เผยแพร่สามารถส่งข้อความไปยังสมาชิกหลายรายโดยไม่ต้องรู้อะไรเกี่ยวกับพวกเขาเลย ทำให้เหมาะสำหรับการสร้างแอปพลิเคชันแบบเรียลไทม์ เช่น ห้องสนทนาหรือเกมออนไลน์
ในส่วนถัดไปของซีรีส์โคลน Redis เราจะเพิ่มการรองรับเพื่อความคงอยู่โดยการนำระบบจัดเก็บข้อมูลบนดิสก์แบบธรรมดาไปใช้ คอยติดตาม!
ที่มา:
https://github.com/kzeiter/build-redis-in-go
ส่วนที่ 1: การสร้างที่เก็บคีย์-ค่าพื้นฐาน:
https://khalidzeiter.medium.com/building-a-simple-redis-clone-in-golang-part-1 -7f816f2f61bd
ส่วนที่ 2: การเพิ่มการรองรับสำหรับตัวเลข รายการ และชุด:
https://khalidzeiter.medium.com/building-a-simple-redis-clone-in-golang-part -2-9130c0c3ef22
ส่วนที่ 4: การเพิ่มความคงทนด้วยพื้นที่จัดเก็บดิสก์:
https://khalidzeiter.medium.com/building-a-simple-redis-clone-in-golang-part-4-bf4ff349dad3
บทสรุปและขั้นตอนถัดไป:
https://khalidzeiter.medium.com/building-a-simple-redis-clone-in-golang-conclusion-and-next-steps-128b0503a55b