• openresty出现socket read/write busy的原因及解决方法


    openresty出现socket read/write busy的原因及解决方法

    基本概念

    cosocket

    cosocket即openresty将其协程(coroutine)与网络套接字结合在一起实现的非阻塞网络I/O

    其中tcp相关api为:

    • 创建对象:ngx.socket.tcp
    • 设置超时:tcpsock:settimeout 和 tcpsock:settimeouts
    • 建立连接:tcpsock:connect
    • 发送数据:tcpsock:send
    • 接受数据:tcpsock:receive、tcpsock:receiveany 和 tcpsock:receiveuntil
    • 连接池:tcpsock:setkeepalive
    • 关闭连接:tcpsock:close

    其中*setkeepalive(timeout, size)*需要关注的api,用于将tcp连接放入连接池,timeout设置超时时间,size设置连接池大小

    • 每个worker进程会有一个连接池

    • 连接池是通过ip和port标志的,若ip和port相同则会使用一个连接池

    • 连接池的大小在第一次调用setkeepalive就会确定下不会再变更

    • 在调用setkeepalive后即会将tcp连接放入连接池中,无需再调用close方法

    • 每次调用connect方法时会优先查询连接池中是否有可用连接,若有则直接返回连接,若无才会新建连接

    ngx.ctx 参考文档

    ngx.ctx此表用于存储请求的上下文环境数据,其生命周期与当前请求的生命请求相同,若当前请求中有子请求存在,子请求会维护自己的ngx.ctx,当前请求并不能与子请求共享ngx.ctx

    ==注意:==使用ngx.ctx尽量使用传参的方式,ngx.ctx表查询效率略低

    local _M = {}
    
    -- 错误
    -- ctx变量是在 Lua 模块级别,并且属于单个worker的(worker级变量,同一worker的所有请求共享,多个请求同时写入时不安全)
    -- local ctx = ngx.ctx
    -- function _M.main()
    --  	 ctx.foo = "bar"
    -- end
    
    -- 正确,通过传参方式完成
    function _M.main(ctx)
      	ctx.foo = "bar"
    end
    
    return _M
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    错误原因及解决方法

    cosocket 是全双工的,如果一个读线程和一个写线程同时操作一个cosocket对象不会有问题,但如果两个线程一起读或者写同一个cosocket则会引发socket busy错误

    作为tcp client的解决方法

    读线程不会出现socket read busy,因为读取tcp数据的地方有且仅有一个

    使用单独的协程来操作socket,其他业务处理协程使用消息队列来通知需要发送的tcp数据

    连接mongodb、redis时的问题

    本质上两者实现相同都是基于cosocket,mongodb经常报错而redis不报错的原因是:redis是在每一个调用函数内部建立连接且名字都不相同(保证在同一请求中即使有多个协程ngx.ctx返回的也不是同一个连接)

    会导致出错的代码:

    -- 会导致出错的代码
    -- 若同一请求中有多个协程,它们共享一个上下文环境(ngx.ctx表),并且会对name相同的连接进行读写操作,大概率会出现socket busy的问题
    if name and ngx.ctx[name] then
    		return ngx.ctx[name]
    end
    
    local client, errmsg = mongol:new()
    if not client then
      	return nil, 'mongo socket failed: '..(errmsg or 'no errMsg')
    end
    
    client:set_timeout(timeout)
    
    -- 若上下文中没有找到相同名字的连接,则从连接池中获取socket,若连接池中也没有则新建tcp连接
    local result, errmsg = client:connect(host, port)
    if not result then
      	return nil, errmsg
    end
    
    ngx.ctx[name] = client
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    mongodb数据库认证也需要对数据库进行读取,增加了报错的概率

    对于并发的请求,并发的tcp连接不会报错的原因是:它们每一个连接的ngx.ctx表是分开维护的,所以仅当一个请求或者tcp连接中的子协程的socket操作冲突了才会报错

    redis使用注意点

    在使用redis中select命令切换数据库时,要注意在使用完后,将数据库切换为数据库0,否则在执行完set_keepalive后将连接到其他数据库的tcp长连接放入连接池中,有概率导致其他业务从连接池中取出的连接并不是连接到数据库0的

    更多lua-resty-redis使用说明

    解决方法

    如果需要在共享模块内操作redis和mongodb,eg: common.lua,需要在调用时传递ngx.ctx所需的名字

    local function c(name)
    		-- 不加name会报错
        local cc_config_db = require("models.config_mongo"):new(name)
        
        local config = cc_config_db:get_one({name = "SYSTEM_CONFIG"})
        if not config or not next(config) then
            ngx.log(ngx.ERR, name .. " failed")
        else 
            ngx.log(ngx.ERR, name .." success")
        end
    end
    
    local function a()
        while true do
            c("a")
            ngx.sleep(0.1)
        end
    end
    
    local function b()
        while true do
            c("b")
            ngx.sleep(0.1)
        end
    end
    
    local function test()
        ngx.thread.spawn(a)
        ngx.thread.spawn(b)
    end
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
  • 相关阅读:
    全球农业经济论坛 丰收节贸促会-万祥军:产业链解决中国方案
    基本组网步骤
    【21天学习挑战赛】顺序查找
    【C++】string类(详解),常见的函数都在这里,你都了解吗?
    【数据分享】2023年我国上市公司数据(Excel格式/Shp格式)
    DRM全解析 —— plane详解(1)
    DP - OOD - DIP
    如何在Mac电脑上安装WeasyPrint:简单易懂的步骤
    [动态规划] 树形DP
    Typescript的高级tricks(in,keyof,Partial,Pick,Exclude等)
  • 原文地址:https://blog.csdn.net/stallion5632/article/details/125507342