2024年8月

前言

之前不是把 DjangoStarter 的 docker 方案重新搞好了吗

一开始demo部署是使用 SQLite 数据库的,用着没问题,但很快切换到 PostgreSQL 的时候就遇到问题了…

报错

docker 启动之后,app 容器报错

django.core.exceptions.ImproperlyConfigured: Error loading psycopg2 or psycopg module

排错

一开始我还以为是
psycopg2
库装不上,或者是在
python_builder
阶段的依赖没有正确复制到
final
阶段的容器,但后面改了几个地方,重新试了一下都不行。

接下来使用
python src/manage.py shell
进入 shell,测试一下能否导入
psycopg2

>>> import psycopg2
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "/project/.venv/lib/python3.11/site-packages/psycopg2/__init__.py", line 51, in <module>
    from psycopg2._psycopg import (                     # noqa
ImportError: libpq.so.5: cannot open shared object file: No such file or directory

好的,经常被 docker 坑的人应该已经看出问题出在系统环境上了

不使用 slim 镜像换成普通的 python 镜像就正常了…

解决

OK,既然找出问题原因,那么就可以来着手解决了。

使用 psycopg2-binary

最简单的方法就是将 psycopg2 库换成 psycopg2-binary,这是一个预编译的
psycopg2
包,不需要编译环境,安装更简便。

手动安装 libpq 库

既然缺了 libpq 库,那就在 docker 镜像里安装。

这个方法我还没测试,又麻烦又不优雅

# 使用 Python 3.11 slim 基础镜像
FROM python:3.11-slim

# 安装 psycopg2 依赖
RUN apt-get update && apt-get install -y \
    libpq-dev \
    gcc \
    # 清理缓存以减少镜像大小
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/*

不同的python基础镜像

python镜像基于 Debian 系统,默认是用 Debian 的最新稳定版,也可以自己指定版本,比如
python3.11-slim-bookworm
就是指定用 Debian 12 ("bookworm") 作为基础镜像

  • python3.11-slim-bookworm 基础镜像打包出来的镜像大小是424MB,python3.11-slim 打包出来也是这个大小
  • 使用 python3.11 基础镜像打包出来就到 1.3G 了,大小直接翻了几倍

还可以选择 alpine 镜像,不过可能会遇到更多兼容性问题,我就不折腾了,心累。

小结

一般选 slim 镜像还是可以的,基于 Debian 不怎么需要担心兼容性问题

偶尔遇到小问题还是比较好解决的

alpine 镜像不一样的地方就很多了,太折腾了…

动态规划

动态规划,英文:Dynamic Programming,简称DP,如果某一问题有很多重叠子问题,使用动态规划是最有效的。

所以动态规划中每一个状态一定是由上一个状态推导出来的,
这一点就区分于贪心
,贪心没有状态推导,而是从局部直接选最优的,

我在这里举一个典型的动态规划的问题——背包问题:

例如:有N件物品和一个最多能背重量为W 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。
每件物品只能用一次
,求解将哪些物品装入背包里物品价值总和最大。

动态规划中dp[j]是由dp[j-weight[i]]推导出来的,然后取max(dp[j], dp[j - weight[i]] + value[i])。

但如果是贪心呢,每次拿物品选一个最大的或者最小的就完事了,和上一个状态没有关系。

所以贪心解决不了动态规划的问题。

而且很多讲解动态规划的文章都会讲最优子结构啊和重叠子问题啊这些,这些东西都是教科书的上定义,晦涩难懂而且不实用。

大家知道动规是由前一个状态推导出来的,而贪心是局部直接选最优的,对于刷题来说就够用了。

上述提到的背包问题,后序会详细讲解。

动态规划的解题步骤

做动规题目的时候,很多同学会陷入一个误区,就是以为把状态转移公式背下来,照葫芦画瓢改改,就开始写代码,甚至把题目AC之后,都不太清楚dp[i]表示的是什么。

这就是一种朦胧的状态,然后就把题给过了,遇到稍稍难一点的,可能直接就不会了,然后看题解,然后继续照葫芦画瓢陷入这种恶性循环中

状态转移公式(递推公式)是很重要,但动规不仅仅只有递推公式。

动态规划五部曲

对于动态规划问题,我将拆解为如下五步曲,这五步都搞清楚了,才能说把动态规划真的掌握了!

  1. 确定状态变量dp以及dp的含义
  2. 确定递推公式
  3. dp数组如何初始化
  4. 确定遍历顺序
  5. 举例推导dp数组

一些同学可能想为什么要先确定递推公式,然后在考虑初始化呢?

因为一些情况是递推公式决定了dp数组要如何初始化!

确定状态变量dp

  • 在动态规划中,某一步的状态dp,是一定由前面的状态,或者后面的状态所推出来的,所以说我们一定要理解dp数组到底代表着什么,它的含义十分重要,如果没有搞懂一道题的dp数组的含义是什么,那么我们就不能说掌握了这道题!
  • 所以说状态变量dp是非常重要的!

递推公式

后面的讲解中我都是围绕着这五点来进行讲解。

可能刷过动态规划题目的同学可能都知道递推公式的重要性,感觉确定了递推公式这道题目就解出来了。

其实 确定递推公式 仅仅是解题里的一步而已!

一些同学知道递推公式,但搞不清楚dp数组应该如何初始化,或者正确的遍历顺序,以至于记下来公式,但写的程序怎么改都通过不了。

后序的讲解的大家就会慢慢感受到这五步的重要性了。

动态规划应该如何debug

相信动规的题目,很大部分同学都是这样做的。

看一下题解,感觉看懂了,然后照葫芦画瓢,如果能正好画对了,万事大吉,一旦要是没通过,就怎么改都通过不了,对 dp数组的初始化,递推公式,遍历顺序,处于一种黑盒的理解状态。

写动规题目,代码出问题很正常!

找问题的最好方式就是把dp数组打印出来,看看究竟是不是按照自己思路推导的!

一些同学对于dp的学习是黑盒的状态,就是不清楚dp数组的含义,不懂为什么这么初始化,递推公式背下来了,遍历顺序靠习惯就是这么写的,然后一鼓作气写出代码,如果代码能通过万事大吉,通过不了的话就凭感觉改一改。

这是一个很不好的习惯!

做动规的题目,写代码之前一定要把状态转移在dp数组的上具体情况模拟一遍,心中有数,确定最后推出的是想要的结果

然后再写代码,如果代码没通过就打印dp数组,看看是不是和自己预先推导的哪里不一样。

如果打印出来和自己预先模拟推导是一样的,那么就是自己的递归公式、初始化或者遍历顺序有问题了。

如果和自己预先模拟推导的不一样,那么就是代码实现细节有问题。

这样才是一个完整的思考过程,而不是一旦代码出问题,就毫无头绪的东改改西改改,最后过不了,或者说是稀里糊涂的过了

这也是我为什么在动规五步曲里强调推导dp数组的重要性。

举个例子哈:一些同学可能代码通过不了,会把代码抛到讨论群里问:我这里代码都已经和题解一模一样了,为什么通过不了呢?

发出这样的问题之前,其实可以自己先思考这三个问题:

  • 这道题目我举例推导状态转移公式了么?
  • 我打印dp数组的日志了么?
  • 打印出来了dp数组和我想的一样么?

如果这灵魂三问自己都做到了,基本上这道题目也就解决了
,或者更清晰的知道自己究竟是哪一点不明白,是状态转移不明白,还是实现代码不知道该怎么写,还是不理解遍历dp数组的顺序。

总结

  • 这一篇是动态规划的整体概述,讲解了什么是动态规划,动态规划的解题步骤,以及如何debug。

  • 动态规划是一个很大的领域,今天这一篇讲解的内容是整个动态规划系列中都会使用到的一些理论基础。

  • 在后序讲解中针对某一具体问题,还会讲解其对应的理论基础,例如背包问题中的01背包,leetcode上的题目都是01背包的应用,而没有纯01背包的问题,那么就需要在把对应的理论知识讲解一下。

  • 我在后面也会更新很多关于动态规划的经典题型以及解题方法。

注明:

刚看到鱼皮的文章,
一下午连续故障两次,谁把我们接口堵死了?!
,想起之前刚进公司时遇到了一个类似问题

线上接口访问不通,超时等待,但是看后台日志是正常运行的,进服务器看监控,CPU 占用100%,经典面试题了
使用
jsp -l

jstack <进程PID> > stack.txt
,保存案发现场,立马就发现问题

大量的线程在等待本地方法执行完成,经过了解

  1. 这是一段 C 语言编写的 RSA 加密算法,由于Java RSA 加密出来的数据 与 嵌入式解密数据不一致,因此加解密统一采用 C,使用本地方法调用
  2. 嵌入式通过蓝牙透传到后端,再透传到这个加密算法中,加密后将加密串传回给嵌入式比对数据
  3. 每次调用该方法,CPU 飙升至 20%,当前 CPU 几乎达到 100%,并且算法中不存在死锁问题
  4. 工厂短时间内高频调用该方法

综上,这是由于短时间内频繁调用计算密集型逻辑,CPU 计算、调度不过来导致的,需要在入口做限流措施,并且前端在连续调用时等待
一文看懂流量控制
一文了解限流策略的原理与实现

由于老板催的比较紧,并且所在项目是单体,并发量并不高,也没有接入 Redis,可容忍接口响应时间稍长,直接使用对象锁实现,还可以使用 AtomicInteger 做限制

ps:后续考虑编写网关项目,对入口流量统一控制

这是一段防爬代码块,我不介意文章被爬取,但请注明出处
console.log("作者主页:https://www.cnblogs.com/Go-Solo");
console.log("原文地址:https://www.cnblogs.com/Go-Solo/p/18368790");

圆弧形可以算是一种特殊的圆形,它是圆形的一部分。
manim
中,单独为圆弧形状封装了几个模块:

  1. Arc
    :通用的圆弧,根据半径和角度来绘制圆弧
  2. ArcBetweenPoints
    :根据两个点和角度来绘制圆弧
  3. AnnularSector
    :环形圆弧,相当于是圆环的一部分
  4. Sector
    :扇形,始终是1/4个圆环。

其中,
ArcBetweenPoints

AnnularSector
继承自
Arc

Sector
继承自
AnnularSector

image.png
圆弧系列
的模块位于
manim
中的
Mobject
之下。

1. 主要参数

模块
Arc
的主要参数有:

参数名称 类型 说明
radius float 圆弧的半径
start_angle float 圆弧开始的弧度
angle float 圆弧的弧度
num_components int 组成弧的片段,这个值越大,弧越圆滑
arc_center Point3D 圆弧对应的圆心位置

圆弧的起点和终点是由
start_angle

angle
控制的,
start_angle
参数控制圆弧的起点,默认值是
0
(圆心右边为
0度
);
angle
参数是圆弧的弧度,
start_angle + angle
的值就是圆弧的终点,
angle
是逆时针计算的。
image.png

模块
ArcBetweenPoints
的主要参数有:

参数名称 类型 说明
start Point3D 圆弧的起点
end Point3D 圆弧的终点
angle float 圆弧的弧度
raduis float 圆弧的半径

这个模块是根据两个点(
start

end
)来绘制圆弧,
angle

raduis
参数只能设置一个,用来控制圆弧的弧长。
start

end
固定时,
angle
越大,弧长越长;
raduis
越大,弧长越短。

模块
AnnularSector
的主要参数有:

参数名称 类型 说明
inner_radius float 环状弧形的内半径
outer_radius float 环状弧形的外半径
start_angle float 环状弧形开始的弧度
angle float 环状弧形的弧度

inner_radius

outer_radius
的差形成了一个环形;
start_angle

angle
的含义与模块
Arc
中的同名参数一样。

模块
Sector
的主要参数:

参数名称 类型 说明
inner_radius float 环状弧形的内半径
outer_radius float 环状弧形的外半径

Sector
模块只有
inner_radius

outer_radius
两个参数,因为它默认为1/4个圆环。

2. 主要方法

这几个模块的作用主要是展示图形,所以方法不多,常用的有两个:

名称 说明
get_arc_center 获取弧形当前的圆心坐标
move_arc_center_to 设置弧形新的圆心坐标

3. 使用示例

下面分别针对4种圆弧形的模块的参数,演示一些示例。

3.1. 通用圆弧

通用的的圆弧
Arc
是最常用的,
start_angle

angle
设置圆弧的起点和弧度,
arc_center
设置圆弧的位置,
radius
控制圆弧的大小。

Arc(
    arc_center=LEFT * 2,
    angle=PI,
)
Arc(
    radius=1.5,
    arc_center=RIGHT,
    angle=PI,
)
Arc(
    start_angle=PI / 2,
    angle=PI / 2,
    arc_center=LEFT * 2 + DOWN * 2,
)
Arc(
    start_angle=PI,
    angle=PI * 3 / 2,
    arc_center=RIGHT + DOWN * 1.5,
)

out.gif

3.2. 根据两点生成圆弧

从固定的两点生成圆弧
ArcBetweenPoints
也是在制作动画时常用的方式,
参数
start

end
设置圆弧的起点和终点;
angle

radius
设置圆弧的弯曲程度,这两个参数只能使用一个,同时设置时只有
radius

ArcBetweenPoints(
    start.get_center(),
    end.get_center(),
    radius=0.8,
)

ArcBetweenPoints(
    start.get_center(),
    end.get_center(),
    radius=1.6,
)

ArcBetweenPoints(
    start.get_center(),
    end.get_center(),
    angle=PI / 2,
)

ArcBetweenPoints(
    start.get_center(),
    end.get_center(),
    angle=PI,
)

前两个示例用
radius
控制圆弧的度数,后两个示例用
angle
控制圆弧的度数。
out.gif

3.3. 环形的圆弧

环形的圆弧
AnnularSector
可以理解成增加了厚度的圆弧。
它可以使用
Arc
的参数,不同之处在于它的半径有两个,一个是
inner_radius
,一个是
outer_radius

这两个半径之差就是环形的厚度。

AnnularSector(
    inner_radius=0.5,
    outer_radius=1.5,
    angle=PI,
)
AnnularSector(
    inner_radius=0.5,
    outer_radius=0.8,
    angle=PI,
)
AnnularSector(
    start_angle=PI / 2,
    angle=PI / 2,
    inner_radius=0.5,
    outer_radius=1.5,
)
AnnularSector(
    start_angle=PI,
    angle=PI * 3 / 2,
    inner_radius=0.5,
    outer_radius=0.8,
)

out.gif

3.4. 扇形

扇形
Sector
是一种简化的
AnnularSector
,它固定为
1/4
个圆环。

Sector(
    inner_radius=0.5,
    outer_radius=1.5,
)
Sector(
    inner_radius=0.5,
    outer_radius=0.8,
)
Sector(
    inner_radius=1,
    outer_radius=1.5,
)
Sector(
    inner_radius=0.3,
    outer_radius=1.8,
)

out.gif

4. 附件

文中完整的代码放在网盘中了(
arc.py
),
下载地址:
完整代码
(访问密码: 6872)

创建一个简单的Docker镜像
利用docker ps -a命令查看要导出的镜像。

【容器】镜像导出/导入

导出

查看要导出的镜像

[root@localhost ~]# docker ps -a

docker export命令进行镜像导出。

[root@localhost ~]# docker export 418ae > nginx-docker.tar

image

导入

带标签

利用 cat image.tar|docker import – image_name:tag 命令进行导入

# 导入时,带上标签  ----  名称和标签尽量和原镜像保持一致
[vipsoft@kafka1host docker]$ cat nginx-docker.tar | docker import  - nginx:1.20.1

image

不带标签,后期修改

[vipsoft@kafka1host docker]# ll
total 132428
-rw-r--r--. 1 vipsoft root 135605760 Aug 19 15:38 nginx-docker.tar
# docker import 导入,没有镜像名称和标签的
[vipsoft@kafka1host docker]# docker import nginx-docker.tar
sha256:be2f0bed014f5eaf8227e9849f03614944f85f2ac25e7ad261b39bef2799a03a
[vipsoft@kafka1host docker]# docker images
REPOSITORY                                            TAG       IMAGE ID       CREATED         SIZE
<none>                                                <none>    be2f0bed014f   5 minutes ago   131MB
nacos/nacos-server                                    v2.1.1    5ef6d98bd956   2 years ago     1.06GB
java                                                  8         d23bdf5b1b1b   7 years ago     643MB
# 使用 docker tag命令进行修改 ":" 前代表镜像名称,":"后代表镜像标签
[vipsoft@kafka1host docker]# docker tag be2f0bed nginx:1.21.1
[vipsoft@kafka1host docker]# docker images
REPOSITORY                                            TAG       IMAGE ID       CREATED         SIZE
nginx                                                 1.21.1    be2f0bed014f   7 minutes ago   131MB
nacos/nacos-server                                    v2.1.1    5ef6d98bd956   2 years ago     1.06GB
java                                                  8         d23bdf5b1b1b   7 years ago     643MB

利用docker import命令导入的镜像,镜像在仓中是没有镜像名称和标签的,可以使用docker tag命令进行修改。
image

【仓库】镜像导出/导入

导出

docker save -o nginx-vipsoft-docker.tar nginx:1.20.1
# nginx-docker.tar:代表导出镜像后的压缩名字,可以任意命名。
# -o:指定输出文件名,可以任意命名。
# nginx:1.20.1 :代表仓库中要导出的镜像名称。按仓库中实际名字为主。

导入

# docker load -i 镜像保存文件位置
docker load -i /home/vipsoft/docker/nginx-vipsoft-docker.tar

注:如果在某一个容器里安装了某些命令或配置,导出镜像时,一定要先把容器提交到镜像,否则导出的镜像再次导入新的docker中,你安装命令或配置会丢失。

如下所示,我在某一个容器里安装了查看网络的命令,但没有提交容器到镜像,导出后再次导入到其它docker环境后,执行ifconfig时报命令换不到:

docker commit 418ae48f5db8 nginx:1.20.1

导入(完整命令)

[root@localhost ~]# docker images | grep nginx
nginx                                                        1.20.1    c8d03f6b8b91   2 years ago     133MB
registry.baidubce.com/netrainpublic/nginx                    latest    90c072ff69fd   4 years ago     287MB
[root@localhost ~]# docker ps -a | grep nginx
418ae48f5db8   nginx:1.20.1           "/docker-entrypoint.…"   18 hours ago    Up 18 hours                 0.0.0.0:80->80/tcp, :::80->80/tcp   nginx
[root@localhost ~]# docker exec -it 418ae /bin/bash
root@418ae48f5db8:/# vi /usr/share/nginx/html/index.html
bash: vi: command not found
root@418ae48f5db8:/# exit
exit
# 容器中没有 vi 命令,将 index.html 复制到宿主机中进行修改
[root@localhost ~]# docker cp 418ae48:/usr/share/nginx/html/index.html /opt/index.html
[root@localhost ~]# vi /opt/index.html
# 将改好的文件 复制到容器中
[root@localhost ~]# docker cp  /opt/index.html 418ae48:/usr/share/nginx/html/index.html
# 进入容器,查看是否被修改
[root@localhost ~]# docker exec -it 418ae /bin/bash
root@418ae48f5db8:/# cat /usr/share/nginx/html/index.html
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
<b>VipSoft 2024.08.20</b> #增加的内容
</body>
</html>
root@418ae48f5db8:/# exit
[root@localhost ~]# docker images | grep nginx
nginx                                                        1.20.1    c8d03f6b8b91   2 years ago     133MB
registry.baidubce.com/netrainpublic/nginx                    latest    90c072ff69fd   4 years ago     287MB
[root@localhost ~]# docker ps -a | grep nginx
418ae48f5db8   nginx:1.20.1           "/docker-entrypoint.…"   18 hours ago    Up 18 hours                 0.0.0.0:80->80/tcp, :::80->80/tcp   nginx
# 将容器修改后的内容,提交到镜像
[root@localhost ~]# docker commit 418ae nginx:1.20.1
sha256:b532c001cdda870c5cf3c689d720b39571f160071671758ae899aed2870b8517 #生成新的镜像 b532...
[root@localhost ~]# docker images | grep nginx
nginx                                          1.20.1    b532c001cdda   7 seconds ago   133MB #生成新的镜像 b532...
nginx                                          <none>    c8d03f6b8b91   2 years ago     133MB
registry.baidubce.com/netrainpublic/nginx      latest    90c072ff69fd   4 years ago     287MB
# 保存镜像
[root@localhost ~]# docker save -o nginx-vipsoft-docker.tar nginx:1.20.1
[root@localhost ~]# ll | grep nginx
-rw-------. 1 root root 137402368 8月  20 09:38 nginx-vipsoft-docker.tar
[root@localhost ~]#

目标机

[vipsoft@kafka1host docker]# docker images | grep nginx
# 加载保存的镜像
[vipsoft@kafka1host docker]# docker load -i /home/vipsoft/docker/nginx-vipsoft-docker.tar
e81bff2725db: Loading layer [==================================================>]  72.52MB/72.52MB
6662554e871b: Loading layer [==================================================>]   64.8MB/64.8MB
4edd8832c8e8: Loading layer [==================================================>]  3.072kB/3.072kB
0dcd28129664: Loading layer [==================================================>]  4.096kB/4.096kB
8ffde58510c5: Loading layer [==================================================>]  3.584kB/3.584kB
91117a05975b: Loading layer [==================================================>]  7.168kB/7.168kB
dd6582995144: Loading layer [==================================================>]  19.97kB/19.97kB
Loaded image: nginx:1.20.1
[vipsoft@kafka1host docker]# docker images | grep nginx
nginx                                                 1.20.1    b532c001cdda   22 minutes ago   133MB
# 运行 Nginx 镜像
[vipsoft@kafka1host docker]# docker run -d -p 80:80 --name nginx nginx:1.20.1
3d7e05768d372f8753882505313563be23fd594ad6bb2778fd49ff9cf47cecdb
[vipsoft@kafka1host docker]# docker ps -a | grep nginx
3d7e05768d37   nginx:1.20.1   "/docker-entrypoint.…"   10 seconds ago   Up 9 seconds   0.0.0.0:80->80/tcp, :::80->80/tcp   nginx
# 查看日志
[vipsoft@kafka1host docker]# docker logs --tail=100 -f nginx

image