惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

H
Hackread – Cybersecurity News, Data Breaches, AI and More
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
V
V2EX
T
The Blog of Author Tim Ferriss
腾讯CDC
Hugging Face - Blog
Hugging Face - Blog
雷峰网
雷峰网
爱范儿
爱范儿
GbyAI
GbyAI
H
Help Net Security
I
InfoQ
罗磊的独立博客
酷 壳 – CoolShell
酷 壳 – CoolShell
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
人人都是产品经理
人人都是产品经理
J
Java Code Geeks
Microsoft Security Blog
Microsoft Security Blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
N
Netflix TechBlog - Medium
Last Week in AI
Last Week in AI
宝玉的分享
宝玉的分享
云风的 BLOG
云风的 BLOG
Project Zero
Project Zero
P
Privacy & Cybersecurity Law Blog
A
Arctic Wolf
Know Your Adversary
Know Your Adversary
G
Google Developers Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
T
Tor Project blog
V
Vulnerabilities – Threatpost
Y
Y Combinator Blog
WordPress大学
WordPress大学
V
Visual Studio Blog
博客园_首页
G
GRAHAM CLULEY
K
Kaspersky official blog
T
Tailwind CSS Blog
T
Threat Research - Cisco Blogs
博客园 - Franky
D
Docker
Security Latest
Security Latest
I
Intezer
有赞技术团队
有赞技术团队
Application and Cybersecurity Blog
Application and Cybersecurity Blog
博客园 - 【当耐特】
B
Blog RSS Feed
T
The Exploit Database - CXSecurity.com
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻

Random Thoughts

LLM 训练与推理的基本理解 昨日的世界不再重来 Magic Brush - 画出你自己的产品宇宙 写在 AI Coding 奇点之后 尼泊尔布恩山小环线纪行 2025 投资组合年报 三十而笠 我的 8 年职业生涯回顾 我的颈椎病康复之旅 - 关于选择的故事 熊野古道中边路纪行 新加坡旅居三年再回首 高加索三国 - 阿塞拜疆行记 2024 投资组合年报 重新思考 Go:了解程序在线上是如何运行的 欧游散记 —— 环勃朗峰之旅 欧游散记 —— 特摩索斯古城 重新思考 Go:Channel 不是「消息队列」 重新思考 Go:Slice 只是「操作视图」 那一天,我决定踏出一步 True Story 少数价值 A Snapshot of Myself - 2021 RPC 漫谈: 连接问题 RPC 漫谈:序列化问题 RPC 漫谈: 限流问题 科学,技术与工程 Golang for-range 内部实现 Golang Interface 内部实现 真理的有限性 Golang rand 库锁竞争优化 Meaningless or Meaningful 分布式文件系统的演化 从动物森友会聊主机游戏联机机制 创业公司的文化 伊朗见闻录 NodeJS 内存泄漏检测与定位 设计实现高性能本地内存缓存 Travel Map Linux I/O 栈浅析 SSD 背后的奥秘 什么是真正的编程能力 沉默与反沉默的理由 命令行里的设计艺术 自由意志下的选择 学习思维方式而非学习观点 从程序到人 —— 情头配对助手的前世今生 即刻多端实时通信实践 朝鲜 —— 小国寡民的主体思想实践 使用 Surge 提升多网络环境下的流畅开发体验 一份其实好吃的 LaTeX 入门餐 Kafka 的设计与实践思考 欧游散记 —— 民主专制下的德国 欧游散记 —— 维也纳 欧游散记 —— 西班牙 欧游散记 —— 德国国会大厦 社会矛盾讨论统一框架的一种可能性 欧游散记 —— 伪君子布拉格 Lemon : Koa 风格的 Python 异步 Web 框架 Golang : Make Programming Happy Again Podcast 闲言碎语 Kubernetes 中使用 API Gateway 替代 Ingress ElasticSearch 最佳实践 blog.joway.io Presentations
Pond: Golang 通用对象池
Joway · 2021-01-23 · via Random Thoughts

为什么需要通用对象池

在实际生产环境中,我们经常会遇到需要多线程共享同一堆对象的场景,例如常见的RPC、数据库连接池,大内存对象池,以及线程池等。这些池化对象的需求其实有很多重叠的地方,主要分以下几个方面:

  1. 基础设置:
    1. 最大容量
    2. 创建/校验/删除对象的回调方法
    3. 申请对象时,已满时是否阻塞
  2. 多重驱逐策略:
    1. 驱逐闲置对象
    2. 驱逐无效对象
  3. 预创建对象

为避免重复编码,所以设计了 Pond 这样一个能够支撑多种使用场景的通用对象池。另外,在 Pond 的基础上,还实现了 Goroutine 池库: Hive

使用方式

//example
type conn struct {
	addr string
}

func main() {
	ctx := context.Background()
	cfg := pond.NewDefaultConfig()
	cfg.MinIdle = 1
	cfg.ObjectCreateFactory = func(ctx context.Context) (interface{}, error) {
		return &conn{addr: "127.0.0.1"}, nil
	}
	cfg.ObjectValidateFactory = func(ctx context.Context, object interface{}) bool {
		c := object.(*conn)
		return c.addr != ""
	}
	cfg.ObjectDestroyFactory = func(ctx context.Context, object interface{}) error {
		c := object.(*conn)
		c.addr = ""
		return nil
	}
	p, err := pond.New(cfg)
	if err != nil {
		log.Fatal(err)
	}

	obj, err := p.BorrowObject(ctx)
	if err != nil {
		log.Fatal(err)
	}
	defer p.ReturnObject(ctx, obj)
	fmt.Printf("get conn: %v\n", obj.(*conn).addr)
}

使用细则

LIFO 驱逐策略

当前采用 LIFO 的驱逐策略,保证永远优先使用最常被使用的对象。之所以不采用 FIFO 是因为我们只有让热门的对象尽可能保持热门,而不是均衡每个对象的使用频率,才能够保证最大程度筛选出不常用的对象从而使其被驱逐。

避免频繁驱逐

某些情况下会出现不停创建新的对象,到了驱逐时间又被立马销毁的情况,从而使得对象池的大小出现不必要的频繁变动。这里我们可以通过 MinIdleTime 配置最小闲置时间,保证只有当对象闲置超过该时间后,才可能被驱逐。

对象校验

有一种情况是,一开始在池子里创建好了几个对象,但是当用户实际去取出来的时候,发现该对象其实已经被关闭或者失效了。所以在 Pool 内部需要每次取的对象都经过一次校验,如果校验不通过,则销毁对象,再次尝试去取。该策略还能保证当出现部分节点抖动时,会尽可能剔除不可用节点,提供稳定的对象。

同时为了避免当一些灾难情况下,永远无法成功创建对象(例如下游节点完全宕机),我们还需要设置 MaxValidateAttempts 以避免出现恶性循环。

预创建对象

在默认情况下,我们会在每次取对象的时候,判断是否需要创建新的,如需要再取实时创建。但如果创建操作比较费时,我们会希望在条件允许的情况下,池子里能够预留一部分空闲对象,以供未来调用。MinIdle 参数用以确保池子内最小能够拥有的空闲对象数。

超时取消

默认配置下,每次取对象如果当前池已满,且没有闲置对象,会阻塞住,直到能够获取到可用对象为止。我们使用 context 来实现获取超时取消的逻辑。一旦当触发 ctx.Done() 时候,会直接 return,并返回 ctx.Err()

高级扩展

Pond 是一个通用对象池,在此基础上,我们可以非常简单地实现诸如连接池,goroutine 池等多重应用。

以下示例展示了如何使用 Pond 创建一个简单的 goroutine 池:

实现 Worker 对象

Worker 结构体用以封装单个 goroutine:

type Worker struct {
	jobs chan Job
}

func NewWorker() *Worker {
	w := &Worker{
		jobs: make(chan Job),
	}
	go w.Run()
	return w
}

func (w *Worker) Submit(task Task, callback Callback) {
	w.jobs <- Job{
		task:     task,
		callback: callback,
	}
}

func (w *Worker) Run() {
	for job := range w.jobs {
		if job.task != nil {
			job.task()
		}
		if job.callback != nil {
			job.callback()
		}
	}
}

func (w *Worker) Close() {
	close(w.jobs)
}

池化 Worker 对象

pConfig := pond.NewDefaultConfig()
pConfig.MaxSize = h.Size
pConfig.Nonblocking = h.Nonblocking
pConfig.ObjectCreateFactory = func(ctx context.Context) (interface{}, error) {
    return NewWorker(), nil
}
pConfig.ObjectDestroyFactory = func(ctx context.Context, object interface{}) error {
    w := object.(*Worker)
    w.Close()
    return nil
}
workers, err := pond.New(pConfig)

object, err := workers.BorrowObject(ctx)
worker := object.(*Worker)
worker.Submit(func() {
    //do some task
}, func() {
    // when task finished, return object
    _ = h.workers.ReturnObject(ctx, object)
})