某高并发项目流程记录

 

 

代码框架

 

框架图如下,对于该图有如下声明:

  1、来自某HTTP SERVER项目,对其中部分内容做了层次处理

  2、没有写nginx层,入口即HTTP SERVER接收数据层(如tornado)

  3、最右边的IO结束层,包括了MYSQL和其它IO操作(这个容易被遗忘),如HTTP请求,写文件等。

  4、系统采用短连接,在SERVER与CLIENT之间建立HTTP请求

  5、只是一个简图,并非完全一致

 

 

由上图可知:

1、高并发项目中,需要使用数据队列做数据削峰处理

2、数据流程以IO开始,以IO结束。中间的处理流程中尽可能不使用IO处理

3、做项目前必须对每个业务的数据量级有一个清楚的认知,对于数据库的增删改查的执行量也必须有一个清楚的认知。

 

队列使用说明

 

1、禁止将同一个队列重复使用。

  将处理过的数据重复写进原来的队列,会造成以下不利影响:

  1、数据的处理顺序发生变化

    如队列里依次进入了两种不同类型的数据:data1,data2。处理的时候依次也取出了data1和data2,但是由于data1会重复进入到队列里,造成了data2比data1先处理完;也许会认为这种处理并不影响处理结果,但是如果data1和data2之间存在严格的时间先后关系,在data1重复进入队之前,队列里进入了data2-1(实际应用时,可能在一秒之内多了近千条数据),且data2-1与data2是一类数据,这时data1和data2就出现了不一致的问题,实际处理时间点为data2-1的数据早于了data1。

  2、此刻的数据状态,被早前的数据状态覆盖

     假设server准备做状态处理,且状态处理分为一次处理和二次处理。考虑到状态数据刚处理完,重新写回队列后,会产生一个不利的影响,就是新的状态(还未做过处理的状态)和旧的状态(已经做过一次处理的状态)同时在一个队列里,这种情况有可能会造成旧的状态将新的状态覆盖。

  3、增加排除队列拥堵问题的难度。

             如果队列只有单一的消费者和生产者,可以快速定位到底是生产者还是消费者出了问题。如果将处理完的数据重新写回队列,会造成该种数据的生产速度增加了一倍,无形中自己给自己的队列造成了数据压力,不利于分析数据入口的数据压力级别。

     另外,如果队列后面的业务处理存在瓶颈,无法快速处理数据队列中的数据,而某些数据又需要重新写入原来的队列,则会造成更复杂的排查问题。

 

2、队列的使用方式

错误使用实例:

正确使用方式:

 

 

2、增加队列长度监控

  知道队列长度后,才能知道项目中的数据流压力,可以实时的增加生产者或者消费者的进程数

  开源的很多zabbix监控平台只是监控插件的整体性能,如redis IO,redis memory等等,这块需要自己实现。

 

3、根据实际需要创建队列

  应根据数据的意义创建队列。

    如:有的数据要求可靠性高,有的数据不要求高可靠性,这时就可以根据实际情况分为两类数据,高可靠性数据针对实际需求做可靠性保护。

    如:有的数据要求实时性处理,立刻返回结果;有的数据不要求实时处理,给一个默认结果即可。

  当数据量级不大的时候,可以将多个业务合并成一个业务,数据队列也可以合并成一个队列。

  

4、深入理解队列的使用目的

   首先在项目中有的数据要求百分百可靠,有的数据不要求百分百可靠。其次在项目中有的数据要求一次能取多条,有的数据一次只能取走一条。一次获取多条数据,可以组成mysql的批量操作。

简单说说几种队列:

       rabbitmq的ack机制保证了队列数据的百分百可靠,但是rabbitmq无法实现一次获取几百条数据,虽然可以通过全局变量来实现,但是不推荐使用。

    redis:内存队列会因为断电出现数据丢失,但是redis数据队列,支持一次获取几百条数据

    kafka队列:磁盘队列数据不会丢失,吞吐量大,支持消费者组,消费位移

    

 

 

 

 

3、业务说明

 

1、纯内存操作业务

  从上面的框架结构中可以看到,框架实现慢的地方,主要集中在两端的IO操作中。通过将IO操作分到数据流的两端,业务操作就是纯粹的数据整理和逻辑处理过程,这样就能保证数据处理的最快速化。

 

2、与IO操作结合在一起的业务

  与IO操作结合在一起的业务,必须独立一个流程分支,否则会因为IO操作过慢,导致其后的流程堵塞,大量数据堆积在队列中。

  如下图所示,如果业务2中存在大量的IO操作,如HTTP请求或者数据库查询操作,而队列1中存在大量的数据需要放到队列2和队列3中,以便快速的入数据库。这时就会因为业务2的大量IO操作,导致队列2和队列3里缺少数据,或者没有数据,而数据队列1中的数据会越来越多。这就是典型的由于IO操作阻塞引起的系统瓶颈,导致队列1的数据不能及时到队列2和队列3

 

 

3、依靠客户端实现IO操作和其它慢操作。

  上面讨论到了IO操作导致的数据库缓慢的问题,在一个高并发系统中,如果每一个客户端都会导致server先去执行查询,然后做数据插入,则会给服务端造成大量的压力,这时最好的选择是将查询操作的实现交给客户端自己确认,服务端自己只做插入操作,这样服务器的实现最快。

  在实际应用中,或许会因为业务的问题,在实现上有一定难度,但将一个或者几个操作移动到客户端,对服务器而言是达到了n倍和几个n倍的优化。

 

 

 入口数据说明

  入口数据,即C/S架构中客户端与服务器之间的数据请求和回复。

 

1、利用内存做快速数据处理

  如客户端登录操作,如果依赖mysql数据库的查询操作,则会受限于数据库的查询速度,可以采用REDIS记录做判定,保证REDIS与MYSQL的一致来解决问题。

 

2、对客户端上报区分实时数据与非实时数据

  客户端的请求,分为实时数据和非实时数据。实时数据需要速度返回,需要利用到内存记录,给客户端做快速回复,同时将客户端的请求丢到后端队列,做二元分流处理即可;非实时数据可以使用队列来做削峰操作。

  除此之外,还需要对客户端的请求或者上报做处理上的分类,如客户端的一个请求到达服务器后,需要知道那一部分行为是立刻给客户端的,那一部分是可以放在队列里以后处理的。如一个客户端的心跳达到后,需要立刻将客户端需要取走的数据给客户端,但是有关客户端的心跳状态处理,服务器可以直接丢入队列中延后处理,这样即不影响并发的速度,也不影响状态的显示。

 

3、通讯安全

  1、自定义通讯协议格式和加密措施

    如protobuf + 自定义CRC 或者protobuf + 自定义sha1

  2、防止被抓包后,客户端重复的上报

    UID + 请求序号,配合REDIS窗口

 

4、分离文件传输与数据传输

  在本项目中定义,文件传输发送的数据量级大,数据传输的量级小。具体的划分界限并没有很明确。

 

  WEB资源下载:

    依赖nginx本身提供下载连接

  文件上报:

    通过http协议上报文件,文件内容上报后直接保存为文件,然后将文件路径传输给后面的业务,由业务负责根据路径处理数据。

              这里仅仅限于小文件上报,且文件上报频率不高(或者短时间高频率)的应用场所。对于大文件,长时间高频率上报的文件,建议实现一个单独的服务(或者单独一个服务器),用于实现该目标。

 

 5、深刻理解通讯的目的。

    对客户端而言通讯的目的是获取数据,上报数据和更新数据;对服务端而言通讯的目的是收集数据,更新数据。在没有达到目的的时候,应该尽可能避免不必要的IO操作。

    在某些时候,开发者没有理解业务的目的时,为了避免错误,在每一次业务通讯时都生成了一个IO操作(如mysql更新操作),在实际应用时应该尽可能避免这些问题,所以在开发前了解业务,领会业务的每一个操作流程是很有必要的。

    简而言之:

      在定义通讯业务时更新操作走更新接口,插入操作走插入接口。这类会造成server数据变更的接口(造成服务器会执行IO操作的,如数据库更新,插入),只有在达到目的时才执行,而不是客户端随便执行一个接口都造成了一条IO操作,在达到目的前可以更多的使用内存来做逻辑的一些判定

     如:
      LOGIN (登录)- 内存判断

       REGISTER(注册) - 数据库操作  -》 INSERT

                      REFRESH(更新) - 数据库操作  -》 UPDATE

        

 

      根据上图可知,在客户端登录的时候,如果之前没有做过insert操作,则需要先做insert操作,同时将客户端信息记录到内存中,这样下次就不用继续做insert操作。

      根据上图可知,在做客户端信息更新的时候,需要先做一个内存的状态判断,只有当客户端信息之前已经记录到mysql才能做update操作。

      在业务实现中,IO类操作往往比较慢,为了避免客户端的一些日常操作(比如点开客户端就产生了一个登陆操作),导致数据库不断的执行一些意义不大的IO操作,将IO操作做一个内存判断的限定,这样只需要保证内存上的数据是正确的即可。客户端如果想要强制要求服务器刷新其信息,则通过专门的刷新接口上报,其余日常操作依靠内存做回复,或者当客户端被从服务器业务中卸载了,重新连接时才需要做IO类操作。

 

 

REDIS数据统计

 

1、统一规划好数据的命名方式

  对KEY   VALUE类型,定义好头部,如客户端产品信息KEY:PRODUCT:{UUID},

  对数据队列客户端任务队列:TASKLIST:{UUID}

  对于一种状态的数据,如KEY是字符串,VALUE是数字的数据,使用ZSET来管理,方便统一删除。

  统一的命名方式,能够在使用界面工具时,快速分类内存,也方便统计内存的使用量

 

  一种推荐的KEY使用方式:
    key前缀:key版本号:key字符串


  该格式来自Django内部的KEY编码规范:
    key前缀可以:代表业务模块
    key字符串:(代表key实际用途的内容)

               

  注意避开将具有唯一性的单词当做key的头部,如UUID

    

2、统计各个结构的数量

使用总量统计

  对每个结构,统计使用的个数,这样有助于了解内存的使用率。尤其是REDIS队列,集合,有序集合。KEY_VALUE,HASH类型的数量直接决定了内存的使用多少;REDIS队列的长度反应了生产者和消费者能力,REDIS队列的长度也反应了系统处于不同压力下的内存需求;集合和有序集合的大小决定了访问集合的方式和数据获取时的速率。

 

使用频率统计

  统计每秒(分钟)的SET和GET次数,可以在做系统分析时了解系统的数据是偏向于读还是偏向于写,以及是否会达到REDIS上限等。

  统计队列的写入和读出速率变化,可以了解业务的压力情况,比如白天和晚上的变化等。

  统计使用次数,还可以得到服务器的热点数据

 

 

3、REDIS数据的降级使用

  想要充分使用REDIS就需要考虑到REDIS数据丢失后的处理方式。由于每种数据有每种数据的特性,因此想要实现降低使用,首先就必须知道有哪些REDIS数据,针对不同的REDIS数据做不同的处理,如KEY可以从配置文件获取,KEY可以从数据库获取,队列的数据还可以不做处理,等等。

  除了以上的针对固有结构的数据降级外,某些逻辑上的操作,也会涉及到数据降级的问题。比如执行某个逻辑时数据没了,是需要重新根据逻辑重新添加生成,还是根据预先配置的条件,从数据库读取等。

  实际应用中最麻烦的使用是,数据入了REDIS队列后,如何保证数据的高可靠性。一般是参考类似es,hadoop中的translog来实现。

  

4、REDIS数据替换mysql

  对于频繁更新的数据,可以将数据的更新记录,全部写到REDIS中,然后通过一定策略写到数据库中,而不是实时的将变更写到数据库中。数据库落地保证了项目重启后的状态恢复,REDIS变换保证了快速变更时的显示实时性,同时减少了数据库的压力。

 

5、REDIS内存策略

  

 

平台监控

  在实际布置时,业务系统在用户主机上,很可能无法获取用户的布置环境,这时一旦出现问题,或者遇到运行瓶颈,就很难处理问题。另外在某些容器云的布置环境里,也会遇到无法获取容器环境的问题,或者说在一些saas环境里,开发者是无法解除到系统环境,这个时候在开发阶段,针对针对整个系统设计一套白盒测试流程,是有助于排查系统问题。

  关于监控指标,这里不说,因为本阶段,暂时没有理清应该做哪些指标监控。

 

 

参数修改

  配置参数的修改,只根据记忆记录部分,很多参数不方便获取了。

 

1、系统参数修改

  不记得了。。可以参考es的启动修改,es对这方面的要求更高,使用的IO也不少,基本可以等位替换

  1、增加最大的文件打开句柄数,以及相关的配置

  2、tcp协议参数修改。基本是一些协议级修改,如增加syn队列什么的

  

 

2、mysql参数修改

  1、适当增加mysql的最大连接数 

  2、配置Mysql,使得可以使用的CPU核心数增加(默认为一)

  3、增加mysql的读写缓冲区大小,排序换冲区等,mysql有一些全局内存参数和针对单个连接的参数

  。。。。

  mysql什么主从搭建就不说了。

  另外说一句,存储过程无法主从同步,小心存储过程导致的性能降低,除非必要情况,否则还是别用了。

 

3、nginx参数修改

  1、开启senfile模式,压缩模式

  2、增加反向代理的失败尝试次数

  3、修改读写的缓冲区

  4、扩大发送和接收类的最大字节数等

 

4、

 

 

 

 

 

 

 

  

 

posted @ 2021-11-15 15:25  dos_hello_world  阅读(140)  评论(0)    收藏  举报