我有一个 Rails 5 的 Web 项目,同时有三个 API 接口,我就直接写到 rails controller 里面,返回 json 结果了。这个方案最简单,但是随着 api 调用达到 2 万的 rps 之后,性能急剧下降。
我没做仔细评估,考虑到数据库压力,所以首先是改写了接口的实现,全部数据都采取读写 redis 的方式,然后异步更新到数据库中。这个方案实现后,性能确实很大提升,但是速度依然难以接受。我查看 new relic,发现非数据库非 redis 的底层占用时间巨大,是服务器性能不足吗?服务器集群已经加到 150 台了,有点不理智了。
这种情况该怎么办?有没有剥离 api 的好办法?因为毕竟跟 rails web 的基础项目有一定的关联,是不是要换个框架专门写 api,还是换一种语言来做?
谢谢。
2w rps 的话估计也不缺钱了,好好请个狗驾驶重写一遍吧。
默认的 controller 一路会有很多 api 不需要的中间件,比如 cookie 之类的。可以考虑删减掉不必要的中间件。
回复下:有两万多个客户端,采用了愚蠢的一秒一次的轮询方式,所以 RPS 确实是 2 万。客户端那边的逻辑不受我的技术小组控制,暂时不好动,所以用 websocket 或者 tcp 长连接目前都实现不了,所以只能优化我们负责的服务端。
服务器配置是 4 核心 16GB,可以加到 150 台,还可以再加服务器,但我感觉我的方法应该有问题,按理说用不了这么多服务器的。
单次请求目前平均已经慢到 500ms 了,不可接受了,而且跟读写都没关系,直接用的 redis 存储,redis 是 256GB 的集群实例。
我现在考虑的是重写服务端,问题是 ruby 类的框架真的不行吗(比如迁移到 grape,rails-api 或者 sinatra 之类的)。如果不行的话,我考虑迁移到 java 或者 go 框架,这两个框架有没有推荐的?我们的需求很简单的,三个 API,操作 redis,对框架功能丰富度无要求,关键是并发性能高。
感谢各位的建言献策。
那个接口读取的是什么数据,内容量有多大,既然是多次浪费的轮询,加缓存是否能缓解掉很多不必要的请求(因为很多时候可能都拉到的都是未变动的数据)
同时缓存还可以做几层 Rails 应用里面基于业务增加查询缓存,Nginx 或其他前端 Web 服务器上做缓存,减少请求到 App Server
这么一改动下来,估计大量的请求都被 Nginx 的缓存給扛下来了。
都是纯粹的实时请求,没办法做缓存呢。这个业务蛮特殊的,瓶颈几乎不在 IO 上,因为基本的逻辑都是 redis 逻辑,瓶颈几乎全部在每次的请求上(又或者我的观察不准确,我的业务都是请求到达 => redis 读写 => 请求返回,看了 new relic 数据,redis 时间都在几毫秒之间)。我听说 node.js 和 go 改写 rails 接口的例子,都有 20 倍提升之类的,不知道是不是该换掉?感谢解答。
用的什么 web server?puma 还是 unicorn?这个不说清楚,光看框架一点用都没。另外看看 CPU 跑满了没,没跑满的话,应该还有优化空间
你测试下单台服务器的性能 或者你负载均衡处理有点问题,150 台服务器是不是都在处理请求!!! 是在不行就换成 2w 台 一台对应一个客户端!!这样的话你估计要和你领导一起跑路啦!!以上纯属开玩笑。
我觉得这是个钓鱼贴... 或者你们需要请我,
1)我想知道这个能钓到什么样的鱼?
2)我单台开的是 4 进程,16 个线程的 puma,按响应速度 100ms 计算,RPS 也就 160,单台 133 的 RPS 在你看来很可笑?
3)我的 2 万并发,操作的是同一组数据,我是用 redis 队列分配的,生产者消费者模型,也就是 2 万个客户端同时进行读写,类似于秒杀,还带动态库存的。这里面没有任何缓存可用,也没有任何静态内容可用。
4)我感觉阁下应该没做过这么高并发的应用吧,不是说什么东西都可以水平扩展的。
既然有在用 Newrelic,那么:
我估计你卡在 Redis 的锁了,Redis 默认没有加连接池,为了线程安全,几乎所有的地方都会用锁 https://github.com/redis/redis-rb/blob/master/lib/redis.rb#L93 特别是像你这种并发这么高的情况下,线程极度繁忙,锁个几百毫秒应该很正常
用这个吧 https://github.com/mperham/connection_pool pool size 设成线程数量就好
看了下 OpenResty,好像还真是有戏,我仔细研究下看行不行。确实依赖于现有的管理平台,也就是 Rails 框架,但是如果性能差异很大,多做一些剥离 API 的工作还是值得的。我就是太追求系统的统一性,导致这个压力越来越不可控了。
聊技术嘛,就心平气和地聊,不要带情绪啦~ 也是我不该调侃的,你碰到这种问题肯定也着急,sorry~
回复下你的问题:
单台开的是 4 进程,16 个线程的 puma,按响应速度 100ms 计算,RPS 也就 160
按你给出的指标,TPS / RPS / QPS, 不管用哪个词儿了,应该更高吧
我的 2 万并发,操作的是同一组数据,我是用 redis 队列分配的,生产者消费者模型,也就是 2 万个客户端同时进行读写,类似于秒杀,还带动态库存的。这里面没有任何缓存可用,也没有任何静态内容可用。
这个可以展开说说,有伪代码就更好了,方便大家帮你找问题。#10 跟 #21 都说得有点模糊。
我感觉阁下应该没做过这么高并发的应用吧,不是说什么东西都可以水平扩展的。
确实没做过 2w QPS 的应用,也就做过单机三五百 QPS 吧, . 如果真碰到这种场景 (2w QPS), 我会尽量在设计阶段规避。
多说两句
我觉得在楼主没把具体瓶颈分析出来之前,其他人能帮到的不多。换语言框架也不见得就能解决问题,还是得看处理高并发的设计经验。
newrelic 在生产环境监控效果比本地 bm 效果好很多,可以涵盖各种环境因素。
动态不能缓存,这个说法也未必对……
炮哥分享下高并发经验呗,太期待了
newrelic 在生产环境监控效果比本地 bm 效果好很多,可以涵盖各种环境因素。
监控效果确实挺好的,看趋势,看平均响应时间都很好,但是具体到某个特定慢请求的 Transaction Trace 就很不准了,各种环境因素太多了,请求之间也相互影响,NewRelic 的埋点机制感觉也不是百分百靠谱
解决思路还是着重两点:
感觉优化 1 的空间比较大,可以压测,给数据,试试不同的框架,语言等,首先应该把每台机器的性能压到极致,系统参数也要适当调优。
btw: 没有压测,没有火焰图,怎么谈优化呢
Redis 没加 connection pool 的话,肯定有问题啊,其他啥都不用看,先把这个改了看看再说。而且楼主说了 "非数据库非 redis 的底层占用时间巨大",应该就是 "Ruby" 的时间,这个很大概率就是锁了
同意楼上的的看法,如果 Redis 服务本身没出问题的话,我觉得可能是 Rails 应用与 Redis 之间连接出了问题,Redis 应对 2w QPS 基本是毫无压力的,而 Ruby 又是阻塞型的 IO 模型,所以可能需要更大的连接池
并发的问题,如果不是 CPU 密集型应用,基本就是 IO 的问题,与 Rails 框架本身没太大关系,去掉几个中间件是解决不了问题的,所以分析问题应该从 IO 入手,而不是动不动就换个语言
如果没有复杂业务逻辑和需要一些第三方 gem 的,只是对 redis 的操作,这个业务场景没必要用 rails,单纯用 grape 这个框架都会性能倍升,并还是在 ruby 这个语言 scope 内。而 rails 的光是启动一个实例就加载上百个 Initializer 策略(实测 130 个),楼主描述的场景(完全实时查询)rails 提供的很多缓存策略都用不上,有力使不上劲的感觉,一开始框架选型就不妥了。
一个请求要 500ms 就和 rails 框架没有关系了,因为正常的 rails 返回也是几十 ms
可以在一两台机器上采样看下时间消耗在哪个方法上。
如果在 redis(看你程序的流程好像也没有别的地方可以出问题了)
你如果是用云的 redis 集群,他上面应该有监控,看下负载情况如何
redis 的 slowlog 里面都是什么?(slowlog 没有开开一下,2w rqs, 0.1ms 对你来说都是慢查询了,这还没有计算你一个请求有多个 redis command)
比较好解决的情况就是有人误用 keys 这样的操作,去掉就好了,正常就不应该这么用
这问题可能一眼就能看出来
比较麻烦的是 rang 这种 O(S+N) 的命令,在 slowlog 里比较难找出来
这种就考虑尽量改成 O(1) get set
建议使用 datadog 这种 statsd 采样一下每个命令的执行时间
贴一下我之前给一个类似项目做的性能优化,单机目标 500RPS 跑 wrk 本机测试,单机结果 1105RPS, 这是在一台每月 200 元的 2 核 4GB 最低配云主机跑的结果:
10 threads and 100 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 8.95ms 1.59ms 54.36ms 82.69%
Req/Sec 224.23 142.93 484.00 79.00%
11171 requests in 10.10s, 3.05MB read
Requests/sec: 1105.64
Transfer/sec: 309.35KB
你要问什么架构?基于 rails-api,redis 存储,都是大路货,没什么高端科技 150 台机器...只能说楼主有钱,请我!每小时仅收费$300,保证赚回你的机器钱
简单算一下 2w qps 分到 150 台机器上 也就是单机 133 qps。怎么可能怪在 ruby / rails 头上?
再分析一下其他的系统瓶颈吧
压一压 redis 集群的带宽、qps。检查下 redis 指令 什么的
又或者你们在 redis 层是不是有啥同步机制呢?
如果楼主不能提供更多信息 基本是解答不了了
这个也许不一定。还是要看项目的具体瓶颈。我司一个项目用 go 语言版的 socket io(其实和 socket io 没什么关系,只是前端兼容了 socket io 框架),16g 内存 8 核,后端用的 MongoDB 现在撑 2w 个连接 妥妥的,我觉得可以撑到 5w 连接,不过后面瓶颈就是 MongoDB 的并发了
很有营养的话题,希望能看到最后的解决方案。 我一直以来处理并发都是堆服务器,反正服务器便宜,最便宜的几十块一个月,曾经一个项目堆到 2000 台阿里云最低配服务器,活动期结束,服务器释放掉,也没花多少钱,感觉非常划算。
我们小公司,也没技术大牛,但有时候活动参数人数能引爆到百万级,我唯一能想到的办法就是堆服务器了。负载均衡,数据库,对象存储,redis,消息队列都用阿里云的,磕磕绊绊几年,问题最后都能完美解决。
我查看 new relic,发现非数据库非 redis 的底层占用时间巨大
不是非 redis 嘛?估计是因为 newrelic 没有把 redis 和 ruby 分开,把 redis 也算在 ruby 里了:https://ruby-china.org/topics/20219#Instrumentation%20Redis
加连接池不是为了解决 Redis 慢的问题,而是为了解决竞争的问题吧。Newrelic 比较新的版本,Redis 应该不会把太多 Ruby 的时间算进去
不是的,连接池解决的是 app 和 db 直接创建连接的开销问题。带来额外的问题是,如果池里每个连接都被占用,其他线程就会等待。
newrelic agent 3.13.0 以上不需要安装了:https://docs.newrelic.com/docs/agents/ruby-agent/frameworks/redis-instrumentation#third-party-gems
楼主可以尝试下:1.转换成 api 模式,去掉不必要的中间件,2.redis 加上连接池,3.使用物理服务器压测下,看看真实的数据,感觉楼主的服务器应该都是云服务器,云服务器同等配置下在大并发下的表现是不如真实服务器的,4.使用下类似 oneAPM 之类的工具在线看看是哪条语句拖时间,5.把代码拉出来一个方法一个方法作分析,然后优化,以上都做了那么可以考虑做分布式的负载均衡,拆掉 redis 的集群,和应用跑同一台服务器最大化的减小网络带来的延时,然后前端 Nginx 做好分发工作,参考下现在 git.oschina.net 使用的分布式方案。
我看了半天也没找到有价值的信息,以后这种问题是不是可以先看看性能埋点的分析? 另外,这句话有点用——
我的2万并发,操作的是同一组数据,我是用redis队列分配的
如果楼主有兴趣,不妨解释一下这是什么意思?性能瓶颈的问题绝大多数就是某个点出现了(广义的)锁,导致无用的等待,所以楼主说的越详细越有用
当然,楼上几位说得很对,这个问题中,语言和框架肯定不重要,单机 1000 以下并发、rt 时间超过 100ms 的业务基本不用怀疑到这些地方,虽然这种怀疑很廉价
既然是轮询模式,猜测基本是几个单一的 api 进行轮询,需要分析数据多久更新一次,对数据实时更新的需求;如果数据更新没有那么频繁,可以考虑在 rails 前面加一个 go 写的代理进行缓存,然后再建立合适的缓存失效机制; 这样能够大大缓解 rails 端的压力,同时 go 的多并发能力应该能很好的处理客户端的大量轮询。
我看了半天也没找到有价值的信息
同意这句话,要解决性能问题,首先要做的就是找到性能瓶颈,然后针对瓶颈的地方进行分析,看看这个瓶颈的问题是否可以解决,而不是在这里猜测是不是没有用连接池?还是 rails 性能差?或者服务器的问题等等。找瓶颈的思路也不难,对于程序级别就是埋点分析每一步的执行时间,看看哪一步占的时间长。
请大家研究性能问题之前一定好好的读几遍性能分析的圣经级别的论文《Thinking Clearly about Performance》,由 Oracle 大牛 Cary Millsap 在 10 年左右写的通俗易懂的关于分析性能问题思路的。相信我,看完这论文之后肯定对于性能分析有一个更清晰的思路和认知。
各类工具的性能分析都能看到我楼上说的这篇论文中提到的解决问题的思路,Oracle 的自带的性能分析工具自然不必说;Openresty 作者经常会发的火焰图也是一个例子;Percona Toolkit 里面的pt-query-digest
出来的结果也是这种思路;甚至于最简单的strace
工具加上-c
的参数也会做一个花费时间从高到低的统计结果给你。大家可以先看下上面提到的论文,然后在看下我上面提到的这几个工具,自己去试用一下,然后你就知道我在说什么了。
下面给个strace -cp <pid>
做 nginx 分析的输出给大家感受下:
~# strace -cp 1926
Process 1926 attached - interrupt to quit
^CProcess 1926 detached
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
89.10 0.000711 0 2989 epoll_wait
5.89 0.000047 0 802 close
2.63 0.000021 0 552 writev
1.38 0.000011 0 3014 1305 read
1.00 0.000008 0 2203 epoll_ctl
0.00 0.000000 0 2036 write
0.00 0.000000 0 100 2 open
0.00 0.000000 0 6 stat
0.00 0.000000 0 98 fstat
0.00 0.000000 0 3 mmap
0.00 0.000000 0 3 munmap
0.00 0.000000 0 394 ioctl
0.00 0.000000 0 65 pread
0.00 0.000000 0 414 readv
0.00 0.000000 0 12 sendfile
0.00 0.000000 0 394 socket
0.00 0.000000 0 394 394 connect
0.00 0.000000 0 829 21 recvfrom
0.00 0.000000 0 1 shutdown
0.00 0.000000 0 272 setsockopt
0.00 0.000000 0 397 getsockopt
0.00 0.000000 0 2989 gettimeofday
0.00 0.000000 0 1 1 futex
0.00 0.000000 0 298 accept4
------ ----------- ----------- --------- --------- ----------------
100.00 0.000798 18266 1723 total
@sefier 特别想知道你的业务模型到底是什么样子的,读写分布是什么样样子的,是 io 密集型还是 cpu 密集型。可以交流下,我这面压力和你差不多大。
跟楼上很多人一样,我看完所有回复没有看性能瓶颈到底在哪里。。。不过就楼主对于 qps 和机器数量的描述来说,问题在语言上的可能性较小。 @fsword 说的卡在某个锁的可能性大
可以,用 vert.x,吊打 node 没啥问题,我们用 ruby 都是一万多的并发量,轻轻松松,只不过是 jruby