OpenResty(介绍 安装 常用命令 使用 KONG网关)

OpenResty

OpenResty

1 OpenResty介绍

OpenResty是一个基于 Nginx 与 Lua 的高性能 Web 平台,其内部集成了大量精良的 Lua 库、第三方模块以及大多数的依赖项。
用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关。
OpenResty通过汇聚各种设计精良的 Nginx 模块(主要由 OpenResty 团队自主开发),从而将
Nginx 有效地变成一个强大的通用 Web 应用平台。这样,Web 开发人员和系统工程师可以使用 Lua 脚本语言调动 Nginx 支持的各种 C 以及 Lua 模块,快速构造出足以胜任 10K 乃至 1000K 以上单机并发连接的高性能 Web 应用系统。
OpenResty的目标是让你的Web服务直接跑在Nginx服务内部,充分利用 Nginx 的非阻塞 I/O 模
型,不仅仅对 HTTP 客户端请求,甚至于对远程后端诸如 MySQL、PostgreSQL、Memcached 以及Redis 等都进行一致的高性能响应。

1.1 Nginx 的流程定义

nginx实际把请求处理流程划分为了11个阶段,这样划分的原因是将请求的执行逻辑细分,各阶段按照处理时机定义了清晰的执行语义,开发者可以很容易分辨自己需要开发的模块应该定义在什么阶段。
在这里插入图片描述

  1. 当请求进入Nginx后先READ REQUEST HEADERS 读取头部 然后再分配由哪个指令操作
  2. Identity 寻找匹配哪个Location*
  3. Apply Rate Limits 是否要对该请求限制
  4. Preform Authertication 权限验证
  5. Generate Content 生成给用户的响应内容
  6. 如果配置了反向代理 那么将要和上游服务器通信 Upstream Services
  7. 当返回给用户请求的时候要经过过滤模块 Response Filter
  8. 发送给用户的同时 记录一个Log日志
1.1.1 流程详解

在这里插入图片描述

1.1.2 OpenResty处理流程

由于 Nginx 把一个请求分成了很多阶段,第三方模块就可以根据自己的行为,挂载到不同阶段处理达到目的,OpenResty 也应用了同样的特性。不同的阶段,有不同的处理行为,这是OpenResty 的一大特色,OpenResty 处理一个请求的流程参考下图
在这里插入图片描述
在这里插入图片描述

2 Openresty安装

2.1 yum安装

你可以在你的 CentOS 系统中添加 openresty 仓库,这样就可以便于未来安装或更新我们的软件包(通过 yum update 命令)

2.1.1 添加OpenResty仓库

运行下面的命令就可以添加我们的仓库:

sudo yum install yum-utils 
sudo yum-config-manager --add-repo 
https://openresty.org/package/centos/openresty.repo 
2.1.2 安装OpenResty

然后就可以像下面这样安装软件包,比如 openresty

sudo yum install openresty

2.2 源代码编译安装

OpenResty插件分为自带插件以及第三方插件,如果是自带插件直接激活就可以,如果是第三方插件需要手动下载插件添加进去,这里我们以本地缓存插件安装举例

2.2.1 安装编译环境
yum install -y make cmake gcc gcc-c++ autoconf automake libpng-devel libjpeg- 
devel zlib libxml2-devel ncurses-devel bison libtool-ltdl-devel libiconv 
libmcrypt mhash mcrypt pcre-devel openssl-devel freetype-devel libcurl-devel lua- 
devel readline-devel curl wget 
2.2.2 下载最新版源码
wget https://openresty.org/download/openresty-1.19.3.1.tar.gz 
tar -zxvf openresty-1.19.3.1.tar.gz # 解压openresty 
2.2.3 下载缓存插件

缓存插件地址 下载最新版 缓存插件

wget http://labs.frickle.com/files/ngx_cache_purge-2.3.tar.gz 
tar -zxvf ngx_cache_purge-2.3.tar.gz #解压缓存插件 
2.2.4 编译OpenResty

选择需要的插件启用, –with-Components 激活组件,–without 则是禁止组件 ,–add-module是安装第三方模块

./configure --prefix=/usr/local/openresty --with-luajit --without- 
http_redis2_module --with-http_stub_status_module --with-http_v2_module --with- 
http_gzip_static_module --with-http_sub_module --add- 
module=/usr/local/openresty/modules/ngx_cache_purge-2.3 #配置缓存插件的源码路径

这里禁用了 redis组件 并且 安装了第三方缓存组件
出现如下界面表示编译成功
在这里插入图片描述

2.2.5 安装OpenResty
gmake && gmake install

出现如下界面表示安装成功
在这里插入图片描述

2.2.6 环境设置
vi /etc/profile ##加入path路径 
export PATH=$PATH:/usr/local/openresty/nginx/sbin/ 
source /etc/profile ##生效配置 
2.2.7 查看环境
nginx -v 
nginx version: openresty/1.17.8.2 

查看安装的组件

nginx -V

在这里插入图片描述

2.3 运行测试

2.3.1 创建Nginx配置文件

我们在 conf 目录下创建一个 nginx.conf 文件 代码如下

worker_processes 1; 
error_log logs/error.log; 
events { 
   worker_connections 1024; 
}
http {
   server { 
   listen 9000; 
   location / { 
     default_type text/html; 
     content_by_lua ' 
     ngx.say("<p>Hello, World!</p>") 
    '; 
   } 
  } 
}

如果你熟悉 nginx 的配置,应该对以上代码就很熟悉。这里我们将 html 代码直接写在了配置文件中

2.3.2 启动 OpenResty
nginx -c /usr/local/openresty/nginx/conf/nginx.conf 

接下来我们可以使用 curl 来测试是否能够正常范围

> curl http://localhost:9000/ 
<p>Hello, World!</p>

我们在配置文件写的 html 已正常输出

3 OpenResty常用命令

主要帮助对http请求取参、取header头、输出等

在这里插入图片描述

4 商品详情页系统架构介绍

商品详情页架构的要求->高可用 ,-> 高性能,-> 高并发 ;一般来说 业界分为 种主流的方案

4.1 中小公司的详情页方案

很多中小型 电商的商品详情页 可能一分钟都没有一个访问,这种的话,就谈不上并发设计,一个tomcat 就能搞定。
还有一种中小型公司呢?虽然说公司不大,但是也是有几十万日活。然后几百万用户,这种公司,就是稍微大点的公司,他们的商品详情用,采取的方案可能是全局的一个静态页面这样子
在这里插入图片描述
就是我们有把商品详情页直接做成一个静态页面,然后这样子每次全量的更新,把数据全部静态放到nginx,里面,每次数据变化的时候,我们就通过一个Java服务去渲染这个数据,然后把这个静态页面推送到到文件服务器

4.1.0.1 缺点

这种方案的缺点,如果商品很多,那么渲染的时间会很长,达不到实时的效果
文件服务器性能高,tomcat性能差,压力都在Tomcat服务器了
只能处理一些静态的东西,如果动态数据很多,比如有库存的,你不可能说每次去渲染,然后推送到文件服务器,那不是更加慢?

4.2 大型公司的商品详情页的核心思想

下面这张图就是整体的思路
在这里插入图片描述
上图展示了核心思想主要有以下四步来完成

4.2.1 生成静态页

添加修改页面的时候生成静态页,这个地方生成的是一个通用的静态页,敏感数据比如 价格,商品名称等,通过占位符来替换,然后将生成的静态页的链接,以及敏感数据同步到redis中,如果只修改价格不需要重新生成静态页,只需要修改redis敏感数据即可

4.2.2 推送到文件服务器

这个的文件服务器泛指能够提供静态文件处理的文件服务器,nginx代理静态文件,tomcat,以及OSS等都算静态文件服务器,生成完静态文件后将文件推送到文件服务器,并将请求连接存放进redis中

4.2.3 布隆过滤器过滤请求

Redis和nginx的速度很快,但是如果有人恶意请求不存在的请求会造成redis很大的开销,那么可以采用布隆过滤器将不存在的请求过滤出去。

4.2.4 lua直连Redis读取数据

因为java连接Reids进行操作并发性能很弱,相对于OpenResty来说性能差距很大,这里使用OpenResty,读取Redis中存放的URL以及敏感数据

4.2.5 OpenResty 渲染数据

从Redis获取到URL后lua脚本抓取模板页面内容,然后通过redis里面的敏感数据进行渲染然后返回前端,因为都是lua脚本操作性能会很高

5 环境准备

下面我们就基于这个架构来安装和搭建所需要的环境

5.1 Redis安装布隆过滤器

5.1.1 什么是布隆过滤器

使用布隆过滤器来解决缓存穿透问题

5.1.1.1 布隆过滤器的原理

布隆过滤器其本质就是一个只包含0和1的数组。具体操作当一个元素被加入到集合里面后,该元素通过K个Hash函数运算得到K个hash后的值,然后将K个值映射到这个位数组对应的位置,把对应位置的值设置为1。查询是否存在时,我们就看对应的映射点位置如果全是1,他就很可能存在(跟hash函数的个数和hash函数的设计有关),如果有一个位置是0,那这个元素就一定不存在
在这里插入图片描述

5.1.1.2 布隆过滤器缺点

bloom filter之所以能做到在时间和空间上的效率比较高,是因为牺牲了判断的准确率、删除的便利性

  • 存在误判,可能要查到的元素并没有在容器中,但是hash之后得到的k个位置上值都是1。如果bloom filter中存储的是黑名单,那么可以通过建立一个白名单来存储可能会误判的元素。
  • 删除困难。一个放入容器的元素映射到bit数组的k个位置上是1,删除的时候不能简单的直接置为0,可能会影响其他元素的判断。可以采用Counting Bloom Filter
5.1.1.3 如何选择哈希函数个数和布隆过滤器长度

很显然,过小的布隆过滤器很快所有的 bit 位均为 1,那么查询任何值都会返回“可能存在”,起不到过滤的目的了。布隆过滤器的长度会直接影响误报率,布隆过滤器越长其误报率越小。
另外,哈希函数的个数也需要权衡,个数越多则布隆过滤器 bit 位置位 1 的速度越快,且布隆过滤器的效率越低;但是如果太少的话,那我们的误报率会变高
在这里插入图片描述
如何选择适合业务的 k 和 m 值呢,这里直接贴一个公式:
在这里插入图片描述

5.1.2 插件形式安装
5.1.2.1 下载布隆过滤器插件

在redis布隆过滤器插件地址下载最新的release源码,在编译服务器进行解压编译

wget https://github.com/RedisBloom/RedisBloom/archive/v2.2.4.tar.gz

解压插件进行插件的编译

tar RedisBloom-2.2.5.tar.gz 
cd RedisBloom-2.2.5 
make

编译得到动态库 rebloom.so
启动redis时,如下启动即可加载bloom filter插件
配置文件形式配置

#在redis配置文件(redis.conf)中加入该模块即可 
vim redis.conf 
#添加 
loadmodule /root/bloom/redisbloom-2.2.4/rebloom.so (前面为你自己的路径) 

启动命令挂载

redis-server redis.conf --loadmodule /usr/rebloom/rebloom.so INITIAL_SIZE 
1000000 ERROR_RATE 0.0001 
#容量100万, 容错率万分之一, 占用空间是4m 
5.1.3 docker方式单机安装
docker run -d -p 6379:6379 --name redis-redisbloom redislabs/rebloom:latest
5.1.4 Redis集群部署安装

这里将本地编译好的redisbloom.so挂载到/etc/redis/redisbloom.so目录

配置docker-compose.yml文件

version: '2'
services:
    redis01:
        image: redis
        hostname: redis01
        container_name: redis01
        networks:
            docker-network:
                ipv4_address: 172.18.0.2
        ports:
            - "7001:6379"
        volumes:
            - "/tmp/etc/redis/redis.conf:/etc/redis/redis.conf"
            - "/tmp/data/redis/node1:/data"
            - "/tmp/etc/redis/redisbloom.so:/etc/redis/redisbloom.so"
        command:
           redis-server /etc/redis/redis.conf
    redis02:
        image: redis
        hostname: redis02
        container_name: redis02
        networks:
            docker-network:
                ipv4_address: 172.18.0.3
        ports:
            - "7002:6379"
        volumes:
            - "/tmp/etc/redis/redis.conf:/etc/redis/redis.conf"
            - "/tmp/data/redis/node2:/data"
            - "/tmp/etc/redis/redisbloom.so:/etc/redis/redisbloom.so"
        command:
           redis-server /etc/redis/redis.conf
    redis03:
        image: redis
        hostname: redis03
        container_name: redis03
        networks:
            docker-network:
                ipv4_address: 172.18.0.4
        ports:
            - "7003:6379"
        volumes:
            - "/tmp/etc/redis/redis.conf:/etc/redis/redis.conf"
            - "/tmp/data/redis/node3:/data"
            - "/tmp/etc/redis/redisbloom.so:/etc/redis/redisbloom.so"
        command:
            redis-server /etc/redis/redis.conf
    redis04:
        image: redis
        hostname: redis04
        container_name: redis04
        networks:
            docker-network:
                ipv4_address: 172.18.0.5
        ports:
            - "7004:6379"
        volumes:
            - "/tmp/etc/redis/redis.conf:/etc/redis/redis.conf"
            - "/tmp/data/redis/node4:/data"
            - "/tmp/etc/redis/redisbloom.so:/etc/redis/redisbloom.so"
        command:
            redis-server /etc/redis/redis.conf
    redis05:
        image: redis
        hostname: redis05
        container_name: redis05
        networks:
            docker-network:
                ipv4_address: 172.18.0.6
        ports:
            - "7005:6379"
        volumes:
            - "/tmp/etc/redis/redis.conf:/etc/redis/redis.conf"
            - "/tmp/data/redis/node5:/data"
            - "/tmp/etc/redis/redisbloom.so:/etc/redis/redisbloom.so"
        command:
            redis-server /etc/redis/redis.conf
    redis06:
        image: redis
        hostname: redis06
        container_name: redis06
        networks:
            docker-network:
                ipv4_address: 172.18.0.7
        ports:
            - "7006:6379"
        volumes:
            - "/tmp/etc/redis/redis.conf:/etc/redis/redis.conf"
            - "/tmp/data/redis/node6:/data"
            - "/tmp/etc/redis/redisbloom.so:/etc/redis/redisbloom.so"
        command:
            redis-server /etc/redis/redis.conf

networks:
    docker-network:
        ipam:
            config:
                - subnet: 172.18.0.0/16
                  gateway: 172.18.0.1

启动布隆过滤器集群

docker-compose up -d

配置集群
进入一个容器

docker exec -ti redis01 /bin/bash

执行创建集群命令

redis-cli --cluster create 172.18.0.2:6379 172.18.0.3:6379 172.18.0.4:6379 172.18.0.5:6379 172.18.0.6:6379 172.18.0.7:6379 --cluster-replicas 1

布隆过滤器常用命令
在这里插入图片描述
测试
可以登录集群执行命令

# 进入一个redis集群的节点内部 
docker exec -ti redis01 /bin/bash 
# 以集群方式登录172.18.0.2:3306节点 
redis-cli -h 172.18.0.2 -c 
# 在redis中添加一个布隆过滤器 错误率是0.01 数量是1万个 
BF.RESERVE bf_test 0.01 10000 NONSCALING 
# 在bf_test 的布隆过滤器添加一个key 
BF.ADD bf_test key 
# 验证布隆过滤器key是否存在 
BF.EXISTS bf_test key 
# 验证布隆过滤器key1是否存在 
BF.EXISTS bf_test key1
5.1.5 布隆过滤器实现
第一种方式: Guava

1、引入Guava pom配置

<dependency> 
<groupId>com.google.guava</groupId> 
<artifactId>guava</artifactId> 
<version>29.0-jre</version> 
</dependency> 

2、代码实现

public class BloomFilterTest {

    int size=100000;
    double fpp=0.03;

    @Test
    public void test1(){
        BloomFilter<Integer> integerBloomFilter = BloomFilter.create(Funnels.integerFunnel(), size, fpp);

        //插入10W样本数据
        for (int i = 0; i < size; i++) {
            integerBloomFilter.put(i);
        }
        //用另外十万测试数据,测试误判
        int count=0;
        for (int i = 100001; i < size+100000; i++) {
            if (integerBloomFilter.mightContain(i)){
                count++;
                System.out.println(i+"误判了");
            }
        }
        System.out.println("总共的误判数="+count);
    }
}

代码分析:
核心 BloomFilter.create 方法:

    public static <T> BloomFilter<T> create(Funnel<? super T> funnel, int expectedInsertions, double fpp) {
        return create(funnel, (long)expectedInsertions, fpp);
    }

这里有四个参数:

  • funnel :数据类型(通常是调用Funnels工具类中的)
  • expectedInsertions :指望插入的值的个数
  • fpp :误判率(默认值为0.03)
  • strategy :哈希算法

咱们重点讲一下 fpp 参数

fpp误判率
情景一: fpp = 0.01
误判个数:947 占内存大小:9585058位数
情景二: fpp = 0.03 (默认参数)
误判个数:3033 占内存大小:7298440位数
总结
误判率能够经过 fpp 参数进行调节
fpp越小,须要的内存空间就越大:0.01须要900多万位数,0.03须要700多万位数。
fpp越小,集合添加数据时,就须要更多的hash函数运算更多的hash值,去存储到对应的数组下标里。(忘了去看上面的布隆过滤存入数据的过程)

第二种方式:Redisson

上面使用Guava实现的布隆过滤器是把数据放在了本地内存中。分布式的场景中就不合适了,没法共享内存
还能够用Redis来实现布隆过滤器,这里使用Redis封装好的客户端工具Redisson

pom配置:

        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson-spring-boot-starter</artifactId>
            <version>3.16.0</version>
        </dependency> 

java代码:

public class RedissonBloomFilter {
    public static void main(String[] args) {
        Config config = new Config();
        config.useSingleServer().setAddress("redis://127.0.0.1:6379");
        config.useSingleServer().setPassword("1234");

        RedissonClient redisson = Redisson.create(config);
        RBloomFilter<Object> bloomFilter = redisson.getBloomFilter("phoneList");
        bloomFilter.tryInit(1000000L,0.03);
        bloomFilter.add("10086");

        System.out.println(bloomFilter.contains("123456"));
        System.out.println(bloomFilter.contains("10086"));
    }
}

5.2 OpenResty支持Reids集群配置

5.2.1 下载安装lua_resty_redis

lua_resty_redis 它是一个基于rockspec API的为ngx_lua模块提供Lua redis客户端的驱动。
resty-redis-cluster模块地址:https://github.com/steve0511/resty-redis-cluster
将 resty-redis-cluster/lib/resty/ 下面的文件 拷贝到 openresty/lualib/resty
总共两个文件 rediscluster.lua , xmodem.lua

5.2.2 连接Redis集群封装

创建 RedisUtils.lua 的文件

--操作Redis集群,封装成一个模块
--引入依赖库
local redis_cluster = require "resty.rediscluster"


--配置Redis集群链接信息
local config = {
    name = "testCluster",                   --rediscluster name
    serv_list = {                           --redis cluster node list(host and port),
           {ip="192.168.64.181", port = 7001},
                   {ip="192.168.64.181", port = 7002},
                   {ip="192.168.64.181", port = 7003},
                   {ip="192.168.64.181", port = 7004},
                   {ip="192.168.64.181", port = 7005},
                   {ip="192.168.64.181", port = 7006},
    },
    keepalive_timeout = 60000,              --redis connection pool idle timeout
    keepalive_cons = 1000,                  --redis connection pool size
    connection_timout = 1000,               --timeout while connecting
    max_redirection = 5
}


--定义一个对象
local lredis = {}

--创建查询数据get()
function lredis.get(key)
        local red = redis_cluster:new(config)
        local res, err = red:get(key)
        if err then
            ngx.log(ngx.ERR,"执行get错误:",err)
            return false
        end
        return res
end

-- 执行hgetall方法并封装成table
function lredis.hgetall(hash_key)
    local red = redis_cluster:new(config)
    local flat_map, err = red:hgetall(hash_key)
    if err then
        ngx.log(ngx.ERR,"执行hgetall错误:",err)
        return false
    end
    local result = {}
    for i = 1, #flat_map, 2 do
        result[flat_map[i]] = flat_map[i + 1]
    end
    return result
end

-- 判断key中的item是否在布隆过滤器中
function lredis.bfexists(key,item)
             local red = redis_cluster:new(config)
    		 --	通过eval执行脚本
             local res,err = red:eval([[
             local  key=KEYS[1]
             local  val= ARGV[1]
             local  res,err=redis.call('bf.exists',key,val)
             return res
                ]],1,key,item)
     if  err then
        ngx.log(ngx.ERR,"过滤错误:",err)
        return false
     end
     return res
end

return lredis

5.2.3 配置lua脚本路径

我们编写了lua脚本需要交给nginx服务器去执行,我们需要将创建一个lua目录,并在全局的nginx‘配置文件中配置lua目录,配置参数使用lua_package_path

# 配置redis 本地缓存 
lua_shared_dict redis_cluster_slot_locks 100k; 
# 配置lua脚本路径 
lua_package_path "/usr/local/openresty/script/?.lua;;"; #注意后面是两个分号

完整的配置如下


user  root;
worker_processes  1;

#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

#pid        logs/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       mime.types;
    default_type  application/octet-stream;

    #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
    #                  '$status $body_bytes_sent "$http_referer" '
    #                  '"$http_user_agent" "$http_x_forwarded_for"';

    #access_log  logs/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;
    # lua脚本路径 
    lua_package_path "/usr/local/openresty/script/?.lua;;";

    #gzip  on;

    include	/usr/local/nginx/conf/conf.d/*.conf;

}

5.2.4 测试脚本

在 nginx配置文件嵌入lua脚本进行测试

server {
       listen 9999; 
       charset utf-8; 

       location /test { 
          default_type text/html; 
          content_by_lua ' 
             local lrredis = require("RedisUtils") 
              -- 尝试读取redis中key的值 
             local value = lrredis.get("key") 
              --判断key是否在bf_taxi的布隆过滤器中 
             local bfexist = lrredis.bfexists("bf_taxi","key") 
             local htest = lrredis.hgetall("h_test") 
              ngx.log(ngx.INFO, "key的值是",value) 
              ngx.log(ngx.INFO, "bf_taxi布隆过滤器key的状态",bfexist) 
              ngx.log(ngx.INFO, "h_test[url]的值是",htest["url"]) 
            '; 
       } 
} 

访问查看nginx日志打印

tail -f /usr/local/openresty/nginx/logs/error.log

在这里插入图片描述

5.3 请求参数封装

nginx为了能够处理请求需要获取请求数据,需要将获取请求参数的lua脚本封装以下,创建RequestUtils.lua 的文件

--定义一个对象
local lreqparm={}
-- 获取请求参数的方法
function lreqparm.getRequestParam()
    -- 获取请求方法 get或post
   local request_method = ngx.var.request_method
    -- 定义参数变量
    local args = nil
    if "GET" == request_method then
        args = ngx.req.get_uri_args()
    elseif "POST" == request_method then
        ngx.req.read_body()
        args = ngx.req.get_post_args()
    end
    return args
end
return lreqparm

5.3.1 测试脚本
server {
       listen 9999; 
       charset utf-8; 
       location /testreq { 
           default_type text/html; 
           content_by_lua ' 
             local lreqparm = require("RequestUtils") 
             local params = lreqparm.getRequestParam() 
             local title = params["title"] 
             if title ~= nil then 
                ngx.say("<p>请求参数的Title是:</p>"..title) 
             return 
            end 
            ngx.say("<P>没有输入title请求参数<P>") 
          '; 
       } 
}

访问http://192.168.64.150:9999/testreq?title=ceshi 测试
在这里插入图片描述

5.4 抓取模板内容封装

5.4.1 下载安装lua-resty-http

下载地址 https://github.com/ledgetech/lua-resty-http
将 lua-resty-http-master\lib\resty 下的所有文件复制到 openresty/lualib/resty
总共两个文件 http.lua , http_headers.lua

因为需要从远程服务器抓取远程页面的内容,需要用到http模块,将其封装起来,创建RequestHtml.lua

-- 引入Http库
local http = require "resty.http"
--定义一个对象
local lgethtml={}

function lgethtml.gethtml(requesturl)

    --创建客户端
    local httpc = http:new()
    local resp,err = httpc:request_uri(requesturl,
        {
                method = "GET",
                headers = {["User-Agent"]="Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.111 Safari/537.36"}
        })
	--关闭连接
    httpc:close()
    if not resp then
        ngx.say("request error:",err)
        return
    end
    local result = {}
    --获取状态码
    result.status = resp.status
    result.body  = resp.body
    return result

end

return lgethtml
5.4.2 测试脚本

模板文件我们用

server {
       listen 9999; 
       charset utf-8; 
       location /testgetHtml { 
           default_type text/html; 
           content_by_lua ' 
               local lgethtml = require("RequestHtml") 
               local url = "http://192.168.64.1:9001/template.html" 
               local result = lgethtml.gethtml(url); 
               ngx.log(ngx.INFO, "状态是",result.status) 
               ngx.log(ngx.INFO, "body是",result.body) 
               ngx.say(result.body) 
            '; 
        } 
    }

访问http://192.168.64.181:9999//testgetHtml 测试
在这里插入图片描述

5.5 模版渲染配置

5.5.1 下载安装lua-resty-template
wget https://github.com/bungle/lua-resty-template/archive/v1.9.tar.gz tar -xvzf v1.9.tar.gz

解压后可以看到lib/resty下面有一个template.lua,这个就是我们所需要的,在template目录中还有两个lua文件,将这两个文件复到/usr/openResty/lualib/resty中即可。

5.5.2 使用方式
local template = require "resty.template" 
-- Using template.new 
local html=[[<html> 
<body> 
  <h1>{{message}}</h1> 
</body> 
</html> 
]]
template.render(html, { message = params["title"] }) 
5.5.3 测试

在nginx中配置文件中嵌入lua脚本执行测试

server {
       listen 9999; 
       charset utf-8; 
       location /testtemplate { 
          default_type text/html; 
          content_by_lua ' 
            local lreqparm = require("RequestUtils") 
            local template = require "resty.template" 
            local params = lreqparm.getRequestParam() 
             -- Using template.new 
              local html=[[<html> 
                            <body> 
                               <h1>{{message}}</h1> 
                            </body> 
                          </html>
                                  ]] 
               template.render(html, { message = params["title"] }) 
            '; 
        } 
  }

访问 http://192.168.64.150:9999/testtemplate?title=123456
在这里插入图片描述

6 整体业务分析

整个调用流程如下,需要使用lua脚本编写,整体流程分为7 步
在这里插入图片描述
上面我们将各个组件都给封装了,下面我们需要将各个组件组合起来

6.1 编写lua脚本

创建一个 requestTemplateRendering.lua 的lua脚本

---
--- Generated by EmmyLua(https://github.com/EmmyLua)
--- Created by baiyp.
--- DateTime: 2020/11/24 13:24
---


local template = require("resty.template")
local lrredis = require("RedisUtils")
local lgethtml = require("RequestHtml")
local lreqparm = require("RequestUtils")
--获取请求参数
local reqParams = lreqparm.getRequestParam()
-- 定义本地缓存
local html_template_cache = ngx.shared.html_template_cache

-- 获取请求ID的参数
local reqId = reqParams["id"];

ngx.log(ngx.INFO, "requestID:", reqId);
-- 校验参数
if reqId==nil then
    ngx.say("缺少ID参数");
    return
end

-- 布隆过滤器检查id是否存在
local bfexist = lrredis.bfexists("bf_taxi",reqId)
ngx.log(ngx.INFO, "布隆过滤器检验:", bfexist)

-- 校验key不存在直接返回
if bfexist==0 then
    ngx.say("布隆过滤器校验key不存在...")
    return
end

-- 拼接hget的key
local hgetkey = "hkey_".. reqId

-- 通过hget获取map的所有数据
local templateData = lrredis.hgetall(hgetkey);
if next(templateData) == nil then
    ngx.say("redis没有存储数据...")
    return
end


--获取模板价格数据
local amount = templateData["amount"]
ngx.log(ngx.INFO, "amount:", amount)
if amount == nil then
    ngx.say("价格数据未配置");
    return
end


-- 获取本地缓存对象
ngx.log(ngx.INFO, "开始从缓存中获取模板数据----")
local html_template = html_template_cache:get(reqId)
-- 判断本地缓存是否存在
if html_template == nil then
    -- 获取模板url中的数据
        ngx.log(ngx.INFO, "缓存中不存在数据开始远程获取模板")
    local url = templateData["url"]
        ngx.log(ngx.INFO, "从缓存中获取的url地址:", url)
    if url == nil then
        ngx.say("URL路径未配置");
        return
    end
        -- 抓取远程url的html
        ngx.log(ngx.INFO, "开始抓取模板数据:", url)
    local returnResult = lgethtml.gethtml(url);
    -- 判断抓取模板是否正常
    if returnResult==nil then
        ngx.say("抓取URL失败...");
        return
    end
        -- 判断html状态
    if returnResult.status==200 then
        html_template = returnResult.body
                --设置模板缓存为一小时
                ngx.log(ngx.INFO, "将模板数据加入到本地缓存")
        html_template_cache:set(reqId,html_template,60 * 60)
    end
end
ngx.log(ngx.INFO, "缓存中获取模板数据结束----")

-- 模板渲染
--编译得到一个lua函数

local func = template.compile(html_template)
local context = {amount=amount}
ngx.log(ngx.INFO, "开始渲染模板数据")
--执行函数,得到渲染之后的内容
local content = func(context)

--通过ngx API输出
ngx.say(content)

6.2 编写nginx配置

6.2.1 添加本地缓存

在nginx主配置文件中添加模板缓存

lua_shared_dict html_template_cache 10m;
6.2.2 编写nginx配置文件

我们这里使用 content_by_lua_file 的方式执行脚本

server {
       listen 8888; 
       charset utf-8; 
       #配置静态资源代理 
       location /static { 
          proxy_pass http://192.168.64.1:9001/static; 
        }
        #删除本地缓存 
        location /delete { 
             default_type text/html; 
             content_by_lua ' 
              local lreqparm = require("RequestUtils") 
               --获取请求参数 
              local reqParams = lreqparm.getRequestParam() 
               -- 定义本地缓存 
              local html_template_cache = ngx.shared.html_template_cache 
               -- 获取请求ID的参数 
              local reqId = reqParams["id"]; 
              ngx.log(ngx.INFO, "requestID:", reqId); 
              -- 校验参数 
             if reqId==nil then 
                 ngx.say("缺少ID参数"); 
                 return 
              end 
              -- 获取本地缓存对象 
              html_template_cache:delete(reqId); 
              ngx.say("清除缓存成功"); 
            '; 
          }
          
         location /template { 
            default_type text/html; 
            content_by_lua_file 
   /usr/local/openresty/script/requestTemplateRendering.lua; 
   } 
}

6.3 初始化数据

# 进入一个redis集群的节点内部 
docker exec -ti redis01 /bin/bash 
# 以集群方式登录172.18.0.2:3306节点 
redis-cli -h 172.18.0.2 -c 
# 在redis中添加一个布隆过滤器 错误率是0.01 数量是1万个 
BF.RESERVE bf_taxi 0.01 10000 NONSCALING 
# 在bf_test 的布隆过滤器添加一个key 
BF.ADD bf_taxi 1 
#检查数据是否存在 
BF.EXISTS bf_taxi 1 
# 添加URL以及价格 
hset hkey_1 url http://192.168.64.1:9001/template.html amount 100.00 

6.4 访问测试

访问 http://192.168.64.181:8888/template?id=1 进行测试

7 kong网关

7.1 kong网关简介

Kong是一款基于OpenResty(Nginx + Lua模块)编写的高可用、易扩展的,由Mashape公司开源的API Gateway项目。Kong是基于NGINX和Apache Cassandra或PostgreSQL构建的,能提供易于使用的RESTful API来操作和配置API管理系统,所以它可以水平扩展多个Kong服务器,通过前置的负载均衡
配置把请求均匀地分发到各个Server,来应对大批量的网络请求。

7.2 为什么需要 API 网关

API网关是一个服务器,是系统的唯一入口。从面向对象设计的角度看,它与外观模式类似。API网关封装了系统内部架构,为每个客户端提供一个定制的API。它可能还具有其它职责,如身份验证、监控、负载均衡、缓存、请求分片与管理、静态响应处理。API网关方式的核心要点是,所有的客户端和消费端都通过统一的网关接入微服务,在网关层处理所有的非业务功能。通常,网关也
是提供REST/HTTP的访问API。
在这里插入图片描述
在微服务架构之下,服务被拆的非常零散,降低了耦合度的同时也给服务的统一管理增加了难度。如上图左所示,在旧的服务治理体系之下,鉴权,限流,日志,监控等通用功能需要在每个服务中单独实现,这使得系统维护者没有一个全局的视图来统一管理这些功能。API 网关致力于解决的问题便是为微服务纳管这些通用的功能,在此基础上提高系统的可扩展性。如右图所示,微服务搭配上 API 网关,可以使得服务本身更专注于自己的领域,很好地对服务调用者和服务提供者做了隔离。
目前,比较流行的网关有:Nginx 、 Kong 、Orange等等,还有微服务网关Zuul 、Spring Cloud Gateway等等
对于 API Gateway,常见的选型有基于 Openresty 的 Kong、基于 Go 的 Tyk 和基于 Java 的gateway,这三个选型本身没有什么明显的区别,主要还是看技术栈是否能满足快速应用和二次开发。

7.2.1 和Spring Cloud Gateway区别
  • 像Nginx这类网关,性能肯定是没得说,它适合做那种门户网关,是作为整个全局的网关,是对外的,处于最外层的;而Gateway这种,更像是业务网关,主要用来对应不同的客户端提供服务的,用于聚合业务的。各个微服务独立部署,职责单一,对外提供服务的时候需要有一个东西把业务聚合起来。
  • 像Nginx这类网关,都是用不同的语言编写的,不易于扩展;而Gateway就不同,它是用Java写的,易于扩展和维护
  • Gateway这类网关可以实现熔断、重试等功能,这是Nginx不具备的

所以,你看到的网关可能是这样的
在这里插入图片描述

7.3 为什么要使用kong

  • 插件市场丰富,很多插件可以降低开发成本;
  • 可扩展性,可以编写lua脚本来定制自己的参数验证权限验证等操作;
  • 基于openResty,openResty基于Nginx保障了强劲的性能;
  • 便捷性能扩容,只需要水平增加服务器资源性能就能提升 ;
  • 负载均衡健康检查
7.3.1 kong的组成部分

Kong主要有三个组件

  • Kong Server :基于nginx的服务器,用来接收API请求。
  • Apache Cassandra/PostgreSQL :用来存储操作数据。
  • Kong dashboard:官方推荐UI管理工具,当然,也可以使用 restfull 方式 管理admin api

Kong采用插件机制进行功能定制,插件集(可以是0或N个)在API请求响应循环的生命周期中被执行。插件使用Lua编写,目前已有几个基础功能:HTTP基本认证、密钥认证、CORS(Cross-Origin Resource Sharing,跨域资源共享)、TCP、UDP、文件日志、API请求限流、请求转发以及Nginx监
控。

7.3.2 Kong网关的特性

Kong网关具有以下的特性

  • 可扩展性: 通过简单地添加更多的服务器,可以轻松地进行横向扩展,这意味着您的平台可以在一个较低负载的情况下处理任何请求;
  • 模块化: 可以通过添加新的插件进行扩展,这些插件可以通过RESTful Admin API轻松配置;
  • 在任何基础架构上运行:Kong网关可以在任何地方都能运行。您可以在云或内部网络环境中部署Kong,包括单个或多个数据中心设置,以及public,private
    或invite-only APIs。

7.4 kong网关架构

  1. Kong核心基于OpenResty构建,实现了请求/响应的Lua处理化;
  2. Kong插件拦截请求/响应,如果接触过Java Servlet,等价于拦截器,实现请求/响应的AOP处理;
  3. Kong Restful 管理API提供了API/API消费者/插件的管理;
  4. 数据中心用于存储Kong集群节点信息、API、消费者、插件等信息,目前提供了PostgreSQL和Cassandra支持,如果需要高可用建议使用Cassandra;
  5. Kong集群中的节点通过gossip协议自动发现其他节点,当通过一个Kong节点的管理API进行一些变更时也会通知其他节点。每个Kong节点的配置信息是会缓存的,如插件,那么当在某一个Kong节点修改了插件配置时,需要通知其他节点配置的变更。
7.4.1 Kong网关请求流程

为了更好地理解系统,这是使用Kong网关的API接口的典型请求工作流程:
在这里插入图片描述
当Kong运行时,每个对API的请求将先被Kong命中,然后这个请求将会被代理转发到最终的API接口。在请求(Requests)和响应(Responses)之间,Kong将会执行已经事先安装和配置好的任何插件,授权您的API访问操作。Kong是每个API请求的入口点(Endpoint)。

7.5 kong 部署

7.5.1 搭建网络

首先我们创建一个Docker自定义网络,以允许容器相互发现和通信。在下面的创建命令中 kong- net 是我们创建的Docker网络名称。

docker network create kong-net
7.5.2 搭建数据库环境

Kong 目前使用Cassandra(Facebook开源的分布式的NoSQL数据库) 或者PostgreSql,你可以执行以下命令中的一个来选择你的Database。请注意定义网络 --network=kong-net 。

docker run -d --name kong-database --network=kong-net -p 5432:5432 -v  /tmp/data/kong:/var/lib/postgresql/data -e "POSTGRES_USER=kong" -e  "POSTGRES_DB=kong" -e "POSTGRES_PASSWORD=kong" postgres:11.7 
7.5.3 kong网关部署
7.5.3.1 初始化kong数据

使用 docker run --rm 来初始化数据库,该命令执行后会退出容器而保留内部的数据卷(volume)

docker run --rm --network=kong-net -e "KONG_DATABASE=postgres" -e 
"KONG_PG_HOST=kong-database" -e "KONG_PG_PASSWORD=kong" -e 
"KONG_CASSANDRA_CONTACT_POINTS=kong-database" kong:latest kong migrations bootstrap

这个命令我们还是要注意的,一定要跟你声明的网络,数据库类型、host名称一致。同时注意Kong的版本号,本文是在 Kong 1.4.x 版本下完成的。

出现如下信息说明数据初始化成功
在这里插入图片描述

7.5.3.2 启动Kong容器

完成初始化数据库后,我们就可以启动一个连接到数据库容器的Kong容器,请务必保证你的数据库容器启动状态,同时检查所有的环境参数 -e 是否是你定义的环境

docker run -d --name kong \ 
--network=kong-net \ 
-e "KONG_DATABASE=postgres" \ 
-e "KONG_PG_HOST=kong-database" \ 
-e "KONG_PG_PASSWORD=kong" \ 
-e "KONG_CASSANDRA_CONTACT_POINTS=kong-database" \ 
-e "KONG_PROXY_ACCESS_LOG=/dev/stdout" \ 
-e "KONG_ADMIN_ACCESS_LOG=/dev/stdout" \ 
-e "KONG_PROXY_ERROR_LOG=/dev/stderr" \ 
-e "KONG_ADMIN_ERROR_LOG=/dev/stderr" \ 
-e "KONG_ADMIN_LISTEN=0.0.0.0:8001, 0.0.0.0:8444 ssl" \ 
-p 8000:8000 \ 
-p 8443:8443 \ 
-p 8001:8001 \ 
-p 8444:8444 \ 
kong:latest 
7.5.3.3 验证

通过服务器执行curl访问尝试服务是否已经启动

curl http://localhost:8001/status

在这里插入图片描述
出现如下信息说明服务已经启动

7.6 Kong配置

我们来看一个典型 Nginx 的配置对应在 Kong 上是怎么样的,下面是一个典型的 Nginx 配置

upstream passportUpstream { 
    server 192.168.64.1:9001 weight=10; 
}
server { 
    listen 80; 
    location /hello { 
       proxy_pass http://passportUpstream; 
    } 
}

下面我们开看看其对应 Kong 中的配置

# 配置 upstream 
curl -X POST http://localhost:8001/upstreams --data "name=passportUpstream" 

# 配置 target 
curl -X POST http://localhost:8001/upstreams/passportUpstream/targets --data 
"target=192.168.64.1:9001" --data "weight=10" 

# 配置 service 
curl -X POST http://localhost:8001/services --data "name=hello" --data "host=passportUpstream" 

# 配置 route 
curl -X POST http://localhost:8001/routes \ 
--data "paths[]=/hello" \ 
--data "service.id=bfdaf1ea-9b81-4f53-a16d-f7255e44264a" 

curl -X POST http://localhost:8001/routes 
--data "hosts[]=*.example.com,test.com,*.abc.com" 
--data "service.id=8695cc65-16c1-43b1-95a1-5d30d0a50409"

这一切配置都是通过其 Http Restful API 来动态实现的,无需我们再手动的 reload Nginx.conf
在上述的配置中涉及到了几个概念: Upstream、Target、Service、Route 等概念,它们是 Kong的几个核心概念,也是我们在使用 Kong API 时经常打交道的,下面我们就其几个核心概念做一下简单的说明。

7.6.1 Kong关键术语
7.6.1.1 Upstream

Upstream 对象表示虚拟主机名,可用于通过多个服务(目标)对传入请求进行负载均衡。例如:service.v1.xyz 为 Service 对象命名的上游 Host 是 service.v1.xyz 对此服务的请求将代理到上游定义的目标。

7.6.1.2 Target

目标 IP地址/主机名,其端口表示后端服务的实例。每个上游都可以有多个 Target,并且可以动态添加 Target。
由于上游维护 Target 的更改历史记录,因此无法删除或者修改 Target。要禁用目标,请发布一个新的 Targer weight=0,或者使用 DELETE 来完成相同的操作

7.6.1.3 Service

顾名思义,服务实体是每个上游服务的抽象,服务的示例是数据转换微服务,计费API等。
服务的主要属性是它的 URL(其中,Kong 应该代理流量),其可以被设置为单个JSON串或通过指定其 protocol, host,port 和path。
服务与路由相关联(服务可以有许多与之关联的路由),路由是 Kong 的入口点,并定义匹配客户端请求的规则。一旦匹配路由,Kong 就会将请求代理到其关联的服务。

7.6.1.4 Route

路由实体定义规则以匹配客户端的请求。每个 Route 与一个 Service 相关联,一个服务可能有多个与之关联的路由,与给定路由匹配的每个请求都将代理到其关联的 Service 上。可以配置的字段有:

  • hosts
  • paths
  • methods

Service 和 Route 的组合(以及它们之间的关注点分离)提供了一种强大的路由机制,通过它可以在Kong 中定义细粒度的入口点,从而使基础架构路由到不同上游服务。

7.6.1.5 Consumer

Consumer 对象表示服务的使用者或者用户,你可以依靠 Kong 作为主数据库存储,也可以将使用者列表与数据库映射,以保持Kong 与现有的主数据存储之间的一致性。

7.6.1.6 Plugin

插件实体表示将在 HTTP请求/响应生命周期 期间执行的插件配置。它是为在 Kong 后面运行的服务添加功能的,例如身份验证或速率限制。

将插件配置添加到服务时,客户端向该服务发出的每个请求都将运行所述插件。如果某个特定消费者需要将插件调整为不同的值,你可以通过创建一个单独的插件实例,通过 service 和 consumer 字段指定服务和消费者。

7.7 kongAPI操作

7.7.1 配置网关

配置服务

# 添加服务 
curl -i -X POST http://192.168.64.181:8001/services/ -d 'name=test-service' -d 'url=http://116.62.213.90/1.html' 

url 参数是一个简化参数,用于一次性添加 protocol,host,port 和 path。

添加路由

# 添加service 的路由 
curl -i -X POST http://192.168.64.181:8001/routes/ -d 'methods=GET' -d 'paths=/service' -d 'service.id=65c11356-e86d-431e-a76a-aa7c3acd9aeb'

访问测试

# 通过如下URL访问 
http://192.168.64.181:8000/service?a=123 
7.7.2 配置负载均衡

配置 upstream 和 target
创建一个名称 load 的 upstream

curl -X POST http://192.168.64.181:8001/upstreams --data "name=load"

添加3000 端口的负载

curl -X POST http://192.168.64.181:8001/upstreams/load/targets --data "target=192.168.64.1:9001" --data "weight=10" 
curl -X POST http://192.168.64.181:8001/upstreams/load/targets --data "target=192.168.64.1:9002" --data "weight=10" 

如上的配置对应了 Nginx 的配置

upstream load { 
   server 192.168.64.181:9001 weight=10; 
   server 192.168.64.181:9002 weight=10; 
}

配置service
host 的值便对应了 upstream 的名称,配置成功后会返回生成的 service 的 id

curl -X POST http://192.168.64.181:8001/services --data "name=load-service" -- data "host=load" 

配置路由信息
这里的service.id 就是上面添加service

curl -X POST http://192.168.64.181:8001/routes --data "paths[]=/load" --data "service.id=3a6b839b-00c1-49b7-b271-8024a12d19be"

访问测试
http://192.168.64.181:8000/load/index

7.8 安装Kong 管理UI

Kong 企业版提供了管理UI,开源版本是没有的。但是有很多的开源的管理 UI ,其中比较好用的是Konga。项目地址:https://github.com/pantsel/konga
在这里插入图片描述

7.8.1 初始化konga数据

使用 docker run --rm 来初始化数据库,该命令执行后会退出容器而保留内部的数据卷(volume)。

docker run --name konga --rm \ 
--network=kong-net \ 
pantsel/konga -c prepare -a postgres -u postgresql://kong:kong@kong- 
database:5432/kong

执行后出现如下界面说明执行成功
在这里插入图片描述

7.8.2 启动konga
docker run -p 1337:1337 -d \ 
--network=kong-net \ 
-e "DB_ADAPTER=postgres" \ 
-e "DB_HOST=kong-database" \ 
-e "DB_USER=kong" \ 
-e "DB_PASSWORD=kong" \ 
-e "DB_DATABASE=kong" \ 
-e "KONGA_HOOK_TIMEOUT=120000" \ 
-e "DB_PG_SCHEMA=public" \ 
-e "NODE_ENV=production" \ 
--name konga \ 
pantsel/konga
7.8.3 验证

通过浏览器请求 http://IP:1337 检查是否能够访问,如果能够访问说明已经启动成功
在这里插入图片描述

7.8.4 连接kong网关

在这个界面添加一个用户 登录就可以看到如下页面
在这里插入图片描述
点击Connections 来填加kong的信息
在这里插入图片描述
添加kong名称以及API地址就可以管理kong了
在这里插入图片描述
添加后点击’active’激活按钮就可以激活kong网关
在这里插入图片描述
然后就会出现如下界面管理可以通过菜单操作管理kong
在这里插入图片描述

7.8.5 限流插件

限流的场景:服务提供的能力是有限的。为了防止大量的请求将服务拖垮,可以通过网关对服务的请求做限流。例如:某个服务1s只能处理100个请求,超过限流阈值的请求丢弃。

路由配置限流
为/load的路由添加限流插件
在这里插入图片描述
设置限流为每分钟2个
在这里插入图片描述
测试验证
多次访问 http://192.168.64.150:8000/load 发现已经被限流了
在这里插入图片描述
配置属性如下
在这里插入图片描述

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值