Redis Pub/Sub ile Gerçek Zamanlı Olmak

Yayınlanan: 2022-03-11

Bir web uygulamasını ölçeklendirmek, içerdiği karmaşıklık ne olursa olsun, neredeyse her zaman ilginç bir zorluktur. Ancak, gerçek zamanlı web uygulamaları benzersiz ölçeklenebilirlik sorunları ortaya çıkarır. Örneğin, istemcileriyle iletişim kurmak için WebSockets kullanan bir mesajlaşma web uygulamasını yatay olarak ölçeklendirebilmek için, tüm sunucu düğümlerini bir şekilde senkronize etmesi gerekir. Uygulama bunu akılda tutarak oluşturulmadıysa, yatay olarak ölçeklendirmek kolay bir seçenek olmayabilir.

Bu yazıda, basit bir gerçek zamanlı görüntü paylaşımı ve mesajlaşma web uygulamasının mimarisini inceleyeceğiz. Burada, gerçek zamanlı bir uygulama oluşturmaya dahil olan Redis Pub/Sub gibi çeşitli bileşenlere odaklanacağız ve bunların hepsinin genel mimarideki rollerini nasıl oynadıklarını göreceğiz.

Redis Pub/Sub ile Gerçek Zamanlı Olmak

Redis Pub/Sub ile Gerçek Zamanlı Olmak
Cıvıldamak

İşlevsellik açısından, uygulama çok hafiftir. Görüntülerin yüklenmesine ve bu görüntülere gerçek zamanlı yorumlar yapılmasına izin verir. Ayrıca, herhangi bir kullanıcı resme dokunabilir ve diğer kullanıcılar ekranlarında bir dalgalanma efekti görebilirler.

Bu uygulamanın tüm kaynak kodu GitHub'da mevcuttur.

İhtiyacımız Olan Şeyler

Gitmek

Go programlama dilini kullanacağız. Bu makale için Go'yu seçmemizin özel bir nedeni yok, ayrıca Go'nun sözdizimi temiz ve anlambilimini takip etmek daha kolay. Bir de yazarın önyargısı var tabii. Ancak, bu makalede tartışılan tüm kavramlar, seçtiğiniz dile kolayca çevrilebilir.

Go'yu kullanmaya başlamak kolaydır. İkili dağılımı resmi siteden indirilebilir. Windows kullanıyorsanız, indirme sayfalarında Go için bir MSI yükleyicisi vardır. Veya işletim sisteminizin (neyse ki) bir paket yöneticisi sunması durumunda:

Arch Linux:

 pacman -S go

Ubuntu:

 apt-get install golang

Mac OS X:

 brew install go

Bu, yalnızca Homebrew kuruluysa çalışır.

MongoDB

Redis'imiz varsa neden MongoDB kullanalım, soruyorsun? Daha önce de belirtildiği gibi, Redis bir bellek içi veri deposudur. Verileri diskte tutabilse de, Redis'i bu amaçla kullanmak muhtemelen en iyi yol değildir. Yüklenen resim meta verilerini ve mesajlarını depolamak için MongoDB kullanacağız.

MongoDB'yi resmi web sitelerinden indirebiliriz. Bazı Linux dağıtımlarında, MongoDB'yi kurmanın tercih edilen yolu budur. Yine de çoğu dağıtımın paket yöneticisi kullanılarak kurulabilir olmalıdır.

Arch Linux:

 pacman -S mongodb

Ubuntu:

 apt-get install mongodb

Mac OS X:

 brew install mongodb

Go kodumuzda mgo (mango olarak telaffuz edilir) paketini kullanacağız. Sadece savaşta test edilmekle kalmıyor, sürücü paketi gerçekten temiz ve basit bir API sunuyor.

MongoDB uzmanı değilseniz, hiç endişelenmeyin. Örnek uygulamamızda bu veritabanı hizmetinin kullanımı minimum düzeydedir ve bu makalenin odağıyla neredeyse alakasız: Pub/Sub mimarisi.

Amazon S3

Kullanıcı tarafından yüklenen görüntüleri depolamak için Amazon S3'ü kullanacağız. Amazon Web Services hazır hesabımız olduğundan ve geçici bir kova oluşturduğumuzdan emin olmak dışında burada yapılacak pek bir şey yok.

Yüklenen dosyaları yerel diskte saklamak bir seçenek değil çünkü web düğümlerimizin kimliğine hiçbir şekilde güvenmek istemiyoruz. Kullanıcıların mevcut web düğümlerinden herhangi birine bağlanabilmelerini ve aynı içeriği görebilmelerini istiyoruz.

Go kodumuzdan Amazon S3 kovası ile etkileşim kurmak için, Canonical'in goamz paketinin bir çatalı olan ve bazı farklılıklar gösteren AdRoll/goamz'ı kullanacağız.

redis

Son fakat en az değil: Redis. Dağıtımımızın paket yöneticisini kullanarak kurabiliriz:

Arch Linux:

 pacman -S redis

Ubuntu:

 apt-get install redis-server

Mac OS X:

 brew install redis

Veya kaynak kodunu alın ve kendiniz derleyin. Redis'in onu oluşturmak için GCC ve libc dışında hiçbir bağımlılığı yoktur:

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

Redis yüklenip çalıştırıldığında, bir terminal başlatın ve Redis'in CLI'sini girin:

 redis-cli

Aşağıdaki komutları girmeyi deneyin ve beklenen çıktıyı alıp almadığınızı görün:

 SET answer 41 INCR answer GET answer

İlk komut, "cevap" anahtarına karşı "41" değerini kaydeder, ikinci komut değeri artırır, üçüncü komut verilen tuşa karşı depolanan değeri yazdırır. Sonuç “42” olarak okunmalıdır.

Redis'in desteklediği tüm komutlar hakkında daha fazla bilgiyi resmi web sitesinde bulabilirsiniz.

Uygulama kodumuzdan Redis'e bağlanmak için Go paketi redigo'yu kullanacağız.

Redis Pub/Sub'a göz atın

Yayınla-abone ol modeli, mesajları rastgele sayıda göndericiye iletmenin bir yoludur. Bu mesajları gönderenler (yayıncılar) hedeflenen alıcıları açıkça tanımlamaz. Bunun yerine mesajlar, herhangi bir sayıda alıcının (abonenin) onları bekleyebileceği bir kanalda gönderilir.

Basit Yayınla-Abone Ol Yapılandırması

Bizim durumumuzda, bir yük dengeleyicinin arkasında çalışan herhangi bir sayıda web düğümüne sahip olabiliriz. Herhangi bir anda, aynı görüntüye bakan iki kullanıcı aynı düğüme bağlı olmayabilir. İşte burada Redis Pub/Sub devreye giriyor. Bir web düğümünün bir değişiklik gözlemlemesi gerektiğinde (örneğin, kullanıcı tarafından yeni bir mesaj oluşturulur), bu bilgiyi tüm ilgili web düğümlerine yayınlamak için Redis Pub/Sub'ı kullanır. Bu da, bilgileri ilgili istemcilere yayar, böylece güncellenmiş mesaj redis listesini alabilirler.

Yayınla-abone ol modeli, mesajları adlandırılmış kanallara göndermemize izin verdiği için, her bir web düğümünü Redis'e bağlayabilir ve yalnızca bağlı kullanıcılarının ilgilendiği kanallara abone olabiliriz. Örneğin, iki kullanıcının her ikisi de aynı görüntü ancak birçok web düğümünden iki farklı web düğümüne bağlıysa, yalnızca bu iki web düğümünün ilgili kanala abone olması gerekir. Bu kanalda yayınlanan herhangi bir mesaj yalnızca bu iki web düğümüne iletilecektir.

Gerçek olamayacak kadar iyi geliyor mu? Redis'in CLI'sini kullanarak deneyebiliriz. Üç redis-cli örneğini başlatın. İlk durumda aşağıdaki komutu yürütün:

 SUBSCRIBE somechannel

İkinci Redis CLI örneğinde aşağıdaki komutu yürütün:

 SUBSCRIBE someotherchannel

Redis CLI'nin üçüncü örneğinde aşağıdaki komutları yürütün:

 PUBLISH somechannel lorem PUBLISH someotherchannel ipsum

İlk örneğin nasıl “lorem” aldığına, ancak “ipsum” almadığına ve ikinci örneğin nasıl “ipsum” aldığına ancak “lorem” almadığına dikkat edin.

Redis Pub/Sub İş Başında

Bir Redis istemcisi abone moduna girdiğinde artık daha fazla kanala abone olmak veya abone olunanlardan çıkmak dışında herhangi bir işlem gerçekleştiremeyeceğini belirtmekte fayda var. Bu, her bir web düğümünün, biri Redis'e abone olarak bağlanmak için ve diğeri kanallara abone olan herhangi bir web düğümünün bunları alabilmesi için kanallarda mesajlar yayınlamak için Redis ile iki bağlantı sürdürmesi gerekeceği anlamına gelir.

Gerçek Zamanlı ve Ölçeklenebilir

Sahne arkasında neler olduğunu keşfetmeye başlamadan önce, depoyu klonlayalım:

 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 ./...

… ve derleyin:

 go build ./cmd/tonesad

Uygulamayı çalıştırmak için öncelikle .env adında bir dosya oluşturun (tercihen env-sample.txt dosyasını kopyalayarak):

 cp env-sample.txt .env

.env dosyasını gerekli tüm ortam değişkenleriyle doldurun:

 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}

Sonunda yerleşik ikili dosyayı çalıştırın:

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

Web düğümü şimdi çalışıyor ve http://localhost:9091 üzerinden erişilebilir olmalıdır.

Canlı örnek

Yatay olarak ölçeklendiğinde hala çalışıp çalışmadığını test etmek için, farklı bağlantı noktası numaralarıyla başlatarak birden çok web düğümünü döndürebilirsiniz:

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

… ve bunlara karşılık gelen URL'leri aracılığıyla erişim: http://localhost:9092 ve http://localhost:9093.

Canlı örnek

Kamera ARKASI

Uygulamanın geliştirilmesindeki her adımdan geçmek yerine, en önemli kısımlardan bazılarına odaklanacağız. Bunların tümü Redis Pub/Sub ve gerçek zamanlı sonuçlarıyla %100 alakalı olmasa da, uygulamanın genel yapısıyla hâlâ alakalıdır ve daha derine indiğimizde takip etmeyi kolaylaştıracaktır.

İşleri basit tutmak için, kullanıcı kimlik doğrulaması ile uğraşmayacağız. Yüklemeler anonim olacak ve URL'yi bilen herkes tarafından kullanılabilir. Tüm izleyiciler mesaj gönderebilir ve kendi takma adlarını seçme olanağına sahip olur. Uygun kimlik doğrulama mekanizmasını ve gizlilik özelliklerini uyarlamak önemsiz olmalıdır ve bu makalenin kapsamı dışındadır.

Kalıcı Veri

Bu kolay.

Bir kullanıcı ne zaman bir resim yüklese, onu Amazon S3'te saklarız ve ardından ona giden yolu iki kimliğe karşı MongoDB'de saklarız: bir BSON Nesne Kimliği (MongoDB'nin favorisi) ve bir başka kısa 8 karakter uzunluğundaki kimlik (gözleri biraz memnun eder). Bu, veritabanımızın “yüklemeler” koleksiyonuna girer ve şöyle bir yapıya sahiptir:

 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"` }

Tür alanı, bu "yüklemenin" içerdiği medya türünü belirtmek için kullanılır. Bu, görseller dışındaki medyayı desteklediğimiz anlamına mı geliyor? Ne yazık ki hayır. Ancak alan, burada mutlaka görüntülerle sınırlı olmadığımızı hatırlatmak için orada bırakıldı.

Kullanıcılar birbirlerine mesaj gönderirken, farklı bir koleksiyonda saklanırlar. Evet, tahmin ettiniz: “mesajlar”.

 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"` }

Buradaki tek ilginç kısım, mesajları belirli bir yüklemeyle ilişkilendirmek için kullanılan UploadID alanıdır.

API Uç Noktaları

Bu uygulamanın temel olarak üç bitiş noktası vardır.

POST /api/yüklemeler

Bu uç noktanın işleyicisi, "dosya" alanındaki görüntüyle birlikte bir "çok parçalı/form-veri" gönderimi bekler. İşleyicinin davranışı kabaca aşağıdaki gibidir:

 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, tüm hataların açıkça ele alınmasını gerektirir. Bu, prototipte yapılmıştır, ancak kritik kısımlara odaklanmak için bu makaledeki parçacıklardan çıkarılmıştır.

Bu API uç noktasının işleyicisinde, esasen dosyayı okuyoruz, ancak boyutunu belirli bir değerle sınırlıyoruz. Yükleme bu değeri aşarsa istek reddedilir. Aksi takdirde, bir BSON Kimliği oluşturulur ve yükleme varlığını MongoDB'ye kalıcı hale getirmeden önce görüntüyü Amazon S3'e yüklemek için kullanılır.

BSON Nesne Kimliklerinin oluşturulma şeklinin bir artısı ve bir eksisi vardır. İstemci tarafında oluşturulurlar. Bununla birlikte, Nesne Kimliği oluşturmak için kullanılan strateji, çarpışma olasılığını o kadar küçük yapar ki, bunları istemci tarafında oluşturmak güvenlidir. Öte yandan, oluşturulan Nesne Kimliklerinin değerleri genellikle sıralıdır ve bu, Amazon S3'ün pek hoşlanmadığı bir şeydir. Bunun kolay bir çözümü, dosya adının önüne rastgele bir dize eklemektir.

/api/uploads/{id}/mesajları GET

Bu API, son mesajları ve belirli bir zamandan sonra gönderilen mesajları almak için kullanılır.

 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) }

Bir kullanıcının tarayıcısına, o anda bakmakta olan bir yüklemede yeni bir mesaj bildirildiğinde, bu uç noktayı kullanarak yeni mesajları getirir.

POST /api/uploads/{id}/mesajlar

Ve son olarak, mesajlar oluşturan ve herkesi bilgilendiren işleyici:

 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()) }

Bu işleyici diğerlerine o kadar benzer ki, onu buraya eklemek bile neredeyse sıkıcı. Yoksa öyle mi? İşlevin en sonunda nasıl bir hub.Emit() işlev çağrısı olduğuna dikkat edin. Merkez dediğin nedir? Tüm Pub/Sub büyüsünün gerçekleştiği yer burasıdır.

Merkez: WebSockets'in Redis ile Buluştuğu Yer

Hub, WebSocket'leri Redis'in Pub/Sub kanallarıyla yapıştırdığımız yerdir. Ve tesadüfen, web sunucularımızda WebSockets'i işlemek için kullandığımız pakete yapıştırıcı denir.

Hub, esasen, bağlı tüm WebSocket'ler ile ilgilendikleri tüm kanallar arasında bir eşleme oluşturan birkaç veri yapısını korur. Örneğin, kullanıcının tarayıcı sekmesindeki belirli bir yüklenen resme işaret eden bir WebSocket, doğal olarak ilgili tüm bildirimlerle ilgilenmelidir. ona.

Hub paketi altı işlevi uygular:

  • Abone ol
  • Abonelikten ÇıkTümü
  • yaymak
  • EmitYerel
  • InitHub
  • KulpSoket

Abone Ol ve Abonelikten ÇıkTüm

 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 }

Bu fonksiyon, tıpkı bu paketteki diğerlerinin çoğu gibi, yürütülürken bir okuma/yazma muteksinde bir kilit tutar. Bu, ilkel veri yapılarının değişkenlerini, soketleri ve konuları güvenli bir şekilde değiştirebilmemiz içindir. İlk değişken olan sockets , soketleri kanal adlarına eşlerken, ikincisi, konu başlıkları , kanal adlarını soketlere eşler. Bu fonksiyonda bu eşlemeleri oluşturuyoruz. Soketin yeni bir kanal adına abone olduğunu gördüğümüzde Redis bağlantımızı subconn , subconn.Subscribe kullanarak Redis üzerinde o kanala abone oluyoruz. Bu, Redis'in o kanaldaki tüm bildirimleri bu web düğümüne iletmesini sağlar.

Ve aynı şekilde UnsubscribeAll işlevinde eşlemeyi yıkıyoruz:

 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 }

Belirli bir kanalla ilgilenen veri yapısından son soketi çıkardığımızda, subconn.Unsubscribe kullanarak Redis'teki kanaldan aboneliği iptal ediyoruz.

yaymak

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

Bu işlev, Redis'e yayın bağlantısını kullanarak kanal t'de bir m mesajı yayınlar.

EmitYerel

 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 işlevinde, Redis'e iki bağlantı oluşturuyoruz: biri bu web düğümünün ilgilendiği kanallara abone olmak için, diğeri ise mesajları yayınlamak için. Bağlantılar kurulduktan sonra, abone bağlantısı üzerinden Redis'e mesaj almayı sonsuza kadar bekleyen bir döngü ile yeni bir Go rutini başlatırız. Her mesaj aldığında, onu yerel olarak yayar (yani bu web düğümüne bağlı tüm WebSockets'e).

KulpSoket

Son olarak, HandleSocket , mesajların WebSockets üzerinden gelmesini veya bağlantı kapandıktan sonra temizlenmesini beklediğimiz yerdir:

 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]) } }) }

Ön Uç JavaScript'i

Yapıştırıcı kendi ön uç JavaScript kitaplığıyla birlikte geldiğinden, WebSockets'i kullanmak (veya WebSockets kullanılamadığında XHR yoklamasına geri dönmek) çok daha kolaydır:

 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)

İstemci tarafında, WebSocket aracılığıyla gelen herhangi bir mesajı dinliyoruz. Tutkal tüm mesajları dizeler olarak ilettiğinden, içindeki tüm bilgileri belirli kalıpları kullanarak kodlarız:

  • Yeni mesaj: "message:{messageID}"
  • Resme tıklayın: “touch:{coordX},{coordY}”, burada coordX ve coordY, kullanıcının resimdeki tıklama konumunun yüzde bazlı koordinatıdır

Kullanıcı yeni bir mesaj oluşturduğunda, yeni bir mesaj oluşturmak için “POST /api/uploads/{uploadID}/messages” API'sini kullanırız. Bu, mesajlar için omurga koleksiyonundaki create yöntemi kullanılarak yapılır:

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

Kullanıcı görsele tıkladığında, tıklamanın konumunu görüntünün genişlik ve yüksekliğinin yüzdesi olarak hesaplıyor ve bilgileri doğrudan WebSocket üzerinden gönderiyoruz.

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

Genel Bakış

Uygulamanın Mimarisine Genel Bakış

Kullanıcı bir mesaj yazıp enter tuşuna bastığında, istemci “POST /api/uploads/{id}/messages” API uç noktasını çağırır. Bu da, veritabanında bir mesaj varlığı oluşturur ve hub paketi aracılığıyla "upload:{uploadID}" kanalında Redis Pub/Sub aracılığıyla bir "message:{messageID}" dizesi yayınlar.

Redis bu dizeyi "upload:{uploadID}" kanalıyla ilgilenen her web düğümüne (abone) iletir. Bu dizeyi alan web düğümleri, kanalla ilgili tüm WebSocket'leri yineler ve dizeyi WebSocket bağlantıları aracılığıyla istemciye gönderir. Bu dizeyi alan istemciler, “GET /api/uploads/{id}/messages” kullanarak sunucudan yeni mesajlar almaya başlar.

Benzer şekilde, tıklama olaylarını görüntü üzerinde yaymak için, istemci WebSocket aracılığıyla doğrudan "dokunmatik yükleme:{uploadID} {coordX} {coordY}" gibi görünen bir mesaj gönderir. Bu mesaj, "upload:{uploadID}" kanalında aynı kanalda yayınlandığı hub paketinde sona erer. Sonuç olarak, dize yüklenen resme bakan tüm kullanıcılara dağıtılır. İstemci, bu dizeyi aldıktan sonra, koordinatları çıkarmak için onu ayrıştırır ve tıklama konumunu anlık olarak vurgulamak için büyüyen bir solma çemberi oluşturur.

Sarmak

Bu makalede, yayınla-abone ol modelinin, gerçek zamanlı web uygulamalarını büyük ölçüde ve nispeten kolaylıkla ölçeklendirme sorununu çözmeye nasıl yardımcı olabileceğine dair bir bakış gördük.

Örnek uygulama, Redis Pub/Sub ile deney yapmak için bir oyun alanı olarak hizmet etmek üzere mevcuttur. Ancak, daha önce de belirtildiği gibi, fikirler hemen hemen tüm diğer popüler programlama dillerinde uygulanabilir.