Hi, 使用多年的go pprof检查内存泄漏的方法居然是错的?!

最近在做一个 Redis 的 Proxy 的项目,其中利用Redis 6.0 新加的 tracking 功能实现客户端缓存的功能,可以为某些特定的redis使用场景提高吞吐和延迟。

当然,cache的实现也是有代价的。首先,cache的大小不能无限制的大,否则总有一点会把内存撑爆的;其次,cache的淘汰算法有多种方式,LRU、LFU等等,具体可以参考Cache replacement policies,不同的场景下各种淘汰算法的效果是不一样的;第三,对于大并发情况实现cache是有代价的,因为并发情况下对cache的访问需要加锁,而加锁就意味着有性能的损失。

我在实现这个cache的过程中稍微偷了一下懒, 想尽量的减少锁的scope,结果导致内存泄漏的问题。本来cache占用的最大内存我设置为10GB, 结果过了个周末发现程序已经占用了80GB的内存了。

当然本文不是要介绍这个项目的内存泄漏原因,而是介绍一下Go pprof工具查找内存泄漏的一个不太常用的方法。

阅读全文

[译]Rust返回引用的不同策略

原文: Strategies for Returning References in Rust by Bryce Fisher-Fleig.

译者按: 这是 Fisher-Fleig 整理一篇关于从函数/方法中返回引用值的知识。
很显然,对于Rust初学者来说,很容易陷入无法返回函数内的本地变量的泥沼里,尤其是从其它编程语言如Java、Go转过来的程序员,在其它编程语言中很容易的编程方式却在Rust编程语言中行不通。这篇文章可以帮助你理解返回引用的各种方法,包括网友也提供的一些方法。

这次我会演示我在编程中经常和Borrow Checker有冲突的一些场景,提供一些修改代码以便成功编译的编程模式。

假定我们使用一个数据库连接池去连接一个假想的Postgres数据库。这个假想的库的API需要我们首先使用一个字符串初始化一个连接池。一旦连接池初始化成功,我们就可以调用它的connect方法得到一个可用的拥有所用权的链接对象进行查询。

阅读全文

用最简单的方式实现 Rust RPC 服务

RPC是对进程内函数调用的分布式开展,它将进程内的函数调用,扩展成对远程机器上的相应的函数的调用。如何使用最少的代码,用最容易的方式调用远程函数呢?

首先需要知道远程机器的IP地址和端口,然后呢,使用原先进程内的方式直接调用即可,这是最容易的RPC调用。容易不意味着功能简单,而是rpc库背后默默为你承担了序列化、网络传输、路由、服务调用、服务治理的功能。

rpcx秉承 Go 标准库简单而强大的设计理念,为 Rust 提供了一个原生的 rpc 库。

为什么说是原生呢?因为你可以使用任何你熟悉的编程语言通过HTTP或者JSON-RPC2.0的方式访问Go或者Java实现的rpcx服务,但是除了Go/Java编程语言你没有办法使用raw的rpcx protocol实现TCP的网络调用,而基于TCP的RPC性能要远远高于Request-Response这种类HTTP的调用模型。

现在rpcx for rust的库也发布了: rpcx-rs。 你可以使用它原生的访问Go或者Java实现的rpcx服务,也可以使用Rust提供rpcx服务。

关键是,依然是那么的简单。

阅读全文

读写 Redis RESP3 协议以及Redis 6.0客户端缓存

在四月份的一篇翻译的文章中,我介绍了读写Redis RESP version 2的协议的Go 语言的实现,你可以使用它采用底层的方式读写5.0以及以下版本的Redis。Redis 6.0还在开发之中年底或者明年初就要发布了。Redis 6.0支持多线程I/O,还有客户端缓存。

客户端缓存是未来Redis最重要的特性。如果我们需要快速存储和快速缓存,那么我们就需要在客户端存储数据的子集。这是为了提供小延迟、大规模数据的想法的自然延伸。很多公司都采用了在客户端缓存数据以避免每次都请求redis,但是本地缓存和redis服务器数据之间有延迟,很难保证数据的一致性。Ben Malec在Redis Conf 2018上做了一个关于客户端缓存的演讲,给了Salvatore Sanfilippo以灵感,Salvatore Sanfilippo决定在Redis 6.0中支持客户端缓存的功能。但是为了支持这个功能,使用当前的redis协议很难实现,所以他设计了下一代的Redis协议: RESP3

阅读全文

理解Rust的生命周期

原文: Understanding Lifetimes in Rust by Daniel P. Clark.

高级语言很方便地处理我们代码中的每个对象的存活的范围(scope), 我们不需要理解这些对象的生命周期。Rust同样为我们管理着生命周期,我们可以通过所有权(ownership)和借用(borrowing)对简单情况进行更多的控制,但是对于复杂的情况,我们需要在代码中给出识别标识,以便编译器能够理解更大的生命周期的范围。

简单的说,一切的一切归根结底都是为了在对象使用完之后就释放它。高级语言如Go、Ruby、Python等等,使用垃圾回收器在整个代码中扫描和标记对象,以查看它们是否准备好从内存中释放掉,并将对所有已标记可释放的对象执行释放操作。当你不再使用对象时,低级语言如C、汇编要求你手工释放它们。

Rust避免了垃圾回收和手写代码释放内存的成本,它根据代码库中每个对象的生命周期维护释放内存的时间。生存期主要由所有权系统决定(描述代码的哪个部分负责拥有内存中对象的系统),以及在复杂情况下,由帮助编译器而提供的手动生命周期描述符来决定。一旦对象的生命周期束,内存将立即释放。

阅读全文

连接跟踪模块导致的网络不可用

今天将一个业务的流量切到新部署的一台机器上。前几天已经灰度了一个业务到这台机器上,一直很稳定,所以准备切更多的流量。
11点左右开始把业务的流量切到这台机器上,没多久业务反馈服务不可访问,紧急把流量切回到原服务,留下新机器备查。

阅读全文

并发编程趣题: 制造一氧化二氢

最近leetcode的算法题中新增加了一个并发问题的分类, 目前提供了四道题,一道简单题,两道中级题和一道Hard题。这是leetcode尝试在并发算法分类的一个尝试,虽然目前还在摸索阶段,有的题目可以通过作弊的方式提供高分答案,有的即使是通过了测试,但是其实还是有并发的问题的,只不过测试用例没有覆盖到而已。

这也说明了并发编程并不是一件很容易的事情,但是还是很高兴leetcode能在这个方面往前走一步。其中的最难的那道题,看它第一眼的时候觉得还是比较简单的,但是仔细思考后却发现实现它并不是一件很容易的事情,即使目前的接受率才51.9%,但其实已接收的提交有很多还是有并发的问题的,只不过由于验证的问题并没有验证出来。

阅读全文