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

Go學(xué)習(xí)圣經(jīng):隊列削峰+批量寫入 超高并發(fā)原理和實操

這篇具有很好參考價值的文章主要介紹了Go學(xué)習(xí)圣經(jīng):隊列削峰+批量寫入 超高并發(fā)原理和實操。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

說在前面:

本文是《Go學(xué)習(xí)圣經(jīng)》 的第二部分。

第一部分請參見:Go學(xué)習(xí)圣經(jīng):0基礎(chǔ)精通GO開發(fā)與高并發(fā)架構(gòu)(1)

現(xiàn)在拿到offer超級難,甚至連面試電話,一個都搞不到。

尼恩的技術(shù)社群中(50+),很多小伙伴憑借 “左手云原生+右手大數(shù)據(jù)”的絕活,拿到了offer,并且是非常優(yōu)質(zhì)的offer,據(jù)說年終獎都足足18個月

從Java高薪崗位和就業(yè)崗位來看,云原生、K8S、GO 現(xiàn)在對于 高級工程師/架構(gòu)師來說,越來越重要。尼恩從架構(gòu)師視角出發(fā),基于自己的尼恩 3高架構(gòu)師知識體系和知識宇宙,寫一本《GO學(xué)習(xí)圣經(jīng)》

Go學(xué)習(xí)圣經(jīng):隊列削峰+批量寫入 超高并發(fā)原理和實操

最終的學(xué)習(xí)目標(biāo)

咱們的目標(biāo),不僅僅在于 GO 應(yīng)用編程自由,更在于 GO 架構(gòu)自由。

前段時間,一個2年小伙伴希望漲薪到18K, 尼恩把GO 語言的項目架構(gòu),給他寫入了簡歷,導(dǎo)致他的簡歷金光閃閃,脫胎換股,完全可以去拿頭條、騰訊等30K的offer, 年薪可以直接多 20W。

足以說明,GO 架構(gòu)的含金量。

另外,前面尼恩的云原生是沒有涉及GO的,但是,沒有GO的云原生是不完整的。

所以, GO語言、GO架構(gòu)學(xué)習(xí)完了之后,咱們在去打個回馬槍,完成云原生的第二部分: 《Istio + K8S CRD的架構(gòu)與開發(fā)實操》 , 幫助大家徹底穿透云原生。

Go學(xué)習(xí)圣經(jīng):隊列削峰+批量寫入 超高并發(fā)原理和實操

本文目錄

并發(fā)編程

Go 將并發(fā)結(jié)構(gòu)作為核心語言的一部分提供。

Go 協(xié)程

Go 協(xié)程(Goroutine)是 Go 語言中的一種輕量級線程實現(xiàn)。

Go 協(xié)程(Goroutine)通過在單個線程內(nèi)同時運行多個函數(shù)來實現(xiàn)并發(fā),從而避免了線程切換的開銷,并且能夠更加高效地利用系統(tǒng)資源。

與傳統(tǒng)的線程模型不同,Go 協(xié)程不是由操作系統(tǒng)內(nèi)核調(diào)度的,而是由 Go 運行時(runtime)自己調(diào)度的。

為啥是輕量級線程呢?Go 協(xié)程(Goroutine)可以避免因為線程調(diào)度引起的額外開銷,并且能夠更好地控制協(xié)程的數(shù)量和調(diào)度機制。

創(chuàng)建一個協(xié)程非常簡單,只需要在函數(shù)調(diào)用前面添加 go 關(guān)鍵字即可,例如:

func main() {
    go func() {
        fmt.Println("Hello, world!")
    }()
}

這段代碼會創(chuàng)建一個新的協(xié)程,并在其中執(zhí)行匿名函數(shù)中的代碼。

這個協(xié)程會在后臺運行,不會阻塞主線程的執(zhí)行。

創(chuàng)建Go 協(xié)程(Goroutine)

Go 程(goroutine)是由 Go 運行時管理的輕量級線程。

創(chuàng)建一個協(xié)程非常簡單,只需要在函數(shù)調(diào)用前面添加 go 關(guān)鍵字即可

go f(x, y, z)

上面的代碼,會啟動一個新的 Go 協(xié)程(Goroutine)去執(zhí)行 f(x, y, z) 函數(shù), x, yz 的求值發(fā)生在當(dāng)前的 Go協(xié) 程中,而 f 的執(zhí)行發(fā)生在新的 Go 協(xié)程中。

下面是一個例子

package cocurrent

import (
	"fmt"
	"time"
)

func say(s string) {
	for i := 0; i < 5; i++ {
		time.Sleep(100 * time.Millisecond)
		fmt.Printf("字符 %s: %d  \n", s, i)
	}
}
func GoroutineDemo() {
	go say("sync world ")
	say("hello")
}

Go學(xué)習(xí)圣經(jīng):隊列削峰+批量寫入 超高并發(fā)原理和實操

執(zhí)行的結(jié)果

Go學(xué)習(xí)圣經(jīng):隊列削峰+批量寫入 超高并發(fā)原理和實操

Go 協(xié)程在相同的地址空間中運行,因此在訪問共享的內(nèi)存時必須進(jìn)行同步。

Go標(biāo)準(zhǔn)庫 協(xié)程同步

Go 標(biāo)準(zhǔn)庫中提供了多種同步機制,可以滿足不同場景下的需求。以下是 Go 中常用的同步機制:

  1. Mutex:互斥鎖,用于保護(hù)臨界區(qū)(critical section)代碼,只允許一個協(xié)程進(jìn)入臨界區(qū)執(zhí)行代碼,其他協(xié)程需要等待。使用 sync.Mutex 類型來定義互斥鎖。
  2. RWMutex:讀寫鎖,用于保證在讀操作時允許多個協(xié)程同時訪問資源,在寫操作時只允許一個協(xié)程進(jìn)入臨界區(qū)修改資源。使用 sync.RWMutex 類型來定義讀寫鎖。
  3. WaitGroup:等待組,用于等待一組并發(fā)協(xié)程執(zhí)行完成后再繼續(xù)執(zhí)行。使用 sync.WaitGroup 類型來定義等待組。
  4. Cond:條件變量,用于在協(xié)程之間同步和通信。使用 sync.Cond 類型來定義條件變量。
  5. Once:單次執(zhí)行,用于確保某個操作只會被執(zhí)行一次。使用 sync.Once 類型來定義單次執(zhí)行。

這些同步機制都可以幫助我們更好地控制協(xié)程的執(zhí)行順序和并發(fā)訪問共享資源的安全性。在實際開發(fā)中,我們需要根據(jù)具體情況選擇合適的同步機制,并且要注意避免死鎖等問題。

Mutex互斥鎖同步

這里涉及的概念叫做 互斥(mutual exclusion) ,我們通常使用互斥鎖(Mutex)這一數(shù)據(jù)結(jié)構(gòu)來提供這種機制。

Go 中的 Mutex(互斥鎖)是一種最基本的同步機制,用于保護(hù)臨界區(qū)代碼,只允許一個協(xié)程進(jìn)入臨界區(qū)執(zhí)行代碼,其他協(xié)程需要等待。在 Go 標(biāo)準(zhǔn)庫中,可以使用 sync.Mutex 類型來定義互斥鎖。

Go 標(biāo)準(zhǔn)庫中提供了 sync.Mutex 互斥鎖類型及其兩個方法:

  • Lock
  • Unlock

我們可以通過在代碼前調(diào)用 Lock 方法,在代碼后調(diào)用 Unlock 方法來保證一段代碼的互斥執(zhí)行。參見 Inc 方法。

我們也可以用 defer 語句來保證互斥鎖一定會被解鎖。參見 Value 方法。

sync.Mutex類似于java 里邊的 Lock 顯示鎖。 關(guān)于java顯示鎖,請參見 尼恩《Java 高并發(fā)核心編程 卷2 加強版》

啰嗦一下,sync.Mutex 類型包含兩個方法:

  1. Lock():獲得互斥鎖,如果當(dāng)前鎖已經(jīng)被其他協(xié)程獲得,就會一直等待,直到鎖被釋放為止。
  2. Unlock():釋放互斥鎖,允許其他協(xié)程獲得鎖并進(jìn)入臨界區(qū)。

下面是一個使用 Mutex 實現(xiàn)協(xié)程同步的例子:

import (
    "fmt"
    "sync"
)

var counter int

func MutexDemo() {
	var wg sync.WaitGroup
	var mu sync.Mutex
	wg.Add(100)

	for i := 0; i < 100; i++ {
		go func() {
			mu.Lock()
			counter++
			mu.Unlock()
			wg.Done()
		}()
	}

	wg.Wait()

	fmt.Println("Counter:", counter)
}

在這個例子中,我們創(chuàng)建了一個計數(shù)器 counter,并啟動了 100 個協(xié)程對其進(jìn)行累加操作。由于對 counter 的訪問是并發(fā)的,因此需要使用互斥鎖 mu 來保護(hù)它,以避免不同協(xié)程之間的競爭條件。

在每個協(xié)程中,首先使用 mu.Lock() 方法獲得互斥鎖,然后對 counter 進(jìn)行加 1 操作,并最終使用 mu.Unlock() 方法釋放互斥鎖。由于只有一個協(xié)程可以同時獲得互斥鎖并進(jìn)入臨界區(qū),因此可以保證對 counter 的操作是安全的。

最后,我們使用 sync.WaitGroup 來等待所有協(xié)程執(zhí)行完畢,并輸出最終的計數(shù)器值。

WaitGroup 等待組

在 Go 中,可以使用 sync.WaitGroup 來等待一組協(xié)程完成執(zhí)行。

sync.WaitGroup 類似于java 里邊的閉鎖。 關(guān)于java閉鎖,請參見 尼恩《Java 高并發(fā)核心編程 卷2 加強版》

sync.WaitGroup 類型提供了三個方法:

  1. Add(delta int):將 WaitGroup 的計數(shù)器加上 delta 值。如果 delta 是負(fù)數(shù),則會 panic。
  2. Done():將 WaitGroup 的計數(shù)器減 1。相當(dāng)于 Add(-1)。
  3. Wait():阻塞當(dāng)前協(xié)程,直到 WaitGroup 的計數(shù)器為 0。

下面是一個使用 sync.WaitGroup 實現(xiàn)并發(fā)下載的例子:

import (
    "fmt"
    "sync"
)

func main() {
    urls := []string{
        "https://www.google.com",
        "https://www.bing.com",
        "https://www.yahoo.com",
        "https://www.baidu.com",
        "https://www.amazon.com",
        "https://www.apple.com",
    }

    var wg sync.WaitGroup

    for _, url := range urls {
        wg.Add(1)
        go func(url string) {
            defer wg.Done()
            download(url)
        }(url)
    }

    wg.Wait()

    fmt.Println("All downloads completed.")
}

func download(url string) {
    fmt.Printf("Downloading %s...\n", url)
    // 模擬下載操作
}

在這個例子中,我們定義了一個 urls 列表,包含了需要下載的網(wǎng)址。

然后創(chuàng)建了一個 sync.WaitGroup 對象 wg,并通過調(diào)用 wg.Add(1) 把計數(shù)器置為 1。

接著使用 for 循環(huán)遍歷 urls 列表,對每個網(wǎng)址都啟動一個新的協(xié)程,并在協(xié)程中調(diào)用 download() 函數(shù)來下載網(wǎng)頁內(nèi)容。

在協(xié)程中,通過 defer wg.Done() 將 WaitGroup 的計數(shù)器減 1,表示當(dāng)前協(xié)程已經(jīng)完成了下載任務(wù)。

最后,主程序調(diào)用 wg.Wait() 來等待所有協(xié)程執(zhí)行完畢,并輸出提示信息表示所有下載任務(wù)都已經(jīng)完成了。

Cond(條件變量)

Go 中的 Cond(條件變量)是一種同步機制,用于在協(xié)程之間同步和通信。

Cond 是基于 Mutex 和 WaitGroup 實現(xiàn)的,它可以讓一個或多個協(xié)程等待某個條件滿足后再執(zhí)行下一步操作。

在 Go 標(biāo)準(zhǔn)庫中,可以使用 sync.Cond 類型來定義條件變量。

sync.Cond 類型包含三個方法:

  1. Broadcast():喚醒所有正在等待條件變量的協(xié)程。
  2. Signal():喚醒一個正在等待條件變量的協(xié)程。
  3. Wait():阻塞當(dāng)前協(xié)程,并解鎖 Mutex,直到收到 Broadcast 或 Signal 信號后才會被喚醒并重新獲得 Mutex。

下面是一個使用 Cond 實現(xiàn)生產(chǎn)者-消費者模型的例子:

import (
    "fmt"
    "sync"
)

const capacity = 5

var queue []int
var mu sync.Mutex
var cond = sync.NewCond(&mu)

func main() {
    var wg sync.WaitGroup
    wg.Add(2)

    // 生產(chǎn)者協(xié)程
    go func() {
        defer wg.Done()
        for i := 0; i < capacity*2; i++ {
            mu.Lock()

            for len(queue) == capacity {
                cond.Wait()
            }

            queue = append(queue, i)
            fmt.Println("Produce:", i)

            if len(queue) == 1 {
                cond.Signal()
            }

            mu.Unlock()
        }
    }()

    // 消費者協(xié)程
    go func() {
        defer wg.Done()
        for i := 0; i < capacity*2; i++ {
            mu.Lock()

            for len(queue) == 0 {
                cond.Wait()
            }

            item := queue[0]
            queue = queue[1:]
            fmt.Println("Consume:", item)

            if len(queue) == capacity-1 {
                cond.Signal()
            }

            mu.Unlock()
        }
    }()

    wg.Wait()
}

在這個例子中,我們定義了一個長度為 5 的隊列,然后創(chuàng)建了兩個協(xié)程,一個用來生產(chǎn)數(shù)據(jù),另一個用來消費數(shù)據(jù)。

在協(xié)程中,使用 sync.Mutex 和 sync.Cond 對象來保護(hù)和同步共享資源。

在生產(chǎn)者協(xié)程中,首先調(diào)用 mu.Lock() 獲取互斥鎖,然后使用 for 循環(huán)判斷隊列是否已滿,

  • 如果已滿則調(diào)用 cond.Wait() 阻塞當(dāng)前協(xié)程,等待消費者協(xié)程喚醒。
  • 如果隊列未滿,則將數(shù)據(jù)插入隊列并打印生產(chǎn)的數(shù)據(jù)。

在插入數(shù)據(jù)后,如果隊列原來為空,則調(diào)用 cond.Signal() 喚醒一個正在等待條件變量的協(xié)程。最后,使用 mu.Unlock() 釋放互斥鎖。

在消費者協(xié)程中,首先調(diào)用 mu.Lock() 獲取互斥鎖,然后使用 for 循環(huán)判斷隊列是否為空

  • 如果為空則調(diào)用 cond.Wait() 阻塞當(dāng)前協(xié)程,等待生產(chǎn)者協(xié)程喚醒。
  • 如果隊列非空,則取出隊頭元素并打印消費的數(shù)據(jù)。

在取出數(shù)據(jù)后,如果隊列原來已滿,則調(diào)用 cond.Signal() 喚醒一個正在等待條件變量的協(xié)程。

最后,使用 mu.Unlock() 釋放互斥鎖。

channel 通道

除了標(biāo)準(zhǔn)庫 sync 包提供了協(xié)程 同步能力,還可以使用channel 來實現(xiàn)。

channel 是一種特殊的數(shù)據(jù)類型,可以用來在協(xié)程之間傳遞數(shù)據(jù),并且能夠?qū)崿F(xiàn)阻塞式等待和喚醒功能。

channel 通道(/信道)的兩個基本操作

和映射與切片一樣,channel 通道在使用前必須創(chuàng)建:

ch := make(chan int)

使用 make 函數(shù)創(chuàng)建 channel 時,第一個參數(shù)為 channel 類型,第二個參數(shù)為緩沖區(qū)大?。蛇x)。注意,第二個參數(shù)是可選的。

channel 通道在創(chuàng)建的時候, 類型參數(shù)表示 通道里邊 值的類型。所以,通道是帶有類型的管道,你可以通過它用信道操作符 <- 來發(fā)送或者接收值。

ch <- v    // 將 v 發(fā)送至信道 ch。
v := <-ch  // 從 ch 接收值并賦予 v。

“箭頭” <- 就是數(shù)據(jù)流的方向。默認(rèn)情況下,發(fā)送和接收操作在另一端準(zhǔn)備好之前都會阻塞。這使得 Go 程可以在沒有顯式的鎖或競態(tài)變量的情況下進(jìn)行同步。

在使用 channel 進(jìn)行同步時,一般有兩種基本的操作:

  1. 發(fā)送數(shù)據(jù)到 channel:通過 channel 的 <- 操作符向其中發(fā)送一個值,例如:
ch <- "hello"
  1. 從 channel 接收數(shù)據(jù):通過 channel 的 <- 操作符從其中接收一個值,例如:
msg := <- ch

當(dāng)調(diào)用 <- 操作符時,如果 channel 中沒有數(shù)據(jù)可用,則當(dāng)前協(xié)程會被阻塞,直到有數(shù)據(jù)可用為止。

下面是一個使用 channel 實現(xiàn)協(xié)程同步的例子:

func main() {
    ch := make(chan string)

    go func() {
        fmt.Println("Sending message...")
        ch <- "Hello, world!"
        fmt.Println("Message sent!")
    }()

    msg := <- ch
    fmt.Println("Received message:", msg)
}

在這個例子中,我們創(chuàng)建了一個字符串類型的 channel,然后啟動了一個新的協(xié)程。

在協(xié)程中,先打印一條信息表示正在發(fā)送消息,然后將消息發(fā)送到 channel 中。發(fā)送完成后,再打印一條信息表示消息已經(jīng)發(fā)送完畢。

在主程序中,我們等待從 channel 中接收到消息,并將其保存到變量 msg 中。接收到消息后,再打印一條信息表示已經(jīng)接收到了消息,并輸出這個消息的內(nèi)容。

注意,在這個例子中,主程序會被阻塞,直到從 channel 中接收到了消息為止。就是這句:

msg := <- ch

這是因為主程序使用 <- ch 操作符從 channel 中接收數(shù)據(jù)時,如果 channel 中沒有數(shù)據(jù)可用,它會一直阻塞等待,直到有數(shù)據(jù)可用為止。

附錄:make 函數(shù)如何使用?

在 Go 中,make 函數(shù)用于創(chuàng)建一個類型為 slice、map 或 channel 的對象,并返回其引用。make 函數(shù)的語法如下:

make(Type, size)

其中 Type 表示要創(chuàng)建的對象類型,size 則表示對象大小或緩沖區(qū)大?。▋H適用于 channel)。具體來說,make 函數(shù)有以下三種用法:

1.創(chuàng)建 slice:使用 make 函數(shù)創(chuàng)建 slice 時,第一個參數(shù)為 slice 類型,第二個參數(shù)為 slice 的長度(數(shù)量),第三個參數(shù)為 slice 的容量(可選)。例如:

// 創(chuàng)建長度為 10,容量為 20 的 int 類型 slice
s := make([]int, 10, 20)

2.創(chuàng)建 map:使用 make 函數(shù)創(chuàng)建 map 時,第一個參數(shù)為 map 類型,不需要指定大小。例如:

// 創(chuàng)建 string 到 int 的映射表
m := make(map[string]int)

3.創(chuàng)建 channel:使用 make 函數(shù)創(chuàng)建 channel 時,第一個參數(shù)為 channel 類型,第二個參數(shù)為緩沖區(qū)大?。蛇x)。例如:

// 創(chuàng)建一個無緩沖的 channel
ch := make(chan string)


// 創(chuàng)建一個可以緩存 10 個字符串的 channel
ch := make(chan string, 10)

除此之外,make 函數(shù)還可以用于創(chuàng)建一些類型的值,例如 string、array 和 struct 等。

但是,在這些情況下,通常更推薦使用字面量語法來創(chuàng)建相應(yīng)的值。

range遍歷 和 通道關(guān)閉 close

在 Go 中,可以使用 close 函數(shù)來關(guān)閉通道。關(guān)閉通道后,發(fā)送方不能再向通道中發(fā)送數(shù)據(jù),但是接收方仍然可以從通道中接收數(shù)據(jù),直到通道中所有的數(shù)據(jù)都被讀取完畢。

如果要關(guān)閉通道,生產(chǎn)者/發(fā)送者可通過 close 函數(shù)關(guān)閉一個信道,來表示沒有需要發(fā)送的值了。

close函數(shù)的使用方法,非常簡單,具體如下:

 close(ch)

消費者/接收者如何判定呢?

在消費的時候, 可以通過接收表達(dá)式返回的第二個參數(shù),來測試信道是否被關(guān)閉, 兩個返回值版本的接收表達(dá)式如下:

v, ok := <-ch

若兩個返回值中,如果沒有值可以接收、且信道已被關(guān)閉,第一個值為0值,第二個值 ok 會被設(shè)置為 false。

其中 ok 是一個 bool 類型,可以通過它來判斷 channel 是否已經(jīng)關(guān)閉,如果 channel 關(guān)閉該值為 false ,此時 v 接收到的是 channel 類型的零值。比如:channel 是傳遞的 int, 那么 v 就是 0 ;如果是結(jié)構(gòu)體,那么 v 就是結(jié)構(gòu)體內(nèi)部對應(yīng)字段的零值。

注意:

  • 只有發(fā)送者才能關(guān)閉信道,而接收者不能。
  • 向一個已經(jīng)關(guān)閉的信道發(fā)送數(shù)據(jù)會引發(fā)程序恐慌(panic)。

在 Go 中,可以使用 range 來遍歷通道中的數(shù)據(jù)。使用 range 遍歷通道時,會一直等待通道中有新的數(shù)據(jù)可讀取,直到通道被關(guān)閉或者顯式地使用 break 終止循環(huán)。

簡單來說,循環(huán) for i := range c 會不斷從信道接收值,直到它被關(guān)閉。

還要注意: 信道與文件不同,通常情況下無需關(guān)閉它們。只有在必須告訴接收者不再有需要發(fā)送的值時才有必要關(guān)閉,例如終止一個 range 循環(huán)。

下面是一個使用 range 遍歷通道的示例:

import (
    "fmt"
    "time"
)

func main() {
    ch := make(chan int)

    go func() {
        for i := 0; i < 5; i++ {
            ch <- i
            time.Sleep(time.Second)
        }
        close(ch)
    }()

    for x := range ch {
        fmt.Println("Received:", x)
    }

    fmt.Println("Done")
}

在這個例子中,我們創(chuàng)建了一個無緩沖的 channel ch,并啟動了一個協(xié)程向 ch 中發(fā)送數(shù)據(jù)。

在主程序中,使用 range 遍歷 ch 中的數(shù)據(jù),并打印接收到的數(shù)據(jù)。

當(dāng)協(xié)程向 ch 中發(fā)送完數(shù)據(jù)后,通過 close 函數(shù)關(guān)閉 ch。

在使用 range 遍歷通道時,如果通道未關(guān)閉,則循環(huán)會一直等待直到通道被關(guān)閉。當(dāng)通道被關(guān)閉后,循環(huán)會自動終止,無需使用其他方式來判斷通道是否已經(jīng)關(guān)閉。同時,如果在循環(huán)中使用 break 終止循環(huán),則需要注意在終止前將通道關(guān)閉,否則可能會導(dǎo)致死鎖等問題。

需要注意的是,使用 range 遍歷通道時,如果通道中已經(jīng)沒有數(shù)據(jù)可讀取,則循環(huán)會被阻塞,直到有新的數(shù)據(jù)可讀取或者通道被關(guān)閉。因此,在使用 range 遍歷通道時,需要確保在發(fā)送方將所有數(shù)據(jù)發(fā)送完畢后及時關(guān)閉通道,否則可能會導(dǎo)致循環(huán)一直阻塞等待。

close Channel 的一些說明

channel 不需要通過 close 來釋放資源,這個是它與 socket、file 等不一樣的地方,對于 channel 而言,唯一需要 close 的就是我們想通過 close 觸發(fā) channel 讀事件。

  • close channel對 channel阻塞無效,寫了數(shù)據(jù)不讀,直接 close,還是會阻塞的。
  • 如果 channel 已經(jīng)被關(guān)閉,繼續(xù)往它發(fā)送數(shù)據(jù)會導(dǎo)致 panic send on closed channel
  • closed 的 channel,再次關(guān)閉 close 會 panic
  • close channel 的推薦使用姿勢是在發(fā)送方來執(zhí)行,因為 channel 的關(guān)閉只有接收端能感知到,但是發(fā)送端感知不到,因此一般只能在發(fā)送端主動關(guān)閉。而且大部分時候可以不執(zhí)行 close,只需要讀寫即可。
  • 從一個已經(jīng) close 的 channel中讀取數(shù)據(jù),是可以讀取的,讀到的數(shù)據(jù)為 0
  • 讀取的 channel 如果被關(guān)閉,并不會影響正在讀的數(shù)據(jù),它會將所有數(shù)據(jù)讀取完畢,在讀取完已發(fā)送的數(shù)據(jù)后會返回元素類型的零值(zero value)。

多通道查詢select 語句/通道的多路復(fù)用

select 語句使一個 Go 程可以等待多個channel通信操作。

select 會阻塞到某個分支可以繼續(xù)執(zhí)行為止,這時就會執(zhí)行該分支。當(dāng)多個分支都準(zhǔn)備好時會隨機選擇一個執(zhí)行。

在 Go 中,可以使用 select 語句來等待多個 channel 中的數(shù)據(jù),并執(zhí)行相應(yīng)的操作。

當(dāng)有多個 channel 中的數(shù)據(jù)可讀取時,select 語句會隨機選擇一個可用的 channel,并執(zhí)行對應(yīng)的操作。

下面是一個示例代碼,演示如何使用 select 語句查詢多個 channel 中的數(shù)據(jù):

import (
    "fmt"
    "time"
)

func main() {
    ch1 := make(chan int)
    ch2 := make(chan string)

    go func() {
        for i := 0; i < 5; i++ {
            ch1 <- i
            time.Sleep(time.Second)
        }
    }()

    go func() {
        for i := 0; i < 5; i++ {
            ch2 <- fmt.Sprintf("Message %d", i)
            time.Sleep(time.Second)
        }
    }()

    for i := 0; i < 10; i++ {
        select {
        case x := <-ch1:
            fmt.Println("Received from ch1:", x)
        case x := <-ch2:
            fmt.Println("Received from ch2:", x)
        }
    }

    fmt.Println("Done")
}

在這個示例代碼中,我們創(chuàng)建了兩個 channel ch1ch2,分別用于發(fā)送 int 類型和 string 類型的數(shù)據(jù)。

在兩個協(xié)程中,分別向 ch1ch2 中發(fā)送數(shù)據(jù),并間隔一秒鐘。

在主函數(shù)中,使用 select 語句查詢 ch1ch2 中的數(shù)據(jù),并打印接收到的數(shù)據(jù)。在循環(huán)中共查詢 10 次,由于兩個協(xié)程的間隔時間不同,因此可能會先從 ch1 中接收到數(shù)據(jù),也可能會先從 ch2 中接收到數(shù)據(jù)。最后,當(dāng)所有數(shù)據(jù)被讀取完畢后,程序輸出 Done

需要注意的是,在使用 select 語句查詢多個 channel 時,如果多個 channel 同時有數(shù)據(jù)可讀取,則隨機選擇一個 channel,并執(zhí)行對應(yīng)的操作。

因此,在設(shè)計程序邏輯時,需要考慮到 channel 的使用順序可能會發(fā)生變化。此外,如果在 select 語句中同時等待多個 channel,而其中一個 channel 被關(guān)閉了,則程序仍然會等待其它的 channel,并在有數(shù)據(jù)可讀取時執(zhí)行相應(yīng)的操作。

Go的select 和 OS的select 對比

Go語言中的select 和操作系統(tǒng)中的系統(tǒng)調(diào)用select比較相似。

C語言的select系統(tǒng)調(diào)用可以同時監(jiān)聽多個文件描述符的可讀或者可寫的狀態(tài),Go 語言的select可以讓Goroutine同時等待多個Channel可讀或可寫,在多個文件或Channel狀態(tài)改變之前,select會一直阻塞當(dāng)前線程或Goroutine。

select是與switch相似的控制結(jié)構(gòu),不過select的case中的表達(dá)式必須都是channel的收發(fā)操作。當(dāng)select中的多個case同時被觸發(fā)時,會隨機執(zhí)行其中一個。

通常情況下,select語言會阻塞goroutine并等待多個Channel中的一個達(dá)到可以收發(fā)的狀態(tài)。但如果有default語句,可以實現(xiàn)非阻塞,就是當(dāng)多個channel都不能執(zhí)行的時候,運行default。

非阻塞查詢

select 默認(rèn)是阻塞的,如果所有的通道都沒有數(shù)據(jù),那么 函數(shù)就會被阻塞。

如何不進(jìn)行阻塞呢? 在 Go 中,select 語句還可以使用 default 分支,用于在沒有任何 channel 可讀取時執(zhí)行默認(rèn)操作。當(dāng)所有被查詢的 channel 都沒有數(shù)據(jù)可讀取時,select 會立即執(zhí)行 default 分支,從而實現(xiàn)不會被阻塞。

換句話來說,當(dāng) select 中的其它分支都沒有準(zhǔn)備好時,default 分支就會執(zhí)行。所以,為了在嘗試在接收時不發(fā)生阻塞,可使用 default 分支, 使用的方式如下:

select {
case i := <-c:
    // 使用 i
default:
    // 從 c 中接收會阻塞時執(zhí)行
}

下面是一個示例代碼,演示如何使用 default 分支:

import (
    "fmt"
    "time"
)

func main() {
    ch := make(chan int)

    go func() {
        time.Sleep(time.Second * 3)
        close(ch)
    }()

    for {
        select {
        case x, ok := <-ch:
            if !ok {
                fmt.Println("Channel closed")
                return
            }
            fmt.Println("Received:", x)
        default:
            fmt.Println("No data received")
            time.Sleep(time.Second)
        }
    }

    fmt.Println("Done")
}

在這個示例代碼中,我們創(chuàng)建了一個無緩沖的通道 ch,并在一個協(xié)程中等待 3 秒鐘后關(guān)閉通道。

在主函數(shù)中,使用 select 語句監(jiān)聽 ch 中的數(shù)據(jù),并打印接收到的數(shù)據(jù)。

由于 ch 一開始并沒有數(shù)據(jù)可讀取,因此 select 會立即執(zhí)行 default 分支,并打印提示信息。在 ch 被關(guān)閉后,通過判斷第二個返回值 ok 的值來確定通道是否已經(jīng)關(guān)閉。如果通道已經(jīng)關(guān)閉,則跳出循環(huán)并輸出結(jié)束信息。

需要注意的是,在使用 default 分支時,需要考慮到程序的實際需求,并合理設(shè)置等待時長。

如果等待時間過短,則可能會頻繁地執(zhí)行 default 分支,導(dǎo)致性能損失;如果等待時間過長,則可能會導(dǎo)致數(shù)據(jù)延遲等問題。

此外,在使用 default 分支時,需要注意區(qū)分通道中的零值和通道已經(jīng)關(guān)閉兩種情況,以避免出現(xiàn)不必要的錯誤。

帶緩沖的通道

在 Go 中,可以使用帶緩沖的 channel 來實現(xiàn)協(xié)程之間的同步和通信。channel 可以是 帶緩沖的。

如何創(chuàng)建帶緩沖通道呢? 將緩沖長度作為第二個參數(shù)提供給 make 來初始化一個帶緩沖的信道

在創(chuàng)建帶緩沖的 channel 時,需要在 channel 類型后面添加一個整數(shù),表示緩沖區(qū)大小。例如:

// 創(chuàng)建一個可以緩存 10 個字符串的 channel
ch := make(chan string, 10)

在這個例子中,我們創(chuàng)建了一個可以緩存 10 個字符串的 channel ch。

  • 當(dāng)有協(xié)程向 ch 發(fā)送數(shù)據(jù)時,如果緩沖區(qū)未滿,則可以直接將數(shù)據(jù)寫入緩沖區(qū);否則,發(fā)送操作會被阻塞,直到有協(xié)程從 ch 中讀取數(shù)據(jù)為止。
  • 同樣地,當(dāng)有協(xié)程從 ch 中讀取數(shù)據(jù)時,如果緩沖區(qū)非空,則可以直接從緩沖區(qū)讀取數(shù)據(jù);否則,接收操作會被阻塞,直到有協(xié)程向 ch 中發(fā)送數(shù)據(jù)為止。

帶緩沖的通道的特點是:

  • 僅當(dāng)信道的緩沖區(qū)填滿后,向其發(fā)送數(shù)據(jù)時才會阻塞。
  • 當(dāng)緩沖區(qū)為空時,接受方會阻塞。

帶緩沖的 channel 是一種有固定緩沖區(qū)大小的 channel,當(dāng)緩沖區(qū)滿時,向 channel 發(fā)送數(shù)據(jù)會被阻塞,直到有協(xié)程從 channel 中接收數(shù)據(jù)為止。相反,當(dāng)緩沖區(qū)為空時,從 channel 接收數(shù)據(jù)也會被阻塞,直到有協(xié)程向 channel 中發(fā)送數(shù)據(jù)為止。

下面是一個使用帶緩沖的 channel 實現(xiàn)生產(chǎn)者-消費者模型的例子:

import (
    "fmt"
)

const capacity = 5

func main() {
    ch := make(chan int, capacity)
    done := make(chan bool)

    // 生產(chǎn)者協(xié)程
    go func() {
        for i := 0; i < capacity*2; i++ {
            ch <- i
            fmt.Println("Produce:", i)
        }
        done <- true
    }()

    // 消費者協(xié)程
    go func() {
        for i := 0; i < capacity*2; i++ {
            item := <-ch
            fmt.Println("Consume:", item)
        }
        done <- true
    }()

    <-done
    <-done
}

在這個例子中,我們創(chuàng)建了一個緩沖區(qū)大小為 5 的 channel ch,然后創(chuàng)建了兩個協(xié)程,一個用來生產(chǎn)數(shù)據(jù)(向 ch 中發(fā)送數(shù)據(jù)),另一個用來消費數(shù)據(jù)(從 ch 中接收數(shù)據(jù))。

當(dāng)所有數(shù)據(jù)都被生產(chǎn)和消費完畢后,使用兩個 done channel 來通知主程序結(jié)束。

在生產(chǎn)者協(xié)程中,首先向 ch 中發(fā)送數(shù)據(jù),并打印生產(chǎn)的數(shù)據(jù)。如果緩沖區(qū)已滿,則發(fā)送操作會被阻塞,等待消費者協(xié)程從 ch 中讀取數(shù)據(jù)。在最后一個數(shù)據(jù)被生產(chǎn)和發(fā)送完畢后,通過 done channel 向主程序發(fā)送結(jié)束信號。

在消費者協(xié)程中,首先從 ch 中接收數(shù)據(jù),并打印消費的數(shù)據(jù)。如果緩沖區(qū)為空,則接收操作會被阻塞,等待生產(chǎn)者協(xié)程向 ch 中發(fā)送數(shù)據(jù)。在最后一個數(shù)據(jù)被消費完畢后,通過 done channel 向主程序發(fā)送結(jié)束信號。

Java BlockingQueue 和 Go channel 對比學(xué)習(xí)

Java 中的 BlockingQueue 和 Go 中的 channel 都是用于實現(xiàn)線程之間的通信的工具,但是它們在一些方面存在差異 , 主要有3點:

  • 1:實現(xiàn)方式

Java 中的 BlockingQueue 是一個接口,它有多個不同的實現(xiàn)類,如 ArrayBlockingQueue、LinkedBlockingQueue 等。這些實現(xiàn)類都是基于數(shù)組或鏈表等數(shù)據(jù)結(jié)構(gòu)實現(xiàn)的,提供了一些阻塞式的隊列操作方法。

Go 中的 channel 是語言內(nèi)置的類型,直接由編譯器實現(xiàn)。在底層,channel 是使用 waitgroup、mutex、cond 等同步原語實現(xiàn)的,而不是基于數(shù)據(jù)結(jié)構(gòu)實現(xiàn)的。

  • 2:緩存機制

Java 的 BlockingQueue 有兩種類型:有界阻塞隊列和無界阻塞隊列。有界阻塞隊列的大小是固定的,當(dāng)隊列元素數(shù)量達(dá)到上限時,生產(chǎn)者線程會被阻塞,直到隊列中有空位。無界阻塞隊列沒有容量限制,在添加元素時不會被阻塞,但是獲取元素時可能會被阻塞。

Go 的 channel 也可以分為兩種類型:帶緩存的 channel 和非緩存的 channel。帶緩存的 channel 可以緩存一定數(shù)量的元素,當(dāng)緩沖區(qū)滿時,發(fā)送操作會被阻塞。非緩存的 channel 不允許緩存元素,每個元素只能被發(fā)送和接收一次。

  • 3:阻塞機制

Java 的 BlockingQueue 提供了多種阻塞式隊列操作方法,如 puttake 等。其中,put 方法會在隊列已滿時阻塞直到有空位,而 take 方法會在隊列為空時阻塞直到有元素可取。

Go 的 channel 通過阻塞操作實現(xiàn)協(xié)程之間的同步和通信。當(dāng)發(fā)送或接收操作無法進(jìn)行時,協(xié)程會被阻塞,并暫停執(zhí)行,直到對應(yīng)的操作可以進(jìn)行為止。

Java 的 BlockingQueue 和 Go 的 channel 在實現(xiàn)方式和應(yīng)用場景不同,但是它們也有一些相同點。主要有4點:

  • 1:用途相同

Java 的 BlockingQueue 和 Go 的 channel 都是用于協(xié)程之間的通信和同步。它們允許多個協(xié)程在不同的時間段進(jìn)行讀寫操作,并提供了阻塞式的方法來確保線程安全和正確性。

  • 2:阻塞機制相同

Java 的 BlockingQueue 和 Go 的 channel 都通過阻塞操作來實現(xiàn)協(xié)程之間的同步。當(dāng)隊列為空或已滿時,生產(chǎn)者線程和消費者線程都會被阻塞,直到對應(yīng)的條件得到滿足為止。

  • 3:線程安全性相同

Java 的 BlockingQueue 和 Go 的 channel 都是線程安全的。它們都提供了阻塞式的方法,可以確保多個協(xié)程在不同的時間段進(jìn)行讀寫操作時不會發(fā)生競態(tài)條件等問題。

  • 4:可靠性相同

Java 的 BlockingQueue 和 Go 的 channel 都是可靠的。它們都能夠確保協(xié)程之間的通信和同步。同時,在使用過程中也可以通過異常捕獲等方法來處理潛在的錯誤,并保證程序的正確性和健壯性。

綜上所述,Java 的 BlockingQueue 和 Go 的 channel 在用途、阻塞機制、線程安全性和可靠性等方面存在相同點,這些共同點也是它們成為編寫多線程程序時的優(yōu)秀工具的原因之一。

SynchronousQueue VS 無緩沖channel

go 中channel 分為緩沖通道和非緩沖通道(容量為0)。

Go 語言的無緩沖channel,只有在發(fā)送操作和接收操作配對上了,發(fā)送方和接收方才能得以繼續(xù)執(zhí)行,否則將會阻塞在發(fā)送或者接收操作。

Go 語言的無緩沖channel,本質(zhì)上就是以同步的方式來傳遞數(shù)據(jù)。

所以, Go 語言的無緩沖channel 正是 Java 中的 SynchronousQueue 具有的特性。

零容量 無緩沖 有限容量
Go unbuffered channel buffered channel
Java SynchronousQueue LinkedBlockingQueue

LinkedBlockingQueue VS 緩沖通道 buffered channel

緩沖通道,顧名思義,就是能起到緩沖作用的數(shù)據(jù)類型。

相對于非緩沖通道發(fā)送操作如果沒有配對的接收操作則會阻塞的情況,緩沖通道在容量未滿的時候允許發(fā)送操作發(fā)送成功之后立即執(zhí)行后續(xù)的操作而不阻塞。

Java 中的 LinkedBlockingQueue 也具有這一特性,從命名來看就是底層基于鏈表的阻塞隊列。

操作對比

Go中,可以使用 len 獲取通道的 長度,cap 函數(shù) 獲取通道的 容量,下面是一個例子:

unbufChan := make(chan int)           // 創(chuàng)建一個非緩沖通道
fmt.Printf("容量為%d\n", cap(unbufChan)) // 容量為0
fmt.Printf("長度為%d\n", len(unbufChan)) // 長度為0

bufChan := make(chan int, 8)        // 創(chuàng)建一個緩沖通道
fmt.Printf("容量為%d\n", cap(bufChan)) // 容量為8
fmt.Printf("長度為%d\n", len(bufChan)) // 長度為0
bufChan <- 1
fmt.Printf("容量為%d\n", cap(bufChan)) // 容量為8
fmt.Printf("長度為%d\n", len(bufChan)) // 長度為1

對于 Go 語言的非緩沖通道,其容量也總是為0

其中隊列(或通道)的長度代表它當(dāng)前包含的元素值的個數(shù)。當(dāng)隊列(或通道)已滿時,其長度與容量相同。

SynchronousQueue VS 無緩沖channel 的長度和 容量比較:

容量 長度 剩余容量
SynchonousQueue 0 0 0
unbuffered channel 0 0 0

LinkedBlockingQueue VS 緩沖通道 buffered channel 的長度和 容量比較:

容量 長度 剩余容量
LinkedBlockingQueue 構(gòu)造函數(shù)指定的capacity size() remainingCapacity()
buffered channel cap(ch) len(ch) cap(ch) - len(ch)

其中隊列(或通道)的長度代表它當(dāng)前包含的元素值的個數(shù)。當(dāng)隊列(或通道)已滿時,其長度與容量相同。

go rocketmq 編程

Apache RocketMQ 是一個開源的、分布式的消息中間件系統(tǒng),支持高吞吐量和高可用性的消息傳遞。

在 Go 編程中,可以使用 Apache RocketMQ 的 Go 客戶端來實現(xiàn)與 RocketMQ 的交互。

golang 模塊安裝

go get github.com/apache/rocketmq-client-go/v2

尼恩提示:

在 goland 工具中的 模塊安裝過程,請參考后面的附錄。

實例:使用 RocketMQ 的 Go 客戶端來發(fā)送和接收消息

下面是一個簡單的示例代碼,演示如何使用 RocketMQ 的 Go 客戶端來發(fā)送和接收消息:

package batchprocess

import (
	"context"
	"fmt"
	"github.com/apache/rocketmq-client-go/v2"
	"log"
	"time"

	"github.com/apache/rocketmq-client-go/v2/consumer"
	"github.com/apache/rocketmq-client-go/v2/primitive"
	"github.com/apache/rocketmq-client-go/v2/producer"
)

// 創(chuàng)建生產(chǎn)者
const NAME_NODE = "192.168.56.121:9876"
const TOPIC = "test"

func RocketMQDemo() {

	producer, err := rocketmq.NewProducer(
		producer.WithNameServer([]string{NAME_NODE}),
		producer.WithRetry(2),
	)
	if err != nil {
		fmt.Println("create producer error:", err)
		return
	}
	err = producer.Start()
	if err != nil {
		fmt.Println("start producer error:", err)
		return
	}
	defer producer.Shutdown()

	// 發(fā)送消息
	for i := 0; i < 10; i++ {
		msg := &primitive.Message{
			Topic: TOPIC,
			Body:  []byte("Hello RocketMQ"),
		}
		res, err := producer.SendSync(context.Background(), msg)
		if err != nil {
			log.Printf("send message error: %v\n", err)
		} else {
			log.Printf("send message success: %v\n", res)
		}
		time.Sleep(time.Second)
	}

	// 創(chuàng)建消費者
	c, err := rocketmq.NewPushConsumer(
		consumer.WithNameServer([]string{NAME_NODE}),
		consumer.WithGroupName("test-group"),
	)
	if err != nil {
		fmt.Println("create consumer error:", err)
		return
	}
	err = c.Subscribe(TOPIC, consumer.MessageSelector{},
		func(ctx context.Context, msgs ...*primitive.MessageExt) (consumer.ConsumeResult, error) {
			for _, msg := range msgs {
				log.Printf("receive message: topic=%s, body=%s\n",
					msg.Topic, string(msg.Body))
			}
			return consumer.ConsumeSuccess, nil
		})
	if err != nil {
		fmt.Println("subscribe error:", err)
		return
	}
	err = c.Start()
	if err != nil {
		fmt.Println("start consumer error:", err)
		return
	}
	defer c.Shutdown()

	time.Sleep(time.Second * 10)
}

在這段代碼中,首先生產(chǎn)者并用其向 RocketMQ 中的 test 主題發(fā)送了一條消息。然后創(chuàng)建了一個消費者,訂閱了 test 主題,并在回調(diào)函數(shù)中處理接收到的消息。

在這個示例中,首先創(chuàng)建了一個 RocketMQ 生產(chǎn)者,并通過 WithNameServerWithRetry 分別設(shè)置了 NameServer 地址和重試次數(shù)等配置項。

然后,在循環(huán)中創(chuàng)建了一個消息對象,并調(diào)用 SendSync 方法發(fā)送同步消息。該方法的第一個參數(shù)是上下文對象,可以使用 context.Background() 創(chuàng)建;第二個參數(shù)是消息對象。如果發(fā)送成功,則返回一個 SendResult 對象,否則返回一個非空的錯誤對象。

最后,使用 time.Sleep() 方法等待一秒鐘,以便觀察發(fā)送結(jié)果。在真實的應(yīng)用程序中,可以根據(jù)需要調(diào)整等待時間。

綜上所述,在 RocketMQ 的 Go 版本客戶端 rocketmq-client-go 中,可以使用 SendSync 方法發(fā)送同步消息,并通過返回值和錯誤對象獲取發(fā)送結(jié)果。

需要注意的是: 在使用 RocketMQ 的 Go 客戶端時,必須先安裝和配置好 RocketMQ 的服務(wù)端,并將 Go 客戶端庫引入到項目中。同時,也需要根據(jù)實際情況進(jìn)行配置和參數(shù)設(shè)置,以確保程序能夠正常運行。

消息發(fā)送和接受的驗證
啟動 rocketmq

使用尼恩的一鍵啟動環(huán)境

Go學(xué)習(xí)圣經(jīng):隊列削峰+批量寫入 超高并發(fā)原理和實操

啟動之后的效果
Go學(xué)習(xí)圣經(jīng):隊列削峰+批量寫入 超高并發(fā)原理和實操

啟動 go 實例
package main

import (
	"crazymakercircle.com/awesomeProject/batchprocess"
	"fmt"
)

func main() {

	fmt.Println("\tcocurrent RocketMQDemo  :")

	//cocurrent.GoroutineDemo()

	fmt.Println("\tcocurrent MutexDemo  :")

	batchprocess.RocketMQDemo()
}

使用goland 直接執(zhí)行

發(fā)送消息效果

Go學(xué)習(xí)圣經(jīng):隊列削峰+批量寫入 超高并發(fā)原理和實操

消費消息效果

Go學(xué)習(xí)圣經(jīng):隊列削峰+批量寫入 超高并發(fā)原理和實操

附錄:Go 模塊的安裝和使用

Go 模塊是 Go 語言1.11版本后引入的官方包管理工具,可以自動管理依賴項和版本。

一個模塊是一些以版本作為單元相關(guān)的包的集合。模塊記錄精確的依賴要求并創(chuàng)建可復(fù)制的構(gòu)建。

通常,版本控制存儲庫僅包含在存儲庫根目錄中定義的一個模塊。(單個存儲庫中支持多個模塊,但是通常,與每個存儲庫中的單個模塊相比,這將導(dǎo)致正在進(jìn)行的工作更多)。

總結(jié)存儲庫,模塊和軟件包之間的關(guān)系:

  • 一個存儲庫包含一個或多個Go模塊。
  • 每個模塊包含一個或多個Go軟件包。
  • 每個軟件包都在一個目錄中包含一個或多個Go源文件。

下面是使用 Go 模塊安裝和管理第三方庫的步驟:

啟用 Go 模塊

在使用 Go 模塊之前,需要先啟用 Go 模塊功能。

可以通過設(shè)置 GO111MODULE 環(huán)境變量來控制 Go 是否使用模塊。要啟用模塊,請將該環(huán)境變量設(shè)置為 on,例如:

$ export GO111MODULE=on
創(chuàng)建新項目

在開始開發(fā)項目之前,需要創(chuàng)建一個新的項目目錄,并在其中初始化 Go 模塊。

可以使用 go mod init 命令來完成初始化操作,例如:

$ go mod init crazymakercircle.com/awesomeProject

Go學(xué)習(xí)圣經(jīng):隊列削峰+批量寫入 超高并發(fā)原理和實操

這個命令會創(chuàng)建一個新的 Go 模塊,并在當(dāng)前目錄中生成一個名為 go.mod 的文件。

打開看看
Go學(xué)習(xí)圣經(jīng):隊列削峰+批量寫入 超高并發(fā)原理和實操

go.mod

模塊由Go源文件樹定義,該go.mod文件在樹的根目錄中。模塊源代碼可能位于GOPATH之外。

在 Go 1.11 版本之后,Go 引入了官方的包管理工具 Go modules。使用 Go modules 可以更好地管理項目中的依賴項和版本,避免了 GOPATH 和 vendor 目錄等傳統(tǒng)的包管理方式中存在的一些問題。

在使用 Go modules 時,需要在項目根目錄中創(chuàng)建一個名為 go.mod 的文件,并在其中定義模塊路徑和依賴項等信息。

下面是一個示例的 go.mod 文件:

module example.com/myproject

go 1.16

require (
    github.com/gin-gonic/gin v1.7.4
    github.com/go-sql-driver/mysql v1.6.0
)

在這個文件中,第一行指定了當(dāng)前模塊的名稱,即 example.com/myproject。

注意,這個名稱應(yīng)該是唯一的,以便其他項目可以引用該模塊。

第二行指定了所使用的 Go 版本,即 go 1.16。

下面的 require 塊定義了所有依賴項及其版本信息。

每個依賴項都由一個完整的包名稱和版本號組成,例如 github.com/gin-gonic/gin v1.7.4。這個版本號表示需要使用的確切版本,也可以使用語義化版本號范圍來指定版本,例如 github.com/gin-gonic/gin v1.7.x。

mod文件 有四種指令:module,require,replace,exclude

在 Go modules 中,一個模塊可以包含多個軟件包,每個軟件包都有一個唯一的導(dǎo)入路徑。這個導(dǎo)入路徑由模塊路徑和從 go.mod 到軟件包目錄的相對路徑共同確定。

假設(shè)有一個名為 example.com/myproject 的模塊,其中包含兩個軟件包 foobar,它們的目錄結(jié)構(gòu)如下:

myproject/
  |- go.mod
  |- foo/
      |- foo.go
  |- bar/
      |- bar.go

在這個例子中,軟件包 foo 的導(dǎo)入路徑為 example.com/myproject/foo,軟件包 bar 的導(dǎo)入路徑為 example.com/myproject/bar

這個導(dǎo)入路徑由模塊路徑 example.com/myproject 和相對路徑 foobar 共同組成。

注意,在 Go modules 中,所有軟件包的導(dǎo)入路徑都將模塊路徑共享為公共前綴。這個公共前綴可以幫助防止命名沖突和混淆。

總之,模塊中所有軟件包的導(dǎo)入路徑是由模塊路徑和從 go.mod 到軟件包目錄的相對路徑共同決定的。對于不同的軟件包,它們的相對路徑是不同的,但它們共享相同的模塊路徑前綴。

安裝第三方庫

在 Go 模塊中安裝第三方庫與在傳統(tǒng)的 GOPATH 中安裝方式略有不同??梢允褂?go get 命令來安裝第三方庫并將其添加到當(dāng)前項目的依賴項中,例如:

$ go get github.com/gin-gonic/gin@v1.7.4

這個命令會下載指定版本的 gin 庫,并將其添加到當(dāng)前項目的依賴項中。

go get github.com/apache/rocketmq-client-go/v2

此外,還可以使用 go get 命令下載最新版本的庫,并將其添加到依賴項中,例如:

$ go get github.com/gin-gonic/gin

這個命令會下載指定版本的 rocketmq-client-go庫,并將其添加到當(dāng)前項目的依賴項中。

比如,安裝 RocketMQ client 依賴

go get github.com/apache/rocketmq-client-go/v2

Go學(xué)習(xí)圣經(jīng):隊列削峰+批量寫入 超高并發(fā)原理和實操

如果下載不來,或者設(shè)置代理試試,打開你的終端并執(zhí)行(Go 1.13 及以上)

go env -w GO111MODULE=on
go env -w GOPROXY=https://goproxy.cn,direct
下載依賴項

當(dāng)安裝了第三方庫后,還需要將其下載到本地計算機上。

可以使用 go mod download 命令來下載所有依賴項,例如:

$ go mod download

這個命令會下載當(dāng)前項目依賴的所有庫及其版本。

管理依賴項

在開發(fā)過程中,可能需要升級或刪除某些依賴項。可以使用 go mod tidy 命令來清理不再使用的依賴項,例如:

$ go mod tidy

這個命令會分析項目代碼并移除未使用的庫。

同時,還可以使用 go get -u 命令來升級依賴項到最新版本,例如:

$ go get -u github.com/gin-gonic/gin

這個命令會下載并安裝 gin 庫的最新版本,并更新 go.mod 文件中的版本號。

綜上所述,使用 Go 模塊安裝和管理第三方庫非常方便,可以自動解決依賴關(guān)系和版本問題,大大簡化了項目的依賴管理。

GoLand 中使用 Go 模塊(go mod)管理依賴項

在 GoLand 中使用 Go 模塊(go mod)管理依賴項,可以通過以下步驟進(jìn)行操作:

打開或創(chuàng)建一個 Go 項目

在 GoLand 中打開或創(chuàng)建一個 Go 項目,并確保該項目啟用了 Go 模塊功能。

要啟用 Go modules,可以通過菜單欄中的 File > Settings > Go > Go Modules 來啟用 Go modules。

在這個對話框中,可以選擇全局或項目級別的 Go modules 設(shè)置。建議選擇項目級別的設(shè)置,以避免影響其他項目。

Go學(xué)習(xí)圣經(jīng):隊列削峰+批量寫入 超高并發(fā)原理和實操

初始化 Go modules

在啟用 Go modules 后,需要初始化 Go modules??梢栽诮K端中切換到項目目錄,然后執(zhí)行以下命令來初始化 Go modules:

go mod init crazymakercircle.com/awesomeProject

這個命令會創(chuàng)建一個新的 Go 模塊,并在當(dāng)前目錄中生成一個名為 go.mod 的文件。

Go學(xué)習(xí)圣經(jīng):隊列削峰+批量寫入 超高并發(fā)原理和實操

添加依賴項

在 GoLand 中添加依賴項非常簡單??梢允褂?go get 命令來安裝第三方庫并將其添加到當(dāng)前項目的依賴項中。例如,在 GoLand 的終端窗口中輸入以下命令:

$ go get github.com/gin-gonic/gin

這個命令會下載并安裝 Gin HTTP 框架,并將其添加到 go.mod 文件中。在此之后,即可在代碼中引用 gin 庫。

比如,安裝 RocketMQ client 依賴

go get github.com/apache/rocketmq-client-go/v2

Go學(xué)習(xí)圣經(jīng):隊列削峰+批量寫入 超高并發(fā)原理和實操

解決require內(nèi)依賴全部飄紅問題

解決go.mod文件中require內(nèi)依賴全部飄紅

Go學(xué)習(xí)圣經(jīng):隊列削峰+批量寫入 超高并發(fā)原理和實操

設(shè)置 go 模塊化,并設(shè)置環(huán)境變量 GOPROXY=https://goproxy.cn,direct

Go學(xué)習(xí)圣經(jīng):隊列削峰+批量寫入 超高并發(fā)原理和實操

ok了

Go學(xué)習(xí)圣經(jīng):隊列削峰+批量寫入 超高并發(fā)原理和實操

管理依賴關(guān)系

在開發(fā)過程中,可能需要升級或刪除某些依賴項??梢允褂?go get -u 命令來升級依賴項到最新版本,并更新 go.mod 文件中的版本號,例如:

$ go get -u github.com/gin-gonic/gin

除此之外,還可以使用 GoLand 自帶的依賴關(guān)系管理工具,包括自動生成和維護(hù) go.mod 和 go.sum 文件、自動提示缺失的依賴項以及檢查依賴項的版本等。

比如:

 go get -u github.com/apache/rocketmq-client-go/v2
構(gòu)建和運行項目

在完成依賴項的添加和管理后,即可構(gòu)建和運行項目。可以使用 GoLand 的集成工具來構(gòu)建和運行項目,例如:

  • 點擊菜單欄中的 Run 按鈕或使用快捷鍵 Shift + F10 來運行程序;
  • 在編輯器窗口中右鍵單擊并選擇 Run ‘main’ 選項來運行程序;
  • 在終端中輸入 go build 命令來編譯項目,并使用 ./<executable> 命令來運行可執(zhí)行文件。

綜上所述,GoLand 提供了便捷的工具來支持使用 Go 模塊管理依賴項,包括自動化生成和維護(hù) go.mod 和 go.sum 文件、自動提示缺失的依賴項以及檢查依賴項的版本等,大大簡化了項目的依賴管理。

gorm 操作mysql

什么是ORM?

ORM框架操作數(shù)據(jù)庫都需要預(yù)先定義模型,模型可以理解成數(shù)據(jù)模型,作為操作數(shù)據(jù)庫的媒介。

例如:

  • 從數(shù)據(jù)庫讀取的數(shù)據(jù)會先保存到預(yù)先定義的模型對象,然后我們就可以從模型對象得到我們想要的數(shù)據(jù)。
  • 插入數(shù)據(jù)到數(shù)據(jù)庫也是先新建一個模型對象,然后把想要保存的數(shù)據(jù)先保存到模型對象,然后把模型對象保存到數(shù)據(jù)庫。

在golang中g(shù)orm模型定義是通過struct實現(xiàn)的,這樣我們就可以通過gorm庫實現(xiàn)struct類型和mysql表數(shù)據(jù)的映射。

提示:gorm負(fù)責(zé)將對模型的讀寫操作翻譯成sql語句,然后gorm再把數(shù)據(jù)庫執(zhí)行sql語句后返回的結(jié)果轉(zhuǎn)化為我們定義的模型對象。

gorm介紹

GORM是Golang目前比較熱門的數(shù)據(jù)庫ORM操作庫,對開發(fā)者也比較友好,使用非常方便簡單,使用上主要就是把struct類型和數(shù)據(jù)庫表記錄進(jìn)行映射,操作數(shù)據(jù)庫的時候不需要直接手寫Sql代碼,

GORM庫github地址: https://github.com/go-gorm/gorm

gorm安裝

操作MySQL需要安裝兩個包:

  • MySQL驅(qū)動包
  • GORM包 使用go get命令安裝依賴包
//安裝MySQL驅(qū)動
go get -u gorm.io/driver/mysql
//安裝gorm包
go get -u gorm.io/gorm

Go學(xué)習(xí)圣經(jīng):隊列削峰+批量寫入 超高并發(fā)原理和實操

go.md里邊,加了依賴
Go學(xué)習(xí)圣經(jīng):隊列削峰+批量寫入 超高并發(fā)原理和實操

導(dǎo)入包

import (
	"bytes"
	"fmt"
	_ "gorm.io/driver/mysql"
	_ "gorm.io/gorm"
	"sync"
	"time"
)	

gorm模型定義

gorm模型定義主要就是在struct類型定義的基礎(chǔ)上增加字段標(biāo)簽說明實現(xiàn),下面看個完整的例子。

假如有個sample表,表結(jié)構(gòu)如下


CREATE TABLE `sample` (
`id`  int(11) NOT NULL COMMENT '主鍵' ,
`title`  varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT '標(biāo)題' ,
`create_time`  datetime NULL DEFAULT NULL COMMENT '創(chuàng)建時間' ,
PRIMARY KEY (`id`)
)
ENGINE=InnoDB
DEFAULT CHARACTER SET=utf8 COLLATE=utf8_bin
ROW_FORMAT=DYNAMIC
;

模型定義如下

//字段注釋說明了gorm庫把struct字段轉(zhuǎn)換為表字段名長什么樣子。
type Sample struct {
	Id    int64  //表字段名為:id
	Title string   //表字段名為:title

	//字段定義后面使用兩個反引號``包裹起來的字符串部分叫做標(biāo)簽定義,這個是golang的基礎(chǔ)語法,不同的庫會定義不同的標(biāo)簽,有不同的含義
	CreateTime int64 `gorm:"column:create_time"` //表字段名為:create_time
}

默認(rèn)gorm對struct字段名使用Snake Case命名風(fēng)格轉(zhuǎn)換成mysql表字段名(需要轉(zhuǎn)換成小寫字母)。

根據(jù)gorm的默認(rèn)約定,上面例子只需要使用gorm:"column:create_time"標(biāo)簽定義為CreateTime字段指定表字段名,其他使用默認(rèn)值即可。

提示:Snake Case命名風(fēng)格,就是各個單詞之間用下劃線(_)分隔,例如: CreateTime的Snake Case風(fēng)格命名為create_time

3、gorm模型標(biāo)簽

通過上面的例子,大家看到可以通過類似gorm:"column:createtime"這樣的標(biāo)簽定義語法,定義struct字段的列名(表字段名)。

gorm標(biāo)簽語法:gorm:"標(biāo)簽定義"

標(biāo)簽定義部分,多個標(biāo)簽定義可以使用分號(;)分隔,例如定義列名:

gorm:"column:列名"

gorm常用標(biāo)簽如下:

標(biāo)簽 說明 例子
column 指定列名 gorm:“column:createtime”
primaryKey 指定主鍵 gorm:“column:id; PRIMARY_KEY”
- 忽略字段 gorm:“-” 可以忽略struct字段,被忽略的字段不參與gorm的讀寫操作

定義表名

可以通過定義struct類型的TableName函數(shù)實現(xiàn)定義模型的表名

接上面的例子:

//設(shè)置表名,可以通過給Food struct類型定義 TableName函數(shù),返回一個字符串作為表名
func (v Sample) TableName() string {
	return "sample"
}

建議:

默認(rèn)情況下都給模型定義表名,有時候定義模型只是單純的用于接收手寫sql查詢的結(jié)果,這個時候是不需要定義表名;手動通過gorm函數(shù)Table()指定表名,也不需要給模型定義TableName函數(shù)。

gorm.Model

GORM 定義一個 gorm.Model 結(jié)構(gòu)體,其包括字段 ID、CreatedAt、UpdatedAt、DeletedAt。

// gorm.Model 的定義
type Model struct {
    ID        uint           `gorm:"primaryKey"`
    CreatedAt time.Time
    UpdatedAt time.Time
    DeletedAt gorm.DeletedAt `gorm:"index"`
}

以將它嵌入到我們的結(jié)構(gòu)體中,就以包含這幾個字段,類似繼承的效果。

type User struct {
    gorm.Model // 嵌入gorm.Model的字段
    Name string
}

自動更新時間

GORM 約定使用 CreatedAt、UpdatedAt 追蹤創(chuàng)建/更新時間。

如果定義了這種字段,GORM 在創(chuàng)建、更新時會自動填充當(dāng)前時間。

要使用不同名稱的字段,您可以配置 autoCreateTime、autoUpdateTime 標(biāo)簽

如果想要保存 UNIX(毫/納)秒時間戳,而不是 time,只需簡單地將 time.Time 修改為 int 即可。

例子:

type User struct {
    CreatedAt time.Time // 默認(rèn)創(chuàng)建時間字段, 在創(chuàng)建時,如果該字段值為零值,則使用當(dāng)前時間填充
    UpdatedAt int       // 默認(rèn)更新時間字段, 在創(chuàng)建時該字段值為零值或者在更新時,使用當(dāng)前時間戳秒數(shù)填充
    Updated   int64 `gorm:"autoUpdateTime:nano"` // 自定義字段, 使用時間戳填納秒數(shù)充更新時間
    Updated   int64 `gorm:"autoUpdateTime:milli"` //自定義字段, 使用時間戳毫秒數(shù)填充更新時間
    Created   int64 `gorm:"autoCreateTime"`      //自定義字段, 使用時間戳秒數(shù)填充創(chuàng)建時間
}

gorm連接數(shù)據(jù)庫

gorm支持多種數(shù)據(jù)庫,這里主要介紹mysql,連接mysql主要有兩個步驟:

1)配置DSN (Data Source Name)

2)使用gorm.Open連接數(shù)據(jù)庫

1、配置DSN (Data Source Name)

gorm庫使用dsn作為連接數(shù)據(jù)庫的參數(shù),dsn翻譯過來就叫數(shù)據(jù)源名稱,用來描述數(shù)據(jù)庫連接信息。

一般都包含數(shù)據(jù)庫連接地址,賬號,密碼之類的信息。

DSN格式:

[username[:password]@][protocol[(address)]]/dbname[?param1=value1&...&paramN=valueN]

mysql連接dsn例子:

//mysql dsn格式
//涉及參數(shù):
//username   數(shù)據(jù)庫賬號
//password   數(shù)據(jù)庫密碼
//host       數(shù)據(jù)庫連接地址,可以是Ip或者域名
//port       數(shù)據(jù)庫端口
//Dbname     數(shù)據(jù)庫名
username:password@tcp(host:port)/Dbname?charset=utf8&parseTime=True&loc=Local
 
//填上參數(shù)后的例子
//username = root
//password = 123456
//host     = localhost
//port     = 3306
//Dbname   = tizi365
//后面K/V鍵值對參數(shù)含義為:
//  charset=utf8 客戶端字符集為utf8
//  parseTime=true 支持把數(shù)據(jù)庫datetime和date類型轉(zhuǎn)換為golang的time.Time類型
//  loc=Local 使用系統(tǒng)本地時區(qū)
root:123456@tcp(localhost:3306)/tizi365?charset=utf8&parseTime=True&loc=Local
 
//gorm 設(shè)置mysql連接超時參數(shù)
//開發(fā)的時候經(jīng)常需要設(shè)置數(shù)據(jù)庫連接超時參數(shù),gorm是通過dsn的timeout參數(shù)配置
//例如,設(shè)置10秒后連接超時,timeout=10s
//下面是完成的例子
root:123456@tcp(localhost:3306)/tizi365?charset=utf8&parseTime=True&loc=Local&timeout=10s
 
//設(shè)置讀寫超時時間
// readTimeout - 讀超時時間,0代表不限制
// writeTimeout - 寫超時時間,0代表不限制
root:123456@tcp(localhost:3306)/tizi365?charset=utf8&parseTime=True&loc=Local&timeout=10s&readTimeout=30s&writeTimeout=60s

2、使用gorm.Open連接數(shù)據(jù)庫

有了上面配置的dsn參數(shù),就可以使用gorm連接數(shù)據(jù)庫,下面是連接數(shù)據(jù)庫的例子

package main
 
import (
  "gorm.io/driver/mysql"
  "gorm.io/gorm"
)
 
func main()  {
    //配置MySQL連接參數(shù)
	username := "root"  //賬號
	password := "123456" //密碼
	host := "127.0.0.1" //數(shù)據(jù)庫地址,可以是Ip或者域名
	port := 3306 //數(shù)據(jù)庫端口
	Dbname := "tizi365" //數(shù)據(jù)庫名
	timeout := "10s" //連接超時,10秒
	
	//拼接下dsn參數(shù), dsn格式可以參考上面的語法,這里使用Sprintf動態(tài)拼接dsn參數(shù),因為一般數(shù)據(jù)庫連接參數(shù),我們都是保存在配置文件里面,需要從配置文件加載參數(shù),然后拼接dsn。
	dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8&parseTime=True&loc=Local&timeout=%s", username, password, host, port, Dbname, timeout)
	//連接MYSQL, 獲得DB類型實例,用于后面的數(shù)據(jù)庫讀寫操作。
	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
	if err != nil {
		panic("連接數(shù)據(jù)庫失敗, error=" + err.Error())
	}
	//延時關(guān)閉數(shù)據(jù)庫連接
	defer db.Close()
}

3、gorm調(diào)試模式

為了方便調(diào)試,了解gorm操作到底執(zhí)行了怎么樣的sql語句,開發(fā)的時候需要打開調(diào)試日志,這樣gorm會打印出執(zhí)行的每一條sql語句。

使用Debug函數(shù)執(zhí)行查詢即可

result := db.Debug().Where("username = ?", "tizi365").First(&u)

4、gorm連接池

在高并發(fā)實踐中,為了提高數(shù)據(jù)庫連接的使用率,避免重復(fù)建立數(shù)據(jù)庫連接帶來的性能消耗,會經(jīng)常使用數(shù)據(jù)庫連接池技術(shù)來維護(hù)數(shù)據(jù)庫連接。

gorm自帶了數(shù)據(jù)庫連接池使用非常簡單只要設(shè)置下數(shù)據(jù)庫連接池參數(shù)即可。

數(shù)據(jù)庫連接池使用例子:

定義tools包,負(fù)責(zé)數(shù)據(jù)庫初始化工作(備注:借助連接池說明,一般在操作數(shù)據(jù)庫時,可以將數(shù)據(jù)庫連接單獨封裝成一個包)

// 定義一個工具包,用來管理gorm數(shù)據(jù)庫連接池的初始化工作。
package tools

import (
	"fmt"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
)

// 定義全局的db對象,我們執(zhí)行數(shù)據(jù)庫操作主要通過他實現(xiàn)。
var _db *gorm.DB

// 包初始化函數(shù),golang特性,每個包初始化的時候會自動執(zhí)行init函數(shù),這里用來初始化gorm。
func init() {

	//配置MySQL連接參數(shù)
	host := "192.168.56.121" //數(shù)據(jù)庫地址,可以是Ip或者域名
	username := "root"       //賬號
	password := "123456"     //密碼
	port := 3306             //數(shù)據(jù)庫端口
	Dbname := "store"        //數(shù)據(jù)庫名
	timeout := "10s"         //連接超時,10秒

	//拼接下dsn參數(shù), dsn格式可以參考上面的語法,這里使用Sprintf動態(tài)拼接dsn參數(shù),因為一般數(shù)據(jù)庫連接參數(shù),我們都是保存在配置文件里面,需要從配置文件加載參數(shù),然后拼接dsn。
	dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8&parseTime=True&loc=Local&timeout=%s", username, password, host, port, Dbname, timeout)

	// 聲明err變量,下面不能使用:=賦值運算符,否則_db變量會當(dāng)成局部變量,導(dǎo)致外部無法訪問_db變量
	var err error
	//連接MYSQL, 獲得DB類型實例,用于后面的數(shù)據(jù)庫讀寫操作。
	_db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{})
	if err != nil {
		panic("連接數(shù)據(jù)庫失敗, error=" + err.Error())
	}

	sqlDB, _ := _db.DB()

	//設(shè)置數(shù)據(jù)庫連接池參數(shù)
	sqlDB.SetMaxOpenConns(100) //設(shè)置數(shù)據(jù)庫連接池最大連接數(shù)
	sqlDB.SetMaxIdleConns(20)  //連接池最大允許的空閑連接數(shù),如果沒有sql任務(wù)需要執(zhí)行的連接數(shù)大于20,超過的連接會被連接池關(guān)閉。
}

// 獲取gorm db對象,其他包需要執(zhí)行數(shù)據(jù)庫查詢的時候,只要通過tools.getDB()獲取db對象即可。
// 不用擔(dān)心協(xié)程并發(fā)使用同樣的db對象會共用同一個連接,db對象在調(diào)用他的方法的時候會從數(shù)據(jù)庫連接池中獲取新的連接
func GetDB() *gorm.DB {
	return _db
}

使用例子:

package main
//導(dǎo)入tools包
import tools
 
func main() {
    //獲取DB
    db := tools.GetDB()
    
    //執(zhí)行數(shù)據(jù)庫查詢操作
    u := User{}
	//自動生成sql: SELECT * FROM `users`  WHERE (username = 'tizi365') LIMIT 1
	db.Where("username = ?", "tizi365").First(&u)
}

注意:使用連接池技術(shù)后,千萬不要使用完db后調(diào)用db.Close關(guān)閉數(shù)據(jù)庫連接,這樣會導(dǎo)致整個數(shù)據(jù)庫連接池關(guān)閉,導(dǎo)致連接池沒有可用的連接。

CRUD操作

gorm 是一個 Go 語言的 ORM(Object Relational Mapping)庫,可以方便地操作數(shù)據(jù)庫。下面是 gorm 模塊的使用步驟:

插入數(shù)據(jù)

func InsertDemo() {

	// 創(chuàng)建 Sample 實例
	Sample := Sample{Id: 1, Title: "張三", CreateTime: time.Now()}
	//獲取DB
	MysqlDB := tools.GetDB()
	// 添加數(shù)據(jù)
	MysqlDB.Create(&Sample)

	// 獲取添加后的自增 ID
	fmt.Println(Sample.Id)
}

執(zhí)行結(jié)果

Go學(xué)習(xí)圣經(jīng):隊列削峰+批量寫入 超高并發(fā)原理和實操

查詢數(shù)據(jù)

func SearchDemo() {
	//獲取DB
	db := tools.GetDB()
	//獲取第一個 Sample 記錄
	var firstSample Sample
	db.First(&firstSample)
	fmt.Println("('%d','%s','%s');", firstSample.Id, firstSample.Title, firstSample.CreateTime)
	// 條件查詢
	var sample Sample
	db.Where("title = ?", "張三").First(&sample)
	fmt.Println("('%d','%s','%s');", firstSample.Id, firstSample.Title, firstSample.CreateTime)

	// 查詢所有 Sample 記錄
	var samples []Sample
	db.Find(&samples)
}

Go學(xué)習(xí)圣經(jīng):隊列削峰+批量寫入 超高并發(fā)原理和實操

更新數(shù)據(jù)

func UpdateDemo() {
	//獲取DB
	db := tools.GetDB()
	//獲取第一個 Sample 記錄
	var sample Sample
	db.Where("title = ?", "張三").First(&sample)
	fmt.Println("('%d','%s','%s');", sample.Id, sample.Title, sample.CreateTime)

	// 更新指定字段
	db.Model(&sample).Update("age", 20)

	// 更新多個字段
	db.Model(&sample).Updates(Sample{Id: 20, Title: "李四"})
}

刪除數(shù)據(jù)

func DeleteDemo() {
	//獲取DB
	db := tools.GetDB()
	//獲取第一個 Sample 記錄
	var sample Sample
	db.Where("title = ?", "張三").First(&sample)
	fmt.Println("('%d','%s','%s');", sample.Id, sample.Title, sample.CreateTime)

	// 刪除 sample 記錄
	db.Delete(&sample)
	// 根據(jù)條件刪除多個記錄
	db.Where("title = ?", "張三").Delete(&Sample{})
}

這些是 gorm 模塊的基本使用方法,可以根據(jù)實際需求進(jìn)行調(diào)整和擴展。

go與mysql數(shù)據(jù)類型關(guān)系

Go學(xué)習(xí)圣經(jīng):隊列削峰+批量寫入 超高并發(fā)原理和實操

mysql日期時間格式

Go學(xué)習(xí)圣經(jīng):隊列削峰+批量寫入 超高并發(fā)原理和實操

go 存儲 mysql TIMESTAMP格式

存:
type TestTime struct{
CreatedAt      time.Time 
}

m:=new(TestTime)
m.CreatedAt:=time.Now()

取:
go orm 取TestTime結(jié)構(gòu)體數(shù)據(jù)
str:=orm_data.CreatedAt.Format("2006-01-02 15:04:05")
str == "2019-08-27 09:35:13"

高并發(fā)實操: 消息隊列削峰解耦+ 批量寫入DB

為什么要使用消息隊列

三個最主要的應(yīng)用場景:解耦、異步、削峰

  • 削峰填谷(最主要的作用)可以削去到達(dá)系統(tǒng)的峰值流量,讓業(yè)務(wù)邏輯的處理更加緩和;但是會造成請求處理的延遲
  • 異步處理可以簡化業(yè)務(wù)流程中的步驟,提升系統(tǒng)性能;
    • 需要分清同步流程和異步流程的邊界
    • 消息存在著丟失的風(fēng)險
  • 解耦合可以將系統(tǒng)和系統(tǒng)解耦開,這樣兩個系統(tǒng)的任何變更都不會影響到另一個系統(tǒng)
削峰

傳統(tǒng)模式:并發(fā)量大的時候,所有的請求直接懟到數(shù)據(jù)庫,造成數(shù)據(jù)庫連接異常

Go學(xué)習(xí)圣經(jīng):隊列削峰+批量寫入 超高并發(fā)原理和實操

消息隊列模式:系統(tǒng)A慢慢的按照數(shù)據(jù)庫能處理的并發(fā)量,從消息隊列中慢慢拉取消息。

在生產(chǎn)中,這個短暫的高峰期積壓是允許的。

Go學(xué)習(xí)圣經(jīng):隊列削峰+批量寫入 超高并發(fā)原理和實操

解耦

傳統(tǒng)模式:系統(tǒng)間耦合性太強,如下圖所示,

系統(tǒng)A在代碼中直接調(diào)用系統(tǒng)B和系統(tǒng)C的代碼,如果將來D系統(tǒng)接入,系統(tǒng)A還需要修改代碼,過于麻煩!

Go學(xué)習(xí)圣經(jīng):隊列削峰+批量寫入 超高并發(fā)原理和實操

消息隊列模式:將消息寫入消息隊列,需要消息的系統(tǒng)自己從消息隊列中訂閱,從而系統(tǒng)A不需要做任何修改

Go學(xué)習(xí)圣經(jīng):隊列削峰+批量寫入 超高并發(fā)原理和實操

異步

傳統(tǒng)模式:一些非必要的業(yè)務(wù)邏輯以同步的方式運行,太耗費時間。

Go學(xué)習(xí)圣經(jīng):隊列削峰+批量寫入 超高并發(fā)原理和實操

消息隊列模式: 將消息寫入消息隊列,非必要的業(yè)務(wù)邏輯以異步的方式運行,加快相應(yīng)速度。

Go學(xué)習(xí)圣經(jīng):隊列削峰+批量寫入 超高并發(fā)原理和實操

實操:用GO實現(xiàn)消息隊列削峰解耦

用GO實現(xiàn)消息隊列削峰解耦,參考代碼如下:

package batchprocess


// 創(chuàng)建生產(chǎn)者
const SAMPLE_TOPIC = "sample"

func ProducerStart() {

	producer, err := rocketmq.NewProducer(
		producer.WithNameServer([]string{NAME_NODE}),
		producer.WithRetry(2),
	)
...

	// 發(fā)送消息,無限循環(huán)
	for i := 0; ; i++ {

		sample := Sample{Id: int64(i + 100), Title: "張三", CreateTime: time.Now()}
		//序列化
		json, err := json.Marshal(&sample)

		msg := &primitive.Message{
			Topic: SAMPLE_TOPIC,
			Body:  []byte(json),
		}
		res, err := producer.SendSync(context.Background(), msg)
		if err != nil {
			log.Printf("send Sample error: %v\n", err)
		} else {
			log.Printf("send Sample success: %v\n", res)
		}
		time.Sleep(time.Second)
	}
}
func ConsumerStart() {

	// 創(chuàng)建消費者
	c, err := rocketmq.NewPushConsumer(
		consumer.WithNameServer([]string{NAME_NODE}),
		consumer.WithGroupName("test-group"),
	)

	err = c.Subscribe(SAMPLE_TOPIC, consumer.MessageSelector{},
		func(ctx context.Context, msgs ...*primitive.MessageExt) (consumer.ConsumeResult, error) {

			for _, msg := range msgs {
				var sample Sample
				err := json.Unmarshal(msg.Body, &sample)
				if err != nil {
					fmt.Println(err)
				}
				fmt.Println(sample)

				messageMysqlChan <- sample // 加入通道
				log.Printf("receive Sample: topic=%s, body=%s\n",
					msg.Topic, string(msg.Body))
			}
			return consumer.ConsumeSuccess, nil
		})

	wg.Wait()
}

執(zhí)行的效果如下(后面尼恩會在 穿透云原生視頻中,進(jìn)行詳細(xì)介紹):

Go學(xué)習(xí)圣經(jīng):隊列削峰+批量寫入 超高并發(fā)原理和實操

為什么應(yīng)該使用批量插入來提高M(jìn)ySQL性能?

MySQL是一種常用的開源關(guān)系數(shù)據(jù)庫管理系統(tǒng)(RDBMS),常用于建立網(wǎng)站和應(yīng)用程序后端的數(shù)據(jù)存儲和管理系統(tǒng)。但隨著數(shù)據(jù)量的增大,MySQL的性能也會逐漸下降,此時需要使用批量插入來提高M(jìn)ySQL性能。

批量插入是指一次性向MySQL數(shù)據(jù)庫中插入多條記錄,相對于逐個插入單條記錄,批量插入可以大大提高M(jìn)ySQL的性能。那么,為什么應(yīng)該使用批量插入呢?以下是幾個原因。

  1. 減少網(wǎng)絡(luò)往返次數(shù)

MySQL是一種客戶端/服務(wù)器模式的數(shù)據(jù)庫,在客戶端插入一條記錄時,需要與MySQL服務(wù)器建立一次網(wǎng)絡(luò)連接,而這個過程將耗費時間和帶寬。如果每插入一條記錄就要建立一次網(wǎng)絡(luò)連接,那么對于大批量的數(shù)據(jù)插入將會非常低效。通過批量插入,可以減少網(wǎng)絡(luò)連接次數(shù),從而提高M(jìn)ySQL的性能。

  1. 減少SQL語句的解析次數(shù)

MySQL中,每條SQL語句都需要進(jìn)行解析并編譯成執(zhí)行計劃,這個過程也需要耗費時間。如果逐個插入單條記錄,那么每條SQL語句都需要解析和編譯,而使用批量插入,只需要解析和編譯一次SQL語句即可,從而減少了SQL語句的解析次數(shù),提高M(jìn)ySQL的性能。

  1. 減少磁盤I/O操作

MySQL將數(shù)據(jù)存儲在磁盤上,每次向磁盤寫入一條記錄都將會進(jìn)行一次磁盤I/O操作。如果逐個插入單條記錄,那么每次插入都將會進(jìn)行一次磁盤I/O操作,而使用批量插入,多條記錄將會一起寫入磁盤,從而減少了磁盤I/O操作,提高了MySQL的性能。

  1. 減少鎖的競爭

在MySQL中,插入一條記錄時需要獲取表級鎖或行級鎖,如果逐個插入單條記錄,那么每次插入都將會競爭鎖資源,從而影響MySQL的性能。使用批量插入時,多條記錄被看做一個事務(wù),只需要獲取一次鎖,從而減少了鎖的競爭,提高了MySQL的性能。

以上是使用批量插入來提高M(jìn)ySQL性能的幾個原因。但是,批量插入也存在一些缺點,例如批量插入一起錯誤時很難進(jìn)行回滾操作,可能導(dǎo)致數(shù)據(jù)的不一致性。因此,在使用批量插入時,需謹(jǐn)慎考慮。

總而言之,使用批量插入是提高M(jìn)ySQL性能的有效方式,可以減少網(wǎng)絡(luò)連接次數(shù)、SQL語句的解析次數(shù)、磁盤I/O操作和鎖的競爭,從而提高M(jìn)ySQL的性能。但是,在使用批量插入時也需要注意一些可能的缺陷。

實操:用GO實現(xiàn)批量寫入

package batchprocess

func StartBatchWriter() {
	messageMysqlChan = make(chan Sample, 100)
	insertedFlags = make(map[int64]bool)

	go batchMessageReceive()
	go batchStartTimer()
}

/*
接收消息的邏輯,只負(fù)責(zé)接收消息
*/
func batchMessageReceive() {
	for {
		select {
		case oneMessage := <-messageMysqlChan:
			mesLock.Lock()
			tmpMessage = append(tmpMessage, oneMessage)
			mesLock.Unlock()
		}
	}

}

func batch(batchMessage []Sample) {
	if len(batchMessage) == 0 {
		fmt.Print(">>>>>>>>>  空消息")
		return
	}

	var buffer bytes.Buffer
	sql := "insert into `sample` (`id`,`title`,`create_time`) values"
	if _, err := buffer.WriteString(sql); err != nil {
		fmt.Print(err.Error())
	}

	for index, value := range batchMessage {

		/*查看元素在集合中是否存在 */
		_, ok := insertedFlags[value.Id] /*如果確定是處理過 */
		if ok {
			continue
		} else {
			insertedFlags[value.Id] = true
		}
		if index == len(batchMessage)-1 {
			buffer.WriteString(fmt.Sprintf("('%d','%s','%s');", value.Id, value.Title, value.CreateTime.Format("2006-01-02 15:04:05")))
		} else {
			buffer.WriteString(fmt.Sprintf("('%d','%s','%s'),", value.Id, value.Title, value.CreateTime.Format("2006-01-02 15:04:05")))
		}
	}
	//獲取DB
	MysqlDB := tools.GetDB()
	err := MysqlDB.Exec(buffer.String()).Error
	if err != nil {
		fmt.Println("插入數(shù)據(jù)庫失?。?, err.Error())
	} else {

		fmt.Printf("插入數(shù)據(jù)庫成功,一共插入的條數(shù): %d:", len(batchMessage))
		fmt.Println("祝賀")

	}
	return
}

執(zhí)行效果

執(zhí)行的效果如下(后面尼恩會在 穿透云原生視頻中,進(jìn)行詳細(xì)介紹):

Go學(xué)習(xí)圣經(jīng):隊列削峰+批量寫入 超高并發(fā)原理和實操

Golang GC垃圾回收器

Cache 和 Buffer的區(qū)別

在理解垃圾回收之前,我們先理解一下Cache 和 Buffer,這兩個都是緩存,這兩者之間有什么區(qū)別呢?

buffer:緩沖

用于存儲速度不同步的設(shè)備或優(yōu)先級不同的設(shè)備之間傳輸數(shù)據(jù);通過buffer可以減少進(jìn)程間通信需要等待的時間,當(dāng)存儲速度快的設(shè)備與存儲速度慢的設(shè)備進(jìn)行通信時,存儲慢的數(shù)據(jù)先把數(shù)據(jù)存放到buffer,達(dá)到一定程度存儲快的設(shè)備再讀取buffer的數(shù)據(jù),在此期間存儲快的設(shè)備CPU可以做其他的事情。

A buffer is something that has yet to be "written" to disk.

cache:緩存

是高速緩存,是位于CPU和主內(nèi)存之間的容量較小但速度很快的存儲器,因為CPU的速度遠(yuǎn)遠(yuǎn)高于主內(nèi)存的速度,CPU從內(nèi)存中讀取數(shù)據(jù)需等待很長的時間,而 Cache保存著CPU剛用過的數(shù)據(jù)或循環(huán)使用的部分?jǐn)?shù)據(jù),這時從Cache中讀取數(shù)據(jù)會更快,減少了CPU等待的時間,提高了系統(tǒng)的性能。

A cache is something that has been "read" from the disk and stored for later use.

buffer是用于存放將要輸出到disk(塊設(shè)備)的數(shù)據(jù),進(jìn)行流量整形,把突發(fā)的大數(shù)量較小規(guī)模的 I/O 整理成平穩(wěn)的小數(shù)量較大規(guī)模的 I/O,以減少響應(yīng)次數(shù),而cache是存放從disk上讀出的數(shù)據(jù),為了彌補高速設(shè)備和低速設(shè)備的鴻溝而引入的中間層,最終起到加快訪問速度的作用。。二者都是為提高IO性能而設(shè)計的。

而Go標(biāo)準(zhǔn)庫Buffer是一個可變大小的字節(jié)緩沖區(qū),可以用Wirte和Read方法操作它.

Golang GC發(fā)展史

通常在編程中的垃圾指內(nèi)存中不再使用的內(nèi)存區(qū)域,自動發(fā)現(xiàn)與釋放這種內(nèi)存區(qū)域的過程就是垃圾回收。

內(nèi)存資源是有限的,而垃圾回收可以讓內(nèi)存重復(fù)使用,并且減輕開發(fā)者對內(nèi)存管理的負(fù)擔(dān),減少程序中的內(nèi)存問題。

我們透過這個來看下Go垃圾回收發(fā)展史:

  • go1.1,提高效率和垃圾回收精確度。
  • go1.3,提高了垃圾回收的精確度。
  • go1.4,之前版本的runtime大部分是使用C寫的,這個版本大量使用Go進(jìn)行了重寫,讓GC有了掃描stack的能力,進(jìn)一步提高了垃圾回收的精確度。
  • go1.5,目標(biāo)是降低GC延遲,采用了并發(fā)標(biāo)記和并發(fā)清除,三色標(biāo)記,write barrier,以及實現(xiàn)了更好的回收器調(diào)度,設(shè)計文檔1,文檔2,以及這個版本的[Go talk]。
  • go1.6,小優(yōu)化,當(dāng)程序使用大量內(nèi)存時,GC暫停時間有所降低。
  • go1.7,小優(yōu)化,當(dāng)程序有大量空閑goroutine,stack大小波動比較大時,GC暫停時間有顯著降低。
  • go1.8,write barrier切換到hybrid write barrier,以消除STW中的re-scan,把STW的最差情況降低到50us,設(shè)計文檔。

混合屏障的優(yōu)勢在于它允許堆棧掃描永久地使堆棧變黑(沒有STW并且沒有寫入堆棧的障礙),這完全消除了堆棧重新掃描的需要,從而消除了對堆棧屏障的需求。重新掃描列表。特別是堆棧障礙在整個運行時引入了顯著的復(fù)雜性,并且干擾了來自外部工具(如GDB和基于內(nèi)核的分析器)的堆棧遍歷。

此外,與Dijkstra風(fēng)格的寫屏障一樣,混合屏障不需要讀屏障,因此指針讀取是常規(guī)的內(nèi)存讀取; 它確保了進(jìn)步,因為物體單調(diào)地從白色到灰色再到黑色。

混合屏障的缺點很小。它可能會導(dǎo)致更多的浮動垃圾,因為它會在標(biāo)記階段的任何時刻保留從根(堆棧除外)可到達(dá)的所有內(nèi)容。然而,在實踐中,當(dāng)前的Dijkstra障礙可能幾乎保留不變?;旌掀琳线€禁止某些優(yōu)化:特別是,如果Go編譯器可以靜態(tài)地顯示指針是nil,則Go編譯器當(dāng)前省略寫屏障,但是在這種情況下混合屏障需要寫屏障。這可能會略微增加二進(jìn)制大小。

  • go1.9,提升指標(biāo)主要是:
  1. 過去 runtime.GC, debug.SetGCPercent, 和 debug.FreeOSMemory都不能觸發(fā)并發(fā)GC,他們觸發(fā)的GC都是阻塞的,go1.9可以了,變成了在垃圾回收之前只阻塞調(diào)用GC的goroutine。
  2. debug.SetGCPercent只在有必要的情況下才會觸發(fā)GC。
  • go.1.10,小優(yōu)化,加速了GC,程序應(yīng)當(dāng)運行更快一點點。
  • go1.12,顯著提高了堆內(nèi)存存在大碎片情況下的sweeping性能,能夠降低GC后立即分配內(nèi)存的延遲。

還有 5W字待發(fā)布

本文,僅僅是《Golang 圣經(jīng)》 的第一部分。

《Golang 圣經(jīng)》后面的內(nèi)容 更加精彩,涉及到高并發(fā)、分布式微服務(wù)架構(gòu)、 WEB開發(fā)架構(gòu),具體請關(guān)注進(jìn)展,請關(guān)注《技術(shù)自由圈》 公眾號。

如果需要領(lǐng)取 《Golang 圣經(jīng)》, 請關(guān)注《技術(shù)自由圈》 公眾號,發(fā)送暗號 “領(lǐng)電子書” 。

最后,如果學(xué)習(xí)過程中遇到問題,可以來尼恩的 萬人高并發(fā)社群中交流。

參考資料

  • Tracing Garbage Collection - wikipedia
  • On-the-fly Garbage Collection: an exercise in cooperation.
  • Garbage Collection
  • Tracing Garbage Collection
  • Copying Garbage Collection
  • Generational Garbage Collection
  • Golang Gc Talk
  • Eliminate Rescan

技術(shù)自由的實現(xiàn)路徑 PDF:

實現(xiàn)你的 架構(gòu)自由:

《吃透8圖1模板,人人可以做架構(gòu)》

《10Wqps評論中臺,如何架構(gòu)?B站是這么做的?。?!》

《阿里二面:千萬級、億級數(shù)據(jù),如何性能優(yōu)化? 教科書級 答案來了》

《峰值21WQps、億級DAU,小游戲《羊了個羊》是怎么架構(gòu)的?》

《100億級訂單怎么調(diào)度,來一個大廠的極品方案》

《2個大廠 100億級 超大流量 紅包 架構(gòu)方案》

… 更多架構(gòu)文章,正在添加中

實現(xiàn)你的 響應(yīng)式 自由:

《響應(yīng)式圣經(jīng):10W字,實現(xiàn)Spring響應(yīng)式編程自由》

這是老版本 《Flux、Mono、Reactor 實戰(zhàn)(史上最全)》

實現(xiàn)你的 spring cloud 自由:

《Spring cloud Alibaba 學(xué)習(xí)圣經(jīng)》

《分庫分表 Sharding-JDBC 底層原理、核心實戰(zhàn)(史上最全)》

《一文搞定:SpringBoot、SLF4j、Log4j、Logback、Netty之間混亂關(guān)系(史上最全)》

實現(xiàn)你的 linux 自由:

《Linux命令大全:2W多字,一次實現(xiàn)Linux自由》

實現(xiàn)你的 網(wǎng)絡(luò) 自由:

《TCP協(xié)議詳解 (史上最全)》

《網(wǎng)絡(luò)三張表:ARP表, MAC表, 路由表,實現(xiàn)你的網(wǎng)絡(luò)自由?。 ?/p>

實現(xiàn)你的 分布式鎖 自由:

《Redis分布式鎖(圖解 - 秒懂 - 史上最全)》

《Zookeeper 分布式鎖 - 圖解 - 秒懂》

實現(xiàn)你的 王者組件 自由:

《隊列之王: Disruptor 原理、架構(gòu)、源碼 一文穿透》

《緩存之王:Caffeine 源碼、架構(gòu)、原理(史上最全,10W字 超級長文)》

《緩存之王:Caffeine 的使用(史上最全)》

《Java Agent 探針、字節(jié)碼增強 ByteBuddy(史上最全)》

實現(xiàn)你的 面試題 自由:

4000頁《尼恩Java面試寶典 》 40個專題

以上尼恩 架構(gòu)筆記、面試題 的PDF文件更新,請到下面《技術(shù)自由圈》公號取↓↓↓文章來源地址http://www.zghlxwxcb.cn/news/detail-458274.html

到了這里,關(guān)于Go學(xué)習(xí)圣經(jīng):隊列削峰+批量寫入 超高并發(fā)原理和實操的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實不符,請點擊違法舉報進(jìn)行投訴反饋,一經(jīng)查實,立即刪除!

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

相關(guān)文章

  • 削峰填谷與應(yīng)用間解耦:分布式消息中間件在分布式環(huán)境下并發(fā)流量控制的應(yīng)用

    削峰填谷與應(yīng)用間解耦:分布式消息中間件在分布式環(huán)境下并發(fā)流量控制的應(yīng)用

    這是《百圖解碼支付系統(tǒng)設(shè)計與實現(xiàn)》專欄系列文章中的第(18)篇,也是流量控制系列的第(4)篇。點擊上方關(guān)注,深入了解支付系統(tǒng)的方方面面。 本篇重點講清楚分布式消息中間件的特點,常見消息中間件的簡單對比,在支付系統(tǒng)的應(yīng)用場景,比如削峰填谷,系統(tǒng)應(yīng)用間

    2024年01月20日
    瀏覽(57)
  • JUC并發(fā)編程學(xué)習(xí)筆記(九)阻塞隊列

    JUC并發(fā)編程學(xué)習(xí)筆記(九)阻塞隊列

    阻塞 隊列 隊列的特性:FIFO(fist inpupt fist output)先進(jìn)先出 不得不阻塞的情況 什么情況下會使用阻塞隊列:多線程并發(fā)處理、線程池 學(xué)會使用隊列 添加、移除 四組API 方式 拋出異常 不拋出異常,有返回值 阻塞等待 超時等待 添加 add offer put offer(E e, long timeout, TimeUnit unit) 移

    2024年02月06日
    瀏覽(25)
  • Golang掃盲式學(xué)習(xí)——GO并發(fā) | (一)

    Golang掃盲式學(xué)習(xí)——GO并發(fā) | (一)

    并行:同一個時間段內(nèi)多個任務(wù)同時在不同的CPU核心上執(zhí)行。強調(diào)同一時刻多個任務(wù)之間的” 同時執(zhí)行 “。 并發(fā):同一個時間段內(nèi)多個任務(wù)都在進(jìn)展。強調(diào)多個任務(wù)間的” 交替執(zhí)行 “。 隨著硬件水平的提高,現(xiàn)在的終端主機都是多個CPU,每個CPU都是多核結(jié)構(gòu)。當(dāng)多個CPU同

    2024年02月07日
    瀏覽(21)
  • openGauss學(xué)習(xí)筆記-224 openGauss性能調(diào)優(yōu)-系統(tǒng)調(diào)優(yōu)-數(shù)據(jù)庫系統(tǒng)參數(shù)調(diào)優(yōu)-數(shù)據(jù)庫并發(fā)隊列參數(shù)調(diào)優(yōu)

    openGauss學(xué)習(xí)筆記-224 openGauss性能調(diào)優(yōu)-系統(tǒng)調(diào)優(yōu)-數(shù)據(jù)庫系統(tǒng)參數(shù)調(diào)優(yōu)-數(shù)據(jù)庫并發(fā)隊列參數(shù)調(diào)優(yōu)

    數(shù)據(jù)庫提供兩種手段進(jìn)行并發(fā)隊列的控制,全局并發(fā)隊列和局部并發(fā)隊列。 224.1 全局并發(fā)隊列 全局并發(fā)隊列采用GUC參數(shù)max_active_statements控制數(shù)據(jù)庫主節(jié)點上運行并發(fā)執(zhí)行的作業(yè)數(shù)量。采用全局并發(fā)隊列機制將控制所有普通用戶的執(zhí)行作業(yè),不區(qū)分復(fù)雜度,即執(zhí)行語句都將作

    2024年02月22日
    瀏覽(29)
  • 數(shù)學(xué)建模 Excel的批量寫入與批量導(dǎo)出

    數(shù)學(xué)建模中編程手們常常會被要求將大量的數(shù)據(jù)進(jìn)行批量的預(yù)測操作,并寫入某個文件中 Excel的批量導(dǎo)出數(shù)據(jù),用循環(huán)就可以簡單實現(xiàn),例如 同時當(dāng)我們對上述的每一個文件進(jìn)行預(yù)測操作之后,需要將結(jié)果輸出在一個.csv或.xlsx文件中,可以這樣寫: 值得注意的是,result.xlsx需

    2024年02月06日
    瀏覽(22)
  • 高并發(fā)寫入優(yōu)化

    高并發(fā)寫入是一個常見的性能問題,可以從以下幾個方面入手進(jìn)行優(yōu)化: 數(shù)據(jù)庫優(yōu)化:選擇合適的數(shù)據(jù)庫類型和版本,使用合適的數(shù)據(jù)庫引擎、緩存、索引等技術(shù)來提高數(shù)據(jù)庫的寫入性能。還可以考慮使用分庫分表、異步寫入等技術(shù)來降低單個數(shù)據(jù)庫的寫入壓力。 緩存優(yōu)化

    2024年02月09日
    瀏覽(18)
  • 大數(shù)據(jù)HBase學(xué)習(xí)圣經(jīng):一本書實現(xiàn)HBase學(xué)習(xí)自由

    大數(shù)據(jù)HBase學(xué)習(xí)圣經(jīng):一本書實現(xiàn)HBase學(xué)習(xí)自由

    本文是《大數(shù)據(jù)HBase學(xué)習(xí)圣經(jīng)》 V1版本,是 《尼恩 大數(shù)據(jù) 面試寶典》姊妹篇。 這里特別說明一下:《尼恩 大數(shù)據(jù) 面試寶典》5個專題 PDF 自首次發(fā)布以來, 已經(jīng)匯集了 好幾百題,大量的大廠面試 干貨、正貨 。 《尼恩 大數(shù)據(jù) 面試寶典》面試題集合, 將變成大數(shù)據(jù)學(xué)習(xí)和面

    2024年02月10日
    瀏覽(48)
  • Java 實現(xiàn)ES批量寫入

    ?先學(xué)會用,再去研究原理,代碼如下 ?業(yè)務(wù)代碼實現(xiàn)

    2024年02月16日
    瀏覽(13)
  • Elasticsearch并發(fā)寫入版本沖突解決方案

    搜索公眾號, AmCoder 干貨及時送達(dá)??? 眾所周知,es經(jīng)常被用于存儲日志數(shù)據(jù),其中在某些場景下,日志產(chǎn)生的時機不同,并且需要將多類具備關(guān)聯(lián)關(guān)系的日志寫入同一個document,就會帶來同一個文檔可能會被其它文檔覆蓋,或者missing等問題。 大家都知道es是不支持事務(wù)的,

    2023年04月19日
    瀏覽(24)

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

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

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

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

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包