近几个月,做了一个可以对500万数据实时排序的程序

此想法源于一个实际的需求:
微博达人目前有500万以上,每一位达人都有达人积分,积分可以通过发博,评论等活动来获取。
为了激励用户,产品需要获取每一位达人的积分排名,目前的做法是一天排序一次,排序方法如下:
1,先从数据库中以文本形式导出500万达人的积分数据
2,用sort命令对文本进行排序
3,将排序后的数据一条一条的更新到数据库中
经过日志分析,所有过程大概1个半小时,在每天的凌晨三四点钟运行,步骤1和2共占用几分钟时间,剩下的时间都消耗在数据库更新上

最开始的想法是怎样节省数据库操作的的代价,由于经验不足没有什么好的想法,再加上需求也不迫切,久而久之,这个项目就变成了个人的一个兴趣项目,在利用这个项目锻炼一下linux下C的编程能力。

初步的想法是做一个服务端,接收请求,实时返回排名

经过搜索和咨询一些同学,最终定下了数据结构采用红黑树。
红黑树是一种平衡的二叉查找树,由于它的平衡性,它的增、删、改、查的时间复杂度都是logN
N=5000000时,logN=23
维基百科对它介绍的很详细 http://zh.wikipedia.org/wiki/红黑树

经过几个月断断续续的编程,最终初步实现了功能,程序结构如下图所示


1,服务端开始运行时,会创建N(可配置)个处理线程
2,主线程接收用户请求,分发给处理线程
3,处理线程在没有任务时,挂起在一个信号量上,当主线程将任务分配给某一人线程后,会将此线程唤醒
4,唤醒后的处理线程,全权处理用户请求,包括处理业务逻辑,网络传输,关闭socket等




为了提高程序的性能,对程序进行了一些优化,优化点如下:
1,
开始的时间使用的是一个任务队列,所以的处理线程都会从任务队列取任务,这样的话,任务队列就需要加锁,任务队列就变成了系统的一个瓶颈。优化后,为每一个线程分配一个缓冲区,每一个线程从自己的缓冲区中拿任务,各个线程之间就不存在竞争关系。但这样的话,主线程要向每一个线程的缓冲区中放任务,主线程和处理线程之间就存在了竞争关系,这样每一个处理线程和主线程之间都需要一把锁来保持同步。为了防止主线程挂起在某一把锁上,每一个线程都有一个标记flag,flag=0代表此线程处于空闲状态,flag=1代表此线程处于繁忙状态。主线程在分发任务时,会首先判断flag=0的线程,当flag=0时,再调用pthread_mutex_trylock来尝试加锁,如果不成功,就选择下一个处理线程。
 
2,
一个节点的结构如下:
typedef struct rb_node
{
        unsigned int rb_parent;//父亲
        unsigned int rb_left;//左孩子
        unsigned int rb_right;//右孩子
        unsigned int score;//积分        
        unsigned int time;//// 插入时间
        unsigned int child_num:31;//以此节点为根的子树中的节点数目
        unsigned int rb_color:1;//颜色,红或黑
        #define RB_RED          0
        #define RB_BLACK        1
}daren_node;
A:正常情况下,父亲、左孩子、右孩子用指针类型,但一个指针在64位系统中是8个字节,如果用int来实现,一个节点就可以节省12个字节,12*5000000=60M
B:优化点A节省了内存使用,但这个其实并不重要。重要的是,采用int,而非指针,这样就需要我们自己来管理内存的使用,我们就可以一次性向系统申请sizeof(daren_node)*5000000个字节的内存,这样内存是连续的,大大的减少了内存碎片。代价是我们得把红黑树的算法进行改造。
C:score采用int而非float,因为float的比较运算肯定比int代价高(这个优化可以忽略)
D:插入时间字段的用途?因为用户的积分可能是一样的,为了最精确的对用户进行排名(在积分相同的情况下,插入时间晚的节点,排名在插入时间早的节点后)。
E:为什么没有UID(user_id)字段?因为UID字段并不参与运算,存下来是个浪费,但积分和UID之间的对应关系需要调用方来维护。 
 
性能分析:
自己编了个压力测试脚本,结果见用EPOLL进行压力测试 ,单核虚拟机最大TPS=6500左右,同样的环境mc的tps=7000左右
稳定性:
在某一天的晚上开启了一个服务端,在另外一台机器上配置了一个cron,此Cron每隔10秒种更新一批节点
到第二天上班时,服务依旧正常运行。
 
可靠性:
设计了500万个达人,达人的积分为1-500万,经过少量的查询与插入,达人的积分排名正确。