zoukankan      html  css  js  c++  java
  • 用红黑树实现500万数据的动态排序

    近几个月,做了一个可以对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万,经过少量的查询与插入,达人的积分排名正确。
     
     
  • 相关阅读:
    c# 大白话告诉你Thread的Sleep和Join的区别
    c# winform richtextbox控制每行颜色 + 滚动条始终滚动到最底部
    c# winform禁止窗口多开
    winform窗口关闭,进程没有关掉的解决办法
    System.Threading.Timer定时器使用注意事项
    c# socket 心跳 重连
    javascript 从对象数组中 按字段/属性取最大值或最小值
    codesmith设置mysql的连接字符串
    .net桌面程序或者控制台程序使用NLog时的注意事项
    vue获取不到后端返回的响应头
  • 原文地址:https://www.cnblogs.com/hxdoit/p/3359787.html
Copyright © 2011-2022 走看看