晨曦's Blog

This is a window to the soul

安装

1
2
3
wget --no-check-certificate https://dlcdn.apache.org/kafka/3.0.0/kafka_2.13-3.0.0.tgz  
tar -xzf kafka_2.13-3.0.0.tgz
cd kafka_2.13-3.0.0

配置

配置参数前假设我们的节点为:10.200.128.106

配置文件路径:config/kraft/server.properties

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 节点角色
process.roles=broker,controller

# 节点的ID,唯一
node.id=1

# 连接的集群地址字符串,每个服务器一样,单机就是本机
controller.quorum.voters=1@10.200.128.106:9093

# IP:Port,每个服务器不一样
listeners=PLAINTEXT://10.200.128.106:9092,CONTROLLER://10.200.128.106:9093
inter.broker.listener.name=PLAINTEXT

# IP:Port,每个服务器不一样
advertised.listeners=PLAINTEXT://10.200.128.106:9092

# 日志目录
log.dirs=/tmp/kraft-combined-logs

还有很多参数就不一一列举了

生成集群 ID

1
2
# bin/kafka-storage.sh random-uuid
VV7Jb8bWT-Oz_F99BNXvAQ

格式化日志目录

如果是集群应在每台机器上运行格式化命令,并且应该使用同一集群 ID

1
2
# bin/kafka-storage.sh format -t VV7Jb8bWT-Oz_F99BNXvAQ -c ./config/kraft/server.properties
Formatting /tmp/kraft-combined-logs

启动

1
bin/kafka-server-start.sh ./config/kraft/server.properties

也可以加上 -daemon 实现后台守护

停止

1
bin/kafka-server-stop.sh stop

创建 Topics

1
2
# bin/kafka-topics.sh --create --partitions 1 --replication-factor 1 --topic topic-name --bootstrap-server 10.200.128.106:9092
Created topic topic-name.

生产者

1
2
3
4
bin/kafka-console-producer.sh --topic topic-name --bootstrap-server 10.200.128.106:9092
>test1
>test2
>test3

可以按 Ctrl+C 结束生产者进程

消费者

1
2
3
4
# bin/kafka-console-consumer.sh --topic topic-name --from-beginning --bootstrap-server 10.200.128.106:9092
test1
test2
test3

可以按 Ctrl+C 结束消费者进程

错误

1
2
3
4
5
[Composer\Downloader\TransportException]
The "https://packagist.phpcomposer.com/packages.json" file could not be downloaded: SSL operation failed with code 1. OpenSSL Error messages:
error:14090086:SSL routines:ssl3_get_server_certificate:certificate verify failed
Failed to enable crypto
failed to open stream: operation failed

解决

composer.lock 文件删除,重新执行 composer install

前记

利用 Puppet 做远程脚本执行的时候,发现 Python 脚本无法获取到环境变量

解决

在执行脚本前去获取到环境变量,这样就可以获取到环境变量了,或者重写 os.environ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def get_env():
try:
source_profile_cmd = "source /etc/profile && env"
status, out = commands.getstatusoutput(source_profile_cmd)
envs = {}
for info in out.split("\n"):
env = info.split("=")
if len(env) == 2:
envs[env[0]] = env[1]
# os.environ[env[0]] = env[1]
return envs
except:
return {}

get_env()

很少在 Mac 上调试 Shell,今天在 Mac 调试 Shell 的时候发现 sed 的用法还有点不一样,报错:

1
sed: 1: "02.data.sql": invalid command code .

解决

-i 后面增加'' 变成 -i '' 即可

Mac

1
2
3
4
5
6
7
8
9
10
11
12
#!/bin/bash

public_domain="auth.xxx.com"
private_domain="authconsole.xxx.com"

if [ "$(sed -i '' s/ztsg.xxx.com/${public_domain}/ 02.data.sql)" ]; then
echo "public_domain update success"
fi

if [ "$(sed -i '' s/ztsgconsole.xxx.com/${private_domain}/ 02.data.sql)" ]; then
echo "private_domain update success"
fi

Linux

1
2
3
4
5
6
7
8
9
10
11
12
#!/bin/bash

public_domain="auth.xxx.com"
private_domain="authconsole.xxx.com"

if [ "$(sed -i s/ztsg.xxx.com/${public_domain}/ 02.data.sql)" ]; then
echo "public_domain update success"
fi

if [ "$(sed -i s/ztsgconsole.xxx.com/${private_domain}/ 02.data.sql)" ]; then
echo "private_domain update success"
fi

MacLinux 下分别用以上两种写法就可以了

说到写 Shell 第一反应肯定都是 Vim,接下来我们看看如何通过 VsCode 的插件把 VsCode 打造成一个 Shell IDE

shellman

说到 IDE 最起码的需要自动提示和补全,这就需要 shellman 效果如下:

ShellCheck

有了提示和自动补全,还需要校验我们写得代码是否优雅,这就需要 ShellCheck 语法检验,效果如下:

而且 shellcheckshellman 是兼容的
shellcheck compatible (99.99%).

shell-format

最后就是格式化插件 shell-format,写完代码风格还是需要格式化一下的

在用 PythonNodejs 做项目的时候我喜欢把项目的路由分文件存放在 routers 文件夹下,然后在启动文件夹中加载,这样即简洁也好维护,试试如何在 Gin 中实现

main.go

1
2
3
r := gin.Default()
routers.Router(r) // 路由
r.Run(":8888")

3 行代码,Router 方法用来加载所有的路由,接下来看 Router 方法具体实现

routers/router.go

1
2
3
4
5
6
7
8
9
func Router(e *gin.Engine) {
// V1版本
v1 := e.Group("/v1")
{
RouterUser(v1)
// 其他模块路由
}
// V2版本
}

Router 方法主要是去把当前下的其他模块的路由加载进来,这里使用了路由组,如果不需要路由组的话去掉即可,接来下看看 RouterUser 方法具体实现

routers/user.go

1
2
3
4
func RouterUser(e *gin.RouterGroup) {
e.GET("/user/list", controllers.UserList)
e.POST("/user/add", controllers.UserAdd)
}

使用路由组接收的参数是 RouterGroup 不使用路由组接收的参数是 Engine,这里需要注意一下

目录

最后规划下来的目录如下:

1
2
3
4
5
6
7
8
9
10
11
.
├── README.md
├── app
│   └── controllers
│   └── user.go
├── go.mod
├── go.sum
├── main.go
└── routers
   ├── router.go
   └── user.go

这样规划的话,就只需要维护 router.go 和其他的对应模块自己的路由规则,main.go 也就会变得非常的简洁

一个非常简单的需求,需要在 ES 中去匹配 IPV62409:8a10:* 段的数据,看看如何查询

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
GET logstash-nginx-*/doc/_search
{
"_source": "remote_ip",
"query": {
"bool": {
"must": [
{
"query_string": {
"fields": [
"remote_ip.keyword"
],
"query": """2409\:8a10\:*""",
"analyzer": "keyword",
"analyze_wildcard": true
}
}
],
"filter": {
"range": {
"@timestamp": {
"gte": "2021-12-14T09:54:00.000000+0800",
"lt": "2021-12-16T09:55:00.000000+0800"
}
}
}
}
}
}

这里的重点是 """2409\:8a10\:*""" 也可以写成 "2409\\:8a10\\:*",其他的符号也可按此类推即可

最后效果:


ES 相关参考

Python 中需要热加载是非常简单的,以 Sanic 为例只需要设置 auto_reload=True,但是在 Gin 中没有内置相应的功能,这里我们可以利用 Go 中的一些其他包实现,即更改源码,保存后,自动触发更新,浏览器上刷新即可。免去了杀进程、重新启动之苦

Fresh

试了下 github.com/gravityblast/fresh 还觉得不错
安装:

1
go get github.com/pilu/fresh

使用:

1
fresh

其他

记录一下其他实现框架
Air:https://github.com/cosmtrek/air
Bee:https://github.com/beego/bee
Realize:https://github.com/oxequa/realize
Gin:https://github.com/codegangsta/gin
gowatch:https://github.com/silenceper/gowatch


题外话:
在安装非项目使用的包时如:fresh,最好是不要在项目根目录下进行 go get 或者会被写入到项目的 go.mod

前记

最近需要实现通过 Supervisor 来守护 Haproxy,折腾了半天记录下实现配置

实现

1
2
3
4
5
6
[program:haproxy]
command=/usr/sbin/haproxy -f /etc/haproxy/haproxy.cfg -p /run/haproxy.pid -Ds
process_name=haproxy
autostart=true
autorestart=true
startretries=1

这里的重点是 -p /run/haproxy.pid 必须要指定 pid 的路径,要不然没法获取到 program 状态

类型?

type 定义的结构体接口都是类型

导出名

Go 中,如果一个名字以大写字母开头,那么它就是已导出的,在导入一个包时,你只能引用其中已导出的名字。任何未导出的名字在该包外均无法访问
类似其他语言 PublicPrivate 修饰符

命名返回值

Go 的返回值可被命名,它们会被视作定义在函数顶部的变量。返回值的名称应当具有一定的意义,它可以作为文档使用。没有参数的 return 语句返回已命名的返回值。也就是直接返回

短变量声明

函数中,简洁赋值语句:= 可在类型明确的地方代替 var 声明
函数外的每个语句都必须以关键字开始(var, func 等等),因此 := 结构不能在函数外使用

零值

没有明确初始值的变量声明会被赋予它们的零值0false""(空字符串)

常量

使用 const 关键字声明,常量不能用:= 语法声明

defer

defer 语句会将函数推迟到外层函数返回之后执行,推迟调用的函数其参数会立即求值,但直到外层函数返回前该函数都不会被调用
推迟的函数调用会被压入一个栈中。当外层函数返回时,被推迟的函数会按照后进先出的顺序调用可以看做是倒序执行

指针

指针保存了值的内存地址

切片

它会选择一个半开区间,包括第一个元素,但排除最后一个元素
切片并不存储任何数据,它只是描述了底层数组中的一段。更改切片的元素会修改其底层数组中对应的元素。与它共享底层数组的切片都会观测到这些修改

匿名函数与闭包

有了匿名函数,就可以在函数中再定义函数(函数嵌套),定义的这个匿名函数,也可以称为内部函数。更重要的是,在函数内定义的内部函数,可以使用外部函数的变量等,这种方式也称为闭包

方法与函数

方法就是一类带特殊的接收者参数的函数
接收者的类型定义和方法声明必须在同一包内;不能为内建类型声明方法

选择值或指针作为接收者

使用指针接收者的原因有二:
首先,方法能够修改其接收者指向的值。
其次,这样可以避免在每次调用方法时复制该值。若值的类型为大型结构体时,这样做会更加高效
通常来说,所有给定类型的方法都应该有值或指针接收者,但并不应该二者混用

接口

接口类型是由一组方法签名定义的集合
类型通过实现一个接口的所有方法来实现该接口。既然无需专门显式声明,也就没有 “implements” 关键字
隐式接口从接口的实现中解耦了定义,这样接口的实现可以出现在任何包中,无需提前准备
因此,也就无需在每一个实现上增加新的接口名称,这样同时也鼓励了明确的接口定义


未完待续…