乐趣区

关于openresty:在openresty上基于是lock和redis快速搭建高性能long-polling推送服务

为啥须要?

在理论开发中咱们常常会遇到须要长时间期待后盾事件的状况,例如较为常见的扫码登录性能,二维码界面需期待后盾扫码登录胜利的事件,再如导入导出等须要较长时间能力解决实现的工作,此时须要把工作放到后盾由异步工作进行解决,实现后再给前台界面推送实现事件,以上需要咱们须要用长连贯能力实现推送,但长连贯推送状态治理简单,且须要部署独立零碎,零碎流程简单且横向程度扩大艰难,此时抉择更简略 long polling 期待是一个更好的抉择,http 申请间接期待返回,显然逻辑更简略,可用性可维护性也会更高。

openresty 是一个构建在 nginx 上的高性能能零碎,个别状况下咱们也须要在本身服务前部署 nginx 作为网关,那么抉择 openresty 来构建一个高性能的 long polling 服务显然是一个好抉择。slock 是高性能的状态及原子操作数据库,redis 则是高性能的内存缓存数据库,应用下边 nginx 配置文件即可疾速基于 slock 和 redis 构建一个高性能高可用 long polling 服务。同时构建的此 long polling 服务是一个通用服务,即可用于扫码登录这样的需要实现状态推送,也可用于像音讯零碎、私信零碎等的音讯推送。

slock 我的项目地址:https://github.com/snower/slock

slock 简介可看:https://segmentfault.com/a/11…

疾速配置构建

首先需在装置好的 openresty 服务中装置 slock 的 lua client 包。

我的项目地址:https://github.com/snower/slo…

装置形式即把 slock-lua-nginx 中 slock.lua 复制到 openresty 目录中的 lualib/ 中,而后增加以下 nginx 配置文件批改相干参数即可。

init_worker_by_lua_block {
    local slock = require "slock"
    slock:connect("server1", "127.0.0.1", 5658)
}

server {
    listen 8081;
    default_type application/json;

    location /poll/event {
        content_by_lua_block {
            local cjson = require "cjson"
            local slock = require "slock"
            local slock_client = slock:get("server1")
            local default_type = ngx.var.arg_default_type or "clear"
            local wait_type = ngx.var.arg_wait_type or ""local event_key = ngx.var.arg_event or""
            local wait_timeout = tonumber(ngx.var.arg_timeout) or 60

            local sendResult = function(err_code, err_message)
                ngx.say(cjson.encode({
                    err_code = err_code,
                    err_message = err_message,
                }))
            end

            if event_key == "" then
                return sendResult(400, "event key is empty")
            end

            local event = nil
            if default_type == "set" then
                event = slock_client:newDefaultSetEvent(event_key, 5, wait_timeout * 2)
            else
                event = slock_client:newDefaultClearEvent(event_key, 5, wait_timeout * 2)
            end

            if wait_type == "reset" then
                local ok, err = event:waitAndTimeoutRetryClear(wait_timeout)
                if not ok then
                    return sendResult(504, "wait event timeout")
                end
                return sendResult(0, "succed")
            end

            local ok, err = event:wait(wait_timeout)
            if not ok then
                return sendResult(504, "wait event timeout")
            end
            return sendResult(0, "succed")
        }
    }

    location /poll/message {
        content_by_lua_block {
            local cjson = require "cjson"
            local redis = require "resty.redis"
            local slock = require "slock"
            local redis_client = redis:new()
            local slock_client = slock:get("server1")

            local default_type = ngx.var.arg_default_type or "clear"
            local wait_type = ngx.var.arg_wait_type or ""local event_key = ngx.var.arg_event or""
            local wait_timeout = tonumber(ngx.var.arg_timeout) or 60

            local sendResult = function(err_code, err_message, data)
                ngx.say(cjson.encode({
                    err_code = err_code,
                    err_message = err_message,
                    data = data,
                }))
            end

            if event_key == "" then
                return sendResult(400, "event key is empty")
            end

            redis_client:set_timeouts(5000, wait_timeout * 500, wait_timeout * 500)
            local ok, err = redis_client:connect("10.10.10.251", 6379)
            if not ok then
                return sendResult(502, "redis connect fail")
            end
            local message, err = redis_client:lpop(event_key)
            if err ~= nil then
                return sendResult(500, "redis lpop fail")
            end
            if message ~= ngx.null then
                redis_client:set_keepalive(7200000, 16)
                return sendResult(0, "", message)
            end

            local event = nil
            if default_type == "set" then
                event = slock_client:newDefaultSetEvent(event_key, 5, wait_timeout * 2)
            else
                event = slock_client:newDefaultClearEvent(event_key, 5, wait_timeout * 2)
            end

            if wait_type == "reset" then
                local ok, err = event:waitAndTimeoutRetryClear(wait_timeout)
                if not ok then
                    return sendResult(504, "wait timeout")
                end

                local message, err = redis_client:lpop(event_key)
                if err ~= nil then
                    return sendResult(500, "redis lpop fail")
                end
                redis_client:set_keepalive(7200000, 16)
                return sendResult(0, "succed", message)
            end

            local ok, err = event:wait(wait_timeout)
            if not ok then
                return sendResult(504, "wait timeout")
            end

            local message, err = redis_client:lpop(event_key)
            if err ~= nil then
                return sendResult(500, "redis lpop fail")
            end
            redis_client:set_keepalive(7200000, 16)
            return sendResult(0, "succed", message)
        }
    }
}

/poll/event 接口只期待事件触发,不返回数据。

/poll/message 则是先从 redis 中获取数据,胜利则返回,否则期待事件触发,再从 redis 获取数据返回。

接口 Query String 参数:

  • default_type 创立 Event 的初始状态是 seted 还是 cleared,wait 期待 seted 状态触发,即如果应用初始是 seted 则需其它零碎先执行 clear 操作,可选值:set、clear,默认值 clear
  • wait_type 事件触发后是否重置 Event 状态,不重置可保障在过期工夫内可重入,重置则可用于私信零碎的循环获取音讯或事件,设置为 reset 为重置,默认值空字符不重置
  • event 期待的事件 key,不可为空,redis 也应用该 key 在 List 数据结构中保留音讯
  • timeout 数字,期待超时工夫,单位秒,默认期待 60 秒

特地留神:

  • openresty 应用繁多连贯到 slock 的 tcp 连贯解决所有申请,nginx 只有 init_worker_by_lua_block 创立的 socket 才可在整个 worker 生命周期中放弃存在,所以 slock connect 需在 init_worker_by_lua_block 实现,第一个参数为连贯名称,后续可用该名称获取该连贯应用。
  • slock 配置为 replset 模式时,可用 replset 形式连贯,例:slock:connectReplset(“server1”, {{“127.0.0.1”, 5658}, {“127.0.0.1”, 5659}}),应用该模式连贯时,nginx 会主动跟踪可用节点,放弃高可用。

配置实现后执行下方 shell 会处于期待返回状态:

curl "http://localhost:8081/poll/message?event=test&default_type=clear"

其它零碎如何推送事件?

应用 slock java client 推送事件

java client 我的项目地址:https://github.com/snower/jas…

package main;

import io.github.snower.jaslock.Client;
import io.github.snower.jaslock.Event;
import io.github.snower.jaslock.exceptions.SlockException;
import redis.clients.jedis.Jedis;

import java.io.IOException;
import java.nio.charset.StandardCharsets;

public class App {public static void main(String[] args) {Client slock = new Client("localhost", 5658);
        Jedis jedis = new Jedis("10.10.10.251", 6379);
        try {byte[] eventKey = "test".getBytes(StandardCharsets.UTF_8);
            slock.open();
            jedis.rpush(eventKey, "hello".getBytes(StandardCharsets.UTF_8));
            jedis.expire(eventKey, 120);
            Event event = slock.newEvent(eventKey, 5, 120, false);
            event.set();} catch (IOException | SlockException e) {e.printStackTrace();
        } finally {slock.close();
            jedis.close();}
    }
}

newEvent 参数:

  • eventKey 事件名称,和前端申请统一
  • timeout set 操作超时事件
  • expried 如果初始状态时 cleared,则示意 set 之后状态放弃工夫,如果初始状态是 seted,则示意 clear 之后状态放弃工夫,超过改工夫都将被主动回收
  • defaultSeted ture 示意初始是 seted 状态,false 为 cleared 状态,需和前端传参统一

注:只推送事件时去除 redis 操作即可。

php、python、golang 操作相似。

php client 我的项目地址:https://github.com/snower/pys…

python client 我的项目地址:https://github.com/snower/phs…

应用 redis 自定义命令推送事件

也可用 redis 自定义命令来执行 slock Event 的 set 和 clear 操作实现事件触发。

初始是 seted 时:

#clear 操作
lock ${EVENT_KEY} lock_id ${EVENT_KEY} flag 2 timeout ${TIMEOUT} expried ${EXPRIED}
#如 lock test lock_id test flag 2 timeout 5 expried 120


#set 操作
unlock ${EVENT_KEY} lock_id ${EVENT_KEY}
#如 unlock test lock_id test

初始是 cleared 时:

#clear 操作
unlock ${EVENT_KEY} lock_id ${EVENT_KEY}
#如 unlock test lock_id test


#set 操作
lock ${EVENT_KEY} lock_id ${EVENT_KEY} flag 2 timeout ${TIMEOUT} expried ${EXPRIED} count 2
#如 lock test lock_id test flag 2 timeout 5 expried 120 count 2
#用 redis-cli -p 5658 连贯 slock 后执行该示例命令,即可看到上方期待 curl 命令胜利返回。

对于高可用与扩大

对于高可用,slock 反对配置为集群模式,在集权模式运行时主节点异样时可主动抉择新主节点,此时如果 openresty 应用 resplset 模式连贯时,可主动应用新可用节点,保障高可用。

对于程度扩大,slock 良好的多核反对,百万级 qps,保障了无需过多思考程度扩大问题,而 openresty 则仍然放弃了 web 服务惯例无状态个性,可依照 web 惯例程度扩大形式扩容即可一直进步零碎承载性能。

退出移动版