一、数独求解服务简介
为了服务器可以乱序返回,方便服务器并发处理。服务器可以用线程池,不用算完再做排序了。每一个请求都带有一个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台