国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

使用 Golang 構(gòu)建實時通知系統(tǒng) - 分步通知系統(tǒng)設(shè)計指南

在本文中,我的目標(biāo)是對 Golang 進行深入探索,重點關(guān)注項目結(jié)構(gòu)、軟件原理和并發(fā)性。

Golang通知系統(tǒng)設(shè)計示意圖

Go 初學(xué)者面臨的常見挑戰(zhàn)

我一直在從事一個涉及向客戶提供實時通知的副項目。雖然這個名為 Crisp 的項目對于一篇文章來說過于復(fù)雜,但我將討論其主要功能并深入研究多核應(yīng)用程序的各個方面。在繼續(xù)之前,讓我們先解決 Go 初學(xué)者面臨的一些常見挑戰(zhàn),以及在實現(xiàn)通知系統(tǒng)時如何避免這些挑戰(zhàn)。

單一職責(zé)

當(dāng)開始一個新的 Golang 項目時,考慮到 Go 是一種靜態(tài)類型語言是至關(guān)重要的。如果您從一個糟糕的項目結(jié)構(gòu)開始并堅持繼續(xù)下去,那可能是不可原諒的。許多從動態(tài)語言過渡的開發(fā)人員面臨著 Go 包和循環(huán)依賴的挑戰(zhàn)。這通常是由于不遵守單一責(zé)任原則造成的。

基本的通知系統(tǒng)由三個主要部分組成:

  1. 客戶端和服務(wù)器之間的通信(WebSocket、HTTP 或 gRPC)。

  2. 用于存儲用戶上線時的訪問通知的存儲。

  3. 向客戶端發(fā)出信號以接收實時通知。

這些部分中的每一個都應(yīng)該是一個單獨的包,因為它們在系統(tǒng)中具有不同的職責(zé)。雖然可以將它們?nèi)糠旁谝粋€包中,但職責(zé)的分離不僅僅涉及性能;還涉及性能。這關(guān)系到開發(fā)人員的效率。這種分離與依賴注入相結(jié)合,可以快速測試和開發(fā)系統(tǒng)的每個部分,使團隊合作和并行工作更易于管理。

依賴注入

這些部分如何相互作用?他們通過接口來做到這一點。當(dāng)我們想要一個使用存儲和信號包來構(gòu)建業(yè)務(wù)邏輯的服務(wù)時,它不依賴于這些包的類(或 Golang 中的結(jié)構(gòu))名稱。相反,它通過它們的接口依賴于它們。這種方法有利于開發(fā)和測試。

通過依賴接口的實現(xiàn)而不是完全實現(xiàn)的結(jié)構(gòu),我們可以在測試期間輕松模擬接口。例如,當(dāng)處理依賴于另一個服務(wù)的服務(wù)時,您可以模擬該服務(wù)端點的響應(yīng),而無需在本地計算機上運行全新的應(yīng)用程序及其所有依賴項。這種關(guān)注點分離使開發(fā)人員能夠?qū)W⒂谒麄兊奶囟ㄈ蝿?wù),從而使團隊合作更加高效。

寫一個簡單的示例

Golang 為單元測試提供了一個簡單而強大的結(jié)構(gòu)。測試可以與實現(xiàn)一起編寫,以維護代碼組織。讓我們考慮一個例子。我們想要創(chuàng)建并測試一個序列化包,該包從數(shù)據(jù)庫(通常稱為存儲庫)檢索行并將這些行轉(zhuǎn)換為可序列化的結(jié)構(gòu)。為了實現(xiàn)這一目標(biāo),我們討論了一個滿足我們要求的接口。

package repository

import (
   "context"
   "errors"
)

var (
   ErrNotFound = errors.New("article not found")
)

type Article struct {
   ID      uint64
   Title   string
   Content string
}

type ArticleRepository interface {
   ByID(ctx context.Context, id int) (Article, error)
}

我們可以將錯誤和實體定義(例如 Article)存儲在單獨的包中。在本教程中,我們將它們放在一起?,F(xiàn)在,讓我們實現(xiàn)序列化程序包,請記住,此實現(xiàn)可能無法準(zhǔn)確反映現(xiàn)實世界的序列化程序。

type SimpleSummaryArticle struct {
    ID      uint64 `json:"id"`
    Title   string `json:"title"`
    Summary string `json:"summary"`
    More    string `json:"more"`
 }
 
 type Article struct {
    articles          repository.ArticleRepository
    summaryWordsLimit int
 }
 
 func NewArticle(articles repository.ArticleRepository, summaryWordsLimit int) *Article {
    return &Article{articles: articles, summaryWordsLimit: summaryWordsLimit}
 }
 
 func (a *Article) ByID(ctx context.Context, id uint64) (SimpleSummaryArticle, error) {
    article, err := a.articles.ByID(ctx, id)
    if err != nil {
       return SimpleSummaryArticle{}, fmt.Errorf("error while retrieving a single article by id: %w", err)
    }
    return SimpleSummaryArticle{
       ID:      article.ID,
       Title:   article.Title,
       Summary: a.summarize(article.Content),
       More:    fmt.Sprintf("https://site.com/a/%d", article.ID),
    }, nil
 }
 
 func (a *Article) summarize(content string) string {
    words := strings.Split(strings.ReplaceAll(content, "\n", " "), " ")
    if len words > a.summaryWordsLimit {
       words = words[:a.summaryWordsLimit]
    }
    return strings.Join(words, " ")
 }

此代碼從存儲庫檢索數(shù)據(jù)并將其轉(zhuǎn)換為所需的格式。正如您所看到的,我們可以summarize有效地測試該方法。在Golang中,測試可以放置在帶有_test.go后綴的文件中。例如,如果我們的主文件名為article.go,則測試文件應(yīng)命名為article_test.go。在測試文件中,我們?yōu)槲恼麓鎯靹?chuàng)建了一個模擬:

type mockArticle struct {
    items map[uint64]repository.Article
 }
 
 func (m *mockArticle) ByID(ctx context.Context, id uint64) (repository.Article, error) {
    val, has := m.items[id]
    if !has {
       return repository.Article{}, repository.ErrNotFound
    }
    return val, nil
 }

我們可以輕松地使用這個模擬來測試我們的序列化程序包:

func TestArticle_ByID(t *testing.T) {
    ma := &mockArticle{items: map[uint64]repository.Article{
       1: {
          ID:      1,
          Title:   "Title#1",
          Content: "content of the first article.",
       },
    }}
    a := NewArticle(ma, 3)
 
    _, err := a.ByID(context.Background(), 10)
    assert.ErrorIs(t, repository.ErrNotFound, err)
 
    item, err := a.ByID(context.Background(), 1)
    assert.Equal(t, "https://site.com/a/1", item.More)
    assert.Equal(t, uint64(1), item.ID)
    assert.Equal(t, "content of the", item.Summary)
 }

對于斷言,我們使用了該github.com/stretchr/testify/assert包。然而,代碼有一個重要問題:它沒有利用接口來描述序列化器。如果另一個包需要這些序列化器,則需要進行更改。請記住這一點。

讓我們寫一個基準(zhǔn)

Golang 中的基準(zhǔn)測試很簡單。Golang 提供了用于編寫基準(zhǔn)測試的強大實用程序?;鶞?zhǔn)測試與測試放在相同的測試文件中,但以“Benchmark”為前綴,并采用*testing.B包含N屬性的參數(shù),指示函數(shù)應(yīng)執(zhí)行多少次。

func BenchmarkArticle(b *testing.B) {
    ma := &mockArticle{items: map[uint64]repository.Article{
       1: {
          ID:      1,
          Title:   "Title#1",
          Content: "content of the first article.",
       },
    }}
    a := NewArticle(ma, 3)
 
    for
 
  i := 0; i < b.N; i++ {
       a.ByID(context.Background(), 10)
    }
 }

該基準(zhǔn)測試評估我們的序列化器的性能。結(jié)果顯示,序列化一行平均需要 15.64 納秒。讓我們實現(xiàn)“使用 Golang 構(gòu)建 Uber 等在線出租車應(yīng)用程序 - 第 3 部分:Redis 來救援!”一文中的示例并對其進行基準(zhǔn)測試。

如果帖子存儲在數(shù)組中,則代碼需要檢查幾乎每個帖子以找到所需的帖子。在最壞的情況下,可能需要進行 10,000 次比較。如果服務(wù)器每秒可以處理 1,000 次比較,則需要 10 秒。讓我們放大影響。

按順序存儲帖子并使用二分搜索等算法將顯著減少所需的比較次數(shù)。在最好的情況下,只需 100 毫秒。

如果我們使用映射來存儲帖子,則每次查找都將是一條指令,導(dǎo)致整個頁面需要 10 毫秒。

以下是兩種具有相似搜索行為的算法。它們獲取一個整數(shù)切片和一個數(shù)字,并返回給定數(shù)字的索引,如果未找到,則返回 -1。

// CheckEveryItem 在切片中查找給定的查找參數(shù),如果存在則返回其索引。
// 否則,返回-1。
func CheckEveryItem(items []int, lookup int) int {
    for i := 0; i < len(items); i++ {
       if items[i] == lookup {
          return i
       }
    }
    return -1
 }
 
 // BinarySearch 期望接收排序后的切片并相應(yīng)地查找給定值的索引。
 func BinarySearch(items []int, lookup int) int {
    left := 0
    right := len(items) - 1
    for {
       if left == lookup {
          return left
       }
       if right == lookup {
          return right
       }
 
       center := (right + left) / 2
       if items[center] == lookup {
          return center
       }
       if center > lookup {
          right = center
       }
       if center < lookup {
          left = center
       }
       if left >= right-1 {
          return -1
       }
    }
 }

兩種算法具有相似的行為,它們都采用整數(shù)切片和一個數(shù)字作為輸入。您可以為此行為定義一個類型:

type Algorithm func(items []int, lookup int) int

這種類型允許您編寫一次測試和基準(zhǔn)測試,而不是為每個算法重復(fù)它們。以下是如何為這些算法編寫測試的示例:

func testAlgorithm(alg Algorithm, t *testing.T) {
    items := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
    for i := 0; i <= 9; i++ {
       assert.Equal(t, i, alg(items, i))
    }
    assert.Equal(t, -1, alg(items, 100))
 }

該功能benchmarkAlgorithm類似,但它執(zhí)行基準(zhǔn)測試:

func benchmarkAlgorithm(alg Algorithm, b *testing.B) {
    totalItems := int(1e3)
    items := make([]int, totalItems)
    for i := 0; i < totalItems; i++ {
       items[i] = i
    }
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
       lookup := rand.Intn(totalItems - 1)
       alg(items, lookup)
    }
 }

這些基準(zhǔn)函數(shù)創(chuàng)建一個包含 1,000 個成員的切片,并搜索該范圍內(nèi)的隨機數(shù)。b.ResetTimer()確保創(chuàng)建大切片不會影響基準(zhǔn)測試結(jié)果非常重要。

以下是基準(zhǔn)函數(shù):

func BenchmarkCheckEveryItem(b *testing.B) {
    benchmarkAlgorithm(CheckEveryItem, b)
 }
 
 func BenchmarkBinarySearch(b *testing.B) {
    benchmarkAlgorithm(BinarySearch, b)
 }

現(xiàn)在,讓我們運行這些測試來評估每種算法的性能。結(jié)果表明,該CheckEveryItem算法耗時 143.7 ns/op,BinarySearch完成耗時 58.54 ns/op。然而,這些測試的目的不僅僅是節(jié)省幾納秒。讓我們將切片的大小增加到一百萬:

func benchmarkAlgorithm(alg Algorithm, b *testing.B) {
    totalItems := int(1e6)
    items := make([]int, totalItems)
    // (其余代碼保持不變)
 }

對于 100 萬個項目,該CheckEveryItem算法需要 199 μs/op,而仍BinarySearch保持在納秒范圍內(nèi),為 145.6 ns/op。讓我們更進一步,用一億個項目:

func benchmarkAlgorithm(alg Algorithm, b *testing.B) {
    totalItems := int(1e8)
    items := make([]int, totalItems)
    // (其余代碼保持不變)
 }

由于二分搜索是一種對數(shù)算法,因此它僅用 302.6 ns/op 就完成了基準(zhǔn)測試。相比之下,CheckEveryItem花費的時間明顯更長,為 28 ms/op (28,973,093 ns/op)。

這證明了針對特定任務(wù)使用高效算法的重要性。這些基準(zhǔn)測試展示了為您的應(yīng)用程序選擇正確的數(shù)據(jù)結(jié)構(gòu)和算法的好處。

當(dāng)然,這里有完整的內(nèi)容和代碼供您參考:

實施清晰通知系統(tǒng)

在本教程中,我們將實現(xiàn)一個名為 Crisp 的通知系統(tǒng)。該系統(tǒng)遵循的設(shè)計是將新通知發(fā)送到服務(wù)器、存儲,然后向相關(guān)客戶發(fā)出信號。我們將探索 Crisp 的代碼,包括存儲包、實體包和信號包,它們是通知系統(tǒng)的關(guān)鍵組件。我們還將討論并發(fā)注意事項并演示如何使用通道和切片來有效管理通知。

收納包

存儲包管理通知的存儲。它定義了一個接口并提供了兩個實現(xiàn)。該包根據(jù)您的要求使用通道或切片存儲通知。讓我們仔細(xì)看看存儲包:

var ErrEmpty = errors.New("no notifications found")

type Storage interface {
   Push(ctx context.Context, clientID int, notification entity.Notification) error
   Count(ctx context.Context, clientID int) (int, error)
   Pop(ctx context.Context, clientID int) (entity.Notification, error)
   PopAll(ctx context.Context, clientID int) ([]entity.Notification, error)
}

在此包中,Push添加新通知、Count返回客戶端的通知數(shù)量、Pop檢索單個通知以及PopAll檢索客戶端的所有通知。

實體包

實體包定義了通知的結(jié)構(gòu)。它包括Notification接口和一些示例實現(xiàn):

type Notification interface {
    IsNotification()
 }
 
 type BaseNotification struct {
    CreatedAt time.Time `json:"createdAt"`
 }
 
 func (BaseNotification) IsNotification() {}
 
 type UnreadWorkRequest struct {
    BaseNotification
    WorkID int    `json:"workID"`
    Title  string `json:"title"`
 }
 
 type UnreadMessagesNotification struct {
    BaseNotification
    Count int `json:"count"`
 }

在這里,我們定義了Notification接口并提供了一些通知類型,例如UnreadWorkRequest和UnreadMessagesNotification。您可以根據(jù)應(yīng)用程序的需求添加更多通知類型。

存儲實現(xiàn)

存儲包提供兩種實現(xiàn):一種使用通道,另一種使用切片。

使用渠道:

type memoryWithChannel struct {
    storage *sync.Map
    size    int
 }
 
 func NewMemoryWithChannel(size int) Storage {
    return &memoryWithChannel{
       storage: new(sync.Map),
       size:    size,
    }
 }
 
 // 使用通道的 Push、Count、Pop 和 PopAll 函數(shù)...

使用切片:

type userStorage struct {
    mu            *sync.Mutex
    notifications []entity.Notification
 }
 
 type memoryWithList struct {
    size    int
    storage *sync.Map
 }
 
 func NewMemoryWithList(size int) Storage {
    return &memoryWithList{
       size:    size,
       storage: new(sync.Map),
    }
 }
 
 //使用切片的 Push、Count、Pop 和 PopAll 函數(shù)...

兩種實現(xiàn)都支持相同的功能,但底層數(shù)據(jù)結(jié)構(gòu)不同。在處理并發(fā)時,必須考慮競爭條件和線程安全。

使用并發(fā)的技巧:

  • 通道是線程安全的;您可以在多個線程中同時讀取和寫入它們。

  • Go 中的默認(rèn)映射不是線程安全的。要管理并發(fā)訪問,您可以使用sync.Map線程安全的 。

  • a 的內(nèi)容sync.Map不是線程安全的。您應(yīng)該為每個切片使用互斥體。

  • 該len函數(shù)是線程安全的。

  • 通道具有固定大小并預(yù)先分配內(nèi)存,這與可以動態(tài)調(diào)整大小的切片和映射不同。

處理競爭條件:

使用并發(fā)時處理競爭條件至關(guān)重要。以下是潛在競爭條件的示例:

func (m *memoryWithChannel) PopAll(ctx context.Context, clientID int) ([]entity.Notification, error) {
    c := m.get(clientID)
    l := len(c)
    items := make([]entity.Notification, l)
    for i := 0; i < l; i++ {
       items[i] = <-c
    }
    return items, nil
 }

在此示例中,兩個并發(fā)請求可能len(c)同時調(diào)用,導(dǎo)致兩個請求都嘗試從通道檢索 100 個項目。這可能會導(dǎo)致僵局。基于切片的實現(xiàn)不存在這個問題。

測試和基準(zhǔn)測試:

為了確保您的代碼性能良好并且不會導(dǎo)致內(nèi)存泄漏,您可以編寫測試和基準(zhǔn)測試。以下是測試和基準(zhǔn)測試的示例:

func testNewMemory(m Storage, t *testing.T) {
    // Test code...
 }
 
 func benchmarkMemory_PushAverage(m Storage, b *testing.B) {
    // Benchmark code...
 }
 
 func benchmarkMemory_PushNewItem(m Storage, b *testing.B) {
    // Benchmark code...
 }

您可以使用基準(zhǔn)測試來衡量不同場景的性能和內(nèi)存使用情況。

信號包

信號包處理向客戶發(fā)送新通知的信號。它利用信道來發(fā)送信號。這是信號包的代碼:

var (
    ErrEmpty = errors.New("no topic found")
 )
 
 type Signal interface {
    Subscribe(id string) (<-chan struct{}, func(), error)
    Publish(id string) error
 }

該Subscribe函數(shù)返回一個只讀通道和一個取消函數(shù)??蛻羰褂迷撏ǖ澜邮胀ㄖ?,取消功能用于清理資源。該Publish功能向訂閱客戶發(fā)出通知信號。

信號包(續(xù))

信號包繼續(xù):

type topic struct {
    listeners []chan<- struct{}
    mu        *sync.Mutex
 }
 
 type signal struct {
    listeners *sync.Map
    topicSize int
 }
 
 func NewSignal() Signal {
    return &signal{
       listeners: new(sync.Map),
    }
 }
 
 // 用于信令的訂閱和發(fā)布功能...

脆片包裝

Crisp 包作為通知系統(tǒng)的核心。它使用存儲和信號包為用戶提供監(jiān)聽通知和推送新通知的功能。這是 Crisp 包的代碼:

type Crisp struct {
    Storage storage.Storage
    Signal  signal.Signal
 
    defaultTimeout time.Duration
 }
 
 func NewCrisp(str storage.Storage, sig signal.Signal) *Crisp {
    return &Crisp{
       Storage: str,
       Signal:  sig,
       defaultTimeout: 2 * time.Minute,
    }
 }
 
 // GetNotifications 和通知函數(shù)...

該GetNotifications功能允許用戶檢索他們的通知。它處理長輪詢以實時或在用戶重新連接時提供通知。該Notify功能允許客戶端將新通知推送到系統(tǒng)中。

HTTP服務(wù)器

HTTP 服務(wù)器為客戶端提供了一種與 Crisp 包通信的方式。這是 HTTP 服務(wù)器的代碼:

func (s *Server) listen(c echo.Context) error {
    // 監(jiān)聽代碼...
 }
 
 // NotifyRequest 結(jié)構(gòu)體和通知函數(shù)...

該listen函數(shù)向客戶端提供通知,并在必要時使用長輪詢。該notify功能允許客戶端將新通知推送到系統(tǒng)中。

有了這個結(jié)構(gòu),您就可以根據(jù)需要自定義通信層以使用各種方法,例如 HTTP、終端或其他方法。將通信問題與應(yīng)用程序的其他部分分開可以提供靈活性和可擴展性。

Crisp 通知系統(tǒng)的代碼到此結(jié)束。您可以調(diào)整和擴展此代碼以構(gòu)建強大的應(yīng)用程序通知系統(tǒng)。

或者閱讀文章更加深入去了解: 使用 Go 和 gRPC 構(gòu)建生產(chǎn)級微服務(wù) - 帶有示例的分步開發(fā)人員指南 


文章來源地址http://www.zghlxwxcb.cn/article/484.html

到此這篇關(guān)于使用 Golang 構(gòu)建實時通知系統(tǒng) - 分步通知系統(tǒng)設(shè)計指南的文章就介紹到這了,更多相關(guān)內(nèi)容可以在右上角搜索或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

原文地址:http://www.zghlxwxcb.cn/article/484.html

如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實不符,請聯(lián)系站長進行投訴反饋,一經(jīng)查實,立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費用

相關(guān)文章

  • 使用 LibVLC 構(gòu)建自定義 Android 視頻播放器:分步指南,降低延遲/圖像失幀(附源碼)

    前言 在這篇博文中,我們將深入探討使用 LibVLC 庫的自定義 Android 視頻播放器的實現(xiàn)細(xì)節(jié)。本分步指南將涵蓋設(shè)置播放器、處理各種事件以及在您的 Android 應(yīng)用程序中提供無縫視頻播放體驗的基本方面。 一、LibVLC 概述: 視頻播放是許多 Android 應(yīng)用程序中的常見功能,從流媒

    2024年02月02日
    瀏覽(197)
  • 在校園跑腿系統(tǒng)小程序中,如何設(shè)計高效的實時通知與消息推送系統(tǒng)?

    在校園跑腿系統(tǒng)小程序中,如何設(shè)計高效的實時通知與消息推送系統(tǒng)?

    在校園跑腿系統(tǒng)小程序中,選擇一個適合的消息推送服務(wù)。例如,使用WebSocket技術(shù)、Firebase Cloud Messaging (FCM)、或第三方推送服務(wù)如Pusher或OneSignal等。注冊并獲取相關(guān)的API密鑰或訪問令牌。 使用選定的服務(wù)提供商的文檔,將其集成到小程序后端。這通常涉及使用相應(yīng)的SDK或AP

    2024年02月05日
    瀏覽(30)
  • 如何通過 Docker 使用 AutoGPT:分步指南

    如何通過 Docker 使用 AutoGPT:分步指南

    AutoGPT 是一個自治的 GPT-4 代理。AutoGPT 基本上是 ChatGPT 與自己對話。它可以創(chuàng)建代碼、執(zhí)行代碼,還可以訪問互聯(lián)網(wǎng)。通過與自身對話,ChatGPT 可以自行驗證源代碼、創(chuàng)建程序和調(diào)試程序。這是 AI 領(lǐng)域的最新大事。在本文中,我將幫助您逐步使用 Docker 運行 AutoGPT。 我假設(shè)您知

    2024年02月07日
    瀏覽(22)
  • 推薦系統(tǒng)架構(gòu)設(shè)計實踐:Spark Streaming+Kafka構(gòu)建實時推薦系統(tǒng)架構(gòu)

    作者:禪與計算機程序設(shè)計藝術(shù) 推薦系統(tǒng)(Recommendation System)一直都是互聯(lián)網(wǎng)領(lǐng)域一個非?;馃岬脑掝}。其主要目標(biāo)是在用戶多樣化的信息環(huán)境中,通過分析用戶的偏好、消費習(xí)慣等數(shù)據(jù),提供個性化的信息推送、商品推薦、購物指導(dǎo)等服務(wù)。如何設(shè)計一個推薦系統(tǒng)的架構(gòu)及

    2024年02月08日
    瀏覽(26)
  • 在蘋果 macOS m1和m2 使用 MATLAB分步指南

    MATLAB 是用于數(shù)值計算和數(shù)據(jù)分析的強大軟件工具。如果您是 Mac 用戶并想開始使用 MATLAB,那么本分步指南適合您。 在開始之前,請確保您的 Mac 滿足運行 MATLAB 的最低系統(tǒng)要求。 轉(zhuǎn)到 MathWorks 網(wǎng)站 (?https://www.mathworks.com/?)。單擊“下載”按鈕。創(chuàng)建一個帳戶或登錄您現(xiàn)有的

    2024年02月01日
    瀏覽(44)
  • 使用 Python 流式傳輸來自 OpenAI API 的響應(yīng):分步指南

    使用 Python 流式傳輸來自 OpenAI API 的響應(yīng):分步指南

    OpenAI API 提供了大量可用于執(zhí)行各種 NLP 任務(wù)的尖端 AI 模型。但是,在某些情況下,僅向 OpenAI 發(fā)出 API 請求可能還不夠,例如需要實時更新時。這就是服務(wù)器發(fā)送事件 (SSE) 發(fā)揮作用的地方。 SSE 是一種簡單有效的技術(shù),用于將數(shù)據(jù)從服務(wù)器實時流式傳輸?shù)娇蛻舳恕?如何在 W

    2023年04月19日
    瀏覽(39)
  • 實時消息傳送:WebSocket實現(xiàn)系統(tǒng)后臺消息實時通知

    實時消息傳送:WebSocket實現(xiàn)系統(tǒng)后臺消息實時通知

    在現(xiàn)代Web應(yīng)用中,提供實時通知對于改善用戶體驗至關(guān)重要。WebSocket技術(shù)允許建立雙向通信通道,從系統(tǒng)后臺將消息實時傳送給系統(tǒng)用戶,并在前端以彈窗的形式通知用戶。本文將深入探討如何使用WebSocket來實現(xiàn)這一功能。 WebSocket是一種雙向通信協(xié)議,與傳統(tǒng)的HTTP通信不同

    2024年02月08日
    瀏覽(28)
  • SpringBoot使用SSE進行實時通知前端

    SpringBoot使用SSE進行實時通知前端

    項目有個需求是要實時通知前端,告訴前端這個任務(wù)加載好了。然后想了2個方案,一種是用websocket進行長連接,一種是使用SSE(Sever Send Event),是HTTP協(xié)議中的一種,Content-Type為text/event-stream,能夠保持長連接。 websocket是前端既能向后端發(fā)送消息,后端也能向前端發(fā)送消息。

    2024年02月08日
    瀏覽(23)
  • Java使用websocket實現(xiàn)消息實時通知

    博客僅做學(xué)習(xí)記錄使用。 做項目中遇到這樣一個實時通知需求,因為第一次接觸這個,期間查了很多資料,看了很多博客,最后實現(xiàn)功能,查詢的博客太多,就不一一放出來了,感謝各位大佬。 websocket方式主要代碼來源于這個大佬的博客: https://blog.csdn.net/moshowgame/article/d

    2024年02月08日
    瀏覽(31)
  • 使用 LSTM 和 TensorFlow 中的注意力機制進行高級股票形態(tài)預(yù)測:Apple Inc. (AAPL) 數(shù)據(jù)分步指南

    使用 LSTM 和 TensorFlow 中的注意力機制進行高級股票形態(tài)預(yù)測:Apple Inc. (AAPL) 數(shù)據(jù)分步指南

    ????????在瞬息萬變的金融市場中,準(zhǔn)確的預(yù)測就像圣杯一樣。當(dāng)我們尋求更復(fù)雜的技術(shù)來解釋市場趨勢時,機器學(xué)習(xí)成為希望的燈塔。在各種機器學(xué)習(xí)模型中,長短期記憶(LSTM)網(wǎng)絡(luò)受到了極大的關(guān)注。當(dāng)與注意力機制相結(jié)合時,這些模型變得更加強大,尤其是在分析

    2024年04月17日
    瀏覽(24)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包