前言

前面我们讲了Nginx的三节内容,主要是基本操作以及应用场景,在Nginx的最后一节,主要讲一下OpenRestry(Nginx的扩展)实现API网关限流及登录授权的过程!

OpenResty

OpenResty 是一个通过 Lua 扩展 Nginx 实现的可伸缩的 Web 平台,内部集成了大量精良的 Lua 库、第三方模块以及大多数的依赖项。用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关。

安装

  1. 下载安装包
    https://openresty.org/cn/download.html
  2. 安装软件包

tar -zxvf openresty-1.15.8.3.tar.gz

分布式专题-高性能的Web容器之Nginx04-Nginx的扩展-OpenRestry_lua

cd openrestry-1.15.8.3

分布式专题-高性能的Web容器之Nginx04-Nginx的扩展-OpenRestry_nginx_02

./configure [默认会安装在/usr/local/openresty 目录] --prefix= 指定路径

分布式专题-高性能的Web容器之Nginx04-Nginx的扩展-OpenRestry_nginx_03

make && make install

分布式专题-高性能的Web容器之Nginx04-Nginx的扩展-OpenRestry_分布式_04

  1. 可能存在的错误,第三方依赖库没有安装的情况下会报错,则可根据日志错误信息做相应的安装
  • yum install -y readline-devel
  • yum install -y pcre-devel
  • yum install -y openssl-devel
  1. 安装完成

分布式专题-高性能的Web容器之Nginx04-Nginx的扩展-OpenRestry_分布式_05

看到自定义目录下的是否有文件输出,有输出,且install时没有报错,说明安装成功了~
安装过程和 Nginx 是一样的,因为他是基于 Nginx 做的扩展

OpenRestry版HelloWorld

cd /data/program/openresty/nginx/conf

修改nginx.conf
分布式专题-高性能的Web容器之Nginx04-Nginx的扩展-OpenRestry_openrestry_06
nginx.conf

location / {
  default_type	text/html;

  content_by_lua_block {
     ngx.say("hello world");
   }
}

在/data/program/openresty/nginx/sbin 目录下执行.nginx 命令就可以运行,看到 helloworld
(注:我使用的linux1:192.168.200.111机器安装的openresty)
分布式专题-高性能的Web容器之Nginx04-Nginx的扩展-OpenRestry_nginx_07

OpenRestry的基本操作

创建目录

或者为了不影响默认的安装目录,我们可以创建一个独立的空间来练习,先到在安装目录下创建 demo 目录,安装目录为/data/program/openresty/demo

mkdir demo

然后在 demo 目录下创建两个子目录,一个是 logs 、一个是 conf
分布式专题-高性能的Web容器之Nginx04-Nginx的扩展-OpenRestry_openrestry_08

创建配置文件

在conf目录下创建nginx.conf文件:

worker_processes 1;
error_log logs/error.log;

events {
     worker_connections 1024;
}

http {
  
  server {
    listen 80;
    location /add {
     # 引入lua模块
      content_by_lua_block {
        local args = ngx.req.get_uri_args();
        ngx.say(args.a+args.b);
      }
    }
    location /sub {
      content_by_lua_block {
        local args = ngx.req.get_uri_args();
        ngx.say(args.a-args.b);
      }
    }
  }
}

分布式专题-高性能的Web容器之Nginx04-Nginx的扩展-OpenRestry_nginx_09
注意:这里执行的nginx运行命令,是在/data/program/openresty/nginx/sbin/.nginx 文件,而不是之前已经单独安装的Nginx目录下执行

到/data/program/openresty/nginx/sbin/执行:./nginx -p /data/program/openresty/demo 【-p 主要是指明 nginx 启动时的配置目录】
分布式专题-高性能的Web容器之Nginx04-Nginx的扩展-OpenRestry_lua_10

效果演示
加法效果
分布式专题-高性能的Web容器之Nginx04-Nginx的扩展-OpenRestry_lua_11
减法效果:
分布式专题-高性能的Web容器之Nginx04-Nginx的扩展-OpenRestry_分布式_12

脚本优化

优化问题的思考:
分布式专题-高性能的Web容器之Nginx04-Nginx的扩展-OpenRestry_nginx_13
如图所示,容错性很差!没有检查机制,并且逻辑都写在一个主配置文件不便维护与管理,因此:

  • 第一步:公共方法抽离
    在刚才创建的nginx.conf配置文件下,同级目录创建lua目录,使用lua脚本存放加法与减法的方法
    分布式专题-高性能的Web容器之Nginx04-Nginx的扩展-OpenRestry_运维_14
    配置add.lua
local args = ngx.req.get_uri_args();
ngx.say(args.a+args.b);

配置sub.lua

local args = ngx.req.get_uri_args();
ngx.say(args.a-args.b);
  • 第二步:
    配置params.lua
# 定义了一个模块
local _M = {}

# 定义了一个方法 ,并且会把参数传进来
function _M.is_number(...)

	local arg = (...);
	local num;
	# 遍历参数
	for i,v in ipairs(arg) do
		num = tonumber(v);
		# 是否为整形
		if nil = num then
			return false;
		end
	end
	return true;
end

return _M;
  • 第三步:
    配置check.lua
# 添加一个模块的引用
local param =require("params");

# 获取请求参数
local args = ngx.req.get_uri_args();

# 判断参数,调用函数
if not args.a or not args.b or not params.is_number(args.a,args.b) then
	ngx.exit(ngx.HTTP_BAD_REQUEST);
	return;
end
  • 第四步:
    修改nginx.conf:
worker_processes 1;
error_log logs/error.log;

events {
     worker_connections 1024;
}

http {

  # 导入包
  lua_package_path '$prefix/lua/?.lua';
  # 代码自动编译
  lua_code_cache off;

  server {
    listen 80;
    # 正则匹配
    location ~ ^/api/([-_a-zA-Z0-9]+) {
      # 访问模块:做检查
      access_by_lua_file lua/check.lua;
      content_by_lua_file lua/$1.lua;
    }
  }
}

分布式专题-高性能的Web容器之Nginx04-Nginx的扩展-OpenRestry_openrestry_15
最终的目录结构是这样的!

  • 优化后的demo测试
    注意:测试地址为:192.168.200.111/api/…
    分布式专题-高性能的Web容器之Nginx04-Nginx的扩展-OpenRestry_openrestry_16
    正常情况:
    分布式专题-高性能的Web容器之Nginx04-Nginx的扩展-OpenRestry_分布式_17
    输入错误,报错bad_request,同之前脚本设定的
    分布式专题-高性能的Web容器之Nginx04-Nginx的扩展-OpenRestry_lua_18

小节

我们刚刚通过一个 helloworld 的简单案例来演示了 nginx+lua 的功能,其中用到了ngx.say 这个表达式,通过在 content by lua block 这个片段中进行访问;这个表达式属于 ngxlua 模块提供的 api, 用于向客户端输出一个内容。

OpenRestry库文件的使用

通过上面的案例,我们基本上对 openresty 有了一个更深的认识,其中我们用到了自定义的 lua 模块。实际上 openresty 提供了很丰富的模块。让我们在实现某些场景的时候更加方便。可以在 /openresty/lualib 目录下看到;比如在 resty 目录下可以看到 redis.lua、mysql.lua 这样的操作 redis 和操作数据库的模块。

使用Redis模块连接Redis

worker_processes 1;
error_log  logs/error.log;

events { 
    worker_connections 1024;
}
http {
  lua_package_path "$prefix/lualib/?.lua;;"; 
  lua_package_cpath "$prefix/lualib/?.so;;"; 

  server {
		location /demo {
			content_by_lua_block {
				local redisModule=require "resty.redis";
				# lua 的对象实例
				local redis=redisModule:new();	
				redis:set_timeout(1000); 
					ngx.say("=======begin connect redis server");
				#连接 redis
				local ok,err = redis:connect("127.0.0.1",6379);	
				if not ok then
					ngx.say("==========connection redis failed,error message:",err);
				end
				ngx.say("======begin set key and value"); 
				ok,err=redis:set("hello","world"); if not 
				ok then
					ngx.say("set value failed");
					return;
				end

				ngx.say("===========set value result:",ok); 
				redis:close();
			}
		}
	}
}

演示效果

首先启动linux1的redis,在运行nginx前,我们看到没有任何数据
分布式专题-高性能的Web容器之Nginx04-Nginx的扩展-OpenRestry_nginx_19
到nginx 路径下执行 ./nginx -p /data/program/openresty/redisdemo
分布式专题-高性能的Web容器之Nginx04-Nginx的扩展-OpenRestry_运维_20
在浏览器中输入:http://192.168.200.111/demo 即可看到输出内容
分布式专题-高性能的Web容器之Nginx04-Nginx的扩展-OpenRestry_分布式_21
并且连接到 redis 服务器上以后,可以看到 redis 上的结果(通过刚才的配置写入的数据此时已经存入redis中)
分布式专题-高性能的Web容器之Nginx04-Nginx的扩展-OpenRestry_运维_22
redis 的所有命令操作,在 lua 中都有提供相应的操作 .比如 redis:get(“key”)、redis:set()等

网关

通过扩展以后的,在实际过程中应该怎么去应用呢?一般的使用场景: 网关、 web 防火墙、缓存服务器(对响应内容进行缓存,减少到达后端的请求,来提升性能),接下来重点讲讲网关的概念以及如何通过 Openresty 实现网关开发

上古服务配置
分布式专题-高性能的Web容器之Nginx04-Nginx的扩展-OpenRestry_lua_23
网关的引入

  • 按照服务组件进行统一抽象
    分布式专题-高性能的Web容器之Nginx04-Nginx的扩展-OpenRestry_运维_24
  • 针对不同的客户端来实现不同的api网关
    分布式专题-高性能的Web容器之Nginx04-Nginx的扩展-OpenRestry_lua_25

网关的概念

从一个房间到另一个房间,必须必须要经过一扇门,同样,从一个网络向另一个网络发送信息,必须经过一道“关口”,这道关口就是网关。顾名思义,网关(Gateway)就是一个网络连接到另一个网络的“关口”。

那什么是 api 网关呢?

在微服务流行起来之前,api 网关就一直存在,最主要的应用场景就是开放平台,也就是 open api; 这种场景大家接触的一定比较多,比如阿里的开放平台;当微服务流行起来以后,api 网关就成了上层应用集成的标配组件

为什么需要网关

  • 鉴权
  • 限流
  • 灰度发布
  • 分流
  • 日志记录

对微服务组件地址进行统一抽象

API 网关意味着你要把 API 网关放到你的微服务的最前端,并且要让 API 网关变成由应用所发起的每个请求的入口。这样就可以简化客户端实现和微服务应用程序之间的沟通方式

Backends for frontends

分布式专题-高性能的Web容器之Nginx04-Nginx的扩展-OpenRestry_运维_26
当服务越来越多以后,我们需要考虑一个问题,就是对某些服务进行安全校验以及用户身份校验。甚至包括对流量进行控制。 我们会对需要做流控、需要做身份认证的服务单独提供认证功能,但是服务越来越多以后,会发现很多组件的校验是重复的。这些东西很明显不是每个微服务组件需要去关心的事情。微服务组件只需要负责接收请求以及返回响应即可。可以把身份认证流控都放在 API 网关层进行控制

灰度发布

在单一架构中,随着代码量和业务量不断扩大,版本迭代会逐步变成一个很困难的事情,哪怕是一点小的修改,都必须要对整个应用重新部署。 但是在微服务中,各个模块是是一个独立运行的组件,版本迭代会很方便,影响面很小。

同时,为服务化的组件节点,对于我们去实现灰度发布(金丝雀发布:将一部分流量引导到新的版本)来说,也会变的很简单;

所以通过 API 网关,可以对指定调用的微服务版本,通过版本来隔离。如下图所示
分布式专题-高性能的Web容器之Nginx04-Nginx的扩展-OpenRestry_openrestry_27

图上表示的是,通过openresty实现灰度发布,将新版本2.0只开放给一小部分用户,提前体验,大多数人还是使用老版本,等到2.0版本没什么问题之后,统一将流量都引到2.0版本,完成新版本上线!
实际上就是新版本公测,用一部分用户来测试,没问题直接替换老版本,这样就可以极大地降低错误出现,这就是灰度发布~

OpenResty实现API网关限流及登录授权

OpenResty为什么能做网关?

前面我们了解到了网关的作用,通过网关,可以对 api 访问的前置操作进行统一的管理,比如鉴权、限流、负载均衡、日志收集、请求分片等。所以 API 网关的核心是所有客户端对接后端服务之前,都需要统一接入网关,通过网关层将所有非业务功能进行处理。

OpenResty 为什么能实现网关呢? OpenResty 有一个非常重要的因素是,对于每一个请求,Openresty 会把请求分为不同阶段,从而可以让第三方模块通过挂载行为来实现不同阶段的自定义行为。而这样的机制能够让我们非常方便的设计 api 网关
分布式专题-高性能的Web容器之Nginx04-Nginx的扩展-OpenRestry_lua_28

Nginx 本身在处理一个用户请求时,会按照不同的阶段进行处理,总共会分为 11 个阶段。而 openresty 的执行指令,就是在这 11 个步骤中挂载 lua 执行脚本实现扩展,我们分别看看每个指令的作用

lua脚本名称 功能
init by lua 当 Nginx master 进程加载 nginx 配置文件时会运行这段 lua 脚本,一般用来注册全局变量或者预加载 lua 模块
init woker by_lua 每个 Nginx worker 进程启动时会执行的 lua 脚本,可以用来做健康检查
set by lua 设置一个变量
rewrite by lua 在 rewrite 阶段执行,为每个请求执行指定的 lua 脚本
access by lua 为每个请求在访问阶段调用 lua 脚本
content by lua 前面演示过,通过 lua 脚本生成 content 输出给 http 响应
balancer by lua 实现动态负载均衡,如果不是走 contentbylua,则走 proxy_pass,再通过 upstream 进行转发
header filter by_lua 通过 lua 来设置 headers 或者 cookie
body filter by_lua 对响应数据进行过滤
log by lua 在 log 阶段执行的脚本,一般用来做数据统计,将请求数据传输到后端进行分析

灰度发布的实现

灰度发布有很多种,比如:

  • 按白名单
    分布式专题-高性能的Web容器之Nginx04-Nginx的扩展-OpenRestry_openrestry_29
  • 按流量
    访问量大的与不经常访问的
  • 按地域
    北上广与其他地域

接下来,我们通过一个小案例,演示一下灰度发布的过程~

在开始之前,我现在介绍一下准备环境:
三台机器
linux1:192.168.200.111 【openresty,redis:6379】
linux2:192.168.200.112【tomcat:8080】
linux3:192.168.200.113【tomcat:8080】

  1. 新建文件目录, /data/program/openresty/gray [conf、logs、lua]
    分布式专题-高性能的Web容器之Nginx04-Nginx的扩展-OpenRestry_lua_30

  2. 在/conf目录下编写 Nginx 的配置文件 nginx.conf

worker_processes 1;
error_log  logs/error.log;

events { 
    worker_connections 1024;
}
http {
  # 加载lua库地址,两个分号指定的是默认路径
  lua_package_path "$prefix/lualib/?.lua;;"; 
  lua_package_cpath "$prefix/lualib/?.so;;"; 

  upstream prod {
    server 192.168.200.112:8080;
  }
  upstream pre {
    server 192.168.200.113:8080;
  }

  server { 
    listen 80;
    server_name localhost; 
    location /api {
      # 自定义lua脚本地址
      content_by_lua_file lua/gray.lua;
    }
    location @prod { 
      proxy_pass http://prod;
    }
    location @pre { 
      proxy_pass http://pre;

    }
  }

  #server { 
    #listen 8080; 
    #location / {
      #content_by_lua_block { 
       # ngx.say("I'm prod env");
      #}
    #}
  #}

  #server {
    #listen 8081;
    #location / {
      #content_by_lua_block {
        #ngx.say("I'm pre env");
      #}
    #}
  #}
}
  1. 在/lua目录下编写 gray.lua 文件
# 引入redis库
local redis=require "resty.redis"; 

# 建立连接
local red=redis:new(); 
# 设置超时时间
red:set_timeout(1000);

# 连接到111服务器的redis
local ok,err=red:connect("192.168.200.111",6379);

# 判断连接是否成功
if not ok then
	ngx.say("failed to connect redis",err); 
	return;
end

# 获取远程地址
local_ip=ngx.var.remote_addr; 
local ip_lists=red:get("gray");

# 根据白名单判断:当前ip是否在白名单内
if string.find(ip_lists,local_ip) == nil then 
	ngx.exec("@prod");
else 
	ngx.exec("@pre");
end

# 关闭连接
local ok,err=red:close();
  1. 执行命令启动 nginx: [./nginx -p /data/program/openresty/gray]

分布式专题-高性能的Web容器之Nginx04-Nginx的扩展-OpenRestry_openrestry_31

  1. 启动 redis,并设置 set gray 192.168.200.112

分布式专题-高性能的Web容器之Nginx04-Nginx的扩展-OpenRestry_openrestry_32
6. 通过浏览器运行: http://192.168.200.111 查看运行结果

分布式专题-高性能的Web容器之Nginx04-Nginx的扩展-OpenRestry_运维_33
修改 redis gray 的值, 讲客户端的 ip 存储到 redis 中 set gray 1. 再次运行结果,即可看到访问结果已经发生了变化

后记

更多架构知识,欢迎关注本套Java系列文章Java架构师成长之路