分散式高效能狀態與原子運算元據庫slock簡介

snower發表於2021-12-21

專案地址:github.com/snower/slock

何為狀態與原子運算元據庫?區別於redis主要用於儲存資料,可在多節點多系統間高效統同步資料,slock則是設計為只用於儲存同步狀態,幾乎不能攜帶資料,高效能的非同步二進位制協議也保證了在狀態達成時高效的主動觸發等待系統。區別於redis被動檢查的過期時間,slock的等待超時時間及鎖定過期時間都是精確主動觸發的。多核支援、更簡單的系統結構也保證了其擁有遠超redis的效能及延時,這也更符合狀態同步需求中更高效能更低延時的需求。

秒殺為何難做?其問題就是我們需要在很短時間內完成大量的無效請求中夾雜僅很少的有效請求處理,進一步簡化就是需要完成超高併發下海量請求間的狀態同步的過程,slock高QPS可以快速解決過濾大量無效請求的問題,高效能的原子操作又可以很好的解決搶庫存的邏輯。

隨著nodejs的使用,非同步IO相關框架也越來越成熟,使用也越來越方便,多執行緒同步IO模式下,某些場景很多時候我們需要轉化為佇列處理然後再推送結果,但非同步IO就完全不需要這麼複雜,直接加分散式鎖等待可用就行,整個過程完全回到了單機多執行緒程式設計的邏輯,更簡單也更容易理解和維護了,比如下單請求需要操作很多,在高併發下可能需要傳送到佇列中處理完成再推送結果,但用非同步IO的加分散式鎖話,仔細看非同步IO加鎖其實又組成了一個更大的分散式佇列,大大簡化了實現步驟。

  • 超高效能,在Intel i5-4590上超過200萬QPS
  • 高效能二進位制非同步協議簡單穩定可靠,也可用redis同步文字協議
  • 多核多執行緒支援
  • 4級AOF持久化
    • 不持久化直接返回
    • 超過過期時間百分比時間後持久化後返回
    • 超過AOF時間持久化後返回
    • 立刻非同步持久化後返回
    • 需整個叢集活躍節點都成功並持久化後返回
  • 高可用叢集模式,自動遷移、自動代理
  • 精確到毫秒、秒、分鐘超時、過期時間,可單獨訂閱超時、過期時間
  • 多次鎖定支援,重入鎖定支援
  • 遺言命令

分散式鎖

整個協議只有兩頭指令,Lock和Unlock,分散式鎖也即是最常用場景,和redis實現分散式鎖區別除了效能更好延時也更低外,等待超時及鎖定超時過期時間時精確主動觸發的,所以有wait機制,redis實現的分散式鎖一般則需要client主動延時重試來檢查。

package main;

import io.github.snower.jaslock.Client;
import io.github.snower.jaslock.Event;
import io.github.snower.jaslock.Lock;
import io.github.snower.jaslock.ReplsetClient;
import io.github.snower.jaslock.exceptions.SlockException;

import java.io.IOException;
import java.nio.charset.StandardCharsets;

public class App {
    public static void main(String[] args) {
        ReplsetClient replsetClient = new ReplsetClient(new String[]{"172.27.214.150:5658"});
        try {
            replsetClient.open();
            Lock lock = replsetClient.newLock("test".getBytes(StandardCharsets.UTF_8), 5, 5);
            lock.acquire();
            lock.release();
        } catch (SlockException e) {
            e.printStackTrace();
        } finally {
            replsetClient.close();
        }
    }
}

nginx & openresty限流

openresty使用此服務完成限流可以很方便的完成跨節點,同時因為使用高效能非同步二進位制協議,每個work只需要一個和server的連線,高併發下不會產生內部連線耗盡的問題,server主節點變更的時候work可自動使用新可用主節點,實現高可用。

最大併發數限流

每個key可以設定最大鎖定次數,使用該邏輯可以非常方便的實現最大併發限流。

lua_package_path "lib/resty/slock.lua;";

init_worker_by_lua_block {
        local slock = require("slock")
        slock:connect("lock1", "127.0.0.1", 5658)
}

server {
    listen 80;

    location /flow/maxconcurrent {
          access_by_lua_block {
                  local slock = require("slock")
                  local client = slock:get("lock1")
                  local flow_key = "flow:maxconcurrent"
                  local args = ngx.req.get_uri_args()
                  for key, val in pairs(args) do
                          if key == "flow_key" then
                                  flow_key = val
                          end
                  end
                  local lock = client:newMaxConcurrentFlow(flow_key, 10, 5, 60)
                  local ok, err = lock:acquire()
                  if not ok then
                          ngx.say("acquire error:" .. err)
                          ngx.exit(ngx.HTTP_OK)
                  else
                          ngx.ctx.lock1 = lock
                  end
          }

          echo "hello world";

          log_by_lua_block {
                  local lock = ngx.ctx.lock1
                  if lock ~= nil then
                          local ok, err = lock:release()
                          if not ok then
                                  ngx.log(ngx.ERR, "slock release error:" .. err)
                          end
                  end
          }
    }
}

令牌桶限流

每個key可以設定最大鎖定次數,並設定為在令牌到期時過期,即可實現令牌桶限流,使用毫秒級過期時間的時候也可以從此方式來完成削峰平衡流量。

lua_package_path "lib/resty/?.lua;";

init_worker_by_lua_block {
        local slock = require("slock")
        slock:connect("lock1", "127.0.0.1", 5658)
}

server {
    listen 80;

    location /flow/tokenbucket {
                access_by_lua_block {
                        local slock = require("slock")
                        local client = slock:get("lock1")
                        local flow_key = "flow:tokenbucket"
                        local args = ngx.req.get_uri_args()
                        for key, val in pairs(args) do
                                if key == "flow_key" then
                                        flow_key = val
                                end
                        end
                        local lock = client:newTokenBucketFlow(flow_key, 10, 5, 60)
                        local ok, err = lock:acquire()
                        if not ok then
                                ngx.say("acquire error:" .. err)
                                ngx.exit(ngx.HTTP_OK)
                        end
                }

                echo "hello world";
        }
}

其它可用場景

  • 分散式Event,一個常用場景如掃碼登入,二維碼這邊需等待掃碼狀態。
  • 分散式Semaphore,這個即是更通用的限流,此外也可以用於非同步任務結果通知。
  • 分散式讀寫鎖。
  • 秒殺場景,秒殺場景是典型的請求數很高但有效請求數十分少的場景,原子操作的特性可以很好支援搶庫存的邏輯,超高的併發支援也可以很好解決天量無效請求的問題。
  • 非同步結果通知,網頁直接實現的功能又需要後臺定時任務執行,此時完全可以網路也呼叫非同步任務,然後通過分散式Event等待執行完成即可。

以上這些使用場景都可以在openresty完成對外介面,再有內部系統完成觸發即可,openresty的高效能高併發完全可以很容易的解決很多之前需要用佇列需要長連線推送的需求。

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章