我们不能失去信仰

我们在这个世界上不停地奔跑...

0%

GitLab

关于 GitLab 方面的问题,其实以前已经写过了,以前是在黑群晖上面使用,但是这个黑群晖越使用越发恶心,动不动死机系统无响应,终于在某天开机的过程中告诉我需要重装系统(可能和我直接暴力ssh上去操作了一波又一波有关)。最终的结果就是应用数据全丢,因为 GitLab 是以Docker形式部署的,所以也没了,不过还好,电脑的原始资料还在,就是费点时间重建一下。

并且这个黑群晖的机器性能也不行,蜗牛星际矿机。干啥啥不行,我都问我自己为啥不直接装个 Linux。念在我还是几T数据在上面,还是先忍忍了。换台机器吧。

Ubuntu 20.04 部署 GitLab

还有人说自己搞 Linux 部署会比较麻烦, 这个我肯定,但是也不是那么麻烦,毕竟现在不跟以前一样了,部署过程不叙述了,GitLab 文档写的十分清楚,就是一些配置可以说一下。

由于我是内网主机部署,但是我有公网IP,所以几乎所有的服务我都需要进行转发。

在转发的基础上,还是不能通过域名进行到底给内网的哪个服务,所以在路由器做了端口转发后,还需要一个反向代理,我使用Nginx做了反向代理。

阅读全文 »

使用 uptime 命令可以简洁的了解系统的负载情况:

1
2
3
$ uptime
09:46:05 up 7:13, 1 user, load average: 0.62, 0.39, 0.37
当前时间 系统运行时间 正在登陆的用户数 1 分钟 5 分钟 15 分钟的平均负载

可以通过查到最后三个数字来判断服务器的负载情况,如果是从左到右依次减小,那么说明系统目前的负载是在升高的,如果从左到右依次增加,那么说明系统目前的负载是在下降的。

平均负载代表的是单位时间内,系统处于可运行状态(ps命令中处于 R 状态 Running/Runnable)和不可中断状态(ps 命令中处于 D 状态 Uninterruptible Sleep, 也称为 Disk Sleep)的平均进程数,也就是平均活跃进程数,它和 CPU使用率 没有直接关系。

平均负载最理想的情况是等于 CPU 个数, 可以通过 cat /proc/cpuinfo 来进行查看 CPU 的信息。当平均负载高于 CPU 数量 70% 的时候,可能就已经开始影响进程服务,导致其变慢。

阅读全文 »

在 Python 中,如果要生成一个全是0的列表,并且这个列表的长度为100,可以使用如下操作: a = [0] * 100 就生成了,效果和 a = [0 for _ in range(100)] 一样。

在某次需要生成二维数组的时候,奇怪的事情发生了,写一道题总是过不了。检查了好几遍,最终定位到问题在于 [[0]* n]* n

其实从表面上看 [[0] *n ]* n[[0]*n for _ in range(n)] 是一样的,但是当你改变某个位置的值的时候,却发现 后面生成的只会改变哪一个位置的值, 而前者会改变每一列的值。究其原因,发现前者生成出来的列表其实都是第一个的引用。

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

# coding:utf8

n = 10

a = [[0] * n] * n

b = [[0] * n for _ in range(n)]

a[0][0] = 1

b[0][0] = 1

print(a)

print(b)

执行结果:

[[1, 0, 0, 0, 0, 0, 0, 0, 0, 0], [1, 0, 0, 0, 0, 0, 0, 0, 0, 0], [1, 0, 0, 0, 0, 0, 0, 0, 0, 0], [1, 0, 0, 0, 0, 0, 0, 0, 0, 0], [1, 0, 0, 0, 0, 0, 0, 0, 0, 0], [1, 0, 0, 0, 0, 0, 0, 0, 0, 0], [1, 0, 0, 0, 0, 0, 0, 0, 0, 0], [1, 0, 0, 0, 0, 0, 0, 0, 0, 0], [1, 0, 0, 0, 0, 0, 0, 0, 0, 0], [1, 0, 0, 0, 0, 0, 0, 0, 0, 0]]

[[1, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]

首先在 GitLab 上面创建项目,打开项目,然后在左侧页面点击 Setting –> Integrations 然后输入回调的 url 以及设置在发生哪些事件时通知, 因为我这里主要是想实现我推送新的内容到 GitLab, 然后我的博客服务器可以收到 GitLab 发送过来的通知, 进而实现 我的博客服务器可以去自动拉取代码进行部署。

不过,如果你输入的是一个私有地址,如 192.168.xxx.xxx 这样的一看就是内网地址,会提示你: url is blocked requests to the local network are not allowed 。解决方法: 需要先登陆管理员账号–> Admin area –> Setting –> NetWork 找到 *Outbound requests * 把 Allow requests to local network from hooks and services 打上勾保存就可以了。

从 GitLab 发请求到 服务器时,会让你设置一个 token , 这个 token 是让你用来验证这个请求是来自 GitLab 服务器的。

服务器收到请求后,接下来会从 GitLab 拉取代码,如果直接放 ssh,是否会权限有点大了,可以在 setting 里面点击 Repository 然后在 Deploy Tokens 里面申请一个 Token 。

阅读全文 »

问题是这样的,打开我们的网站后,打开开发者工具,然后偶尔会看到某个请求一直处于 pending 状态,大概会过去个十几秒,偶尔又全部都很快,几百毫秒就完成了。

看了下 TTFB, 发现基本就花在了等服务器响应的时间上。 其实到这里基本上就可以断定问题出在了服务器上,但是我有点不信邪,觉得一个 TTFB 有可能不太准确,然后就分析了 chrome 的网络日志。具体方法:

  1. 打开 chrome://net-export/ ,然后点击 Start Logging to Disk 然后会让你选择一个地方来存储日志文件,好了之后录制也就开始了。
  2. 现在去出现问题的页面疯狂尝试重现,重现完毕后,回到第一步的那个页面Stop 就可以了。
  3. 查看日志, 打开这个地址 https://netlog-viewer.appspot.com/#import 然后选择文件,将刚才的日志文件导入。选择 event, 搜索那个出问题的 api, 就可以查看到详细的日志,简单到点的话,看看有没有 error 之类的日志,如果没有的话,看看从哪一步开始时间边长的,我这个是从读取响应 header 的时候边长的,估计是在等待,也没有 error , 基本就断定问题出现在了服务端。

然后在服务端查了一会,没有查到,最后靠猜猜到的。当时有个现场就是服务器重新部署后,严谨的说应该是第一次请求,发现大量接口都处于 pending 状态,这个时候基本就可以知道结论了, 某些初始化采用了类似单例模式中的懒汉模式,就是访问的时候才进行初始化,这就导致第一次非常慢。 然后继续往下看,发现数据库的连接池有点问题, 采用了 Blocking 模式,并且也没有按需增加和减少连接数,这样的话,如果某个 库的连接急剧上涨,那后面的请求肯定得处于 pending 状态。 最终将连接池 Blocking 改掉发现 pending 消失了。 其实当时考虑到,在以前的代码中有的使用了select LAST_INSERT_ID() 这就搞得无法使用共享的连接池, 最终只能改成随线程创建,随线程销毁的模式的连接池,这种模式也没有 blocking 模式,最终就解决了。

阅读全文 »

其实大家都知道判断一个数字是否存在,使用集合速度会更快。这次,我实际测试了一下,结果出乎意料,也在意料之中。 出乎意料的是,这个不应该会被底层优化成集合嘛!意料之中的是,不要什么都需要底层优化来帮你优化,其实有的时候,他并不会为我们优化。 正是某些情况如: 现在不需要注意那些了, 底层都会帮你优化的,导致最终性能下降的厉害,毕竟他优化没优化我们也不知道。 说到底还是一句话,写代码规范问题。

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
#!/usr/bin/python env
# coding:utf8

import time


arr = [i for i in range(10000)]

def test_list(arr):
for i in range(10000):
if i in arr:
pass


def test_set(arr):
for i in range(10000):
if i in arr:
pass



list_arr = arr
set_arr = set(arr)
start = time.time()
test_list(list_arr)
t_list = time.time() - start

start = time.time()
test_set(set_arr)
t_set = time.time() - start

print('列表耗时: ', str(t_list))

print('集合耗时: ', str(t_set))

print('快多少倍: ' , str(t_list // t_set))

python3 执行结果(仅供参考):

列表耗时: 0.498913049697876
集合耗时: 0.00047206878662109375
快多少倍: 1056.0

直接写段代码试下就完事了,简单直观。

规范的重要性油然而生。

把 html 文件批量转换成 PDF 文件,然后再用 calibre 处理成 Kindle 的格式,就可以在
kindle 上面欣赏优秀的文章和博客了。

一开始,我拿到一堆 html 文件的时候,我记得 Chrome 浏览器可以点击文件,打印,然后
选择保存成 PDF 格式, 于是手动转换了几个 html 文件是在觉得麻烦物料,重复的操作。

正在前几天无意中看到了一个新的名词 Headless Chrome , 其实就是一个无界面的 Chrome
浏览器,易于通过编程来进行控制。于是我自然就联想到了是否有 文件–打印–存储为PDF
这个 API, 看了一会,发现确实有。

于是马上开工,直接使用 Python 的 os 模块来循环执行生成,最终还算满意,都转换成了 PDF。

既然能编程了,那要求可能就不能止于此了, 尝试把广告,无用的水印,无关的内容都去掉,

阅读全文 »

近期写一些脚本需要用到 os.popen 函数,然后就看了下实现,发现它返回的是一个文件描述符,就跟 open 这个操作返回的东西一样,只不过 os.popen 返回的是一个只读的。

然后联想到,如果返回的是一个类似文件的fd,岂不是需要执行完命令后,需要手动 close 嘛。

于是做了一个小实验来一探究竟。首先需要一些命令辅助。

1
ps -ef |grep demo.py|awk '{print $2}'

这个命令是用来查看我们运行 Python 程序后进程 PID 的。

1
2
<!--more -->
lsof -p PID | wc -l

这个命令把上一步我们拿到的 PID 填充进去,就可以得到这个进程打开的文件数量。

一般来说,就算我们的程序没有打开额外的文件,这个命令也不会显示0,因为这个进程总会打开一些 Python 库必须的文件,我这里是10个。

在额外说一个命令,修改系统单个进程能够打开的最大文件句柄数量:

1
2
3
4
5
6
7
ulimit -n       # 查看当前系统定义的单个进程最大打开的句柄数

lsof -n|awk '{print $2}'|sort|uniq -c|sort -nr|more #输入第一列为 PID ,第二列为数量

ps aef|grep PID # 根据进程 PID 查看进程的信息

ulimit – HSn 2048 # 临时设置文件句柄数, 重启失效 H 为硬性 S为软性 n 表示设定单个进程最大的打开文件句柄数量

下面是实验的程序:

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
#!/usr/bin/python env
# coding:utf8


import os
import time


time.sleep(10)

def main():
for i in range(10):
time.sleep(2)
a = os.popen('ls')
a.close()
os.popen('ls')
a.close()

c = open('./test.sh')
c.close()
d = open('./test.sh')
d.close()
e = open('./test.sh')
f = open('./test.sh')
g = open('./test.sh')
h = open('./test.sh')


main()

print('退出前20s')
time.sleep(20)

你可以 删除 c.close() 、d.close() 、或者添加更多。最终得到的结论是:

  1. 如果函数退出,文件描述符自动回收,即使没有手动 close 。

  2. 使用 popen 执行后确实会返回一个类似的fd(文件描述符),但是在系统上使用命令查询却看不出增加了,说明其实这个只是一个 Python 内部的对象,并不需要操作系统真正的分配文件描述符。 而既然是一个对象,那么如果不 close, 最终函数执行结束了,也会被垃圾回收给清理掉。

  3. 内存回收机制一定不会不回收,否则就要内存泄露了,像 Python Java 这种语言应该不存在这个问题

看到了以上结论后,也许会问,那都可以自动回收,那为啥还要设计 close 呢。以下只是我个人的思考:

  1. close 设计的原因: 如果你自己不 close, 等 垃圾回收的话,比如你一个函数巨长,最终还没开始垃圾回收, 就已经无法再打开新文件了,因为超过个数了。
  2. 手动 close 可以保证及时把写完的内容刷到文件里。
  3. 手动 close 可以随时释放资源,提高资源利用率。
  4. 要养成 close 的习惯,来避免一些难于找到又奇怪的 bug。

除此之外,执行系统命令还可以用:

os.system() : 这个就不会返回一个 fd 了,无法获取命令的输出和返回值

os.popen() : 可以获取到命令执行后的输出

commands : 还有这个库,可以同时获得返回值和输出

参考:

Linux 查看文件句柄数

如果你是 Linux 初级用户,为什么不建议使用 root 账户,一方面 root 权限太大,这谁都知道,另一方面可能也是较为重要的一点,那就是 普通用户和 root 用户执行同一个操作,最终的结果可能不一样,而在公司工作,往往都不会使用 root 账户。这也就是有时候你查询到的资料的结果和你自己执行的结果不一样的原因。

在 MySQL 中,执行 show processlist; 是显示用户用户正在运行的线程,

1
2
3
4
5
6
7
8
9

mysql> show processlist;
+----------+------------+----------------------------------------+------+---------+------+-------+------------------+
| Id | User | Host | db | Command | Time | State | Info |
<!--more -->
+----------+------------+----------------------------------------+------+---------+------+-------+------------------+
| 12913293 | my_user | localhost:54716 | NULL | Query | 0 | NULL | show processlist |
+----------+------------+----------------------------------------+------+---------+------+-------+------------------+
1 row in set (0.00 sec)
  • Id : 数据库连接的id,可以在 MySQL 中使用 kill Id 将其结束。
  • User:用户名
  • Host: 分为主机名和主机的主动连接的端口(这个端口是随机的)
  • db: 选择的数据库,这里还没选择数据库就是 NULL
  • Command: 当前正在执行的命令
  • Time: 处于 State 显示的状态多久了,如果没有其他连接,只有这一个连接,它永远是0,因为你一执行命令他就刷新了。
  • State: 状态取决于正在执行的 Command 命令。
  • Info:记录线程直接的语句。默认只显示100个字符,如果要看全部信息,需要使用 show full processlist

State的各种状态:

A thread can have any of the following Command values:

  • Binlog Dump

    This is a thread on a replication source for sending binary log contents to a replica.

  • Change user

    The thread is executing a change-user operation.

  • Close stmt

    The thread is closing a prepared statement.

  • Connect

    A replica is connected to its source.

  • Connect Out

    A replica is connecting to its source.

  • Create DB

    The thread is executing a create-database operation.

  • Daemon

    This thread is internal to the server, not a thread that services a client connection.

  • Debug

    The thread is generating debugging information.

  • Delayed insert

    The thread is a delayed-insert handler.

  • Drop DB

    The thread is executing a drop-database operation.

  • Error

  • Execute

    The thread is executing a prepared statement.

  • Fetch

    The thread is fetching the results from executing a prepared statement.

  • Field List

    The thread is retrieving information for table columns.

  • Init DB

    The thread is selecting a default database.

  • Kill

    The thread is killing another thread.

  • Long Data

    The thread is retrieving long data in the result of executing a prepared statement.

  • Ping

    The thread is handling a server-ping request.

  • Prepare

    The thread is preparing a prepared statement.

  • Processlist

    The thread is producing information about server threads.

  • Query

    The thread is executing a statement.

  • Quit

    The thread is terminating.

  • Refresh

    The thread is flushing table, logs, or caches, or resetting status variable or replication server information.

  • Register Slave

    The thread is registering a replica server.

  • Reset stmt

    The thread is resetting a prepared statement.

  • Set option

    The thread is setting or resetting a client statement-execution option.

  • Shutdown

    The thread is shutting down the server.

  • Sleep

    The thread is waiting for the client to send a new statement to it.

  • Statistics

    The thread is producing server-status information.

  • Table Dump

    The thread is sending table contents to a replica.

  • Time

    Unused.

MySQL官网

中文解释:

  • Binlog Dump: 主节点正在将二进制日志 ,同步到从节点
  • Change User: 正在执行一个 change-user 的操作
  • Close Stmt: 正在关闭一个Prepared Statement 对象
  • Connect: 一个从节点连上了主节点
  • Connect Out: 一个从节点正在连主节点
  • Create DB: 正在执行一个create-database 的操作
  • Daemon: 服务器内部线程,而不是来自客户端的链接
  • Debug: 线程正在生成调试信息
  • Delayed Insert: 该线程是一个延迟插入的处理程序
  • Drop DB: 正在执行一个 drop-database 的操作
  • Execute: 正在执行一个 Prepared Statement
  • Fetch: 正在从Prepared Statement 中获取执行结果
  • Field List: 正在获取表的列信息
  • Init DB: 该线程正在选取一个默认的数据库
  • Kill : 正在执行 kill 语句,杀死指定线程
  • Long Data: 正在从Prepared Statement 中检索 long data
  • Ping: 正在处理 server-ping 的请求
  • Prepare: 该线程正在准备一个 Prepared Statement
  • ProcessList: 该线程正在生成服务器线程相关信息
  • Query: 该线程正在执行一个语句
  • Quit: 该线程正在退出
  • Refresh:该线程正在刷表,日志或缓存;或者在重置状态变量,或者在复制服务器信息
  • Register Slave: 正在注册从节点
  • Reset Stmt: 正在重置 prepared statement
  • Set Option: 正在设置或重置客户端的 statement-execution 选项
  • Shutdown: 正在关闭服务器
  • Sleep: 正在等待客户端向它发送执行语句
  • Statistics: 该线程正在生成 server-status 信息
  • Table Dump: 正在发送表的内容到从服务器
  • Time: Unused

感谢知乎博主

由于 Python 是支持多继承的,而 Mixin 类就是利用了这种特性。

Mixin 类是只包含了一组特定的函数组合,我们将其与其他类进行混合,从而生成一个适用于实际需要的新类。

直接上 code 更好理解:

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
#!/usr/bin/python env
# coding:utf8

class A(object):
<!--more -->

def say(self, word):
print('I am A')
print('say' + word)


class AMixin(object):

def eat(self, food):
print('eat ' + food)

def say(self, word):
super().say(word + 'from mixin')
print('i am AMixin')



class B(AMixin, A):
pass



b = B()

b.say('hello')


# 这是 Python3 的语法

# 执行结果:

I am A
sayhellofrom mixin
i am AMixin

以上执行结果说明了一个问题,就是 AMixin 的父类并不是 A ,但是竟然可以调用 A 的 say 方法。

是因为 B 同时继承了 AMixin 和 A , 这就是 Mixin 类的作用。

而 Mixin 类为什么可以执行到 A 的 say 方法呢,在多重继承的环境下, super() 有相对来说更加复杂的语义。它会查看你的继承链, 使用一种叫做 Methods Resolution Order(方法解析顺序)的方式,来决定调用最近的继承父类的方法。

也就是说super().method() 与 self.method() 是差不多的,只是 super().method() 需要跳过当前类而已。

参考:

感谢 Python 的 Mixin 类