我正在开发一个python脚本,它监视目录(使用libinotify)获取新文件,并为每个新文件执行一些处理,然后将其复制到存储服务器。我们使用的是NFS挂载,但有一些性能问题,现在我们正在使用FTP进行测试。看起来FTP使用的资源远少于nfs(负载总是低于2,nfs高于5)。
我们现在遇到的问题是在TIME_WAIT状态下保持打开的连接数量。存储在等待时间内具有约15k连接的峰值。
我想知道是否有某种方法可以重新使用以前的连接进行新的转移。
任何人都知道有没有办法做到这一点?
由于
答案 0 :(得分:1)
这是一个新的答案,基于对前一个的评论。
我们将使用单个TCP套接字,并通过交替发送每个文件的名称和内容(netstrings)来发送每个文件,所有文件都在一个大流中。
我假设Python 2.6,双方的文件系统使用相同的编码,并且您不需要大量并发客户端(但您可能偶尔需要,例如,两个 - 例如,真正的客户端,以及测试员)。而且我再次假设你有一个模块filegenerator
,其generate()
方法注册inotify
,排队通知,yield
一个接一个。
client.py:
import contextlib
import socket
import filegenerator
sock = socket.socket()
with contextlib.closing(sock):
sock.connect((HOST, 12345))
for filename in filegenerator.generate():
with open(filename, 'rb') as f:
contents = f.read()
buf = '{0}:{1},{2}:{3},'.format(len(filename), filename,
len(contents), contents)
sock.sendall(buf)
server.py:
import contextlib
import socket
import threading
def pairs(iterable):
return zip(*[iter(iterable)]*2)
def netstrings(conn):
buf = ''
while True:
newbuf = conn.recv(1536*1024)
if not newbuf:
return
buf += newbuf
while True:
colon = buf.find(':')
if colon == -1:
break
length = int(buf[:colon])
if len(buf) >= colon + length + 2:
if buf[colon+length+1] != ',':
raise ValueError('Not a netstring')
yield buf[colon+1:colon+length+1]
buf = buf[colon+length+2:]
def client(conn):
with contextlib.closing(conn):
for filename, contents in pairs(netstrings(conn)):
with open(filename, 'wb') as f:
f.write(contents)
sock = socket.socket()
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
with contextlib.closing(sock):
sock.bind(('0.0.0.0', 12345))
sock.listen(1)
while True:
conn, addr = sock.accept()
t = threading.Thread(target=client, args=[conn])
t.daemon = True
t.start()
如果你在Windows上需要超过200个客户端,在Linux和BSD上有100个客户端(包括Mac),在不太好的平台上有十几个客户端,你可能想要使用事件循环设计而不是线程设计,使用{{ Linux上有1}},BSD上有epoll
,Windows上有IO完成端口。这很痛苦,但幸运的是,有一些框架可以为您提供一切。两种流行(和非常不同)的选择是Twisted和gevent。
关于kqueue
的一个好处是,你今天可以编写线程代码,并通过一些简单的更改将其转换为基于事件的代码,如魔法。
另一方面,如果你最终想要基于事件的代码,那么从一开始就学习和使用框架可能会更好,所以你不必处理{{的所有繁琐的部分。围绕gevent
循环并循环,直到您收到完整的消息并干净地关闭等等,然后编写您关心的部分。毕竟,上面一半以上的代码基本上都是每个服务器共享的东西的样板,所以如果你不必写它,为什么还要费心呢?
在评论中,你说:
这些文件也是二进制文件,因此如果客户端编码与服务器编码不同,我可能会遇到问题。
请注意,我以二进制模式(accept
和recv
)打开了每个文件,故意选择了一个可以处理二进制字符串的协议(netstrings),而不试图将它们解释为字符或处理嵌入式NUL字符作为EOF或类似的东西。而且,虽然我使用'rb'
,但在Python 2.x中不会进行任何隐式编码,除非您提供'wb'
字符串或给它基于语言环境的格式类型,我都不知道我在做。 (请注意,在3.x中,您需要使用str.format
而不是unicode
,这会改变一些代码。)
换句话说,客户端和服务器编码不会进入;你正在进行二进制传输,与FTP的I模式完全相同。
但是,如果你想要相反的话,为目标系统自动传输文本和重新编码呢?有三种简单的方法可以做到:
使用第三个选项,假设文件将采用默认文件系统编码,更改的客户端代码为:
bytes
在服务器上:
str
默认情况下,with io.open(filename, 'r', encoding=sys.getfilesystemencoding()) as f:
contents = f.read().encode('utf-8')
函数也使用通用换行符,因此客户端会将任何内容转换为Unix风格的换行符,服务器将转换为自己的本机换行符类型。
请注意,FTP的T模式实际上不进行任何重新编码;它只进行换行(以及更有限的版本)。
答案 1 :(得分:0)
是的,您可以重复使用ftplib
的连接。你所要做的就是不要关闭它们并继续使用它们。
例如,假设您有filegenerator
方法向generate()
注册的模块inotify
,将通知排队,并逐个yield
:< / p>
import ftplib
import os
import filegenerator
ftp = ftplib.FTP('ftp.example.com')
ftp.login()
ftp.cwd('/path/to/store/stuff')
os.chdir('/path/to/read/from/')
for filename in filegenerator.generate():
with open(filename, 'rb') as f:
ftp.storbinary('STOR {}'.format(filename), f)
ftp.close()
我有点困惑:
我们现在遇到的问题是在TIME_WAIT状态下保持打开状态的连接数量。
听起来你的问题并不是你为每个文件创建一个新连接,而是你永远不会关闭旧文件。在这种情况下,解决方案很简单:只需关闭它们。
或者,或者你试图并行完成所有这些,但是没有意识到这就是你正在做的事情。
如果你想要一些并行性,但不是无限制的,你可以轻松地,例如创建一个包含4个线程的池,每个线程都有一个打开的ftplib
连接,每个都从队列中读取,然后是一个刚刚推入该队列的inotify
线程。