golang--redigo中连接池的使用

redigo连接池的使用


前言

最近使用redigo的时候遇到了并发问题,于是想起了redigo并发是否存在安全性的问题,查了下源码,发现可以用连接池解决,简单介绍一下。

conn

redigo实现了不止一种的Conn对象,一般初次使用,会用redis.Dial()获取一条连接对象。
它是在conn.go中定义的对象。

// conn is the low-level implementation of Conn
type conn struct {
	// Shared
	mu      sync.Mutex
	pending int
	err     error
	conn    net.Conn

	// Read
	readTimeout time.Duration
	br          *bufio.Reader

	// Write
	writeTimeout time.Duration
	bw           *bufio.Writer

	// Scratch space for formatting argument length.
	// '*' or '$', length, "\r\n"
	lenScratch [32]byte

	// Scratch space for formatting integers and floats.
	numScratch [40]byte
}

它实际上是一种低水平的实现,在遇到多协程需要并发操作时是不能用它的 :

[外链图片转存失败(img-nrxuA0pv-1563279188074)(./1563275388184.png)]

连接池

连接池其实就是一种线程池模型,预先开辟并申请一定数量的资源放在池子中,用的时候取一条出来,用完在放回去。
不清楚线程池的可以看看我以前写的c++实现的线程池文章了解下。

接着看看pool,pool的Get()方法可以获取一条Conn对象,它是activeConn对象,不同于上面的conn,它也实现了Conn接口。

// NewPool creates a new pool.
//
// Deprecated: Initialize the Pool directory as shown in the example.
func NewPool(newFn func() (Conn, error), maxIdle int) *Pool {
	return &Pool{Dial: newFn, MaxIdle: maxIdle}
}

// Get gets a connection. The application must close the returned connection.
// This method always returns a valid connection so that applications can defer
// error handling to the first use of the connection. If there is an error
// getting an underlying connection, then the connection Err, Do, Send, Flush
// and Receive methods return that error.
func (p *Pool) Get() Conn {
	pc, err := p.get(nil)
	if err != nil {
		return errorConn{err}
	}
	return &activeConn{p: p, pc: pc}
}

// Close releases the resources used by the pool.
func (p *Pool) Close() error {
	....

type activeConn struct {
	p     *Pool
	pc    *poolConn
	state int
}
// Conn represents a connection to a Redis server.
type Conn interface {
	// Close closes the connection.
	Close() error

	// Err returns a non-nil value when the connection is not usable.
	Err() error

	// Do sends a command to the server and returns the received reply.
	Do(commandName string, args ...interface{}) (reply interface{}, err error)

	// Send writes the command to the client's output buffer.
	Send(commandName string, args ...interface{}) error

	// Flush flushes the output buffer to the Redis server.
	Flush() error

	// Receive receives a single reply from the Redis server
	Receive() (reply interface{}, err error)
}

例程

3个协程同时进行redis的ping操作,连接池中有最大活跃数2条,最大闲置数1条,
结果会有一个协程在进行Do方法时connection pool exhausted 返回连接池资源耗尽错误, 注意它不是在Get()的时候报错,而是会在使用这个Conn对象的时候返回错误。

另外pool的Coon由两种状态Idle和Active,使用中的连接是Active状态超时(IdleTimeout)后变为Idle状态,idle状态的连接数目最多为MaxIdle条,active状态的连接数最多为MaxActive条。

package main

import(
	"fmt"
	"time"
	"./redigo/redis"
)

func TestPing(){
	conn := redisPool.Get()
	res, _ := conn.Do("ping")
	fmt.Println(res.(string))
	conn.Close()
}

func newRedisPool(addr string) *redis.Pool {
	return &redis.Pool{
		MaxIdle: 1,
		MaxActive: 2,
		IdleTimeout: 6 * time.Second,
		// Dial or DialContext must be set. When both are set, DialContext takes precedence over Dial.
		Dial: func () (redis.Conn, error) { return redis.Dial("tcp", addr) },
	}
}

var (
	redisPool *redis.Pool
	redisServer = "127.0.0.1:6379"
)

func TestPoolMaxIdle() {

	go func(){
		fmt.Println("go---1---")
		conn := redisPool.Get()
		fmt.Println("go---1---: Get Conn")
		defer conn.Close()

		res, err := conn.Do("ping")
		if err != nil {
			fmt.Println("go---1---error:", err)
			return
		}
		fmt.Println("go---1---:", res.(string))

		time.Sleep(3*time.Second)
	}()

	go func(){
		fmt.Println("go---2---")
		conn := redisPool.Get()
		fmt.Println("go---2---: Get Conn")
		defer conn.Close()

		res, err := conn.Do("ping")
		if err != nil {
			fmt.Println("go---2---error:", err)
			return
		}
		fmt.Println("go---2---:", res.(string))

		time.Sleep(3*time.Second)
	}()

	go func(){
		fmt.Println("go---3---")
		conn := redisPool.Get()
		fmt.Println("go---3---: Get Conn")
		defer conn.Close()

		time.Sleep(5*time.Second)
		res, err := conn.Do("ping")
		if err != nil {
			fmt.Println("go---3---error:", err)
			return
		}
		fmt.Println("go---3---:", res.(string))

		time.Sleep(3*time.Second)
	}()
}

func main()  {
	redisPool = newRedisPool(redisServer)
	fmt.Println("-------------PoolStats------------")
	fmt.Println("Active/Idle:", redisPool.ActiveCount(), ":", redisPool.IdleCount())

	fmt.Println("-------------TestPing------------")
	TestPing()

	fmt.Println("-------------PoolStats------------")
	fmt.Println("Active/Idle:", redisPool.ActiveCount(), ":", redisPool.IdleCount())

	time.Sleep(8*time.Second)

	fmt.Println("-------------PoolStats------------")
	fmt.Println("Active/Idle:", redisPool.ActiveCount(), ":", redisPool.IdleCount())

	fmt.Println("-------------TestPoolMaxIdle------------")
	TestPoolMaxIdle()

	time.Sleep(1*time.Second)
	fmt.Println("-------------PoolStats------------")
	fmt.Println("Active/Idle:", redisPool.ActiveCount(), ":", redisPool.IdleCount())

	time.Sleep(20*time.Second)

	fmt.Println("-------------PoolStats------------")
	fmt.Println("Active/Idle:", redisPool.ActiveCount(), ":", redisPool.IdleCount())
}

[外链图片转存失败(img-e9krZj69-1563279188080)(./1563279050566.png)]

golang-lru 是一个 Golang 语言实现的 LRU 缓存库,它提供了一个简单易用的 API 用于创建和使用 LRU 缓存。 下面是一个简单的使用示例: ```go package main import ( "fmt" "github.com/hashicorp/golang-lru" ) func main() { // 创建一个 LRU 缓存,容量为 2 cache, _ := lru.New(2) // 添加两个元素到缓存 cache.Add("key1", "value1") cache.Add("key2", "value2") // 从缓存获取一个元素 if v, ok := cache.Get("key1"); ok { fmt.Println(v.(string)) } // 添加一个新元素到缓存,此时缓存容量已满,会自动淘汰最久未使用的元素 "key2" cache.Add("key3", "value3") // 遍历缓存的所有元素 for _, k := range cache.Keys() { if v, ok := cache.Get(k); ok { fmt.Println(k, v.(string)) } } } ``` 运行上述代码,将会输出: ``` value1 key1 value1 key3 value3 ``` 在这个示例,我们首先使用 `lru.New()` 函数创建了一个容量为 2 的 LRU 缓存。然后我们添加了两个元素到缓存,并从缓存获取了一个元素。接着我们添加了一个新元素,此时缓存已满,会自动淘汰最久未使用的元素 "key2"。最后我们遍历了缓存的所有元素,输出了它们的键和值。 除了 `Add()` 和 `Get()` 方法外,golang-lru 还提供了 `Remove()` 和 `Contains()` 方法来删除和判断缓存是否存在某个元素,以及 `Len()` 和 `Clear()` 方法来获取缓存元素的数量和清空缓存。 golang-lru 还支持并发安全,你可以通过 `NewWithOptions()` 函数创建一个并发安全的 LRU 缓存,具体用法请参考官方文档:https://pkg.go.dev/github.com/hashicorp/golang-lru。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值