作者:matrix
发布时间:2024-08-17
分类:Golang
Golang是不支持函数参数默认值的,但是也有很多办法可以解决
动态可变参数
func main() {
addItem("11", "a1")
addItem("2", "a2", "222")
}
func addItem(name, value string, opts ...string) {
fmt.Println("add item-->", name, value)
for _, opt := range opts {
fmt.Println("opt:", opt)
}
}
其中opts ...string
表示可变参数,类型为string,如果需要不同类型传入 看下面
可变参数 + 动态类型
type AlfredItem struct {
Title string
Subtitle string
Arg int
}
type AlfredWorkflow struct {
Items []AlfredItem
}
func (aw *AlfredWorkflow) AddItem(name, value string, opts ...func(*AlfredItem)) {
item := AlfredItem{
Title: value,
Subtitle: name,
Arg: 111,
}
for _, opt := range opts {
opt(&item)
}
aw.Items = append(aw.Items, item)
}
func main() {
aw := AlfredWorkflow{}
aw.AddItem("A", "a")
aw.AddItem("B", "b", func(ai *AlfredItem) {
ai.Arg = 22222
}, func(ai *AlfredItem) {
ai.Arg = 3333
})
fmt.Printf("%+v", aw)
}
高阶用法 封装为选项模式(Option Pattern)
采用Functional Options Patter
方法来解决
核心点:定义 type func(*AlfredItem),且每个参数定义with函数
type AlfredItem struct {
Title string
Subtitle string
Arg int
}
type AlfredWorkflow struct {
Items []AlfredItem
}
type Option func(*AlfredItem)
func WithTitle(title string) Option {
return func(ai *AlfredItem) {
ai.Title = title
}
}
func WithSubtitle(subtitle string) Option {
return func(ai *AlfredItem) {
ai.Subtitle = subtitle
}
}
func WithArg(arg int) Option {
return func(ai *AlfredItem) {
ai.Arg = arg
}
}
func (aw *AlfredWorkflow) AddItem(name, value string, opts ...Option) {
item := AlfredItem{
Title: value,
Subtitle: name,
Arg: 111,
}
for _, opt := range opts {
opt(&item)
}
aw.Items = append(aw.Items, item)
}
func main() {
aw := AlfredWorkflow{}
aw.AddItem("DefaultName", "DefaultVlaue")
aw.AddItem("DefaultName-B", "DefaultVlaue-b", WithArg(222), WithSubtitle("0000"))
aw.AddItem("C", "c", WithTitle("hahah"))
fmt.Printf("%+v", aw)
}
参考:
https://www.cnblogs.com/smartrui/p/10324320.html
作者:matrix
发布时间:2024-08-10
分类:Golang
go generate
命令可以方便的为自动生成源代码,利用官方的stringer
库来完成
安装stringer工具
如果本地已经安装,跳过
go get -u golang.org/x/tools/cmd/stringer
Case
main.GO
package main
import "fmt"
type UserStatus int
const (
Active UserStatus = 40
Inactive UserStatus = 1
Pending UserStatus = 9
Other = Inactive
)
上面定义的常量类型UserStatus
,原始类型为 int 值,每次使用 fmt.Print打印会只显示数字,可读性会很差。
那怎么让fmt.Print输出对应的描述?
自定义结构体String()
方法,打印时会自动调用
...
func (s UserStatus) String() string {
switch s {
case Active:
return "Active"
case Inactive:
return "Inactive"
case Pending:
return "Pending"
default:
return "Other"
}
}
func main(){
var a UserStatus = Active
fmt.Println(a) //Active
}
定义go:generate
上面手动编写的确可以,但如果有状态值调整后续维护会很麻烦,结合go:generate
能自动生成String()方法
定义特定开头规则的注释//go:generate
,这样go generate
可以自动识别
//go:generate go run golang.org/x/tools/cmd/stringer -type=UserStatus
type UserStatus int
说明:
go:generate
表示go generate命令标记
go run golang.org/x/tools/cmd/stringer
表示stringer的执行命令,如果本地已经全局安装了其实也可以替换为stringer
。但你得确保环境变量能够读取到它
-type
参数用于指定自定义的类型UserStatus
执行go:generate
go generate main.go
不指定main.go 文件,generate命令会查找所有包含 //go:generate
指令的文件,并执行这些指令后面的命令。这个例子就会运行 stringer -type=UserStatus
,为 UserStatus 类型生成一个新的 Go 文件userstatus_string.go
,包含 String() 方法的实现。
自动生成的userstatus_string.go
文件
// Code generated by "stringer -type=UserStatus"; DO NOT EDIT.
package main
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[Active-40]
_ = x[Inactive-1]
_ = x[Pending-9]
}
const (
_UserStatus_name_0 = "Inactive"
_UserStatus_name_1 = "Pending"
_UserStatus_name_2 = "Active"
)
func (i UserStatus) String() string {
switch {
case i == 1:
return _UserStatus_name_0
case i == 9:
return _UserStatus_name_1
case i == 40:
return _UserStatus_name_2
default:
return "UserStatus(" + strconv.FormatInt(int64(i), 10) + ")"
}
}
自动生成的代码中String()
其实都大同小异,但是他考虑到了其他值。
并且_()
匿名的函数内置逻辑用例可以起到防止枚举值被修改的问题,比如这里Active
值被调整后会导致x[Active-40]
取到非下标值导致编译失败 So Nice~
并且标注了DO NOT EDIT.
这样以后维护和构建过程更简单明了。
参考:
https://medium.com/@dadcod/6-unique-and-lesser-known-go-techniques-9821be24972b
https://www.jvt.me/posts/2022/06/15/go-tools-dependency-management/
stringer 源码:
https://cs.opensource.google/go/x/tools/+/master:cmd/stringer/stringer.go
作者:matrix
发布时间:2024-07-06
分类:Golang Linux
在构建GO的docker镜像时,都需要安装很多开发环境和依赖包,如果正常打包完整环境为镜像完全没有必要。因为运行时只需要Golang打包的二进制文件,不需要完整 dev 环境。
Dockerfile多阶段构建就可以完美解决,将构建和运行环境分开,可以最终镜像最小化。 😆 😆 爽~~
# 第一阶段:使用开发环境镜像进行构建,设置别名builder
FROM golang:1.22 AS builder
# 设置工作目录
WORKDIR /app
# 复制所有文件到工作目录
COPY . .
# 编译应用程序
RUN go build -o go-demo .
# 第二阶段:使用小体积的基础镜像 打包最终镜像
FROM alpine:latest
WORKDIR /app
# 从构建阶段复制编译好的可执行文件
COPY --from=builder /app/go-demo .
# 运行可执行文件
CMD ["./go-demo"]
这样就可以确保最终的镜像只包含运行应用所需的最小文件,镜像环境也只是基础的alpine镜像。
作者:matrix
发布时间:2023-07-07
分类:Golang
Golang中嵌入结构体类型有两种:值或指针
结论
创建 含内嵌指针struct实例时,必须手动声明嵌入的结构指针。
伪代码如下:
package main
type BaseDao struct{name string}
# 匿名结构体字段BaseDao
type OptDao1 struct{BaseDao} # 嵌入值
type OptDao2 struct{*BaseDao} # 嵌入指针
func main(){
opt := OptDao2{BaseDao:&BaseDao{}} //必须手动声明嵌入的结构指针
}
上面代码中OptDao1、OptDao2嵌入了BaseDao结构体,主要区别只有嵌入值的类型不同。值和指针区别
嵌入值
创建OptDao1对象
mOptDao1 := OptDao1{}
mOptDao1.name
代码调用会正常,属性name会获取到空字符串
其他例:
type Base struct {
value int
}
func (b *Base) Increase() {
b.value++
}
type Derived struct {
Base
}
func main() {
d := Derived{}
d.Increase()
fmt.Println(d.value) // 输出 1
}
嵌入指针
创建OptDao2对象
mOptDao2 := OptDao2{}
mOptDao2.name
上面代码调用会出现nil空指针异常,runtime error: invalid memory address or nil pointer dereference
,即nil指针解引用错误。
原因是访问一个nil对象的方法或属性,这就会panic。
怎么办?
mOptDao2 := OptDao2{BaseDao:&BaseDao{}}
mOptDao2.name
创建mOptDao2实例时必须声明嵌入的结构指针
其他例:
type Base struct {
value int
}
func (b *Base) Increase() {
b.value++
}
type Derived struct {
*Base
}
func main() {
d := Derived{Base: &Base{}}
d.Increase()
fmt.Println(d.value) // 输出 1
}
简而言之,选择值类型嵌入还是指针类型嵌入,主要取决于你是否需要多个实例共享同一个嵌入实例的状态。如果你需要共享状态,使用指针类型嵌入。如果你不需要共享状态,使用值类型嵌入。
一般情况下选择嵌入值即可,除非多个对象需要共享一个Base结构实例。
作者:matrix
发布时间:2023-06-24
分类:Golang
熟悉面向对象语言的话,Golang的struct结构体有点像面向对象编程中的类
。但这两者不是完全一样,只能说都有继承、封装、多态的特点。
结构体(struct)
结构体可以将零个或多个任意类型的值聚合在一起,能描述多个数据类型
type Person struct {
name string
age int
value string
address string
}
成员方法和接收者
func (p *Person) setName(name string) {
p.name = name
}
说明:
setName
为声明的方法
p *Person
为接收者(指针类型)
struc类型新增成员方法的语法很另类,像是单独给struct做绑定,绑定的时候会有接收者
来指定当前实例类型。
GOlang其实可以给任何类型创建成员方法:
type MyInt int
func (i MyInt) IsZero() bool{
return i == 0
}
这里通过声明int的自定义类型MyInt,然后绑定一个成员方法。灵活~
值接收者 指针接收者
方法的接收者
可以是结构体的值
或者指针
。上面例子的接收者是一个Person类型的指针。指针接收者的一个优点是可以直接修改接收者的字段值,还避免值的拷贝(内部实际上是拷贝的指针)。
声明为值接收者也是可以:
func (p Person) getName() string {
return p.name
}
getName方法会在调用时复制接收者,就可能会导致性能问题。一般是建议使用指针作为接收者
小结
值接收者或者指针接收者 都能调用结构体或者内嵌结构体的方法或者属性。
给结构体绑定成员方法时,参数最好使用指针,防止值拷贝
func (this *Person) setName(name string)
// 申明结构体struct
type Person struct {
name string
age int
value string
address string
}
//结构绑定
// 给结构体绑定成员方法。 不推荐使用,内存利用低效(参数会使用值传递,会内存拷贝)
//p Person这里p表示值接收者
// func (p Person) getName() string {
// 这里的p变量指针和外部调用的mPerson不同,这里属于值拷贝!!!
// return p.name
// }
// 给Person结构体指针绑定成员方法,同上面效果。但是参数属于引用传递
//this为指针接收者
func (this *Person) getName() string {
return this.name
}
// 给Person结构体指针绑定方法
func (this *Person) setName(name string) {
this.name = name
}
func main() {
//创建实例
// var mPerson *Person = new(Person)//返回实例指针
// var mPerson Person = *new(Person)//返回实例
// var mPerson Person = Person{}//返回实例
var mPerson *Person = &Person{name: "Hi~"} //获取实例指针
mPerson.setName("")
fmt.Println(mPerson.getName())
}
作者:matrix
发布时间:2022-10-02
分类:零零星星
SLA探活的需求很广泛,简单的可以自己实现。但是专门独立的探活工具倒是极少~
EaseProbe由GO编写,不需要其他依赖支持直接使用二进制程序运行。
这几天测试用来给api接口、ssl证书、web 200探活,好用~
![图片5579-简单好用的SLA探活工具 - EaseProbe](//static.hhtjim.com/wp-content/uploads/2022/09/img_631eef89cc036.png?sign=5be333cdd602e223b707a6236ffdbb6e&t=66dcfe98)
github仓库
https://github.com/megaease/EaseProbe
支持HTTP、TCP、SSH、ssl证书、各种数据库/消息中间件服务探活,和email、Slack、Discord、Telegram、飞书...的通知。还支持消息通知和定时发送报表,可以自定义分类告警渠道
配置config.yaml
参照官方配置,新建文件config.yaml
配置SSL证书过期检测、WEB HTTP200检测、接口HTTP状态检测:
http: # http探活
# 默认监控网页HTTP是否为200OK
- name: "HHTJIM.COM OK"
url: https://www.hhtjim.com
- name: "LINK.HHTJIM.COM OK"
url: https://www.hhtjim.com
# 监控接口是否返回指定状态码
- name: link mp3 parse
url: https://link.hhtjim.com/163/5146554.mp3
method: GET
insecure: true
success_code:
# 配置允许的状态码范围
- [200,206] # the code >=200 and <= 206
- [300,308] # the code >=300 and <= 308
timeout: 1s # default is 30 seconds
tls: # SSL证书探活
# 监控网页证书是否临近过期(24小时内触发告警)
- name: "www.hhtjim.com SSL EXPIRED"
host: www.hhtjim.com:443
insecure_skip_verify: true # dont check cert validity
expire_skip_verify: false # dont check cert expire date
alert_expire_before: 24h # alert if cert expire date is before X, the value is a Duration, see https://pkg.go.dev/time#ParseDuration. example: 1h, 1m, 1s. expire_skip_verify must be false to use this feature.
# 监控网页证书是否临近过期(7天内触发告警)
- name: "link.hhtjim.com SSL EXPIRED"
host: link.hhtjim.com:443
insecure_skip_verify: true
expire_skip_verify: false
alert_expire_before: 168h
notify: # 告警通知方式
lark:
- name: "lark alert service"
# 配置飞书通知机器人的webhook
webhook: "https://open.feishu.cn/open-apis/bot/v2/hook/00000-10b1-000000-8949-00000000"
# 全局配置
settings:
probe:
timeout: 30s # the time out for all probes
interval: 1m # probe every minute for all probes
说明:
参照上面注释可自由配置,我这里使用的是飞书通知。也可以其他方式告警~
探活配置参数:
https://github.com/megaease/easeprobe/blob/main/docs/Manual.md#1-probe
告警通知配置参数:
https://github.com/megaease/easeprobe/blob/main/docs/Manual.md#2-notification
开启监控
EaseProbe已经有docker镜像,可以直接一键启停。
# 首次启动
$ docker run -d -p 8181:8181 --name sla -v $(pwd)/config.yaml:/opt/config.yaml megaease/easeprobe
# 重启
$ docker restart sla
# 关闭
$ docker stop sla
查看状态
访问http://HOST:8181`就能看到web监控面板,且支持api接口
http://HOST:8181/api/v1/sla`
![图片5569-简单好用的SLA探活工具 - EaseProbe](//static.hhtjim.com/wp-content/uploads/2022/09/img_631ee3eb7d5fd.png?sign=514d607a31352d10cf446be566b9b14b&t=66dcfde4)
附. 飞书BOT创建
这里的告警通知使用的是群自定义机器人webhook,需要使用飞书客户端创建(web端没有找到入口)
![图片5581-简单好用的SLA探活工具 - EaseProbe](//static.hhtjim.com/wp-content/uploads/2022/09/img_631ef40018aab.png?sign=27c806335792f2c129268197429beb39&t=66dcfde4)
![图片5582-简单好用的SLA探活工具 - EaseProbe](//static.hhtjim.com/wp-content/uploads/2022/09/img_631ef6f6e3989.png?sign=b8c38e5b42b0ee917886d96051779295&t=66dcfd6c)
参考:
https://mp.weixin.qq.com/s/c73ZPBGoMbqjT-xbRiCJ3g
https://github.com/megaease/easeprobe/blob/main/docs/Manual.md
https://open.feishu.cn/document/ukTMukTMukTM/ucTM5YjL3ETO24yNxkjN?lang=zh-CN
作者:matrix
发布时间:2018-10-29
分类:零零星星
SOAR -- sql Optimizer And Rewriter 由小米运维 DBA 团队开发的sql 智能优化与改写工具20181021宣布开源。
github:https://github.com/xiaomi/soar
安装说明:https://github.com/XiaoMi/soar/blob/master/doc/install.md
测试环境:ubuntu 16.04
安装GO
apt-get安装失败改用
源码下载:https://GOlang.GOogle.cn/dl/
配置环境变量
解压:
> sudo tar -C /usr/local -xzf go1.11.1.linux-amd64.tar.gz
全局用户的环境变量:
> sudo vi /etc/profile
末尾添加:
export PATH=$PATH:/usr/local/go/bin
go版本查看:
> go version
source更新环境变量:
source /etc/profile
还需要配置GOPATH环境变量:表示go的工作目录 USER_NAME 为用户名
export GOPATH="/home/USER_NAME/go"
安装soar
> go get -d github.com/XiaoMi/soar
> cd ${GOPATH}/src/github.com/XiaoMi/soar && make
若安装顺利,最终会显示success
。否则 build error
我前几次安装都失败
go build github.com/pingcap/tidb/parser: /usr/local/go/pkg/tool/linux_amd64/compile: signal: killed
Makefile:69: recipe for target 'build' failed
之后google找到帖子 有人说是vps内存太低导致的,遂重启了下Ubuntu 重新make
。bingo~
安装成功之后会发现~/go/src/github.com/XiaoMi/soar
多出一个 soar
文件。
执行测试:
> cd ~/go/src/github.com/XiaoMi/soar
> echo 'select * from film' | ./soar
使用soar
常用命令:https://github.com/XiaoMi/soar/blob/master/doc/cheatsheet.md
打印所有的启发式规则
$ soar -list-heuristic-rules
打印支持的报告格式
$ soar -list-report-types
以指定格式输出报告
$ soar -report-type json
语法检查工具
$ echo "select * from tb" | soar -only-syntax-check
$ echo $?
0
$ echo "select * fromtb" | soar -only-syntax-check
At SQL 0 : syntax error at position 16 near 'fromtb'
$ echo $?
1
慢日志进行分析示例
$ pt-query-digest slow.log > slow.log.digest
# parse pt-query-digest's output which example script
$ python2.7 doc/example/digest_pt.py slow.log.digest > slow.md
SQL指纹
$ echo "select * from film where col='abc'" | soar -report-type=fingerprint
输出
select * from film where col=?
将UPDATE/DELETE/INSERT语法转为SELECT
$ echo "update film set title = 'abc'" | soar -rewrite-rules dml2select,delimiter -report-type rewrite
输出
select * from film;
合并多条ALTER语句
$ echo "alter table tb add column a int; alter table tb add column b int;" | soar -report-type rewrite -rewrite-rules mergealter
输出
ALTER TABLE `tb` add column a int, add column b int ;
SQL美化
$ echo "select * from tbl where col = 'val'" | ./soar -report-type=pretty
输出
SELECT
*
FROM
tbl
WHERE
col = 'val';
EXPLAIN信息分析报告
$ soar -report-type explain-digest << EOF
+----+-------------+-------+------+---------------+------+---------+------+------+-------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+------+------+-------+
| 1 | SIMPLE | film | ALL | NULL | NULL | NULL | NULL | 1131 | |
+----+-------------+-------+------+---------------+------+---------+------+------+-------+
EOF
## Explain信息
| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 0 | 0.00% | ☠️ **O(n)** | |
### Explain信息解读
#### SelectType信息解读
* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).
#### Type信息解读
* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.
markdown转HTML
通过指定-report-css, -report-javascript, -markdown-extensions, -markdown-html-flags这些参数,你还可以控制HTML的显示格式。
$ cat test.md | soar -report-type md2html > test.html
PEACE~
参考:
https://github.com/beego/wetalk/issues/32
https://www.oschina.net/news/101034/xiaomi-opensource-soar
https://juejin.im/entry/5bbf21fde51d450e61605d99