欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

關于redigo中PubSub的一點小坑分析

 更新時間:2019年01月03日 10:41:02   作者:Chen Jiehua  
這篇文章主要給大家介紹了關于redigo中PubSub的一點小坑的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧

前言

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

Redis Client

首先,我們來初始化一個帶連接池的 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

然后我們可以簡單的實現(xiàn)一個 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

接下來就是一個稍微復雜點的帶有心跳的 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
}

最后,我們寫一個簡單地 main 函數(shù)來調用 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
}


咋一看之下,好像并沒有什么異常?然而,如果我們這時候去看 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 就新建了一個連接,而 connection pool 似乎沒有什么作用。

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

Concurrent

排查許久之后,終于定位到了問題!引用 redigo 的說明

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.

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

其他相似的問題還可以參考 issue

Fix

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

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

總結

以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持。

相關文章

  • 淺談Redis中bind的坑

    淺談Redis中bind的坑

    本文主要介紹了淺談Redis中bind的坑,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2023-07-07
  • Redis?如何清空所有數(shù)據(jù)

    Redis?如何清空所有數(shù)據(jù)

    這篇文章主要介紹了Redis?如何清空所有數(shù)據(jù),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-08-08
  • 虛擬機下的Redis無法訪問報錯500解決方法

    虛擬機下的Redis無法訪問報錯500解決方法

    這篇文章主要介紹了虛擬機下的Redis無法訪問,報錯500解決方法,由于我的redis是在虛擬機下安裝的,無法訪問redis的原因是因為虛擬機的ip地址和主機不同,文中通過圖文結合給出了詳細的解決方法,需要的朋友可以參考下
    2024-02-02
  • Redis持久化深入詳解

    Redis持久化深入詳解

    這篇文章主要介紹了Redis持久化深入詳解,講解的還是比較詳細的,有感興趣的同學可以學習下
    2021-03-03
  • 在Centos?8.0中安裝Redis服務器的教程詳解

    在Centos?8.0中安裝Redis服務器的教程詳解

    由于考慮到linux服務器的性能,所以經(jīng)常需要把一些中間件安裝在linux服務上,今天通過本文給大家介紹下在Centos?8.0中安裝Redis服務器的詳細過程,感興趣的朋友一起看看吧
    2022-03-03
  • redis過期回調坑的解決

    redis過期回調坑的解決

    Redis提供了一種過期回調的機制,可以在某個鍵過期時觸發(fā)一個回調函數(shù),然而,在實際使用中,我們往往會遇到一些災難性的問題,其中一個就是在使用過期回調的時候,我們可能會遭遇到無法預料的錯誤,本文就詳細的介紹一下
    2023-09-09
  • Springboot/Springcloud項目集成redis進行存取的過程解析

    Springboot/Springcloud項目集成redis進行存取的過程解析

    大家都知道Redis支持五種數(shù)據(jù)類型:string(字符串),hash(哈希),list(列表),set(集合),zset(sorted set:有序集合),本文重點給大家介紹Springboot/Springcloud項目集成redis進行存取的過程,需要的朋友參考下吧
    2021-12-12
  • Redis?HyperLogLog數(shù)據(jù)統(tǒng)計輕量級解決方案詳解

    Redis?HyperLogLog數(shù)據(jù)統(tǒng)計輕量級解決方案詳解

    這篇文章主要為大家介紹了Redis?HyperLogLog數(shù)據(jù)統(tǒng)計輕量級解決方案詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-12-12
  • redis哈希類型_動力節(jié)點Java學院整理

    redis哈希類型_動力節(jié)點Java學院整理

    這篇文章主要介紹了redis哈希類型的常用方法及原理淺析,感興趣的朋友一起看看吧
    2017-08-08
  • Redis特殊數(shù)據(jù)類型HyperLogLog基數(shù)統(tǒng)計算法講解

    Redis特殊數(shù)據(jù)類型HyperLogLog基數(shù)統(tǒng)計算法講解

    這篇文章主要為大家介紹了Redis特殊數(shù)據(jù)類型HyperLogLog基數(shù)統(tǒng)計算法講解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-05-05

最新評論