0%

清风拂南浦,微浪拍青泥。
赤霞惊走蟹,白鹭喜衔鱼。
细沙滚顽儿,阔海逐姝女。
遥遥万里梦,今夕似往夕。

阅读

书籍名称 进度
《十日谈》 100%
《中国人史纲》 90%
《Java 编程思想》 25%
《黑客与画家》 40%
目标 600%
实际 255%
完成情况 42.5%

回顾:第 1 个季度的阅读情况完成较差,需要考虑如何有效完成以上目标。

未完成目标的主要原因是:

  1. 阅读不够积极,没有足够的兴趣和动力进行阅读,需要经常自省,反思自己;
  2. 阅读需要一定的条件,微信读书收费,纸质书不能常伴左右,需要购买会员或者通过其他渠道进行阅读。

需要做的事情:

  1. 周期性总结和反思自己的阅读进度和阅读目标,沉下心来思考阅读的意义和阅读的启示
  2. 考虑将纸质书随身携带,或者在 QQ 阅读、Kindle 等阅读器上进行阅读

学习外语

事项 进度
打卡次数 90
目标 85
完成进度 105%

回顾:
由于背单词的习惯已经在 2020 年开始,并持续了很长一段时间,因此已经能很好地坚持每天背单词。单词本确实拥有良好的效果,但是会延长外语的学习时间。建议每日固定有限个的单词本背单词的个数,提高可执行性。

需要做的事情:

  1. 确定每日单词本背单词个数
  2. 选一个合适的背包,每天坚持携带单词本

记录与分享

回顾:本周期内除了 2021 年 Flag 外未有其他博客更新。

未完成目标的原因:思考不足,没有惩戒行为。

需要做的事情:

  1. 坚持每日小记,通过每日至少一句话的形式培养写作习惯,并在有灵感和时间时形成真正的文章
  2. 加强截止日的紧迫感,截止日之前未完成应该有相应的惩戒行为

控制情绪

事项 进度
火花次数 1
目标 <= 3
完成进度 300%

回顾: 本周期内与他人有火花的次数为 1 次,比较好的做到了与他人沟通保持良好的状态。

需要做的事情:
继续在交流时整理情绪,保持正常的心态。

注重家庭

事项 进度 目标 是否完成
沟通 1 月 1次 1月 1 次
纪念日准备 / / /
吵架次数 1 1.5

回顾:
基本还做的可以。

需要做的事情:

目标修改为每个月有效沟通一次。
增加打扫目标,每 2 周至少做一次大扫除。

保持运动

事项 完成次数 目标 完成进度
健身 32 37.5 85.33%
体重 127 120-130 100%

回顾:
本周期内做到了冬练三九,比较好地完成了目标。

问题:

  1. 下雨天锻炼问题
  2. 假期内锻炼问题
  3. 睡眠问题

结论:
下雨天如果是工作日,则在公司健身房进行锻炼;
下雨天如果是休息日,则改为散步或者挪到工作日进行锻炼;
假期内超过 15000 步,且在公园或者绿地等休闲场所连续运动超过 5000 步认为完成了运动(走路、跑步、爬山皆可)

作息规律

事项 完成次数 目标 完成进度
早睡早起 55 75 73%

回顾:
早睡早起与锻炼身体密切相关,基本在努力做到作息规律,但是楼上楼下的噪音是最大困扰。

结论:
早睡早起基本做到了,要想完全做到的话搬到一个安静的小区或者找到如何在嘈杂的环境中入睡是最迫切的事情,没有办法在牺牲睡眠的情况下去做其他事情。不过,做到了早睡早起的日子里,在多出来的时间中应该做些什么,值得认真思考。

看看世界

本周期内未完成远游,但是去了世纪公园、中山公园等地方,2 次去看房,1 次春节,基本做到了周末没有呆在家中。

提升技术

事项 完成次数 目标 完成进度
分享技术 0 3 0%
Leetcode 刷题 13 12.5 100%

回顾: 本周期内分享技术完全没有做到,Leetcode 刷题 全部完成。

问题: 没有思考,未能及时将学习的知识点进行记录与分享。

需要做的事情:学习知识时及时落下来,弄懂弄通并在分享中巩固知识。

总结

一半的事情如期在进行,还不错,值的鼓励,但是做事要踏实,对得起自己,不能敷衍。

以下几点作为提高点去进一步执行:

  1. 读书需沉下心来,多想想读书的意义,保持积极蓬勃的心态;可以通过 Kindle 和 QQ 阅读等 APP 去跳过无法阅读的困境,或者直接付费;随身携带背包,将要读的书带在身边。
  2. 随身携带单词本,每天通过单词本背单词 6 个左右。
  3. 每日小记,培养写作习惯,如果一个月未更新博客,罚跑 5 公里一次。
  4. 交流时整理情绪,保持良好心态。
  5. 给家人打电话时做到有效沟通,温暖、积极、平衡;每 2 周做一次大扫除;准备纪念日。
  6. 开始学习游泳,继续坚持健身。
  7. 考虑搬家或者找到嘈杂环境中睡眠的方案,早起如果不健身的时候要读书、写字或者整理房间。
  8. 准备做一次远游规划。
  9. 学习知识点时做笔记,并分享。
  10. 月底时应该思考本月的完成情况,心中有数。

TarsBenchmark 可以用来压测 tars 服务,方便测试服务的性能。TarsBenchmark 主要依赖 tars2case 命令和 tb 命令。

tars2case 命令用于生成测试用例,tb 命令用来进行压测。

构建环境

可以根据以下的 dockerfile 手动构建镜像,然后根据镜像生成容器。

1
2
3
FROM golang:latest
RUN apt update && apt install cmake bison flex zlib1g-dev -y && rm -rf /var/lib/apt/lists/*
RUN cd /root && git clone https://github.com/TarsCloud/TarsCpp.git --recursive && mkdir -p /root/TarsCpp/build && cd /root/TarsCpp/build && cmake .. && make install && cd /root && git clone https://github.com/TarsCloud/TarsBenchmark.git && mkdir -p /root/TarsBenchmark/build && cd /root/TarsBenchmark/build && cmake .. && make all && export PATH=$PATH:/usr/local/tars/cpp/tools:/root/TarsBenchmark/build/bin

或者直接利用现有镜像,

1
2
docker run -td --name golang golang:latest
docker exec -it golang bash

进入容器后执行:

1
2
apt update && apt install cmake bison flex zlib1g-dev && rm -rf /var/lib/apt/lists/*
cd /root && git clone https://github.com/TarsCloud/TarsCpp.git --recursive && mkdir -p /root/TarsCpp/build && cd /root/TarsCpp/build && cmake .. && make install && cd /root && git clone https://github.com/TarsCloud/TarsBenchmark.git && mkdir -p /root/TarsBenchmark/build && cd /root/TarsBenchmark/build && cmake .. && make all && export PATH=$PATH:/usr/local/tars/cpp/tools:/root/TarsBenchmark/build/bin

生成用例文件

例如你的 Tars 文件是这样的,名字为 test.tars

1
2
3
4
5
module Test {
interface Greet {
void Hello(string text, out string response);
};
};

进入 docker 容器,执行

1
tars2case --json test.tars --dir=.

得到如下文件

1
2
3
|-- Hello.case
|-- Hello.desc
`-- test.tars

每个方法会生成一个对应的 desc 和 case 文件,前者为描述文件,后者为测试用例。

Hello.desc

1
{ "1_text_string": "" }

Hello.case

1
{ "text": "string" }

修改用例内容

tb 对 tars 结构中 Number 和 string 支持生成随机内容,通过如下两种随机方式(Value 必须以 string)。

<范围随机值>用 [1-100] 表示,表示在 1-100 内随机出现, 必须是数字。
<限定随机值>用 [369,aaa,bbb] 表示,表示在 369,aaa,bbb 中随机出现。

例如修改 Hello.case,

1
{ "text": "[369, aaa, bbb]" }

执行用例

执行以下命令开始压测,压测会每隔指定时间展示该段时间内的请求结果,最终会生成汇总结果。

1
tb -c 5 -s 100 -I 60 -D 172.16.1.139 -P 10037 -p json -i 10 -I 60 -S QDINT.BookshelfService.BookshelfObj -M getAllItemList

执行压测时当前目录需要有对应的 .case 和 .desc 的用例文件,上边的命令需要有 getAllItemList.case 和 getAllItemList.desc 这 2 个文件。

参数说明

1
2
3
4
5
6
7
8
9
10
11
-h                   帮助信息
-c 连接数量
-D 压测服务器IP,多个IP用';'区隔
-P 网络传输端口
-p 接口通信协议(tars|http)
-t(可选) 单个请求超时时间,默认3秒
-T(可选) 网络层协议,默认tcp
-I(可选) 压测持续时间(单位秒),默认1H
-i(可选) 控制台打印周期时间(单位秒),默认10秒
-s(可选) 最大速率限制,为空或0表示自动适配服务最佳速率
-n(可选) 最大压测进程限制,默认跟CPU核心数

  1. 坚持阅读,阅读 4 本技术书籍,总共 24 本书籍
  2. 学习外语, 利用单词 APP 和单词本背单词,打卡 340 次以上
  3. 记录与分享,博客更新 24 篇左右,感悟、思考、技术均可
  4. 控制情绪,与人沟通保持良好的状态,降低交流时的压迫感,记录到的有火花的交流次数少于 12 次
  5. 注重家庭,家务当日事当日毕,纪念日、生日有准备,因自己不当行为导致的吵架次数低于 6 次,与双方父母至少每月沟通一次
  6. 保持运动,健身 150 次以上,学会游泳,保持体重,体重继续控制在 120 到 130 斤之间
  7. 作息规律, 按时作息,11点半睡觉,7 点起床,300 次以上
  8. 看看世界,远游至少 2 次,日本 + 新疆,如果疫情原因日本仍不能成行,则去九寨沟或云南
  9. 提升技术,每个月学习一个新的技术点并分享到博客,完成 50 道中等及以上难度算法题
  10. 按时总结,看一下 2021 年目标的执行情况,而非 2022 才发现一个都没做

Apple 最近发布了 Big Sur,由于实在是受不了小红点,就尝鲜升级到了新版本的系统。然而,还是遇到了很多问题。

2K 显示屏字体过小问题

这个问题其实在上个版本我就遇到过,没想到升级了系统后再次遇到了该问题。

好在回忆起来在 v2ex 上看过该问题的解决方法,github 上的 一键开启 macOS HiDPI 可以解决该问题。

执行

1
bash -c "$(curl -fsSL https://raw.githubusercontent.com/xzhih/one-key-hidpi/master/hidpi.sh)"

第一步选择开启 HIDPI,之后选择 保持原样,再之后选择 手动输入分辨率,最后输入分辨率 2048x1152,然后就可以了。

这样显示器既可以看起来字体大小正常,又不会字体模糊。

键盘失灵问题

升级了系统之后,Macbook Pro 自带的键盘和外接键盘全部失灵。所幸的是外接键盘拔掉之后重新连接,还能正常使用。而原生键盘则无法正常使用。

考虑到开机输入密码时,键盘还能正常输入,遂认为是软件引起的问题,马上想到了自己有安装 Karabiner Elements,所以尝试将其卸载。

在 Applications 目录直接删除 Karabiner Elements 会遇到 The operation can’t be completed because the item “Karabiner-EventViewer.app” is locked.,造成无法删除。

可以先打开 Karabiner Elements ,在 Misc 菜单中有 Uninstall 选项,输入密码完成卸载即可。

卸载完成后重启电脑,键盘果然恢复正常使用。

顶部菜单栏空白间隔过大问题

最后一个非常影响外观的问题就是顶部菜单栏的图标中间有个巨大的间隔,尝试退出很多软件都无法解决。

最后经过搜索发现是搜狗输入法的问题,升级搜狗输入法,该问题即可解决。

网易 Mumu 模拟器

更新系统后,网易 Mumu 模拟器也不能正常工作了,表现为提示驱动安装失败。

卸载并重新安装后,应用恢复正常。

需要注意的是,在安装 Helper 后,需要在安全与隐私设置中进行同意。

大部分开源应用都是用了 MySQL 数据库。了解如何获取 MySQL 数据库的高阶信息,对于调试开源应用程序非常重要。在这篇文章中,我将用 9 个例子来说明如何查看任何 MySQL 数据库的数据库、表、列和索引的信息。

在接下来的 mysqlshow 示例中,你可以通过以下 2 种方式的任何一种来输入密码:

  • mysqlshow 命令后紧跟密码(不要输入任何空格)。在你直接使用 shell 脚本时很有用。

  • mysqlshow 命令后仅仅输入 -p 选项,但不输入密码,之后会提示你输入密码。当你从命令行中交互式地使用 mysqlshow 命令时,推荐使用这种方式。

1. 列出所有可用的数据库

请将 「 tmppassword 」 替换成你的 MySQL 数据库的 root 用户密码。

1
2
3
4
5
6
7
8
9
# mysqlshow  -u root -ptmppassword

+--------------------+
| Databases |
+--------------------+
| information_schema |
| mysql |
| sugarcrm |
+--------------------+

2. 列出数据库中的所有表

下边这个示例会列出 sugarcrm 数据库中的所有表。

1
2
3
4
5
6
7
8
9
10
# mysqlshow  -u root -ptmppassword sugarcrm

Database: sugarcrm
+--------------------------------+
| Tables |
+--------------------------------+
| accounts |
| accounts_audit |
| accounts_bugs |
+--------------------------------+

3. 列出数据库中的所有表并附带每张表的列数

1
2
3
4
5
6
7
8
9
10
11
# mysqlshow  -v -u root -p sugarcrm

Enter password:
Database: sugarcrm
+--------------------------------+----------+
| Tables | Columns |
+--------------------------------+----------+
| accounts | 33 |
| accounts_audit | 10 |
| accounts_bugs | 5 |
+-------------------------------------------+

4. 列出数据库中的所有表并附带每张表的列数和行数

请记住下边这个命令要输入 2 个 -v

1
2
3
4
5
6
7
8
9
10
11
# mysqlshow  -v -v -u root -p sugarcrm

Enter password:
Database: sugarcrm
+--------------------------------+----------+------------+
| Tables | Columns | Total Rows |
+--------------------------------+----------+------------+
| accounts | 33 | 252 |
| accounts_audit | 10 | 63 |
| accounts_bugs | 5 | 0 |
+---------------------------------------------------------+

5.列出某表的所有列

这个示例中,显示了 sugarcrm 库中的 accounts 表的所有可用的列名和该列其他信息。

1
2
3
4
5
6
7
8
# mysqlshow  -u root -ptmppassword sugarcrm accounts id

Database: sugarcrm Table: accounts Wildcard: id
+-------+----------+-----------------+------+-----+---------+-------+---------------------------------+---------+
| Field | Type | Collation | Null | Key | Default | Extra | Privileges | Comment |
+-------+----------+-----------------+------+-----+---------+-------+---------------------------------+---------+
| id | char(36) | utf8_general_ci | NO | PRI | | | select,insert,update,references | |
+-------+----------+-----------------+------+-----+---------+-------+---------------------------------+---------+

6. 列出某表的某列的详细信息

在这个示例中,列出了 accounts 表的 id 列的信息。

1
2
3
4
5
6
7
8
# mysqlshow  -u root -ptmppassword sugarcrm accounts id

Database: sugarcrm Table: accounts Wildcard: id
+-------+----------+-----------------+------+-----+---------+-------+---------------------------------+---------+
| Field | Type | Collation | Null | Key | Default | Extra | Privileges | Comment |
+-------+----------+-----------------+------+-----+---------+-------+---------------------------------+---------+
| id | char(36) | utf8_general_ci | NO | PRI | | | select,insert,update,references | |
+-------+----------+-----------------+------+-----+---------+-------+---------------------------------+---------+

7. 显示某张表的所有的元信息

1
# mysqlshow  -i  -u root -ptmppassword sugarcrm accounts

这结汇列出关于 accounts 表的以下所有信息。

  • Name
  • Engine
  • Version
  • Row_format
  • Rows
  • Avg_row_length
  • Data_length
  • Max_data_length
  • Index_length
  • Data_free
  • Auto_increment
  • Create_time
  • Update_time
  • Check_time
  • Collation
  • Checksum
  • Create_options
  • Comment

8. 列出某张表的列和索引信息

请记住索引信息会显示在输出的末尾,列信息的下方。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# mysqlshow -k -u root -ptmppassword sugarcrm accounts

Database: sugarcrm Table: accounts
+-----------------------------+--------------+-----------------+------+-----+---------+-------+---------------------------------+---------+
| Field | Type | Collation | Null | Key | Default | Extra | Privileges | Comment |
+-----------------------------+--------------+-----------------+------+-----+---------+-------+---------------------------------+---------+
| id | char(36) | utf8_general_ci | NO | PRI | | | select,insert,update,references | |
| name | varchar(150) | utf8_general_ci | YES | | | | select,insert,update,references | |
+----------+------------+------------------------+--------------+------------------+-----------+-------------+----------+--------+------+------------+---------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment |
+----------+------------+------------------------+--------------+------------------+-----------+-------------+----------+--------+------+------------+---------+
| accounts | 0 | PRIMARY | 1 | id | A | 252 | | | | BTREE | |
| accounts | 1 | idx_accnt_id_del | 1 | id | A | | | | | BTREE | |
+----------+------------+------------------------+--------------+------------------+-----------+-------------+----------+--------+------+------------+---------+

9. 显示某张表的索引但是不包含列信息

可以通过提供非法的列名来骗过 mysqlshow。因为 invalide_col_nameaccounts 表中不存在,所以下边这个命令仅会输出 accounts 表的索引信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
# mysqlshow -k -u root -ptmppassword sugarcrm accounts invalid_col_name

Database: sugarcrm Table: accounts Wildcard: invalid_col_name
+-------+------+-----------+------+-----+---------+-------+------------+---------+
| Field | Type | Collation | Null | Key | Default | Extra | Privileges | Comment |
+-------+------+-----------+------+-----+---------+-------+------------+---------+
+-------+------+-----------+------+-----+---------+-------+------------+---------+
+----------+------------+------------------------+--------------+------------------+-----------+-------------+----------+--------+------+------------+---------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment |
+----------+------------+------------------------+--------------+------------------+-----------+-------------+----------+--------+------+------------+---------+
| accounts | 0 | PRIMARY | 1 | id | A | 254 | | | | BTREE | |
| accounts | 1 | idx_accnt_id_del | 1 | id | A | | | | | BTREE | |
+----------+------------+------------------------+--------------+------------------+-----------+-------------+----------+--------+------+------------+---------+

说明

本篇文章翻译自 mysqlshow – Get Quick Info On MySQL DB, Table, Column and Index,笔者出于分享的目的翻译了该文章。如有侵权,请联系本人删除本文章。

如果你用到了一个库的 v2 版本,你依赖的另外一个库用到了该库的 v1 版本,而二者不兼容,该如何处理呢?

我之前在用到比如 github.com/urfave/cli/v2github.com/go-playground/validator/v10 这种库时,经常会看到一个库可以有多个版本,方便做版本的升级。

golang 本身在 1.13 版本后支持通过 go mod 的方式,引入同一个包的不同版本,防止出现交叉引用时出现的不兼容情况。

项目的 v1 版本

app.go

1
2
3
4
5
package mod

func Version() string {
return "v1"
}

go.mod

1
module github.com/phpcyy/mod

将项目打上 tag v1.0.0 并发布。

项目的 v2 版本

app.go

1
2
3
4
5
package mod

func Version() string {
return "v2"
}

go.mod

1
module github.com/phpcyy/mod/v2

将项目打上 tag v2.0.0 并发布。

测试

在另外一个项目中,执行 go get github.com/phpcyy/mod
go get github.com/phpcyy/mod/v2 来引入不同版本的库。

1
2
3
4
5
6
7
8
9
10
11
12
package main

import (
"fmt"
"github.com/phpcyy/mod"
mod2 "github.com/phpcyy/mod/v2"
)

func main() {
fmt.Println(mod.Version())
fmt.Println(mod2.Version())
}

打印显示

1
2
v1
v2

这样便可以做到在一个项目中引入一个库的不同版本。

具体代码见于 github.com/phpcyy/mod,可以作为一个小示例。

今天阅读了《Go Code Review Comments》 一文,该文明确了 Go 的代码风格,读后很有收获,在这里记录一些要点。

Gofmt

使用 gofmt 或者 goimports 格式化自己的代码来避免争论。

注释语句

注释应该是完整的语句,即便看起来有些冗余。这样可以在使用 godoc 生成文档时排版更友好。注释应该以要注释的对象开头,以句号结尾。

1
2
3
4
5
// Request represents a request to run a command.
type Request struct { ...

// Encode writes the JSON encoding of req to w.
func Encode(w io.Writer, req *Request) { ...

上下文 Contexts

context.Context 类型携带有认证、trace、deadlines、cancel 信号等信息。在 RPC 和 HTTP 请求中,应该显式地在整个链路中传递 Context。

绝大多数函数的第一个参数应该是 Context:

1
func F(ctx context.Context, /* other arguments */) {}

不要把 Context 作为 struct 的一个属性,而是把 Context 作为 struct method 的第一个参数,有个例外是如果 method 必须
要实现某个接口或者某个第三方 pkg 的签名。

在函数参数中不要使用自定义的 Context 类型、不要使用非 Context 包中定义的接口。

如果有参数需要传递,首先要考虑使用函数参数、reciever、全局变量。如果真的需要的话,才放到 Context 中。

复制

为了避免意想不到的问题,从其他 package 复制 struct 的时候一定要小心。比如说, bytes.Buffer 包含了一个 []byte 的 slice。如果复制了一个 Buffer,你复制出来的 slice 是源数组的别名,调用后续方法时可能会出现意想不到的问题。

通常情况下,一个叫做 T 的 struct,若其 method receiver 是 *T ,不要复制他。

Crypto Rand

不要使用 math/rand 包来生成秘钥,即使是一次性的。在未 Seed 的情况下,生成的结果完全是可预测的。就算使用了 time.Nanoseconds() 来 Seed,它的熵依旧只有几个 bit。取而代之的是,使用 crypto/rand 包的 Reader。如果你需要文本类型的值,将其转换成十六进制或者 Base64。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import (
"crypto/rand"
// "encoding/base64"
// "encoding/hex"
"fmt"
)

func Key() string {
buf := make([]byte, 16)
_, err := rand.Read(buf)
if err != nil {
panic(err) // out of randomness, should never happen
}
return fmt.Sprintf("%x", buf)
// or hex.EncodeToString(buf)
// or base64.StdEncoding.EncodeToString(buf)
}

声明空 Slice

声明空 Slice 时,使用

1
var t []string

而不是

1
t := []string{}

前者声明了一个值为 nil 的 Slice,后者不为 nil 但是长度为 0。它们的功能是一样的——它们的 lencap 都是 0,但是前者依然是推荐的写法。

在几种有限的情况下推荐使用非 nil 但长度为 0 的 Slice(即后一种写法),比如说要 Encode 成 JSON 的时候(nil 会被 Encode 成 null, 而 []string{} 会被 JSON 的 [])。

文档注释

所有顶层的、可导出的名字都应该有注释,其他的类型和函数也应该注释。

不要 Panic

不要使用 panic 来进行正常的错误处理,使用 error 和多返回值。

错误字符串

错误字符串不要以大写字母开头(除非是专有名词和缩写),不要以标点符号结尾。

示例

在添加新的 Package 时,应当包括示例:可以运行的示例,或演示完整调用过程的简单测试。

1
2
3
4
5
6
7
8
9
10
11
12
package stringutil_test

import (
"fmt"

"github.com/golang/example/stringutil"
)

func ExampleReverse() {
fmt.Println(stringutil.Reverse("hello"))
// Output: olleh
}

Goroutine 生命周期

当你开启一个 goroutine 时,弄清楚他们何时退出、是否能够退出。

通过阻塞 channel 的发送或接收可能会引起 goroutines 的内存泄漏:即使被阻塞的 channel 无法访问,垃圾收集器也不会终止 goroutine。

即使 goroutines 没有泄漏,当它们不再需要时却仍然将其留在内存中会导致其他细微且难以诊断的问题。往已经关闭的 channel 发送数据将会引发 panic。在“结果不被需要之后”修改仍在使用的输入仍然可能导致数据竞争。并且将 goroutines 留在内存中任意长时间将会导致不可预测的内存使用。

请尽量让并发代码足够简单,从而更容易地确认 goroutine 的生命周期。如果这不可行,请记录 goroutines 退出的时间和原因。

错误处理

不要使用 _ 来丢弃 error。如果一个函数返回了 error,要检查他以确保函数调用成功。处理 error,返回 error,或者在特定情况下 panic。

Package 导入

避免 Packge 导入时重命名,除非是为了防止名称冲突;好的 Package 名称不需要重命名。如果发生命名冲突,则更倾向于重命名最接近本地的 Package 或特定项目的 Package。

Package 导入按组进行组织,组与组之间有空行。标准库 Package 始终位于第一组中。

1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import (
"fmt"
"hash/adler32"
"os"

"appengine/foo"
"appengine/user"

"github.com/foo/bar"
"rsc.io/goversion/version"
)

goimports 会为你做这件事。

命名返回参数

想象一下以下示例在 godoc 中的样子。 命名返回参数:

1
2
func (n *Node) Parent1() (node *Node) {}
func (n *Node) Parent2() (node *Node, err error) {}

这样看起来啰啰嗦嗦,最好这样:

1
2
func (n *Node) Parent1() *Node {}
func (n *Node) Parent2() (*Node, error) {}

但是,如果一个函数返回了 2 个或以上的相同类型的参数,或者从上下文中难以清楚地断定返回参数的含义,那么最好给返回参数命名。但是不要仅为了避免参数声明而命名结果参数,这得不偿失。

1
func (f *Foo) Location() (float64, float64, error)

不如下边的代码清晰:

1
2
3
// Location returns f's latitude and longitude.
// Negative values mean south and west, respectively.
func (f *Foo) Location() (lat, long float64, err error)

还有,在某些情况下,你需要命名结果参数,以便在延迟闭包中更改它,这也是可以的。

Package 名字

Package 的所有调用都将使用该 Package 的字来完成,因此可以从标识符中省略该名称。例如,如果有一个 Package 叫做 chubby ,那么不要把类型命名为 ChubbyFile ,否则使用者将写为 chubby.ChubbyFile。而应该将该类型命名为 File,使用时将写为 chubby.File。避免使用无意义的包名称,如 util,common,misc,api,types 和 interfaces。

Reciever 名

一个 method 的 receiver 应该反映其身份,通常情况下 1 到 2 个字母的缩写就可以了(比如说 c、cl 作为 Client 的 receiver)。不要使用通用类的名字诸如 me、this 或者 self 这些。在 Go 中,method 的 receiver 的命名不必像 method 的其他参数一样那么具有描述性,因为他的角色显而易见、不必具有文档性。上下文要保持一致,如果你在一个 method 中使用 c 作为 receiver,不要在另外一个方法中使用 cl

变量名

在 Go 里,变量名应该尽量短而不是长。这一点在有限作用域的局部变量尤为必要。使用 c 而不是 lineCount,使用 i 而不是 sliceIndex

基本规则:作用域越长的变量,其名字越应该更具有描述性。对于方法接收器(method receiver),一个或两个字母就足够了。循环下标、Reader 之类的名字可以是单个字母(i,r)。不常用的变量和全局变量则需要更具描述性的名称。

docker 中 MySQL 的默认编码为 latin1,这会导致中文乱码,因此我们需要修改 MySQL 的默认字符集。

我们可以通过以下几种方式来修改 MySQL 的默认字符集。

修改配置文件

我们可以通过修改配置文件的形式,来改变字符集。

1
2
3
4
5
6
7
8
9
[client]
default-character-set=utf8mb4

[mysql]
default-character-set=utf8mb4

[mysqld]
collation-server = utf8mb4_unicode_ci
character-set-server = utf8mb4

修改 docker 启动命令

我们也可以通过修改 docker 命令来实现:

1
docker run mysql --name mysql --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci --skip-character-set-client-handshake

修改 docker-compose 配置文件

如果使用了 docker-compose,那么可以这样:

1
2
3
4
mysql:
image: mysql:5.7
container_name: mysql
command: ['mysqld', '--character-set-server=utf8mb4', '--collation-server=utf8mb4_unicode_ci', '--skip-character-set-client-handshake']

附言

特别地,我们来解释下 skip-character-set-client-handshake 命令的作用。

我们可以看下 MySQL 官方手册的如下片段:

--character-set-client-handshake

Property Value
Command-Line Format `–character-set-client-handshake[={OFF
Type Boolean
Default Value ON

Do not ignore character set information sent by the client. To ignore client information and use the default server character set, use --skip-character-set-client-handshake; this makes MySQL behave like MySQL 4.0.

也就是说在默认情况下,MySQL 客户端是使用客户端设置的编码的。可以通过 --skip-character-set-client-handshake 来让客户端使用服务端的编码,来保持编码的一致性。因为我们 docker 里边多数情况下都是连接 docker 里边的 MySQL 服务,所以设置 --skip-character-set-client-handshake 是非常合理的一个情况,这样便不必单独再去设置客户端编码。

什么是雪崩效应?

在微服务架构中通常会有多个服务层调用,基础服务的故障可能会导致级联故障,进而造成整个系统不可用的情况,这种现象被称为服务雪崩效应。服务雪崩效应是一种因服务提供者的不可用导致服务消费者的不可用,并将不可用逐渐放大的过程。

如果下图所示:A 作为服务提供者,B 为 A 的服务消费者,C 和 D 是 B 的服务消费者。A 不可用引起了 B 的不可用,并将不可用像滚雪球一样放大到 C 和 D 时,雪崩效应就形成了。

另外,在 A 不可用的情况下,由于 B 的重试会放大 A 的请求量,使 A 更加难以恢复。

image.png

熔断器

概念

熔断器模式可以有效防止对故障服务的不断重试,可以使服务调用者继续执行,不用等待错误的修正,或者浪费 CPU 等待超时发生。熔断器模式也可以使应用程序能够诊断错误是否已经修正,如果已经修正,应用程序会再次尝试调用操作。

能力

熔断器模式为远程服务提供的关键能力:

  • 快速失败
    当远程服务处于降级状态时,应用程序将会快速失败,并防止通常会拖垮整个应用程序的资源耗尽问题的出现。在大多数中断情况下,最好是部分服务关闭而不是完全关闭。
  • 优雅地失败
    通过超时和快速失败,熔断器模式使应用程序开发人员有能力优雅地失败或提供替代方案。
  • 无缝恢复
    熔断器模式可以定期检查所请求的资源是否重新上线,并在没有认为干预的情况下重新允许对该资源进行访问。

实现

  • 关闭状态(Closed)
    在关闭状态下,熔断器会放行请求;通过计数器来判断当前的服务状态,当超过阈值时进入打开状态;当到达设定的间隔时间后会清空计数器,重新开始计数。
  • 打开状态(Open)
    在打开状态下,熔断器会拦截所有请求并返回失败;当到达熔断器的超时时间(timeout)后进入半打开状态。
  • 半打开状态(Half-Open)
    在半打开状态下,熔断器会放行设定的个数个请求,如果请求全部成功,则进入关闭状态;如果有任何失败,则进入打开状态。

image.png
熔断器状态机

熔断器的选型

对比项/框架 sony/gobreaker rubyist/circuitbreaker mercari/go-circuitbreaker
Stars 1.1k 867 193
Forks 79 94 7
Issues Opened 4 10 1
Issues Closed 9 15 0
Last Updated 2019-11-25 2019-10-21 2019-12-27
Latest Version v0.4.1 v2.2.1 /
Complexity Easy Complex Middle
CancelContext Support No Yes Yes
Deadline Support No Yes Yes
Panel Support No Yes No
External Call Support No Yes Yes

github.com/sony/gobreaker

优点:

  • 简单、实现了完整的熔断器
  • sony 出品、稳定、star 最多

缺点:

  • 只支持传入 Func 的方式,如果返回错误则会判定为失败;如果忽略错误,则会在外层丢失错误。
  • 不支持 CancelContext 和 Timeout,可能会将非上游的错误识别为上游错误。

github.com/rubyist/circuitbreaker

优点:

  • 提供了完整的熔断器实现,直接支持错误率和错误个数的配置。
  • 提供了超时和 CancelContext 的支持。
  • 提供了 Panel,在服务中用到多个熔断器的时候可简化实现。
  • 提供了非 Func 方式的支持,便于自定义是否错误和返回正常的数据结构。

缺点:

  • 实现相对复杂,学习成本较高。

github.com/mercari/go-circuitbreaker

优点:

  • 供了完整的熔断器实现
  • 提供了 CancelContext 和 DeadlineContext 的支持。
  • 提供了 IgnoreErr 的支持,可以忽略部分错误。
  • 提供了非 Func 方式的支持,便于自定义是否错误返回正常的数据结构。

缺点:

  • 项目没有发布稳定版本的 tag。
  • issue 少,star 少,可能未得到充分验证。

个人倾向于选择 github.com/rubyist/circuitbreaker