前记

最近在做监控 Spring Boot /actuator/health 的时候,总是会出现一些莫名其妙的网络超时中断,于是想到了用重试机制来进行重试请求。

下面看看 Python 的第三方库 Tenacity

安装

1
pip install Tenacity

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import requests
from tenacity import retry, stop_after_attempt, wait_fixed

# reraise 表示是否抛出原异常,默认为 False
# stop_after_attempt 表示最大尝试次数
# wait_fixed 表示等待时间
@retry(reraise=True, stop=stop_after_attempt(2), wait=wait_fixed(2))
def main():
print("Start Try " + str(main.retry.statistics["attempt_number"]))
resp = requests.get("https://httpstat.us/200?sleep=5000", timeout=3)
print(resp.status_code)

try:
main()
except Exception as e:
print(str(e))

输出:

1
2
3
Start Try 1
Start Try 2
HTTPSConnectionPool(host='httpstat.us', port=443): Read timed out. (read timeout=3)

上面示例中,设定超时为 3s 请求却 sleep=5000 此请求必然会超时

tenacity 只要遇见 raise 就会触发重试,上面代码中 requests 底层已经 raise,所以即使 main 函数中没有 raise 依然会重试

参考文档

Celery

Celery 是一个基于 Python 开发的分布式异步消息任务队列,通过它可以轻松的实现任务的异步处理

看看如何 CelerySanic 一起使用

使用

使用的组件

1
2
3
4
sanic==21.12.1
celery==5.2.3
redis==4.1.4
flower==1.0.0

tasks/task.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import time

from celery import Celery

REDIS_URI = "127.0.0.1"
REDIS_PORT = 6379
REDIS_DB = 0

app = Celery(
"tasks",
broker=f"redis://{REDIS_URI}:{REDIS_PORT}/{REDIS_DB}",
backend=f"redis://{REDIS_URI}:{REDIS_PORT}/{REDIS_DB}",
)


@app.task
def run_task(name):
time.sleep(5)
return "Hello, {}!".format(name)

server.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from sanic import Sanic
from sanic.response import text

from tasks.task import run_task

app = Sanic("MyHelloWorldApp")


@app.post("/start_task")
async def start_task(request):
result = run_task.delay("jakehu")
print(result)
return text("Task started.")


if __name__ == "__main__":
app.run(host="0.0.0.0", debug=True, auto_reload=True)

worker.py

1
from tasks.task import app

运行 worker 进程

1
celery -A worker:app worker --loglevel=info --concurrency=1

Flower

Flower 是基于 web 的监控和管理 Celery 的工具

使用

1
celery --broker=redis://127.0.0.1:6379/0 flower --port=5566

Celery 参考文档
Flower 参考文档

前记

当使用 Sqlalchemy 查询数据通过 ujsonjson 工具 dumps 时会报以下错误

1
TypeError: datetime.datetime(2022, 2, 20, 14, 17, 23) is not JSON serializable

解决

可以通过在 SQL 查询的时候先行进行格式化

1
2
3
4
5
6
7
8
select(
order.c.id,
func.date_format(order.c.time, "%Y-%m-%d %H:%i:%s"),
)
.select_from(order)
.where(
order.c.uid == 1,
)

路线

升级前明确升级路线
参考

备份

利用 gitlab-backup 命令进行备份

1
docker exec -t <container name> gitlab-backup create

复制备份至宿主机

1
2
cd ~/gitlab/backups
docker cp <container name>:/var/opt/gitlab/backups/xxx_gitlab_backup.tar .

升级

停止现有容器

1
docker stop <container name>

删除现有容器

1
docker rm <container name>

拉取新的容器镜像

1
docker pull gitlab/gitlab-ce:13.12.15-ce.0

启动新的容器

1
docker-compose -f gitlab.yml up -d

docker-compose 配置如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
web:
image: 'gitlab/gitlab-ce:13.12.15-ce.0'
restart: always
hostname: 'git.xxx.com'
environment:
GITLAB_OMNIBUS_CONFIG: |
gitlab_rails['gitlab_shell_ssh_port'] = 2224
ports:
- '8081:80'
- '2224:22'
volumes:
- '/opt/gitlab/config:/etc/gitlab'
- '/opt/gitlab/logs:/var/log/gitlab'
- '/opt/gitlab/data:/var/opt/gitlab'

安装

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 结束消费者进程

前记

利用 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 相关参考