对于自行实现的很多服务,很多人喜欢用一个专门的监控进程来监控自己的服务,为了防止监控进程也崩掉,甚至使用了经典的双进程监控。不说别的,光编写调试这个双进程就够我喝一壶的啦,对于我这样专门写bug的家伙来讲,更是一件揪心的差事。就算有一天,我不再写bug了,可以操作系统的关卡你又过不了了,难关重重。这些监控程序,不管是单进程还是双进程,其根本肯定是一个big loop,而且这个big loop中几乎都要有一个sleep,否则CPU飙升,却不见得能给真正的服务,净给了这个big loop,就算没有sleep,你的cpu核心超级多,也不在乎钱,你可以专门买一个cpu来运行你这个monitor的big loop,然而在这次检测和下次检测之间的微小空隙里面还是会有很多事情可以发生的。比如下列的双进程序列:
- p1:while(true) {:t0
- status=monitor(p2):t1
- if(status == 0) {
- start(p2);
- }
- }:t2
- p2:while(true) {:t0’
- status=monitor(p1):t1’
- if(status == 0) {
- start(p1);
- }
- }:t2’
最直接的,假设p1和p2同时运行在两个cpu上,且序列同步,在t0和t1之间,t0’和t1’之间,p1和p2都被干掉会发生什么;如果在同一个cpu上运行,事情更简单,t1和t2之间干掉p1,同时给p2发送sigkill,此时操作系统调度到p2,p2本该从t0开始运行,然而由于内核信号队列中存在sigkill,它直接就exit啦,之后谁来启动它们俩。操作系统调度是程序员和管理员控制不了的,它也根本没有导出给应用层更多的控制权,因此在单cpu情况下,你无法预料调度器会怎样调度你的monitor,因而就可以设计一个混沌的序列来不断尝试干掉你的双monitor,多cpu情况下,事情更糟糕,注定你要带着脚镣跳舞。
因此,双进程保护在逻辑上并不是很可取的,虽然大体上,对于一般的场合,大家都在用它。问题就出在不管是p1还是p2都是可以在任意时刻被干掉的,如果它们其中之一不会被干掉,事情就解决了。在linux中,init进程不能被干掉,因此可以让init来充当monitor,实际上它里面也有一个经典的big loop,可是人家因为有特权,所以多多的sleep一下也是无大碍的。事实上,很多的linux服务都使用了init进程作为了自己的monitor,你只需要编辑一下/etc/inittab就可以了,在这个inittab中,注意最后面的几行:
1:2345:respawn:/sbin/mingetty tty1
…
这个respawn的含义就是结束了再起来的意思,你只需要将后面再添加几行这样的信息即可,你的服务就可以被监控了。需要注意的是,这些行以:分割,第一个字段是一个唯一的id,往下顺延即可,具体的信息可以man inittab,定会一目了然,我就不翻译man手册了。