最近写了一个服务器,业务逻辑很简单,每个协议包往服务器上报数据, 每个数据包中可能有N块数据需要保存在数据库中的.显然, 这个业务逻辑是不能使用类似memcached这样的缓存的, 因为每条数据都是相对独立的, 而且必须保证每个数据都保存到数据库中.这里抛开服务器最基本的那些IO模型之类的不说,谈谈对这个服务器的几个优化步骤.
1) 最简单的处理
最简单的处理就是按部就班的,每条数据老老实实的插入到数据库中.显然, 这样做的效率是低的, 如果并发量大的时候,mysql负载变大,而服务器阻塞在数据库操作上, 导致处理连接比较慢.
2) 第一步优化:定量插入数据
这里基本的模型不变, 只是不是每个数据一来就插入, 而是缓存起来, 等到积累到了一定量才开始一起插入到数据库中.这种方式比第一种方式并不能获得太大的提高.
3) 第二步优化:开线程处理往数据库中插入数据
在这里, 多开一个线程用于往数据库中插入数据, 同时开辟了一个缓冲区, 主线程不停的往这个缓冲区中倒数据, 副线程负责将缓冲区中的数据导入数据库.用伪代码来表示, 这个优化就是这样的:
主线程:
往缓冲区A中添加数据(注意这里不需要加锁)
副线程
加锁
将缓冲区A,B的指针互换, 这样缓冲区B就指向了原来缓冲区A,A指向B缓冲区
解锁
将缓冲区B的数据导入数据库
清空B缓冲区
这样的优化效率获得了极大的提高, 主线程只需要负责与客户端之间的通信, 接收客户端的数据, 而副线程只需要将数据导入缓冲区, 不需要关心通信问题.这样,主线程就不会由于插入数据缓慢而导致接收数据和新的连接缓慢了.而由于是多线程, 主副线程之间的数据共享很容易做到, 主线程往缓冲区插入数据的时候甚至不需要加锁, 而副线程倒数据的时候只需要加锁然后把两个缓冲区的指针互调就行了(我怀疑这个加锁也是可以免去的).各司其职,又各不骚扰,简单而高效.
4)第三步优化:将向数据库导入数据的时间尽可能的平均分布
这一步与上一步大体相同, 只是在副线程倒数据的时候定一个量, 当计数器达到这个量时, 副线程休眠一阵(比如sleep一秒).这样的好处是可以将向数据库导入数据的时间尽可能的平均分布, 减小峰值时间点数据库的压力.
5)第四步优化:从数据库角度进行优化
因为服务器只需要往数据库中插入数据, 可以考虑在插入的时候将同一个表的数据缓存在一起, 然后写在一条sql语句中一起插入, 这样减少了sql语句的调用, IO减少了, 效率也提高了.我使用的是mysql, 关于mysql的优化插入数据, 可以参考
这里.
经过这几个优化步骤之后, 服务器的效率比之最开始有了极大的提高, 不仅是服务器的效率提高了, 整个系统的IO,数据库压力也减少了.
以下是分割线:
-------------------------------------------------------------------
我仔细想了一下,觉得似乎真的可以去掉加锁的步骤...
我的缓冲区是一个STL的list链表,主线程不停的往list中push_back.
而交换缓冲区的时候调用的是STL中的swap操作, 也就是交换两个链表的头结点而已.
从这里来看,确实可以省去加锁的步骤的.
--------------------------------------------------------------------
第二天,我决定还是加锁了.由于临界区内的操作并不多, 我想效率没有太大的影响.