文 : robby
在海量用户环境中,很容易出现高并发应用程序同时向服务器发送请求的情况,
有时候会因为发送请求过于密集,致使服务器负载过重,无法及时响应请求,
即使服务器能够正常响应,也常常会产生一定程度上的延时,造成应用程序的等待,
如果是交互程序的话,用户的体验就是网页打开很慢。
针对这一种情况,有经验的程序员一般都会考虑先将需要发送的数据存放到队列里,
然后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,原子操作也是不会出现乱序问题的) 。
也许有人会问,在不对文件加锁的情况下,多进程同时往文件里面写数据也许没问题,
但是其他进程如何同时从文件里读取数据呢?这里可以通过rename这个函数来实现,其他进程
可先调用rename函数将队列文件重命名为其他文件,最好以时间戳作为文件名,这样就不会产
生文件覆盖的问题,然后该进程再去读取重命名后的文件数据,此时每个进程只会读取对应的
唯一文件,自然不会出现问题。
通过以上方法,多进程可以在不用对文件加锁的情况下读写同一队列文件,这样能够有效
提高执行效率,而且减少了程序的复杂性。