Archive

Archive for December, 2011

2011年终总结

December 31st, 2011 No comments

说这个是总结,我觉得也不是特别贴切。不过语文很差的我就不用计较这个东西了。

2011年,对我而言主要是CDN这一块的东西,因为商用产品要正式上线。而在测试性能的时候发现诸多问题。结果导致不得不加班加点的测试、评估、修改代码。最终使得产品可以顺利的上线。并且撑过了用户高峰。当然,这个并不是我希望的最终效果。我希望的是一个可以支持大并发、平稳有效的数据发送机制,且可以有效检测恶意客户端(或者说出现错误的客户端)的这么一个service software。这也是我最近在做的事情。

2011年,在三亚的时候,老板画了一个大饼,让所有的人都幻想也许我们也可以有一个美好的未来。但是我越来越发现,或许这个只是一个永远摸不到的,或者连看都看不清楚的幻象。

2011年,有很多的同事离职,去追寻他们自己的理想。

2011年,我学到了很多,在代码方面,在构架方面。同时我也开始思考我的路到底该怎么走。

2011年,我很幸福。老婆的爱让我一直充满着对未来的坚定信心。

2011年马上就要过去了,但是一切远未结束,恰恰才刚刚开始。即将到来的2012,它不是玛雅人心中的那个年代,它是我追寻我的梦想的开始。

Categories: life Tags:

为何编译时的头文件所在的目录也需要可执行权限?

December 15th, 2011 No comments

  开发机是大家共用的,一人一个账号。刚才突然发现昨天还可以正常build的source code无法编译通过了。非常的纳闷,g++的输出如下:

common_conf.h:181:23: error: /usr/local/include/unistd.h: Permission denied
common_conf.h:182:26: error: /usr/local/include/sys/types.h: Permission denied

其实unistd.h根本不在/usr/local/include文件夹下面,第二,这个文件以及路径上都是由可读权限的。为何会出现Permission Denied呢?查看了/usr/local/目录下面的其他文件夹的属性以后,我猜是因为include没有可执行权限造成的这个错误。于是

chmod +x /usr/local/include

以后发现一切正常了。不过现在我还是不明白为何需要/usr/local/include有执行权限才可以正常编译下去,为何可读的权限不行。还有为何unistd.h不在/usr/local/include下的时候会出现读不到(这个时候是因为Permission Denied)而停止编译,但是这个文件明明可以在/usr/include下面找到的,实在有点想不明白。

Categories: mutter, programming Tags:

到底是哪个线程导致了crash呢?

December 15th, 2011 No comments

  在linux下调试确实不熟悉,这不?又上当了。

   以前同事告知,当程序crash以后,用gdb打开core文件,第一个引入眼帘线程就是出现异常的那个线程。通常情况下一个where指令就可以让你知道到底是什么位置引起的crash。

  但是今天遇到的问题却有点奇怪。事情是这样的,一个程序在测试环境中crash了。拿到了core文件用gdb打开以后,直接where,出现的call stack如下。(下面的callstack是我随意写的,只是问了说明一下大概情形)

0 memcpy() ...
1 std::string::string()
2 xmlbuild::buildnode()
....

也就是说看起来是在Buildnode函数中调用一个std::string的构造函数的时候出错了。我满以为找到问题了,但是当我看代码的时候就郁闷了。buildnode中调用string的构造函数的传入传输也是一个std::string.用gdb的p命令查看这个std::string instance发现没有异常。奇怪了,要抓狂了。我开始怀疑这个call stack是否准确了,难道gdb坏了。

  经过较长时间的瞎想乱猜,我觉得会不会是我们的程序因为catch了SIGSEGV并且在这个信号的处理函数中做了一些事情(打印一些日志说当前的进程光荣就义了之类的,然后flush在内存中的log消息到磁盘)影响了线程在gdb里面的顺序。于是我修改程序去掉了信号的处理的函数。等待再次crash的时候发现这个时候gdb中的第一线程的callstack中所显示的代码确实有问题,有个地方没有判断是否是NULL就开始操作了。

  上面导致crash的代码逻辑很容易修改,但是这个事情也告诉我。在多核的环境下,如果在信号的处理函数中还做了一些事情(我估计特别是这个事情需要再次进入内核)那么用gdb打开core文件得到的第一个线程不一定是真正导致程序crash的那个线程。现在的解决方案很简单,如果你觉得gdb打开的core文件的第一个线程怎么看都不太像是有问题的线程,那就看看其他线程吧。没准在其他的线程里面可以找到真正引起错误的元凶。

Categories: programming Tags: , ,

一个愚蠢的错误,inotify handle不可写

December 8th, 2011 No comments

以前的同事的一个模块在程序退出的会卡住,也就是pthread_join不返回。于是检查其原因。发现这个模块是一个线程类,用于监视文件系统上的文件事件。该类并没有加入退出的逻辑。该类的run函数中使用read函数来获得文件系统的消息。为了能正常退出,我添加了一些退出的逻辑。大致如下

void FileEventMonitor::run()
{//这里是在一个新的线程中运行的
	while(!mbQuit)
	{
		int r = read( inotifyHandle , buffer, buffersize );
		if( this->bQuit) //增加了判断,如果要退出的话就break
			break;
		/process the event
	}
}
void FileEventMonitor::stop()
{
	bQuit = true;
	write(inotifyHandle,"q",1);
	pthread_join();//等待线程退出
}

我天真的以为直接想inotifyHandle中写入一点数据,read函数就可以退出,然后由于bQuit被置成了true,那么线程就会退出了。结果测试的时候发现还是退不出去,而我又没有判断write的返回值,所以一开始我一直认为是其他的什么怪异的原因。知道后来加上了对write返回值的判断才发现是写失败了。不过很是奇怪的是write失败后的errno是9,也就是bad file number。

  很明显,这条路走不通了,于是我在read之前加上了select,只有当有数据的时候才去read,并且加上一个timeout时间,例如500ms(这样的话service退出也不会等待太久)。这样就解决了这个无法正常退出的问题。

代码如下:

void FileEventMonitor::monitor()
{
    fd_set fds;
    struct timeval tv;
    while(!mbQuit)
    {
        tv.tv_sec = 0;
        tv.tv_usec = 5000 * 1000; //500ms
        FD_ZERO(&fds);
        FD_SET(mEventhandle,&fds);
        int r = select( mEventhandle + 1 , &fds, NULL , NULL , &tv);
        if( r < 0)
        {
            //log error
            break
        }
        else if( r == 0 )
        {
            continue;
        }
	//process the event
    }
}

在stop函数中只需要简单的吧mbQuit置成true就可以了。

不过我现在还有点疑问,既然write的时候返回bad file number,那么为何可以使用select来检测这个handle上是否有数据可读,select不也是操作文件描述符的吗?有点不太懂。

   不过,至少退出的问题是解决了。今天记录一下这个问题,希望以后不要想当然的认为某个调用就一定会成功。

Categories: programming Tags:

程序运行时的数据存储

December 1st, 2011 No comments

  想了半天也不知道到底如何命名这个标题,暂且就这样将就吧。

程序在运行的过程中肯定会涉及到保留很多运行时数据在内存里面,服务器程序就更是这样了。在我们的服务器程序里面,有很多事需要同时服务大量的客户端,每一个客户端都会相应的数据被记录在程序的进程中,有的时候过多的程序数据对系统来说是一个压力,例如300K的session数据,每一个session数据平均有大概10K,这样的话将会有3G的内存需要,如果某一个session上有特别多的操作,那么根据协议要求将会保存更多的信息,可能多达50K到100K。这样的话对于服务来说是一个不小的负担。而且,如果程序非正常死亡了(crash掉了),那么当服务程序起来以后,以前的session数据就都不见了,这个是一个正常运行中非常严重的问题。

这么多的数据同时保存在内存中对系统要求是一个问题,而且重启以后数据会丢失,并且,在程序运行的过程中的一个短的时间内,并不是所有的session都会被访问到,例如300K sessions,可能每秒也就是1000个session上面有操作。这样的话自然想到把数据放到缓存中去,当某个session的数据需要被访问的时候再冲缓存中读出、实例化。我们的程序采用了Ice的freeze来作为缓存,freeze采用BerkeleyDB作为存储层,然后freeze自己控制数据的序列化/反序列化、刷新、储存以及实例化,同时可以设置保存在内存中的session的个数。

  采用了freeze以后,基本上解决了内存占用的问题,因为内存中只保存最近使用的一部分session的数据(例如,只保存1000个session的instance)。其他的暂时不使用的session数据就保存在Freeze(实际上是BerkeleyDB)里面。当一个是新的请求到了,并且发现需要的session不在内存中,freeze将会从DB中把数据读出并且重新实例化成为内存数据结构。然后交给程序继续处理。这样对于程序来说看起来就像所有的数据都在内存中,但是实际的内存消耗又比较小。这样看来,好处是大大地了。而实际应用下来发现确实好处多多。即便程序重新启动,保存在数据库中的session数据还是可以直接回复为内存session数据结构。这样的话在现场部署的程序升级的时候将不需要选择一个基本上没有用户操作的时间点了(当然为了将session数据丢失的概率减小,基本上还是选择一个用户操作较少的时间点)。

但是最近发生的事情却让人有点郁闷。由于每一个session的建立在时延方面是有规定的,例如(500ms)。在大多数时候这个都不是一个问题。这里我先讲一下Freeze的大体操作流程:

Freeze基本上设计三个操作,添加一个session到Freeze,从Freeze中删除一个session, 内存中session的数据更改。

1.当要向Freeze中添加一个session,或者从Freeze中删除一个session时候的操作基本差不多,首先是lock住整个Freeze(在我们的应用中实际上是Evictor,这里就说成是Freeze了 ^_^)。然后如果是添加的话就先检查这个session是否存在,如果不存在的话就把这个session添加到一个队列中去,并且标志为modified,也就是说表明这个session的数据需要保存到DB中去。如果是删除的话就直接标志这个session为destroyed,意即这个session的数据需要从DB中删除。

2.当内存中的session被修改以后,在freeze中这个session会被标志位modified。以便将来保存数据到DB中去。 而且Freeze有一个后台线程,每隔一段时间(或者被唤醒),就会把当前被置为modified的session的数据保存到DB中去,或者从DB中删除那些被标识为destroyed的session的数据。但是在这个保存的过程中Freeze(evictor)会被lock住。

好了,上面大致讲述了Freeze工作方式,在大部分时候这种方式是可以应付需求的。但是当在某一个时间段上,突然有相当多的client发出session的操作,当Freeze决定flush数据到DB的时候就会发现有很多的数据需要被存储。于是Freeze锁住自己,然后开始对每一个session的数据进行序列化,并且把新的数据保存在DB中。而且相当不幸的是这个过程尽然耗费令人发指的10秒钟,于是在这宝贵的10s中那些新的session建立的请求由于session不能及时被加入到Freeze中而超时(通常500ms就会超时),于是乎遭到一大堆的抱怨。

初看起来,解决方法也很直接:

  1. 增加Freeze flush的频率,也就是每一次写入的DB的session的数量尽量的少
  2. 尽量减少锁住Freeze的范围,让session add remove的等待的时间尽量缩短
  3. 不知道是否有无锁的方案
  4. 不知道了

也许还有其他的解决办法,还需要仔细想想

Categories: programming Tags: ,