Sudoku

一、数独求解服务简介

 

为了服务器可以乱序返回,方便服务器并发处理。服务器可以用线程池,不用算完再做排序了。每一个请求都带有一个id

压测数据在recipes/sudok/test1

做成库还是服务:
(1)服务是跨进程边界,带来一些格外开销,跨机器
(2)服务可以跨语言,只要网络协议明确都能实现客户协议,不需要提供多个版本语言的sudoku求解
(3)各个团队自己维护,无依赖

sudoku是cpu密集型无状态服务,一类代表性服务

二、并发模型和测试工具

5、单线程事件循环
8、单线程事件循环做i/o+线程池做计算
9、多个事件循环,每个客户端会固定分到某一个线程
11、多个事件循环处理I/O+线程池做计算
5、可以处理client并发连接,client的请求在server是顺序执行的,不能使用多核
8/9/11可以使用多核,8/11在同一个client也会利用多核,线程池会并行处理请求

系统过载时候不同的表现?

(1)内存会有重启。如果使用线程池,I/O线程很快,很快把数据都进来,扔给线程池计算,线程计算跟不上I/O速度,会堆积在线程池数据队列上。导致内存使用飙升

(2)I/O写数据会堆积

(3)如何监控和保护?遇到情况服务做好降级,不要崩溃 server_prod.cc

程序负载测试:
(1)本地测试:不跟服务器连接,把sudoku求解当作一个库,直接调用函数测试,测试出来就是程序的基线base line 

(2)batch client:4万个请求一次性发给服务端,等服务器返回

(3)测试服务器的最大容量。发现服务器端cpu可能没有跑满,服务器与client距离越长,网络延迟越大服务器cpu使用率越低。因为很多时间请求在流水线飘。pipeline可以使网络始终有数据在传递
一共有多少个并发连接, in-fly的请求数多少

(4)stress testing只发送请求,pipelines长度是无穷,不去等到response在发送第二个。看服务器在巨大压力下能不能撑住

(5)性能测试,包括容量,延迟,延迟测试中位数,百分位数。为了定server的工作容量

三、过载保护

1.server存在线程池过载

server端启动:./sudoku_solver_hybrid 2 4

client load test启动:./sudoku_loadtest ~/recipes/sudoku/test1000 127.0.0.1 40000,单个客户端,每秒发送4W个请求 40000qps

client端看见:in-fly:未接收到的响应在不断增加

server端看见:top -p -H pid,server端内存在不断增加

说明问题:io线程的处理能力高于计算线程的处理能力,造成速度不匹配。io线程线程把数据读下来,把请求解析出来往线程池放。

http://192.168.244.131:9982/可以看到一些系统信息

查看http://192.168.244.131:9982/sudoku/stats,server requests_per_second,server端每秒只能处理约13000(1.3万)请求1.3W qps,但是client端4W qps。

如果把client端停下来,发现server端I/O使用率将为0,线程池的使用率还是100%。说明数据堆积I/O与线程池间的队列上

线程池数据计算完成。cpu使用率变为0,。看到的server内存不是泄露,已经释放了。只是内存分配器没有归还操作系统。看到系统过载后,如果不加以措施内存会一直涨上去

2.server无线程池过载

server端启动:./sudoku_solver_hybrid(server端无线程池)

client端启动:不是测试性能,测试程序负载时候的表现。

(1)只发送不接受 -r选项
(2)发送和接收不同步。./sudoku_stress 127.0.0.1,默认发送接近无穷请求,以阻塞的方式发送。另一个线程读响应。client I/O线程比较快,发送的请求数与接收响应数量差不多,说明server没有过载

(1)./sudoku_stress 127.0.0.1 200000(20W),client发送完成,几秒内会收response。client 无-r, server RES内存没有增加
(2)./sudoku_stress 127.0.0.1 200000 -r,client -r,server处理是阻塞的,处理完成一个才处理下一个。server RES内存上涨,用户态发送缓冲区增加,因为client不接受响应

测试:./sudoku_stress 127.0.0.1 300000 -r,测试30W个请求

server端处理完成CPU使用率变为0,RES内存月30M(如果客户端发送30W request,server内存30M,因为每个respinse是100K)

http://192.168.244.131:9982/pprof/memstats 查看那服务器的内存

第二部:client,ctrl+c停止

http://192.168.244.131:9982/pprof/memstats 查看那服务器的内存

server端内存RES降下来

server端内存放到 heap free list里面,是30M

http://192.168.244.131:9982/pprof/releasefreememory 可以强制释放一下

1.第一种过载。tpc/sudoku_stress,没有线程池情况下的过载。 client只发送不接受,由于非阻塞网络库的关系,server在用户态发送缓冲区堆积很多数据

server启动:启动1个线程池,./sudoku_solver_hybrid 1

client启动:./sudoku_stress 127.0.0.1 300000,client接收response

现象:server会出现短时过载,server端短时内存会上涨,存在线程是的过载更需要保存,即使把client断掉,I/O进程已经收到请求还是回放到队列中一直存这,一直占用sever端资源

过载保护:

如果client发送大量请求,并且不接收?

有线程池关系大一点,有线程池请求是异步处理,IO线程读取request扔到内部队列,请求在线程池输入队列堆积起来,并且client不接受数据,server端发送缓冲区堆积

表现server端占用一大段内存,只要client不关闭连接。非阻塞I/O本质缺点

如何做过载保护?

(1)限定线程池队列长度,如果线程池队列长度大于100W,server响应server too busy

    if (req.puzzle.size() == implicit_cast<size_t>(kCells))                                         
    {                                                                                               
      bool throttle = boost::any_cast<bool>(conn->getContext());                                    
      if (threadPool_.queueSize() < 1000 * 1000 && !throttle)                                       
      {                                                                                             
        threadPool_.run(std::bind(&SudokuServer::solve, this, conn, req));                          
      }                                                                                             
      else                                                                                          
      {                                                                                             
        if (req.id.empty())                                                                         
        {                                                                                           
          conn->send("ServerTooBusy\r\n");                                                          
        }                                                                                           
        else                                                                                        
        {                                                                                           
          conn->send(req.id + ":ServerTooBusy\r\n");                                                
        }                                                                                           
        stat_.recordDroppedRequest();                                                               
      }                                                                                             
      return true;                                                                                  
    } 

(2)为什么这里是5M,线程池所有client共享,高水位对于一个client来说,一个client每个response都是100个字节,如果有5W条response没有读,认为过载了。

server端某一个conn 用户态发送缓存大于5M

[1]更新高水位回调为10M,此时server进入throttle状态

[2]设置writeComplete

结果:

[1]在throttle状态下,如果这个conn的发送缓冲区发送完毕,重新设置高水位回调5M

[2]如果达到10M最高阀值,关闭conn

  void highWaterMark(const TcpConnectionPtr& conn, size_t tosend)                                   
  {                                                                                                 
    LOG_WARN << conn->name() << " high water mark " << tosend;                                      
    if (tosend < 10 * 1024 * 1024)                                                                  
    {                                                                                               
      conn->setHighWaterMarkCallback(                                                               
          std::bind(&SudokuServer::highWaterMark, this, _1, _2), 10 * 1024 * 1024);                 
      conn->setWriteCompleteCallback(std::bind(&SudokuServer::writeComplete, this, _1));            
      bool throttle = true;                                                                         
      conn->setContext(throttle);                                                                   
    }                                                                                               
    else                                                                                            
    {                                                                                               
      conn->send("Bad Request!\r\n");                                                               
      conn->shutdown();  // FIXME: forceClose() ?                                                   
      stat_.recordBadRequest();                                                                     
    }                                                                                               
  }                                                                                                 
                                                                                                    
  void writeComplete(const TcpConnectionPtr& conn)                                                  
  {                                                                                                 
    LOG_INFO << conn->name() << " write complete";                                                  
    conn->setHighWaterMarkCallback(                                                                 
        std::bind(&SudokuServer::highWaterMark, this, _1, _2), 5 * 1024 * 1024);                    
    conn->setWriteCompleteCallback(WriteCompleteCallback());                                        
    bool throttle = false;                                                                          
    conn->setContext(throttle);                                                                     
  } 

四、负载均衡

[一]一个服务器处理的请求数量是有限的,需要多个服务器启动多个进程处理。

1.负载均衡器以库的形式,client library

2.反向代理

3.旁路式

4.client library + load blance组合

举例子:负载均衡器可能有多个, client library,连接到10的load balance,10个load balance连接到100个sudoku server,两级分发。比如client有1000个,

(1)如果没有load balance这层,会有10W个连接到sudoku server,每个sudoku server有1000个连接。

(2)如有load balance,每个sudoku server只有10个连接,前面是1000个client连接10个load balance,总共是1W+1千个,但是延迟会增加

[二]连接级别的反向代理,TR不管协议,TR可以根据s最少的连接数。

请求级别负载均衡。连接上的负载可能是很不均衡的,有的连接上请求比较多,有的连接比较空闲,按照连接数做负载均衡,可能出现不怎么均衡的情况

连接负载均衡在网络层来做,4层或者3层

[三]负载均衡的策略,请求的负载均衡在请求来的时候,往哪一个服务器发

1.随机选,(随机数种子,时间,ip地址,mac地址,进程pid)

2.轮询round robin,首次发送的第一个是随机的,单机运行好的随机算法在多机的情况要考虑,种子如何选

3.高级策略要把机器的负载考虑进来,如果有多个load balance,大家请求一个来都看见某一个服务器负载最轻,10个load balance都向最近负载的server发请求,一下子把它变为最重的。负载均衡器间没有通讯,或者来不及通讯,一下子把最轻的压垮掉

4.还要考虑服务器是否过载,虽然现在3号服务器最近,但是已经达到容量了,服务器再来请求会过载,过载可能会宕机,内存使用太多被杀掉,或者它的用户请求过来,延迟较大,影响用户体验

[四]client->nginx->sudoku server,长短连接,连接数由nginx管理,sudoku server只负责处理业务,nginx给fastcgi给sudoku server

五、如何进一步适应生产环境

放在生产环境中,产品级服务

1.心跳协议,容错故障转移,升级。sudoku给client发,周期性发。有了心跳可以在不影响client客户服务情况下,重启进程,web界面将s1停止心跳,client两个10s没有检测到心跳,知道s1处于下线状态,虽然连接还连着,为什么不能断开连接,因为还有client发来的请求,如果直接断开连接,可能造成某个请求丢失。s1停止心跳,client认为s1死掉了,client新请求不会向s1发送。当s1在web界面显示rps变为0,可以直接将s1进程杀掉,s1重启。client自动重试,

2.故障转移故障转移,地址变为一个动态配置的,client访问服务地址是可以动态修改,新启动s3,告诉client此时有一个新服务s3,在不重启client的情况下,client可以连接s3.s1可以停止心跳,然后把s1进程关掉服务转移。对client有要求能解析地址,用ip:post不一定可以,使用host:port,host的dns解析没有通知功能。命名服务

3.8核机器,每个线程10krps,8个线程是8Wrps,10K不是cpu使用100%情况下,而是在可以接受的latency情况。每日的峰值,daily peak 1000qps,需要 100cores,如果是8cores机器,需要13台

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值