Nginx内嵌lua定时获取consul中的redis集群列表

我们使用lua脚本定时获取consul中的Redis集群信息;

之前我们配置Nginx的动态负载均衡也是使用到consul;

如何使用consul https://blog.5858xy.xyz/index.php/Home/Index/article/aid/24

api核心 https://github.com/openresty/lua-nginx-module 

Nginx配置文件

#加载需要的LUA包 共享内存包->Redis扩展包->Redis集群包->Html模板渲染包
ua_package_path "/lua-packge/lua-resty-lock-0.07/lib/?.lua;;/lua-packge/lua-resty-redis-0.26/lib/?.lua;;
/lua-packge/resty-redis-cluster-master/lib/?.lua;;/lua-packge/lua-resty-http-0.12/lib/?.lua;;
/lua-packge/lua-resty-template-1.9/lib/?.lua;;/lua-packge/function/?.lua;;";
#引入C扩展
lua_package_cpath "/lua-packge/resty-redis-cluster-master/lib/?.so;;";
#共享内存->上面引入了包,这里才可以使用
lua_shared_dict redis_cluster_slot_locks 100k;
lua_shared_dict redis_cluster_addr 100k; #redis集群列表
#第一个work进行启动执行的文件
init_worker_by_lua_file "/nginx-test/lua-project/init/init.lua";
 server {
        listen       80;
        server_name  localhost;
        set   $cachePATH   "/cache/";
        location / {
            default_type text/html;
            root /nginx-test/lua-project/html;
            #content_by_lua '
            #     ngx.header.content_type="text/html";
            #     ngx.say(ngx.var.cachePATH);
            #';
            content_by_lua_file "/nginx-test/lua-project/template.lua";

            #limit_req zone=two;
             #default_type text/html;
             #return 200  "$limit";
            #index  nginx.23673.html index.htm;
        }
        location ^~ /php {
              default_type text/html;
              #root /nginx-test/lua-project/html;
              #做php-fpm 配置,注意地址
               root           /www;  #php-fpm容器当中的路径,不是nginx路径
               fastcgi_index  index.php;
               fastcgi_pass   45.40.207.143:9090; #php容器端口
              #为php-fpm指定的根目录
               fastcgi_param  SCRIPT_FILENAME  $DOCUMENT_ROOT$fastcgi_script_name;
               #注意是容器当中的位置
               include        /usr/local/nginx/conf/fastcgi_params;
                set $path_info "";
                #定义变量 $real_script_name,用于存放真实地址
                 #set $real_script_name $fastcgi_script_name;
                 #如果地址与引号内的正则表达式匹配
                 #if ($fastcgi_script_name ~ "^(.+?\.php)(/.+)$") {
                    #将文件地址赋值给变量 $real_script_name
                   # set $real_script_name $1;
                    #将文件地址后的参数赋值给变量 $path_info
                   # set $path_info $2;
                #}
        }
}

init.lua文件内容,定时向请求consul中的redis集群列表,写入到共享内存中;在第一个work进行启动时开始执行

local share_data= ngx.shared.redis_cluster_addr --共享内存 外部需要定义共享内存变量
local tool = require "tool"
local cjson =require "cjson"
local cjsonObj=cjson.new()

--错误信息不会写入到erro.log当中,因为级别不够
--[[
  check = function()
    ngx.log(ngx.INFO,'111')
  end
]]
local init = function()
  --获取consul中的redis集群列表
  local  consul =  tool.read_http("http://45.40.207.143:6000/v1/kv/redis?recurse")
  local unjson=cjsonObj.decode(consul)
  consul_addr={}
  for k,v in pairs(unjson) do
            consul_addr[k]=ngx.decode_base64(v['Value'])
  end
  local result=table.concat(consul_addr,',')  -- 是将表里的value值连接
  --写入数据到共享内存中
  share_data:set('consul_addr',result)
end
--定时执行  每隔3秒钟执行调用init函数
--只有第一个work进行才启动这个定时器,id为0,不然会导致多个定时启动
local ok,err = ngx.timer.every(3,init)
if not ok then
    ngx.log(ngx.err,'初始化错误',err)
end

template.lua文件,在访问服务时候执行, 从共享内存中获取Redis集群信息,并且连接Redis集群,查询请求的数据是否存在Redis集群中;

如果存在就直接获取数据渲染模板;如果不存在就发送内部请求到 php_fpm,查询数据库,更新Redis数据;

--init.lua脚本在启动work进程执行,定时3秒获取consul中的Redis集群列表
--将获取到的Redis数据处理后,set到redis_cluster_addr中
--share_data:set('consul_addr',result)
local redis_cluster_addr = ngx.shared.redis_cluster_addr  --nginx内存中的集群地址
local tool = require("tool")
local cjson =require "cjson" --此模块需要安装才能使用
local cjsonObj=cjson.new()

--获取到共享内存中,Redis数据
local data =  redis_cluster_addr:get("consul_addr")
local addr=tool.split(data,",")
local redis_list = {}
--循环Redis集群列表: 45.40.207.143:6391,45.40.207.143:6392,
45.40.207.143:6393,45.40.207.143:6394,45.40.207.143:6395,45.40.207.143:6396
for k,v in pairs(addr) do
        --拼接需要的格式 ip='45.40.207.143',port='6391"
        ip_addr=tool.split(v,":")
        --将拼接好的格式存放到 redis_list变量中
        redis_list[k]={ ip=ip_addr[1],port=ip_addr[2] }
end
--连接集群
local config = {
    name = "testCluster",                   --Redis集群名称
    serv_list = redis_list,                 --Redis集群列表
    keepalive_timeout = 60000,              --redis connection pool idle timeout
    keepalive_cons = 1000,                  --redis connection pool size
    connection_timout = 1000,               --timeout while connecting
    max_redirection = 5,                    --maximum retry attempts for redirection,
    --auth = "pass"                           --set password while setting auth
}

local redis_cluster = require "rediscluster"
local red_c = redis_cluster:new(config)

--获取redis缓存的方法封装 如果没有获取到返回null
local function redis_get(key)
        local v, err = red_c:get(key)
        if err then
            ngx.log(ngx.ERR, "err: ", err)
            return
        end
        return v
end

--应用层连接php_fpm
function php_fpm()
     --内部子请求
     res = ngx.location.capture(
        '/php'..ngx.var.request_uri,
        method_body()
     )
     ngx.say('/php'..ngx.var.request_uri)
     --返回的状态码不等于200才算成功
     --ngx.print(cjsonObj.encode(res))
     if res.status == ngx.HTTP_OK then
            ngx.say(res.body)
     else
      --状态码不是200就返回404或者其它信息
            ngx.say('404')
     end
return
end

--拼接请求方式和数据
function method_body()
    local  req_data,res
    local  action=ngx.var.request.method
    --根据不同的请求类型
    if action=="POST" then
        req_data={ method=ngx.HTTP_POST,body=ngx.req.read_body()}
    elseif action == "PUT" then
        req_data={ method=ngx.HTTP_PUT,body=ngx.req.read_body()}
    else
        req_data={ method=ngx.HTTP_GET}
    end
    --打印数据 ngx.print(cjsonObj.encode(req_data))
    return req_data
end

local uri_args=ngx.req.get_uri_args() --get携带的请求参数
--获取redis集群缓存
local re=redis_get("id_"..uri_args['id'])
--判断结果是否为空
if re==ngx.null then
   --请求php服务器
   php_fpm()
   else
   --获取到数据
   ngx.say(re)
end

tool是我们封装的工具类

local tool ={}
--发送http请求
function tool.read_http(url)
    local http = require("resty.http")
    local httpc = http.new()
    local resp, err = httpc:request_uri(url,{
       method = "GET"
    })
    if not resp then
      ngx.log(ngx.ERR,"request error: ", err)  --????
      return
    end
    httpc:close()
    return resp.body
end

--Base64解码  由于我的consul的value值是Base64编码,所以我们需要解码
function decodeBase64(str64)
    local b64chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
    local temp={}
    for i=1,64 do
        temp[string.sub(b64chars,i,i)] = i
    end
    temp['=']=0
    local str=""
    for i=1,#str64,4 do
        if i>#str64 then
            break
        end
        local data = 0
        local str_count=0
        for j=0,3 do
            local str1=string.sub(str64,i+j,i+j)
            if not temp[str1] then
                return
            end
            if temp[str1] < 1 then
                data = data * 64
            else
                data = data * 64 + temp[str1]-1
                str_count = str_count + 1
            end
        end
        for j=16,0,-8 do
            if str_count > 0 then
                str=str..string.char(math.floor(data/math.pow(2,j)))
                data=math.mod(data,math.pow(2,j))
                str_count = str_count - 1
            end
        end
    end

    local last = tonumber(string.byte(str, string.len(str), string.len(str)))
    if last == 0 then
        str = string.sub(str, 1, string.len(str) - 1)
    end
    return str
end

--分割函数
function tool.split(input, delimiter)
     input = tostring(input)
     delimiter = tostring(delimiter)
     if (delimiter=='') then return false end
     local pos,arr = 0, {}
     -- for each divider found
     for st,sp in function() return string.find(input, delimiter, pos, true) end do
         table.insert(arr, string.sub(input, pos, st - 1))
         pos = sp + 1
     end
     table.insert(arr, string.sub(input, pos))
     return arr
end

--测试
function tool.say()
    ngx.say('tool')
end

--后面我们经常需要使用的模块封装在里面
return tool

测试:在Redis集群中我们有id_1的数据, 我们获取key 是通过id_拼接地址栏id的值;

MyAnswer博客

再请求下我们Redis集群中没有的key;

MyAnswer博客

结果请求到了php_fpm;我们通过在请求的地址前面加上php目录,再发送内部请求, 使server中处理php的location匹配到,将请求转发给php_fpm;


php_fpm默认不允许从公网发送请求


MyAnswer博客
请先登录后发表评论
  • 最新评论
  • 总共0条评论