Code For Colorful Life
MySQL 如何执行一条 SQL
词法分析
词法分析(英语:lexical analysis)是计算机科学中将字符序列转换为记号(token)序列的过程。进行词法分析的程序或者函数叫作词法分析器(lexical analyzer,简称lexer),也叫扫描器(scanner)。词法分析器一般以函数的形式存在,供语法分析器调用。
MySQL 的词法分析器并没有直接使用开源的 flex ,而是自己手动实现了词法分析的函数:
int MYSQLlex(YYSTYPE *yylval, YYLTYPE *yylloc, THD *thd)
输入参数为
- yylval:存储解析到的 token 的一些信息:
- yylval->lex_str:记录解析到的 token 字符串
- yylval->charset:记录指定 token 的字符编码,Character Set Introducers
- yylval->optimizer_hints:记录解析到的查询优化器 hint
- yylval->symbol:如果 token 是 MySQL 关键字的话,会记录到这个字段
- yylloc:记录解析到的 token 在原始字符串和分析过程中用到的预处理缓冲中的位置
- thd:处理当前请求的线程
- m_parser_state:
- m_input:解析器参数
- m_lip:字符输入流
- m_yacc:语法解析器内部状态
- m_parser_state:
MYSQLlex(yylval, yylloc, thd)
调用 lex_one_token(yylval, thd)
从 thd->m_parser_state->m_lip
读取字符流解析为一个个 token 返回给语法分析器 yyparse。token 用一个整型值表示,在 sql_yacc.h 中通过 #define 定义:
词法分析器的核心实现逻辑都在 lex_one_token,通过有限状态机实现分词。
/* Tokens. */
#define ABORT_SYM 258
#define ACCESSIBLE_SYM 259
#define ACCOUNT_SYM 260
......
#define ZEROFILL 907
#define JSON_OBJECTAGG 908
#define JSON_ARRAYAGG 909
以 select uid from table1
为例,会被解析为 token 序列:SELECT_SYM IDENT_QUOTED FROM IDENT_QUOTED END_OF_INPUT
。
Read more...
Go PProf 采样的实现
CPU Profiler
CPU Profiler 能帮助我们分析程序中消耗 CPU Time (包括用户空间和内核空间的时间)最多的调用栈,我们可以通过如下 API 来获得一份 CPU 消耗的采样结果:
- 通过命令行测试工具:
go test -cpuprofile cpu.pprof
- 代码中主动开始和停止:
pprof.StartCPUProfile(w)
和pprof.StopCPUProfile()
- 通过 HTTP 接口:
import _ "net/http/pprof"
和GET /debug/pprof/profile?seconds=30
虽然提供了 API runtime.SetCPUProfileRate(hz)
让我们设置采样的速率,但通常我们并不需要调整。在 pprof.StartCPUProfile(w)
中,默认会设置 runtime.SetCPUProfileRate(100)
,即程序每消耗 10ms CPU Time 就采样一次。
runtime.SetCPUProfileRate(hz)
会调用 setcpuprofilerate(hz)
设置 CPU 采样的速率为 hz 次每秒,如果 hz <= 0,则会停止 CPU 采样。
在 Go 1.18 之前,setcpuprofilerate(hz)
会通过 signal_unix.go 中的 setProcessCPUProfiler(hz)
执行系统调用 setitimer(_ITIMER_PROF, new, old *itimerval)
设置进程级别的定时器,即当所有线程消耗的 CPU Time 达到 1000/hz ms 时,进程会收到 SIGPROF 信号,并且会随机地由任意一个正在运行中的线程(其信号屏蔽字没有 SIGPROF)执行信号处理器。
这会存在一个问题,即当进程消耗大量的 CPU Time 时,会产生大量的 SIGPROF 信号,而 SIGPROF 信号属于标准信号,多个连续的标准信号,只能有一个处于 pending 状态,其他会被丢弃,这会导致生成的 profile 数据是不准确的。详情可查看 ISSUE:runtime/pprof: Linux CPU profiles inaccurate beyond 250% CPU use
所以从 Go 1.18 开始,在 Linux 平台上,setcpuprofilerate(hz)
会通过 os_linux.go setThreadCPUProfiler(hz)
执行系统调用 timer_create(_CLOCK_THREAD_CPUTIME_ID, &sevp, &timerid)
为每个线程设置单独的定时器,即当一个线程消耗的 CPU Time 达到 1000/hz ms 时,该线程会定向收到 SIGPROF 信号,然后执行信号处理器。
在 Go 中有一个统一的信号处理器 sighandler(sig uint32, info *siginfo, ctxt unsafe.Pointer, gp *g)
,如果是 SIGPROF 会调用 sigprof(pc, sp, lr uintptr, gp *g, mp *m)
生成当前线程的调用栈,然后调用 cpuProfile.add(tagPtr *unsafe.Pointer, stk []uintptr)
记录下来。
所以当我们需要压测消耗大量 CPU 的程序,应该使用 Go 1.18 及以上,并且在 Linux 平台下进行。
Read more...
Go GC 如何优化掉重新扫描协程栈
三色垃圾回收算法维基百科图解:
如果三色垃圾回收算法是在程序 STW 的时候执行,那么算法的正确性是能够保证的。但如果三色垃圾回收算法在执行的过程中,程序也在执行(在垃圾回收算法中的术语称为赋值器),赋值器会修改对象之间的引用关系,当出现黑色对象引用了白色对象,同时不存在可以通过其他灰色对象直接/间接访问到该白色对象的路径,由于三色垃圾回收算法不会重新扫描黑色对象,就会出现黑色对象引用的白色对象被回收的情况,也就是本该存活的对象被回收器(垃圾回收代码的执行)错误地当做垃圾回收了。
Read more...
Golang Mock 原理分析
在写单元测试时,通常需要对某些不容易构造或者不容易获取的对象进行 mock,那么在 Golang 中,我们可以 mock 哪些对象,又有哪些 mock 的方法呢,以及它们是如何实现的?本文将对几个 Golang 常见的开源库进行分析,了解其实现原理。
gomock 的实现
https://github.com/golang/mock 用于 interface 接口的 mock,需要先通过命令行工具 mockgen 生成 interface 的 mock 类型,通常会把命令用 //go:generate
写在代码中,比如:
package main
//go:generate mockgen -source=main.go -destination=foo_mock.go -package=main Foo
type Foo interface {
Say(string, []string) string
}
func main() {
}
mockgen 会为 Foo 生成 MockFoo 类型的实现:
// MockFoo is a mock of Foo interface.
type MockFoo struct {
ctrl *gomock.Controller
recorder *MockFooMockRecorder
}
// MockFooMockRecorder is the mock recorder for MockFoo.
type MockFooMockRecorder struct {
mock *MockFoo
}
// NewMockFoo creates a new mock instance.
func NewMockFoo(ctrl *gomock.Controller) *MockFoo {
mock := &MockFoo{ctrl: ctrl}
mock.recorder = &MockFooMockRecorder{mock}
return mock
}
Read more...
MySQL DDL
MySQL 表结构信息存储
InnoDB 表和索引可以创建在系统表空间或者独立表空间(innodb_file_per_table=on)。对于 InnoDB 表,MySQL 会将表的数据字典信息存储在 .frm 文件中,同时也会存储入口信息(数据库名/表名)到系统表空间,在 INFORMATION_SCHEMA.INNODB_SYS_* 系统表中能够查询到表结构信息;如果 innodb_file_per_table=on,MySQL 会将表的数据存储在 .ibd 文件中,否则存储在系统表空间中。
Read more...
Istio 学习笔记
常用的 Istio 资源类型
VirtualService
控制请求路由到网格中哪个服务。
- hosts: 可以配置为带通配符前缀的 DNS 名称,或者 IP 地址。如果采用缩写,比如 reviews,那么在 Kubernetes 平台上就会根据 VirtualService 所在的命名空间,解释为 reviews.default.svc.cluster.local
- gateways: 该 VirtualService 应用于哪些 Gateway 和 Sidecar。如果没有配置,那么默认值为 mesh。mesh 表示所有的 Sidecar。
- http HTTPRoute[]: 匹配 HTTP 流量的有序路由规则列表,按顺序应用于 Kubernetes service.spec.ports.name(以 http-/http2-/grpc-* 命名)、gateway.spec.servers.port.protocol(HTTP/HTTP2/GRPC/ TLS-terminated-HTTPS)、serviceentry.spec.ports.protocol(HTTP/HTTP2/GRPC),第一条匹配路由规则将被使用。
- name: 路由的名字,会被记录到 access log 中
- match HTTPMatchRequest[]: 单个 HTTPMatchRequest 中的条件是 AND 语义,多个 HTTPMatchRequest 之间是 OR 语义
- name: 与路由的名字拼接后记录到 access log 中
- uri StringMatch: 匹配 URI,支持三种匹配规则:
- exact: 精确匹配
- prefix:前缀匹配
- regex:正则匹配
- ignoreUriCase: 不区分大小写匹配 URI
- scheme StringMatch: 匹配 URI Scheme
- method StringMatch: 匹配 HTTP method
- authority StringMatch: 匹配 HTTP Authority
- headers map<string, StringMatch>: 必须小写,并且采用连字符作为分隔符,例如 x-request-id。如果值为空,则判断请求头是否存在。注意 key 如果为 uri、scheme、method、authority 将被忽略
- port: 端口
- sourceLabels: 限制部分 Sidecar 应用此匹配规则,gateways 必须包含 mesh 值
- gateways: 覆盖顶层的 gateways
- queryParams map<string, StringMatch>: 匹配查询参数,不支持前缀匹配
- withoutHeaders map<string, StringMatch>: 与 headers 参数相反的含义
- sourceNamespace: 限制应用此匹配规则的命名空间,gateways 必须包含 mesh 值
- route HTTPRouteDestination[]: 流量被转发到哪个服务上
- destination Destination:
- host: 必须存在于 service registry(Kubernetes services)中,或来自 ServiceEntry 中定义的 hosts
- subset: DestinationRule 中定义的 subset 名字
- port: 转发的端口
- weight: 权重
- headers Headers: 对请求头或响应头进行修改
- destination Destination:
- redirect: 配置重定向
- delegate: route 和 redirect 为空时才可以设置,用于指定把流量转交给其他 VirtualService 处理
- rewrite: 转发请求之前对 uri 和 Authority/Host 请求头进行重写
- timeout: 请求超时
- retries: 重试策略
- fault: 故障注入
- mirror Destination: 流量镜像
- mirrorPercentage: 流量镜像百分比,默认 100%
- corsPolicy:CORS 策略
- headers Headers:对请求头或响应头进行修改
- tls: 匹配 HTTPS 流量的有序路由规则列表。
- tcp: 匹配 TCP 流量的有序路由规则列表。
- exportTo string[]: 控制 VirtualService 能否被其他命名空间的 Gateway 和 Sidecar 使用,如果不设置值,默认导出到所有命名空间
- . 表示当前命名空间
- * 表示所有命名空间
Read more...
Kubernetes CronJob 完全指南
Linux Crontab 在过去作为执行定时任务的服务被广泛使用至今,但因其缺乏弹性,也出现了一些分布式的定时任务解决方案,比如 vipshop/Saturn、ihaolin/antares,那么在云原生时代,Kubernetes 作为最受欢迎的容器编排系统,它所提供的 CronJob 类型的工作负载,如何满足我们的需求,以及使用上会有哪些需要注意的地方,包括官方文档中没有讲述的细节,笔者将在这篇文章进行介绍。
Linux Crontab 相信很多人都熟悉,通过 crontab -u nobody -l
我们可以查看指定用户的定时任务,如果需要修改定时任务,则执行 crontab -u nobody -e
,通过修改文件的方式配置定时任务,文件格式为:
# For details see man 4 crontabs
# Example of job definition:
# .---------------- minute (0 - 59)
# | .------------- hour (0 - 23)
# | | .---------- day of month (1 - 31)
# | | | .------- month (1 - 12) OR jan,feb,mar,apr ...
# | | | | .---- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat
# | | | | |
# * * * * * user-name command to be executed
具体的调度执行是由 crond.service 负责。
Read more...