portaldacalheta.pt
  • หลัก
  • ทีมแบบกระจาย
  • เคล็ดลับและเครื่องมือ
  • ชีวิตนักออกแบบ
  • นวัตกรรม
ส่วนหลัง

ไปแบบเรียลไทม์กับ Redis Pub / Sub



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

ในบทความนี้เราจะอธิบายถึงสถาปัตยกรรมของการแชร์รูปภาพแบบเรียลไทม์และแอพส่งข้อความบนเว็บ ในที่นี้เราจะเน้นไปที่ส่วนประกอบต่างๆเช่น Redis Pub / Sub มีส่วนร่วมในการสร้างแอปแบบเรียลไทม์และดูว่าพวกเขาทั้งหมดมีบทบาทอย่างไรในสถาปัตยกรรมโดยรวม



ไปแบบเรียลไทม์กับ Redis Pub / Sub



ไปแบบเรียลไทม์กับ Redis Pub / Sub ทวีต

ฟังก์ชั่นที่ชาญฉลาดแอปพลิเคชันมีน้ำหนักเบามาก ช่วยให้สามารถอัปโหลดภาพและแสดงความคิดเห็นแบบเรียลไทม์ในภาพเหล่านั้นได้ นอกจากนี้ผู้ใช้ทุกคนสามารถแตะที่รูปภาพและผู้ใช้คนอื่น ๆ จะสามารถเห็นเอฟเฟกต์การกระเพื่อมบนหน้าจอได้



วิธีคำนวนราคา ipo ต่อหุ้น

ซอร์สโค้ดทั้งหมดของแอพนี้คือ พร้อมใช้งานบน GitHub .

สิ่งที่เราต้องการ

ไป

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



เริ่มต้นใช้งาน Go เป็นเรื่องง่าย การแจกแจงแบบไบนารีสามารถเป็นได้ ดาวน์โหลดจากเว็บไซต์อย่างเป็นทางการ . ในกรณีที่คุณใช้ Windows จะมีตัวติดตั้ง MSI สำหรับ Go อยู่ในหน้าดาวน์โหลด หรือในกรณีที่ระบบปฏิบัติการของคุณ (โชคดี) มีตัวจัดการแพ็คเกจ:

Arch Linux:



pacman -S go

Ubuntu:

apt-get install golang

Mac OS X:



brew install go

อันนี้จะใช้ได้ก็ต่อเมื่อเรามี Homebrew ติดตั้ง

MongoDB

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



เราทำได้ ดาวน์โหลด MongoDB จากเว็บไซต์ทางการของพวกเขา ในลินุกซ์บางรุ่นนี่เป็นวิธีที่แนะนำในการติดตั้ง MongoDB แต่ก็ยังควรติดตั้งได้โดยใช้ตัวจัดการแพ็คเกจของการแจกจ่ายส่วนใหญ่

Arch Linux:



pacman -S mongodb

Ubuntu:

apt-get install mongodb

Mac OS X:

brew install mongodb

ภายในรหัส Go ของเราเราจะใช้แพ็คเกจ mgo (ออกเสียงว่ามะม่วง). ไม่เพียง แต่ทดสอบการต่อสู้เท่านั้น แต่แพ็คเกจไดรเวอร์ยังมี API ที่เรียบง่ายและสะอาดตา

หากคุณไม่ใช่ ผู้เชี่ยวชาญ MongoDB ไม่ต้องกังวลเลย การใช้บริการฐานข้อมูลนี้มีเพียงเล็กน้อยในแอปตัวอย่างของเราและแทบไม่เกี่ยวข้องกับจุดสำคัญของบทความนี้: สถาปัตยกรรม Pub / Sub

Amazon S3

เราจะใช้ Amazon S3 เพื่อจัดเก็บภาพที่ผู้ใช้อัปโหลด ที่นี่ไม่มีอะไรให้ทำมากนักยกเว้นตรวจสอบให้แน่ใจว่าเรามีไฟล์ Amazon Web Services บัญชีที่พร้อมใช้งานและสร้างที่เก็บข้อมูลชั่วคราว

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

ในการโต้ตอบกับถัง Amazon S3 จากรหัส Go ของเราเราจะใช้ AdRoll / goamz , ส้อมของ Goamz ของ Canonical แพคเกจที่มีความแตกต่างบางอย่าง

Redis

สุดท้าย แต่ไม่ท้ายสุด: Redis เราสามารถติดตั้งได้โดยใช้ตัวจัดการแพ็คเกจของเรา:

Arch Linux:

pacman -S redis

Ubuntu:

apt-get install redis-server

Mac OS X:

brew install redis

หรือดึงซอร์สโค้ดและ รวบรวมด้วยตัวคุณเอง . Redis ไม่มีการพึ่งพานอกเหนือจาก GCC และ libc สำหรับการสร้าง:

wget http://download.redis.io/redis-stable.tar.gz tar xvzf redis-stable.tar.gz cd redis-stable make

เมื่อติดตั้งและเรียกใช้ Redis แล้วให้เริ่มเทอร์มินัลและเข้าสู่ Redis ’CLI:

redis-cli

ลองป้อนคำสั่งต่อไปนี้และดูว่าคุณได้ผลลัพธ์ที่ต้องการหรือไม่:

SET answer 41 INCR answer GET answer

คำสั่งแรกเก็บ“ 41” เทียบกับคีย์“ คำตอบ” คำสั่งที่สองจะเพิ่มค่าคำสั่งที่สามจะพิมพ์ค่าที่เก็บไว้เทียบกับคีย์ที่กำหนด ผลลัพธ์ควรอ่าน“ 42”

คุณสามารถ เรียนรู้เพิ่มเติม เกี่ยวกับคำสั่งทั้งหมดที่ Redis รองรับบนเว็บไซต์ทางการ

เราจะใช้แพ็คเกจ Go การลด เพื่อเชื่อมต่อกับ Redis จากภายในรหัสแอปของเรา

แอบดู Redis Pub / Sub

รูปแบบการสมัครสมาชิกเผยแพร่เป็นวิธีการส่งข้อความไปยังผู้ส่งตามจำนวนที่กำหนด ผู้ส่งข้อความเหล่านี้ (ผู้เผยแพร่) ไม่ได้ระบุผู้รับเป้าหมายอย่างชัดเจน แต่ข้อความจะถูกส่งออกไปในช่องทางที่สามารถรอผู้รับ (สมาชิก) จำนวนเท่าใดก็ได้

การกำหนดค่าการสมัครใช้งานการเผยแพร่อย่างง่าย

ในกรณีของเราเราสามารถมีเว็บโหนดจำนวนเท่าใดก็ได้ที่ทำงานหลังตัวจัดสรรภาระงาน ในช่วงเวลาหนึ่งผู้ใช้สองคนที่ดูภาพเดียวกันอาจไม่ได้เชื่อมต่อกับโหนดเดียวกัน นี่คือจุดที่ Redis Pub / Sub เข้ามามีบทบาท เมื่อใดก็ตามที่เว็บโหนดต้องการสังเกตการเปลี่ยนแปลง (เช่นข้อความใหม่ถูกสร้างโดยผู้ใช้) จะใช้ Redis Pub / Sub เพื่อเผยแพร่ข้อมูลนั้นไปยังเว็บโหนดที่เกี่ยวข้องทั้งหมด ซึ่งในทางกลับกันจะเผยแพร่ข้อมูลไปยังลูกค้าที่เกี่ยวข้องเพื่อที่พวกเขาจะได้ดึงรายการข้อความที่อัปเดตแล้ว

เนื่องจากรูปแบบการสมัครสมาชิกเผยแพร่ช่วยให้เราสามารถส่งข้อความในช่องที่มีชื่อได้เราจึงสามารถให้แต่ละโหนดของเว็บเชื่อมต่อกับ Redis และสมัครรับข้อมูลเฉพาะช่องที่ผู้ใช้ที่เชื่อมต่อของพวกเขาสนใจตัวอย่างเช่นหากผู้ใช้สองคนทั้งคู่กำลังดู ภาพเดียวกัน แต่เชื่อมต่อกับเว็บโหนดที่แตกต่างกันสองโหนดจากหลาย ๆ เว็บโหนดดังนั้นจึงมีเพียงเว็บโหนดสองโหนดเท่านั้นที่ต้องสมัครสมาชิกช่องที่เกี่ยวข้อง ข้อความใด ๆ ที่เผยแพร่บนช่องนั้นจะถูกส่งไปยังเว็บโหนดทั้งสองเท่านั้น

ฟังดูดีเกินจริง? เราสามารถทดลองใช้ได้โดยใช้ CLI ของ Redis เริ่มต้นสามอินสแตนซ์ของ redis-cli ดำเนินการคำสั่งต่อไปนี้ในอินสแตนซ์แรก:

SUBSCRIBE somechannel

ดำเนินการคำสั่งต่อไปนี้ในอินสแตนซ์ Redis CLI ที่สอง:

SUBSCRIBE someotherchannel

ดำเนินการคำสั่งต่อไปนี้ในอินสแตนซ์ที่สามของ Redis CLI:

PUBLISH somechannel lorem PUBLISH someotherchannel ipsum

สังเกตว่าอินสแตนซ์แรกได้รับ“ ลอเรม” แต่ไม่ใช่“ อิปซัม” อย่างไรและอินสแตนซ์ที่สองได้รับ“ ipsum” อย่างไร แต่ไม่ใช่“ ลอเรม”

Redis Pub / Sub ในการดำเนินการ

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

เรียลไทม์และปรับขนาดได้

ก่อนที่เราจะเริ่มสำรวจสิ่งที่เกิดขึ้นเบื้องหลังให้เราโคลนที่เก็บ:

วิธีตั้งโปรแกรมหุ่นยนต์ใน java
mkdir tonesa cd tonesa export GOPATH=`pwd` mkdir -p src/github.com/hjr265/tonesa cd src/github.com/hjr265/tonesa git clone https://github.com/hjr265/tonesa.git . go get ./...

... และรวบรวม:

go build ./cmd/tonesad

ในการเรียกใช้แอปก่อนอื่นให้สร้างไฟล์ชื่อ. env (ควรจะคัดลอกไฟล์ env-sample.txt):

cp env-sample.txt .env

กรอกไฟล์. env ด้วยตัวแปรสภาพแวดล้อมที่จำเป็นทั้งหมด:

MONGO_URL=mongodb://127.0.0.1/tonesa REDIS_URL=redis://127.0.0.1 AWS_ACCESS_KEY_ID={Your-AWS-Access-Key-ID-Goes-Here} AWS_SECRET_ACCESS_KEY={And-Your-AWS-Secret-Access-Key} S3_BUCKET_NAME={And-S3-Bucket-Name}

สุดท้ายเรียกใช้ไบนารีที่สร้างขึ้น:

PORT=9091 ./tonesad -env-file=.env

ขณะนี้เว็บโหนดควรทำงานและสามารถเข้าถึงได้ผ่าน http: // localhost: 9091

ตัวอย่างสด

หากต้องการทดสอบว่ายังใช้งานได้หรือไม่เมื่อปรับขนาดในแนวนอนคุณสามารถหมุนโหนดเว็บหลายโหนดได้โดยเริ่มต้นด้วยหมายเลขพอร์ตต่างกัน:

PORT=9092 ./tonesad -env-file=.env PORT=9093 ./tonesad -env-file=.env

…และเข้าถึงผ่าน URL ที่เกี่ยวข้อง: http: // localhost: 9092 และ http: // localhost: 9093

ตัวอย่างสด

เบื้องหลัง

แทนที่จะทำทุกขั้นตอนในการพัฒนาแอปเราจะมุ่งเน้นไปที่ส่วนที่สำคัญที่สุดบางส่วน แม้ว่าทั้งหมดนี้จะไม่เกี่ยวข้องกับ Redis Pub / Sub และผลกระทบแบบเรียลไทม์ 100% แต่ก็ยังคงเกี่ยวข้องกับโครงสร้างโดยรวมของแอปและจะทำให้ง่ายต่อการติดตามเมื่อเราดำน้ำลึกลงไป

เพื่อให้สิ่งต่างๆง่ายขึ้นเราจะไม่กังวลเกี่ยวกับการตรวจสอบผู้ใช้ การอัปโหลดจะไม่ระบุชื่อและพร้อมใช้งานสำหรับทุกคนที่รู้จัก URL ผู้ชมทุกคนสามารถส่งข้อความและจะสามารถเลือกนามแฝงของตนเองได้ การปรับกลไกการตรวจสอบสิทธิ์ที่เหมาะสมและความสามารถในการรักษาความเป็นส่วนตัวควรเป็นเรื่องเล็กน้อยและอยู่นอกเหนือขอบเขตของบทความนี้

ข้อมูลที่ยังคงอยู่

อันนี้ง่ายดี

เมื่อใดก็ตามที่ผู้ใช้อัปโหลดภาพเราจะจัดเก็บภาพนั้นไว้ใน Amazon S3 จากนั้นจัดเก็บเส้นทางไปยัง MongoDB โดยเทียบกับ ID สองรหัส ได้แก่ BSON Object ID หนึ่งรายการ (รายการโปรดของ MongoDB) และอีกรหัสความยาว 8 อักขระสั้น ๆ (ค่อนข้างน่าดู) สิ่งนี้จะเข้าไปในคอลเล็กชัน 'การอัปโหลด' ของฐานข้อมูลของเราและมีโครงสร้างดังนี้:

type Upload struct { ID bson.ObjectId `bson:'_id'` ShortID string `bson:'shortID'` Kind Kind `bson:'kind'` Content Blob `bson:'content'` CreatedAt time.Time `bson:'createdAt'` ModifiedAt time.Time `bson:'modifiedAt'` } type Blob struct { Path string `bson:'path'` Size int64 `bson:'size'` }

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

เมื่อผู้ใช้ส่งข้อความถึงกันข้อมูลเหล่านั้นจะถูกเก็บไว้ในคอลเลกชั่นอื่น ใช่คุณเดาถูก:“ ข้อความ”

type Message struct { ID bson.ObjectId `bson:'_id'` UploadID bson.ObjectId `bson:'uploadID'` AuthorName string `bson:'anonName'` Content string `bson:'content'` CreatedAt time.Time `bson:'createdAt'` ModifiedAt time.Time `bson:'modifiedAt'` }

สิ่งเดียวที่น่าสนใจในที่นี้คือฟิลด์ UploadID ซึ่งใช้เพื่อเชื่อมโยงข้อความกับการอัปโหลดเฉพาะ

ปลายทาง API

แอปพลิเคชันนี้มีจุดสิ้นสุดสามจุด

POST / api / อัปโหลด

ตัวจัดการสำหรับปลายทางนี้ต้องการการส่งแบบ 'หลายส่วน / แบบฟอร์มข้อมูล' พร้อมกับรูปภาพในช่อง 'ไฟล์' ลักษณะการทำงานของตัวจัดการมีคร่าวๆดังนี้:

func HandleUploadCreate(w http.ResponseWriter, r *http.Request) { f, h, _ := r.FormFile('file') b := bytes.Buffer{} n, _ := io.Copy(&b, io.LimitReader(f, data.MaxUploadContentSize+10)) if n > data.MaxUploadContentSize { ServeBadRequest(w, r) return } id := bson.NewObjectId() upl := data.Upload{ ID: id, Kind: data.Image, Content: data.Blob{ Path: '/uploads/' + id.Hex(), Size: n, }, } data.Bucket.Put(upl.Content.Path, b.Bytes(), h.Header.Get('Content-Type'), s3.Private, s3.Options{}) upl.Put() // Respond with newly created upload entity (JSON encoded) }

Go ต้องการให้มีการจัดการข้อผิดพลาดทั้งหมดอย่างชัดเจน สิ่งนี้ได้ทำในต้นแบบแล้ว แต่จะถูกละไว้ในตัวอย่างในบทความนี้เพื่อให้โฟกัสไปที่ส่วนที่สำคัญ

ในตัวจัดการของปลายทาง API นี้เรากำลังอ่านไฟล์เป็นหลัก แต่ จำกัด ขนาดไว้ที่ค่าเฉพาะ หากการอัปโหลดเกินค่านี้คำขอจะถูกปฏิเสธ มิฉะนั้น BSON ID จะถูกสร้างขึ้นและใช้เพื่ออัปโหลดรูปภาพไปยัง Amazon S3 ก่อนที่จะคงเอนทิตีการอัปโหลดไปยัง MongoDB

มีข้อดีและข้อเสียเกี่ยวกับวิธีสร้างรหัสวัตถุ BSON พวกเขาถูกสร้างขึ้นที่ส่วนท้ายของไคลเอนต์ อย่างไรก็ตามกลยุทธ์ที่ใช้ในการสร้าง Object ID ทำให้ความน่าจะเป็นของการชนกันน้อยที่สุดจึงปลอดภัยที่จะสร้างขึ้นในฝั่งไคลเอ็นต์ ในทางกลับกันค่าของ Object ID ที่สร้างขึ้นมักจะเป็นลำดับและนั่นคือสิ่งที่ Amazon S3 เป็น ไม่ค่อยชอบ . วิธีแก้ปัญหาง่ายๆคือการนำหน้าชื่อไฟล์ด้วยสตริงแบบสุ่ม

รับ / api / อัปโหลด / {id} / ข้อความ

API นี้ใช้เพื่อดึงข้อความล่าสุดและข้อความที่โพสต์หลังจากช่วงเวลาหนึ่ง

func ServeMessageList(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) idStr := vars['id'] if !bson.IsObjectIdHex(idStr) { ServeNotFound(w, r) return } upl, _ := data.GetUpload(bson.ObjectIdHex(idStr)) if upl == nil { ServeNotFound(w, r) return } sinceStr := r.URL.Query().Get('since') var msgs []data.Message if sinceStr != '' { since, _ := time.Parse(time.RFC3339, sinceStr) msgs, _ = data.ListMessagesByUploadID(upl.ID, since, 16) } else { msgs, _ = data.ListRecentMessagesByUploadID(upl.ID, 16) } // Respond with message entities (JSON encoded) }

เมื่อเบราว์เซอร์ของผู้ใช้ได้รับแจ้งเกี่ยวกับข้อความใหม่ในการอัปโหลดที่ผู้ใช้กำลังดูอยู่เบราว์เซอร์จะดึงข้อความใหม่โดยใช้ปลายทางนี้

POST / api / uploads / {id} / ข้อความ

และสุดท้ายตัวจัดการที่สร้างข้อความและแจ้งทุกคน:

func HandleMessageCreate(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) idStr := vars['id'] if !bson.IsObjectIdHex(idStr) { ServeNotFound(w, r) return } upl, _ := data.GetUpload(bson.ObjectIdHex(idStr)) if upl == nil { ServeNotFound(w, r) return } body := Message{} json.NewDecoder(r.Body).Decode(&body) msg := data.Message{} msg.UploadID = upl.ID msg.AuthorName = body.AuthorName msg.Content = body.Content msg.Put() // Respond with newly created message entity (JSON encoded) hub.Emit('upload:'+upl.ID.Hex(), 'message:'+msg.ID.Hex()) }

เครื่องจัดการนี้คล้ายกับตัวอื่น ๆ มากจนแทบจะน่าเบื่อที่จะรวมไว้ที่นี่ หรือว่า? สังเกตว่ามีการเรียกใช้ฟังก์ชันอย่างไร ฮับ. มิต () ที่ส่วนท้ายสุดของฟังก์ชัน ฮับคุณพูดอะไร นั่นคือจุดที่เวทมนตร์ Pub / Sub ทั้งหมดเกิดขึ้น

Hub: ที่ WebSockets พบกับ Redis

Hub เป็นที่ที่เราติด WebSockets ด้วยช่อง Pub / Sub ของ Redis และบังเอิญแพคเกจที่เราใช้จัดการ WebSockets ภายในเว็บเซิร์ฟเวอร์ของเราเรียกว่า กาว .

โดยพื้นฐานแล้ว Hub จะดูแลโครงสร้างข้อมูลบางส่วนที่สร้างการแมประหว่าง WebSockets ที่เชื่อมต่อทั้งหมดกับช่องทั้งหมดที่พวกเขาสนใจตัวอย่างเช่น WebSocket บนแท็บเบราว์เซอร์ของผู้ใช้ที่ชี้ไปที่รูปภาพที่อัปโหลดโดยเฉพาะควรให้ความสนใจในการแจ้งเตือนทั้งหมดที่เกี่ยวข้อง ไปเลย

แพ็คเกจฮับใช้ฟังก์ชันหกอย่าง:

  • ติดตาม
  • ยกเลิกการสมัครสมาชิกทั้งหมด
  • ปล่อย
  • EmitLocal
  • InitHub
  • มือจับ

สมัครและยกเลิกการสมัครสมาชิกทั้งหมด

func Subscribe(s *glue.Socket, t string) error { l.Lock() defer l.Unlock() _, ok := sockets[s] if !ok { sockets[s] = map[string]bool{} } sockets[s][t] = true _, ok = topics[t] if !ok { topics[t] = map[*glue.Socket]bool{} err := subconn.Subscribe(t) if err != nil { return err } } topics[t][s] = true return nil }

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

และในทำนองเดียวกันใน ยกเลิกการสมัครสมาชิกทั้งหมด ฟังก์ชั่นเราฉีกการทำแผนที่ลง:

func UnsubscribeAll(s *glue.Socket) error { l.Lock() defer l.Unlock() for t := range sockets[s] { delete(topics[t], s) if len(topics[t]) == 0 { delete(topics, t) err := subconn.Unsubscribe(t) if err != nil { return err } } } delete(sockets, s) return nil }

เมื่อเราลบซ็อกเก็ตสุดท้ายออกจากโครงสร้างข้อมูลที่สนใจในช่องใดช่องหนึ่งเราจะยกเลิกการสมัครจากช่องใน Redis โดยใช้ subconn ยกเลิกการสมัคร .

ปล่อย

func Emit(t string, m string) error { _, err := pubconn.Do('PUBLISH', t, m) return err }

ฟังก์ชันนี้เผยแพร่ข้อความ ม ในช่อง t โดยใช้การเชื่อมต่อเผยแพร่กับ Redis

EmitLocal

func EmitLocal(t string, m string) { l.RLock() defer l.RUnlock() for s := range topics[t] { s.Write(m) } }

InitHub

func InitHub(url string) error { c, _ := redis.DialURL(url) pubconn = c c, _ = redis.DialURL(url) subconn = redis.PubSubConn{c} go func() { for { switch v := subconn.Receive().(type) { case redis.Message: EmitLocal(v.Channel, string(v.Data)) case error: panic(v) } } }() return nil }

ใน InitHub เรากำลังสร้างการเชื่อมต่อกับ Redis สองรายการ: รายการหนึ่งสำหรับสมัครรับข้อมูลช่องที่เว็บโหนดนี้สนใจและอีกรายการหนึ่งเพื่อเผยแพร่ข้อความ เมื่อสร้างการเชื่อมต่อแล้วเราจะเริ่มรูทีน Go ใหม่โดยมีลูปทำงานตลอดไปเพื่อรอรับข้อความผ่านการเชื่อมต่อสมาชิกกับ Redis ทุกครั้งที่ได้รับข้อความข้อความนั้นจะส่งเสียงในเครื่อง (เช่นไปยัง WebSockets ทั้งหมดที่เชื่อมต่อกับเว็บโหนดนี้)

มือจับ

และในที่สุดก็, มือจับ เป็นที่ที่เรารอให้ข้อความเข้ามาทาง WebSockets หรือล้างข้อมูลหลังจากการเชื่อมต่อปิดลง:

มีกี่คำถามใน aws solution Architect Associate exam
func HandleSocket(s *glue.Socket) { s.OnClose(func() { UnsubscribeAll(s) }) s.OnRead(func(data string) { fields := strings.Fields(data) if len(fields) == 0 { return } switch fields[0] { case 'watch': if len(fields) != 2 { return } Subscribe(s, fields[1]) case 'touch': if len(fields) != 4 { return } Emit(fields[1], 'touch:'+fields[2]+','+fields[3]) } }) }

JavaScript ส่วนหน้า

เนื่องจากกาวมาพร้อมกับไลบรารี JavaScript ส่วนหน้าของตัวเองจึงง่ายกว่ามากในการจัดการ WebSockets (หรือทางเลือกในการสำรวจ XHR เมื่อ WebSockets ไม่พร้อมใช้งาน):

var socket = glue() socket.onMessage(function(data) { data = data.split(':') switch(data[0]) { case 'message': messages.fetch({ data: since: _.first(messages.pluck('createdAt')) , add: true, remove: false }) break case 'touch': var coords = data[1].split(',') showTouchBubble(coords) break } }) socket.send('watch upload:'+upload.id)

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

  • ข้อความใหม่:“ message: {messageID}”
  • คลิกที่ภาพ:“ touch: {CoordX}, {CoordY}” โดยที่ CoordX และ CoordY เป็นพิกัดตามเปอร์เซ็นต์ของตำแหน่งการคลิกของผู้ใช้บนภาพ

เมื่อผู้ใช้สร้างข้อความใหม่เราจะใช้ API“ POST / api / uploads / {uploadID} / messages” เพื่อสร้างข้อความใหม่ ซึ่งทำได้โดยใช้ไฟล์ สร้าง วิธีการในคอลเลกชันกระดูกสันหลังสำหรับข้อความ:

messages.create({ authorName: $messageAuthorNameEl.val(), content: $messageContentEl.val(), createdAt: '' }, { at: 0 })

เมื่อผู้ใช้คลิกที่รูปภาพเราจะคำนวณตำแหน่งของการคลิกเป็นเปอร์เซ็นต์ของความกว้างและความสูงของรูปภาพและส่งข้อมูลผ่าน WebSocket โดยตรง

socket.send('touch upload:'+upload.id+' '+(event.pageX - offset.left) / $contentImgEl.width()+' '+(event.pageY - offset.top) / $contentImgEl.height())

ภาพรวม

ภาพรวมของแอปพลิเคชัน

เมื่อผู้ใช้พิมพ์ข้อความและกดปุ่ม Enter ไคลเอ็นต์จะเรียกใช้ปลายทาง API“ POST / api / uploads / {id} / messages” สิ่งนี้จะสร้างเอนทิตีข้อความในฐานข้อมูลและเผยแพร่สตริง 'ข้อความ: {messageID}' ผ่าน Redis Pub / Sub ในช่อง 'อัปโหลด: {uploadID}' ผ่านแพ็คเกจฮับ

Redis จะส่งต่อสตริงนี้ไปยังทุกเว็บโหนด (สมาชิก) ที่สนใจช่อง“ อัปโหลด: {uploadID}” เว็บโหนดที่รับสตริงนี้จะวนซ้ำบน WebSockets ทั้งหมดที่เกี่ยวข้องกับแชนเนลและส่งสตริงไปยังไคลเอนต์ผ่านการเชื่อมต่อ WebSocket ไคลเอ็นต์ที่ได้รับสตริงนี้จะเริ่มดึงข้อความใหม่จากเซิร์ฟเวอร์โดยใช้“ GET / api / uploads / {id} / messages”

ในทำนองเดียวกันสำหรับการเผยแพร่เหตุการณ์การคลิกบนรูปภาพไคลเอ็นต์จะส่งข้อความโดยตรงผ่าน WebSocket ซึ่งมีลักษณะคล้ายกับ 'การอัปโหลดด้วยการแตะ: {uploadID} {CoordX} {CoordY}' ข้อความนี้จะลงเอยในแพ็กเกจฮับที่เผยแพร่ในช่องเดียวกัน“ อัปโหลด: {uploadID}” ด้วยเหตุนี้สตริงจึงถูกแจกจ่ายให้กับผู้ใช้ทุกคนที่ดูรูปภาพที่อัปโหลด ไคลเอนต์เมื่อได้รับสตริงนี้จะแยกวิเคราะห์เพื่อแยกพิกัดและแสดงผลวงกลมที่กำลังเติบโตเพื่อเน้นตำแหน่งการคลิกชั่วขณะ

สรุป

ในบทความนี้เราได้เห็นภาพคร่าวๆว่ารูปแบบการสมัครสมาชิกเผยแพร่สามารถช่วยแก้ปัญหาการปรับขนาดเว็บแอปแบบเรียลไทม์ได้อย่างไรในระดับที่ดีและง่ายดาย

แอปตัวอย่างมีไว้เพื่อใช้เป็นสนามเด็กเล่นสำหรับทดลอง Redis Pub / Sub แต่ดังที่ได้กล่าวไว้ก่อนหน้านี้แนวคิดนี้สามารถนำไปใช้ในภาษาโปรแกรมยอดนิยมอื่น ๆ ได้เกือบทั้งหมด

อาร์เรย์ของความเป็นไปได้: คำแนะนำเกี่ยวกับการจับคู่รูปแบบทับทิม

ส่วนหลัง

อาร์เรย์ของความเป็นไปได้: คำแนะนำเกี่ยวกับการจับคู่รูปแบบทับทิม
การจัดลำดับความสำคัญของ Backlog ของผลิตภัณฑ์กับผู้มีส่วนได้ส่วนเสียหลักหลายราย: กรณีศึกษา

การจัดลำดับความสำคัญของ Backlog ของผลิตภัณฑ์กับผู้มีส่วนได้ส่วนเสียหลักหลายราย: กรณีศึกษา

กระบวนการและเครื่องมือ

โพสต์ยอดนิยม
เอกสาร Agile: การปรับสมดุลความเร็วและการรักษาความรู้
เอกสาร Agile: การปรับสมดุลความเร็วและการรักษาความรู้
ทำลายหลักการออกแบบ (ด้วยอินโฟกราฟิก)
ทำลายหลักการออกแบบ (ด้วยอินโฟกราฟิก)
วิธีจัดโครงสร้างลำดับชั้นการพิมพ์ที่มีประสิทธิภาพ
วิธีจัดโครงสร้างลำดับชั้นการพิมพ์ที่มีประสิทธิภาพ
ฮาร์ดแวร์ที่คล่องตัวพร้อมการพัฒนาซอฟต์แวร์ในตัว
ฮาร์ดแวร์ที่คล่องตัวพร้อมการพัฒนาซอฟต์แวร์ในตัว
วิธีการรวม OAuth 2 เข้ากับ Django / DRF Back-end ของคุณโดยไม่บ้า
วิธีการรวม OAuth 2 เข้ากับ Django / DRF Back-end ของคุณโดยไม่บ้า
 
GWT Toolkit: สร้างส่วนหน้า JavaScript ที่มีประสิทธิภาพโดยใช้ Java
GWT Toolkit: สร้างส่วนหน้า JavaScript ที่มีประสิทธิภาพโดยใช้ Java
แหล่งข้อมูลสำหรับธุรกิจขนาดเล็กสำหรับ COVID-19: เงินกู้เงินช่วยเหลือและสินเชื่อ
แหล่งข้อมูลสำหรับธุรกิจขนาดเล็กสำหรับ COVID-19: เงินกู้เงินช่วยเหลือและสินเชื่อ
Libation Frontiers: เจาะลึกอุตสาหกรรมไวน์โลก
Libation Frontiers: เจาะลึกอุตสาหกรรมไวน์โลก
เรียนรู้ Markdown: เครื่องมือการเขียนสำหรับนักพัฒนาซอฟต์แวร์
เรียนรู้ Markdown: เครื่องมือการเขียนสำหรับนักพัฒนาซอฟต์แวร์
พบกับ Phoenix: กรอบงานคล้ายรางสำหรับเว็บแอปสมัยใหม่บน Elixir
พบกับ Phoenix: กรอบงานคล้ายรางสำหรับเว็บแอปสมัยใหม่บน Elixir
โพสต์ยอดนิยม
  • อะไรทำให้เกิดวิกฤตหนี้กรีก
  • กลยุทธ์คือการออกแบบโมเดลธุรกิจ
  • llc s corp c corp
  • วิธีการรับลูกค้าที่ปรึกษา
  • บริษัท เอส vs บริษัท ซี vs llc
  • วิธีการเรียนรู้ angularjs ตั้งแต่เริ่มต้น
หมวดหมู่
  • ทีมแบบกระจาย
  • เคล็ดลับและเครื่องมือ
  • ชีวิตนักออกแบบ
  • นวัตกรรม
  • © 2022 | สงวนลิขสิทธิ์

    portaldacalheta.pt