GitLab系列3 Unicorn

3,816 阅读5分钟

GitLab 为什么需要 Unicorn

上一回我们主要讲解了 GitLab-workhorse 组件的智能代理功能,从这一回起将开始介绍最核心也最复杂的组件:Unicorn(GitLab Rails),上次也说了这个组件主要处理动态网页和 api 接口


此篇文章先介绍 Unicorn 的作用,光是这个就能讲很多内容了


Unicorn 是使用多进程模型的 Ruby web 服务器,遵循 Rack 协议。如果要类比 java web 开发技术栈的话,Rails 应用相当于 Spring MVC 框架应用,Unicorn web 服务器相当于 tomcat

GitLab 的 Rails 应用程序(即 gitlab-ce)是在 Unicorn 服务器内运行的,使用 Unicorn 的原因是: Unicorn 能为 Rails 应用提供并发处理客户端请求的能力,并且提供了更强的容错处理

Unicorn 的多进程模型能很好地利用服务器多核 CPU 的资源,以提供更好的并发能力。当 Unicorn 启动时,Unicorn 的主进程,即 master 进程,会以 fork 的方式创建一系列 worker 进程。Unicorn 通过一个 master 进程来管理多个 worker 进程,其中 master 进程不负责处理客户端的 HTTP 请求,多个 worker 进程监听同一组套接字以处理客户端请求


实际上,worker 进程可能会挂掉或者超时(超时是指,如果 master 进程发现某个 worker 进程耗费太长时间在处理一个请求,master 进程将以发送信号(SIGKILL, kill -9)终止 worker 进程)

# unicorn_stderr.log 日志,以下表示id 为 10 的 worker 的进程超时,master 进程杀掉后又重启了新进程,重启前后 pid 是不一致的
[2015-06-05T10:58:08.660325 #56227] ERROR -- : worker=10 PID:53009 timeout (61s > 60s), killing
[2015-06-05T10:58:08.699360 #56227] ERROR -- : reaped #<Process::Status: pid 53009 SIGKILL (signal 9)> worker=10
[2015-06-05T10:58:08.708141 #62538]  INFO -- : worker=10 spawned pid=62538
[2015-06-05T10:58:08.708824 #62538]  INFO -- : worker=10 ready

不管 worker 进程会以哪种方式终止,master 进程总会创建全新的 worker 进程替代原 worker 进程,整个过程不会丢弃用户的请求

gitlab-ce 本身是内存泄漏的应用。由于 Unicorn 在运行过程会 fork 大量的 worker 进程,内存泄漏会显现在长时间运行的进程中,如 worker 进程(master 进程因为不频繁处理用户请求而几乎没有内存泄漏的现象)。而 Unicorn 本身并没有提供自动重启 worker 进程的功能,为解决这个问题就出现了 unicorn-worker-killer ,具体查看 github.com/kzk/unicorn…

GitLab 使用了 unicorn-worker-killer 以让这些进程的内存泄漏得以管控:Unicorn worker 进程在每处理 16 个请求后将进行内存自检,如果 worker 进程占用的内存大小超过了预设定的值,Unicorn master 进程将会自动将此 worker 进程替换掉,整个过程不会影响任何请求的处理

# unicorn_stderr.log 日志,以下表示 worker 进程的当前占有内存超过约 250M,于是对此 worker 进程执行重启替换的操作
[2015-06-05T12:07:41.828374 #125918]  WARN -- : #<Unicorn::HttpServer:0x00000002734770>: worker (pid: 125918) exceeds memory limit (256413696 bytes > 254802235 bytes)
[2015-06-05T12:07:41.828472 #125918]  WARN -- : Unicorn::WorkerKiller send SIGQUIT (pid: 125918) alive: 23 sec (trial 1)
[2015-06-05T12:07:42.025916 #117565]  INFO -- : reaped #<Process::Status: pid 125918 exit 0> worker=4
[2015-06-05T12:07:42.034527 #127549]  INFO -- : worker=4 spawned pid=127549
[2015-06-05T12:07:42.035217 #127549]  INFO -- : worker=4 ready

综上所述,GitLab 使用 Unicorn 的目的:

  1. 充分利用服务器多核 CPU 以并发处理客户端请求
  2. 可用性:一个进程的异常不会致使整个 GitLab 应用瘫痪;
  3. 管理 Rails 应用的内存泄漏

当然它也带来了一些问题:

  1. 使用多进程就不得不面临吃内存的难题,同时使得 unicorn 的 worker 进程数量较受限制
  2. 多进程阻塞式 IO 难以接受慢客户端造成的性能损失(想象所有的 worker 进程都正在处理慢客户端,如果客户端还在慢慢地读取 worker 进程准备好的响应信息,那 worker 进程就没办法处理下一个请求),因此一般情况下需通过反向代理服务器(如 nginx 服务器,或 gitlab-workhorse 等)才能解决慢客户端的问题(worker 进程把处理好的响应消息交给反向代理服务器缓冲区,由反向代理服务器继续和客户端慢慢纠缠,自己继续处理下一个请求,这样 Unicorn 的吞吐量自然就不会受影响了)


  3. Unicorn 的设计与 GitLab 的 git-over-http/https 业务不兼容,即通过 HTTP/HTTPS 对 Git 存储仓库进行访问(git clone/push 等)。git-over-http/https 本身是相对比较耗时的操作,而 unicorn 服务器如果为了满足此业务而调大请求的超时参数显然是不合适的

附录

参考链接