{"id":4262,"date":"2025-12-22T17:37:53","date_gmt":"2025-12-22T09:37:53","guid":{"rendered":"https:\/\/blog.quantoyo.com\/?p=4262"},"modified":"2025-12-23T18:14:24","modified_gmt":"2025-12-23T10:14:24","slug":"nginx-lua-memcached","status":"publish","type":"post","link":"https:\/\/blog.quantoyo.com\/?p=4262","title":{"rendered":"Nginx + Lua + memcached"},"content":{"rendered":"\n<p>\u6574\u9ad4\u7d50\u69cb<\/p>\n\n\n\n<p>Client<br>\u2193<br>Nginx (OpenResty + Lua)<br>\u2193                                 \u2198<br>Memcached            Apache (localhost)<\/p>\n\n\n\n<!--more-->\n\n\n\n<p>\u4f7f\u7528 OpenResty\uff08Nginx + Lua \u7684\u6a19\u6e96\u767c\u884c\u7248\uff09\uff0c\u4e0d\u7528\u81ea\u5df1\u5b89\u88ddLUA<\/p>\n\n\n\n<p><\/p>\n\n\n\n<p>\u5b89\u88dd OpenResty\uff08\u7bc4\u4f8b\uff1aUbuntu \/ Debian\uff09<\/p>\n\n\n\n<pre class=\"wp-block-code has-cyan-bluish-gray-background-color has-background has-small-font-size\"><code>\nsudo apt install -y curl gnupg\ncurl -fsSL https:\/\/openresty.org\/package\/pubkey.gpg | sudo apt-key add -\necho \"deb http:\/\/openresty.org\/package\/ubuntu $(lsb_release -sc) main\" | sudo tee \/etc\/apt\/sources.list.d\/openresty.list\n\nsudo apt update\nsudo apt install -y openresty<\/code><\/pre>\n\n\n\n<p>\u555f\u52d5\uff1a<\/p>\n\n\n\n<pre class=\"wp-block-code has-cyan-bluish-gray-background-color has-background has-small-font-size\"><code><code>sudo systemctl start openresty<\/code><\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">\u5b89\u88dd memcached<\/h2>\n\n\n\n<pre class=\"wp-block-code has-cyan-bluish-gray-background-color has-background has-small-font-size\"><code><code>sudo apt install -y memcached\nsudo systemctl start memcached<\/code><\/code><\/pre>\n\n\n\n<p>\u6e2c\u8a66\uff1a<\/p>\n\n\n\n<pre class=\"wp-block-code has-cyan-bluish-gray-background-color has-background has-small-font-size\"><code><code>echo stats | nc 127.0.0.1 11211<\/code><\/code><\/pre>\n\n\n\n<p><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">\u6a94\u6848\u7d50\u69cb\uff08\u5efa\u8b70\uff09<\/h2>\n\n\n\n<pre class=\"wp-block-code has-cyan-bluish-gray-background-color has-background has-small-font-size\"><code><code>\/usr\/local\/openresty\/nginx\/\n\u251c\u2500 conf\/nginx.conf\n\u2514\u2500 lua\/cache.lua<\/code><\/code><\/pre>\n\n\n\n<p><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Nginx \u8a2d\u5b9a\u6a94\uff08nginx.conf\uff09<\/h2>\n\n\n\n<pre class=\"wp-block-code has-cyan-bluish-gray-background-color has-background has-small-font-size\"><code>worker_processes auto;\n\nevents {\n    worker_connections 4096;\n}\n\nhttp {\n    # Lua \u6a94\u6848\u8def\u5f91\n    lua_package_path \"\/usr\/local\/openresty\/nginx\/lua\/?.lua;;\";\n\n    # \u65e5\u8a8c\uff08debug \u6642\u975e\u5e38\u91cd\u8981\uff09\n    error_log logs\/error.log info;\n\n    # upstream\uff1a\u672c\u6a5f Apache\n    upstream apache_backend {\n        server 127.0.0.1:8080;\n        keepalive 32;\n    }\n\n    server {\n        listen 80;\n        server_name _;\n\n        # === \u5165\u53e3 ===\n        location \/ {\n            # \u6240\u6709 request \u5148\u4ea4\u7d66 Lua\n            content_by_lua_file \/usr\/local\/openresty\/nginx\/lua\/cache.lua;\n        }\n\n        # === \u771f\u6b63\u7684\u5f8c\u7aef\uff08\u53ea\u6709 Lua \u6703\u8df3\u9032\u4f86\uff09 ===\n        location @backend {\n            proxy_http_version 1.1;\n            proxy_set_header Connection \"\";\n\n            proxy_set_header Host $host;\n            proxy_set_header X-Real-IP $remote_addr;\n            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n\n            proxy_pass http:\/\/apache_backend;\n\n            # ===== \u628a Apache \u56de\u4f86\u7684 response \u5b58\u9032 memcached =====\n            body_filter_by_lua_block {\n                -- \u53ea\u5728 MISS \u6642\u624d\u5beb cache\n                if not ngx.ctx.cache_key then\n                    return\n                end\n\n                local chunk = ngx.arg&#91;1]\n                local eof   = ngx.arg&#91;2]\n\n                ngx.ctx.buffer = (ngx.ctx.buffer or \"\") .. (chunk or \"\")\n\n                if eof then\n                    local memcached = require \"resty.memcached\"\n                    local memc = memcached:new()\n                    memc:set_timeout(50)\n\n                    -- \u5f9e ctx \u53d6\u56de key \/ ttl\uff08\u7531 cache.lua \u8a2d\u5b9a\uff09\n                    local key = ngx.ctx.cache_key\n                    local ttl = ngx.ctx.cache_ttl or 30\n\n                    -- \u8207 cache.lua \u4f7f\u7528\u540c\u4e00\u7d44\u7bc0\u9ede\u8207 hash\n                    local servers = {\n                        { host = \"10.0.0.1\", port = 11211 },\n                        { host = \"10.0.0.2\", port = 11211 },\n                        { host = \"10.0.0.3\", port = 11211 },\n                    }\n\n                    local function pick(key)\n                        local idx = (ngx.crc32_long(key) % #servers) + 1\n                        return servers&#91;idx]\n                    end\n\n                    local s = pick(key)\n                    local ok, err = memc:connect(s.host, s.port)\n                    if ok then\n                        memc:set(key, ngx.ctx.buffer, ttl)\n                        memc:set_keepalive()\n                    else\n                        ngx.log(ngx.ERR, \"memcached set failed: \", err)\n                    end\n                end\n            }\n        }\n    }\n}<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Lua \u7a0b\u5f0f\uff08cache.lua\uff09<\/h2>\n\n\n\n<pre class=\"wp-block-code has-cyan-bluish-gray-background-color has-background has-small-font-size\"><code>local memcached = require \"resty.memcached\"\n\n-- memcached servers\nlocal memc_servers = {\n    { host = \"10.0.0.1\", port = 11211 },\n    { host = \"10.0.0.2\", port = 11211 },\n    { host = \"10.0.0.3\", port = 11211 },\n}\n\nlocal function pick_memc_server(key)\n    local crc = ngx.crc32_long(key)\n    local idx = (crc % #memc_servers) + 1\n    return memc_servers&#91;idx]\nend\n\n-- request params\nlocal args = ngx.req.get_uri_args()\nlocal func = args&#91;\"function\"]\nlocal fun2 = args&#91;\"fun2\"]\n\n-- function=a \u2192 bypass cache\nif func == \"a\" then\n    ngx.header&#91;\"X-Cache\"] = \"BYPASS\"\n    return ngx.exec(\"@backend\")\nend\n\n-- cookie\nlocal user_cookie = ngx.var.cookie_user_id or \"\"\n\n-- cache key\nlocal raw_key =\n    ngx.var.request_method .. \"|\" ..\n    ngx.var.uri .. \"|\" ..\n    (ngx.var.args or \"\") .. \"|\" ..\n    user_cookie\n\nlocal cache_key = \"cache:\" .. ngx.md5(raw_key)\n\n-- TTL logic\nlocal ttl = 30\nif func == \"b\" and (fun2 == \"BB\" or fun2 == \"CC\") then\n    ttl = 10\nend\n\n-- connect memcached\nlocal memc = memcached:new()\nmemc:set_timeout(50)\n\nlocal server = pick_memc_server(cache_key)\nlocal ok, err = memc:connect(server.host, server.port)\nif not ok then\n    ngx.log(ngx.ERR, err)\n    return ngx.exec(\"@backend\")\nend\n\n-- get cache\nlocal res, flags, err = memc:get(cache_key)\nif res then\n    ngx.header&#91;\"X-Cache\"] = \"HIT\"\n    ngx.say(res)\n    memc:set_keepalive()\n    return\nend\n\nngx.header&#91;\"X-Cache\"] = \"MISS\"\nngx.ctx.cache_key = cache_key\nngx.ctx.cache_ttl = ttl\n\nngx.exec(\"@backend\")<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Reload<\/h3>\n\n\n\n<pre class=\"wp-block-code has-cyan-bluish-gray-background-color has-background has-small-font-size\"><code><code>\/usr\/local\/openresty\/nginx\/sbin\/nginx -t\n\/usr\/local\/openresty\/nginx\/sbin\/nginx -s reload<\/code><\/code><\/pre>\n\n\n\n<p>\u6e2c\u8a66<\/p>\n\n\n\n<pre class=\"wp-block-code has-cyan-bluish-gray-background-color has-background has-small-font-size\"><code>curl -i \"http:\/\/server\/test.php?function=b&amp;fun2=BB\"\n\u4f60\u61c9\u8a72\u6703\u770b\u5230\uff1a\n\ncss\n\u8907\u88fd\u7a0b\u5f0f\u78bc\nX-Cache: MISS\n\u7b2c\u4e8c\u6b21\uff1a\n\ncss\n\u8907\u88fd\u7a0b\u5f0f\u78bc\nX-Cache: HIT<\/code><\/pre>\n\n\n\n<p><\/p>\n\n\n\n<p>cache.lua \u4e5f\u53ef\u4ee5\u6539\u6210\u6709\u9396\u7684\u7248\u672c<\/p>\n\n\n\n<pre class=\"wp-block-code has-cyan-bluish-gray-background-color has-background has-small-font-size\"><code>local memcached = require \"resty.memcached\"\n\n-- memcached servers\nlocal memc_servers = {\n    { host = \"10.0.0.1\", port = 11211 },\n    { host = \"10.0.0.2\", port = 11211 },\n    { host = \"10.0.0.3\", port = 11211 },\n}\n\nlocal function pick_memc_server(key)\n    local crc = ngx.crc32_long(key)\n    local idx = (crc % #memc_servers) + 1\n    return memc_servers&#91;idx]\nend\n\n-- request params\nlocal args = ngx.req.get_uri_args()\nlocal func = args&#91;\"function\"]\nlocal fun2 = args&#91;\"fun2\"]\n\n-- function=a \u2192 bypass cache\nif func == \"a\" then\n    ngx.header&#91;\"X-Cache\"] = \"BYPASS\"\n    return ngx.exec(\"@backend\")\nend\n\n-- cookie\nlocal user_cookie = ngx.var.cookie_user_id or \"\"\n\n-- cache key\nlocal raw_key =\n    ngx.var.request_method .. \"|\" ..\n    ngx.var.uri .. \"|\" ..\n    (ngx.var.args or \"\") .. \"|\" ..\n    user_cookie\n\nlocal cache_key = \"cache:\" .. ngx.md5(raw_key)\n\n-- TTL logic\nlocal ttl = 30\nif func == \"b\" and (fun2 == \"BB\" or fun2 == \"CC\") then\n    ttl = 10\nend\n\n-- pick memcached server\nlocal server = pick_memc_server(cache_key)\nlocal memc = memcached:new()\nmemc:set_timeout(50)\nlocal ok, err = memc:connect(server.host, server.port)\nif not ok then\n    ngx.log(ngx.ERR, \"memcached connect failed: \", err)\n    return ngx.exec(\"@backend\")\nend\n\n-- 1\ufe0f\u20e3 \u5148\u67e5 cache\nlocal res, flags, err = memc:get(cache_key)\nif res then\n    ngx.header&#91;\"X-Cache\"] = \"HIT\"\n    ngx.say(res)\n    memc:set_keepalive()\n    return\nend\n\n-- 2\ufe0f\u20e3 \u5617\u8a66 set lock\uff0c\u907f\u514d cache stampede\nlocal lock_key = \"lock:\" .. cache_key\nlocal lock_ttl = 5  -- lock \u6700\u9577\u5b58\u6d3b 5 \u79d2\n\nlocal lock_set, err = memc:add(lock_key, \"1\", lock_ttl)\nif lock_set then\n    -- \u6210\u529f\u7372\u5f97\u9396 \u2192 \u6211\u53bb\u6253\u5f8c\u7aef\n    ngx.header&#91;\"X-Cache\"] = \"MISS|LOCKED\"\n    ngx.ctx.cache_key = cache_key\n    ngx.ctx.cache_ttl = ttl\n    return ngx.exec(\"@backend\")\nelse\n    -- \u6c92\u62ff\u5230\u9396 \u2192 \u7b49\u5f85 \/ retry\n    ngx.header&#91;\"X-Cache\"] = \"MISS|WAIT\"\n    local wait_time = 0\n    local wait_interval = 0.05  -- 50ms\n    local max_wait = 2           -- \u6700\u591a\u7b49 2 \u79d2\n\n    while wait_time &lt; max_wait do\n        local res2, _, _ = memc:get(cache_key)\n        if res2 then\n            ngx.say(res2)\n            memc:set_keepalive()\n            return\n        end\n        ngx.sleep(wait_interval)\n        wait_time = wait_time + wait_interval\n    end\n\n    -- \u8d85\u6642\u6c92\u62ff\u5230 cache \u2192 \u4ecd\u7136\u6253\u5f8c\u7aef\n    ngx.ctx.cache_key = cache_key\n    ngx.ctx.cache_ttl = ttl\n    return ngx.exec(\"@backend\")\nend<\/code><\/pre>\n\n\n\n<p><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Nginx \u53ef\u4ee5\u89e3\u6c7a\u7684\u75db\u9ede<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>\u975e\u963b\u585e<\/strong> \u2192 \u4e0d\u6703\u88ab\u6162 PHP \u5361\u6b7b<\/li>\n\n\n\n<li><strong>cache + lock<\/strong> \u2192 \u91cd\u8907\u8acb\u6c42\u4e0d\u6253 Apache<\/li>\n\n\n\n<li><strong>rate limit<\/strong> \u2192 \u653b\u64ca \/ \u9ad8\u4f75\u767c\u88ab\u63a7\u7ba1<\/li>\n\n\n\n<li><strong>\u8f15\u91cf\u8a18\u61b6\u9ad4\u4f7f\u7528<\/strong> \u2192 \u4e0a\u842c\u9023\u7dda\u4ecd\u7a69\u5b9a<\/li>\n<\/ul>\n\n\n\n<p><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Apache \u5bb9\u6613\u5361\u4f4f\u7684\u539f\u56e0<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u6bcf\u500b thread\/process \u90fd\u8981\u7dad\u8b77 TCP \/ TLS \/ PHP context<\/li>\n\n\n\n<li>\u5982\u679c\u5f8c\u7aef PHP \u57f7\u884c\u6162\uff1a\n<ul class=\"wp-block-list\">\n<li>\u9023\u7dda\u88ab\u5360\u7528<\/li>\n\n\n\n<li>\u65b0\u9023\u7dda\u7b49\u5f85 \u2192 client \u8d85\u6642<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>\u9ad8\u4f75\u767c\u6642\u8a18\u61b6\u9ad4\u7206\u6389<\/li>\n\n\n\n<li>Linux process scheduler \u958b\u92b7\u5927\uff0ccontext switch \u6210\u672c\u9ad8<\/li>\n<\/ul>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>\u7d50\u679c\uff1aApache \u770b\u8d77\u4f86\u300c\u5361\u4f4f\u300d\u6216\u300c\u7121\u6cd5\u56de\u6536 thread\u300d<\/p>\n<\/blockquote>\n","protected":false},"excerpt":{"rendered":"<p>\u6574\u9ad4\u7d50\u69cb Client\u2193Nginx (OpenResty + Lua)\u2193 \u2198Memcached Apache  &hellip; <\/p>\n<p class=\"link-more\"><a href=\"https:\/\/blog.quantoyo.com\/?p=4262\" class=\"more-link\">\u95b1\u8b80\u5168\u6587<span class=\"screen-reader-text\">\u3008Nginx + Lua + memcached\u3009<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[3],"tags":[705,706,202,707,708,212],"class_list":["post-4262","post","type-post","status-publish","format-standard","hentry","category-technology","tag-lua","tag-memcached","tag-nginx","tag-openresty","tag-reverse-proxy","tag-212"],"_links":{"self":[{"href":"https:\/\/blog.quantoyo.com\/index.php?rest_route=\/wp\/v2\/posts\/4262","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.quantoyo.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.quantoyo.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.quantoyo.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.quantoyo.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=4262"}],"version-history":[{"count":1,"href":"https:\/\/blog.quantoyo.com\/index.php?rest_route=\/wp\/v2\/posts\/4262\/revisions"}],"predecessor-version":[{"id":4263,"href":"https:\/\/blog.quantoyo.com\/index.php?rest_route=\/wp\/v2\/posts\/4262\/revisions\/4263"}],"wp:attachment":[{"href":"https:\/\/blog.quantoyo.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=4262"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.quantoyo.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=4262"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.quantoyo.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=4262"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}