Bootstrap

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

前言

前面我们讲了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

在这里插入图片描述

cd openrestry-1.15.8.3

在这里插入图片描述

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

在这里插入图片描述

make && make install

在这里插入图片描述

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

在这里插入图片描述

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

OpenRestry版HelloWorld

cd /data/program/openresty/nginx/conf

修改nginx.conf
在这里插入图片描述
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)
在这里插入图片描述

OpenRestry的基本操作

创建目录

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

mkdir demo

然后在 demo 目录下创建两个子目录,一个是 logs 、一个是 conf
在这里插入图片描述

创建配置文件

在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);
      }
    }
  }
}

在这里插入图片描述
注意:这里执行的nginx运行命令,是在/data/program/openresty/nginx/sbin/.nginx 文件,而不是之前已经单独安装的Nginx目录下执行

到/data/program/openresty/nginx/sbin/执行:./nginx -p /data/program/openresty/demo 【-p 主要是指明 nginx 启动时的配置目录】
在这里插入图片描述

效果演示
加法效果
在这里插入图片描述
减法效果:
在这里插入图片描述

脚本优化

优化问题的思考:
在这里插入图片描述
如图所示,容错性很差!没有检查机制,并且逻辑都写在一个主配置文件不便维护与管理,因此:

  • 第一步:公共方法抽离
    在刚才创建的nginx.conf配置文件下,同级目录创建lua目录,使用lua脚本存放加法与减法的方法
    在这里插入图片描述
    配置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;
    }
  }
}

在这里插入图片描述
最终的目录结构是这样的!

  • 优化后的demo测试
    注意:测试地址为:192.168.200.111/api/…
    在这里插入图片描述
    正常情况:
    在这里插入图片描述
    输入错误,报错bad_request,同之前脚本设定的
    在这里插入图片描述

小节

我们刚刚通过一个 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前,我们看到没有任何数据
在这里插入图片描述
到nginx 路径下执行 ./nginx -p /data/program/openresty/redisdemo
在这里插入图片描述
在浏览器中输入:http://192.168.200.111/demo 即可看到输出内容
在这里插入图片描述
并且连接到 redis 服务器上以后,可以看到 redis 上的结果(通过刚才的配置写入的数据此时已经存入redis中)
在这里插入图片描述
redis 的所有命令操作,在 lua 中都有提供相应的操作 .比如 redis:get(“key”)、redis:set()等

网关

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

上古服务配置
在这里插入图片描述
网关的引入

  • 按照服务组件进行统一抽象
    在这里插入图片描述
  • 针对不同的客户端来实现不同的api网关
    在这里插入图片描述

网关的概念

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

那什么是 api 网关呢?

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

为什么需要网关

  • 鉴权
  • 限流
  • 灰度发布
  • 分流
  • 日志记录
对微服务组件地址进行统一抽象

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

Backends for frontends

在这里插入图片描述
当服务越来越多以后,我们需要考虑一个问题,就是对某些服务进行安全校验以及用户身份校验。甚至包括对流量进行控制。 我们会对需要做流控、需要做身份认证的服务单独提供认证功能,但是服务越来越多以后,会发现很多组件的校验是重复的。这些东西很明显不是每个微服务组件需要去关心的事情。微服务组件只需要负责接收请求以及返回响应即可。可以把身份认证流控都放在 API 网关层进行控制

灰度发布

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

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

所以通过 API 网关,可以对指定调用的微服务版本,通过版本来隔离。如下图所示
在这里插入图片描述

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

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

OpenResty为什么能做网关?

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

OpenResty 为什么能实现网关呢? OpenResty 有一个非常重要的因素是,对于每一个请求,Openresty 会把请求分为不同阶段,从而可以让第三方模块通过挂载行为来实现不同阶段的自定义行为。而这样的机制能够让我们非常方便的设计 api 网关
在这里插入图片描述

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 阶段执行的脚本,一般用来做数据统计,将请求数据传输到后端进行分析

灰度发布的实现

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

  • 按白名单
    在这里插入图片描述
  • 按流量
    访问量大的与不经常访问的
  • 按地域
    北上广与其他地域

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

在开始之前,我现在介绍一下准备环境:
三台机器
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]
    在这里插入图片描述

  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]

在这里插入图片描述

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

在这里插入图片描述
6. 通过浏览器运行: http://192.168.200.111 查看运行结果

在这里插入图片描述
修改 redis gray 的值, 讲客户端的 ip 存储到 redis 中 set gray 1. 再次运行结果,即可看到访问结果已经发生了变化

后记

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

;