注:以a+方式open一个文件的时候,write文件是个原子操作,多进程之间不会出现交叉写的情况,并且write大小没有限制的,不受4k大小限制。
write一个文件内核是加锁的,会保证原子执行。
ssize_t write(int fd, const void *buf, size_t count);
返回值代表写了多少字节,ret
一次write调用相当于原子地写了ret字节,因为write调用进入内核后会对fd加锁。内核并不保证一次性write写完count字节,但是写了ret大小一定是原子写的。他是内核加锁的。
rename和a+的write都为原子操作,所以当write后才可以rename.但是要注意程序中要每次重新open文件,不能一直使用同一fd.
在海量用户环境中,很容易出现高并发应用程序同时向服务器发送请求的情况,有时候会因为发送请求过于密集,致使服务器负载过重,无法及时响应请求,即使服务器能够正常响应,也常常会产生一定程度上的延时,造成应用程序的等待,如果是交互程序的话,用户的体验就是网页打开很慢。针对这一种情况,有经验的程序员一般都会考虑先将需要发送的数据存放到队列里,然后agent读取队列的数据向指定服务器发送数据。这样的好处有如下几点
1、
2、
3、
4、
5、
队列根据数据存储方式不同一般可以分为
1、
2、
读取内存数据要比读取硬盘数据快几个数量级,所以基于共享内存的队列要比基于文件的队列在数据读取方面有优势一些。但是基于共享内存的操作往往具有较高的复杂性和风险性,而且内存的容量是十分有限的,使用不当的话很容易造成out of memory。另外如果服务器意外宕机的话,共享内存队列里面的数据将全部丢失,难以保证数据的完整性和持久性。
利用文件来存放队列数据,一般不用担心空间容量的问题,因为文件数据是存放磁盘上的,而且磁盘数据是持久的,不会因为异常宕机重启后导致数据的丢失。一般多进程对同一文件同时进行读写操作的时候都需要加锁,但是在linux环境下可以通过追加写以及rename的方式不加锁对同一文件进行读写操作,而且不用担心会发生异常或者数据丢失。
应用程序可以将要发送的数据以约定的格式存放到指定文件里,这里值得注意的是必须是以a+追加写的方式写入文件,因为在linux内核中,以追加的方式在底层是自动加锁的。write底层是调用sys_write。而sys_write则是调用vfs_write,vfs_write的实现里有下面一行:
ret = rw_verify_area(WRITE, file, pos, count);
假设wrtie参数里通知要追加200字节(size_t count=200),当前尾端位移为offset,它会立即通过inode结构将offset ~ offset+200这个区域lock住。这时候任何其他想操作这个区域的进程将会阻塞,所以多个进程同时往文件里面写数据是不会造成数据的紊乱的。(直接用open函数打开,write函数写入的话,就算数据大小超过4K,原子操作也是不会出现乱序问题的,如果用带有缓存的函数,如fopen打开文件,原子操作大小限制是4k)
也许有人会问,在不对文件加锁的情况下,多进程同时往文件里面写数据也许没问题,但是其他进程如何同时从文件里读取数据呢?这里可以通过 rename这个函数来实现,其他进程可先调用rename函数将队列文件重命名为其他文件,最好以时间戳作为文件名,这样就不会产生文件覆盖的问题,然后该进程再去读取重命名后的文件数据,此时每个进程只会读取对应的唯一文件,自然不会出现问题。
通过以上方法,多进程可以在不用对文件加锁的情况下读写同一队列文件,这样能够有效提高执行效率,而且减少了程序的复杂性。