开发者社区 > 博文 > nginx+lua+redis实现灰度发布
分享
  • 打开微信扫码分享

  • 点击前往QQ分享

  • 点击前往微博分享

  • 点击复制链接

nginx+lua+redis实现灰度发布

  • 马仁喜122815
  • 2023-12-11
  • IP归属:北京
  • 10880浏览

    前言:授人以鱼不如授人以渔.先学会用,在学原理,在学创造,可能一辈子用不到这种能力,但是不能不具备这种能力。这篇文章主要是沉淀使用nginx+lua+redis实现灰度,当我们具备了这种能力,随时可以基于这种能力和思想调整实现方案:比如nginx+lua+(其他数据源)、nginx+(其他脚本语言)

    一、灰度方案:

    常见的灰度实现方案:

    1. 请求路由:通过请求中的标识(如用户ID、设备ID、请求头等)来决定是否将请求路由到灰度环境。可以使用反向代理(如Nginx、Envoy)或API网关(如Kong、Apigee)来实现路由规则。
    2. 权重控制:将流量按照一定的权重比例分配到不同的环境中。可以通过负载均衡器(如HAProxy、Kubernetes Ingress)或代理服务器(如Nginx、Envoy)来实现权重控制。
    3. 特性开关:通过在代码中嵌入特性开关(Feature Flag)来控制功能的开启与关闭。可以使用配置文件、数据库、键值存储或特性管理平台(如LaunchDarkly、Unleash)来管理特性开关。
    4. 分阶段发布:将功能的发布分为多个阶段,从内部测试到灰度环境再到全量发布。可以使用部署工具(如Jenkins、GitLab CI/CD)或云平台(如AWS、Azure)来支持分阶段发布。
    5. A/B测试:将流量分为多个不同版本的应用程序,比较它们的性能和用户反馈。可以使用A/B测试平台(如Optimizely、Google Optimize)来管理和监控A/B测试。
    6. 金丝雀发布:将新版本的应用程序逐步引入生产环境,仅将少量流量导向新版本,并根据其性能和稳定性逐步增加流量。可以使用部署工具、容器编排平台或云平台来实现金丝雀发布。


    常用的灰度发布方案:

    1. 基于用户ID的灰度发布:基于用户ID来划分灰度用户或百分比灰度,例如根据用户ID的哈希值或随机数来决定用户是否路由到灰度环境。
    2. 基于IP地址的灰度发布:根据用户的IP地址来划分灰度用户,例如将某一范围的IP地址指定为灰度用户,将请求从这些IP地址路由到灰度环境。
    3. Cookie/Session的灰度发布:通过在用户的Cookie或会话中设置特定的标识来划分灰度用户。例如,将特定的Cookie或会话变量设置为灰度标识,将具有该标识的请求路由到灰度环境。
    4. 请求头的灰度发布:基于请求头中的特定标识来划分灰度用户。例如,根据请求头中的自定义标识或特定的HTTP Header来路由请求到灰度环境。
    5. 权重或百分比的灰度发布:将请求随机分配给不同的环境,可以通过给不同环境设置不同的权重或百分比来控制流量的分配。
    6. A/B测试:将流量分为多个不同版本的应用程序,在实验期间比较它们的性能和用户反馈,最终选择最佳版本进行全量发布。


    二、nginx+lua+redis实现灰度

    理论:

    1、安装并配置 Nginx 和 Redis。确保 Nginx 启用 Lua 模块,并可以访问 Redis。

    2、在 Nginx 配置中定义灰度规则。您可以使用 Lua 脚本来判断用户是否应该被路由到灰度环境。示例配置如下:

    server {
        listen 80;
        server_name example.com;
        location / {
            access_by_lua_block {
                local redis = require "resty.redis"
                local red = redis:new()
                -- 连接到 Redis
                local ok, err = red:connect("redis_host", redis_port)
                if not ok then
                    ngx.log(ngx.ERR, "failed to connect to Redis: ", err)
                    ngx.exit(500)
                end
                -- 使用 Redis 根据用户 ID 判断是否路由到灰度环境
                local user_id = ngx.req.get_headers()["X-User-ID"]
                local is_gray = red:get("gray:" .. user_id)
                if is_gray == "1" then
                    ngx.var.upstream = "gray_backend"
                end
            }
            proxy_pass http://backend;
        }
        location /gray {
            # 灰度环境的配置
            proxy_pass http://gray_backend;
        }
        location /admin {
            # 管理后台的配置
            proxy_pass http://admin_backend;
        }
    }

    在上面的示例中,我们连接到 Redis,并根据请求中的用户 ID 判断是否将请求路由到灰度环境。ngx.var.upstream 变量用于动态设置上游地址,从而实现灰度环境的路由。

    3、在 Redis 中设置灰度用户。您可以在 Redis 中维护一个键值对,其中键是用户 ID,值表示是否是灰度用户(例如,1 表示是灰度用户,0 表示不是)。您可以使用 Redis 的 SET 和 GET 命令来操作这些值。

    -- 设置用户为灰度用户
    local ok, err = red:set("gray:" .. user_id, 1)
    if not ok then
        ngx.log(ngx.ERR, "failed to set gray status for user: ", err)
        ngx.exit(500)
    end
    
    -- 设置用户为非灰度用户
    local ok, err = red:set("gray:" .. user_id, 0)
    if not ok then
        ngx.log(ngx.ERR, "failed to set gray status for user: ", err)
        ngx.exit(500)
    end
    

    通过在 Redis 中设置用户的灰度状态,您可以动态地控制用户是否应该被路由到灰度环境。

    4、根据需要,配置其他路径或功能的灰度规则。您可以根据需要在 Nginx 配置中添加其他路径或功能的灰度规则,以实现更复杂的灰度发布策略。

    实践:

    这里主要使用OpenResty

    nginx+lua 实现灰度----主要使用OpenResty

    OpenResty(又称:ngx_openresty) 是一个基于 NGINX 的可伸缩的 Web 平台,OpenResty 是一个强大的 Web 应用服务器,Web 开发人员可以使用 Lua 脚本语言调动 Nginx 支持的各种 C 以及 Lua 模块

    openresty的api文档: https://www.kancloud.cn/qq13867685/openresty-api-cn/159190

    1、根据post请求url参数匹配进行路由

    nginx配置如下:

    #user  nobody;
    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 '$time_local 客户端地址:$remote_addr–$remote_port 请求的URI和HTTP协议:$request 请求地址:$http_host HTTP请求状态:$status upstream状态:$upstream_status 负载地址:$upstream_addr url跳转来源:$http_referer $body_bytes_sent $http_user_agent $request_uri';
        log_format  logFormat '$group $time_local 客户端:$remote_addr–$remote_port 请求的URI和HTTP协议:$request 请求:$http_host HTTP状态:$status upstream状态:$upstream_status 负载:$upstream_addr 
                              url跳转:$http_referer $body_bytes_sent $http_user_agent $request_uri 请求参数 $query_string $args $document_root $uri
                              -----$request_uri $request_filename $http_cookie';
        access_log  logs/access.log  logFormat;
        sendfile        on;
        #tcp_nopush     on;
        #keepalive_timeout  0;
        keepalive_timeout  65;
        server{
            listen       80;   #监听端口
            server_name  域名; #监听地址 
            access_log  logs/xx.com.access.log  logFormat;
                  location /hello {
                    default_type 'text/plain';
                    content_by_lua 'ngx.say("hello ,lua scripts")';
            }
            location /myip {
                    default_type 'text/plain';
                    content_by_lua '
                            clientIP = ngx.req.get_headers()["x_forwarded_for"]
                            ngx.say("Forwarded_IP:",clientIP)
                            if clientIP == nli then
                                    clientIP = ngx.var.remote_addr
                                    ngx.say("Remote_IP:",clientIP)
                            end
                            ';
            }
            location / {
                    default_type 'text/plain';
                    lua_need_request_body on;
                    #content_by_lua_file /etc/nginx/lua/dep.lua;
                    #content_by_lua_file D:/sortware/openresty/openresty-1.17.8.2-win64/conf/dep.lua; # 指定由lua文件处理http请求
                    content_by_lua_file D:/user/Downloads/openresty-1.19.9.1-win64/conf/dep.lua; # 指定由lua文件处理http请求
            }
            location @default_version {
                proxy_pass http://default; 
                proxy_set_header  Host  $http_host;
                proxy_set_header  X-Real-IP  $remote_addr;
                proxy_set_header  X-Forwarded-For $proxy_add_x_forwarded_for;
            }
            location @new_version {
                proxy_pass http://new_version;
                proxy_set_header Host $http_host;
                #proxy_redirect default;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            }
            location @old_version {
                proxy_pass http://old_version; 
                proxy_set_header Host $http_host;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;    
            }
        }
    	#标准预发环境
    	upstream default {
    		 server ip:port;
    	}
        #预发2
    	upstream new_version {
    		 server ip:port;
    	}
        #预发3
    	upstream old_version {
    		server ip:port;
    	}
    }

    lua脚本如下:

    --get请求uri参数
    
    function SaveTableContent(file, obj)
          local szType = type(obj);
          print(szType);
          if szType == "number" then
                file:write(obj);
          elseif szType == "string" then
                file:write(string.format("%q", obj));
          elseif szType == "table" then
                --把table的内容格式化写入文件
                --file:write("{\n");
                for i, v in pairs(obj) do
                      SaveTableContent(file, i);
                      file:write(":");
                      SaveTableContent(file, v);
                      file:write(",");
    			end
                --file:write("}\n");
          else
    			error("can't serialize a "..szType);
          end
    end
    
    function SaveTable(obj)
    
          local file = io.open("D:\\user\\Downloads\\openresty-1.19.9.1-win64\\logs\\parmas.txt", "a");
          assert(file);
          SaveTableContent(file,obj);
          file:close();
    end
    
    local request_method = ngx.var.request_method
    local getargs = nil
    local args = nil
    local read_body = nil
    local body_data = nil
    local thirdPolicystatus = nil
    if "GET" == request_method then
    	args = ngx.req.get_uri_args()
    elseif "POST"== request_method then
    	getargs = ngx.req.get_uri_args()
    	args  	  = ngx.req.get_post_args()
    	read_body = ngx.req.read_body()
    	body_data = ngx.req.get_body_data()
    end
    if getargs ~= nil then
    	SaveTable(getargs);
    	thirdPolicystatus= getargs["thirdPolicystatus"];
    	if thirdPolicystatus ~= nil then
    		SaveTable(thirdPolicystatus);
    	end
    end
    
    if args ~= nil then
    	SaveTable(args);
    end
    
    if read_body ~= nil then
    	SaveTable(read_body);
    end
    
    if body_data ~= nil then
    	SaveTable(body_data);
    end
    
    if getargs ~= nil then
    	thirdPolicystatus = getargs["thirdPolicystatus"]
    	if thirdPolicystatus ~= nil and thirdPolicystatus == "1" then
    		SaveTable("new_version-getargs");
    		ngx.exec('@new_version')
    	elseif thirdPolicystatus ~= nil and thirdPolicystatus == "2" then
    		SaveTable("old_version-getargs");
    		ngx.exec('@old_version')
    	else
    		SaveTable("default_version-getargs");
    		ngx.exec('@default_version')
    	end
    end
    
    if args ~= nil then
    	if type(args) == "table" then
    		thirdPolicystatus = tostring(args["thirdPolicystatus"])
    		if thirdPolicystatus ~= nil and thirdPolicystatus == 1 then
    			SaveTable("new_version-args-table");
    			ngx.exec('@new_version')
    		elseif thirdPolicystatus ~= nil and thirdPolicystatus == 2 then
    			SaveTable("old_version-args-table");
    			ngx.exec('@old_version')
    		else
    			SaveTable("default_version-args-table");
    			ngx.exec('@default_version')
    		end
    	elseif type(args) == "string" then
    		local json = require("cjson")
    		local jsonObj = json.decode(args)
    			thirdPolicystatus = jsonObj['thirdPolicystatus']
    		if thirdPolicystatus ~= nil and thirdPolicystatus == 1 then
    			SaveTable("new_version-args-string");
    			ngx.exec('@new_version')
    		elseif thirdPolicystatus ~= nil and thirdPolicystatus == 2 then
    			SaveTable("old_version-args-string");
    			ngx.exec('@old_version')
    		else
    			SaveTable("default_version-args-string");
    			ngx.exec('@default_version')
    		end
    	end
    end
    return
    

    host如下:

    127.0.0.1  域名

    访问地址:

    域名

    菜单运营数据---保单数据,默认走default集群,保单状态承保成功走new_version集群,保单状态终止走old_version集群

    2、根据请求参数或ip等进行匹配redis缓存数据进行路由,灵活性更高。

    redis下载地址:https://github.com/tporadowski/redis/releases

    nginx配置如下:

    #user  nobody;
    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 '$time_local 客户端地址:$remote_addr–$remote_port 请求的URI和HTTP协议:$request 请求地址:$http_host HTTP请求状态:$status upstream状态:$upstream_status 负载地址:$upstream_addr url跳转来源:$http_referer $body_bytes_sent $http_user_agent $request_uri';
        log_format  logFormat '$group $time_local 客户端:$remote_addr–$remote_port 请求的URI和HTTP协议:$request 请求:$http_host HTTP状态:$status upstream状态:$upstream_status 负载:$upstream_addr 
                              url跳转:$http_referer $body_bytes_sent $http_user_agent $request_uri 请求参数 $query_string $args $document_root $uri
                              -----$request_uri $request_filename $http_cookie';
        access_log  logs/access.log  logFormat;
        sendfile        on;
        #tcp_nopush     on;
        #keepalive_timeout  0;
        keepalive_timeout  65;
       server{
            listen       80;   #监听端口
            server_name  域名; #监听地址 
            access_log  logs/xx.com.access.log  logFormat;
                  location /redis {
                    default_type 'text/plain';
                    content_by_lua 'ngx.say("hello ,lua scripts redis")';
            }
            location / {
                    default_type 'text/plain';
                    lua_need_request_body on;
                    content_by_lua_file D:/user/Downloads/openresty-1.19.9.1-win64/conf/redis.lua; # 指定由lua文件处理http请求
            }
            location @pre-prd {
                proxy_pass http://pre-prd;
                proxy_set_header Host $http_host;
                #proxy_redirect default;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            }
            location @prd {
                proxy_pass http://prd; 
                proxy_set_header Host $http_host;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;    
            }
        }
        #预发2演示线上
    	upstream prd {
    		 server ip:port;
    	}
        #预发演示预发线上
    	upstream pre-prd {
    		 server ip:port;
    	}
    }

    lua脚本如下:

    --get请求uri参数
    function SaveTableContent(file, obj)
          local szType = type(obj);
          print(szType);
          if szType == "number" then
                file:write(obj);
          elseif szType == "string" then
                file:write(string.format("%q", obj));
          elseif szType == "table" then
                --把table的内容格式化写入文件
                --file:write("{\n");
                for i, v in pairs(obj) do
                      SaveTableContent(file, i);
                      file:write(":");
                      SaveTableContent(file, v);
                      file:write(",");
    			end
                --file:write("}\n");
          else
    			error("can't serialize a "..szType);
          end
    end
    
    function SaveTable(obj)
          --local file = io.open("D:\\user\\Downloads\\openresty-1.19.9.1-win64\\logs\\parmas.txt", "a");
          local file = io.open("D:\\user\\Downloads\\openresty-1.19.9.1-win64\\logs\\redis.txt", "a");
          assert(file);
          SaveTableContent(file,obj);
          file:close();
    end
    
    
    local request_method = ngx.var.request_method
    local getargs = nil
    local args = nil
    local read_body = nil
    local body_data = nil
    local thirdPolicystatus = nil
    if "GET" == request_method then
    	args = ngx.req.get_uri_args()
    elseif "POST"== request_method then
    	getargs = ngx.req.get_uri_args()
    	args  	  = ngx.req.get_post_args()
    	read_body = ngx.req.read_body()
    	body_data = ngx.req.get_body_data()
    end
    
    if getargs ~= nil then
    	SaveTable("getargs");
    	SaveTable(getargs);
    	thirdPolicystatus= getargs["thirdPolicystatus"];
    	if thirdPolicystatus ~= nil then
    		SaveTable("thirdPolicystatus");
    		SaveTable(thirdPolicystatus);
    	end
    end
    
    if args ~= nil then
    	SaveTable("args");
    	SaveTable(args);
    end
    
    if read_body ~= nil then
    	SaveTable("read_body");
    	SaveTable(read_body);
    end
    
    if body_data ~= nil then
    	SaveTable("body_data");
    	SaveTable(body_data);
    end
    
    
    local redis = require "resty.redis"
    local cache = redis.new()
    cache:set_timeout(60000)
    
    local ok, err = cache.connect(cache, '127.0.0.1', 6379)
    if not ok then
    	SaveTable("not ok");
    	ngx.exec("@prd")
    	return
    end
    
    local local_ip = ngx.req.get_headers()["X-Real-IP"]
    if local_ip == nil then
            local_ip = ngx.req.get_headers()["x_forwarded_for"]
    		SaveTable("local_ip1");
    		if local_id ~= nil then
    		SaveTable(local_id);
    		end
    end
    
    if local_ip == nil then
    	local_ip = ngx.var.remote_addr
    	SaveTable("local_ip2");
    	if local_id ~= nil then
    	SaveTable(local_id);
    	end
    end
    
    -- 在 redis 中根据客户端 ip 获取是否存在值
    local res, err = cache:get(local_ip)
    -- 如果存在则转发到 @pre-prd
    if res == "1" then
    		SaveTable(res);
    		SaveTable("pre-prd");
            ngx.exec("@pre-prd")
            return
    else
    	SaveTable("-------");
    	SaveTable(local_ip);
    	SaveTable(res);
    	cache:set(local_ip)
    end
    
    -- 如果不存在,则转发到 @prd
    SaveTable("prd");
    ngx.exec("@prd")
    
    local ok, err = cache:close()
    if not ok then
        ngx.say("failed to close:", err)
        return
    end
    return
    

    使用时这里根据redis缓里缓存的ip地址进行负载路由。


    三、相关配置与语法

    1、Nginx配置文件详解

    源码:https://trac.nginx.org/nginx/browser

    官网:http://www.nginx.org/

    windows 安装包下载地址:https://nginx.org/en/download.html

    nginx.conf

    ########### 每个指令必须有分号结束。#################
    # 全局块  比如工作进程数,定义日志路径;
    #配置用户或者组,默认为nobody nobody。
    #user  nobody;
    #user administrator administrators;
    
    #允许生成的进程数,默认为1,一般建议设成CPU核数1-2倍
    worker_processes 1;
    #worker_processes  8;
    
    
    #指定nginx进程运行文件存放地址
    #pid /nginx/pid/nginx.pid;
    
    #制定日志路径,级别。这个设置可以放入全局块,http块,server块,级别依次为:#debug|info|notice|warn|error|crit|alert|emerg
    error_log logs/error.log error;
    #error_log  logs/error.log;
    #error_log  logs/error.log  notice;
    #error_log  logs/error.log  info;
    
    #Events块 设置处理轮询事件模型,每个工作进程最大连接数及http层的keep-alive超时时间;
    events {
       	#使用epoll的I/O 模型处理轮询事件。
       	#可以不设置,nginx会根据操作系统选择合适的模型
       	#事件驱动模型,select|poll|kqueue|epoll|resig|/dev/poll|eventport
       	#use epoll; 
       	#工作进程的最大连接数量, 默认1024个
       	worker_connections  2048;
    
        #设置网路连接序列化,防止惊群现象发生,默认为on
        accept_mutex on;  
        #设置一个进程是否同时接受多个网络连接,默认为off   
        multi_accept on;  
    }
    
    # http块 路由匹配、静态文件服务器、反向代理、负载均衡等
    http {
     	# 导入文件扩展名与文件类型映射表 mime.types
        include       mime.types;
        #默认文件类型,默认为text/plain
        default_type  application/octet-stream;
        #取消服务日志 
        #access_log off; 
        #日志格式及access日志路径 自定义格式
        log_format myFormat '$time_local 客户端地址:$remote_addr–$remote_port 请求的URI和HTTP协议:$request 请求地址:$http_host HTTP请求状态:$status upstream状态:$upstream_status 负载地址:$upstream_addr url跳转来源:$http_referer $upstream_addr $body_bytes_sent $http_user_agent';
        #combined为日志格式的默认值
        access_log logs/access.log myFormat;
        #允许sendfile方式传输文件,默认为off,可以在http块,server块,location块。
        sendfile on; 
        #sendfile开启时才开启。
        tcp_nopush   on; 
    	server_names_hash_bucket_size 64; 
        #每个进程每次调用传输数量不能大于设定的值,默认为0,即不设上限。
        sendfile_max_chunk 100k;
        #连接超时时间,默认为75s,可以在http,server,location块。
        keepalive_timeout 65;
    
        #--------------------静态文件压缩-----------------------------#
        #Nginx可以对网站的css、js 、xml、html 文件在传输前进行压缩,大幅提高页面加载速度。经过Gzip压缩后页面大小可以变为原来的30%甚至更小。使用时仅需开启Gzip压缩功能即可。你可以在http全局块或server块增加这个配置。
        # 开启gzip压缩功能
        #gzip  on;
        gzip on;
         
        # 设置允许压缩的页面最小字节数; 这里表示如果文件小于10k,压缩没有意义.
        gzip_min_length 10k; 
     
        # 设置压缩比率,最小为1,处理速度快,传输速度慢;
        # 9为最大压缩比,处理速度慢,传输速度快; 推荐6
        gzip_comp_level 6; 
         
        # 设置压缩缓冲区大小,此处设置为16个8K内存作为压缩结果缓冲
        gzip_buffers 16 8k; 
         
        # 设置哪些文件需要压缩,一般文本,css和js建议压缩。图片视需要要锁。
        gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript; 
        #--------------------静态文件压缩-----------------------------#
    	server {
    		listen       80;
    		server_name  localhost;
    		location / {
                root   html;
                index  index.html index.htm;
            }
            error_page   500 502 503 504  /50x.html;
            location = /50x.html {
                root   html;
            }
        }
        #http server块
        server {
            keepalive_requests 120; #单连接请求上限次数。
            listen       8081;   #监听端口
            server_name  域名 #监听地址
            #ssi on;
    		#autoindex on;
            charset utf-8;
            client_max_body_size 10M; # 限制用户上传文件大小,默认1M
            #access_log  logs/host.access.log  myFormat; #定义访问日志,可以针对每一个server(即每一个站点)设置它们自己的访问日志。
    
            # 转发动态请求到web应用服务器
            #location ^~ /api {
                #rewrite ^/api/(.*)$ /$1 break;
                #proxy_pass https://stream;
                #break;#终止匹配
            #}
    		location / {
               # 使用proxy_pass转发请求到通过upstream定义的一组应用服务器
    			proxy_pass      http://stream ;
    			proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    			proxy_set_header Host $http_host;
    			proxy_redirect off;
    			proxy_set_header X-Real-IP  $remote_addr;
            }
    		location  ~*^.+$ {       #请求的url过滤,正则匹配,~为区分大小写,~*为不区分大小写。  
    			proxy_pass  	http://stream ;  #请求转向stream 定义的服务器列表          
            } 
    		
            #location / {
                #autoindex on;
                #try_files $uri $uri/ /index.html?$args;
            #}
    
            # 规则1:通用匹配
            #location / {
    			#ssi on;
    			#autoindex on;                 #自动显示目录
                #autoindex_exact_size off;     #人性化方式显示文件大小否则以byte显示
                #autoindex_localtime on;       #按服务器时间显示,否则以gmt时间显示
                #root   /root;                 #定义服务器的默认网站根目录位置 
                #index index.html index.htm;   #定义首页索引文件的名称  设置默认页
                # 使用proxy_pass转发请求到通过upstream定义的一组应用服务器
                #proxy_pass   http://mysvr;      #负载配置
                #proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                #proxy_set_header Host $http_host;
                #proxy_redirect off;
                #proxy_set_header X-Real-IP  $remote_addr; 
                #deny ip;  # 拒绝的ip
                #allow ip; # 允许的ip 
            #}
    
            # 规则2:处理以/static/开头的url
            location ^~ /static {                         
                alias /usr/share/nginx/html/static; # 静态资源路径
            }
            #= 精确匹配 1
            #^~ 以某个字符串开头 2
            #~ 区分大小写的正则匹配 3
            #~* 不区分大小写的正则匹配 4
            #!~ 区分大小写的不匹配正则 5
            #!~* 不区分大小写的不匹配正则 6
            #/ 通用匹配,任何请求都会匹配到 7
            #location  ~*^.+$ {       #请求的url过滤,正则匹配,~为区分大小写,~*为不区分大小写。  
                #root path;  #根目录
                #index vv.txt;  #设置默认页
    			#proxy_pass  http://stream;  #请求转向stream 定义的服务器列表
                #deny 127.0.0.1;  #拒绝的ip
                #allow ip; #允许的ip           
            #} 
            #-----------------------------静态文件缓存--------------------#
            #缓存可以加快下次静态文件加载速度。我们很多与网站样式相关的文件比如css和js文件一般不怎么变化,缓存有效器可以通过expires选项设置得长一些。
            # 使用expires选项开启静态文件缓存,10天有效
            location ~ ^/(images|javascript|js|css|flash|media|static)/  {
                 root    /var/www/big.server.com/static_files;
                expires 10d;
            }
    		#-----------------------------静态文件缓存--------------------#
            # 错误页面
    		error_page   500 502 503 504  /50x.html;
            location = /50x.html {
                root   html;
            }
        }
    
        #-------------$符号的全局变量含义--------------#
        #$args, 请求中的参数;
        #$content_length, HTTP请求信息里的"Content-Length";
        #$content_type, 请求信息里的"Content-Type";
        #$document_root, 针对当前请求的根路径设置值;
        #$document_uri, 与$uri相同;
        #$host, 请求信息中的"Host",如果请求中没有Host行,则等于设置的服务器名;
        #$limit_rate, 对连接速率的限制;
        #$request_method, 请求的方法,比如"GET"、"POST"等;
        #$remote_addr, 客户端地址;
        #$remote_port, 客户端端口号;
        #$remote_user, 客户端用户名,认证用;
        #$request_filename, 当前请求的文件路径名
        #$request_body_file,当前请求的文件
        #$request_uri, 请求的URI,带查询字符串;
        #$query_string, 与$args相同;
        #$scheme, 所用的协议,比如http或者是https,比如rewrite ^(.+)$
        #$scheme://example.com$1 redirect;        
        #$server_protocol, 请求的协议版本,"HTTP/1.0"或"HTTP/1.1";
        #$server_addr, 服务器地址;
        #$server_name, 请求到达的服务器名;
        #$server_port, 请求到达的服务器端口号;
        #$uri, 请求的URI,可能和最初的值有不同,比如经过重定向之类的。
        #-------------$符号的全局变量含义--------------#
    
        
        #错误页面
        #error_page 404 https://www.baidu.com; #错误页
        #error_page 404 500 502 503 504 403 /error.shtml;
        
        # 负载均衡
        upstream insurance-pre {   
          #weigth参数表示权值,权值越高被分配到的几率越大
          #--------------------负载均衡方式------------------#
          #1.轮询(默认)
          #2.权重,weight越大,承担任务越多
          #server ip:port weight=5
          #3.ip_hash   
          #ip_hash;
          #4.url_hash
          #hash $request_uri;
          #5. fair(第三方)--按后端服务器的响应时间来分配请求,响应时间短的优先分配。使用这个算法需要安装nginx-upstream-fair这个库。
          #fair;
          #--------------------负载均衡方式------------------#
          server ip:port   weight=5; # weight越高,权重越大
          server ip:port weight=1;
          server ip:port  weight=1;
          server ip:port backup; # 热备
        }
    	# 转发动态请求
        #server {  
            #listen 80;                                                         
            #server_name  localhost;                                               
            #client_max_body_size 1024M;
            #location / {
                #proxy_pass http://localhost:8080;   
                #proxy_set_header Host $host:$server_port;
            #}
        #} 
        # http请求重定向到https请求
        #server {
            #listen 80;
            #server_name 域名;
            #return 301 https://$server_name$request_uri;
        #}
        server {
            keepalive_requests 120; #单连接请求上限次数。
            listen       80;   #监听端口
            server_name  域名 #监听地址
            #ssi on;
    		#autoindex on;
            charset utf-8;
            client_max_body_size 10M; # 限制用户上传文件大小,默认1M
            #access_log  logs/host.access.log  myFormat; #定义访问日志,可以针对每一个server(即每一个站点)设置它们自己的访问日志。
            # 转发动态请求到web应用服务器
            #location ^~ /api {
                #rewrite ^/api/(.*)$ /$1 break;
                #proxy_pass https://域名;
                #break;#终止匹配
            #}
    		location / {
               # 使用proxy_pass转发请求到通过upstream定义的一组应用服务器
    			proxy_pass       http://tomcat_gray1;
    			proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    			proxy_set_header Host $http_host;
    			proxy_redirect off;
    			proxy_set_header X-Real-IP  $remote_addr;
            }
    		location  ~*^.+$ {       #请求的url过滤,正则匹配,~为区分大小写,~*为不区分大小写。  
    			proxy_pass  	http://域名;  #请求转向域名 定义的服务器列表          
            } 
        }
        #标准预发环境
    	upstream tomcat_gray1 {
    		server ip; 
    		server 域名;
    	}
    
    	upstream tomcat_gray2 {
    		server 域名;
    	}
    }

    host 配置

    127.0.0.1  域名

    浏览器访问 域名

    可以通过观察access.log发现请求接入日志。

    2、lua基础语法

    教程:https://www.runoob.com/lua/if-else-statement-in-lua.html

    lua的IDE编辑器:https://github.com/rjpcomputing/luaforwindows

    3、nginx实现灰度

    根据前端请求参数进行灰度到不同节点。

    #user  nobody;
    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 '$time_local 客户端地址:$remote_addr–$remote_port 请求的URI和HTTP协议:$request 请求地址:$http_host HTTP请求状态:$status upstream状态:$upstream_status 负载地址:$upstream_addr url跳转来源:$http_referer $body_bytes_sent $http_user_agent $request_uri';
        log_format  logFormat '$group $time_local 客户端:$remote_addr–$remote_port 请求的URI和HTTP协议:$request 请求:$http_host HTTP状态:$status upstream状态:$upstream_status 负载:$upstream_addr 
                              url跳转:$http_referer $body_bytes_sent $http_user_agent $request_uri 请求参数 $query_string $args $document_root $uri
                              -----$request_uri $request_filename $http_cookie';
        access_log  logs/access.log  logFormat;
        sendfile        on;
        #tcp_nopush     on;
        #keepalive_timeout  0;
        keepalive_timeout  65;
        #gzip  on;
        server {
            listen       80;   #监听端口
            server_name  域名; #监听地址 
            access_log  logs/xx.com.access.log  logFormat;
            #方式二、nginx+lua实现灰度
            ## 1、将对localhost访问由/opt/app/lua/dep.lua进行处理
            ## 2、根据逻辑处理后,决定回调如下两个其中1个内部跳转
            #方式三根据请求参数值匹配进行路由
            #/policy/policyInfoList?thirdPolicystatus=2
            set $group "default";
            if ($query_string ~* "thirdPolicystatus=1"){ #动态控制路由
                set $group new_version;
            }
            if ($query_string ~* "thirdPolicystatus=2"){
                set $group old_version;
            }
            location / 
            {
                default_type "text/html"; 
                #content_by_lua_file D:/sortware/openresty/openresty-1.17.8.2-win64/conf/dep.lua; # 指定由lua文件处理http请求
                proxy_pass http://$group;
                proxy_set_header  Host       $host;
                proxy_set_header  X-Real-IP    $remote_addr;
                proxy_set_header  X-Forwarded-For $proxy_add_x_forwarded_for;
                index index.html index.htm;
            }
        }
    	#标准预发环境
    	upstream default {
    		server ip:port; 
    	}
        #预发2
    	upstream new_version {
    		server ip:port;
    	}
        #预发3
    	upstream old_version {
    		server ip:port;
    	}
    }

    host如下:

    127.0.0.1  域名

    访问地址:

    域名

    菜单运营数据---保单数据,默认走default集群,保单状态承保成功走new_version集群,保单状态终止走old_version集群

    根据cookie内的参数进行负载

    
    #user  nobody;
    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 '$time_local 客户端地址:$remote_addr–$remote_port 请求的URI和HTTP协议:$request 请求地址:$http_host HTTP请求状态:$status upstream状态:$upstream_status 负载地址:$upstream_addr url跳转来源:$http_referer $body_bytes_sent $http_user_agent $request_uri';
        log_format  logFormat '$http_cookie $group $time_local 客户端:$remote_addr–$remote_port 请求的URI和HTTP协议:$request 请求:$http_host HTTP状态:$status upstream状态:$upstream_status 负载:$upstream_addr 
                              url跳转:$http_referer $body_bytes_sent $http_user_agent $request_uri 请求参数 $query_string $args $document_root $uri
                              -----$request_uri $request_filename ';
        access_log  logs/access.log  logFormat;
    
        sendfile        on;
        #tcp_nopush     on;
    
        #keepalive_timeout  0;
        keepalive_timeout  65;
        #gzip  on;
        server {
            listen       80;   #监听端口
            server_name  域名; #监听地址 
            access_log  logs/xx.com.access.log  logFormat;
            #方式二、nginx+lua实现灰度
            ## 1、将对localhost访问由/opt/app/lua/dep.lua进行处理
            ## 2、根据逻辑处理后,决定回调如下两个其中1个内部跳转
            #方式三根据请求参数值匹配进行路由
            #域名policy/policyInfoList?thirdPolicystatus=2
            set $group "default";
            if ($query_string ~* "thirdPolicystatus=1"){ #动态控制路由
                set $group new_version;
                }
            if ($query_string ~* "thirdPolicystatus=2"){
                set $group old_version;
                }
            if ($http_cookie ~* "sso.xx.com=BJ.E2C7D319112E7F6252BF010770269E235820211121073248"){
                set $group pro_version;
                }
            if ($http_cookie ~* "sso.xx.com!=BJ.E2C7D319112E7F6252BF010770269E235820211121073248"){
                set $group grey_version;
                }
            location / 
            {
                default_type "text/html"; 
                #content_by_lua_file D:/sortware/openresty/openresty-1.17.8.2-win64/conf/dep.lua; # 指定由lua文件处理http请求
                proxy_pass http://$group;
                proxy_set_header  Host       $host;
                proxy_set_header  X-Real-IP    $remote_addr;
                proxy_set_header  X-Forwarded-For $proxy_add_x_forwarded_for;
                index index.html index.htm;
            }
        }
    	#标准预发环境
    	upstream default {
    		server ip:port; 
    	}
        #预发2
    	upstream new_version {
    		server ip:port;
    	}
        #预发3
    	upstream old_version {
    		server ip:port;
    	}
            #预发2
    	upstream pro_version {
    		server ip:port;
    	}
        #预发3
    	upstream grey_version {
    		server ip:port;
    	}
    }

    根据cookie内容转发到不同的集群

    四、相关可操作和替换性

    想法一:如果这个时候我门需要一个动态化配置控制台则可以通过javaweb等工程进行操作redis实现实时更新redis数据从而控制灰度

    想法二:切换其他数据源比如

    1. MySQL/MariaDB: 使用 Lua 的 lua-mysql 或 LuaSQL 库,您可以在 Lua 中连接和查询 MySQL 或 MariaDB 数据库。
    2. PostgreSQL: 使用 Lua 的 lua-postgres 或 LuaSQL 库,您可以在 Lua 中连接和查询 PostgreSQL 数据库。
    3. MongoDB: 使用 Lua 的 mongo-lua-driver 库,您可以在 Lua 中连接和操作 MongoDB 数据库。
    4. HTTP API: 使用 Lua 的 LuaHTTP 库,您可以在 Lua 中发起 HTTP 请求,并与远程的 HTTP API 进行通信。
    5. Cassandra: 使用 Lua 的 lua-cassandra 库,您可以在 Lua 中连接和查询 Cassandra 数据库。

    想法三:切换其他脚本语言

    1. JavaScript: 通过使用 Nginx 的 ngx_http_js_module,您可以在 Nginx 中使用 JavaScript。这可以让您使用 JavaScript 脚本来实现一些灰度发布或其他功能。此外,JavaScript 也广泛用于前端开发,因此可以在前后端一体化的项目中更容易共享代码逻辑。
    2. LuaJIT: LuaJIT 是一个通过即时编译实现高性能的 Lua 解释器。它提供了与标准 Lua 解释器兼容的 API,但是比标准 Lua 解释器更快。使用 LuaJIT,您可以获得更高的性能,同时保持与 Lua 的兼容性。
    3. Python: 如果您已经熟悉 Python,您可以使用 Python-NGINX-Module 在 Nginx 中嵌入 Python。这样可以使用 Python 编写 Nginx 的配置文件和处理请求的逻辑。
    4. Java: 使用 nginx-jvm-clojure 或 nginx-jwt 等模块,您可以在 Nginx 中嵌入 Java 或 Clojure。这些模块提供了在 Nginx 上运行 Java 或 Clojure 代码的功能,可以与其他 Java 或 Clojure 库和框架进行集成。

    想法四:切换其他web服务器或反向代理服务器

    1. Apache HTTP Server: Apache 是一个广泛使用的开源 Web 服务器和反向代理服务器,它支持多种模块和扩展,提供了丰富的功能和配置选项。
    2. Microsoft IIS: Internet Information Services (IIS) 是由 Microsoft 开发的 Web 服务器,专为 Windows 操作系统设计。它是 Windows Server 默认的 Web 服务器,提供了广泛的功能和集成。
    3. Caddy: Caddy 是一个用 Go 编写的现代化的 Web 服务器和反向代理服务器。它具有简单配置、自动 HTTPS、HTTP/2 支持等特性。
    4. HAProxy: HAProxy 是一个高性能的负载均衡器和反向代理服务器,适用于高流量的 Web 应用程序。它具有丰富的负载均衡和代理功能。
    5. Envoy: Envoy 是一个轻量级的开源代理服务器和通信总线,适用于云原生和微服务架构。它具有动态配置、负载平衡、流量管理等功能。

    大家可以根据自己的想法或者兴趣进行研究,本文不做过多介绍

    五、其他相关优秀文章传送门

    一种简单从端上进行服务端上线灰度验证方案

    功能级灰度开量工具

    采购域灰度环境搭建策略

    基于istio的灰度发布架构方案实践之路

    【稳定性】履约平台组上线发布三板斧(可灰度、可验证、可回滚)

    代码层面的灰度

    几年前写的一个灰度工具(拿来即用)

    【行云部署】小狗漫画-指定ip的灰度部署