君不密则失臣,臣不密则失身,几事不密则害成。是以君子慎密而不出也。

密

Hexo保留链接隐藏文章

Hexo生成的一些文章,默认是全部被主页和标签索引的,那么有些文章不希望被索引,而且有希望通过链接直接分享给别人看,就可以利用hexo-hide-posts插件。

  1. 先安装hexo-hide-posts
    1
    npm install hexo-hide-posts --save
  2. 在想要隐藏的文章里添加hidden标记。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    ---
    layout: post
    title: "Hexo保留链接隐藏文章"
    date: 2024-01-02 13:04:38 +0800
    hidden: true
    tags:
    - Hexo
    categories: Tech
    ---

只需简单这两部就可以了,虽然可以在_config.yml做一些更详细的配置,但是没必要。
重新生成文章,就可以看到隐藏的文章不在主页和标签页里了。但是可以通过链接访问,链接可以在生成文章的日志里找到。

在centos上安装immich

一直在研究怎么把自己的上万张照片找一个合适的地方放置,目前还没有定论,但是比较倾向于immich.app.

记录在centos上的安装过程。

安装docker-ce

  1. enable 阿里云里的docker-ce的repo

    1
    2
    yum install -y yum-utils
    yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
  2. yum安装docker-ce

    1
    yum install -y docker-ce docker-ce-cli --nobest --skip-broken
  3. 启动docker-ce

    1
    2
    3
    systemctl enable docker
    systemctl start docker
    systemctl status docker # 检查是不是启动成功

通过docker compose安装immich.app

  1. 创建目录
    如果挂载了多个磁盘,建议选择一个合适的目录,immich启动后会挂载全部的所在目录。

    1
    2
    mkdir ./immich-app
    cd ./immich-app
  2. 准备 docker-compose.yml 和 example.env
    下载文件:

    1
    2
    wget https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml
    wget -O .env https://github.com/immich-app/immich/releases/latest/download/example.env

    下载后docker-compose.yml需要改动一下镜像的地址,换成国内的源。
    github的镜像源ghcr.io可以换成南京大学的ghcr.nju.edu.cn,默认dockerhub的源换成docker.nju.edu.cn,国内访问速度比较快。

    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
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    version: "3.8"

    services:
    immich-server:
    container_name: immich_server
    image: ghcr.nju.edu.cn/immich-app/immich-server:${IMMICH_VERSION:-release}
    command: [ "start.sh", "immich" ]
    volumes:
    - ${UPLOAD_LOCATION}:/usr/src/app/upload
    env_file:
    - .env
    depends_on:
    - redis
    - database
    - typesense
    restart: always

    immich-microservices:
    container_name: immich_microservices
    image: ghcr.nju.edu.cn/immich-app/immich-server:${IMMICH_VERSION:-release}
    # extends:
    # file: hwaccel.yml
    # service: hwaccel
    command: [ "start.sh", "microservices" ]
    volumes:
    - ${UPLOAD_LOCATION}:/usr/src/app/upload
    env_file:
    - .env
    depends_on:
    - redis
    - database
    - typesense
    restart: always

    immich-machine-learning:
    container_name: immich_machine_learning
    image: ghcr.nju.edu.cn/immich-app/immich-machine-learning:${IMMICH_VERSION:-release}
    volumes:
    - model-cache:/cache
    env_file:
    - .env
    restart: always

    immich-web:
    container_name: immich_web
    image: ghcr.nju.edu.cn/immich-app/immich-web:${IMMICH_VERSION:-release}
    env_file:
    - .env
    restart: always

    typesense:
    container_name: immich_typesense
    image: docker.nju.edu.cn/typesense/typesense:0.24.1@sha256:9bcff2b829f12074426ca044b56160ca9d777a0c488303469143dd9f8259d4dd
    environment:
    - TYPESENSE_API_KEY=${TYPESENSE_API_KEY}
    - TYPESENSE_DATA_DIR=/data
    # remove this to get debug messages
    - GLOG_minloglevel=1
    volumes:
    - tsdata:/data
    restart: always

    redis:
    container_name: immich_redis
    image: docker.nju.edu.cn/redis:6.2-alpine@sha256:70a7a5b641117670beae0d80658430853896b5ef269ccf00d1827427e3263fa3
    restart: always

    database:
    container_name: immich_postgres
    image: docker.nju.edu.cn/postgres:14-alpine@sha256:28407a9961e76f2d285dc6991e8e48893503cc3836a4755bbc2d40bcc272a441
    env_file:
    - .env
    environment:
    POSTGRES_PASSWORD: ${DB_PASSWORD}
    POSTGRES_USER: ${DB_USERNAME}
    POSTGRES_DB: ${DB_DATABASE_NAME}
    volumes:
    - pgdata:/var/lib/postgresql/data
    restart: always

    immich-proxy:
    container_name: immich_proxy
    image: ghcr.nju.edu.cn/immich-app/immich-proxy:${IMMICH_VERSION:-release}
    environment:
    # Make sure these values get passed through from the env file
    - IMMICH_SERVER_URL
    - IMMICH_WEB_URL
    ports:
    - 5003:8080
    depends_on:
    - immich-server
    - immich-web
    restart: always

    volumes:
    pgdata:
    model-cache:
    tsdata:

.env不需要改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# You can find documentation for all the supported env variables at https://immich.app/docs/install/environment-variables

# The location where your uploaded files are stored
UPLOAD_LOCATION=./library

# The Immich version to use. You can pin this to a specific version like "v1.71.0"
IMMICH_VERSION=release

# Connection secrets for postgres and typesense. You should change these to random passwords
TYPESENSE_API_KEY=some-random-text
DB_PASSWORD=postgres

# The values below this line do not need to be changed
###################################################################################
DB_HOSTNAME=immich_postgres
DB_USERNAME=postgres
DB_DATABASE_NAME=immich

REDIS_HOSTNAME=immich_redis

  1. 确认docker-compose.yml文件中immich-proxy段落里的ports端口没有问题,启动immich。

    1
    docker compose up -d
  2. 访问 http://{ip}:{port}

根据照片的拍摄时间重新命名照片文件名

这几天一直想给照片搞个备份,在寻找一个性价比比较高而且又比较稳定的方案,现在还没找到,先进行图片整理工作。
整理过程中想把所有的文件名给改一下,于是总结了这个脚本。
需要提前从这里https://exiftool.org/ 下载一个exiftool.exe用于操作图片或者视频的exif信息。

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

$currentDirectory = Get-Location
$imageFiles = Get-ChildItem -Path $currentDirectory -File | Where-Object { $_.Extension -match '\.(jpg|jpeg|png|gif)' }

foreach ($imageFile in $imageFiles) {
$dateTaken = & "D:\exiftool.exe" "-DateTimeOriginal" "-d" "%Y%m%d_%H%M%S" "-s" $imageFile.FullName
$dateTaken = $dateTaken.Trim() -replace ".*: ", ""
if ($dateTaken -ne "") {
$newFileName = "IMG_$dateTaken$($imageFile.Extension)"
$newFilePath = Join-Path -Path $currentDirectory -ChildPath $newFileName
while (Test-Path $newFilePath) {
# 将日期时间字符串解析为日期时间对象
$year = [int]($dateTaken.Substring(0, 4))
$month = [int]($dateTaken.Substring(4, 2))
$day = [int]($dateTaken.Substring(6, 2))
$hour = [int]($dateTaken.Substring(9, 2))
$minute = [int]($dateTaken.Substring(11, 2))
$second = [int]($dateTaken.Substring(13, 2))
$newDate = Get-Date -Year $year -Month $month -Day $day -Hour $hour -Minute $minute -Second $second

# 递增一秒钟
$newDate = $newDate.AddSeconds(1)
$dateTaken = $newDate.ToString("yyyyMMdd_HHmmss")

$newFileName = "IMG_$dateTaken$($imageFile.Extension)"
$newFilePath = Join-Path -Path $currentDirectory -ChildPath $newFileName
}
Rename-Item -Path $imageFile.FullName -NewName $newFilePath -Force
}
Write-Host $imageFile
}

$videoFiles = Get-ChildItem -Path $currentDirectory -File | Where-Object { $_.Extension -match '\.(mp4|mov|avi|mkv)' }

foreach ($videoFile in $videoFiles) {
$mediaCreateTime = & "D:\exiftool.exe" "-MediaCreateDate" "-d" "%Y%m%d_%H%M%S" "-s" $videoFile.FullName
$mediaCreateTime = $mediaCreateTime.Trim() -replace ".*: ", ""

if ($mediaCreateTime -ne "") {
$newFileName = "VID_$mediaCreateTime$($videoFile.Extension)"
$newFilePath = Join-Path -Path $currentDirectory -ChildPath $newFileName

if ($videoFile.Name -ne $newFileName) {
Rename-Item -Path $videoFile.FullName -NewName $newFilePath -Force
}
}
Write-Host $videoFile
}

Write-Host "重命名完成。"

百度网盘读取全部文件列表

代码来源于网上,很遗憾找不到从哪里找到的了。

安装百度云盘的PC版本,BaiduYunCacheFileV0.db这个文件一般放置在百度网盘的安装目录下的user目录下。
这个目录类似于单文件数据SQLite,直接调用sqlite3的python库把数据读取出来,然后转成txt文件就可以了。

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#!/usr/bin/env python3
# -*- coding:utf-8 -*-

import sqlite3
from tkinter import *
from tkinter.filedialog import askopenfilename
from tkinter.filedialog import asksaveasfilename
from tkinter.ttk import *


def select_db_file():
db_file = askopenfilename(title="请选择BaiduYunCacheFileV0.db文件", filetypes=[('db', '*.db')])
db.set(db_file)


def select_save_file():
save_file = asksaveasfilename(filetypes=[('文件', '*.txt')])
f.set(save_file + ".txt")


def write_file(file_dict, f, item, gap=""):
if item == "/":
f.write("━" + "/" + "\n")
for i in file_dict["/"]:
f.write("┣" + "━" + i + "\n")
i = item + i + "/"
if i in file_dict:
write_file(file_dict, f, i, gap="┣━")
else:
gap = "┃ " + gap
for i in file_dict[item]:
f.write(gap + i + "\n")
i = item + i + "/"
if i in file_dict:
write_file(file_dict, f, i, gap)


def create_baiduyun_filelist():
file_dict = {}
conn = sqlite3.connect(db.get())
cursor = conn.cursor()
cursor.execute("select * from cache_file")
while True:
value = cursor.fetchone()
if not value:
break
path = value[2]
name = value[3]
size = value[4]
isdir = value[6]
if path not in file_dict:
file_dict[path] = []
file_dict[path].append(name)
else:
file_dict[path].append(name)
with open(f.get(), "w", encoding='utf-8') as fp:
write_file(file_dict, fp, "/")


root = Tk()
root.title('百度云文件列表生成工具')
db_select = Button(root, text=' 选择DB文件 ', command=select_db_file)
db_select.grid(row=1, column=1, sticky=W, padx=(2, 0), pady=(2, 0))
db = StringVar()
db_path = Entry(root, width=80, textvariable=db)
db_path['state'] = 'readonly'
db_path.grid(row=1, column=2, padx=3, pady=3, sticky=W + E)
save_path = Button(root, text='选择保存地址', command=select_save_file)
save_path.grid(row=2, column=1, sticky=W, padx=(2, 0), pady=(2, 0))
f = StringVar()
file_path = Entry(root, width=80, textvariable=f)
file_path['state'] = 'readonly'
file_path.grid(row=2, column=2, padx=3, pady=3, sticky=W + E)
create_btn = Button(root, text='生成文件列表', command=create_baiduyun_filelist)
create_btn.grid(row=3, column=1, columnspan=2, pady=(0, 2))
root.columnconfigure(2, weight=1)
root.mainloop()

flask全局的@app.errorhandler(Exception)无法起效

正常情况下,在能查到的所有文档都会这样表达如何给falsk设置全局的Excepition拦截器。

1
2
3
@app.errorhandler(Exception)
def handle_exception(exc):
pass

但是无论如何,在我的项目中就是无法使用,后来看了一下报出的异常,是从flask_restx打出来的,总是返回internal server error。
于是怀疑是使用了flask_restx或者flask_restful的原因,是这个架构提前抓住了异常没有往上抛出,导致flask根本无法接到异常。
看了一flask_restx的源码,尤其是flask_restx.api.Api.handle_error方法,最后发现在一种判断条件是,flask_restx就会把异常给抛出去了。
于是发现,只需要加个配置就可以了。

1
app.config['PROPAGATE_EXCEPTIONS'] = True

这样问题就解决了。

在多个文件中搜索汉字

之前写代码的过程,用中文加了很多的注释,现在需要找出来,改掉。
可以利用正则表达式来进行搜索, 匹配中文汉字的编码就可以了。

1
^((?!(\*|//)).)+[\u4e00-\u9fa5]

将某市医师服务协会网站上的定考模拟测评试题导出PDF

给朋友帮忙,想将某市医师服务协会网站上的定考模拟测评试题导出成为PDF,方便在手机上进行学习。

经过操作分析后,网页是每一道试题都是一个json数据,需要想办法直接获取所有的数据,代码没有混淆和加密,可以直接尝试调用已经存在的方法。
在console口直接执行下面的代码,在bbresult中就可以获得所有试题的数据。

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
var bbresult = [];
for (var i=0;i<ordersortArray.length;i++)
{
var nextItemID = itemidArray[i];
var nextItemType = itemtypeArray[i];
var nextOrderSort = ordersortArray[i];
$.ajax({
url: '/Service/Web/GetItemSingleByPaperCodeAndItemCode',
type: 'POST', //GET
async: false, //或false,是否异步
data: {
'paperCode': '1758ae61-7dc2-43b5-a726-2c81cc9fec9b',
'itemCode': nextItemID,
'itemType': nextItemType,
},
dataType: 'json', //返回的数据格式:json/xml/html/script/jsonp/text
beforeSend: function (xhr) {
},
success: function (data) {
bbresult.push(data);
console.log("题号:"+nextOrderSort+", 长度:"+bbresult.length)

}
});
}

取到的结果示例:

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
[
{
"DataCount": 0,
"total": 0,
"Status": 1,
"Msg": "试题加载成功!",
"DataList": {
"PaperID": "1758ae61-7dc2-43b5-a726-2c81cc9fec9b",
"ItemID": "00abcc70-5f06-4b6d-a821-dd93af2d97c1",
"Caption": "<p>患者女,68岁,风湿性心脏病史,牙龈出血1年。检查见牙石,牙龈红肿,探诊出血。对其进行治疗时应注意以下内容,除外:()</p>",
"ItemType": "A2型",
"Remark": "心脑血管疾病患者的牙周治疗时病史询问和收集要尽量全,对风湿性心脏病、先天性心脏病和有人工心脏瓣膜者预防性使用抗生素以防感染性心内膜炎,在接受牙周检查或治疗当天应服用抗生素;可进行牙周手术治疗,但进行牙周手术的患者,抗生素的应用应延长至拆线后。还可在治疗前用过氧化氢或氯己定含漱液含漱,以减少口腔内的细菌。美国心脏病协会强调“感染性心内膜炎的易感者应特别注意口腔卫生,以减少细菌入血”。",
"OrderSort": 0,
"AnswerSortNum": 3,
"StrAnswerSortNum": null,
"OptionList": [
{
"ItemID": "00abcc70-5f06-4b6d-a821-dd93af2d97c1",
"OptionID": "fa278e33-cf73-4da6-ac5e-01021ff964a0",
"OptionContent": "接受治疗当天服用抗生素",
"OrderSort": 1,
"IsAnswer": false
},
{
"ItemID": "00abcc70-5f06-4b6d-a821-dd93af2d97c1",
"OptionID": "ccc63faa-ac42-4542-a781-18ec03e40683",
"OptionContent": "治疗前可用氯己定含漱液含漱",
"OrderSort": 2,
"IsAnswer": false
},
{
"ItemID": "00abcc70-5f06-4b6d-a821-dd93af2d97c1",
"OptionID": "d687911e-37ff-46be-9687-9326666eb2b3",
"OptionContent": "避免进行手术治疗",
"OrderSort": 3,
"IsAnswer": true
},
{
"ItemID": "00abcc70-5f06-4b6d-a821-dd93af2d97c1",
"OptionID": "6c5270c1-e6d8-4b94-ae34-7ad9a6b3cf22",
"OptionContent": "口腔卫生宣教",
"OrderSort": 4,
"IsAnswer": false
},
{
"ItemID": "00abcc70-5f06-4b6d-a821-dd93af2d97c1",
"OptionID": "c1ce0f1d-0fe4-47fa-af33-f4ce172476b3",
"OptionContent": "详细询问病史",
"OrderSort": 5,
"IsAnswer": false
}
],
"A3A4B1ParentItem": null,
"SelectedOptionID": "d687911e-37ff-46be-9687-9326666eb2b3"
},
"rows": null
},
{
"DataCount": 0,
"total": 0,
"Status": 1,
"Msg": "试题加载成功!",
"DataList": {
"PaperID": "1758ae61-7dc2-43b5-a726-2c81cc9fec9b",
"ItemID": "01a4678f-f17e-47fd-9781-8f4750a80aab",
"Caption": "<p>关于生命价值论的理解错误的是</p>",
"ItemType": "A1型",
"Remark": "根据一定的社会标准来衡量和评价的人的个体生命的自然素质的质量状态。生命质量与生命价值既有联系又有区别。生命质量是决定生命价值的内在要素,是生命价值的基础。生命质量是对人的生命的自然素质的社会性衡量和评价,即它所衡量的是生命存在的生理功能状态,它用以衡量和评价的标准是生命存在的生理功能状态能够去过一种愉快、健康和有意义的生活。",
"OrderSort": 0,
"AnswerSortNum": 2,
"StrAnswerSortNum": null,
"OptionList": [
{
"ItemID": "01a4678f-f17e-47fd-9781-8f4750a80aab",
"OptionID": "dc835eae-18c6-4770-9670-b28e103040b8",
"OptionContent": "生命价值论是指根据生命对自身和他人、社会的效用如何,而采取不同对待的生命伦理观",
"OrderSort": 1,
"IsAnswer": false
},
{
"ItemID": "01a4678f-f17e-47fd-9781-8f4750a80aab",
"OptionID": "cdf00a87-53a8-42b9-afe7-9efbe3383c4c",
"OptionContent": "根据生命价值主体的不同,生命价值分为正生命价值、负生命价值和零生命价值",
"OrderSort": 2,
"IsAnswer": true
},
{
"ItemID": "01a4678f-f17e-47fd-9781-8f4750a80aab",
"OptionID": "28a0d551-6afc-41d4-aaed-7fce3f73546e",
"OptionContent": "根据生命价值是否已经体现出来,生命价值分为现实的生命价值和潜在的生命价值",
"OrderSort": 3,
"IsAnswer": false
},
{
"ItemID": "01a4678f-f17e-47fd-9781-8f4750a80aab",
"OptionID": "c1c99cb5-a8ad-4958-8620-99153acf7f00",
"OptionContent": "生命价值论为全面认识人的生命存在意义提供了科学依据",
"OrderSort": 4,
"IsAnswer": false
},
{
"ItemID": "01a4678f-f17e-47fd-9781-8f4750a80aab",
"OptionID": "88b59efd-912f-4b83-bbc0-5374ca0f875c",
"OptionContent": "生命价值论为医护人员对于那些濒于死亡的极度痛苦病人做出医疗决策提供伦理依据",
"OrderSort": 5,
"IsAnswer": false
}
],
"A3A4B1ParentItem": null,
"SelectedOptionID": "cdf00a87-53a8-42b9-afe7-9efbe3383c4c"
},
"rows": null
}
]

将得到的500条数据直接存在一个json文件中,命名为data.json。
利用下面的代码,就可以将这个json文件里的数据,成功的转换成带有格式的PDF文件。

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
import json
import re

from reportlab.lib.pagesizes import A4
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer


def remove_html_tags(text):
"""过滤字符串中的HTML标签"""
clean = re.compile('<.*?>')
return re.sub(clean, '', text)


if __name__ == '__main__':

with open('data.json', 'r', encoding='utf-8') as file:
data = json.load(file)

pdfmetrics.registerFont(TTFont('MicrosoftYaHei', 'msyh.ttc'))

doc = SimpleDocTemplate("paper.pdf", pagesize=A4)

# 定义样式
styles = getSampleStyleSheet()
question_style = styles["Normal"]
question_style.fontName = 'MicrosoftYaHei'
question_style.leading = 18

option_style = ParagraphStyle("OptionStyle", parent=styles["Normal"], leftIndent=15, bulletIndent=20)

elements = []

for index, value in enumerate(data):

d = value['DataList']
ccc = str(index + 1) + ". " + remove_html_tags(d['Caption'])
question_style.fontSize = 14
caption = Paragraph(ccc, question_style)
elements.append(caption)

for o in d['OptionList']:
question_style.fontSize = 12

item = str(chr(ord('A') + int(o["OrderSort"]) - 1)) + ": " + remove_html_tags(o['OptionContent'])

option_paragraph = Paragraph(item, option_style)
elements.append(Spacer(18, 8))
elements.append(option_paragraph)
AnswerSortNum_paragraph = Paragraph("答案:" + str(chr(ord('A') + int(d["AnswerSortNum"]) - 1)), option_style)
elements.append(AnswerSortNum_paragraph)
Remark_paragraph = Paragraph("解析:" + remove_html_tags(str(d["Remark"])), option_style)
elements.append(Remark_paragraph)

elements.append(Spacer(1, 20))

doc.build(elements)

PDF效果非常不错,放在手机上查看清晰可读,收获范主任稀有的赞一次!

声明:代码不会对任何系统产生危害,所有的操作都是在正规注册付费账号里合规合法完成,未传播任何带有版权数据。如有侵权,请联系删除。

在Python Flask程序中发起request请求时卡死的问题

当前的Python Flask程序部署到包含三个pod的一个deployment中,业务中需要一个消息通知到三个pod,目前不想引入消息队列组件,所以调用现有的kubernetes python库,直接取出三个pod的IP,直接访问三遍。方法略显粗糙,虽然不优雅,但是轻量级的解决了问题。

1
2
3
resp = requests.post(url,
data=json.dumps(data_info, sort_keys=True, default=str),
headers={'Content-Type': 'application/json'})

但是在调试中发现,在一个API代码中一旦发起新的request请求,系统直接卡住,甚至所有的POD IP都无法支持访问了。
这个问题,一开始的研究思路是以为循环调用一个API导致的无限循环,但是加入debug log以后并没有打出log来,看起来并不是。
最后突然想起,之前把flask的app启动方式换成了gevent.pywsgi.WSGIServer, 会不会是线程无法启动的问题。
最后查到了如下内容:

1
2
3
4
5
6
7
8
9
10
11
12
gevent is a coroutine -based Python networking library that uses greenlet to provide a high-level synchronous API on top of the libev or libuv event loop.

Features include:
- Fast event loop based on libev or libuv.
- Lightweight execution units based on greenlets.
- API that re-uses concepts from the Python standard library (for examples there are events and queues).
- Cooperative sockets with SSL support
- Cooperative DNS queries performed through a threadpool, dnspython, or c-ares.
- Monkey patching utility to get 3rd party modules to become cooperative
- TCP/UDP/HTTP servers
- Subprocess support (through gevent.subprocess)
- Thread pools

所以,gevent是协程级别的,那么自然就阻塞了IO,那如何解决呢?
https://www.gevent.org/api/gevent.monkey.html#module-gevent.monkey

官方提供了一个patch库。总结起来就是这样:

gevent 是一个基于协程的 Python 网络库,它使用 Greenlet 库提供了一种高效的协程实现。协程是一种轻量级的线程,允许并发执行,但没有真正的并行性。协程可 以在遇到 I/O 操作时自动地切换到其他任务,从而提高程序的并发能力。
然而,在标准的 Python 线程模型中,当一个线程遇到 I/O 操作时,它会被阻塞,直到 I/O 操作完成。这意味着在传统的多线程模型下,一个线程在等待 I/O 完成时 会占用一个线程资源,而其他线程则无法被调度执行。
monkey.patch_all() 是 gevent 提供的一个函数,用于实现对标准库的自动补丁,以便与协程一起使用。这个函数会对一些常见的阻塞式 I/O 操作进行替换,使其在 遇到阻塞时能够自动地切换到其他协程任务。
当你在执行 monkey.patch_all() 后,gevent 会修改 Python 的内置库,例如 socket、threading、time 等,以便在这些库中的 I/O 操作发生时进行协程切换。 这样一来,当使用 gevent 的 WSGIServer 启动一个服务器时,它会在遇到阻塞的 I/O 操作时自动切换到其他协程,而不会阻塞整个服务器。
总结起来,执行 monkey.patch_all() 会对 Python 的内置库进行补丁,使得在使用 gevent 的 WSGIServer 启动服务器时,能够自动地在 I/O 操作发生时切换到其 他协程,从而实现多线程的并发处理能力。这使得 gevent.pywsgi.WSGIServer 能够更高效地处理并发请求。

所以在整个flask最开始的地方加上这样一段,问题就解决了。

1
2
3
# noinspection PyUnresolvedReferences
from gevent import monkey
monkey.patch_all()

同时,借这个机会,再次重温了一些线程(Process),进程(Thread)和协程(Coroutine)。这篇文章讲的挺好。
https://juejin.cn/post/7027998293351202853

有两个重要的的点:

  1. 线程是程序执行中一个单一的顺序控制流程,是程序执行流的最小单元,是处理器调度和分派的基本单位。
  2. 协程进行阻塞(Blocking)操作(如IO时)会阻塞掉整个程序。

Powered by Hexo and Hexo-theme-hiker

Copyright © 2013 - 2024 Tiaobug All Rights Reserved.

本站总访问量    次