<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:media="http://search.yahoo.com/mrss/">
  <channel>
    <title>高併發｜林鼎淵 Dean Lin</title>
    <link>https://deanlin.net/tags/%E9%AB%98%E4%BD%B5%E7%99%BC/</link>
    <image><url>https://deanlin.net/images/author_hu_aa256fc934e9a932.jpg</url><title>高併發｜林鼎淵 Dean Lin</title><link>https://deanlin.net/tags/%E9%AB%98%E4%BD%B5%E7%99%BC/</link><width>144</width><height>144</height></image>
    <description>林鼎淵（Dean Lin）的部落格：生成式 AI 企業內訓講師、外商資安公司軟體專家、7 本書作者。400 多篇文章用白話分享生成式 AI 實戰、Claude Code 與 Vibe Coding 教學、團隊合作與職場成長。</description>
    <generator>Hugo</generator>
    <language>zh-tw</language>
    <copyright>© 2020–2026 林鼎淵 Dean Lin</copyright>
    <lastBuildDate>Fri, 25 Feb 2022 16:04:32 +0800</lastBuildDate>
    <atom:link href="https://deanlin.net/tags/%E9%AB%98%E4%BD%B5%E7%99%BC/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>調整 php-fpm &amp; nginx 參數，讓 Server 可以承受更大的流量</title>
      <link>https://deanlin.net/blog/202202/tune-php-fpm-nginx/</link>
      <pubDate>Fri, 25 Feb 2022 16:04:32 +0800</pubDate>
      <guid>https://deanlin.net/blog/202202/tune-php-fpm-nginx/</guid>
      <dc:creator>林鼎淵</dc:creator>
      <category>技術教學</category>
      <description>因為準備上線的系統有可能遇到大流量的情境，所以上線前先用 Apache Bench 來做測試；不測還好，一測發現在連線數 100、request 數量 10000 的條件下 Call POST API，居然有 50% 的 request…</description>
      <media:content url="https://deanlin.net/blog/202202/tune-php-fpm-nginx/images/tune-php-fpm-nginx-og_hu_2959ef8ef24dad02.jpg" medium="image" type="image/jpeg" width="1200" height="675" />
      <content:encoded><![CDATA[<p>因為準備上線的系統有可能遇到大流量的情境，所以上線前先用 Apache Bench 來做測試；不測還好，一測發現在連線數 100、request 數量 10000 的條件下 Call POST API，居然有 50% 的 request fail，如果把這樣的系統上線肯定會出包，所以來研究如何調校 php-fpm &amp; nginx 的參數，讓 Server 在硬體不升級的狀態下盡可能承受最大的流量。</p>
<blockquote>
<p>如果對壓力測試不熟悉的朋友，可以參考筆者先前分享的文章：「<a href="https://deanlin.net/blog/202110/apache-bench-load-testing/">Apache Bench — 壓力測試好夥伴</a>」</p>
</blockquote>
<p><img src="https://deanlin.net/blog/202202/tune-php-fpm-nginx/images/tune-php-fpm-nginx-01.webp" width="1400" height="740" alt="調整 php-fpm &amp; nginx 參數，讓 Server 可以承受更大的流量（圖 1）" fetchpriority="high" decoding="async"></p>
<pre tabindex="0"><code>大綱
一、了解目前 Server 資源利用率
二、調整 php-fpm 參數
三、調整 nginx 參數增加併發數量
</code></pre><h2 id="一了解目前-server-資源利用率">一、了解目前 Server 資源利用率<a class="h-anchor" href="#%e4%b8%80%e4%ba%86%e8%a7%a3%e7%9b%ae%e5%89%8d-server-%e8%b3%87%e6%ba%90%e5%88%a9%e7%94%a8%e7%8e%87" aria-label="這個段落的連結"></a></h2>
<p>進入 Server 後，你可以用 <code>top</code> 這個指令來觀察 Server 的各項參數；它所呈現參數是動態的，非常易於理解與判斷。</p>
<p><img src="https://deanlin.net/blog/202202/tune-php-fpm-nginx/images/tune-php-fpm-nginx-02.webp" width="1400" height="790" alt="一、了解目前 Server 資源利用率（圖 2）" loading="lazy" decoding="async"></p>
<p>接下來你可以執行一段 Apache Bench 的指令（建議是 POST），來測試看看他怎麼爆掉的：</p>
<pre tabindex="0"><code>ab -n 10000 -c 100 -p post.txt -T application/json -H ‘Authorization: Token your_token’ https://your_test_ip/
</code></pre><p>上面的指令執行後，可以得到類似下面的報表：</p>
<p><img src="https://deanlin.net/blog/202202/tune-php-fpm-nginx/images/tune-php-fpm-nginx-03.webp" width="1400" height="668" alt="一、了解目前 Server 資源利用率（圖 3）" loading="lazy" decoding="async"></p>
<p>在上面報表中可以看到居然有 50% 的 request fail，可是用 <code>top</code>觀察 Server 的資源時，發現無論是 CPU 、記憶體距離極限還有一段距離，且 idle 都還有剩：</p>
<p><img src="https://deanlin.net/blog/202202/tune-php-fpm-nginx/images/tune-php-fpm-nginx-04.webp" width="759" height="126" alt="一、了解目前 Server 資源利用率（圖 4）" loading="lazy" decoding="async"></p>
<p>所以下面我教大家調整哪些 php-fpm &amp; nginx 的設定，盡量讓這台 Server 發揮出他的極限。</p>
<blockquote>
<p><strong>備註 1</strong>：通常 POST 對資源的消耗遠超 GET，如果 API 會修改資料庫內容、進行複雜計算就會消耗更多資源，這些都是要考量進去的。</p>
<p><strong>備註 2</strong>：我們的目標是<strong>追求承受最大流量，而不是努力把 CPU 和 MEM 耗盡</strong>；經實驗，有時把他們耗盡反而會有更多的 request fail，這部分有興趣的讀者可以自己實驗看看。</p>
</blockquote>
<hr>
<h2 id="二調整-php-fpm-參數">二、調整 php-fpm 參數<a class="h-anchor" href="#%e4%ba%8c%e8%aa%bf%e6%95%b4-php-fpm-%e5%8f%83%e6%95%b8" aria-label="這個段落的連結"></a></h2>
<h3 id="step-1先進入-php-fpm-的-conf-設定檔">STEP 1：先進入 php-fpm 的 conf 設定檔<a class="h-anchor" href="#step-1%e5%85%88%e9%80%b2%e5%85%a5-php-fpm-%e7%9a%84-conf-%e8%a8%ad%e5%ae%9a%e6%aa%94" aria-label="這個段落的連結"></a></h3>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo vi /etc/php/7.4/fpm/pool.d/www.conf
</span></span></code></pre></div><p>然後認識以下參數各自有什麼作用：</p>
<ul>
<li><strong>pm</strong>：預設為「dynamic」，如果設定為「static」下面的參數只有「pm.max_children」會生效。</li>
<li><strong>pm.max_children</strong>：設置子進程最大數值，子進程數量太少會影響處理連線的速度，太多可能會導致資源耗盡。</li>
<li><strong>pm.start_servers</strong>：php-fpm 啟動起始進程數，數值需小於 pm.max_children。</li>
</ul>
<blockquote>
<p>建議為：(pm.max_spare_servers — pm.min_spare_servers)/2</p>
</blockquote>
<ul>
<li><strong>pm.min_spare_servers</strong>：php-fpm 的最小空閒進程數。</li>
<li><strong>pm.max_spare_servers</strong>：php-fpm 的最大空閒進程數。</li>
<li><strong>pm.max_requests</strong>：當子進程的 request 數量到達後關閉，再開一個新的。</li>
</ul>
<p>下面提供的參數<strong>僅為範例</strong>，請依照 Server 實際狀況調整，並且用 <code>top</code> 這個指令觀察資源消耗狀態（主要看 CPU、MEM 使用率）。</p>
<pre tabindex="0"><code>pm = dynamic
pm.max_children = 200
pm.start_servers = 150
pm.min_spare_servers = 100
pm.max_spare_servers = 200
pm.max_requests = 1000
</code></pre><h3 id="step-2完成後後重啟-php-fpm">STEP 2：完成後後重啟 php fpm<a class="h-anchor" href="#step-2%e5%ae%8c%e6%88%90%e5%be%8c%e5%be%8c%e9%87%8d%e5%95%9f-php-fpm" aria-label="這個段落的連結"></a></h3>
<p>執行下面指令才會生效喔～</p>
<pre tabindex="0"><code>service php7.4-fpm restart
</code></pre><p>如果剛剛的參數設定上有問題或是錯誤，上面會有錯誤提示，跟著提示操作就好了：</p>
<p><img src="https://deanlin.net/blog/202202/tune-php-fpm-nginx/images/tune-php-fpm-nginx-05.webp" width="1279" height="530" alt="STEP 2：完成後後重啟 php fpm（圖 5）" loading="lazy" decoding="async"></p>
<hr>
<h2 id="三調整-nginx-參數增加併發數量">三、調整 nginx 參數增加併發數量<a class="h-anchor" href="#%e4%b8%89%e8%aa%bf%e6%95%b4-nginx-%e5%8f%83%e6%95%b8%e5%a2%9e%e5%8a%a0%e4%bd%b5%e7%99%bc%e6%95%b8%e9%87%8f" aria-label="這個段落的連結"></a></h2>
<p>調整完 php-fpm 的參數後，Server 相較之前可以承受更大的流量；但如隨著系統使用者的成長，你很快會遇到下一個瓶頸，在決定花錢升級 Server 硬體水平前，可以先嘗試調整 nginx 的參數，看看是否能在支撐一段時間ＸＤ</p>
<h3 id="step-1確認-server-有多少個-cpu-核心">STEP 1：確認 Server 有多少個 CPU 核心<a class="h-anchor" href="#step-1%e7%a2%ba%e8%aa%8d-server-%e6%9c%89%e5%a4%9a%e5%b0%91%e5%80%8b-cpu-%e6%a0%b8%e5%bf%83" aria-label="這個段落的連結"></a></h3>
<p>下面這段指令可以知道這台 Server 有多少個 CPU 核心（worker_processes）：</p>
<pre tabindex="0"><code>grep processor /proc/cpuinfo | wc -l
</code></pre><h3 id="step-2進入-nginx-的-conf-設定檔">STEP 2：進入 nginx 的 conf 設定檔<a class="h-anchor" href="#step-2%e9%80%b2%e5%85%a5-nginx-%e7%9a%84-conf-%e8%a8%ad%e5%ae%9a%e6%aa%94" aria-label="這個段落的連結"></a></h3>
<p>接著進入<strong>nginx.conf</strong>，查看目前 nginx 默認使用多少 CPU 核心數。</p>
<pre tabindex="0"><code>vi /etc/nginx/nginx.conf
</code></pre><p>你可以將 <strong>worker_processes</strong> 調整成 CPU 核心數，或是用 auto 讓他自動偵測：</p>
<pre tabindex="0"><code>worker_processes auto;
</code></pre><p>如果系統會遇到高併發場景，可以設置 <strong>events</strong> 的參數如下：</p>
<ul>
<li><strong>worker_connections</strong>：設定 worker 最大可承受連線數（最大值為 65535）。</li>
</ul>
<blockquote>
<p>連線的總數為 worker_processes * worker_connections。</p>
</blockquote>
<ul>
<li><strong>multi_accept</strong>：收到一個 request 後，盡可能多接收其他的 request。</li>
<li><strong>use epoll</strong>：利用很棒的演算法提升效能。</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span>events<span style="color:#6e7681"> </span><span style="color:#f85149">{</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span>worker_connections<span style="color:#6e7681"> </span><span style="color:#a5d6ff">4096</span>;<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span>multi_accept<span style="color:#6e7681"> </span><span style="color:#ff7b72">on</span>;<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span>use<span style="color:#6e7681"> </span>epoll;<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#f85149">}</span><span style="color:#6e7681">
</span></span></span></code></pre></div><h3 id="step-3設定完記得要重啟-nginx">STEP 3：設定完記得要重啟 nginx<a class="h-anchor" href="#step-3%e8%a8%ad%e5%ae%9a%e5%ae%8c%e8%a8%98%e5%be%97%e8%a6%81%e9%87%8d%e5%95%9f-nginx" aria-label="這個段落的連結"></a></h3>
<pre tabindex="0"><code>nginx -s reload
</code></pre><hr>
<blockquote class="pullquote"><p>希望這篇文章能讓讀者初步了解在 php-fpm &amp; nginx 的設定中，哪些參數可以讓 Server 承受更大的流量，如果讀者有使用其他妙招也歡迎分享喔～</p>
</blockquote>
]]></content:encoded>
    </item>
    <item>
      <title>模擬 K8s Auto Scaling（可在 Local 端測試）</title>
      <link>https://deanlin.net/blog/202201/kubernetes-autoscaling-local/</link>
      <pubDate>Fri, 21 Jan 2022 12:27:18 +0800</pubDate>
      <guid>https://deanlin.net/blog/202201/kubernetes-autoscaling-local/</guid>
      <dc:creator>林鼎淵</dc:creator>
      <category>技術教學</category>
      <description>K8s 最吸引人的其中一個功能應該就是用 Auto Scaling 來應付高併發(High concurrency)情境，今天就用一個水平自動擴展(HPA)的範例來讓大家了解他的實際運作模式。</description>
      <media:content url="https://deanlin.net/blog/202201/kubernetes-autoscaling-local/images/kubernetes-autoscaling-local-og_hu_7b901ad632188507.jpg" medium="image" type="image/jpeg" width="1200" height="675" />
      <content:encoded><![CDATA[<p>K8s 最吸引人的其中一個功能應該就是用 Auto Scaling 來應付高併發(High concurrency)情境，今天就用一個水平自動擴展(HPA)的範例來讓大家了解他的實際運作模式。</p>
<p><img src="https://deanlin.net/blog/202201/kubernetes-autoscaling-local/images/kubernetes-autoscaling-local-01.webp" width="1400" height="740" alt="模擬 K8s Auto Scaling（可在 Local 端測試）（圖 1）" fetchpriority="high" decoding="async"></p>
<pre tabindex="0"><code>文章大綱
一、撰寫 Auto Scaling 的 yaml 檔
  ➤ HorizontalPodAutoscaler（水平自動擴展）參數說明
  ➤ 建立「nodetest-k8s-auto.yaml」來做 Auto Scaling 測試
二、調整 Service、Deployment 參數並加入 yaml 檔
  ➤ 調整 Service 參數
  ➤ 調整 Deployment 參數
  ➤ 將 Service、Deployment 設定合併到 nodetest-k8s-auto.yaml 中
三、部署＆測試 pod 是否會依據流量的增加自動擴展
  ➤ 開啟流量監控、部署 K8s
  ➤ 取得 Deployment 的外部 ip，然後瘋狂存取來測試 Auto Scaling
  ➤ 瘋狂存取後後，確認 maxReplicas 設定是否生效
  ➤ 關閉存取後，確認 minReplicas 設定是否生效
  ➤ 測試完成後將建立的 K8s 服務刪除、關閉 minikube
</code></pre><blockquote>
<p>如果讀者尚未在本機安裝 K8s 環境，可以先閱讀：「<a href="https://deanlin.net/blog/202201/nodejs-docker-kubernetes-deploy/">帶你將 Node.js Web App 給 Docker 化，並用 Kubernetes 部署的超簡單範例</a>」來設定基礎環境喔！</p>
</blockquote>
<h2 id="一撰寫-auto-scaling-的-yaml-檔">一、撰寫 Auto Scaling 的 yaml 檔<a class="h-anchor" href="#%e4%b8%80%e6%92%b0%e5%af%ab-auto-scaling-%e7%9a%84-yaml-%e6%aa%94" aria-label="這個段落的連結"></a></h2>
<blockquote>
<p>你也可以直接去<a href="https://github.com/dean9703111/nodejs-docker-k8s-auto-scaling-example">筆者的 github</a>，直接將專案 Clone 到本機來做測試。</p>
</blockquote>
<h3 id="-horizontalpodautoscaler水平自動擴展參數說明">HorizontalPodAutoscaler（水平自動擴展）參數說明<a class="h-anchor" href="#-horizontalpodautoscaler%e6%b0%b4%e5%b9%b3%e8%87%aa%e5%8b%95%e6%93%b4%e5%b1%95%e5%8f%83%e6%95%b8%e8%aa%aa%e6%98%8e" aria-label="這個段落的連結"></a></h3>
<ul>
<li><strong>scaleTargetRef</strong>：填寫你想要針對哪個 ReplicaSet 或 Deployment 或 Replication Controller 來做 Scaling。</li>
<li><strong>minReplicas、maxReplicas</strong>：Pod 擴展範圍。</li>
<li><strong>targetCPUUtilizationPercentage</strong>：Auto Scaling 是根據 CPU 用量來做擴展，判定的依據為 Deployment resources 的 requests 設定值（如果沒填寫基本上就不會動 XD）。</li>
</ul>
<h3 id="-建立nodetest-k8s-autoyaml來做-auto-scaling-測試">建立「nodetest-k8s-auto.yaml」來做 Auto Scaling 測試<a class="h-anchor" href="#-%e5%bb%ba%e7%ab%8bnodetest-k8s-autoyaml%e4%be%86%e5%81%9a-auto-scaling-%e6%b8%ac%e8%a9%a6" aria-label="這個段落的連結"></a></h3>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#7ee787">apiVersion</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">autoscaling/v1</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#7ee787">kind</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">HorizontalPodAutoscaler</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#7ee787">metadata</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span><span style="color:#7ee787">name</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">node-web-app</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#7ee787">spec</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span><span style="color:#7ee787">scaleTargetRef</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">apiVersion</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">apps/v1</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">kind</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">Deployment</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">name</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">node-web-app</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span><span style="color:#7ee787">minReplicas</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">1</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span><span style="color:#7ee787">maxReplicas</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">5</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span><span style="color:#7ee787">targetCPUUtilizationPercentage</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">10</span><span style="color:#6e7681">
</span></span></span></code></pre></div><p class="code-source">原始碼：<a href="https://gist.github.com/dean9703111/3626e36b2122a62142d94e740157f792" rel="noopener">nodetest-k8s-auto.yaml</a>（GitHub Gist）</p>
<hr>
<h2 id="二調整-servicedeployment-參數並加入-yaml-檔">二、調整 Service、Deployment 參數並加入 yaml 檔<a class="h-anchor" href="#%e4%ba%8c%e8%aa%bf%e6%95%b4-servicedeployment-%e5%8f%83%e6%95%b8%e4%b8%a6%e5%8a%a0%e5%85%a5-yaml-%e6%aa%94" aria-label="這個段落的連結"></a></h2>
<blockquote>
<p>這裡的 Service、Deployment 都是從<a href="https://deanlin.net/blog/202201/nodejs-docker-kubernetes-deploy/">先前這篇文章</a>改良過來的喔。</p>
<p>Docker image 可以 pull 筆者的範例，或是使用自己的都可以。</p>
</blockquote>
<h3 id="-調整-service-參數">調整 Service 參數<a class="h-anchor" href="#-%e8%aa%bf%e6%95%b4-service-%e5%8f%83%e6%95%b8" aria-label="這個段落的連結"></a></h3>
<ul>
<li><strong>nodePort</strong>：這裡建議指定一個 port，如果沒有設定的話，最後對外 ip 的 port 為隨機產生。</li>
</ul>
<h3 id="-調整-deployment-參數">調整 Deployment 參數<a class="h-anchor" href="#-%e8%aa%bf%e6%95%b4-deployment-%e5%8f%83%e6%95%b8" aria-label="這個段落的連結"></a></h3>
<ul>
<li><strong>resources 的 requests</strong>：如果 cpu 設定為 <code>100m</code>，而 targetCPUUtilizationPercentage 設定為 <code>10</code>；當 Pod 的 CPU 使用率為 <code>1m</code>（100m x 10%） 的時候就要進行 Auto Scaling。</li>
<li><strong>replicas</strong>：特別設定為 1 方便觀察，原則上他的值要跟 minReplicas 相同。</li>
</ul>
<h3 id="-將-servicedeployment-設定合併到-nodetest-k8s-autoyaml-中">將 Service、Deployment 設定合併到 nodetest-k8s-auto.yaml 中<a class="h-anchor" href="#-%e5%b0%87-servicedeployment-%e8%a8%ad%e5%ae%9a%e5%90%88%e4%bd%b5%e5%88%b0-nodetest-k8s-autoyaml-%e4%b8%ad" aria-label="這個段落的連結"></a></h3>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#7ee787">apiVersion</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">v1</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#7ee787">kind</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">Service</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#7ee787">metadata</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span><span style="color:#7ee787">name</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">node-app-service</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#7ee787">spec</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span><span style="color:#7ee787">selector</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">app</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">node-web-app</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span><span style="color:#7ee787">ports</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span>- <span style="color:#7ee787">protocol</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">&#34;TCP&#34;</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">      </span><span style="color:#7ee787">port</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">8080</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">      </span><span style="color:#7ee787">targetPort</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">8080</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">      </span><span style="color:#7ee787">nodePort</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">30390</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span><span style="color:#7ee787">type</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">LoadBalancer</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72">---</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#7ee787">apiVersion</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">apps/v1</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#7ee787">kind</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">Deployment</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#7ee787">metadata</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span><span style="color:#7ee787">name</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">node-web-app</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#7ee787">spec</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span><span style="color:#7ee787">selector</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">matchLabels</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">      </span><span style="color:#7ee787">app</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">node-web-app</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span><span style="color:#7ee787">replicas</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">1</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span><span style="color:#7ee787">template</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">metadata</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">      </span><span style="color:#7ee787">labels</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">        </span><span style="color:#7ee787">app</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">node-web-app</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">spec</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">      </span><span style="color:#7ee787">containers</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">        </span>- <span style="color:#7ee787">name</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">node-web-app</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">          </span><span style="color:#7ee787">image</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">babydragon9703111/node-web-app:latest</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">          </span><span style="color:#7ee787">imagePullPolicy</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">Always</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">          </span><span style="color:#7ee787">ports</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">            </span>- <span style="color:#7ee787">containerPort</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">8080</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">          </span><span style="color:#7ee787">resources</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">            </span><span style="color:#7ee787">requests</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">              </span><span style="color:#7ee787">cpu</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">100m</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">              </span><span style="color:#7ee787">memory</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">50Mi</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">            </span><span style="color:#7ee787">limits</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">              </span><span style="color:#7ee787">cpu</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">200m</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">              </span><span style="color:#7ee787">memory</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">100Mi</span><span style="color:#6e7681">
</span></span></span></code></pre></div><p class="code-source">原始碼：<a href="https://gist.github.com/dean9703111/18162c15146ad0bd116b07fa51bfa784" rel="noopener">nodetest-k8s-auto-extend.yaml</a>（GitHub Gist）</p>
<hr>
<h2 id="三部署測試-pod-是否會依據流量的增加自動擴展">三、部署＆測試 pod 是否會依據流量的增加自動擴展<a class="h-anchor" href="#%e4%b8%89%e9%83%a8%e7%bd%b2%e6%b8%ac%e8%a9%a6-pod-%e6%98%af%e5%90%a6%e6%9c%83%e4%be%9d%e6%93%9a%e6%b5%81%e9%87%8f%e7%9a%84%e5%a2%9e%e5%8a%a0%e8%87%aa%e5%8b%95%e6%93%b4%e5%b1%95" aria-label="這個段落的連結"></a></h2>
<h3 id="-開啟流量監控部署-k8s">開啟流量監控、部署 K8s<a class="h-anchor" href="#-%e9%96%8b%e5%95%9f%e6%b5%81%e9%87%8f%e7%9b%a3%e6%8e%a7%e9%83%a8%e7%bd%b2-k8s" aria-label="這個段落的連結"></a></h3>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># 將 minikube 監控流量的 addons 打開</span>
</span></span><span style="display:flex;"><span>minikube addons enable metrics-server
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># 部署剛剛建立的 nodetest-k8s-auto.yaml</span>
</span></span><span style="display:flex;"><span>kubectl apply -f nodetest-k8s-auto.yaml
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># 查看 Auto Scaler 狀態</span>
</span></span><span style="display:flex;"><span>kubectl get hpa
</span></span></code></pre></div><p><img src="https://deanlin.net/blog/202201/kubernetes-autoscaling-local/images/kubernetes-autoscaling-local-02.webp" width="1400" height="360" alt="開啟流量監控、部署 K8s（圖 2）" loading="lazy" decoding="async"></p>
<blockquote>
<p>一開始 TARGETS 會顯示 <code>&lt;unknown&gt;</code>，這是因為還在判斷流量，過一陣子就會顯示百分比了</p>
</blockquote>
<h3 id="-取得-deployment-的外部-ip然後瘋狂存取來測試-auto-scaling">取得 Deployment 的外部 ip，然後瘋狂存取來測試 Auto Scaling<a class="h-anchor" href="#-%e5%8f%96%e5%be%97-deployment-%e7%9a%84%e5%a4%96%e9%83%a8-ip%e7%84%b6%e5%be%8c%e7%98%8b%e7%8b%82%e5%ad%98%e5%8f%96%e4%be%86%e6%b8%ac%e8%a9%a6-auto-scaling" aria-label="這個段落的連結"></a></h3>
<pre tabindex="0"><code># 取得 Deployment 的外部 ip（此時會看到 port 與設定的 nodePort 相同）
minikube service node-app-service --url

# 開始瘋狂存取剛剛輸出的 url 來確認 pod 是否會自動成長
while true
do curl &lt;Deployment 的外部 ip&gt;
done
</code></pre><p><img src="https://deanlin.net/blog/202201/kubernetes-autoscaling-local/images/kubernetes-autoscaling-local-03.webp" width="1400" height="275" alt="取得 Deployment 的外部 ip，然後瘋狂存取來測試 Auto Scaling（圖 3）" loading="lazy" decoding="async"></p>
<h3 id="-瘋狂存取後後確認-maxreplicas-設定是否生效">瘋狂存取後後，確認 maxReplicas 設定是否生效<a class="h-anchor" href="#-%e7%98%8b%e7%8b%82%e5%ad%98%e5%8f%96%e5%be%8c%e5%be%8c%e7%a2%ba%e8%aa%8d-maxreplicas-%e8%a8%ad%e5%ae%9a%e6%98%af%e5%90%a6%e7%94%9f%e6%95%88" aria-label="這個段落的連結"></a></h3>
<p>看 REPLICAS 這個欄位是否會依據流量的增加自動擴展到設定的 maxReplicas。</p>
<p><img src="https://deanlin.net/blog/202201/kubernetes-autoscaling-local/images/kubernetes-autoscaling-local-04.webp" width="1400" height="564" alt="瘋狂存取後後，確認 maxReplicas 設定是否生效（圖 4）" loading="lazy" decoding="async"></p>
<h3 id="-關閉存取後確認-minreplicas-設定是否生效">關閉存取後，確認 minReplicas 設定是否生效<a class="h-anchor" href="#-%e9%97%9c%e9%96%89%e5%ad%98%e5%8f%96%e5%be%8c%e7%a2%ba%e8%aa%8d-minreplicas-%e8%a8%ad%e5%ae%9a%e6%98%af%e5%90%a6%e7%94%9f%e6%95%88" aria-label="這個段落的連結"></a></h3>
<p>將上面的存取 Deployment 外部 ip 的 while 迴圈關掉後，pod 會自動減少到設定的 minReplicas。</p>
<p><img src="https://deanlin.net/blog/202201/kubernetes-autoscaling-local/images/kubernetes-autoscaling-local-05.webp" width="811" height="174" alt="關閉存取後，確認 minReplicas 設定是否生效（圖 5）" loading="lazy" decoding="async"></p>
<h3 id="-測試完成後將建立的-k8s-服務刪除關閉-minikube">測試完成後將建立的 K8s 服務刪除、關閉 minikube<a class="h-anchor" href="#-%e6%b8%ac%e8%a9%a6%e5%ae%8c%e6%88%90%e5%be%8c%e5%b0%87%e5%bb%ba%e7%ab%8b%e7%9a%84-k8s-%e6%9c%8d%e5%8b%99%e5%88%aa%e9%99%a4%e9%97%9c%e9%96%89-minikube" aria-label="這個段落的連結"></a></h3>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>kubectl delete -f nodetest-k8s-auto.yaml
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># 如果沒關閉的話，你會發現電腦燙燙的，CPU 居高不下</span>
</span></span><span style="display:flex;"><span>minikube delete
</span></span></code></pre></div><hr>
<blockquote class="pullquote"><p>以上就是模擬 K8s Auto Scaling 的測試方法啦！希望讀者可以透過文章了解這個技術的應用～</p>
</blockquote>
]]></content:encoded>
    </item>
    <item>
      <title>用 Node.js &#43; Redis 解決高併發秒殺問題</title>
      <link>https://deanlin.net/blog/202108/nodejs-redis-flash-sale-concurrency/</link>
      <pubDate>Wed, 18 Aug 2021 20:06:42 +0800</pubDate>
      <guid>https://deanlin.net/blog/202108/nodejs-redis-flash-sale-concurrency/</guid>
      <dc:creator>林鼎淵</dc:creator>
      <category>技術教學</category>
      <description>商城在高併發環境下除了要考量到使用者體驗外，還要處理「超賣」的問題；Redis 是一種記憶體資料庫，它有高讀寫效率的特性、能承受高 qps，而這篇文章則是運用它獨特的「鎖」解決超賣問題。</description>
      <media:content url="https://deanlin.net/blog/202108/nodejs-redis-flash-sale-concurrency/images/nodejs-redis-flash-sale-concurrency-og_hu_6a3c65a275786513.jpg" medium="image" type="image/jpeg" width="1200" height="675" />
      <content:encoded><![CDATA[<p>商城在高併發環境下除了要考量到使用者體驗外，還要處理「超賣」的問題；Redis 是一種記憶體資料庫，它有高讀寫效率的特性、能承受高 qps，而這篇文章則是運用它獨特的「鎖」解決超賣問題。</p>
<p><img src="https://deanlin.net/blog/202108/nodejs-redis-flash-sale-concurrency/images/nodejs-redis-flash-sale-concurrency-01.webp" width="1087" height="608" alt="用 Node.js &#43; Redis 解決高併發秒殺問題（圖 1）" fetchpriority="high" decoding="async"></p>
<pre tabindex="0"><code>大綱
一、文章目標
二、使用技術
  ➤ Redis
  ➤ Lua
三、程式架構
  ➤ 主程式：redisSecKill.js
  ➤ Lua腳本：secKill.lua
四、執行結果
五、補充
</code></pre><h2 id="一文章目標">一、文章目標<a class="h-anchor" href="#%e4%b8%80%e6%96%87%e7%ab%a0%e7%9b%ae%e6%a8%99" aria-label="這個段落的連結"></a></h2>
<ol>
<li>產生秒殺商品基礎資訊(商品名稱、庫存)</li>
<li>模擬秒殺情境，確認是否會超賣</li>
<li>確認有保存購買人資訊</li>
</ol>
<hr>
<h2 id="二使用技術">二、使用技術<a class="h-anchor" href="#%e4%ba%8c%e4%bd%bf%e7%94%a8%e6%8a%80%e8%a1%93" aria-label="這個段落的連結"></a></h2>
<h3 id="-redis">Redis<a class="h-anchor" href="#-redis" aria-label="這個段落的連結"></a></h3>
<ol>
<li>如果完全沒有相關基礎，建議先參考<a href="https://deanlin.net/blog/202108/install-redis-on-macos/">這篇文章</a>來安裝 Redis 以及他的 GUI 工具</li>
<li>本篇文章使用 Redis 的 Hash、List type 來儲存資訊</li>
</ol>
<h3 id="-lua">Lua<a class="h-anchor" href="#-lua" aria-label="這個段落的連結"></a></h3>
<ol>
<li>為了避免超賣，這裡採用 Lua 腳本</li>
<li>在 Redis Server 執行 EVAL 指令時，在結果回傳前只會執行當下 Lua 腳本的邏輯，其他 Client 端的命令須等待直到 EVAL 執行完為止。</li>
</ol>
<blockquote>
<p>Lua 腳本的邏輯應盡量簡單以保證執行效率，否則會影響 Client 端的體驗</p>
</blockquote>
<hr>
<h2 id="三程式架構">三、程式架構<a class="h-anchor" href="#%e4%b8%89%e7%a8%8b%e5%bc%8f%e6%9e%b6%e6%a7%8b" aria-label="這個段落的連結"></a></h2>
<h3 id="-主程式redisseckilljs">主程式：redisSecKill.js<a class="h-anchor" href="#-%e4%b8%bb%e7%a8%8b%e5%bc%8fredisseckilljs" aria-label="這個段落的連結"></a></h3>
<ol>
<li>先啟用 Redis Client 端（如未安裝相關套件，請在專案資料夾下輸入 <code>yarn add ioredis</code>）</li>
<li>用 <strong>prepare</strong> 函式，以 Hash type 建立參加秒殺的產品庫存</li>
<li>用 <strong>secKill</strong> 函式模擬使用者購買行為，緩存並執行 Lua 腳本</li>
</ol>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#ff7b72">const</span> fs <span style="color:#ff7b72;font-weight:bold">=</span> require(<span style="color:#a5d6ff">&#39;fs&#39;</span>);
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> Redis <span style="color:#ff7b72;font-weight:bold">=</span> require(<span style="color:#a5d6ff">&#39;ioredis&#39;</span>);
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> redis <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">new</span> Redis({
</span></span><span style="display:flex;"><span>    host<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">&#39;127.0.0.1&#39;</span>,
</span></span><span style="display:flex;"><span>    port<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">6379</span>,
</span></span><span style="display:flex;"><span>    password<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">&#39;&#39;</span>
</span></span><span style="display:flex;"><span>});
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>redis.on(<span style="color:#a5d6ff">&#34;error&#34;</span>, <span style="color:#ff7b72">function</span> (error) {
</span></span><span style="display:flex;"><span>    console.error(error);
</span></span><span style="display:flex;"><span>});
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">async</span> <span style="color:#ff7b72">function</span> prepare (item_name) {
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic">// 參加秒殺活動的商品庫存
</span></span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">await</span> redis.hmset(item_name, <span style="color:#a5d6ff">&#34;Total&#34;</span>, <span style="color:#a5d6ff">100</span>, <span style="color:#a5d6ff">&#34;Booked&#34;</span>, <span style="color:#a5d6ff">0</span>);
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> secKillScript <span style="color:#ff7b72;font-weight:bold">=</span> fs.readFileSync(<span style="color:#a5d6ff">&#39;./secKill.lua&#39;</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">async</span> <span style="color:#ff7b72">function</span> secKill (item_name, user_name) {
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic">// 1. 緩存腳本取得 sha1 值
</span></span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">const</span> sha1 <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">await</span> redis.script(<span style="color:#a5d6ff">&#34;load&#34;</span>, secKillScript);
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic">// console.log(sha1);
</span></span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic">// 2. 透過 evalsha 執行腳本
</span></span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic">// redis Evalsha 命令基本語法如下
</span></span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic">// EVALSHA sha1 numkeys key [key ...] arg [arg ...] 
</span></span></span><span style="display:flex;"><span>    redis.evalsha(sha1, <span style="color:#a5d6ff">1</span>, item_name, <span style="color:#a5d6ff">1</span>, <span style="color:#a5d6ff">&#34;order_list&#34;</span>, user_name);
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">async</span> <span style="color:#ff7b72">function</span> main () {
</span></span><span style="display:flex;"><span>    console.time(<span style="color:#a5d6ff">&#39;secKill&#39;</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">const</span> item_name <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">&#34;item_name&#34;</span>
</span></span><span style="display:flex;"><span>    prepare(item_name)
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">for</span> (<span style="color:#ff7b72">var</span> i <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">1</span>; i <span style="color:#ff7b72;font-weight:bold">&lt;</span> <span style="color:#a5d6ff">10000</span>; i<span style="color:#ff7b72;font-weight:bold">++</span>) {
</span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">const</span> user_name <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">&#34;baobao&#34;</span> <span style="color:#ff7b72;font-weight:bold">+</span> i
</span></span><span style="display:flex;"><span>        secKill(item_name, user_name)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    console.timeEnd(<span style="color:#a5d6ff">&#39;secKill&#39;</span>)
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>main()
</span></span></code></pre></div><p class="code-source">原始碼：<a href="https://gist.github.com/dean9703111/64cfadcce24bc32d5fabbafd07da98a6" rel="noopener">redisSecKill.js</a>（GitHub Gist）</p>
<h3 id="-lua-腳本seckilllua">Lua 腳本：secKill.lua<a class="h-anchor" href="#-lua-%e8%85%b3%e6%9c%acseckilllua" aria-label="這個段落的連結"></a></h3>
<p>在 Lua 腳本執行「取得商品庫存」➜「如果庫存足夠就-1」➜「儲存購買者資訊(List type)」</p>
<pre tabindex="0"><code>local n = tonumber(ARGV[1])
local order_name = ARGV[2]
if not n  or n == 0 then
    return 0       
end                
local vals = redis.call(&#34;HMGET&#34;, KEYS[1], &#34;Total&#34;, &#34;Booked&#34;);
local total = tonumber(vals[1])
local blocked = tonumber(vals[2])
if not total or not blocked then
    return 0       
end                
if blocked + n &lt;= total then
    redis.call(&#34;HINCRBY&#34;, KEYS[1], &#34;Booked&#34;, n)
    redis.call(&#34;LPUSH&#34;, order_name,ARGV[3])                                   
    return n;   
end                
return 0
</code></pre><p class="code-source">原始碼：<a href="https://gist.github.com/dean9703111/d5981082cc8c93f1c51fb785a3895f99" rel="noopener">secKill.lua</a>（GitHub Gist）</p>
<hr>
<h2 id="四執行結果">四、執行結果<a class="h-anchor" href="#%e5%9b%9b%e5%9f%b7%e8%a1%8c%e7%b5%90%e6%9e%9c" aria-label="這個段落的連結"></a></h2>
<p><strong>SETP 1</strong>：在專案資料夾下輸入 <code>node redisSecKill.js</code> 模擬秒殺</p>
<p><img src="https://deanlin.net/blog/202108/nodejs-redis-flash-sale-concurrency/images/nodejs-redis-flash-sale-concurrency-02.webp" width="1019" height="73" alt="四、執行結果（圖 2）" loading="lazy" decoding="async"></p>
<p><strong>SETP 2</strong>：待執行完後，用 Redis GUI 程式確認商品沒有超賣</p>
<p><img src="https://deanlin.net/blog/202108/nodejs-redis-flash-sale-concurrency/images/nodejs-redis-flash-sale-concurrency-03.webp" width="1400" height="563" alt="四、執行結果（圖 3）" loading="lazy" decoding="async"></p>
<p><strong>SETP 3</strong>：確認訂單都有對應的買家</p>
<p><img src="https://deanlin.net/blog/202108/nodejs-redis-flash-sale-concurrency/images/nodejs-redis-flash-sale-concurrency-04.webp" width="1400" height="1004" alt="四、執行結果（圖 4）" loading="lazy" decoding="async"></p>
<h2 id="五補充">五、補充<a class="h-anchor" href="#%e4%ba%94%e8%a3%9c%e5%85%85" aria-label="這個段落的連結"></a></h2>
<p>上面程式只是 MVP，現實狀況還有很多要討論的：</p>
<ol>
<li>哪些商品被列為秒殺，如何紀錄</li>
<li>設定秒殺開始時間、結束時間</li>
<li>存在 Redis 的資料怎麼存入關連式資料庫中</li>
</ol>
<pre tabindex="0"><code>如果你對 Redis 感興趣，筆者這邊也整理了很多相關資源：
1. 一文讓你徹底瞭解 Redis(進階)，史上最全,不看後悔！！！【建議收藏】
2. 【总结】瞬时高并发（秒杀/活动）Redis 方案
3. redis 之 sorted sets 类型及操作
4. node.js 中对 redis 的安装和基本操作
5. Node.js 中实践 Redis Lua 脚本
6. 使用 Redis 搭建电商秒杀系统
</code></pre><blockquote class="pullquote"><p>筆者也是剛學習 Redis 的新人，如果描述有誤，或是有更好的解決方式，再麻煩大神們不吝賜教，感謝～</p>
</blockquote>
]]></content:encoded>
    </item>
  </channel>
</rss>
