> 轉載聲明: 本文轉載自https://yxudong.github.io/%E3%80%8AOpenResty%E7%B2%BE%E5%8D%8E%E6%95%B4%E7%90%86%E3%80%8B3.OpenResty%20%E9%AB%98%E6%80%A7%E8%83%BD%E7%9A%84%E5%8E%9F%E5%9B%A0/,原作者為`Yxd`。本轉載僅出于學習和分享之目的,并不代表本公眾號作者觀點。版權屬于原作者,未經允許不得用于商業用途。如需轉載或引用,請與原作者聯系。
>
## 運行在 Nginx 整體架構之上
OpenResty 的 master 和 worker 進程中,都包含一個 LuaJIT VM。在同一個進程內的所有協程,都會共享這個 VM,并在這個 VM 中運行 Lua 代碼。
而在同一個時間點上,每個 worker 進程只能處理一個用戶的請求,也就是只有一個協程在運行。
NGINX 實際上是通過 epoll 的事件驅動,來減少等待和空轉,才盡可能地讓 CPU 資源都用于處理用戶的請求。
畢竟,只有單個的請求被足夠快地處理完,整體才能達到高性能的目的。
如果采用的是多線程模式,讓一個請求對應一個線程,那么在 C10K 的情況下,資源很容易就會被耗盡的。
> OpenResty 和 LuaJit 架構圖

## cosocket
是 OpenResty 的核心和精髓。
在早期的 OpenResty 版本中,如果想要去與 Redis、memcached 這些服務交互的話,需要使用 redis2-nginx-module、redis-nginx-module 和 memc-nginx-module這些 C 模塊。
這些模塊至今仍然在 OpenResty 的發行包中。不過,cosocket 功能加入以后,它們都已經被 lua-resty-redis 和 lua-resty-memcached 替代,基本上沒人再去使用 C 模塊連接外部服務了。
實際上,cosocket 是 OpenResty 中的專有名詞,是把協程和網絡套接字的英文拼在一起形成的,即 cosocket = coroutine + socket。
cosocket 是各種 lua-resty-\* 非阻塞庫的基礎,沒有 cosocket,開發者就無法用 Lua 來快速連接各種外部的網絡服務。
cosocket 不僅需要 Lua 協程特性的支持,也需要 Nginx 中非常重要的事件機制的支持,這兩者結合在一起,最終實現了非阻塞網絡 I/O。
如果我們在 OpenResty 中調用一個 cosocket 相關函數,內部實現便是下面這張圖的樣子:

cosocket 函數流程
遇到網絡 I/O 時,它會交出控制權(yield),把網絡事件注冊到 Nginx 監聽列表中,并把權限交給 Nginx;當有 Nginx 事件達到觸發條件時,便喚醒對應的協程繼續處理(resume)。
### cosocket API 和指令簡介
* 創建對象:ngx.socket.tcp。
* 設置超時:tcpsock:settimeout 和 tcpsock:settimeouts。
* 建立連接:tcpsock:connect。
* 發送數據:tcpsock:send。
* 接受數據:tcpsock:receive、tcpsock:receiveany 和 tcpsock:receiveuntil。
* 連接池:tcpsock:setkeepalive。
* 關閉連接:tcpsock:close。
這些 API 可以使用的上下文:
rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer., ssl_certificate_by_lua, ssl_session_fetch_by_lua*_
在某些階段是不能使用的,比如 init_by_lua,log_by_lua*,參考 https://github.com/openresty/lua-nginx-module#cosockets-not-available-everywhere
## LuaJit
通過 tracing 對熱代碼進行編譯。
### LuaJIT 在 OpenResty 整體架構中的位置

OpenResty 的 worker 進程都是 fork master 進程而得到的,master 進程中的 LuaJIT 虛擬機也會一起 fork 過來。
在同一個 worker 內的所有協程,都會共享這個 LuaJIT 虛擬機,Lua 代碼的執行也是在這個虛擬機中完成的。
### LuaJIT vs Lua
標準 Lua 和 LuaJIT 是兩回事兒,LuaJIT 只是兼容了 Lua 5.1 的語法。
在 OpenResty 中,可以用 Lua C API 來調用 C 函數,還可以在 LuaJIT 中使用 FFI。
#### 性能
其實標準 Lua 出于性能考慮,也內置了虛擬機 (Lua VM),所以 Lua 代碼并不是直接被解釋執行的,而是先由 Lua 編譯器編譯為字節碼(Byte Code),然后再由 Lua 虛擬機執行。
而 LuaJIT 的運行時環境,除了一個匯編實現的 Lua 解釋器外,還有一個可以直接生成機器代碼的 JIT 編譯器。
開始的時候,LuaJIT 和標準 Lua 一樣,Lua 代碼被編譯為字節碼,字節碼被 LuaJIT 的解釋器解釋執行。
但不同的是,LuaJIT 的解釋器會在執行字節碼的同時,記錄一些運行時的統計信息,當這些次數超過某個隨機的閾值時,便認為對應的 Lua 函數入口或者對應的 Lua 循環足夠熱,這時便會觸發 JIT 編譯器開始工作。
編譯的過程,是把 LuaJIT 字節碼先轉換成 LuaJIT 自己定義的中間碼(IR),然后再生成針對目標體系結構的機器碼。
所以,所謂 LuaJIT 的性能優化,本質上就是讓盡可能多的 Lua 代碼可以被 JIT 編譯器生成機器碼,而不是回退到 Lua 解釋器的解釋執行模式。
#### FFI
除了兼容 Lua 5.1 的語法并支持 JIT 外,LuaJIT 還緊密結合了 FFI(Foreign Function Interface),可以直接在 Lua 代碼中調用外部的 C 函數和使用 C 的數據結構。
類似的,我們可以用 FFI 來調用 NGINX、OpenSSL 的 C 函數,來完成更多的功能。

實際上,FFI 方式比傳統的 Lua/C API 方式的性能更優,這也是 lua-resty-core 項目(后面會介紹)存在的意義。
#### JIT 為什么不是全程編譯?
既然編譯過后效率更高,為什么不采用全程編譯,而是只針對熱代碼編譯?
1. 時間:如果是少量運行,得不償失
2. 空間
編譯后占用的內存會變大
3. JIT 編譯優化需要運行的信息
并不是所有的編譯執行都比解釋執行效率高,給的運行時信息越多,效果越好
- 設計模式系列
- 工廠方法模式
- 序言
- Windows程序注冊為服務的工具WinSW
- 基礎
- 安裝
- 開發規范
- 目錄結構
- 配置
- 快速入門
- 架構
- 請求流程
- 架構總覽
- URL訪問
- 容器和依賴注入
- 中間件
- 事件
- 代碼層結構
- 四個層次
- 路由
- 控制器
- 請求
- 響應
- 數據庫
- MySQL實時同步數據到ES解決方案
- 阿里云DTS數據MySQL同步至Elasticsearch實戰
- PHP中的MySQL連接池
- PHP異步非阻塞MySQL客戶端連接池
- 模型
- 視圖
- 注解
- @SpringBootApplication(exclude={DataSourceAutoConfiguration.calss})
- @EnableFeignClients(basePackages = "com.wotu.feign")
- @EnableAspectJAutoProxy
- @EnableDiscoveryClient
- 錯誤和日志
- 異常處理
- 日志處理
- 調試
- 驗證
- 驗證器
- 驗證規則
- 擴展庫
- 附錄
- Spring框架知識體系詳解
- Maven
- Maven和Composer
- 構建Maven項目
- 實操課程
- 01.初識SpringBoot
- 第1章 Java Web發展史與學習Java的方法
- 第2章 環境與常見問題踩坑
- 第3章 springboot的路由與控制器
- 02.Java編程思想深度理論知識
- 第1章 Java編程思想總體
- 第2章 英雄聯盟的小案例理解Java中最為抽象的概念
- 第3章 徹底理解IOC、DI與DIP
- 03.Spring與SpringBoot理論篇
- 第1章 Spring與SpringBoot導學
- 第2章 Spring IOC的核心機制:實例化與注入
- 第3章 SpringBoot基本配置原理
- 04.SprinBoot的條件注解與配置
- 第1章 conditonal 條件注解
- 第2章 SpringBoot自動裝配解析
- 05.Java異常深度剖析
- 第1章 Java異常分類剖析與自定義異常
- 第2章 自動配置Url前綴
- 06.參數校驗機制與LomBok工具集的使用
- 第1章 LomBok工具集的使用
- 第2章 參數校驗機制以及自定義校驗
- 07.項目分層設計與JPA技術
- 第1章 項目分層原則與層與層的松耦合原則
- 第2章 數據庫設計、實體關系與查詢方案探討
- 第3章 JPA的關聯關系與規則查詢
- 08.ORM的概念與思維
- 第1章 ORM的概念與思維
- 第2章 Banner等相關業務
- 第3章 再談數據庫設計技巧與VO層對象的技巧
- 09.JPA的多種查詢規則
- 第1章 DozerBeanMapper的使用
- 第2章 詳解SKU的規格設計
- 第3章 通用泛型Converter
- 10.令牌與權限
- 第1章 通用泛型類與java泛型的思考
- 常見問題
- 微服務
- demo
- PHP中Self、Static和parent的區別
- Swoole-Cli
- 為什么要使用現代化PHP框架?
- 公眾號
- 一鍵部署微信公眾號Markdown編輯器(支持適配和主題設計)
- Autodesigner 2.0發布
- Luya 一個現代化PHP開發框架
- PHPZip - 創建、讀取和管理 ZIP 文件的簡單庫
- 吊打Golang的PHP界天花板webman壓測對比
- 簡潔而強大的 YAML 解析庫
- 推薦一個革命性的PHP測試框架:Kahlan
- ServBay下一代Web開發環境
- 基于Websocket和Canvas實現多人協作實時共享白板
- Apipost預執行腳本如何調用外部PHP語言
- 認證和授權的安全令牌 Bearer Token
- Laradock PHP 的 Docker 完整本地開發環境
- 高效接口防抖策略,確保數據安全,避免重復提交的終極解決方案!
- TIOBE 6月榜單:PHP穩步前行,編程語言生態的微妙變化
- Aho-Corasick字符串匹配算法的實現
- Redis鍵空間通知 Keyspace Notification 事件訂閱
- ServBay如何啟用并運行Webman項目
- 使用mpdf實現導出pdf文件功能
- Medoo 輕量級PHP數據庫框架
- 在PHP中編寫和運行單元測試
- 9 PHP運行時基準性能測試
- QR碼生成器在PHP中的源代碼
- 使用Gogs極易搭建的自助Git服務
- Gitea
- webman如何記錄SQL到日志?
- Sentry PHP: 實時監測并處理PHP應用程序中的錯誤
- Swoole v6 Alpha 版本已發布
- Proxypin
- Rust實現的Redis內存數據庫發布
- PHP 8.4.0 Alpha 1 測試版本發布
- 121
- Golang + Vue 開發的開源輕量 Linux 服務器運維管理面板
- 內網穿透 FRP VS Tailscale
- 新一代開源代碼托管平臺Gitea
- 微服務系列
- Nacos云原生配置中心介紹與使用
- 輕量級的開源高性能事件庫libevent
- 國密算法
- 國密算法(商用密碼)
- GmSSL 支持國密SM2/SM3/SM4/SM9/SSL 密碼工具箱
- GmSSL PHP 使用
- 數據庫
- SQLite數據庫的Web管理工具
- 阿里巴巴MySQL數據庫強制規范
- PHP
- PHP安全測試秘密武器 PHPGGC
- 使用declare(strict_types=1)來獲得更健壯的PHP代碼
- PHP中的魔術常量
- OSS 直傳阿里騰訊示例
- PHP源碼編譯安裝APCu擴展實現數據緩存
- BI性能DuckDB數據管理系統
- 為什么別人可以是架構師!而我卻不是?
- 密碼還在用 MD5 加鹽?不如試試 password_hash
- Elasticsearch 在電商領域的應用與實踐
- Cron 定時任務入門
- 如何動態設置定時任務!而不是寫死在Linux Crontab
- Elasticsearch的四種查詢方式,你知道多少?
- Meilisearch vs Elasticsearch
- OpenSearch vs Elasticsearch
- Emlog 輕量級開源博客及建站系統
- 現代化PHP原生協程引擎 PRipple
- 使用Zephir編寫C擴展將PHP源代碼編譯加密
- 如何將PHP源代碼編譯加密,同時保證代碼能正常的運行
- 為什么選擇Zephir給PHP編寫動態擴展庫?
- 使用 PHP + XlsWriter實現百萬級數據導入導出
- Rust編寫PHP擴展
- 阿里云盤開放平臺對接進行文件同步
- 如何構建自己的PHP靜態可執行文件
- IM后端架構
- RESTful設計方法和規范
- PHP編譯器BPC 7.3 發布,成功編譯ThinkPHP8
- 高性能的配置管理擴展 Yaconf
- PHP實現雪花算法庫 Snowflake
- PHP官方現代化核心加密庫Sodium
- pie
- 現代化、精簡、非阻塞PHP標準庫PSL
- PHP泛型和集合
- 手把手教你正確使用 Composer包管理
- JWT雙令牌認證實現無感Token自動續期
- 最先進PHP大模型深度學習庫TransformersPHP
- PHP如何啟用 FFI 擴展
- PHP超集語言PXP
- 低延遲雙向實時事件通信 Socket.IO
- PHP OOP中的繼承和多態
- 強大的現代PHP高級調試工具Kint
- PHP基金會
- 基于webman+vue3高質量中后臺框架SaiAdmin
- 開源免費的定時任務管理系統:Gocron
- 簡單強大OCR工具EasyOCR在PHP中使用
- PHP代碼抽象語法樹工具PHP AST Viewer
- MySQL數據庫管理工具PHPMyAdmin
- Rust編寫的一款高性能多人代碼編輯器Zed
- 超高性能PHP框架Workerman v5.0.0-beta.8 發布
- 高并發系列
- 入門介紹及安裝
- Lua腳本開發 Hello World
- 執行流程與階段詳解
- Nginx Lua API 接口開發
- Lua模塊開發
- OpenResty 高性能的正式原因
- 記一次查找 lua-resty-mysql 庫 insert_id 的 bug
- 包管理工具OPM和LuaRocks使用
- 異步非阻塞HTTP客戶端庫 lua-resty-http
- Nginx 內置綁定變量
- Redis協程網絡庫 lua-resty-redis
- 動態HTML渲染庫 lua-testy-template
- 單獨的
- StackBlitz在線開發環境
- AI
- 基礎概念
- 12312
- 基礎鏡像的坑
- 利用phpy實現 PHP 編寫 Vision Transformer (ViT) 模型
- 語義化版本 2.0.0