前言:
授人以鱼不如授人以渔.先学会用,在学原理,在学创造,可能一辈子用不到这种能力,但是不能不具备这种能力。这篇文章主要是沉淀使用nginx+lua+redis实现灰度,当我们具备了这种能力,随时可以基于这种能力和思想调整实现方案:比如nginx+lua+(其他数据源)、nginx+(其他脚本语言)
一、灰度方案:
常见的灰度实现方案:
-
请求路由:通过请求中的标识(如用户ID、设备ID、请求头等)来决定是否将请求路由到灰度环境。可以使用反向代理(如Nginx、Envoy)或API网关(如Kong、Apigee)来实现路由规则。
-
权重控制:将流量按照一定的权重比例分配到不同的环境中。可以通过负载均衡器(如HAProxy、Kubernetes Ingress)或代理服务器(如Nginx、Envoy)来实现权重控制。
-
特性开关:通过在代码中嵌入特性开关(Feature Flag)来控制功能的开启与关闭。可以使用配置文件、数据库、键值存储或特性管理平台(如LaunchDarkly、Unleash)来管理特性开关。
-
分阶段发布:将功能的发布分为多个阶段,从内部测试到灰度环境再到全量发布。可以使用部署工具(如Jenkins、GitLab CI/CD)或云平台(如AWS、Azure)来支持分阶段发布。
-
A/B测试:将流量分为多个不同版本的应用程序,比较它们的性能和用户反馈。可以使用A/B测试平台(如Optimizely、Google Optimize)来管理和监控A/B测试。
-
金丝雀发布:将新版本的应用程序逐步引入生产环境,仅将少量流量导向新版本,并根据其性能和稳定性逐步增加流量。可以使用部署工具、容器编排平台或云平台来实现金丝雀发布。
常用的灰度发布方案:
-
基于用户ID的灰度发布:基于用户ID来划分灰度用户或百分比灰度,例如根据用户ID的哈希值或随机数来决定用户是否路由到灰度环境。
-
基于IP地址的灰度发布:根据用户的IP地址来划分灰度用户,例如将某一范围的IP地址指定为灰度用户,将请求从这些IP地址路由到灰度环境。
-
Cookie/Session的灰度发布:通过在用户的Cookie或会话中设置特定的标识来划分灰度用户。例如,将特定的Cookie或会话变量设置为灰度标识,将具有该标识的请求路由到灰度环境。
-
请求头的灰度发布:基于请求头中的特定标识来划分灰度用户。例如,根据请求头中的自定义标识或特定的HTTP Header来路由请求到灰度环境。
-
权重或百分比的灰度发布:将请求随机分配给不同的环境,可以通过给不同环境设置不同的权重或百分比来控制流量的分配。
-
A/B测试:将流量分为多个不同版本的应用程序,在实验期间比较它们的性能和用户反馈,最终选择最佳版本进行全量发布。
二、nginx+lua+redis实现灰度
理论:
1、安装并配置 Nginx 和 Redis。确保 Nginx 启用 Lua 模块,并可以访问 Redis。
2、在 Nginx 配置中定义灰度规则。您可以使用 Lua 脚本来判断用户是否应该被路由到灰度环境。示例配置如下:
1server { 2 listen 80; 3 server_name example.com; 4 location / { 5 access_by_lua_block { 6 local redis = require "resty.redis" 7 local red = redis:new() 8 -- 连接到 Redis 9 local ok, err = red:connect("redis_host", redis_port) 10 if not ok then 11 ngx.log(ngx.ERR, "failed to connect to Redis: ", err) 12 ngx.exit(500) 13 end 14 -- 使用 Redis 根据用户 ID 判断是否路由到灰度环境 15 local user_id = ngx.req.get_headers()["X-User-ID"] 16 local is_gray = red:get("gray:" .. user_id) 17 if is_gray == "1" then 18 ngx.var.upstream = "gray_backend" 19 end 20 } 21 proxy_pass http://backend; 22 } 23 location /gray { 24 # 灰度环境的配置 25 proxy_pass http://gray_backend; 26 } 27 location /admin { 28 # 管理后台的配置 29 proxy_pass http://admin_backend; 30 } 31} 32 33
在上面的示例中,我们连接到 Redis,并根据请求中的用户 ID 判断是否将请求路由到灰度环境。ngx.var.upstream 变量用于动态设置上游地址,从而实现灰度环境的路由。
3、在 Redis 中设置灰度用户。您可以在 Redis 中维护一个键值对,其中键是用户 ID,值表示是否是灰度用户(例如,1 表示是灰度用户,0 表示不是)。您可以使用 Redis 的 SET 和 GET 命令来操作这些值。
1-- 设置用户为灰度用户 2local ok, err = red:set("gray:" .. user_id, 1) 3if not ok then 4 ngx.log(ngx.ERR, "failed to set gray status for user: ", err) 5 ngx.exit(500) 6end 7 8-- 设置用户为非灰度用户 9local ok, err = red:set("gray:" .. user_id, 0) 10if not ok then 11 ngx.log(ngx.ERR, "failed to set gray status for user: ", err) 12 ngx.exit(500) 13end 14 15 16 17
通过在 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配置如下:
1#user nobody; 2worker_processes 1; 3#error_log logs/error.log; 4#error_log logs/error.log notice; 5#error_log logs/error.log info; 6#pid logs/nginx.pid; 7events { 8 worker_connections 1024; 9} 10http { 11 include mime.types; 12 default_type application/octet-stream; 13 #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'; 14 log_format logFormat '$group $time_local 客户端:$remote_addr–$remote_port 请求的URI和HTTP协议:$request 请求:$http_host HTTP状态:$status upstream状态:$upstream_status 负载:$upstream_addr 15 url跳转:$http_referer $body_bytes_sent $http_user_agent $request_uri 请求参数 $query_string $args $document_root $uri 16 -----$request_uri $request_filename $http_cookie'; 17 access_log logs/access.log logFormat; 18 sendfile on; 19 #tcp_nopush on; 20 #keepalive_timeout 0; 21 keepalive_timeout 65; 22 server{ 23 listen 80; #监听端口 24 server_name 域名; #监听地址 25 access_log logs/xx.com.access.log logFormat; 26 location /hello { 27 default_type 'text/plain'; 28 content_by_lua 'ngx.say("hello ,lua scripts")'; 29 } 30 location /myip { 31 default_type 'text/plain'; 32 content_by_lua ' 33 clientIP = ngx.req.get_headers()["x_forwarded_for"] 34 ngx.say("Forwarded_IP:",clientIP) 35 if clientIP == nli then 36 clientIP = ngx.var.remote_addr 37 ngx.say("Remote_IP:",clientIP) 38 end 39 '; 40 } 41 location / { 42 default_type 'text/plain'; 43 lua_need_request_body on; 44 #content_by_lua_file /etc/nginx/lua/dep.lua; 45 #content_by_lua_file D:/sortware/openresty/openresty-1.17.8.2-win64/conf/dep.lua; # 指定由lua文件处理http请求 46 content_by_lua_file D:/user/Downloads/openresty-1.19.9.1-win64/conf/dep.lua; # 指定由lua文件处理http请求 47 } 48 location @default_version { 49 proxy_pass http://default; 50 proxy_set_header Host $http_host; 51 proxy_set_header X-Real-IP $remote_addr; 52 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 53 } 54 location @new_version { 55 proxy_pass http://new_version; 56 proxy_set_header Host $http_host; 57 #proxy_redirect default; 58 proxy_set_header X-Real-IP $remote_addr; 59 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 60 } 61 location @old_version { 62 proxy_pass http://old_version; 63 proxy_set_header Host $http_host; 64 proxy_set_header X-Real-IP $remote_addr; 65 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 66 } 67 } 68 #标准预发环境 69 upstream default { 70 server ip:port; 71 } 72 #预发2 73 upstream new_version { 74 server ip:port; 75 } 76 #预发3 77 upstream old_version { 78 server ip:port; 79 } 80} 81 82
lua脚本如下:
1--get请求uri参数 2 3function SaveTableContent(file, obj) 4 local szType = type(obj); 5 print(szType); 6 if szType == "number" then 7 file:write(obj); 8 elseif szType == "string" then 9 file:write(string.format("%q", obj)); 10 elseif szType == "table" then 11 --把table的内容格式化写入文件 12 --file:write("{\n"); 13 for i, v in pairs(obj) do 14 SaveTableContent(file, i); 15 file:write(":"); 16 SaveTableContent(file, v); 17 file:write(","); 18 end 19 --file:write("}\n"); 20 else 21 error("can't serialize a "..szType); 22 end 23end 24 25function SaveTable(obj) 26 27 local file = io.open("D:\\user\\Downloads\\openresty-1.19.9.1-win64\\logs\\parmas.txt", "a"); 28 assert(file); 29 SaveTableContent(file,obj); 30 file:close(); 31end 32 33local request_method = ngx.var.request_method 34local getargs = nil 35local args = nil 36local read_body = nil 37local body_data = nil 38local thirdPolicystatus = nil 39if "GET" == request_method then 40 args = ngx.req.get_uri_args() 41elseif "POST"== request_method then 42 getargs = ngx.req.get_uri_args() 43 args = ngx.req.get_post_args() 44 read_body = ngx.req.read_body() 45 body_data = ngx.req.get_body_data() 46end 47if getargs ~= nil then 48 SaveTable(getargs); 49 thirdPolicystatus= getargs["thirdPolicystatus"]; 50 if thirdPolicystatus ~= nil then 51 SaveTable(thirdPolicystatus); 52 end 53end 54 55if args ~= nil then 56 SaveTable(args); 57end 58 59if read_body ~= nil then 60 SaveTable(read_body); 61end 62 63if body_data ~= nil then 64 SaveTable(body_data); 65end 66 67if getargs ~= nil then 68 thirdPolicystatus = getargs["thirdPolicystatus"] 69 if thirdPolicystatus ~= nil and thirdPolicystatus == "1" then 70 SaveTable("new_version-getargs"); 71 ngx.exec('@new_version') 72 elseif thirdPolicystatus ~= nil and thirdPolicystatus == "2" then 73 SaveTable("old_version-getargs"); 74 ngx.exec('@old_version') 75 else 76 SaveTable("default_version-getargs"); 77 ngx.exec('@default_version') 78 end 79end 80 81if args ~= nil then 82 if type(args) == "table" then 83 thirdPolicystatus = tostring(args["thirdPolicystatus"]) 84 if thirdPolicystatus ~= nil and thirdPolicystatus == 1 then 85 SaveTable("new_version-args-table"); 86 ngx.exec('@new_version') 87 elseif thirdPolicystatus ~= nil and thirdPolicystatus == 2 then 88 SaveTable("old_version-args-table"); 89 ngx.exec('@old_version') 90 else 91 SaveTable("default_version-args-table"); 92 ngx.exec('@default_version') 93 end 94 elseif type(args) == "string" then 95 local json = require("cjson") 96 local jsonObj = json.decode(args) 97 thirdPolicystatus = jsonObj['thirdPolicystatus'] 98 if thirdPolicystatus ~= nil and thirdPolicystatus == 1 then 99 SaveTable("new_version-args-string"); 100 ngx.exec('@new_version') 101 elseif thirdPolicystatus ~= nil and thirdPolicystatus == 2 then 102 SaveTable("old_version-args-string"); 103 ngx.exec('@old_version') 104 else 105 SaveTable("default_version-args-string"); 106 ngx.exec('@default_version') 107 end 108 end 109end 110return 111 112 113 114
host如下:
1127.0.0.1 域名 2 3
访问地址:
1域名 2 3
菜单运营数据---保单数据,默认走default集群,保单状态承保成功走new_version集群,保单状态终止走old_version集群
2、根据请求参数或ip等进行匹配redis缓存数据进行路由,灵活性更高。
redis下载地址:https://github.com/tporadowski/redis/releases
nginx配置如下:
1#user nobody; 2worker_processes 1; 3#error_log logs/error.log; 4#error_log logs/error.log notice; 5#error_log logs/error.log info; 6#pid logs/nginx.pid; 7events { 8 worker_connections 1024; 9} 10http { 11 include mime.types; 12 default_type application/octet-stream; 13 #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'; 14 log_format logFormat '$group $time_local 客户端:$remote_addr–$remote_port 请求的URI和HTTP协议:$request 请求:$http_host HTTP状态:$status upstream状态:$upstream_status 负载:$upstream_addr 15 url跳转:$http_referer $body_bytes_sent $http_user_agent $request_uri 请求参数 $query_string $args $document_root $uri 16 -----$request_uri $request_filename $http_cookie'; 17 access_log logs/access.log logFormat; 18 sendfile on; 19 #tcp_nopush on; 20 #keepalive_timeout 0; 21 keepalive_timeout 65; 22 server{ 23 listen 80; #监听端口 24 server_name 域名; #监听地址 25 access_log logs/xx.com.access.log logFormat; 26 location /redis { 27 default_type 'text/plain'; 28 content_by_lua 'ngx.say("hello ,lua scripts redis")'; 29 } 30 location / { 31 default_type 'text/plain'; 32 lua_need_request_body on; 33 content_by_lua_file D:/user/Downloads/openresty-1.19.9.1-win64/conf/redis.lua; # 指定由lua文件处理http请求 34 } 35 location @pre-prd { 36 proxy_pass http://pre-prd; 37 proxy_set_header Host $http_host; 38 #proxy_redirect default; 39 proxy_set_header X-Real-IP $remote_addr; 40 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 41 } 42 location @prd { 43 proxy_pass http://prd; 44 proxy_set_header Host $http_host; 45 proxy_set_header X-Real-IP $remote_addr; 46 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 47 } 48 } 49 #预发2演示线上 50 upstream prd { 51 server ip:port; 52 } 53 #预发演示预发线上 54 upstream pre-prd { 55 server ip:port; 56 } 57} 58 59
lua脚本如下:
1--get请求uri参数 2function SaveTableContent(file, obj) 3 local szType = type(obj); 4 print(szType); 5 if szType == "number" then 6 file:write(obj); 7 elseif szType == "string" then 8 file:write(string.format("%q", obj)); 9 elseif szType == "table" then 10 --把table的内容格式化写入文件 11 --file:write("{\n"); 12 for i, v in pairs(obj) do 13 SaveTableContent(file, i); 14 file:write(":"); 15 SaveTableContent(file, v); 16 file:write(","); 17 end 18 --file:write("}\n"); 19 else 20 error("can't serialize a "..szType); 21 end 22end 23 24function SaveTable(obj) 25 --local file = io.open("D:\\user\\Downloads\\openresty-1.19.9.1-win64\\logs\\parmas.txt", "a"); 26 local file = io.open("D:\\user\\Downloads\\openresty-1.19.9.1-win64\\logs\\redis.txt", "a"); 27 assert(file); 28 SaveTableContent(file,obj); 29 file:close(); 30end 31 32 33local request_method = ngx.var.request_method 34local getargs = nil 35local args = nil 36local read_body = nil 37local body_data = nil 38local thirdPolicystatus = nil 39if "GET" == request_method then 40 args = ngx.req.get_uri_args() 41elseif "POST"== request_method then 42 getargs = ngx.req.get_uri_args() 43 args = ngx.req.get_post_args() 44 read_body = ngx.req.read_body() 45 body_data = ngx.req.get_body_data() 46end 47 48if getargs ~= nil then 49 SaveTable("getargs"); 50 SaveTable(getargs); 51 thirdPolicystatus= getargs["thirdPolicystatus"]; 52 if thirdPolicystatus ~= nil then 53 SaveTable("thirdPolicystatus"); 54 SaveTable(thirdPolicystatus); 55 end 56end 57 58if args ~= nil then 59 SaveTable("args"); 60 SaveTable(args); 61end 62 63if read_body ~= nil then 64 SaveTable("read_body"); 65 SaveTable(read_body); 66end 67 68if body_data ~= nil then 69 SaveTable("body_data"); 70 SaveTable(body_data); 71end 72 73 74local redis = require "resty.redis" 75local cache = redis.new() 76cache:set_timeout(60000) 77 78local ok, err = cache.connect(cache, '127.0.0.1', 6379) 79if not ok then 80 SaveTable("not ok"); 81 ngx.exec("@prd") 82 return 83end 84 85local local_ip = ngx.req.get_headers()["X-Real-IP"] 86if local_ip == nil then 87 local_ip = ngx.req.get_headers()["x_forwarded_for"] 88 SaveTable("local_ip1"); 89 if local_id ~= nil then 90 SaveTable(local_id); 91 end 92end 93 94if local_ip == nil then 95 local_ip = ngx.var.remote_addr 96 SaveTable("local_ip2"); 97 if local_id ~= nil then 98 SaveTable(local_id); 99 end 100end 101 102-- 在 redis 中根据客户端 ip 获取是否存在值 103local res, err = cache:get(local_ip) 104-- 如果存在则转发到 @pre-prd 105if res == "1" then 106 SaveTable(res); 107 SaveTable("pre-prd"); 108 ngx.exec("@pre-prd") 109 return 110else 111 SaveTable("-------"); 112 SaveTable(local_ip); 113 SaveTable(res); 114 cache:set(local_ip) 115end 116 117-- 如果不存在,则转发到 @prd 118SaveTable("prd"); 119ngx.exec("@prd") 120 121local ok, err = cache:close() 122if not ok then 123 ngx.say("failed to close:", err) 124 return 125end 126return 127 128 129 130
使用时这里根据redis缓里缓存的ip地址进行负载路由。
三、相关配置与语法
1、Nginx配置文件详解
源码:https://trac.nginx.org/nginx/browser
windows 安装包下载地址:https://nginx.org/en/download.html
nginx.conf
1########### 每个指令必须有分号结束。################# 2# 全局块 比如工作进程数,定义日志路径; 3#配置用户或者组,默认为nobody nobody。 4#user nobody; 5#user administrator administrators; 6 7#允许生成的进程数,默认为1,一般建议设成CPU核数1-2倍 8worker_processes 1; 9#worker_processes 8; 10 11 12#指定nginx进程运行文件存放地址 13#pid /nginx/pid/nginx.pid; 14 15#制定日志路径,级别。这个设置可以放入全局块,http块,server块,级别依次为:#debug|info|notice|warn|error|crit|alert|emerg 16error_log logs/error.log error; 17#error_log logs/error.log; 18#error_log logs/error.log notice; 19#error_log logs/error.log info; 20 21#Events块 设置处理轮询事件模型,每个工作进程最大连接数及http层的keep-alive超时时间; 22events { 23 #使用epoll的I/O 模型处理轮询事件。 24 #可以不设置,nginx会根据操作系统选择合适的模型 25 #事件驱动模型,select|poll|kqueue|epoll|resig|/dev/poll|eventport 26 #use epoll; 27 #工作进程的最大连接数量, 默认1024个 28 worker_connections 2048; 29 30 #设置网路连接序列化,防止惊群现象发生,默认为on 31 accept_mutex on; 32 #设置一个进程是否同时接受多个网络连接,默认为off 33 multi_accept on; 34} 35 36# http块 路由匹配、静态文件服务器、反向代理、负载均衡等 37http { 38 # 导入文件扩展名与文件类型映射表 mime.types 39 include mime.types; 40 #默认文件类型,默认为text/plain 41 default_type application/octet-stream; 42 #取消服务日志 43 #access_log off; 44 #日志格式及access日志路径 自定义格式 45 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'; 46 #combined为日志格式的默认值 47 access_log logs/access.log myFormat; 48 #允许sendfile方式传输文件,默认为off,可以在http块,server块,location块。 49 sendfile on; 50 #sendfile开启时才开启。 51 tcp_nopush on; 52 server_names_hash_bucket_size 64; 53 #每个进程每次调用传输数量不能大于设定的值,默认为0,即不设上限。 54 sendfile_max_chunk 100k; 55 #连接超时时间,默认为75s,可以在http,server,location块。 56 keepalive_timeout 65; 57 58 #--------------------静态文件压缩-----------------------------# 59 #Nginx可以对网站的css、js 、xml、html 文件在传输前进行压缩,大幅提高页面加载速度。经过Gzip压缩后页面大小可以变为原来的30%甚至更小。使用时仅需开启Gzip压缩功能即可。你可以在http全局块或server块增加这个配置。 60 # 开启gzip压缩功能 61 #gzip on; 62 gzip on; 63 64 # 设置允许压缩的页面最小字节数; 这里表示如果文件小于10k,压缩没有意义. 65 gzip_min_length 10k; 66 67 # 设置压缩比率,最小为1,处理速度快,传输速度慢; 68 # 9为最大压缩比,处理速度慢,传输速度快; 推荐6 69 gzip_comp_level 6; 70 71 # 设置压缩缓冲区大小,此处设置为16个8K内存作为压缩结果缓冲 72 gzip_buffers 16 8k; 73 74 # 设置哪些文件需要压缩,一般文本,css和js建议压缩。图片视需要要锁。 75 gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript; 76 #--------------------静态文件压缩-----------------------------# 77 server { 78 listen 80; 79 server_name localhost; 80 location / { 81 root html; 82 index index.html index.htm; 83 } 84 error_page 500 502 503 504 /50x.html; 85 location = /50x.html { 86 root html; 87 } 88 } 89 #http server块 90 server { 91 keepalive_requests 120; #单连接请求上限次数。 92 listen 8081; #监听端口 93 server_name 域名 #监听地址 94 #ssi on; 95 #autoindex on; 96 charset utf-8; 97 client_max_body_size 10M; # 限制用户上传文件大小,默认1M 98 #access_log logs/host.access.log myFormat; #定义访问日志,可以针对每一个server(即每一个站点)设置它们自己的访问日志。 99 100 # 转发动态请求到web应用服务器 101 #location ^~ /api { 102 #rewrite ^/api/(.*)$ /$1 break; 103 #proxy_pass https://stream; 104 #break;#终止匹配 105 #} 106 location / { 107 # 使用proxy_pass转发请求到通过upstream定义的一组应用服务器 108 proxy_pass http://stream ; 109 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 110 proxy_set_header Host $http_host; 111 proxy_redirect off; 112 proxy_set_header X-Real-IP $remote_addr; 113 } 114 location ~*^.+$ { #请求的url过滤,正则匹配,~为区分大小写,~*为不区分大小写。 115 proxy_pass http://stream ; #请求转向stream 定义的服务器列表 116 } 117 118 #location / { 119 #autoindex on; 120 #try_files $uri $uri/ /index.html?$args; 121 #} 122 123 # 规则1:通用匹配 124 #location / { 125 #ssi on; 126 #autoindex on; #自动显示目录 127 #autoindex_exact_size off; #人性化方式显示文件大小否则以byte显示 128 #autoindex_localtime on; #按服务器时间显示,否则以gmt时间显示 129 #root /root; #定义服务器的默认网站根目录位置 130 #index index.html index.htm; #定义首页索引文件的名称 设置默认页 131 # 使用proxy_pass转发请求到通过upstream定义的一组应用服务器 132 #proxy_pass http://mysvr; #负载配置 133 #proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 134 #proxy_set_header Host $http_host; 135 #proxy_redirect off; 136 #proxy_set_header X-Real-IP $remote_addr; 137 #deny ip; # 拒绝的ip 138 #allow ip; # 允许的ip 139 #} 140 141 # 规则2:处理以/static/开头的url 142 location ^~ /static { 143 alias /usr/share/nginx/html/static; # 静态资源路径 144 } 145 #= 精确匹配 1 146 #^~ 以某个字符串开头 2 147 #~ 区分大小写的正则匹配 3 148 #~* 不区分大小写的正则匹配 4 149 #!~ 区分大小写的不匹配正则 5 150 #!~* 不区分大小写的不匹配正则 6 151 #/ 通用匹配,任何请求都会匹配到 7 152 #location ~*^.+$ { #请求的url过滤,正则匹配,~为区分大小写,~*为不区分大小写。 153 #root path; #根目录 154 #index vv.txt; #设置默认页 155 #proxy_pass http://stream; #请求转向stream 定义的服务器列表 156 #deny 127.0.0.1; #拒绝的ip 157 #allow ip; #允许的ip 158 #} 159 #-----------------------------静态文件缓存--------------------# 160 #缓存可以加快下次静态文件加载速度。我们很多与网站样式相关的文件比如css和js文件一般不怎么变化,缓存有效器可以通过expires选项设置得长一些。 161 # 使用expires选项开启静态文件缓存,10天有效 162 location ~ ^/(images|javascript|js|css|flash|media|static)/ { 163 root /var/www/big.server.com/static_files; 164 expires 10d; 165 } 166 #-----------------------------静态文件缓存--------------------# 167 # 错误页面 168 error_page 500 502 503 504 /50x.html; 169 location = /50x.html { 170 root html; 171 } 172 } 173 174 #-------------$符号的全局变量含义--------------# 175 #$args, 请求中的参数; 176 #$content_length, HTTP请求信息里的"Content-Length"; 177 #$content_type, 请求信息里的"Content-Type"; 178 #$document_root, 针对当前请求的根路径设置值; 179 #$document_uri, 与$uri相同; 180 #$host, 请求信息中的"Host",如果请求中没有Host行,则等于设置的服务器名; 181 #$limit_rate, 对连接速率的限制; 182 #$request_method, 请求的方法,比如"GET"、"POST"等; 183 #$remote_addr, 客户端地址; 184 #$remote_port, 客户端端口号; 185 #$remote_user, 客户端用户名,认证用; 186 #$request_filename, 当前请求的文件路径名 187 #$request_body_file,当前请求的文件 188 #$request_uri, 请求的URI,带查询字符串; 189 #$query_string, 与$args相同; 190 #$scheme, 所用的协议,比如http或者是https,比如rewrite ^(.+)$ 191 #$scheme://example.com$1 redirect; 192 #$server_protocol, 请求的协议版本,"HTTP/1.0"或"HTTP/1.1"; 193 #$server_addr, 服务器地址; 194 #$server_name, 请求到达的服务器名; 195 #$server_port, 请求到达的服务器端口号; 196 #$uri, 请求的URI,可能和最初的值有不同,比如经过重定向之类的。 197 #-------------$符号的全局变量含义--------------# 198 199 200 #错误页面 201 #error_page 404 https://www.baidu.com; #错误页 202 #error_page 404 500 502 503 504 403 /error.shtml; 203 204 # 负载均衡 205 upstream insurance-pre { 206 #weigth参数表示权值,权值越高被分配到的几率越大 207 #--------------------负载均衡方式------------------# 208 #1.轮询(默认) 209 #2.权重,weight越大,承担任务越多 210 #server ip:port weight=5 211 #3.ip_hash 212 #ip_hash; 213 #4.url_hash 214 #hash $request_uri; 215 #5. fair(第三方)--按后端服务器的响应时间来分配请求,响应时间短的优先分配。使用这个算法需要安装nginx-upstream-fair这个库。 216 #fair; 217 #--------------------负载均衡方式------------------# 218 server ip:port weight=5; # weight越高,权重越大 219 server ip:port weight=1; 220 server ip:port weight=1; 221 server ip:port backup; # 热备 222 } 223 # 转发动态请求 224 #server { 225 #listen 80; 226 #server_name localhost; 227 #client_max_body_size 1024M; 228 #location / { 229 #proxy_pass http://localhost:8080; 230 #proxy_set_header Host $host:$server_port; 231 #} 232 #} 233 # http请求重定向到https请求 234 #server { 235 #listen 80; 236 #server_name 域名; 237 #return 301 https://$server_name$request_uri; 238 #} 239 server { 240 keepalive_requests 120; #单连接请求上限次数。 241 listen 80; #监听端口 242 server_name 域名 #监听地址 243 #ssi on; 244 #autoindex on; 245 charset utf-8; 246 client_max_body_size 10M; # 限制用户上传文件大小,默认1M 247 #access_log logs/host.access.log myFormat; #定义访问日志,可以针对每一个server(即每一个站点)设置它们自己的访问日志。 248 # 转发动态请求到web应用服务器 249 #location ^~ /api { 250 #rewrite ^/api/(.*)$ /$1 break; 251 #proxy_pass https://域名; 252 #break;#终止匹配 253 #} 254 location / { 255 # 使用proxy_pass转发请求到通过upstream定义的一组应用服务器 256 proxy_pass http://tomcat_gray1; 257 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 258 proxy_set_header Host $http_host; 259 proxy_redirect off; 260 proxy_set_header X-Real-IP $remote_addr; 261 } 262 location ~*^.+$ { #请求的url过滤,正则匹配,~为区分大小写,~*为不区分大小写。 263 proxy_pass http://域名; #请求转向域名 定义的服务器列表 264 } 265 } 266 #标准预发环境 267 upstream tomcat_gray1 { 268 server ip; 269 server 域名; 270 } 271 272 upstream tomcat_gray2 { 273 server 域名; 274 } 275} 276 277
host 配置
1127.0.0.1 域名 2 3
浏览器访问 域名
可以通过观察access.log发现请求接入日志。
2、lua基础语法
教程:https://www.runoob.com/lua/if-else-statement-in-lua.html
lua的IDE编辑器:https://github.com/rjpcomputing/luaforwindows
3、nginx实现灰度
根据前端请求参数进行灰度到不同节点。
1#user nobody; 2worker_processes 1; 3#error_log logs/error.log; 4#error_log logs/error.log notice; 5#error_log logs/error.log info; 6#pid logs/nginx.pid; 7events { 8 worker_connections 1024; 9} 10http { 11 include mime.types; 12 default_type application/octet-stream; 13 #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'; 14 log_format logFormat '$group $time_local 客户端:$remote_addr–$remote_port 请求的URI和HTTP协议:$request 请求:$http_host HTTP状态:$status upstream状态:$upstream_status 负载:$upstream_addr 15 url跳转:$http_referer $body_bytes_sent $http_user_agent $request_uri 请求参数 $query_string $args $document_root $uri 16 -----$request_uri $request_filename $http_cookie'; 17 access_log logs/access.log logFormat; 18 sendfile on; 19 #tcp_nopush on; 20 #keepalive_timeout 0; 21 keepalive_timeout 65; 22 #gzip on; 23 server { 24 listen 80; #监听端口 25 server_name 域名; #监听地址 26 access_log logs/xx.com.access.log logFormat; 27 #方式二、nginx+lua实现灰度 28 ## 1、将对localhost访问由/opt/app/lua/dep.lua进行处理 29 ## 2、根据逻辑处理后,决定回调如下两个其中1个内部跳转 30 #方式三根据请求参数值匹配进行路由 31 #/policy/policyInfoList?thirdPolicystatus=2 32 set $group "default"; 33 if ($query_string ~* "thirdPolicystatus=1"){ #动态控制路由 34 set $group new_version; 35 } 36 if ($query_string ~* "thirdPolicystatus=2"){ 37 set $group old_version; 38 } 39 location / 40 { 41 default_type "text/html"; 42 #content_by_lua_file D:/sortware/openresty/openresty-1.17.8.2-win64/conf/dep.lua; # 指定由lua文件处理http请求 43 proxy_pass http://$group; 44 proxy_set_header Host $host; 45 proxy_set_header X-Real-IP $remote_addr; 46 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 47 index index.html index.htm; 48 } 49 } 50 #标准预发环境 51 upstream default { 52 server ip:port; 53 } 54 #预发2 55 upstream new_version { 56 server ip:port; 57 } 58 #预发3 59 upstream old_version { 60 server ip:port; 61 } 62} 63 64
host如下:
1127.0.0.1 域名 2 3
访问地址:
1域名 2 3
菜单运营数据---保单数据,默认走default集群,保单状态承保成功走new_version集群,保单状态终止走old_version集群
根据cookie内的参数进行负载
1 2#user nobody; 3worker_processes 1; 4 5#error_log logs/error.log; 6#error_log logs/error.log notice; 7#error_log logs/error.log info; 8 9#pid logs/nginx.pid; 10 11 12events { 13 worker_connections 1024; 14} 15 16 17http { 18 include mime.types; 19 default_type application/octet-stream; 20 #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'; 21 log_format logFormat '$http_cookie $group $time_local 客户端:$remote_addr–$remote_port 请求的URI和HTTP协议:$request 请求:$http_host HTTP状态:$status upstream状态:$upstream_status 负载:$upstream_addr 22 url跳转:$http_referer $body_bytes_sent $http_user_agent $request_uri 请求参数 $query_string $args $document_root $uri 23 -----$request_uri $request_filename '; 24 access_log logs/access.log logFormat; 25 26 sendfile on; 27 #tcp_nopush on; 28 29 #keepalive_timeout 0; 30 keepalive_timeout 65; 31 #gzip on; 32 server { 33 listen 80; #监听端口 34 server_name 域名; #监听地址 35 access_log logs/xx.com.access.log logFormat; 36 #方式二、nginx+lua实现灰度 37 ## 1、将对localhost访问由/opt/app/lua/dep.lua进行处理 38 ## 2、根据逻辑处理后,决定回调如下两个其中1个内部跳转 39 #方式三根据请求参数值匹配进行路由 40 #域名policy/policyInfoList?thirdPolicystatus=2 41 set $group "default"; 42 if ($query_string ~* "thirdPolicystatus=1"){ #动态控制路由 43 set $group new_version; 44 } 45 if ($query_string ~* "thirdPolicystatus=2"){ 46 set $group old_version; 47 } 48 if ($http_cookie ~* "sso.xx.com=BJ.E2C7D319112E7F6252BF010770269E235820211121073248"){ 49 set $group pro_version; 50 } 51 if ($http_cookie ~* "sso.xx.com!=BJ.E2C7D319112E7F6252BF010770269E235820211121073248"){ 52 set $group grey_version; 53 } 54 location / 55 { 56 default_type "text/html"; 57 #content_by_lua_file D:/sortware/openresty/openresty-1.17.8.2-win64/conf/dep.lua; # 指定由lua文件处理http请求 58 proxy_pass http://$group; 59 proxy_set_header Host $host; 60 proxy_set_header X-Real-IP $remote_addr; 61 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 62 index index.html index.htm; 63 } 64 } 65 #标准预发环境 66 upstream default { 67 server ip:port; 68 } 69 #预发2 70 upstream new_version { 71 server ip:port; 72 } 73 #预发3 74 upstream old_version { 75 server ip:port; 76 } 77 #预发2 78 upstream pro_version { 79 server ip:port; 80 } 81 #预发3 82 upstream grey_version { 83 server ip:port; 84 } 85} 86 87
根据cookie内容转发到不同的集群
四、相关可操作和替换性
想法一:如果这个时候我门需要一个动态化配置控制台则可以通过javaweb等工程进行操作redis实现实时更新redis数据从而控制灰度
想法二:切换其他数据源比如
-
MySQL/MariaDB: 使用 Lua 的 lua-mysql 或 LuaSQL 库,您可以在 Lua 中连接和查询 MySQL 或 MariaDB 数据库。
-
PostgreSQL: 使用 Lua 的 lua-postgres 或 LuaSQL 库,您可以在 Lua 中连接和查询 PostgreSQL 数据库。
-
MongoDB: 使用 Lua 的 mongo-lua-driver 库,您可以在 Lua 中连接和操作 MongoDB 数据库。
-
HTTP API: 使用 Lua 的 LuaHTTP 库,您可以在 Lua 中发起 HTTP 请求,并与远程的 HTTP API 进行通信。
-
Cassandra: 使用 Lua 的 lua-cassandra 库,您可以在 Lua 中连接和查询 Cassandra 数据库。
想法三:切换其他脚本语言
-
JavaScript: 通过使用 Nginx 的 ngx_http_js_module,您可以在 Nginx 中使用 JavaScript。这可以让您使用 JavaScript 脚本来实现一些灰度发布或其他功能。此外,JavaScript 也广泛用于前端开发,因此可以在前后端一体化的项目中更容易共享代码逻辑。
-
LuaJIT: LuaJIT 是一个通过即时编译实现高性能的 Lua 解释器。它提供了与标准 Lua 解释器兼容的 API,但是比标准 Lua 解释器更快。使用 LuaJIT,您可以获得更高的性能,同时保持与 Lua 的兼容性。
-
Python: 如果您已经熟悉 Python,您可以使用 Python-NGINX-Module 在 Nginx 中嵌入 Python。这样可以使用 Python 编写 Nginx 的配置文件和处理请求的逻辑。
-
Java: 使用 nginx-jvm-clojure 或 nginx-jwt 等模块,您可以在 Nginx 中嵌入 Java 或 Clojure。这些模块提供了在 Nginx 上运行 Java 或 Clojure 代码的功能,可以与其他 Java 或 Clojure 库和框架进行集成。
想法四:切换其他web服务器或反向代理服务器
-
Apache HTTP Server: Apache 是一个广泛使用的开源 Web 服务器和反向代理服务器,它支持多种模块和扩展,提供了丰富的功能和配置选项。
-
Microsoft IIS: Internet Information Services (IIS) 是由 Microsoft 开发的 Web 服务器,专为 Windows 操作系统设计。它是 Windows Server 默认的 Web 服务器,提供了广泛的功能和集成。
-
Caddy: Caddy 是一个用 Go 编写的现代化的 Web 服务器和反向代理服务器。它具有简单配置、自动 HTTPS、HTTP/2 支持等特性。
-
HAProxy: HAProxy 是一个高性能的负载均衡器和反向代理服务器,适用于高流量的 Web 应用程序。它具有丰富的负载均衡和代理功能。
-
Envoy: Envoy 是一个轻量级的开源代理服务器和通信总线,适用于云原生和微服务架构。它具有动态配置、负载平衡、流量管理等功能。
大家可以根据自己的想法或者兴趣进行研究,本文不做过多介绍
作者:京东健康 马仁喜
来源:京东云开发者社区 转载请注明来源
