• Lua集成Redis及Nginx


    1 Lua介绍

    Lua是一门以其性能著称的脚本语言,被广泛应用在很多方面。Lua一般用于嵌入式应用,现在越来越多应用于游戏 当中,魔兽世界,愤怒的小鸟都有用到。
    优势

    1. Lua极易嵌入到其他程序,可当做一种配置语言。
    2. 提升应用性能,比如:游戏脚本,nginx,wireshark的脚本
    3. 兼容性强,可以直接使用 C 代码写的函数。比如corona 移动应用开发平台,跟PhonePap类似,不过使用Lua做开发 语言,应用可以build到iOS,Android,kindle fire,nook平台; 服务器端应该也是Lua。
    4. 在给软件提供嵌入式脚本编程能力上,Lua是绝佳选择。

    官网:http://www.lua.org/
    Lua脚本的作用:嵌入到应用程序中,给应用程序提供扩展功能。

    2 安装

    依赖安装:

    yum install gcc -y
    

    安装

    curl -R -O http://www.lua.org/ftp/lua-5.4.4.tar.gz 
    
    tar zxf lua-5.4.4.tar.gz
    mv lua-5.4.4/usr/local/
    cd  /usr/local/lua-5.4.4 
    make linux test
    
    ln -s /usr/local/lua-5.4.4/src/lua /usr/bin/lua
    

    查看版本

    [root@q101 lua-5.4.4]# lua -v
    Lua 5.4.4  Copyright (C) 1994-2022 Lua.org, PUC-Rio
    

    3 Lua语法

    3.1 基础语法

    参考:https://www.lua.org/manual/5.4/
    运行方法
    可以编写xxx.lu脚本,通过lua xxx.lua 运行,或者通过 lua -i 交互式编程
    helloworld

    [root@q101 lua_script]# vi helloworld.lua
    [root@q101 lua_script]# lua helloworld.lua 
    hello!
    

    注释
    单行:两个减号

    --
    

    多行注释

    --[[
    多行注释
    多行注释
    --]]
    

    变量
    全局变量:默认情况下,定义一个变量都是全局变量;
    局部变量:局部变量声明时使用local关键字,例如:

    --全局变量
    a=1
    --局部变量
    local b=2
    

    如果变量没有初始化,则默认值为nil这和Java中的null不同,例如

    [root@q101 ~]# lua -i
    Lua 5.4.4  Copyright (C) 1994-2022 Lua.org, PUC-Rio
    > name="hello"
    > local address="beijin"
    > print(name)
    hello
    > print(address)
    nil
    >
    

    数据类型
    Lua 是动态类型语言,变量不要类型定义,只需要为变量赋值。 值可以存储在变量中,作为参数传递或结果返回。 Lua 中有 8 个基本类型分别为:nilbooleannumberstringuserdatafunctionthreadtable

    数据类型 描述
    nil 只有值nil属于该类,表示一个无效值(在条件表达式中相当于false)。
    boolean 包含两个值:false和true。nil和false是 false
    number 表示双精度类型的实浮点数
    string 字符串由一对双引号或单引号来表示
    function 由 C 或 Lua 编写的函数
    userdata 表示任意存储在变量中的C数据结构
    thread 表示执行的独立线路,用于执行协同程序
    table Lua 中的表(table)其实是一个"关联数组"(associative arrays),数组的索引可以是数字、字 符串或表类型。在 Lua 里,table 的创建是通过"构造表达式"来完成,最简单构造表达式是{},用 来创建一个空表。

    lua中可以通过type函数查看对象类型,例如

    > print(type("hello"))
    string
    > print(type(10.4))
    number
    > print(type(print))
    function
    > print(type(type))
    function
    > print(type(true))
    boolean
    > print(type(nil))
    nil
    

    3.2 流程控制

    if语句
    Lua if语句 由一个布尔表达式作为条件判断,其后紧跟其他语句组成。 语法:

    if(布尔表达式)
    then
        --[[ 布尔表达式为true时执行语句--]]
    end
    

    示例:

    > age = 9
    > if(age < 18)
    >> then print("study")
    >> end
    study
    

    if ...else 语句
    语法:

    if(布尔表达式)
    then
        --[[表达式为true执行--]]
    else
         --[[表达式为flase执行--]]
    end
    

    示例:

    > age=30
    > if(age<18)
    >> then print("study")
    >> else
    >> print("work")
    >> end
    work
    

    3.3 循环

    while循环
    语法:

    while(condition)
    do
        --[[满足条件执行内容--]]
    end
    

    示例:

    a=10
    while(a>0)
    do
      print("a=",a)
      a=a-1
    end
    

    for循环
    语法:
    Lua 编程语言中 for 循环语句可以重复执行指定语句,重复次数可在 for 语句中控制。
    语法:

    for var=exp1,exp2,exp3
    do
        --[[执行体--]]
    end
    

    var 从 exp1 变化到 exp2,每次变化以 exp3 为步长递增 var,并执行一次执行体内容。exp3 是可选的,如果不指定,默认为1。
    示例:

    for a=1,10,2
    do
       print(a)
    end
    

    for a=1,10,2 a=1从1开始循环,10循环数据到10结束,2每次递增2

    repeat...until语句
    repeat...until 循环语句不同于 for 和 while循环,for 和 while 循环的条件语句在当前循环执行开始
    时判断,而 repeat...until 循环的条件语句在当前循环结束后判断,满足until条件后循环结束
    语法:

    repeat
        --[[执行体--]]
    until(conditon)
    

    示例:

    num=10
    repeat
       print(num)
       num=num+1
    until(num>15)
    

    3.4 函数

    lua中也可以定义函数,类似于java中的方法。例如:

    --比较2个值大小
    function max(num1,num2)
       if(num1>num2) then
           result=num1
       else
           result=num2
       end
    
       return result
    end
    
    print("最大值是:",max(5,3))
    print("最大值是:",max(11,12))
    


    table 是 Lua 的一种数据结构用来帮助我们创建不同的数据类型,如:数组、字典等。 Lua也是通过table来解决模块(module)、包(package)和对象(Object)的。

    -- 初始化表 
    mytable = {}
    -- 指定值 
    mytable[0]= "Lua"
    -- 移除引用 
    mytable = nil
    

    模块
    模块定义
    模块类似于一个封装库,从 Lua 5.1 开始,Lua 加入了标准的模块管理机制,可以把一些公用的代码放在一个文件里,以 API 接口的形式在其他地方调用,有利于代码的重用和降低代码耦合度。
    创建一个文件叫module.lua,在module.lua中创建一个独立的模块,代码如下:

    -- 文件名为 module.lua
    -- 定义一个名为 module 的模块 
    module = {}
    -- 定义一个常量
    module.constant = "这是一个常量"
    -- 定义一个函数
    function module.func1()
        print("这是一个公有函数") 
    end
    local function func2() 
        print("这是一个私有函数!")
    end
    function module.func3()
        func2()
    end
    return module
    

    由上可知,模块的结构就是一个 table 的结构,因此可以像操作调用 table 里的元素那样来操作调用模块里的常量或 函数。
    上面的 func2 声明为程序块的局部变量,即表示一个私有函数,因此是不能从外部访问模块里的这个私有函数,必须 通过模块里的公有函数来调用.

    require 函数
    require 用于 引入其他的模块,类似于java中的类要引用别的类的效果。
    语法:

    require("模块名")
    require "模块名"
    

    两种都可以。 我们可以将上面定义的module模块引入使用,创建一个test_module.lua文件,代码如下:

     -- test_module.lua 文件
    -- module 模块为上文提到到 
    module.lua require("module")
    print(module.constant)
    module.func3()
    

    4 Redis + Lua

    Redis 在 2.6 版本中推出了脚本功能,允许开发者将 Lua 语言编写的脚本传到 Redis 中执行。使用 Lua 脚本的优点有如下几点:

    1. 减少网络开销:本来需要多次请求的操作,可以一次请求完成,从而节约网络开销;
    2. 原子操作:Redis 会将整个脚本作为一个整体执行,中间不会执行其它命令;
    3. 复用:客户端发送的脚本会存储在 Redis 中,从而实现脚本的复用。

    4.1 Redis执行lua脚本

    # 将lua脚本载到redis
    redis-cli script load "$(cat xxx.lua)"
    # 执行上一步返回的sha值
    redis-cli evalsha "xxx" 0
    

    根据key获取Redis中的value
    Redis中目前存在的数据

    编写redis1.lua脚本

    --索引以数值1开始
    local key=KEYS[1]  
    local list=redis.call("get",key);  
    return list;
    

    通过脚本获取Redis中的值

    [root@q101 lua_script]# redis-cli --eval redis1.lua k1
    "aaaa"
    [root@q101 lua_script]# vi redis1.lua
    [root@q101 lua_script]# redis-cli --eval redis1.lua k2
    "bbb"
    

    统计点击数
    目前Redis中没有count值

    编写redis2.lua

    --每次执行count +1
    local msg='count:'
    local count = redis.call("get","count")
    if not count then
            redis.call("set","count",0)
    end
    
    redis.call("incr","count")
    
    return msg..count+1
    

    执行redis2.lua

    redis-cli --eval redis2.lua 0
    

    redis中查询count值

    4.2 redis2-nginx-module

    redis2-nginx-module是一个支持 Redis 2.0 协议的 Nginx upstream 模块,它可以让 Nginx 以非阻塞方式直接防问远方的 Redis 服务,同时支持 TCP 协议和 Unix Domain Socket 模式,并且可以启用强大的 Redis 连接池功能。
    GitHubhttps://github.com/openresty/redis2-nginx-module
    openresty自带redis2-nginx-module,可以直接使用
    Nginx配置

    location = /foo {
         default_type text/html;
         redis2_query set k1 'aaa';
         # 请求转发Redis
         redis2_pass 127.0.0.1:6379;
    }
    

    重启后访问http://192.168.88.101/foo,查看Redis中k1值

    get请求

    # 访问 ip/get?key=xxx
    location = /get {
         default_type text/html;
         redis2_pass 127.0.0.1:6379;
         #redis2_query auth 123123;
         set_unescape_uri $key $arg_key;  # this requires ngx_set_misc
         redis2_query get $key;
    }
    

    set请求

    location = /set {
        default_type text/html;
        redis2_pass 127.0.0.1:6379;
        #redis2_query auth 123123;
         # $arg_xxx未自定义
         set_unescape_uri $key $arg_uid;  # this requires ngx_set_misc
         set_unescape_uri $val $arg_uname;  # this requires ngx_set_misc
         redis2_query set $key $val;
     }
    

    请求http://192.168.88.101/set?uid=11111&uname=zhangsan,查看Redis

    操作集群

    upstream redis_cluster {
         server 192.168.88.101:6379;
         server 192.168.88.102:6379;
     }
    location = /redis {
    default_type text/html;
             redis2_next_upstream error timeout invalid_response;
             redis2_query get foo;
             redis2_pass redis_cluster;
    }
    

    其他更多方法,参考官方文档。通过redis2-nginx-module需要对Nginx配置进行修改,不适用于复杂场景。通常使用更多的是通过外置lua脚本编写业务逻辑,Nginx conf引用lua,比如lua-resty-redis。

    4.3 lua-resty-redis访问redis

    nginx配置增加

    location /luaresty {
        default_type text/html;
        content_by_lua_file script/luaresty.lua;
    }
    

    编写luaresty.lua
    vi /usr/local/openresty/nginx/script/luaresty.lua

    local redis = require "resty.redis"
    local red = redis:new()
    local ok,err = red:connect("127.0.0.1",6379)
    
    if not ok then
       ngx.say("failed to connect:",err)
       return
    end
    --set key=user1   value=zhangsan
    ok,err = red:set("user1","zhangsan")
    if not ok then
       ngx.say("failed to set user1:",err)
       return
    end
    ngx.say("set result:",ok)
    
    --get key=user1
    local res,err = red:get("user1")
    if not res then
       ngx.say("failed to get user1:",err)
       return
    end
    
    if res == ngx.null then
       ngx.say("user1 not found")
       return
    end
    ngx.say("user1:",res)
    

    访问http://192.168.88.101/luaresty

    更多使用参考:https://github.com/openresty/lua-resty-redis
    redis-cluster支持
    参考:https://github.com/steve0511/resty-redis-cluster

    5 OpenResty Nginx + Lua

    5.1 OpenResty Nginx 安装

    官网地址:http://openresty.org/cn/linux-packages.html

    yum install yum-utils
    yum-config-manager --add-repo https://openresty.org/package/centos/openresty.repo
    yum install openresty -y
    yum install openresty-resty -y
    

    启动\关闭\重启等命令

    # 启动
    systemctl start openresty
    # 查看状态
    systemctl status openresty
    # 加载配置
    systemctl reload openresty
    # 关闭
    systemctl stop openresty
    

    查看版本openresty -v

    [root@q101 utils]# openresty -v
    nginx version: openresty/1.19.9.1
    

    测试lua脚本

    vi /usr/local/openresty/nginx/conf/nginx.conf
    
    # 在nginx.conf server中写入
    location /lua {
    
        default_type text/html;
        content_by_lua 'ngx.say("<p>Hello, OpenResty!</p>")';
      }
      
    # 保存后重新加载配置
    systemctl reload openresty
    

    浏览器访问http://192.168.88.101/lua

    获取请求nginx uri中的变量
    nginx配置修改

    location /nginx_var {
          default_type text/html;
          content_by_lua_block {
             # arg_a表示获取参数名为a的参数值
             ngx.say(ngx.var.arg_a)
         }
     }
    

    重新加载配置后浏览器访问:http://192.168.88.101/nginx_var?a=123123

    5.2 lua-resty-http

    下载安装
    GitHub主页:https://github.com/ledgetech/lua-resty-http
    下载后将lua-resty-http/lib/resty下所有文件上传到/usr/local/openresty/lualib/resty

    编写脚本
    vi /usr/local/openresty/nginx/script/resty1.lua
    内容如下

    local http = require("resty.http")
    local httpc = http.new()
    
    --http://www.baidu.com/s?wd=test
    local resp, err = httpc:request_uri("http://www.baidu.com", {
        method = "GET",
        path = "/s?wd=test",
        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"
        }
    })
    
    if not resp then
        ngx.say("request error :", err)
        return
    end
    
    ngx.status = resp.status
    
    for k, v in pairs(resp.headers) do
        if k ~= "Transfer-Encoding" and k ~= "Connection" then
            ngx.header[k] = v
        end
    end
    
    ngx.say(resp.body)
    httpc:close()
    

    配置nginx

    resolver 8.8.8.8;
    location /resty1 {
        default_type text/html;
        content_by_lua_file script/resty1.lua;
    }
    

    测试
    访问:http://192.168.88.101/resty1

    5.3 lua-resty-http实现一致性hash负载均衡

    本节主要讲解使用lua脚本根据URL做定向转发,实现负载均衡。比如当商品详情访问增加,需要对商品访问进行分流,不同的商品走不同的服务器。网络拓扑图如下:

    nginx配置

    location /resty2 {
        default_type text/html;
        content_by_lua_file script/resty2.lua;
    }
    

    lua脚本编写
    vi /usr/local/openresty/nginx/script/resty2.lua
    内容如下:

    local http = require("resty.http")
    local httpc = http.new()
    local hosts = {"192.168.88.102","192.168.88.103"}
    local item_id= ngx.var.arg_id
    --根据参数名为id的值进行hash计算
    local id_hash = ngx.crc32_long(item_id)
    local index = (id_hash % 2) +1
    
    local resp, err = httpc:request_uri("http://"..hosts[index], {
        method = "GET",
        path = "/",
        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"
        }
    })
    
    if not resp then
        ngx.say("request error :", err)
        return
    end
    ngx.say(resp.body)
    httpc:close()
    

    测试
    再准备两台服务器q102和q103上都安装httpd服务器(测试使用):

    # 下载安装httpd服务,命令如下
    yum install -y httpd
    # 设置首页内容(q103把内容改为this is q103)
    echo this is q102 > /var/www/html/index.html
    # 启动httpd
    systemctl start httpd
    

    通过单独访问192.168.88.101和192.168.88.102可以正常访问。

    访问:http://192.168.88.101/resty2?id=1

    访问:http://192.168.88.101/resty2?id=4

  • 相关阅读:
    MF研究:TinyCLR运行时原理
    不到600美元,即可获取一套MF Porting kit 3.0
    SideShow Gadget开发[1]
    我眼中的WinHEC大会台前幕后
    Visual C++ 2008入门经典 第四章数组 字符串(二) 简单
    PHP的常用函数 简单
    Visual C++ 2008入门经典 第五章程序结构(二) 简单
    用PHP将Unicode 转化为UTF8 简单
    sql GROUP_CONCAT(... SEPARATOR) 简单
    Visual C++ 2008入门经典 第四章数组 字符串(练习题) 简单
  • 原文地址:https://www.cnblogs.com/dooor/p/luabasics.html
Copyright © 2020-2023  润新知