🔬
OpenResty 最佳实践
  • 序
  • 入门篇
  • Lua 入门
    • Lua 简介
    • Lua 环境搭建
    • Lua 编辑器选择
    • 基础数据类型
    • 表达式
    • 控制结构
      • if/else
      • while
      • repeat
      • for
      • break,return 和 goto
    • Lua 函数
      • 函数的定义
      • 函数的参数
      • 函数返回值
      • 全动态函数调用
    • 模块
    • String 库
    • Table 库
    • 日期时间函数
    • 数学库函数
    • 文件操作
  • Lua 高阶
    • 元表
    • 面向对象编程
    • 局部变量
    • 判断数组大小
    • 非空判断
    • 正则表达式
    • 虚变量
    • 抵制使用 module() 定义模块
    • 调用代码前先定义函数
    • 点号与冒号操作符的区别
    • module 是邪恶的
    • FFI
    • 什么是 JIT
  • Nginx
    • Nginx 新手起步
    • location 匹配规则
    • 静态文件服务
    • 日志
    • 反向代理
    • 负载均衡
    • 陷阱和常见错误
  • OpenResty
    • 环境搭建
      • Windows 平台
      • CentOS 平台
      • Ubuntu 平台
      • Mac OS X 平台
    • Hello World
    • 与其他 location 配合
    • 获取 uri 参数
    • 获取请求 body
    • 输出响应体
    • 日志输出
    • 简单 API Server 框架
    • 使用 Nginx 内置绑定变量
    • 子查询
    • 不同阶段共享变量
    • 防止 SQL 注入
    • 如何发起新 HTTP 请求
    • 如何完成 bit 操作
      • 一,复习二进制补码
      • 二,复习位运算
      • 三,LuaJIT 和 Lua BitOp Api
      • 四,位运算算法实例
      • 五,Lua BitOp 的安装
  • LuaRestyRedisLibrary
    • 访问有授权验证的 Redis
    • select+set_keepalive 组合操作引起的数据读写错误
    • redis 接口的二次封装(简化建连、拆连等细节)
    • redis 接口的二次封装(发布订阅)
    • pipeline 压缩请求数量
    • script 压缩复杂请求
    • 动态生成的 lua-resty-redis 模块方法
  • LuaCjsonLibrary
    • json 解析的异常捕获
    • 稀疏数组
    • 空 table 编码为 array 还是 object
  • PostgresNginxModule
    • 调用方式简介
    • 不支持事务
    • 超时
    • 健康监测
    • SQL 注入
  • LuaNginxModule
    • 执行阶段概念
    • 正确的记录日志
    • 热装载代码
    • 阻塞操作
    • 缓存
    • sleep
    • 定时任务
    • 禁止某些终端访问
    • 请求返回后继续执行
    • 调试
    • 请求中断后的处理
    • 我的 lua 代码需要调优么
    • 变量的共享范围
    • 动态限速
    • shared.dict 非队列性质
    • 正确使用长链接
    • 如何引用第三方 resty 库
    • 典型应用场景
    • 怎样理解 cosocket
    • 如何安全启动唯一实例的 timer
    • 如何正确的解析域名
  • LuaRestyDNSLibrary
    • 使用动态 DNS 来完成 HTTP 请求
  • LuaRestyLock
    • 缓存失效风暴
  • OpenResty 与 SSL
    • HTTPS 时代
    • 动态加载证书和 OCSP stapling
    • TLS session resumption
  • 测试
    • 代码静态分析
    • 单元测试
    • 代码覆盖率
    • API 测试
    • 性能测试
    • 持续集成
    • 灰度发布
      • 分流引擎设计
      • 控制台开发
      • 向运维平台发展
  • Web 服务
    • API 的设计
    • 数据合法性检测
    • 协议无痛升级
    • 代码规范
    • 连接池
    • C10K 编程
    • TIME_WAIT 问题
    • 与 Docker 使用的网络瓶颈
  • 火焰图
    • 什么是火焰图
    • 什么时候使用
    • 如何安装火焰图生成工具
    • 如何定位问题
    • 拓展阅读
    • FAQ
Powered by GitBook
On this page
Edit on GitHub
  1. LuaNginxModule

怎样理解 cosocket

Previous典型应用场景Next如何安全启动唯一实例的 timer

Last updated 2 years ago

笔者认为,cosocket 是 OpenResty 世界中技术、实用价值最高的部分。它让我们可以用非常低廉的成本,优雅的姿势,比传统 socket 编程效率高好几倍的方式进行网络编程。无论资源占用、执行效率、还是并发能力都非常出色。

鲁迅有句名言:“其实世界上本没有路,走的人多了,也便成了路”,其实对于 cosocket 的中文翻译貌似我也碰到了类似的问题。当我想给大家一个正面解释,爬过了官方 wiki 发现,原来作者本人(章亦春)也没有先给出 cosocket 的定义。

看来只能通过一些侧面信息,从而让这条路逐渐的清晰起来。

cosocket = coroutine + socket

coroutine:协同程序(后面简称:协程)

socket:网络套接字

OpenResty 中的 cosocket 不仅需要协程特性支撑,它还需 Nginx 非常最重要的“事件循环回调机制”,两部分结合在一起最终达到了 cosocket 效果,外加 Nginx 自身对各种资源的“小气”,LuaJIT 的执行效率,最终加分不少。在 Lua 世界中调用任何一个有关 cosocket 网络函数,其内部关键调用如图所示:

从该图中我们可以看到,用户的 Lua 脚本每触发一个网络操作,都会有协程的 yield 以及 resume,因为请求的 Lua 脚本实际上都 运行在独享协程之上。可以在任何需要的时候暂停自己(yield),也可以在任何需要的时候被唤醒(resume)。

  • 暂停自己,把网络事件注册到 Nginx 监听列表中,并把运行权限交给 Nginx。

  • 当有注册的网络事件达到触发条件时,就唤醒对应的协程继续处理。

以此为蓝图,封装实现 connect、read、receive 等操作,就形成了大家目前所看到的 cosocket API。

可以看到,cosocket 是依赖 Lua 协程 + Nginx 事件通知 两个重要特性拼组而成的。

从 0.9.9 版本开始,cosocket 对象是全双工的,也就是说,有一个专门读取的 light thread 和一个专门写入的 light thread,它们可以同时对同一个 cosocket 对象进行操作(两个 light threads 必须运行在同一个 Lua 环境中,原因见上)。但是你不能让两个 light threads 对同一个 cosocket 对象进行相同的操作(例如,都读取、都写入或者都连接),否则当调用 cosocket 对象时,你将得到一个类似 "socket busy reading" 的错误。

所有东西总结下来,到底什么是 cosocket,中文应该怎么翻译,笔者本人都开始纠结了。我们不妨从另外一个角度来审视它,它到底给我们带来了什么?

  • 它是同步的;

  • 它是非阻塞的;

  • 它是全双工的;

同步与异步解释:

同步:做完一件事再去做另一件; 异步:同时做多件事情,某个事情有结果了再去处理。

阻塞与非阻塞解释:

阻塞:不等到想要的结果我就不走了; 非阻塞:有结果我就带走,没结果我就空手而回,总之一句话:爷等不起。

异步/同步是做事派发方式,阻塞/非阻塞是如何处理事情,两组概念不在同一个层面。

无论 ngx.socket.tcp()、ngx.socket.udp()、ngx.socket.stream()、还是 ngx.req.socket(),它们基本流程都是一样的,只是一些细节参数上有区别(比如 TCP 和 UDP 的区别)。下面这些函数,都是用来辅助完成更高级的 socket 行为控制:

  • connect

  • sslhandshake

  • send

  • receive

  • close

  • settimeout

  • setoption

  • receiveuntil

  • setkeepalive

  • getreusedtimes

它们不仅完整兼容 LuaSocket 库的 TCP API,而且还是 100% 非阻塞 的。

这里给大家 show 一个例子,对 cosocket 使用有一个整体认识。

location /test {
    resolver 114.114.114.114;

    content_by_lua_block {
        local sock = ngx.socket.tcp()
        local ok, err = sock:connect("www.baidu.com", 80)
        if not ok then
            ngx.say("failed to connect to baidu: ", err)
            return
        end

        local req_data = "GET / HTTP/1.1\r\nHost: www.baidu.com\r\n\r\n"
        local bytes, err = sock:send(req_data)
        if err then
            ngx.say("failed to send to baidu: ", err)
            return
        end

        local data, err, partial = sock:receive()
        if err then
            ngx.say("failed to receive from baidu: ", err)
            return
        end

        sock:close()
        ngx.say("successfully talk to baidu! response first line: ", data)
    }
}

可以看到,这里的 socket 操作都是 同步非阻塞 的,完全不像 node.js 那样充满各种回调,整体看上去非常简洁优雅,效率还非常棒。

对 cosocket 做了这么多铺垫,到底他有多么重要呢?直接看一下官方默认绑定包有多少是基于 cosocket 的:

效仿这些基础库的实现方法,可以完成不同系统或组件的对接,例如 syslog、beanstalkd、mongodb 等,直接 copy 这些组件的通讯协议即可。

Nginx "stream" 子系统的官方模块版本(通用的下游 TCP 对话)。

基于 ngx_lua cosocket 的库。

基于 ngx_lua cosocket 的库。

基于 ngx_lua cosocket 的库。

基于 ngx_lua cosocket 的库。

基于 ngx_lua cosocket 的库。

提供 WebSocket 的客户端、服务端,基于 ngx_lua cosocket 的库。

ngx_stream_lua_module
lua-resty-memcached
lua-resty-redis
lua-resty-mysql
lua-resty-upload
lua-resty-dns
lua-resty-websocket