最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

關(guān)于redigo中PubSub的一點(diǎn)小坑分析

 更新時(shí)間:2019年01月03日 10:41:02   作者:Chen Jiehua  
這篇文章主要給大家介紹了關(guān)于redigo中PubSub的一點(diǎn)小坑的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧

前言

最近在用 golang 做一些 redis 相關(guān)的操作,選用了 redigo 這個(gè)第三方庫(kù)。然后在使用 Pub/Sub 的時(shí)候,卻發(fā)現(xiàn)了一個(gè)小坑……

Redis Client

首先,我們來(lái)初始化一個(gè)帶連接池的 Redis Client:

import (
	"github.com/gomodule/redigo/redis"
)

type RedisClient struct {
	pool *redis.Pool
}

func NewRedisClient(addr string, db int, passwd string) *RedisClient {
	pool := &redis.Pool{
		MaxIdle:  10,
		IdleTimeout: 300 * time.Second,
		Dial: func() (redis.Conn, error) {
			c, err := redis.Dial("tcp", addr, redis.DialPassword(passwd), redis.DialDatabase(db))
			if err != nil {
				return nil, err
			}
			return c, nil
		},
		TestOnBorrow: func(c redis.Conn, t time.Time) error {
			if time.Since(t) < time.Minute {
				return nil
			}
			_, err := c.Do("PING")
			return err
		},
	}
	log.Printf("new redis pool at %s", addr)
	client := &RedisClient{
		pool: pool,
	}
	return client
}

Publish

然后我們可以簡(jiǎn)單的實(shí)現(xiàn)一個(gè) publish 方法:

func (r *RedisClient) Publish(channel, message string) (int, error) {
	c := r.pool.Get()
	defer c.Close()
	n, err := redis.Int(c.Do("PUBLISH", channel, message))
	if err != nil {
		return 0, fmt.Errorf("redis publish %s %s, err: %v", channel, message, err)
	}
	return n, nil
}

Subscribe

接下來(lái)就是一個(gè)稍微復(fù)雜點(diǎn)的帶有心跳的 subscribe 方法:

func (r *RedisClient) Subscribe(ctx context.Context, consume ConsumeFunc, channel ...string) error {
	psc := redis.PubSubConn{Conn: r.pool.Get()}
	defer psc.Close()
	log.Printf("redis pubsub subscribe channel: %v", channel)
	if err := psc.Subscribe(redis.Args{}.AddFlat(channel)...); err != nil {
		return err
	}
	done := make(chan error, 1)
	// start a new goroutine to receive message
	go func() {
		for {
			switch msg := psc.Receive().(type) {
			case error:
				done <- fmt.Errorf("redis pubsub receive err: %v", msg)
				return
			case redis.Message:
				if err := consume(msg); err != nil {
					done <- err
					return
				}
			case redis.Subscription:
				if msg.Count == 0 {
					// all channels are unsubscribed
					done <- nil
					return
				}
			}
		}
	}()

	// health check
	tick := time.NewTicker(time.Minute)
	defer tick.Stop()
	for {
		select {
		case <-ctx.Done():
			if err := psc.Unsubscribe(); err != nil {
				return fmt.Errorf("redis pubsub unsubscribe err: %v", err)
			}
			return nil
		case err := <-done:
			return err
		case <-tick.C:
			if err := psc.Ping(""); err != nil {
				return err
			}
		}
	}

	return nil
}

最后,我們寫(xiě)一個(gè)簡(jiǎn)單地 main 函數(shù)來(lái)調(diào)用 publish & subscribe:

func (r *RedisClient) Subscribe(ctx context.Context, consume ConsumeFunc, channel ...string) error {
	psc := redis.PubSubConn{Conn: r.pool.Get()}
	defer psc.Close()
	log.Printf("redis pubsub subscribe channel: %v", channel)
	if err := psc.Subscribe(redis.Args{}.AddFlat(channel)...); err != nil {
		return err
	}
	done := make(chan error, 1)
	// start a new goroutine to receive message
	go func() {
		for {
			switch msg := psc.Receive().(type) {
			case error:
				done <- fmt.Errorf("redis pubsub receive err: %v", msg)
				return
			case redis.Message:
				if err := consume(msg); err != nil {
					done <- err
					return
				}
			case redis.Subscription:
				if msg.Count == 0 {
					// all channels are unsubscribed
					done <- nil
					return
				}
			}
		}
	}()

	// health check
	tick := time.NewTicker(time.Minute)
	defer tick.Stop()
	for {
		select {
		case <-ctx.Done():
			if err := psc.Unsubscribe(); err != nil {
				return fmt.Errorf("redis pubsub unsubscribe err: %v", err)
			}
			return nil
		case err := <-done:
			return err
		case <-tick.C:
			if err := psc.Ping(""); err != nil {
				return err
			}
		}
	}

	return nil
}


咋一看之下,好像并沒(méi)有什么異常?然而,如果我們這時(shí)候去看 redis 的 tcp 連接,就可以發(fā)現(xiàn)一些貓膩:

$sudo netstat -antp | grep redis
tcp  0  0 0.0.0.0:6379   0.0.0.0:*    LISTEN  940/redis-server 0. 
tcp  0  0 172.16.8.128:6379  172.16.8.1:55010  ESTABLISHED 940/redis-server 0. 
tcp  0  0 172.16.8.128:6379  172.16.8.1:55015  ESTABLISHED 940/redis-server 0. 
tcp  0  0 172.16.8.128:6379  172.16.8.1:55009  ESTABLISHED 940/redis-server 0. 
tcp  0  0 172.16.8.128:6379  172.16.8.1:55005  ESTABLISHED 940/redis-server 0. 
tcp  0  0 172.16.8.128:6379  172.16.8.1:55012  ESTABLISHED 940/redis-server 0. 
tcp  0  0 172.16.8.128:6379  172.16.8.1:55011  ESTABLISHED 940/redis-server 0. 
tcp  0  0 172.16.8.128:6379  172.16.8.1:55013  ESTABLISHED 940/redis-server 0. 
tcp  0  0 172.16.8.128:6379  172.16.8.1:55007  ESTABLISHED 940/redis-server 0. 
tcp  0  0 172.16.8.128:6379  172.16.8.1:55006  ESTABLISHED 940/redis-server 0. 
tcp  0  0 172.16.8.128:6379  172.16.8.1:55014  ESTABLISHED 940/redis-server 0. 
tcp  0  0 172.16.8.128:6379  172.16.8.1:54972  ESTABLISHED 940/redis-server 0. 

竟然是每一次 subscribe 就新建了一個(gè)連接,而 connection pool 似乎沒(méi)有什么作用。

更進(jìn)一步地調(diào)試,我們發(fā)現(xiàn)在 defer psc.Close() 的時(shí)候就卡住了,也就是上面的 10 個(gè) goroutine 其實(shí)并沒(méi)有正常退出。

Concurrent

排查許久之后,終于定位到了問(wèn)題!引用 redigo 的說(shuō)明

Connections support one concurrent caller to the Receive method and one concurrent caller to the Send and Flush methods. No other concurrency is supported including concurrent calls to the Do method.

For full concurrent access to Redis, use the thread-safe Pool to get, use and release a connection from within a goroutine. Connections returned from a Pool have the concurrency restrictions described in the previous paragraph.

也就是說(shuō),雖然一個(gè)連接可以在不同的 goroutine 并發(fā)調(diào)用 Receive() 和 Subscribe()(subscribe調(diào)用了send和flush) ,但是卻不能再有其他并發(fā)操作(比如 Close())。

其他相似的問(wèn)題還可以參考 issue

Fix

知道了上面的原因之后,我們稍微修改一下 defer psc.Close() 的位置即可解決問(wèn)題:

	// start a new goroutine to receive message
	go func() {
		// IMPORTANT!
		defer psc.Close()
		for {
			switch msg := psc.Receive().(type) {
			case error:

總結(jié)

以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)腳本之家的支持。

相關(guān)文章

  • Redis高并發(fā)超賣(mài)問(wèn)題解決方案圖文詳解

    Redis高并發(fā)超賣(mài)問(wèn)題解決方案圖文詳解

    Redis是一種基于內(nèi)存的數(shù)據(jù)存儲(chǔ)系統(tǒng),被廣泛用于解決高并發(fā)問(wèn)題,下面這篇文章主要給大家介紹了關(guān)于Redis高并發(fā)超賣(mài)問(wèn)題解決方案的相關(guān)資料,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2024-02-02
  • Redis開(kāi)啟鍵空間通知實(shí)現(xiàn)超時(shí)通知的步驟詳解

    Redis開(kāi)啟鍵空間通知實(shí)現(xiàn)超時(shí)通知的步驟詳解

    這篇文章主要介紹了Redis開(kāi)啟鍵空間通知實(shí)現(xiàn)超時(shí)通知的步驟,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-06-06
  • Redis常用的數(shù)據(jù)結(jié)構(gòu)及實(shí)際應(yīng)用場(chǎng)景

    Redis常用的數(shù)據(jù)結(jié)構(gòu)及實(shí)際應(yīng)用場(chǎng)景

    本文介紹了Redis中常用的數(shù)據(jù)結(jié)構(gòu),包括字符串、列表、集合、哈希表、有序集合和Bitmap,并詳細(xì)說(shuō)明了它們?cè)诟鞣N場(chǎng)景下的使用,需要的朋友可以參考下
    2024-05-05
  • Redis 數(shù)據(jù)類(lèi)型Streams詳解

    Redis 數(shù)據(jù)類(lèi)型Streams詳解

    Redis Streams是Redis 5.0新增的數(shù)據(jù)類(lèi)型,提供了一種日志結(jié)構(gòu)化數(shù)據(jù)存儲(chǔ)方式,這種類(lèi)型適合用于構(gòu)建消息隊(duì)列、事件日志和處理時(shí)間序列數(shù)據(jù)的應(yīng)用,本文介紹Redis 數(shù)據(jù)類(lèi)型Streams相關(guān)知識(shí),感興趣的朋友一起看看吧
    2024-10-10
  • Redis處理高并發(fā)之布隆過(guò)濾器詳解

    Redis處理高并發(fā)之布隆過(guò)濾器詳解

    這篇文章主要為大家介紹了Redis處理高并發(fā)之布隆過(guò)濾器詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-12-12
  • Redis遠(yuǎn)程連接Redis客戶端的實(shí)現(xiàn)步驟

    Redis遠(yuǎn)程連接Redis客戶端的實(shí)現(xiàn)步驟

    本文主要介紹了Redis遠(yuǎn)程連接Redis客戶端的實(shí)現(xiàn)步驟,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2022-06-06
  • 關(guān)于Redis中bitmap的原理和使用詳解

    關(guān)于Redis中bitmap的原理和使用詳解

    這篇文章主要介紹了關(guān)于Redis中bitmap的原理和使用詳解,BitMap即位圖,使用每個(gè)位表示某種狀態(tài),適合處理整型的海量數(shù)據(jù),本質(zhì)上是哈希表的一種應(yīng)用實(shí)現(xiàn),需要的朋友可以參考下
    2023-05-05
  • 淺談Redis哨兵模式的使用

    淺談Redis哨兵模式的使用

    這篇文章主要介紹了淺談Redis哨兵模式的使用,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-12-12
  • 使用Redis實(shí)現(xiàn)分布式鎖的方法

    使用Redis實(shí)現(xiàn)分布式鎖的方法

    為了保證我們線上服務(wù)的并發(fā)性和安全性,目前我們的服務(wù)一般拋棄了單體應(yīng)用,采用的都是擴(kuò)展性很強(qiáng)的分布式架構(gòu),這篇文章主要介紹了使用Redis實(shí)現(xiàn)分布式鎖的方法,需要的朋友可以參考下
    2022-06-06
  • redis熱key問(wèn)題怎樣解決

    redis熱key問(wèn)題怎樣解決

    這篇文章主要介紹了redis熱key問(wèn)題怎樣解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-04-04

最新評(píng)論

静海县| 齐齐哈尔市| 通辽市| 洱源县| 平乡县| 公主岭市| 山西省| 龙陵县| 和政县| 洛隆县| 无锡市| 石家庄市| 修文县| 海伦市| 华坪县| 佛坪县| 夏邑县| 临湘市| 永登县| 明光市| 阿拉尔市| 张家口市| 武乡县| 和龙市| 宜良县| 太白县| 沂源县| 枣强县| 赤壁市| 浏阳市| 临猗县| 南城县| 利辛县| 无锡市| 扶绥县| 新竹市| 桂阳县| 凉城县| 临泽县| 长春市| 柳林县|