实时大数据处理性能瓶颈

时间:2022-08-06 19:28:51


1、业务数据特性
   1、数据源突然流量增大;
      对于这点,曾见有人将数据缓存到硬盘上,待流量变小时再发出去。我认为这样并不是解决问题的根本办法,因为在流量波峰的情况下,这样的串行IO会使效率更低。我的经验是,使用足够大的内存缓冲,并且提高内存的使用率,一般1Gb流量,有个10MB内存就够用了。


   2、业务处理过程的不均匀;
      业务处理过程不均匀往往是由于架构设计的不够合理造成的,耦合度和串行度过高导致的瓶颈。这种情况下,往往需要修改其架构设计,使用异步方式来解耦,并行方式替代串行方式。

 

2、内存管理
   1、内存的预先分配
      预先分配内存一般是比较好的选择,避免频繁的内存分配和回收;


   2、避免大面积的内存操作
      比如大块内存的开辟或memset,如果必须要做这两种操作,那么将这两种操作均匀地分布在处理过程中。


   3、冗余拷贝
      内存空间与对象紧耦合,每次从buffer或者FIFO出来后都需要拷贝进对象数据段中,这种是不可取的,产生冗余拷贝。偶觉得buffer或者FIFO应该起到一定的缓冲作用,它们与处理过程松耦合,处理完成后缓冲区的指针才可以下移;


   4、避免内存资源竞争
      内存锁机制往往是是数据同步的常用机制,但锁亦会带来很大的时间开销,尤其是长年累月的处理过程,所以要避免因为锁机制带来资源竞争。一个生产者与一个消费者模式时,使用环形缓冲区是一个比较好的选择。但由于业务数据的关系,往往数据间的耦合度很大,往往需要采取一定的分流策略,降低数据的耦合度;


   5、内存的清理与垃圾数据的判断
      在实际编码过程中,预先分配的内存经过一段时间后,有可能需要对内存进行清空或重置,这样可能会带来很大的处理延时,所以对内存的清理也会带来瓶颈。偶的一个经验是,不宜做大块内存的memset,可以将数据进行业务逻辑上的判断,看其是否合法,不合法的往往都是垃圾数据。不过这种做法有个前提,那就是内存排列必须均匀(固定大小),数据格式明确。

 

3、线程管理
   1、线程资源的分配
      在实时数据处理过程中,工作线程数量是预先可以确定的,所以我一般是在系统启动的过程中,按照配置分配与启动线程。


   2、线程的工作模式
      在实时数据处理中,数据会源源不断且汹涌而入。经过试验,偶得出的结论是,没有数据需要处理时,才可以短暂地sleep一下,有数据时不应该有任何停顿,更不应该处理一个数据周期后,将线程阻塞,然后下一个数据来后再唤醒。这种阻塞与唤醒机制需要很长的CPU周期,理应避免频繁地阻塞与唤醒线程。
      while(1)
      {
        if (IsBreak())
            break;
        if (!GetData())
          {
            usleep(1000*1);
            contiune;
          }
        if (!CoreProc())
           continue;
        if (!ExportData())
           continue;
      }

 

   3、线程的切换
      线程或进程一般都是由操作系统来调度的,不同的操作系统会有不同的调度策略,用户很难控制。
      线程切换我基本认为有三类:
      1、操作系统调度策略造成的,如线程的时间片到了;
         对于这类切换,用户无法避免。偶甚至认为提高线程优先级来防止线程饥饿等这些措施来保证系统的稳定性都是浮云。曾在一家公司面试时,所谓的研发部总经理提出将线程绑定CPU的办法来提高性能,我想这在理论上似乎可行,因为我在做性能优化工作以前,也这么想,并且这么做过,但这会带来巨大的CPU开销,导致这台服务器就被几个线程占用的差不多了,再想部署其他东西就很难了。所以我的理解是不要去试图用很高深的技术来改变某些东西,做好你该做好的东西就行了。


      2、人为的切换,如sleep函数;
         经过试验,偶得出的结论是,没有数据需要处理时,才可以短暂地sleep一下,有数据时不应该有任何停顿。


      3、调用了造成线程切换的函数,如I/O函数等;
         偶认为在核心处理中,在逻辑运算中不应该夹杂IO操作。如必须有IO,应该另起线程来IO,或者使用线程池技术和消息通知机制,如借鉴epoll和IOCP的思想模型。很多log虽然是线程安全的,但由于log需要随时输出,往往被夹在逻辑运算中,这样的log输出就会带来I/O问题,甚至会造成线程排队,导致核心线程效率降低。


   4、线程的颗粒度
      在实时数据处理过程中,线程一般采用流水结构,所以线程的颗粒度很必须合适。关于线程颗粒度的问题,在后续的架构设计中我们再讨论。

      

4、I/O处理
   1、I/O与核心处理异步
      I/O的速度与CPU的速度相比是龟速,如果将I/O和CPU的逻辑运算同步串行,势必会造成逻辑运算线程阻塞,大大降低核心处理速度,所以建议将I/O独立出一个线程,这个线程只做I/O操作,与核心处理分开,实现异步操作。

 

   2、按块I/O
      频繁的I/O一定会带来效率的下降,所以最好按照一定的缓冲区大小进行I/O,会大大提升I/O效率。I/O操作不外乎socket和文件,对这两种一般都适用,socket的缓冲区比较明确。不过文件IO最好使用顺序存储的方式,减少磁头寻道时间,文件操作最害怕的可能就是写随机文件了。
   
   3、阻塞式I/O
      在实时数据处理系统中,往往需要保证传输的可靠性,自接到数据后,就不能丢失。这里并不是说非阻塞式IO就不能达成这样的目标,而是在后续的大数据处理过程中,往往都需要使用强并行模式,如此使用非阻塞式IO就会造成系统复杂度大大提高。在我的经验中,IO只要是异步的,使用阻塞方式并不会造成效率的降低。
      在这里说句闲话,高并发处理一般都采用异步非阻塞式IO,因为其要支持多路同时IO,其对数据通道比较敏感,须区分是哪个链接或者通道,往往是短链接居多。而海量数据处理则对数据通道不是很敏感,其只关心数据流量,所有的数据进来,都是按照预先分配的通道进行处理,一般都是长链接过程。

 

5、逻辑处理与算法
   算法是个巨大的话题,这里我只说说我的一些小体会。
   1、静态数据的比较与查找
      这里说的静态数据一般是指系统的启动之初就装载的数据,这类的数据我一般使用XML文件来描述,所以字符串居多。如果在处理过程中,进行字符串比较,会非常地耗时。建议使用<key,value>的方式进行存储和查找,这个效率会大大提高。在不超过500万条的情况下,使用STL的map就足矣,如果再大,就使用hashmap。
      
   2、动态数据的比较与查找
      在实时大数据处理中,数据往往都是无序的,所以一般使用hash比较多。hash分开链地址法和拉链地址法,在使用中,拉链地址法往往使用的比较多。但开链地址法却为我们提供一种思路,即查找时,可以使用唯一的key一次性定位到某个offset,我想这和MapReduce有异曲同工之妙。

 

   3、避免大规模的循环
      在逻辑处理过程中,应该避免大规模的循环,尤其是在循环里再开辟内存等耗时操作。