C1000k 这里关注操作系统是否支持百万连接

全局最大打开文件数

cat /proc/sys/fs/file-nr,为了修改这个数值, 用 root 权限修改 /etc/sysctl.conf 文件

1
2
3
fs.file-max = 1020000
net.ipv4.ip_conntrack_max = 1020000
net.ipv4.netfilter.ip_conntrack_max = 1020000

重启系统服务

sudo sysctl -p /etc/sysctl.conf

进程限制

ulimit -n

Linux 系统的每一个进程只能最多打开 1024 个文件. 为了支持 C1000K, 你同样需要修改这个限制.

临时修改: ulimit -n 1020000

永久修改:

编辑 /etc/security/limits.conf 文件,加入以下行

1
2
* hard nofile 1020000
* soft nofile 1020000

docker容器中修改ulimit

容器中应该是无法直接修改的,可以通过修改docker daemon的设置,这种方式要重启docker服务

docker -d --default-ulimit nproc=20480:40960

或者在启动容器时,单独对其ulimit进行设置:

docker run -d --ulimit nofile=20480:40960 nproc=1024:2048 gitlab

  • nofile 指的是单个进程能够打开的最大文件数
  • file-max 是所有进程能够打开的最大文件数
  • nproc是操作系统对每个用户创建的进程数的限制

有关协程的一些问题

昨天在Flask+Celery中写了一个使用gevent的程序,大概是这样的

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
def distribute_calc_gevent(self, fundIds, start, end, stype):
from gevent.pool import Pool
from gevent import monkey
monkey.patch_all()
length = len(fundIds)
current_app.logger.info('handling fundId: {}'.format(length))
pool = Pool(size=GEVENTLETS_NUM)
def calc(fundId):
try:
plat_calc_whole_call(fundId, start, end, stype)
gc.collect()
except Exception as e:
return e, fundId
return None, fundId
# imap是惰性的,所以要list一下吧,或者迭代一下
try:
for index, res, fundId in enumerate(pool.imap_unordered(calc, fundIds)):
if isinstance(res, Exception):
if isinstance(res, StockError):
current_app.logger.error("[** CALC ERROR] fundId: {} date: {} symbol:"
" {} stock not exists error".format(res.fundId, res.date, res.symbol))
elif isinstance(res, FlowsMissingError):
current_app.logger.error("[** CALC ERROR] fundId: {} start_date: {} assets_start: {}"
" assets_end: {} missing flows".
format(res.fundId, res.calc_start, res.assets_start, res.assets_end))
else:
current_app.logger.exception(res)
pool.join()
except Exception as e:
current_app.logger.exception(e)
finally:
current_app.ssdb.close()
  • GEVENTLETS_NUM可以指定并发量
  • monkey.patch_all()如果在全局使用,就会导致整个模块的程序被patch,这不是我想要的,所以在启动task之后才patch_all
  • 使用gevent的pool,这样可以限制并发量
  • pool里面的imap是惰性的,因此需要迭代或者之类操作激活
  • 在calc函数里面不能使用current_app,因为协程的id和外部app上下文所在线程id不是同一个,所以会导致working out of context
  • 可以在calc里使用with app.app_context():来取得上下文,就可以使用current_app,但是没什么意义,所以暂时在外部获取结果的时候使用
  • 获取异常,目前没有好的办法,所以只能在迭代imap的时候来处理,如果以后有更好的操作,再记之
  • 因为并发量太大,会导致ssdb连接池出现过多连接,所以完成任务后,手动关闭连接池,否则没有显式关闭由于垃圾回收的那个原理,会导致一些连接没有关闭,再启动下一次计算会出现问题