ส่วนที่ 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