那谁的技术博客

感兴趣领域:高性能服务器编程,存储,算法,Linux内核
随笔 - 210, 文章 - 0, 评论 - 1183, 引用 - 0
数据加载中……

对一个服务器的几步优化

最近写了一个服务器,业务逻辑很简单,每个协议包往服务器上报数据, 每个数据包中可能有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操作, 也就是交换两个链表的头结点而已.

从这里来看,确实可以省去加锁的步骤的.

--------------------------------------------------------------------
第二天,我决定还是加锁了.由于临界区内的操作并不多, 我想效率没有太大的影响.

posted on 2008-10-22 20:10 那谁 阅读(5198) 评论(18)  编辑 收藏 引用 所属分类: 网络编程服务器设计Linux/Unix

评论

# re: 对一个服务器的几步优化  回复  更多评论   

楼主要要是有例子代码就好多了,希望能够贴出代码,那样比较的直观
2008-10-22 20:17 | QQQ

# re: 对一个服务器的几步优化[未登录]  回复  更多评论   

这样的想法记录也已经很棒了~代码未必能清楚的表示作者的意图。
2008-10-22 21:03 | Xw.Y

# re: 对一个服务器的几步优化  回复  更多评论   

如果插入的数据是即时变化的,并要被其他地方(或不同客户端、不同的进程、不同的线程...)使用的话,实时性就有问题。并且如果有一个计算是相关不同的记录进行计算的话,计算的结果就可能会错误。
2008-10-22 21:29 | wind

# re: 对一个服务器的几步优化  回复  更多评论   

还有做好数据的压缩和缓存问题,有的服务器把缓存量设的太大了。
2008-10-22 21:34 | 金山毒霸2008

# re: 对一个服务器的几步优化  回复  更多评论   

加锁可以免去,因为Exchange有原子操作。
2008-10-22 22:06 | 空明流转

# re: 对一个服务器的几步优化  回复  更多评论   

@QQQ
要代码有什么用,代码往往是最没用的。。。
2008-10-22 22:06 | 空明流转

# re: 对一个服务器的几步优化  回复  更多评论   

不过我还是有个问题。如果主线程正在填充某条记录的一半的时候就被Exchange掉怎么办。
2008-10-22 22:09 | 空明流转

# re: 对一个服务器的几步优化  回复  更多评论   

为啥要用A、B两个缓冲区呢?
可以用一个循环数组data[N]来做缓存,in_index用于主线程写数据,指向下一个空位, out_index用于副线程读数据,指向下一个数据位。in_index和out_index都模N运算。
当out_index = (in_index + 1)%N时,缓冲已慢,等待。
---------
2008-10-22 22:15 | ronliu

# re: 对一个服务器的几步优化  回复  更多评论   

只对一个加锁?跟没加有区别么?
2008-10-22 22:16 | t

# re: 对一个服务器的几步优化[未登录]  回复  更多评论   

@ronliu
这个方法不错,我回头试试.
2008-10-22 22:29 |

# re: 对一个服务器的几步优化[未登录]  回复  更多评论   


@t
是的,所以我才有前面的疑问.
2008-10-22 22:30 |

# re: 对一个服务器的几步优化[未登录]  回复  更多评论   

我仔细想了一下,觉得似乎真的可以去掉加锁的步骤...

我的缓冲区是一个list链表,主线程不停的往list中push_back.

而交换缓冲区的时候调用的是STL中的swap操作, 也就是交换两个链表的头结点而已.

从这里来看,确实可以省去加锁的步骤的.
2008-10-22 22:36 |

# re: 对一个服务器的几步优化  回复  更多评论   

哦如果是这样的话,你可以使用Windows的LIST,提供了添加和删除节点的原子操作
2008-10-22 23:35 | 空明流转

# re: 对一个服务器的几步优化  回复  更多评论   

搂主觉得可以“不加锁”的想法值得商榷

首先,STL 中list swap操作的实现是两步:(以VC6 STL实现为例)
std::swap(_Head, _X._Head);
std::swap(_Size, _X._Size);
此处不能保证两步是原子操作。

其次,也与std::swap的实现有关:(以VC6 STL实现为例)
template<class _Ty> inline
void swap(_Ty& _X, _Ty& _Y)
{_Ty _Tmp = _X;
_X = _Y, _Y = _Tmp; }
不能保证编译器能把这三步操作优化成原子操作。

再次,与容器中的对象类型有关,现有的Exchange原子操作只针对int有效,浮点操作尚无原子操作(或许本人孤陋寡闻),更何况自定义类型乎?

综上所述,搂主的实现中锁操作不可少。
若要实现成锁无关的数据结构,是不是应该交换的是两个list*而不是list的内容,请参考http://blog.csdn.net/pongba/archive/2006/01/29/589864.aspx
2008-10-23 00:34 | Cox

# re: 对一个服务器的几步优化  回复  更多评论   

"而交换缓冲区的时候调用的是STL中的swap操作, 也就是交换两个链表的头结点而已." 谁说只是交换表头.
你看下边STL的源码..
template<typename _Tp>
inline void
swap(_Tp& __a, _Tp& __b)
{
// concept requirements
__glibcxx_function_requires(_SGIAssignableConcept<_Tp>)

const _Tp __tmp = __a;
__a = __b;
__b = __tmp;
}

不可能不加锁. 而且你的sleep用得也很猥琐. 要用条件变量.做一个高水标就可以了.
2008-10-23 10:30 | cui

# re: 对一个服务器的几步优化[未登录]  回复  更多评论   

各位,我想了想,还是改成加锁了...
2008-10-23 11:25 |

# re: 对一个服务器的几步优化[未登录]  回复  更多评论   

一读一写的话,循环buf就可以。以前写过一个类似的,主线程写,多个线程读,用的线程锁,性能不错。
2008-12-29 13:52 | dd

# re: 对一个服务器的几步优化[未登录]  回复  更多评论   

操作数据库失败会怎么样?如果服务器都要等待数据库返回才能进行下一步操作你这种就不行了
2009-11-19 23:30 | xu

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理