#303 Linux 内存占用情况分析

2019-04-04

以我本地系统(Ubuntu)上的 supervisord 为例。

  1. ps -ef | grep supervisord / pgrep supervisord
  2. sudo cat /proc/1553/maps
  3. sudo gdb attach 1553
  4. gdb 中导出指定区域内存
    dump memory /tmp/1553-heap.mem 0x564a7e667000 0x564a7eb25000
  5. 显示内存数据
    strings -n 10 /tmp/1553-heap.mem
    ASCII 格式的展示,过滤掉长度少于 10 的行。

关于 /proc/pid/maps 文件的格式

内存映射(mmap):

  • 文件映射,将整个文件或文件的一部分映射到内存中
  • 匿名映射,创建一个全为 0 的内存空间

这里的 maps 文件就是内存映射的一个情况。

  1. 内核操作集:proc_pid_maps_op,导出函数:show_map(没接触过内核开发)
  2. 可以 sudo head /proc/self/maps 试试。当前进程的内存映射信息表,软链接。
  3. Linux 内存管理单元数据结构:vm_area_struct
$ sudo head /proc/1553/maps
564a7d858000-564a7d8a5000 r--p 00000000 08:01 3018208                    /usr/bin/python2.7
564a7d8a5000-564a7da56000 r-xp 0004d000 08:01 3018208                    /usr/bin/python2.7
564a7da56000-564a7db65000 r--p 001fe000 08:01 3018208                    /usr/bin/python2.7
564a7db66000-564a7db68000 r--p 0030d000 08:01 3018208                    /usr/bin/python2.7
564a7db68000-564a7dbde000 rw-p 0030f000 08:01 3018208                    /usr/bin/python2.7
564a7dbde000-564a7dc02000 rw-p 00000000 00:00 0
564a7e667000-564a7eb25000 rw-p 00000000 00:00 0                          [heap]
7fe8e4308000-7fe8e4448000 rw-p 00000000 00:00 0
7fe8e4448000-7fe8e444c000 r--p 00000000 08:01 1840428                    /lib/x86_64-linux-gnu/libexpat.so.1.6.8
7fe8e444c000-7fe8e446d000 r-xp 00004000 08:01 1840428                    /lib/x86_64-linux-gnu/libexpat.so.1.6.8
  • 第一列:address 地址。[vm_start, vm_end),即起始地址-结束地址。
  • 第二列:perms 权限。vm_flags,前三位分别是 r/w/x,不必说,第四位有两种值:ps,分别表示私有 private 或共享 shared。
  • 第三列:offset 偏移。vm_pgoff,如果是从文件映射到内存,那么偏移值表示从这个文件的指定位置开始,否则就会是 00000000
    我猜,可能比较多的是用在拓展库之类的方面。
  • 第四列:dev 设备。主设备号:次设备号,同样适用于从文件映射到内存的情况,表示文件所存放的设备。
  • vm_file->f_dentry->d_inode->i_sb->s_dev
  • 第五列:inode FS索引节点。同样适用于从文件映射到内存的情况,表示文件所存放的 “块”(或者叫 “区域” 吧)。
  • vm_file->f_dentry->d_inode->i_ino
  • 0 表示不关联,dev 字段也应该为 00000000
  • 第六列:pathname 文件名
  • 文件名
  • 这段虚拟内存在进程中的角色,常见的:
    • [heap]
    • [stack] 栈,主线程(main process)
    • [stack:1001] 栈,线程 ID 我还没见到过这样的情况
    • [vdso]
    • [vvar]
    • [vsyscall]
  • 匿名映射
    • 不显示,其他情况

其他

  1. 据说,主线程申请内存(malloc)会显示 [heap],子线程申请则是匿名映射。
  2. 子线程的栈空间动态分配,匿名。这就是我没看到一例类似 [stack:1001] 情况的原因么?
  3. 从 maps 文件记录上看,增加一个子线程,在 maps 文件中就增加了两条记录,分别是子线程的栈空间和栈保护页的记录。默认情况下,pthread为子线程预留的栈空间大小为1MB,栈保护页为4KB(这主要跟页大小相关)。

#!/usr/bin/env python
import re
maps_file = open('/proc/self/maps', 'r')
mem_file = open('/proc/self/mem', 'r', 0)
for line in maps_file.readlines():  # for each mapped region
    m = re.match(r'([0-9A-Fa-f]+)-([0-9A-Fa-f]+) ([-r])', line)
    if m.group(3) == 'r':  # if this is a readable region
        start = int(m.group(1), 16)
        end = int(m.group(2), 16)
        mem_file.seek(start)  # seek to region start
        chunk = mem_file.read(end - start)  # read region contents
        print chunk,  # dump contents to standard output
maps_file.close()
mem_file.close()
sudo awk -n -F '[- ]' '/\[heap\]/ {h="0x"$2-"0x"$1+1;printf("%d bytes (%.2f MB)\n",h,h/1024/1024)}' /proc/1553/maps

python -c "import sys;a,b=sys.argv[1].split('-');ai=int('0x'+a,16);bi=int('0x'+b,16);x=(bi-ai+1);y=x/(1024*1024);print('%d Bytes (%.2f MB)'%(x,y));" 55854b0f5000-55855a372000

参考资料或拓展阅读

  1. colin.guru,Dumping Ram From Running Linux Processes
  2. StackOverflow,How to identify STACK and HEAP segments in /proc/$PID/maps file?
  3. StackOverflow,How do I read from /proc/$pid/mem under Linux?
  4. CSDN,linux proc maps文件分析

#302 MySQL: wait for table metadata lock

2019-03-29

问题

mysql> status
--------------
mysql  Ver 14.14 Distrib 5.7.25, for Linux (x86_64) using  EditLine wrapper

Connection id:      17190053
Current database:   gkbb
Current user:       root@10.9.165.246
SSL:            Not in use
Current pager:      less
Using outfile:      ''
Using delimiter:    ;
Server version:     5.5.5-10.1.26-MariaDB MariaDB Server
Protocol version:   10
Connection:     10.9.108.125 via TCP/IP
Server characterset:    utf8
Db     characterset:    utf8
Client characterset:    utf8
Conn.  characterset:    utf8
TCP port:       3306
Uptime:         13 days 21 hours 14 min 38 sec

Threads: 276  Questions: 31378648  Slow queries: 212  Opens: 2977  Flush tables: 1  Open tables: 2761  Queries per second avg: 26.155
--------------

mysql> show global variables like "innodb_version";
+----------------+-------------+
| Variable_name  | Value       |
+----------------+-------------+
| innodb_version | 5.6.36-82.1 |
+----------------+-------------+
1 row in set (0.06 sec)

PS: 查看 status 还有一个快捷方式 \s

编辑测试库表结构(添加字段),卡住,任何操作都不行了,等一个多小时,还是不行。。
还一度怀疑是不是表结构设计问题,字段、数据是不是太多了。

过程

偶尔想起看看会话情况:

SELECT * FROM information_schema.processlist WHERE db = 'mydb';

或命令:

  • mysqladmin -uroot -p123456 processlist
  • mysql -uroot -p123456 -e 'SHOW PROCESSLIST'

看到里面好几个会话的状态都是 wait for table metadata lock,这就有点奇怪了,之前没有见过。

网上的资料显示:

为了在并发环境下维护表元数据的数据一致性,在表上有活动事务(显式或隐式)的时候,不可以对元数据进行写入操作。因此 MySQL 引入了 metadata lock ,来保护表的元数据信息。
因此在对表进行上述操作时,如果表上有活动事务(未提交或回滚),请求写入的会话会等待在 Metadata lock wait 。

如果资料没错,那么就是说,如果有事务没有结束,DDL 操作请求 MDL(metadata lock)时会卡住这张表。

我想起我们的服务中确实存在会话没有关闭的情况。

  1. 用了 SQLAlchemy 做 ORM
  2. 每次查询都使用一个会话,包括 SELECT
  3. 增删改操作都立即 commit 了,SELECT 却没有(记得是有个什么原因特意如此)

合理怀疑:这个查询 SESSION 没有关闭,导致 ALTER 语句进入 MDL 等待状态,然后导致了表无法进行任何操作(包括查询,至于为什么这样,我不知道)。

本地复现

  1. 开两个终端,分别建立 MySQL 连接。
  2. 其中一个终端(A):
  3. SET SESSION auto_commit = 0;
  4. SELECT * FROM test.test LIMIT 1;
  5. 另一个终端(B)只需要:TRUNCATE test.test;,然后发现:卡住了。

PS:

  1. DDL 需要 metadata 锁。
  2. TRUNCATE 属于 DDL,可能因为其非事务性(不支持提交和回滚)。参考:https://dba.stackexchange.com/questions/36607/why-is-truncate-ddl

现在,回到终端 A:

mysql> select * from information_schema.processlist where db = 'test';
+----+------+-----------+------+---------+------+---------------------------------+----------------------------------------------------------------+
| ID | USER | HOST      | DB   | COMMAND | TIME | STATE                           | INFO                                                           |
+----+------+-----------+------+---------+------+---------------------------------+----------------------------------------------------------------+
|  3 | root | localhost | test | Query   |    0 | executing                       | select * from information_schema.processlist where db = 'test' |
|  5 | root | localhost | test | Query   | 6111 | Waiting for table metadata lock | truncate test                                                  |
+----+------+-----------+------+---------+------+---------------------------------+----------------------------------------------------------------+
2 rows in set (0.00 sec)

mysql> select * from information_schema.innodb_trx\G
*************************** 1. row ***************************
                    trx_id: 421232684444408
                 trx_state: RUNNING
               trx_started: 2019-03-29 16:06:14
     trx_requested_lock_id: NULL
          trx_wait_started: NULL
                trx_weight: 0
       trx_mysql_thread_id: 3
                 trx_query: select * from information_schema.innodb_trx
       trx_operation_state: NULL
         trx_tables_in_use: 0
         trx_tables_locked: 0
          trx_lock_structs: 0
     trx_lock_memory_bytes: 1136
           trx_rows_locked: 0
         trx_rows_modified: 0
   trx_concurrency_tickets: 0
       trx_isolation_level: REPEATABLE READ
         trx_unique_checks: 1
    trx_foreign_key_checks: 1
trx_last_foreign_key_error: NULL
 trx_adaptive_hash_latched: 0
 trx_adaptive_hash_timeout: 0
          trx_is_read_only: 0
trx_autocommit_non_locking: 0
1 row in set (0.00 sec)

mysql> show engine innodb status\G
*************************** 1. row ***************************
  Type: InnoDB
  Name:
Status:
=====================================
2019-03-29 19:04:40 0x7f1bcc1d6700 INNODB MONITOR OUTPUT
=====================================
Per second averages calculated from the last 3 seconds
-----------------
BACKGROUND THREAD
-----------------
srv_master_thread loops: 7 srv_active, 0 srv_shutdown, 11228 srv_idle
srv_master_thread log flush and writes: 11234
----------
SEMAPHORES
----------
OS WAIT ARRAY INFO: reservation count 16
OS WAIT ARRAY INFO: signal count 10
RW-shared spins 0, rounds 27, OS waits 12
RW-excl spins 0, rounds 32, OS waits 0
RW-sx spins 0, rounds 0, OS waits 0
Spin rounds per wait: 27.00 RW-shared, 32.00 RW-excl, 0.00 RW-sx
------------
TRANSACTIONS
------------
Trx id counter 54542
Purge done for trx's n:o < 54542 undo n:o < 0 state: running but idle
History list length 53
LIST OF TRANSACTIONS FOR EACH SESSION:
---TRANSACTION 421232684445328, not started
0 lock struct(s), heap size 1136, 0 row lock(s)
---TRANSACTION 421232684443488, not started
0 lock struct(s), heap size 1136, 0 row lock(s)
--------
FILE I/O
--------
I/O thread 0 state: waiting for completed aio requests (insert buffer thread)
I/O thread 1 state: waiting for completed aio requests (log thread)
I/O thread 2 state: waiting for completed aio requests (read thread)
I/O thread 3 state: waiting for completed aio requests (read thread)
I/O thread 4 state: waiting for completed aio requests (read thread)
I/O thread 5 state: waiting for completed aio requests (read thread)
I/O thread 6 state: waiting for completed aio requests (write thread)
I/O thread 7 state: waiting for completed aio requests (write thread)
I/O thread 8 state: waiting for completed aio requests (write thread)
I/O thread 9 state: waiting for completed aio requests (write thread)
Pending normal aio reads: [0, 0, 0, 0] , aio writes: [0, 0, 0, 0] ,
 ibuf aio reads:, log i/o's:, sync i/o's:
Pending flushes (fsync) log: 0; buffer pool: 0
639 OS file reads, 99 OS file writes, 21 OS fsyncs
0.00 reads/s, 0 avg bytes/read, 0.00 writes/s, 0.00 fsyncs/s
-------------------------------------
INSERT BUFFER AND ADAPTIVE HASH INDEX
-------------------------------------
Ibuf: size 1, free list len 0, seg size 2, 4 merges
merged operations:
 insert 0, delete mark 0, delete 0
discarded operations:
 insert 0, delete mark 0, delete 0
Hash table size 34679, node heap has 0 buffer(s)
Hash table size 34679, node heap has 0 buffer(s)
Hash table size 34679, node heap has 0 buffer(s)
Hash table size 34679, node heap has 0 buffer(s)
Hash table size 34679, node heap has 0 buffer(s)
Hash table size 34679, node heap has 0 buffer(s)
Hash table size 34679, node heap has 0 buffer(s)
Hash table size 34679, node heap has 0 buffer(s)
0.00 hash searches/s, 0.00 non-hash searches/s
---
LOG
---
Log sequence number 26417867
Log flushed up to   26417867
Pages flushed up to 26417867
Last checkpoint at  26417858
0 pending log flushes, 0 pending chkp writes
17 log i/o's done, 0.00 log i/o's/second
----------------------
BUFFER POOL AND MEMORY
----------------------
Total large memory allocated 137428992
Dictionary memory allocated 133441
Buffer pool size   8192
Free buffers       7710
Database pages     482
Old database pages 0
Modified db pages  0
Pending reads      0
Pending writes: LRU 0, flush list 0, single page 0
Pages made young 0, not young 0
0.00 youngs/s, 0.00 non-youngs/s
Pages read 446, created 41, written 72
0.00 reads/s, 0.00 creates/s, 0.00 writes/s
No buffer pool page gets since the last printout
Pages read ahead 0.00/s, evicted without access 0.00/s, Random read ahead 0.00/s
LRU len: 482, unzip_LRU len: 0
I/O sum[0]:cur[0], unzip sum[0]:cur[0]
--------------
ROW OPERATIONS
--------------
0 queries inside InnoDB, 0 queries in queue
1 read views open inside InnoDB
Process ID=4492, Main thread ID=139757271107328, state: sleeping
Number of rows inserted 6, updated 0, deleted 0, read 20
0.00 inserts/s, 0.00 updates/s, 0.00 deletes/s, 0.00 reads/s
----------------------------
END OF INNODB MONITOR OUTPUT
============================

1 row in set (0.00 sec)

表明:事务存在,TRUNCATE 锁等待。

如果,kill 3 干掉这个没有 commit 的查询 SESSION,TRUNCATE 就会正常执行下去。

结论

  1. 不管怎样,应该保持事务的短小精干,快速执行和退出。
    PS:业务代码中 SELECT 之前为什么不提交的问题需要进一步检查。
  2. DDL 需要 MDL,而没有结束的事务会阻止 MDL。
    更多细节,需要更多深入了解。

#301 TeamViewer 扑街之后的远程终端解决方案

2019-03-21

从今天开始,打开 TeamViewer 就这样了。


商业用途
检测为商业用途
该软件似乎适用于商业环境。请注意:免费版仅供个人使用。
您的会话将在5分钟后终止。请登录我们的网站以获得更多信息。

商业用途
超时后连接将被阻断。
您的许可证对您与伙伴的最大会话时间有所限制,立即重新连接时将被阻断。请稍后再试或升级您的许可证。
与该伙伴的连接在07:33之前都将保持阻断。

我不喜欢下那些破解版软件,对原开发者来说,就是偷东西,总觉得有亏于人。
另一方面也不放心其安全性。

但是,这价格真心用不起。

新方案

需求

远程访问公司的工作机器

备选方案

不管什么方案,从两个内网机器想要实现通信,只能有一个双方都能连接的外网主机进行中转。

我对自己家的公网 IP 很久以前就绝望了,我打电话给电信要求分配公网 IP,之后他们说给我开了,然后我要求进入光猫最高权限,进去设置端口转发规则,不行。。。那要公网 IP 有捷豹用啊!

所以:

  1. 要么别个给免费的全套远程桌面访问解决方案(一般有些限制),比如行云管家
  2. 要么给免费的数据转发(一般也有些带宽之类的限制),或许附带专用软件。

只要能流畅使用终端,都可以接受。

在研究方案的过程中,发现了另一个好的选择:tmate.io。

tmate.io

这是 tmux 的分支。
tmux 我用过好一阵子,也很容易上手。

使用方法:

# 创建远程连接
tmate -S /tmp/tmate.sock new-session -d

# 显示 SSH 连接命令
tmate -S /tmp/tmate.sock display -p '#{tmate_ssh}'

思路

创建定时任务,定时检查并建立 tmate 连接,然后将 ssh 命令输出到文件里面。
文件位于我的坚果云文件同步目录 $HOME/Documents/Mine/,这样我在任何地方都能通过 SSH 访问公司的电脑。

/etc/cron.d/tmate

SHELL=/usr/bin/zsh
* * * * * markjour [ -f $HOME/Documents/Mine/tmate.now ] && $HOME/Documents/Mine/tmate.sh && rm $HOME/Documents/Mine/tmate.now

~/Documents/Mine/tmate.sh

#!/usr/bin/zsh

tmate -S /tmp/tmate.sock kill-session
tmate -S /tmp/tmate.sock new-session -d
sleep 3
tmate -S /tmp/tmate.sock display -p '#{tmate_ssh}' > ~/Documents/Mine/tmate.conn

我在家只需要在同步目录下创建一个名为 tmate.now 的文件,一会儿就能用上 ssh 了。

#300 Python Supervisor 的简单配置

2019-03-11
name =
script-path-and-args =
execute-path =
python-path =
[program:<name>]
; process_name=<name>       ; 默认就是 program 名称

command = python <script-path-and-args>
directory = <execute-path>  ; 执行路径
environment=PYTHONPATH=<python-path>    ; 设置环境变量,逗号隔开
; user=

; killasgroup = false       ; 没用过
stopasgroup = true          ; 杀掉子进程,文档说是包含 killasgroup
; stopsignal=TERM           ; TERM, HUP, INT, QUIT, KILL, USR1, or USR2 中的一个
stopwaitsecs = 15

autostart = true
autorestart = true          ; true, false, unexpected
; exitcodes=0,2             ; 允许的退出码,否则会进入自动重启判断
; startretries = 3          ; 重启次数

numprocs=1
numprocs_start=0

loglevel = debug            ; critical, error, warn, info, debug, trace, blather
redirect_stderr = true
stdout_logfile = /var/log/<name>.log
stderr_logfile = /var/log/<name>.log

; stdout_logfile_maxbytes=1MB
; stdout_logfile_backups=10
; stdout_capture_maxbytes=1MB
; stdout_events_enabled=false

; stderr_logfile_maxbytes=1MB
; stderr_logfile_backups=10
; stderr_capture_maxbytes=1MB
; stderr_events_enabled=false

; 没用过
; umask=022
; priority=999
; serverurl=AUTO

另外,文档中看到一处有意思的用法,配置可以这样通过参数传递给进程:

[program:example]
command=/usr/bin/example --loglevel=%(ENV_LOGLEVEL)s
  1. 简单多进程就让 supervisor 控制
  2. 进程日志由进程自己控制,supervisor 只记录本身的运行与监控日志和进程漏出来的错误日志等。

#299 Bootstrap 基础模板

2019-03-04
<!doctype html>
<html lang="zh-CN">
  <head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />

    <title>HELLO WORLD</title>

    <link
      rel="stylesheet"
      href="/static/bootstrap@3.4.1/css/bootstrap.min.css"
      integrity="sha384-HSMxcRTRxnN+Bdg0JdbxYKrThecOKuH5zCYotlSAcp1+c8xmyTe9GYg1l9a69psu"
      crossorigin="anonymous"
    />
  </head>

  <body>
    <div class="container">
      <h1>你好,世界!</h1>
    </div>

    <script
      src="/static/jquery@1.12.4/dist/jquery.min.js"
      integrity="sha384-nvAa0+6Qg9clwYCGGPpDQLVpLNn0fRaROjHqs13t4Ggj3Ez50XnGQqc/r8MhnRDZ"
      crossorigin="anonymous"
    ></script>
    <script
      src="/static/bootstrap@3.4.1/js/bootstrap.min.js"
      integrity="sha384-aJ21OjlMXNL5UyIl/XNwTMqvzeRMZH2w8c5cRVpzpU8Y5bApTppSuUkhZXN0VxHd"
      crossorigin="anonymous"
    ></script>
  </body>
</html>
  1. 样式参考:https://v3.bootcss.com/getting-started/#examples
  2. 相关文件:
  3. http://code.jquery.com/jquery-1.12.4.min.js
  4. https://github.com/twbs/bootstrap/releases/download/v3.4.1/bootstrap-3.4.1-dist.zip

#297 关于缓存

2019-02-12

什么地方需要缓存?

  • DB 查询,加上缓存可以减小数据库压力,同时提升性能。
  • 某些费时费资源的操作,加上缓存可以避免重复计算。

缓存方式

  1. 本地缓存
  2. 进程内缓存(内存)
  3. 磁盘缓存
  4. 缓存服务(redis/memcache)
  5. 数据库:有些任务可以提前进行计算,将结果存在数据库中。

本地缓存的问题是多个节点之间容易出现数据不一致的情况。我听说过 Java 的一些本地缓存组件,应该其中有一些可以做到多个节点之间的数据同步。如果要是自己实现的话,可以在服务中增加一个刷新缓存的接口调用,其中一个节点刷新缓存时,调用其他节点的刷新接口。也可以引入 MQ,避免这个调用造成的耦合和可能的性能损耗。

缓存策略

  1. 提前进行一些计算,将内容缓存起来。如有必要,可以选择合适的时间间隔进行数据刷新。
  2. DB 缓存可以由数据库中间层来做,也可以有客户端库来做,或者就在应用的数据库层中实现。
    查询时,先尝试本地缓存,再尝试缓存服务(两道缓存,避免击穿),最后再进行数据库查询。
    缓存的数据应该是这样的:
  3. 高命中(缓存命中率需要做好监控)
  4. 较少变更
  5. 尽可能保证数据变更之后(不一致问题)不会产生严重影响
    如果一致性要求很高的话,要反复确实是否必须使用缓存,如果确定的话,缓存刷新策略需要考虑清楚。

穿透

数据库没有数据,缓存也没有数据。这样的请求直接穿过缓存读数据库,给数据库照成压力。

  1. 一般是非法请求所致,对于这一部分请求应该有机制可以进行过滤掉
    恶意请求,或者高频请求
  2. 可以对没有数据的请求也进行缓存,可以给一个相对小一点的 TTL
  3. 布隆过滤器 参考:2021/03/07, 布隆过滤器
  4. 将空 Key 单独存到一个 Redis SET 中应该也可以
    比如 system:cache:empty_keys:hhmm, TTL = 123,每次用 SISMEMBER 查询当前分钟和上一分钟

击穿

某个热 Key 失效,导致大量请求打到数据库。
热 Key 应该预热,然后有一个比较大的 TTL,甚至没有过期时间。最后,通过定时刷新任务来更新这些热 Key。

雪崩

大量 Key 同时过期, 导致请求直接打到数据库,然后影响整个系统。
大面积的击穿。

  1. 比如系统刚启动的时候,批量写入大量数据,这些数据有相同的 TTL,就会同时过期。我们应该给过期时间加入一些随机,将过期时间点分散在一个区间内。
  2. 对于热 Key 的处理, 同击穿部分。

其他:

  1. 为了防止 Redis 奔溃,导致系统崩溃,应该在本地进程中也设置一个缓存。
    LocalCache -> RedisCache -> DB
  2. 为了防止数据库奔溃,数据库请求应该由一个队列来处理。
  3. 网关部分对于大量来不及处理的请求应该丢弃。
  4. 缓存应该能够按重要性划分一下级别,如果遇到问题能够快速丢弃不重要的数据
    还应该可以快速丢弃指定服务的所有缓存。
    这应该叫做缓存降级。

预热

根据之前的经验,或者开发者预判,将部分数据事先写入缓存。
如果提供相关工具,让系统维护人员能够方便快捷地管理缓存数据,能够手动介入缓存的生命周期就更好了。至少在后台提供一个 缓存预热 的按钮。

  1. 提供指定热 Key,让定时任务负责刷新。
    智能预热:给访问量大的 Key 延长 TTL, 启动定时刷新。
  2. 需要对缓存的访问有一个简单的监控,方便作为之后预热的依据。

缓存更新

另起一篇:缓存更新策略

#296 RuntimeError: Event loop is closed

2019-02-11

开发过程中发现报错:

Traceback (most recent call last):
  ...
  File "/usr/local/python3.7.3/lib/python3.7/asyncio/streams.py", line 353, in close
    return self._transport.close()
  File "/usr/local/python3.7.3/lib/python3.7/asyncio/selector_events.py", line 690, in close
    self._loop.call_soon(self._call_connection_lost, None)
  File "/usr/local/python3.7.3/lib/python3.7/asyncio/base_events.py", line 719, in call_soon
    self._check_closed()
  File "/usr/local/python3.7.3/lib/python3.7/asyncio/base_events.py", line 508, in _check_closed
    raise RuntimeError('Event loop is closed')
RuntimeError: Event loop is closed

经过检查,发现是我在代码中调用了 asyncio.run(协程方法(*args, **kwargs))
总是有些同步代码中需要调用协程方法,没有办法。

改成这样就可以了:

asyncio.get_event_loop().run_until_complete(协程方法(*args, **kwargs))

如果是在线程中就改成:

asyncio.new_event_loop().run_until_complete(协程方法(*args, **kwargs))

否则会报:RuntimeError: There is no current event loop in thread 'Thread-2'.

线程中 run_sync

RuntimeError: This event loop is already running

attached to a different loop

Traceback (most recent call last):
  File "/opt/fuckTheWorld/core/framework.py", line 194, in fuck
    value = asyncio.new_event_loop().run_until_complete(lego.get(key))
  File "/usr/local/python3.7.3/lib/python3.7/asyncio/base_events.py", line 616, in run_until_complete
    return future.result()
  File "/opt/fuckTheWorld/core/lego.py", line 30, in execute
    data = await super().execute(*args, **options)
  File "/opt/fuckTheWorld/lego/client.py", line 476, in execute
    return await conn.retry.call_with_retry(
  File "/opt/fuckTheWorld/lego/retry.py", line 51, in call_with_retry
    return await do()
  File "/opt/fuckTheWorld/lego/client.py", line 455, in _send_command_parse_response
    return await self.parse_response(conn, command_name, **options)
  File "/opt/fuckTheWorld/lego/client.py", line 494, in parse_response
    response = await connection.read_response()
  File "/opt/fuckTheWorld/lego/connection.py", line 934, in read_response
    await self.disconnect()
  File "/opt/fuckTheWorld/lego/connection.py", line 822, in disconnect
    await self._writer.wait_closed()  # type: ignore[union-attr]
  File "/usr/local/python3.7.3/lib/python3.7/asyncio/streams.py", line 359, in wait_closed
    await self._protocol._get_close_waiter(self)
RuntimeError: Task <Task pending name='Task-45' coro=<Fuck.execute() running at /opt/fuckTheWorld/core/lego.py:30> cb=[_run_until_complete_cb() at /usr/local/python3.7.3/lib/python3.7/asyncio/base_events.py:184]> got Future <Future pending> attached to a different loop

#295 Python 解析 UserAgent

2019-02-08

GitHub 上搜索到的这些项目:

ua-parser 和 user-agents 这两个库数据漂亮些,所以,就选这两个库研究研究。

ua-parser

from ua_parser import user_agent_parser

ua_string = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.104 Safari/537.36'

parsed_string = user_agent_parser.Parse(ua_string)
pp.pprint(parsed_string)

user-agents


#294 Nginx: multi_accept

2019-02-05

http://nginx.org/en/docs/ngx_core_module.html#multi_accept

Syntax: multi_accept on | off;
Default: multi_accept off;
Context: events

If multi_accept is disabled, a worker process will accept one new connection at a time. Otherwise, a worker process will accept all new connections at a time.

The directive is ignored if kqueue connection processing method is used, because it reports the number of new connections waiting to be accepted.

翻译

如果 multi_accept 禁用, worker 进程一次只接受一个连接
如果启用, 则会一次性接受所有连接。
如果连接处理方法为 kqueue, 则该指令被忽略, 因为 kqueue 会报告新连接的数量(然后好分配)。

PS: 关于连接处理方法, 参考 2017/02/07, Nginx 连接处理方法

理解

感觉有点迷糊, 那么究竟 multi_accept 的作用是什么呢?

Nginx 采用一个 master 进程和多个 worker 进程的模式工作。多个 worker 共享一个 socket (端口绑定), 当请求进来的时候, 被调度到的进程就会去 accept 连接。
multi_accept 的作用就是控制他一次拿走一个连接, 还是拿走所有等待中的连接。

这个参数可以让调度更加高效。如果请求数一直维持在一个很高的水平, 可以设置为 on。
但是在请求数不大的场景下, 那可能会导致同一时刻, 多个进程之间的负载就会不那么均衡, 总是 1C 干活, 7C 围观。