重构 用 Rails 写 API 服务,性能感觉不足,怎么办?

sefier · 发布于 2017年07月25日 · 最后由 zhu_jinlong 回复于 2017年08月06日 · 8815 次阅读
7386
本帖已被设为精华帖!

我有一个Rails 5的Web项目,同时有三个API接口,我就直接写到rails controller里面,返回json结果了。这个方案最简单,但是随着api调用达到2万的rps之后,性能急剧下降。

我没做仔细评估,考虑到数据库压力,所以首先是改写了接口的实现,全部数据都采取读写redis的方式,然后异步更新到数据库中。这个方案实现后,性能确实很大提升,但是速度依然难以接受。我查看new relic,发现非数据库非redis的底层占用时间巨大,是服务器性能不足吗?服务器集群已经加到150台了,有点不理智了。

这种情况该怎么办?有没有剥离api的好办法?因为毕竟跟rails web的基础项目有一定的关联,是不是要换个框架专门写api,还是换一种语言来做?

谢谢。

共收到 79 条回复
1楼 已删除
11524

2w rps的话估计也不缺钱了,好好请个狗驾驶重写一遍吧。

默认的 controller 一路会有很多api不需要的中间件,比如cookie之类的。可以考虑删减掉不必要的中间件。

19766

把controller的继承改成ActionController::API,应该会有可观的提升

De6df3

你确定是两万 rps?

你现在多少台服务器 (看到了 150 台),量高那个接口单次请求响应时间是多少 ms ? 量高的是写入请求么?

3287

服务器配置是怎样的?什么应用服务器,三个API的响应时间,低峰高谷的响应时间

8311

用 elixir 可能会好点?

7386

回复下:有两万多个客户端,采用了愚蠢的一秒一次的轮询方式,所以RPS确实是2万。客户端那边的逻辑不受我的技术小组控制,暂时不好动,所以用websocket或者tcp长连接目前都实现不了,所以只能优化我们负责的服务端。

服务器配置是4核心16GB,可以加到150台,还可以再加服务器,但我感觉我的方法应该有问题,按理说用不了这么多服务器的。

单次请求目前平均已经慢到500ms了,不可接受了,而且跟读写都没关系,直接用的redis存储,redis是256GB的集群实例。

我现在考虑的是重写服务端,问题是ruby类的框架真的不行吗(比如迁移到grape,rails-api或者sinatra之类的)。如果不行的话,我考虑迁移到java或者go框架,这两个框架有没有推荐的?我们的需求很简单的,三个API,操作redis,对框架功能丰富度无要求,关键是并发性能高。

感谢各位的建言献策。

De6df3
7386sefier 回复

那个接口读取的是什么数据,内容量有多大,既然是多次浪费的轮询,加缓存是否能缓解掉很多不必要的请求(因为很多时候可能都拉到的都是未变动的数据)

同时缓存还可以做几层 Rails 应用里面基于业务增加查询缓存,Nginx 或其他前端 Web 服务器上做缓存,减少请求到 App Server

这么一改动下来,估计大量的请求都被 Nginx 的缓存給扛下来了。

7386
De6df3huacnlee 回复

都是纯粹的实时请求,没办法做缓存呢。这个业务蛮特殊的,瓶颈几乎不在IO上,因为基本的逻辑都是redis逻辑,瓶颈几乎全部在每次的请求上(又或者我的观察不准确,我的业务都是请求到达 => redis读写 => 请求返回,看了new relic数据,redis时间都在几毫秒之间)。我听说node.js和go改写rails接口的例子,都有20倍提升之类的,不知道是不是该换掉?感谢解答。

136

如果业务逻辑基本都在 Redis 那就没必要用 Rails… Rails 挂了太多不必要的东西, 直接用个裸 ruby 都够了…速度绝对嗖嗖的

1232

用的什么 web server? puma 还是 unicorn? 这个不说清楚,光看框架一点用都没。另外看看 CPU 跑满了没,没跑满的话,应该还有优化空间

18855

放弃rails 用go重写~

E94ba8

如果业务逻辑还不是很复杂,要不要试一试这个 😀https://github.com/goonr/go-on-rails

9980
7386sefier 回复

你测试下单台服务器的性能 或者你负载均衡处理有点问题,150台服务器是不是都在处理请求!!! 是在不行就换成2w台 一台对应一个客户端!!这样的话你估计要和你领导一起跑路啦!!以上纯属开玩笑。

A908ae
9980hrz3424 回复

觉得这个帖子很有可能会变得很有营养,暂时先别带歪了。

9980
A908aeadamshen 回复

我也是挺好奇的,这个150台服务器是怎么配置的!!

6764
  • 150 台机器撑两万的 QPS, 平均单台 QPS 133
  • 机器配置是 4 核心 16 GB
  • 接口内部只读写 Redis, 无其他 IO, 接口响应时间超过 500 ms

我觉得这是个钓鱼贴... 或者你们需要请我, 😅

4fa3f2

招人吗?我就冲着这150台服务器来的

15420

改客户端 用websocket 或其他长链接方案 应该就不用150台吧

7386
676442thcoder 回复

1)我想知道这个能钓到什么样的鱼?

2)我单台开的是4进程,16个线程的puma,按响应速度100ms计算,RPS也就160,单台133的RPS在你看来很可笑?

3)我的2万并发,操作的是同一组数据,我是用redis队列分配的,生产者消费者模型,也就是2万个客户端同时进行读写,类似于秒杀,还带动态库存的。这里面没有任何缓存可用,也没有任何静态内容可用。

4)我感觉阁下应该没做过这么高并发的应用吧,不是说什么东西都可以水平扩展的。

15317

不应该吧,这个用rails也不应该是这效果。应该有别的问题。

6764
7386sefier 回复

哈哈, 没想到是好朋友的朋友, 看错啦~

你的问题有太多可能了, 可以邮件交流下, 42thcoder # gmail.com

188

既然有在用 Newrelic,那么:

  1. 在 Newrelic 里看 Transactions -> Slowest Average Response Time -> 右边线图里点进去看具体的 transaction trace,看瓶颈具体在哪里
  2. 如果是 Rails 本身太慢的话,去除 Rails —— 我如果要新写一个 API 服务的话,基本不太会选 Rails 或 Rails API,用纯 Ruby 写的话 overhead 小很多
  3. 如果没地方能优化了的话... 来弃暗投明加入 Elixir 的阵营吧(安利模式开启)😏
1232
7386sefier 回复

我估计你卡在 Redis 的锁了,Redis 默认没有加连接池,为了线程安全,几乎所有的地方都会用锁 https://github.com/redis/redis-rb/blob/master/lib/redis.rb#L93 特别是像你这种并发这么高的情况下,线程极度繁忙,锁个几百毫秒应该很正常

用这个吧 https://github.com/mperham/connection_pool pool size 设成线程数量就好

96

纯API,要不是特别依赖rails或者ruby,考虑一下OpenResty,配合redis,性能不会是问题

7386
32fxg 回复

看了下OpenResty,好像还真是有戏,我仔细研究下看行不行。确实依赖于现有的管理平台,也就是Rails框架,但是如果性能差异很大,多做一些剥离API的工作还是值得的。我就是太追求系统的统一性,导致这个压力越来越不可控了。

6764
7386sefier 回复

聊技术嘛, 就心平气和地聊, 不要带情绪啦~ 也是我不该调侃的, 你碰到这种问题肯定也着急, sorry~

回复下你的问题:

单台开的是4进程,16个线程的puma,按响应速度100ms计算,RPS也就160

按你给出的指标, TPS / RPS / QPS, 不管用哪个词儿了, 应该更高吧

我的2万并发,操作的是同一组数据,我是用redis队列分配的,生产者消费者模型,也就是2万个客户端同时进行读写,类似于秒杀,还带动态库存的。这里面没有任何缓存可用,也没有任何静态内容可用。

这个可以展开说说, 有伪代码就更好了, 方便大家帮你找问题. #10 跟 #21 都说得有点模糊.

我感觉阁下应该没做过这么高并发的应用吧,不是说什么东西都可以水平扩展的。

确实没做过 2w QPS 的应用, 也就做过单机三五百 QPS 吧, 😅 . 如果真碰到这种场景(2w QPS), 我会尽量在设计阶段规避.

多说两句

  • 在这种高并发的情况下, New Relic 的 Transaction Trace 非常不准, 远不如在本地做 benchmark
  • 立个 flag, 没必要换语言
4120

能否把newrelic性能部分的图形贴上来。 直觉感觉瓶颈在redis上,而不是在rails上,你加再多的应用服务器没有用。

4120

另外,你一共就三个 api,业务逻辑简单,直接读取 redis,又是 rails5,可以无痛迁到 rails-api 试一下效果,没有任何成本。

6764
13903hooooopo 回复

炮哥分享下高并发经验呗, 太期待了

newrelic在生产环境监控效果比本地bm效果好很多,可以涵盖各种环境因素。

监控效果确实挺好的, 看趋势, 看平均响应时间都很好, 但是具体到某个特定慢请求的 Transaction Trace 就很不准了, 各种环境因素太多了, 请求之间也相互影响, NewRelic 的埋点机制感觉也不是百分百靠谱

2973

解决思路还是着重两点:

  1. 优化单次请求时间
  2. 扩大并发数

感觉优化 1 的空间比较大,可以压测,给数据,试试不同的框架,语言等,首先应该把每台机器的性能压到极致,系统参数也要适当调优。

btw: 没有压测,没有火焰图,怎么谈优化呢

1232

Redis 没加 connection pool 的话,肯定有问题啊,其他啥都不用看,先把这个改了看看再说。而且楼主说了 "非数据库非redis的底层占用时间巨大",应该就是 "Ruby" 的时间,这个很大概率就是锁了

96

能达到那个级别了,直接转Java go,除非创业公司一分钱都没有

377

如果没有复杂业务逻辑和需要一些第三方gem的,只是对redis的操作,这个业务场景没必要用rails,单纯用grape这个框架都会性能倍升,并还是在ruby这个语言scope内。而rails的光是启动一个实例就加载上百个Initializer策略(实测130个),楼主描述的场景(完全实时查询)rails提供的很多缓存策略都用不上,有力使不上劲的感觉,一开始框架选型就不妥了。

713

一个请求要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 采样一下每个命令的执行时间

39楼 已删除
96

学习了

41楼 已删除
7386

好的,我先检查业务逻辑,先优化这里然后再考虑语言与框架的问题。

20690

简单算一下 2w qps 分到150台机器上 也就是单机133 qps。怎么可能怪在ruby / rails头上?

再分析一下其他的系统瓶颈吧

压一压redis集群的带宽、qps。 检查下redis指令 什么的

又或者你们在redis层是不是有啥同步机制呢?

如果楼主不能提供更多信息 基本是解答不了了

2474

😏 我只能说你们的前端一定很厉害。。。比150台服务器还值钱。。。

…………换成websocket一台机器就能解决了……

15317

请他

21568

日薪一万五那位我觉得就很靠谱,(至少开价高🤐

60a8f6

775

不优化读写逻辑肯定不行,同一组数据每秒两万读写,瓶颈可能出现在网络延迟上

1107

请虵

15420
2474kabie 回复

这个也许不一定。还是要看项目的具体瓶颈。 我司一个项目用go语言版的socket io(其实和socket io没什么关系,只是前端兼容了socket io框架),16g内存 8核, 后端用的MongoDB 现在撑2w个连接 妥妥的,我觉得可以撑到5w连接,不过后面瓶颈就是MongoDB的并发了

8cd1d3
162quakewang 回复

请他!

我默默的提升了下我的 hour rate。

4027

很有营养的话题,希望能看到最后的解决方案。 我一直以来处理并发都是堆服务器,反正服务器便宜,最便宜的几十块一个月,曾经一个项目堆到2000台阿里云最低配服务器 ,活动期结束,服务器释放掉,也没花多少钱,感觉非常划算。

15420
4027rfei 回复

会不会不太环保 😂

4027

我们小公司,也没技术大牛,但有时候活动参数人数能引爆到百万级,我唯一能想到的办法就是堆服务器了。负载均衡,数据库,对象存储,redis,消息队列都用阿里云的,磕磕绊绊几年,问题最后都能完美解决。

57楼 已删除
19766
4027rfei 回复

哇。两千台服务器用什么工具管理的?

1107 jasl 将本帖设为了精华贴 07月28日 13:21
1107

设置个精华,方便更多人看到进来讨论吧

7386

根据各位的建议,我准备先将redis的连接池加上,然后再看目前的性能表现,谢谢。

8
7386sefier 回复

我查看new relic,发现非数据库非redis的底层占用时间巨大

不是非redis嘛?估计是因为newrelic没有把redis和ruby分开,把redis也算在ruby里了:https://ruby-china.org/topics/20219#Instrumentation%20Redis

7386
8hooopo 回复

好的,多谢,我加上看看。

1232
8hooopo 回复

加连接池不是为了解决 Redis 慢的问题,而是为了解决竞争的问题吧。Newrelic 比较新的版本,Redis 应该不会把太多 Ruby 的时间算进去

8
1232tony612 回复

不是的,连接池解决的是app和db直接创建连接的开销问题。带来额外的问题是,如果池里每个连接都被占用,其他线程就会等待。

newrelic agent 3.13.0以上不需要安装了:https://docs.newrelic.com/docs/agents/ruby-agent/frameworks/redis-instrumentation#third-party-gems

1232
8hooopo 回复

我说的是他这个场景下的问题。那也比 Redis 不加连接池,一上来就等着好吧,而且连接池比线程数多就可以规避这个问题了。

67楼 已删除
22325

楼主可以尝试下:1.转换成api模式,去掉不必要的中间件,2.redis加上连接池,3.使用物理服务器压测下,看看真实的数据,感觉楼主的服务器应该都是云服务器,云服务器同等配置下在大并发下的表现是不如真实服务器的,4.使用下类似oneAPM之类的工具在线看看是哪条语句拖时间,5.把代码拉出来一个方法一个方法作分析,然后优化,以上都做了那么可以考虑做分布式的负载均衡,拆掉redis的集群,和应用跑同一台服务器最大化的减小网络带来的延时,然后前端Nginx做好分发工作,参考下现在git.oschina.net使用的分布式方案。

244

我看了半天也没找到有价值的信息,以后这种问题是不是可以先看看性能埋点的分析? 另外,这句话有点用——

我的2万并发,操作的是同一组数据,我是用redis队列分配的

如果楼主有兴趣,不妨解释一下这是什么意思?性能瓶颈的问题绝大多数就是某个点出现了(广义的)锁,导致无用的等待,所以楼主说的越详细越有用

当然,楼上几位说得很对,这个问题中,语言和框架肯定不重要,单机1000以下并发、rt时间超过100ms的业务基本不用怀疑到这些地方,虽然这种怀疑很廉价

3347

现在不是serverless挺流行的吗?上aws lambda如何。

96
7386sefier 回复

既然是轮询模式,猜测基本是几个单一的api进行轮询, 需要分析数据多久更新一次,对数据实时更新的需求;如果数据更新没有那么频繁,可以考虑在rails 前面加一个go写的代理进行缓存,然后再建立合适的缓存失效机制; 这样能够大大缓解rails端的压力,同时go的多并发能力应该能很好的处理客户端的大量轮询。

775

其实写入那么频繁,对于返回值的正确性要求没有那么高吧,完全可以写入后马上返回。一点猜测。

9861

@rfei 不扶墙只服你,能解决问题就是好办法

96
244fsword 回复

我看了半天也没找到有价值的信息

同意这句话,要解决性能问题,首先要做的就是找到性能瓶颈,然后针对瓶颈的地方进行分析,看看这个瓶颈的问题是否可以解决,而不是在这里猜测是不是没有用连接池?还是rails性能差?或者服务器的问题等等。找瓶颈的思路也不难,对于程序级别就是埋点分析每一步的执行时间,看看哪一步占的时间长。

请大家研究性能问题之前一定好好的读几遍性能分析的圣经级别的论文《Thinking Clearly about Performance》,由Oracle大牛Cary Millsap在10年左右写的通俗易懂的关于分析性能问题思路的。相信我,看完这论文之后肯定对于性能分析有一个更清晰的思路和认知。

96

各类工具的性能分析都能看到我楼上说的这篇论文中提到的解决问题的思路,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
96

@sefier 特别想知道你的业务模型到底是什么样子的,读写分布是什么样样子的,是io密集型还是cpu密集型。可以交流下,我这面压力和你差不多大。

15139

如果业务逻辑简单的话,OpenResty的收益很好。

96

ruby 做不到nodejs的 3-4k的请求量,优化一下起码也有1k,用不到150太。10-20台应该可以。

9800

真羡慕能抵达性能瓶颈的人。。

9800

1,重构客户端论询方式。 2,用java或erlang/elixr重构

96

跟楼上很多人一样,我看完所有回复没有看性能瓶颈到底在哪里。。。不过就楼主对于qps和机器数量的描述来说,问题在语言上的可能性较小。 @fsword 说的卡在某个锁的可能性大

96

不用换个语言,直接上jruby,用vert.x做,性能包你满意

96
32jimrokliu 回复

可以,用vert.x,吊打node没啥问题,我们用ruby都是一万多的并发量,轻轻松松,只不过是jruby

547643

看看是不是 同一个 key 的异步写 Redis 让大量 Redis 读被挂起了导致的 Redis 时间很长。

96

真的很想知道是哪个公司一个150台服务器的项目在上线后遇到了这种匪夷所思需要在线和网友讨论解决/profile方案的?

7386 sefier 关闭了讨论 08月06日 17:59
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册