基于openresty构建运维工具链实践

7402aa76aa841d9949d50816b02f9f2c.jpeg

3773f391d75b330d465c60c130d30a88.gif

本文字数:4591

预计阅读时间:25

01

导读

如今OpenResty已广泛被各个互联网公司在实际生产环境中应用,在保留Nginx高并发、高稳定等特性基础上,通过嵌入Lua来提升在负载均衡层的开发效率并保证其高性能。本文主要介绍接口鉴权、流量控制以及记录追踪几个工具在实际生产环境中的应用实践,用于解决实际业务问题,提升业务运维效率,对于Openresty基本原理不再赘述。

1.1 OpenResty简介

OpenResty官网介绍如下:

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

2、OpenResty通过汇聚各种设计精良的Nginx模块(主要由OpenResty团队自主开发),从而将Nginx有效地变成一个强大的通用Web应用平台。这样,Web开发人员和系统工程师可以使用Lua脚本语言调动Nginx支持的各种C以及Lua模块,快速构造出足以胜任10K乃至1000K以上单机并发连接的高性能Web应用系统;

3、OpenResty的目标是让你的Web服务直接跑在Nginx服务内部,充分利用Nginx的非阻塞I/O模型,不仅仅对HTTP客户端请求,甚至于对远程后端诸如MySQL、PostgreSQL、Memcached以及Redis等都进行一致的高性能响应;

4、目前,OpenResty已在TikTok、微软、淘宝、腾讯、网易、百度、平安银行以及搜狐等各个行业广泛应用,本文主要对于生产环境的应用实践进行展开描述,对于OpenResty本身的实现原理不再阐述。

02

工具介绍

2.1 接口鉴权

-背景:随着线上环境的日益复杂,服务接口请求很容易被外部拦截,根据拦截的接口请求,执行数据的爬取,外部恶意刷量、攻击等等,对线上服务安全性、稳定性以及可用性造成一定的影响,因此,服务接口安全性需求的也日益增加;

-目标:实现一个通用的、经过简单配置变更即可快速实现指定服务接口接入鉴权处理逻辑,增强服务接口安全性,同时可在不同APP版本之间,实现灵活配置指定是否参与鉴权,参与鉴权使用的key等等,在保证服务接口安全的同时,兼顾老用户的使用体验;

-适用场景:适用于安全性要求相对较低,接口接入鉴权拦截效率高的场景,例如:新闻内容相关服务接口等。而对于安全性要求较高的服务接口则不适用,例如:用户信息相关服务接口等。

2.1.1 实现原理

1、通过WEB端实现APP版本、versionkey、鉴权接口以及鉴权开关的动态管理;

2、Lua初始化指定内存区,动态加载数据库中存储的鉴权接口、鉴权key等相关信息到内存中,供后续处理逻辑使用;

3、客户端版本指定区间范围内固定versionkey,在用户请求时,客户端根据versionkey进行计算生成鉴权key,并拼接到用户请求地址参数后进行下发;

4、用户请求到负载均衡层时,进行用户请求参数解析,对用户请求类型、参数中APP版本、鉴权参数等进行校验,判断用户请求是否参与鉴权;

5、负载均衡层针对参与鉴权的用户请求,进行请求参数解析签名,按照约定算法进行参数排序组装;

6、负载均衡层根据APP版本区间对应的versionkey以及约定的计算算法,计算生成鉴权key,并与客户端下发key进行比对;

7、针对匹配成功的请求进行放行,匹配失败的请求进行拦截并向客户端返回鉴权失败消息。

2.1.2 注意事项

1、versionkey和APP版本区间是1对多的关系,需采用前闭后开区间执行严格对应,防止执行APP版本切换versionkey时导致新版本客户端用户请求鉴权失败;

2、用户请求参数组装,需注意去重、解码、排序,不同的请求类型例如get和post,请求参数格式存在差别,需对请求url或者body体中的参数进行解码、去重、排序后进行鉴权key计算,避免因APP端和负载均衡层参数形式展示不同导致计算的鉴权key不同,影响正常的用户请求;

3、鉴权数据可视化,参与鉴权的接口、鉴权流量,鉴权失败量、鉴权失败原因等需实现可监控告警,及时发现鉴权异常问题,避免因参与鉴权影响正常用户体验;

4、需保留鉴权动态开关功能设置,以便于实现秒级打开/关闭鉴权拦截功能,实现快速鉴权拦截切换,保障线上业务安全性。

2.1.3 处理流程

1、APP接收用户请求,调用具体的服务接口响应本地用户请求;

2、OpenResty源站接收本次请求,进行用户请求参数解析,如是Get请求,则解析请求参数,如是Post请求则解析请求的参数和Body体;

3、进入鉴权判断逻辑之前,首先对解析参数中APP版本进行判断,此次用户请求携带的APP版本是否在参与鉴权的APP版本区间之内,如不在,则正常转发此次用户请求到后端对应服务,响应处理本地请求;

4、如此次用户请求携带的APP版本在鉴权版本区间范围之内,则进一步判断用户请求携带的鉴权参数是否完整,如鉴权参数不全,则向APP返回报错信息:鉴权参数不全;结束本次用户请求的处理过程;

5、如用户请求携带鉴权参数完整,则对本次用户请求的时效性进行判断,如不符合预期,则向APP返回报错信息:用户请求不在有效期内;结束本次用户请求的处理过程;

6、如鉴权参数完整,且在有效期内,则开始根据约定的算法、用户请求参数和app key,计算鉴权key,并与APP下发的鉴权key进行校验对比,如不匹配,则向APP返回报错信息:鉴权key错误;结束本次用户请求的处理过程;

7、如鉴权key校验通过,则顺利完成本次鉴权逻辑处理,转发到后端服务进行响应处理本次用户请求。

鉴权处理流程图:

65881e4e77e9b7af88035f70c638ba86.jpeg

2.1.4 实现简介

2.1.4.1 获取versionkey

根据APP版本获取该版本对应的versionkey,需要注意的是,一个versionkey的有效APP版本区间,例如a版本开始使用key1进行鉴权,到d版本开始切换使用key2,这样key1对应的APP版本区间就是[a, d):

local function get_version_key(version)
     
           local version_number = blacklist:get(config.sig_version)
     
           local first_version = blacklist:get(config.sig_first_version)
           
           local first_version_key = blacklist:get(first_version)
           
           local lower_version_key = first_version_key
     
           for i = 2, version_number, 1 do
                  local n = tostring(i)
                  local k = config.sig_version_number_prefix .. n
                  local v = blacklist:get(k)
                  if v == nil then
                         return nil, "not found ".. k
                  end
     
                  if version < v then
                         return lower_version_key, nil
                  end
     
                  local version_key = blacklist:get(v)
                  if version_key == nil then
                         return nil, "not found " .. v
                  end
     
                  lower_version_key = version_key
     
           end
     
           return lower_version_key, nil
     
    end

2.1.4.2 鉴权拦截功能

针对参与鉴权的用户请求,判断鉴权参数完整性、鉴权key准确性以及鉴权失败拦截信息反馈APP端等:

local function sig_check()
        local sig_prefix = ngx.var.sig_prefix
        if sig_prefix == nil or sig_prefix == "" then
            --ngx.log(ngx.ERR, "sig_prefix not set")
        return
        end
    
        -- 鉴权动态开关, 控制是否开启鉴权功能
        local switch_flag = config.sig_switch .. sig_prefix
        local init_version = blacklist:get(switch_flag)
        if init_version == nil then
            ngx.log(ngx.ERR, "switch_flag not set")
        return
        end
    
        local sig_string, create_err
        if ngx.var.request_method == "GET" then
        sig_string, create_err = create_get_method_sig_string()
        else
        -- post and 请求格式
        content_type == "application/json" then
        sig_string, create_err = create_post_sig_string()
        sig_string, create_err = create_post_json_method_sig_string()
        else
        return
        end
        end
    
        sig_string = sig_string .. version_key
        #鉴权计算,使用算法根据实际需求替换字符串“aa”
        local aa = resty_aa:new()
        if aa == nil then
            ngx.log(ngx.ERR, "resty_aa new err ")
        return
        end
    
        local ok = aa:update(sig_string)
        if not ok then
            ngx.log(ngx.ERR, "resty_aa update err ")
        return
        end
    
        local digest = aa:final()
        local right_sig = resty_str.to_hex(digest)
    
        if right_sig ~= sig then
        ngx.log(ngx.ERR, "signature failed, right_sig: ", right_sig, " client_sig: ", sig, " sig_string: ", sig_string)
    
    end

2.1.4.3 鉴权数据可视化

鉴权监控示例图:

1、参与鉴权接口总计QPS统计样例图:

1f225d62fd5a6123fc60135b3c2ef3cb.png

2、分接口鉴权QPS统计样例图:

e586ae56c9507b6663fc6a199fcd3ad3.png

3、鉴权失败情况统计样例图:

518dad99f2009ad7f04aedfe5aa9534e.png

4、鉴权失败告警信息样例图:

9cd61fd2ba11f439119124f2f885acff.png


2.2 流量控制

当今线上业务服务都采用多IDC或多可用区部署,保障单个IDC或可用区不可用时业务的可用性,如遇到IDC/可用区网络割接、机器维护时,如何快速摘到相关业务流量,保证用户使用不受影响呢?服务异常时,如何快速摘掉一个节点流量保护排查现场,快速恢复服务呢?流量控制工具的设计实现,主要目标就是在负载均衡层可以快速服务摘掉一个节点、一个IDC/可用区的业务流量,满足以上场景使用需求。

2.2.1 实现原理

1、WEB端进行源站节点、需要摘掉的服务节点、IP或者IP段等信息的动态管理维护;

2、Lua实现共享内存区域初始化,加载动态置为down状态的节点信息到共享内存;

3、通过lua的set_peer_down方法,将指定服务节点、IP或者IP段动态置为down/up状态,实现动态摘掉/恢复指定服务节点、指定机器或者指定IDC/可用区的线上流量。

2.2.2 注意事项

1、需注意源站nginx进程多woker之间数据保持同步问题,通过共享内存解决;

2、节点状态监控,实时监控确认节点状态,防止节点状态动态变更失败;

3、节点状态置为down有效期告警管理,避免人工置为down状态摘掉流量后,忘记恢复。

2.2.3 处理流程

1bb3645e01dd1fb4e493fd2b4c71a1a9.jpeg


2.2.4 实现简介

2.2.4.1 共享内存初始化

初始化共享内存,用于存放节点以及节点状态信息,openresty多woker进程,通过共享内存获取节点及状态信息,实现每个woker节点处理请求,获取到的后端集群节点状态数据一致性:

lua_shared_dict healthcheck 1m;
    lua_shared_dict logger_dict 10m;
    lua_shared_dict logger_metric_dict 10m;

2.2.4.2 节点状态变更

通过“set_peer_down”将指定节点状态进行变更,控制节点流量开关,这里需要注意针对backup节点需特殊处置,避免未实现完全摘除指定节点流量的目标:

local function set_primary_backup_peer_down(name, addr, is_backup, down_value)
    
        local err_msg
        local ok = false
        local peers
        if is_backup then
            peers, err_msg = get_backup_peers(name)
        else
            peers, err_msg = get_primary_peers(name)
        end
    
        if not peers then
            err_msg = "failed to get servers in upstream "..name.." err: "..err_msg
            return ok, err_msg
        end
    
        for i, srv in ipairs(peers) do
            for k, v in pairs(srv) do
                if k == "name" and v == addr then
                    local peer_id = i - 1
                    ok, err_msg = set_peer_down(name, is_backup, peer_id, down_value)
                    break
                end
            end
        end
    
        return ok, err_msg
    end

2.3 记录追踪

2.3.1 实现原理

1、WEB端维护CID白名单,控制过滤收录的用户请求日志;

2、Lua开发实现过滤器,实现在Openresty集群,将指定CID用户的所有请求记录进行过滤;

3、Lua实现批处理器,将过滤器处理的请求记录数据批量写入ES集群(这里以ES为例)。

2.3.2 注意事项

1、数据批量处理,杜绝过滤一条处理一条,避免对后端存储ES集群造成读写压力;

2、批处理逻辑设置超时时间,避免因过滤数据较少,导致数据长时间未写入后端存储。

2.3.3 处理流程

e09b5e016493a9d93fb3d10ab2b8685e.jpeg

2.3.4 实现简介

2.3.4.1 全局配置

通过nginx http配置中,进行过滤器全局配置,实现针对整个Openresty集群的用户请求进行过滤处理,避免针对所有接口进行过滤器配置:

log_by_lua_file "/opt/openresty/lualib/logger-plugin/logger.lua";
        server {
            set $resp_body "";
            body_filter_by_lua_file "/opt/openresty/lualib/logger-plugin/filter.lua";
        }

2.3.4.2 过滤器

过滤器需要设置body体大小,避免因用户请求返回数据较大影响处理效率:

local ngx = ngx
    local string = string
    local max_size = 51200 -- 50 * 1024 = 50k
    local body = string.sub(ngx.arg[1], 1, max_size)
      ngx.ctx.resp_buffered = (ngx.ctx.resp_buffered or "") .. body
    if ngx.arg[2] then
        ngx.var.resp_body = ngx.ctx.resp_buffered
    end

2.3.4.3 批处理器

批处理限制数据,声明设置批量处理条数限制、处理超时时间设置、后端存储配置以及批量处置的整体body大小限制等等:

local _M = {}
    
    _M.conf = {
        dict = ngx.shared.logger_dict,
        name = "ES",
        send_metric_exptime = 3600, -- second
        
        elasticsearch_index = "****",
        endpoint_addr = "ES地址",
        -- 失败后,延迟发送时间
    
        response_body_max_size = 51200, -- 50 * 1024
        response_body_max_tag = "response_body_max_tag",
        retry_delay = 5, -- second
        -- 发送失败最大次数
        max_retry_count = 5,
        -- 设置每批发送日志的最大条数,当日志条数达到设置的最大值时,会自动推送全部日志到 ES
        batch_max_size = 1000,
        batch_max_size_exceeded_metric = "***",
    
        batch_log_max_size = 52428800, -- bit, 50 * 1024 * 1024 bit = 50M
        batch_log_max_size_metric = "***",
        batch_log_max_size_exceeded_metric = "***",
        -- inactive_timeout 刷新缓冲区的最大时间(以秒为单位),
        -- 当达到最大的刷新时间时,无论缓冲区中的日志数量是否达到设置的最大条数,
        -- 也会自动将全部日志推送到 ES
        inactive_timeout = 10,
        -- buffer_duration: 必须先处理批次中最旧条目的最长期限(以秒为单位)
        buffer_duration = 10,
    }
    
    return _M

03

总结

本文主要介绍了鉴权和流量控制工具在生产环境的实践,通过Lua开发实现Openresty端工具链开发实现,在不影响负载均衡层性能的前提下,快速构建运维工具,提升运维工作效率。Openresty在互联网行业有较广泛的应用,本文只是简单介绍了这几个工具的实践,希望能够对大家日常工作起到帮助作用。

参考文档:

1、Openresty:[http://openresty.org/cn/nginx.html]

2、lua-nginx-module:[https://github.com/openresty/lua-nginx-module]

3、ngxreqget_uri_args:[https://github.com/openresty/lua-nginx-module#ngxreqget_uri_args]

4、ngxreqget_post_args:[https://github.com/openresty/lua-nginx-module#ngxreqget_post_args]

5、lua-upstream-nginx-module:[https://github.com/openresty/lua-upstream-nginx-module]

6、set_peer_down:[https://github.com/openresty/lua-upstream-nginx-module#set_peer_down]

7、lua-resty-elasticearch:[https://github.com/midoks/lua-resty-elasticsearch]

8、lua-resty-http:[https://github.com/ledgetech/lua-resty-http]

e1078c059a8057301645c1c97b2fe29f.jpeg

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.kler.cn/a/274449.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

从零开始写 Docker(六)---实现 mydocker run -v 支持数据卷挂载

本文为从零开始写 Docker 系列第六篇&#xff0c;实现类似 docker -v 的功能&#xff0c;通过挂载数据卷将容器中部分数据持久化到宿主机。 完整代码见&#xff1a;https://github.com/lixd/mydocker 欢迎 Star 推荐阅读以下文章对 docker 基本实现有一个大致认识&#xff1a; …

搭建项目后台系统基础架构

任务描述 1、了解搭建民航后端框架 2、使用IDEA创建基于SpringBoot、MyBatis、MySQL、Redis的Java项目 3、以原项目为参照搭建项目所涉及到的各个业务和底层服务 4、以原项目为例&#xff0c;具体介绍各个目录情况并参照创建相关文件夹 1、创建项目后端 BigData-KongGuan …

【MySQL】MySQL视图

文章目录 一、视图的基本使用1.创建视图2.修改了视图&#xff0c;对基表数据有影响3.修改了基表&#xff0c;对视图有影响4.删除视图 二、视图规则和限制 一、视图的基本使用 视图是一个虚拟表&#xff0c;其内容由查询定义。同真实的表一样&#xff0c;视图包含一系列带有名称…

15届蓝桥杯备赛(2)

文章目录 刷题笔记(2)二分查找在排序数组中查找元素的第一个和最后一个位置寻找旋转排序数组中的最小值搜索旋转排序数组 链表反转链表反转链表II 二叉树相同的树对称二叉树平衡二叉树二叉树的右视图验证二叉搜索树二叉树的最近公共祖先二叉搜索树的最近公共祖先二叉树层序遍历…

管道(acwing,蓝桥杯,二分)

题目描述&#xff1a; 有一根长度为 len 的横向的管道&#xff0c;该管道按照单位长度分为 len 段&#xff0c;每一段的中央有一个可开关的阀门和一个检测水流的传感器。 一开始管道是空的&#xff0c;位于 Li的阀门会在 Si 时刻打开&#xff0c;并不断让水流入管道。 对于位…

WRF模型运行教程(ububtu系统)--III.运行WRF模型(官网案例)

零、创建DATA目录 # 1.创建一个DATA目录用于存放数据&#xff08;一般为fnl数据&#xff0c;放在Build_WRF目录下&#xff09;。 mkdir DATA # 2.进入 DATA cd DATA 一、WPS预处理 在模拟之前先确定模拟域&#xff08;即模拟范围&#xff09;,并进行数据预处理&#xff08…

我的尝试:Codigger + Vim

若您愿意耐心投入&#xff0c;学习 Vim 的过程其实远比想象中轻松。我对 Vim 产生兴趣&#xff0c;主要是源于它对提升生产力的巨大潜力。我尝试了 Neovim、NvChad 以及 Codigger Vim 插件&#xff0c;如今我的工作效率已远超从前。 那么&#xff0c;Vim 究竟是什么呢&#xff…

Leetcode 79. 单词搜索

心路历程&#xff1a; 做完这道题才发现是回溯&#xff0c;一开始想的是递归&#xff0c;判断完第i个字符后&#xff0c;只需要挨个判断第i1个字符在不在第i个字符的邻域。后来发现由于不能重复使用元素&#xff0c;所以需要维护一个visited列表&#xff0c;并且在遍历所有可能…

【进阶五】Python实现SDVRP(需求拆分)常见求解算法——自适应大邻域算法(ALNS)

基于python语言&#xff0c;采用经典自适应大邻域算法&#xff08;ALNS&#xff09;对 需求拆分车辆路径规划问题&#xff08;SDVRP&#xff09; 进行求解。 目录 往期优质资源1. 适用场景2. 代码调整3. 求解结果4. 代码片段参考 往期优质资源 经过一年多的创作&#xff0c;目前…

Aigtek超声功率放大器产品介绍

超声功率放大器是一种特殊类型的功率放大器&#xff0c;专门用于增强和放大超声信号的功率。它在医疗、工业和科学领域中得到广泛应用。 一、超声功率放大器的基本概述 超声功率放大器是一种能够将低功率超声信号放大到更高功率水平的设备。它是超声系统的关键组成部分&#xf…

力扣1. 两数之和

思路&#xff1a;用一个map存放 已遍历过的元素和下标&#xff1b; 若当前元素是nums[i], 且该元素的另一半 target-nums[i] 在已遍历过的map里面&#xff0c;则返回两个元素的下标&#xff1b; class Solution {public int[] twoSum(int[] nums, int target) {int[] ans new…

腾讯云服务器多少钱1个月?2024一个月收费阿济格IE吧

2024腾讯云服务器多少钱一个月&#xff1f;5元1个月起&#xff0c;腾讯云轻量服务器4核16G12M带宽32元1个月、96元3个月&#xff0c;8核32G22M配置115元一个月、345元3个月&#xff0c;腾讯云轻量应用服务器61元一年折合5元一个月、4核8G12M配置646元15个月、2核4G5M服务器165元…

数据结构:详解【顺序表】的实现

1. 顺序表的定义 顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构&#xff0c;一般情况下采用数组存储。动态顺序表与数组的本质区别是——根据需要动态的开辟空间大小。 2. 顺序表的功能 动态顺序表的功能一般有如下几个&#xff1a; 初始化顺序表打印顺序…

PlantUML Integration 编写短信服务类图

PlantUML Integration 写一个类图&#xff0c;主要功能为 1、编写一个serviceSms短信服务类&#xff1b; 2、需要用到短信的地方统一调用基建层的服务即可&#xff1b; 3、可以随意切换、增加短信厂商&#xff0c;不需要更改场景代码&#xff0c;只需要更改application.yml 里面…

Redis数据结构对象中的对象共享、对象的空转时长

对象共享 概述 除了用于实现引用计数内存回收机制之外&#xff0c;对象的引用计数属性还带有对象共享的作用。 在Redis中&#xff0c;让多个键共享同一个值对象需要执行以下两个步骤: 1.将数据库键的值指针指向一个现有的值对象2.将被共享的值对象的引用计数增一 目前来说…

【Godot4.2】2D导航01 - AStar2D及其使用方法

概述 对于2D平台跳跃或飞机大战&#xff0c;以及一些直接用键盘方向键操控玩家的游戏&#xff0c;是根本用不到寻路的&#xff0c;因为只需要检测碰撞就可以了。 但是对于像RTS或战棋这样需要操控玩家到地图指定位置的移动方式&#xff0c;就绝对绕不开寻路了。 导航、碰撞与…

微信小程序接口请求出错:request:fail url not in domain list:xxxxx

一、微信小程序后台和开发者工具配的不一样导致了这个错误 先说结论&#xff1a; 开发者工具配置了https://www.xxx.cn/prod-api/ 微信后台配置了 https://www.xxx.cn 一、最开始 开发者工具配置了https://www.xxx.cn:7500 微信后台配置了 https://www.xxx.cn 报错:reques…

代码随想录算法训练营第53天 | 1143.最长公共子序列 ,1035.不相交的线 ,53. 最大子序和

动态规划章节理论基础&#xff1a; https://programmercarl.com/%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%E7%90%86%E8%AE%BA%E5%9F%BA%E7%A1%80.html 1143.最长公共子序列 题目链接&#xff1a;https://leetcode.cn/problems/longest-common-subsequence/description/ 思路&…

ASP .Net Core ILogger日志服务

&#x1f433;简介 ILogger日志服务是.NET平台中的一个内置服务&#xff0c;主要用于应用程序的日志记录。它提供了灵活的日志记录机制&#xff0c;允许开发者在应用程序中轻松地添加日志功能。以下是其主要特点和组件&#xff1a; ILogger接口&#xff1a;这是ILogger日志服…

电脑数据安全新利器:自动备份文件的重要性与实用方案

一、数据安全的守护神&#xff1a;自动备份文件的重要性 在数字化时代&#xff0c;电脑中的文件承载着我们的工作成果、个人回忆以及众多重要信息。然而&#xff0c;数据丢失的风险无处不在&#xff0c;无论是硬件故障、软件崩溃&#xff0c;还是恶意软件的攻击&#xff0c;都…
最新文章