移动web开发入门-学习笔记-4

维护有删除操作的动态区间中位数

  返回  

池化技术学习及golang实现

2021/8/20 23:31:31 浏览:

一、池化技术

业务场景:电商系统大量的请求到来后,不可避免的需要进行与数据库的交互,而我们的数据库调用方式是先获取数据量的连接,然后依赖这条连接从数据库查询数据,最后关闭释放连接。这种调用方式,每次执行SQL都需要重新建立连接,频率窗口连接导致访问慢。

解决办法:
使用连接池将数据库连接预先建立好,使用时若连接池中有空闲连接则使用,不需要频繁创建连接,大大提升数据库查询性能

二、数据库连接池关键步骤

数据库连接池有两个最重要的配置:最小连接数min和最大连接数max。这两个参数控制着从连接池中获取连接的流程:

  • 如果当前连接数小于最小连接数min,则创建新连接处理数据库请求
  • 如果连接池有空闲连接则复用空闲连接
  • 如果空闲池中没有连接并且当前连接数cur小于最大连接数max,则创建新的连接处理请求
  • 如果当前连接数已经大于等于最大连接数max,则按照配置中设置的超时时间time_out等待旧连接可用
  • 如果等待超时time_out则向用户抛出错误

三、golang中自定义池的实现

go1.6之后的版本标准库已经自带了资源池的实现sync.Pool,这里仍然自己实现了,方便对上述业务场景进行扩展,代码大幅度改变自《Go语言实战》第七章。

pool.go

package main

import (
    "errors"
    "io"
    "log"
    "sync"
)
     
// Pool 管理一组安全在多个goroutine间共享资源,被管理资源必须实现io.Closer接口
type Pool struct{
    m sync.Mutex
    resources chan io.Closer  //通投类型为接口,可管理任意实现io.Closer接口的资源类型
    factory func() (io.Closer, error) //创建新资源,由使用者提供
    closed bool
}

var ErrPoolClosed = errors.New("Pool has been closed")

// New 函数工厂,指定有缓冲通道大小
func New(fn func() (io.Closer, error), size uint)(*Pool ,error){
    if size <= 0 {
        return nil, errors.New("Size value negative")
    }
    return &Pool{
        factory :fn,
        resources:make(chan io.Closer,size),
    },nil
}


// Acquire 池中获取资源
func (p *Pool) Acquire()(io.Closer,error){
    select{
    case r,ok := <-p.resources:
        log.Println("Acquire: Shared Resource")
        if !ok {
            return nil, ErrPoolClosed
        }
        return r,nil
    default:
    log.Println("Acquire: New Resource")
    return p.factory()
    }
}


// Release 池中释放资源
func (p *Pool) Release(r io.Closer){
    p.m.Lock()
    defer p.m.Unlock()
    
    if p.closed{
        r.Close()
        return 
    }

    select{
    case p.resources <- r: //放入队列
        log.Println("Release: In Queue")
    default: //队列已满,则关闭
        log.Println("Release: Closing")
        r.Close()
    }
}

// Close 池关闭所有现有资源
func (p *Pool) Close() {
    p.m.Lock()
    defer p.m.Unlock()

    if p.closed{
        return 
    }

    p.closed = true

    close(p.resources) //清空通道资源前将通道关闭,否则会产生死锁

    for r:= range p.resources{
        r.Close()
    }
}

main.go

package main

/*
模拟共享数据库连接
*/
import (
	"fmt"
	"io"
	"log"
	"math/rand"
	"sync"
	"sync/atomic"
	"time"
)

const (
	maxGoroutines   = 5 //创建goroutine数量
	pooledResources = 2 //池中资源数量
)

// dbConnection 模拟要共享的资源
type dbConnection struct {
	ID int32
}

// 实现接口,用来完成资源释放
func (dbConn *dbConnection) Close() error {
	log.Println("Close: connection", dbConn.ID)
	return nil
}

var idCounter int32

// 工厂函数,需要新连接时调用
func createConnection() (io.Closer, error) {
	id := atomic.AddInt32(&idCounter, 1)
	log.Println("Create: new connection", id)

	return &dbConnection{id}, nil
}

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

	// 创建管理的连接池
	p, err := New(createConnection, pooledResources)
	if err != nil {
		log.Println(err)
		panic("New error")
	}

	// 使用池中的连接完成查询
	for query := 0; query < maxGoroutines; query++ {
		//每个goroutine要复制的queryID,否则所有查询共享同一个查询变量
		go func(q int) {
			performQueries(q, p)
			wg.Done()
		}(query)
	}

	wg.Wait()

	log.Println("Shutdown Program.")
	p.Close()
	fmt.Println("Done")
}

// 测试连接的资源池
func performQueries(query int, p *Pool) {
	//从池中请求连接
	conn, err := p.Acquire()
	if err != nil {
		log.Println(err)
		return
	}

	//释放
	defer p.Release(conn)

	//用例模拟查询耗时
	time.Sleep(time.Duration(rand.Intn(1000)) * time.Millisecond)
	log.Printf("uid[%d] connectID[%d]\n", query, conn.(*dbConnection).ID)
}

运行结果:

2021/08/20 23:18:18 Acquire: New Resource
2021/08/20 23:18:18 Create: new connection 1
2021/08/20 23:18:18 Acquire: New Resource
2021/08/20 23:18:18 Create: new connection 2
2021/08/20 23:18:18 Acquire: New Resource
2021/08/20 23:18:18 Create: new connection 3
2021/08/20 23:18:18 Acquire: New Resource
2021/08/20 23:18:18 Create: new connection 4
2021/08/20 23:18:18 Acquire: New Resource
2021/08/20 23:18:18 Create: new connection 5
2021/08/20 23:18:18 uid[2] connectID[4]
2021/08/20 23:18:18 Release: In Queue
2021/08/20 23:18:18 uid[3] connectID[5]
2021/08/20 23:18:18 Release: In Queue
2021/08/20 23:18:18 uid[4] connectID[1]
2021/08/20 23:18:18 Release: Closing
2021/08/20 23:18:18 Close: connection 1
2021/08/20 23:18:19 uid[1] connectID[3]
2021/08/20 23:18:19 Release: Closing
2021/08/20 23:18:19 Close: connection 3
2021/08/20 23:18:19 uid[0] connectID[2]
2021/08/20 23:18:19 Release: Closing
2021/08/20 23:18:19 Close: connection 2
2021/08/20 23:18:19 Shutdown Program.
2021/08/20 23:18:19 Close: connection 4
2021/08/20 23:18:19 Close: connection 5
Done

四、文章参考

《Go语言实战》第七章
《极客时间》 池化技术:如何减少频繁创建数据库连接的性能损耗?


周记

cache2go源码最后一讲 借鉴代码走读思想,学习cache2go

groupcache源码解析-概览借鉴代码走读思想,学习groupcatche

极客兔兔 7天用Go从零实现分布式缓存GeeCache 国产版本groupcatche,其中对于LRU算法、一致性哈希思想阐述比较清晰

Coolshell GO编程模式:MAP-REDUCE 与本文无关,近期看了go泛型相关,记录下

阮一峰 科技爱好者周刊与本文无关,仅仅科技波浪读物

联系我们

如果您对我们的服务有兴趣,请及时和我们联系!

服务热线:18288888888
座机:18288888888
传真:
邮箱:888888@qq.com
地址:郑州市文化路红专路93号