Code For Colorful Life
Kubernetes 艰难之路
本文参考 Github 项目 kubernetes-the-hard-way 安装 Kubernetes 集群。
kubernetes-the-hard-way 部署了3台工作节点和3台管理节点,且管理节点不运行 Pod;CNI 网络插件使用 bridge,跨主机之间的容器通信需要人为设置宿主机路由表。
本文部署采用2台 VMware Fusion 的虚拟机,操作系统为 Ubuntu Server 18.04,一台配置为管理节点,另一台配置为工作节点,同时管理节点也为工作节点;CNI 网络插件使用 Calico;并解决无法解析外部域名问题。
总体架构图:
Read more...
golang gc
参考文章:垃圾回收器
因为三色标记以及混合写屏障在 Go 中的源码实现,笔者目前尚未理解清楚,所以本文省略了该部分内容。后续有时间研究明白了再补上~
并发三色标记垃圾回收
算法思想可参考:Golang’s Real-time GC in Theory and Practice 和 Golang’s realtime garbage collector
然而在实际实现上却有些变化,例如网上很多文章都说在启用写屏障的情况下,新创建的对象都标记为灰色,但笔者在 go 1.13 源码中,func gcmarknewobject(obj, size, scanSize uintptr)
方法被 mallocgc 调用,其注释写明了:gcmarknewobject marks a newly allocated object black. obj must not contain any non-nil pointers。
Go 运行时的垃圾回收分四个阶段:
- 准备阶段:STW,初始化标记任务,启用写屏障
- 标记阶段 GCMark:标记存活对象,并发与用户代码执行,保持只占用25%CPU
- 标记终止阶段 GCMarkTermination:STW,关闭写屏障
- 清扫阶段 GCOff:回收白色对象,并发与用户代码执行
Read more...
golang memory
参考文章:
Go 设计内存分配器,一是为了统一管理内存,提前分配或一次性释放大块内存,减少与操作系统进行系统调用造成的开销,进而提高程序的运行性能;二是为垃圾回收器提供支持。
传统意义上堆内存,被 Go 运行时划分为了两个部分:
- Go 运行时自身所需的堆内存
- Go 用户态代码和 goroutine 的执行栈所需的堆内存
Read more...
golang goroutine 堆栈
参考文章:
栈相关的数据结构
// Stack describes a Go execution stack.
// The bounds of the stack are exactly [lo, hi),
// with no implicit data structures on either side.
// stack 数据结构用于表示一个 goroutine 的执行堆栈
// 堆栈的边界范围是 [lo, hi)
type stack struct {
lo uintptr
hi uintptr
}
type g struct {
// Stack parameters.
// stack describes the actual stack memory: [stack.lo, stack.hi).
// stackguard0 is the stack pointer compared in the Go stack growth prologue.
// It is stack.lo+StackGuard normally, but can be StackPreempt to trigger a preemption.
// stackguard1 is the stack pointer compared in the C stack growth prologue.
// It is stack.lo+StackGuard on g0 and gsignal stacks.
// It is ~0 on other goroutine stacks, to trigger a call to morestackc (and crash).
stack stack // offset known to runtime/cgo
// stackguard0 用于跟函数的 sp 进行比较,通常等于 stack.lo+StackGuard,但是在需要触发抢占调度时,会被赋值为 StackPreempt
stackguard0 uintptr // offset known to liblink
// stackguard1 用于跟系统线程的 sp 进行比较,在 g0 和 gsignal 上等于 stack.lo+StackGuard,在其他 goroutine 上等于 ~0,用于触发栈扩容和崩溃
stackguard1 uintptr // offset known to liblink
......
}
Read more...
golang goroutine 的创建、调度和释放
参考文章:
本文作为笔者学习 Golang 调度器实现的总结,笔者带着如下几个问题,研究 go1.13.1 源码。
- goroutine 是如何创建、调度、退出的?
- 调度器是如何对 goroutine 进行上下文切换的?
- 抢占式调度是怎么做的?
- go 启动的时候会创建多少个 M,M 的数量在何时会增加?
- 当 goroutine 阻塞时,关联的 P、M 会怎样?
Read more...
MySQL 大表处理
背景
业务有一张表现在有1亿多条记录,表大小500G,由于对历史数据不会再访问,可以将历史数据进行归档。
如果大表的数据都是会被访问的可以考虑使用 MySQL 的分区表,但 MySQL 的分区表功能有些限制,可参考:Restrictions and Limitations on Partitioning。如果不方便使用 MySQL 的分区表的话,可以考虑在业务上实现。
归档
Percona Toolkit 提供了一个 perl 编写的归档工具:pt-archiver
如果采用归档到文件的方式,该归档工具的工作流程类似下面的伪代码:
以下流程是笔者对 pt-archiver 与 MySQL 的连接进行抓包分析出来的。
set autocommit = 0
chunk = select archive rows limit n // --limit
count = 0
for row in chunk:
archive one row
delete one row
count++
if count == txn-size: // --txn-size
count = 0
commit
if chunk is empty:
chunk = select archive rows limit n // --limit
- –txn-size:太小会出现非常多的小事务,增加 MySQL 负载(比如当innodb_flush_log_at_trx_commit=1时,每一个事务的commit都需要进行flush操作),太大会导致事务执行时间过长,增加出现锁等待和死锁的概率。
- –limit:在 innodb 表上,如果没有使用 –for-update 参数的话,则 select 的时候不会跟其他事务产生锁竞争。
笔者采用的命令参数如下:
# 先导出数据,不删除表里的数据
pt-archiver --charset utf8 --source h=${MYSQL_HOST},D=${MYSQL_DB},t=${MYSQL_TABLE} --user ${MYSQL_USER} --ask-pass --file '%Y-%m-%d-%D.%t' --progress 1000 --where 'created_at < "2019-01-01 00:00:00"' --txn-size 100 --limit 1000 --no-delete
# 确认导出的数据没有问题,删除表中的数据
pt-archiver --charset utf8 --source h=${MYSQL_HOST},D=${MYSQL_DB},t=${MYSQL_TABLE} --user ${MYSQL_USER} --ask-pass --progress 1000 --where 'created_at < "2019-01-01 00:00:00"' --txn-size 100 --limit 1000 --nosafe-auto-increment --purge
- 默认情况下,pt-archiver的查找会强制使用主键索引,虽然 match_logs 表上有 created_at 字段的索引,但是我们不能使用该索引,因为它不是唯一索引,如果使用 created_at 字段的索引会导致导出来的数据少了,这跟 pt-archiver 如何获取下一页数据有关
- 当pt-archiver适合使用主键索引时,默认情况下,不会归档最后一条记录(即自增主键最大的记录,这是出于安全考虑,因为MySQL自增列的最大值在5.7中是存储在内存中的,没有持久化到磁盘,重启后会通过 SELECT MAX(ai_col) FROM table_name FOR UPDATE; 将获取到的值+1作为下一个可用的自增值),如果归档最后一条记录没有影响,可以添加–nosafe-auto-increment参数
- 归档完数据后,还需要执行下
OPTIMIZE TABLE ${MYSQL_TABLE}
,以回收空间,详细原因可参考文章:MySQL ibdata 存储空间的回收
golang grpc 客户端负载均衡、重试、健康检查
Go GRPC 客户端是如何管理与服务端的连接的?
grpc.ClientConn 表示一个客户端实例与服务端之间的连接,主要包含如下数据结构:
grpc.connectivityStateManager(grpc.ClientConn.csMgr) 总体的连接状态
状态类型为 connectivity.State,有如下几种状态:
- Idle
- Connecting
- Ready
- TransientFailure
- Shutdown
grpc.ClientConn 包含了多个 grpc.addrConn(每个 grpc.addrConn 表示客户端到一个服务端的一条连接),每个 grpc.addrConn 也有自己的连接转态。
- 当至少有一个 grpc.addrConn.state = Ready,则 grpc.ClientConn.csMgr.state = Ready
- 当至少有一个 grpc.addrConn.state = Connecting,则 grpc.ClientConn.csMgr.state = Connecting
- 否则 grpc.ClientConn.csMgr.state = TransientFailure
默认实现下客户端与某一个服务端(host:port)只会建立一条连接,所有 RPC 执行都会复用这条连接。 关于为何只建立一条连接可以看下这个 issue:Use multiple connections to avoid the server’s SETTINGS_MAX_CONCURRENT_STREAMS limit #11704 不过如果使用 manual.Resolver,把同一个服务地址复制多遍,也能做到与一个服务端建立多个连接。
关于 grpc.addrConn.state 的转态切换可参考设计文档:gRPC Connectivity Semantics and API
Read more...