返回
软件
分类

必赢备用网址都预示着有一天摩尔定律将不会再这么夸张的进行指数化增长,需要借用wsgi

日期: 2019-11-17 15:18 浏览次数 : 134

unicorn与nginx通讯--[ruby unix socket],unicornnginx

文章来源:【龍昌博客】

 

Ruby 应用服务典型地是与一个 web 服务一同使用的,如 nginx。当用户请求你的 Rails 应用中的页面时,nginx 将请求指派给应用服务。
然而这个过程是如何完成的呢?nginx 与 unicorn 是如何通讯的呢?

最有效的一种选择是使用 unix 套接字(sockets)。让我们来看看它们是如何工作的!
在这篇文章中我们将从一个基本的套接字(sockets)开始,最后将创建一个使用 nginx 代理的简单应用服务。

 

require "socket"

server = UNIXServer.new('/tmp/simple.sock')

puts "==== Waiting for connection"
socket = server.accept

puts "==== Got Request:"
puts socket.readline

puts "==== Sending Response"
socket.write("I read you loud and clear, good buddy!")

socket.close

 

unix socket],unicornnginx 文章来源:【龍昌博客】 Ruby 应用服务典型地是与一个 web 服务一...

前言:

绝大部分工作中所采用的编程语言是 ruby,所以对 ruby 的作者松本行弘十分感兴趣,之前阅读了他的《松本行弘的程序世界》,觉得学到了不少东西,这次又找了本他的《代码的未来》来阅读。

又是WSGI ,这是我曾经比较熟悉的协议,以前针对实现了wsgi server的unicorn和uwsgi都写过源码解析的文章。  其实他们的实现也很简单,就是给flask、django这样的application传递environ,start_response 。

书中主要围绕摩尔定律飞速发展下,未来多核时代编程的一些思考。

必赢备用网址 1

首先便是加深了对摩尔定律是什么这一概念的理解,先前可能只是知道晶体管的集成程度会在十八个月翻一番,随之而来的性能、所占空间等方面会有很大的提升。但摩尔定律的发展瓶颈却不是很知道,只是按照极限的思维考虑,这么做下去一定会有一天集成度不能够再密集了。书中详细阐述了一些原因,诸如光的波长限制、保持电路绝缘性、漏电流以及散热问题,都预示着有一天摩尔定律将不会再这么夸张的进行指数化增长。

必赢备用网址 ,什么是WSGI协议,什么是WSGI Server,他们的区别是什么?

为了阐述摩尔定律的伟大性,松本行弘用了围棋与米的故事来阐述指数爆炸的效应,同时还举了一个啼笑皆非的例子,就是在二十年前他刚接触软盘时,觉得 320 kb 大小的软盘空间实在是太大了,估计一辈子都不会用完,然后时至今日,TB 级别的硬盘存储已经随处可见了。

上线的架构图很容易误导别人,乍一看有nginx这样的web服务器,又有gunicorn这样的wsgi server。  我们先说明wsgi 跟 wsgi server的关系,wsgi是个协议,是web底层跟application解耦的协议。wsgi server是自己做web服务器借用wsgi协议来调用application。 我们需要明确一点,nginx是无法直接跟flask application做通信,需要借用wsgi server。flask本身也有个web服务器是werkzeug,so 才能启动服务并监听端口。记得以前uwsgi没名气的时候,我们都在使用apache + mode_wsgi模式,apache也无法直接跟tornado通信,是借用mod_wsgi把torando做成了unix socket服务,说白了也是启动了一个服务,靠apache来转发。

再者是对进程与线程有了更新的认识。由于一直在写业务逻辑层面的东西,多线程/多进程一直都只是有所了解,但并没有实际接触过。但最近在工作中一直有听到这些词汇,恰好书中有不少解释,感觉非常合乎时机。

nginx、apache在这里只是启动了proxy的作用,那为什么不直接把uwsgi和gunicorn给暴露出去,因为nginx的静态文件处理能力极强。

一个进程包含多个线程,这些线程由于是在一个进程之下,所以便具有内存共享的特点,至于这个特点是优是劣,则取决于具体的应用场景了,共享内存可以减少数据的拷贝,但正因为多个线程同时可以访问这块区域,所以很难避免会发生冲突。

必赢备用网址 2

多进程则因没有共享的内存而不用顾虑这些,但线程通信起来十分方便,进程则就需要额外的手段进行通信。一般通信手段现在记得的,一个是管道,一个是套接字(TCP、UDP),以及 unix 专属的套接字。当然 unix 套接字的局限在于不能够跨机器运行。

WSGI怎么工作的

之前一直不晓得 socket,也就是所谓的套接字到底是个什么玩意儿,现在有种原来如此的感觉,只是进程之间通信的一种方式。

wsgi主要是两层,服务器方 和 应用程序 :

同时,多线程显然不能够跨机器运行,只能利用本机的多核特性来提升性能,但进程则不尽然,对于利用进程的程序而言,整个机房都可以看作一个多核的处理装置。

1  服务器方:从底层解析http解析,然后调用应用程序,给应用程序提供(环境信息)和(回调函数), 这个回调函数是用来将应用程序设置的http header和status等信息传递给服务器方.

正是由于这种多台计算机协作的模式,服务器集群以及云的概念得此而生。

2  应用程序:用来生成返回的header,body和status,以便返回给服务器方。

松本行弘在书中还大胆的预测了未来编程语言的趋势,根据这种多核的特性,猜测未来一定有一种多核编程语言能够充分利用这种特性,同时又具有很好的抽象来减少程序编写的复杂度。

WSGI把来自socket的数据包解析为http格式,然后进而变化为environ变量,这environ变量里面有wsgi本身的信息(比如 host, post,进程模式等),还有client的header及body信息。start_respnse是一个函调函数,必须要附带两个参数,一个是status(http状态),response_headers(响应的header头信息)。

书中还阐述了许多当下以及历史流行的编程语言,使我对许多编程语言的特性以及区别有了比较深的认识,这种感觉在阅读《松本行弘的程序世界》过程中也有体现,因为只撰写业务逻辑的缘故,我曾经一度以为编程语言并没有什么特别的区分,当然只是对那时的我而言。还有一个感触便是国内虽然有 BAT 这样改变中国甚至世界的企业,但终归都是商业型的公司,包括很多商业型的探索也只不过是技术的花样应用,几乎不太能知晓国内有什么技术型为主导的公司,像整个亚洲也只有松本行弘一人开发除了 ruby 编程语言。

像flask、django、tornado都会暴露WSGI协议入口,我们只需要自己实现WSGI协议,wsgi server然后给flask传递environ,及start_response, 等到application返回值之后,我再socket send返回客户端。

经过这些分析和预测,松本行弘十分中意以进程为基础的架构模式,因为进程交互能够不受限于一台机子,更容易扩展和提升性能,也更符合多核时代的情形,所以 ruby 在线程方面的支持可能并不那么完善,松本行弘本人也承认了这一点。

WSGI的优点、缺点是什么?

但 ruby 在网络层面的功力仍是不能小觑,即便是单进程的模式,但可以利用一些架构技巧来提升应用程序的性能,解决所谓的 C10K 问题,并且详细阐述了一些使用 ruby 作为后端语言的常用架构。如使用 nginx/apache 作前端负载均衡,下游使用多个 thin 作为 http 服务器处理请求,这种方式自然是有缺陷的,即请求是靠负载均衡服务器来进行分发的,当请求被阻塞时便可能会导致延误,甚至有些请求会被丢给发生错误的服务器而导致丢弃不处理。然后便给出了利用 unicorn 作为 http 服务器的解决方案。

优点:

使用 unicorn 的技术与现在公司的项目可以说几乎相同的架构,前端使用 nginx/apache 作负载均衡,其后接的是若干个 unicorn 的主进程,由 unicorn 的主进程来监管从 unicorn 主进程 fork 出来的子进程,这样的优点在于,请求不是直接靠分发,也就是所谓的推模式来运作,而是各个 unicorn 子进程从 unicorn master 进程处拉请求来进行处理,这个请求处理结束后再重新从 unicorn 主进程处进行获取,如此循环往复,虽然这样请求同样会在 unicorn master 进程处阻塞,但会减少请求被分发给出现问题的机器,同时由于子进程是 fork 出来的,可以获得使用父进程已经加载过的代码和框架,这样如果出现问题,马上重启即可解决问题,同时不会造成过大的影响。

多样的部署选择和组件之间的高度解耦

确实从《代码的未来》一书中学习到了很多的东西,很多书籍在阅读过后,所能汲取的知识少到甚至只有几个点而已,像这样的能够贯通很多东西的书籍确实不多,希望能够在以后的阅读历程中能够多命中这些好书,而祈祷去少踩一些大坑吧。

由于上面提到的高度解耦特性,理论上,任何一个符合WSGI规范的App都可以部署在任何一个实现了WSGI规范的Server上,这给Python Web应用的部署带来了极大的灵活性。

Flask自带了一个基于Werkzeug的调试用服务器。根据Flask的文档,在生产环境不应该使用内建的调试服务器,而应该采取以下方式之一进行部署:

GUNICORN

UWSGI

缺点:

没有

我们在wsgi层可以做什么时尚的操作:

  1. 黑白名单规则防御.
  2. 可以通过重写环境变量,根据目标URL,将请求消息路由到不同的应用对象。这意思就是说,实现一套类似nginx location proxy的规则,可以把阻塞高性能转给tornado的app. 当然这是理想化的操作.
  3. 允许在一个进程中同时运行多个应用程序或应用框架.
  4. 负载均衡和远程处理,通过在网络上转发请求和响应消息.

我们用python具体实现这个wsgi server及协议.

#xiaorui.cc

import socket
import StringIO
import sys

class WSGIServer(object):

 address_family = socket.AF_INET
 socket_type = socket.SOCK_STREAM
 request_queue_size = 1

 def __init__(self, server_address):
  # 创建一个可用的socket
  self.listen_socket = listen_socket = socket.socket(
   self.address_family,
   self.socket_type
  )
  #socket的工作模式
  listen_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  # Bind
  listen_socket.bind(server_address) #绑定地址
  # Activate
  listen_socket.listen(self.request_queue_size) #监听文件描述符
  # Get server host name and port
  host, port = self.listen_socket.getsockname()[:2]
  self.server_name = socket.getfqdn(host)
  self.server_port = port
  self.headers_set = []

 def set_app(self, application):
  self.application = application

 def serve_forever(self): #启动WSGI server服务,不停的监听并获取socket数据。
  listen_socket = self.listen_socket
  while True:
    self.client_connection, client_address = listen_socket.accept()
   self.handle_one_request() #处理新连接

 def handle_one_request(self): #主要处理函数
  self.request_data = request_data = self.client_connection.recv(1024)
  print(''.join(
   '< {line}n'.format(line=line)
   for line in request_data.splitlines()
  ))

  self.parse_request(request_data)
  env = self.get_environ()

  #给flasktornado传递两个参数,environ,start_response
  result = self.application(env, self.start_response)

  # Construct a response and send it back to the client
  self.finish_response(result)

 def parse_request(self, text): #处理socket的http协议
  request_line = text.splitlines()[0]
  request_line = request_line.rstrip('rn')
  # Break down the request line into components
  (self.request_method, # GET
   self.path,   # /hello
   self.request_version # HTTP/1.1
   ) = request_line.split()

 def get_environ(self): #获取environ数据
  env = {}
  env['wsgi.version']  = (1, 0)
  env['wsgi.url_scheme'] = 'http'
  env['wsgi.input']  = StringIO.StringIO(self.request_data)
  env['wsgi.errors']  = sys.stderr
  env['wsgi.multithread'] = False
  env['wsgi.multiprocess'] = False
  env['wsgi.run_once']  = False
  env['REQUEST_METHOD'] = self.request_method # GET
  env['PATH_INFO']   = self.path    # /hello
  env['SERVER_NAME']  = self.server_name  # localhost
  env['SERVER_PORT']  = str(self.server_port) # 8888
  return env

 def start_response(self, status, response_headers, exc_info=None): #创建回调函数.
  server_headers = [
   ('Date', 'Tue, 31 Mar 2015 12:54:48 GMT'),
   ('Server', 'WSGIServer 0.3'),
  ]
  self.headers_set = [status, response_headers + server_headers]

 def finish_response(self, result): #把application返回给WSGI的数据返回给客户端。
  try:
   status, response_headers = self.headers_set
   response = 'HTTP/1.1 {status}rn'.format(status=status)
   for header in response_headers:
    response += '{0}: {1}rn'.format(*header)
   response += 'rn'
   for data in result:
    response += data
   # Print formatted response data a la 'curl -v'
   print(''.join(
    '> {line}n'.format(line=line)
    for line in response.splitlines()
   ))
   self.client_connection.sendall(response)
  finally:
   self.client_connection.close()

SERVER_ADDRESS = (HOST, PORT) = '', 8888

def make_server(server_address, application):
 server = WSGIServer(server_address)
 server.set_app(application)
 return server

if __name__ == '__main__':
 if len(sys.argv) < 2:
  sys.exit('Provide a WSGI application object as module:callable')
 app_path = sys.argv[1]
 module, application = app_path.split(':')
 module = __import__(module) #动态加载模块
 application = getattr(module, application) #使用自省的模式加载application的WSGI协议入口。
 httpd = make_server(SERVER_ADDRESS, application)
 print('WSGIServer: Serving HTTP on port {port} ...n'.format(port=PORT))
 httpd.serve_forever()

下面是flask application的实例, 我们会发现python的常见web框架都兼容了wsgi接口,没有例外。

#xiaorui.cc
from flask import Flask
from flask import Response
flask_app = Flask('flaskapp')

@flask_app.route('/search')
def hello_world():
 return Response(
  'xiaorui.cc Golang vs python !n',
  mimetype='text/plain'
 )

app = flask_app.wsgi_app

运行方式很简单:

python webserver2.py flaskapp:app

这样一个wsgi就构成了,下次我们会借用这wsgi框架扩展成prefork wsgi server,类似gunicorn那样。   以前在wsgi做过一些application的分流,但涉及到高并发的场景下的分流,还是建议直接在nginx层面做。 现在nginx lua的编程能力越来越强,大家都在使用nginx lua做网关及入口的开发。

参考文章: