基于 .NET Garnet 1.0.91 实现高性能分布式锁(使用 Lua 脚本)

环境说明

软件版本说明:

  • .net 10
  • garnet-server 1.0.91
  • Microsoft.Garnet 1.0.91

两种安装方式:

容器化方式请查看官方文档,此处以 .net tool 方式为例。

  • 在宿主机安装 garnet-server
bash 复制代码
dotnet tool install --global garnet-server --version 1.0.91

输出信息:

text 复制代码
可使用以下命令调用工具: garnet-server
已成功安装工具"garnet-server"(版本"1.0.91")。

查看更最多帮助信息:

bash 复制代码
garnet-server --help
GarnetServer
Copyright (c) Microsoft Corporation

  --port                                           (Default: 6379) Port to run server on

  --bind                                           Whitespace or comma separated
                                                   string of IP addresses to
                                                   bind server to (default: any)

  --cluster-announce-port                          (Default: 0) Port that this node
                                                   advertises to other nodes to
                                                   connect to for gossiping.

  --cluster-announce-ip                            IP address that this node
                                                   advertises to other nodes to
                                                   connect to for gossiping.

  -m, --memory                                     (Default: "16g") Total log memory used in
                                                   bytes (rounds down to power
                                                   of 2)

  -p, --page                                       (Default: "32m") Size of each page in bytes
                                                   (rounds down to power of 2)

  -s, --segment                                    (Default: "1g") Size of each log segment in
                                                   bytes on disk (rounds down to
                                                   power of 2)

  -i, --index                                      (Default: "128m") Start size of hash index in
                                                   bytes (rounds down to power
                                                   of 2)

  --index-max-size                                 Max size of hash index in
                                                   bytes (rounds down to power
                                                   of 2)

  --mutable-percent                                (Default: 90) Percentage of log memory that
                                                   is kept mutable

  --readcache                                      (Default: False) Enables read cache for faster
                                                   access to on-disk records.

  --readcache-memory                               (Default: "1g") Total read cache log memory
                                                   used in bytes (rounds down to
                                                   power of 2)

  --readcache-page                                 (Default: "32m") Size of each read cache page
                                                   in bytes (rounds down to
                                                   power of 2)

  --obj-heap-memory                                Object store heap memory size
                                                   in bytes (Sum of size taken
                                                   up by all object instances in
                                                   the heap)

  --obj-log-memory                                 (Default: "32m") Object store log memory used
                                                   in bytes (Size of only the
                                                   log with references to heap
                                                   objects, excludes size of
                                                   heap memory consumed by the
                                                   objects themselves referred
                                                   to from the log)

  --obj-page                                       (Default: "4k") Size of each object store
                                                   page in bytes (rounds down to
                                                   power of 2)

  --obj-segment                                    (Default: "32m") Size of each object store log
                                                   segment in bytes on disk
                                                   (rounds down to power of 2)

  --obj-index                                      (Default: "16m") Start size of object store
                                                   hash index in bytes (rounds
                                                   down to power of 2)

  --obj-index-max-size                             Max size of object store hash
                                                   index in bytes (rounds down
                                                   to power of 2)

  --obj-mutable-percent                            (Default: 90) Percentage of object store
                                                   log memory that is kept
                                                   mutable

  --obj-readcache                                  (Default: False) Enables object store read
                                                   cache for faster access to
                                                   on-disk records.

  --obj-readcache-log-memory                       (Default: "32m") Total object store read cache
                                                   log memory used in bytes
                                                   (rounds down to power of 2)

  --obj-readcache-page                             (Default: "1m") Size of each object store
                                                   read cache page in bytes
                                                   (rounds down to power of 2)

  --obj-readcache-heap-memory                      Object store read cache heap
                                                   memory size in bytes (Sum of
                                                   size taken up by all object
                                                   instances in the heap)

  --storage-tier                                   (Default: False) Enable tiering of records
                                                   (hybrid log) to storage, to
                                                   support a larger-than-memory
                                                   store. Use --logdir to
                                                   specify storage directory.

  --copy-reads-to-tail                             (Default: False) When records are read from
                                                   the main store's in-memory
                                                   immutable region or storage
                                                   device, copy them to the tail
                                                   of the log.

  --obj-copy-reads-to-tail                         (Default: False) When records are read from
                                                   the object store's in-memory
                                                   immutable region or storage
                                                   device, copy them to the tail
                                                   of the log.

  -l, --logdir                                     Storage directory for tiered
                                                   records (hybrid log), if
                                                   storage tiering
                                                   (--storage-tier) is enabled.
                                                   Uses current directory if
                                                   unspecified.

  -c, --checkpointdir                              Storage directory for
                                                   checkpoints. Uses logdir if
                                                   unspecified.

  -r, --recover                                    (Default: False) Recover from latest
                                                   checkpoint and log, if
                                                   present.

  --no-pubsub                                      (Default: False) Disable pub/sub feature on
                                                   server.

  --incsnap                                        (Default: False) Enable incremental snapshots.

  --pubsub-pagesize                                (Default: "4k") Page size of log used for
                                                   pub/sub (rounds down to power
                                                   of 2)

  --no-obj                                         (Default: False) Disable support for data
                                                   structure objects.

  --cluster                                        (Default: False) Enable cluster.

  --clean-cluster-config                           (Default: False) Start with clean cluster
                                                   config.

  --pmt                                            (Default: 1) Number of parallel migrate
                                                   tasks to spawn when SLOTS or
                                                   SLOTSRANGE option is used.

  --fast-migrate                                   (Default: False) When migrating slots 1. write
                                                   directly to network buffer to
                                                   avoid unecessary copies, 2.
                                                   do not wait for ack from
                                                   target before sending next
                                                   batch of keys.

  --auth                                           (Default: NoAuth) Authentication mode of
                                                   Garnet. This impacts how AUTH
                                                   command is processed and how
                                                   clients are authenticated
                                                   against Garnet. Value
                                                   options: NoAuth, Password,
                                                   Aad, ACL

  --password                                       Authentication string for
                                                   password authentication.

  --cluster-username                               Username to authenticate
                                                   intra-cluster communication
                                                   with.

  --cluster-password                               Password to authenticate
                                                   intra-cluster communication
                                                   with.

  --acl-file                                       External ACL user file.

  --aad-authority                                  (Default: "https://login.microsoftonline.com") The authority of AAD
                                                   authentication.

  --aad-audiences                                  The audiences of AAD token
                                                   for AAD authentication.
                                                   Should be a comma separated
                                                   string.

  --aad-issuers                                    The issuers of AAD token for
                                                   AAD authentication. Should be
                                                   a comma separated string.

  --aad-authorized-app-ids                         The authorized client app Ids
                                                   for AAD authentication.
                                                   Should be a comma separated
                                                   string.

  --aad-validate-acl-username                      (Default: False) Only valid for AclWithAAD
                                                   mode. Validates username -
                                                   expected to be OID of client
                                                   app or a valid group's object
                                                   id of which the client is
                                                   part of.

  --aof                                            (Default: False) Enable write ahead logging
                                                   (append-only file).

  --aof-memory                                     (Default: "64m") Total AOF memory buffer used
                                                   in bytes (rounds down to
                                                   power of 2) - spills to disk
                                                   after this limit

  --aof-page-size                                  (Default: "4m") Size of each AOF page in
                                                   bytes(rounds down to power of
                                                   2)

  --aof-commit-freq                                (Default: 0) Write ahead logging
                                                   (append-only file) commit
                                                   issue frequency in
                                                   milliseconds. 0 = issue an
                                                   immediate commit per
                                                   operation, -1 = manually
                                                   issue commits using COMMITAOF
                                                   command

  --aof-commit-wait                                (Default: False) Wait for AOF to flush the
                                                   commit before returning
                                                   results to client. Warning:
                                                   will greatly increase
                                                   operation latency.

  --aof-size-limit                                 Maximum size of AOF (rounds
                                                   down to power of 2) after
                                                   which unsafe truncation will
                                                   be applied. Left empty AOF
                                                   will grow without bound
                                                   unless a checkpoint is taken

  --aof-size-limit-enforce-frequency               (Default: 5) Frequency (in secs) of
                                                   execution of the
                                                   AutoCheckpointBasedOnAofSizeL
                                                   imit background task.

  --aof-refresh-freq                               (Default: 10) AOF replication (safe tail
                                                   address) refresh frequency in
                                                   milliseconds. 0 = auto
                                                   refresh after every enqueue.

  --subscriber-refresh-freq                        (Default: 0) Subscriber (safe tail
                                                   address) refresh frequency in
                                                   milliseconds (for pub-sub). 0
                                                   = auto refresh after every
                                                   enqueue.

  --compaction-freq                                (Default: 0) Background hybrid log
                                                   compaction frequency in
                                                   seconds. 0 = disabled
                                                   (compaction performed before
                                                   checkpointing instead)

  --expired-object-collection-freq                 (Default: 0) Frequency in seconds for the
                                                   background task to perform
                                                   object collection which
                                                   removes expired members
                                                   within object from memory. 0
                                                   = disabled. Use the HCOLLECT
                                                   and ZCOLLECT API to collect
                                                   on-demand.

  --compaction-type                                (Default: None) Hybrid log compaction type.
                                                   Value options: None - no
                                                   compaction, Shift - shift
                                                   begin address without
                                                   compaction (data loss), Scan
                                                   - scan old pages and move
                                                   live records to tail (no data
                                                   loss), Lookup - lookup each
                                                   record in compaction range,
                                                   for record liveness checking
                                                   using hash chain (no data
                                                   loss)

  --compaction-force-delete                        (Default: False) Forcefully delete the
                                                   inactive segments immediately
                                                   after the compaction strategy
                                                   (type) is applied. If false,
                                                   take a checkpoint to actually
                                                   delete the older data files
                                                   from disk.

  --compaction-max-segments                        (Default: 32) Number of log segments
                                                   created on disk before
                                                   compaction triggers.

  --obj-compaction-max-segments                    (Default: 32) Number of object store log
                                                   segments created on disk
                                                   before compaction triggers.

  --lua                                            (Default: False) Enable Lua scripts on server.

  --lua-transaction-mode                           (Default: False) Run Lua scripts as a
                                                   transaction (lock keys - run
                                                   script - unlock keys).

  --gossip-sp                                      (Default: 100) Percent of cluster nodes to
                                                   gossip with at each gossip
                                                   iteration.

  --gossip-delay                                   (Default: 5) Cluster mode gossip protocol
                                                   per node sleep (in seconds)
                                                   delay to send updated config.

  --cluster-timeout                                (Default: 60) Cluster node timeout is the
                                                   amount of seconds a node must
                                                   be unreachable.

  --cluster-config-flush-frequency                 (Default: 0) How frequently to flush
                                                   cluster config unto disk to
                                                   persist updates. =-1: never
                                                   (memory only), =0:
                                                   immediately (every update
                                                   performs flush), >0:
                                                   frequency in ms

  --cluster-tls-client-target-host                 (Default: "GarnetTest") Name for the client target
                                                   host when using TLS
                                                   connections in cluster mode.

  --server-certificate-required                    (Default: True) Whether server TLS
                                                   certificate is required by
                                                   clients established on the
                                                   server side, e.g., for
                                                   cluster gossip and
                                                   replication.

  --tls                                            (Default: False) Enable TLS.

  --cert-file-name                                 TLS certificate file name
                                                   (example: testcert.pfx).

  --cert-password                                  TLS certificate password
                                                   (example: placeholder).

  --cert-subject-name                              TLS certificate subject name.

  --cert-refresh-freq                              (Default: 0) TLS certificate refresh
                                                   frequency in seconds (0 to
                                                   disable).

  --client-certificate-required                    (Default: True) Whether client TLS
                                                   certificate is required by
                                                   the server.

  --certificate-revocation-check-mode              (Default: NoCheck) Certificate revocation check
                                                   mode for certificate
                                                   validation (NoCheck, Online,
                                                   Offline).

  --issuer-certificate-path                        Full path of file with issuer
                                                   certificate for validation.
                                                   If empty or null, validation
                                                   against issuer will not be
                                                   performed.

  --latency-monitor                                (Default: False) Track latency of various
                                                   events.

  --slowlog-log-slower-than                        (Default: 0) Threshold (microseconds) for
                                                   logging command in the slow
                                                   log. 0 to disable.

  --slowlog-max-len                                (Default: 128) Maximum number of slow log
                                                   entries to keep.

  --metrics-sampling-freq                          (Default: 0) Metrics sampling frequency in
                                                   seconds. Value of 0 disables
                                                   metrics monitor task.

  -q                                               Enabling quiet mode does not
                                                   print server version and text
                                                   art.

  --logger-level                                   (Default: Warning) Logging level. Value options:
                                                   Trace, Debug, Information,
                                                   Warning, Error, Critical,
                                                   None

  --logger-freq                                    (Default: 5) Frequency (in seconds) of
                                                   logging (used for tracking
                                                   progress of long running
                                                   operations e.g. migration)

  --disable-console-logger                         (Default: False) Disable console logger.

  --file-logger                                    Enable file logger and write
                                                   to the specified path.

  --minthreads                                     (Default: 0) Minimum worker threads in
                                                   thread pool, 0 uses the
                                                   system default.

  --maxthreads                                     (Default: 0) Maximum worker threads in
                                                   thread pool, 0 uses the
                                                   system default.

  --miniothreads                                   (Default: 0) Minimum IO completion threads
                                                   in thread pool, 0 uses the
                                                   system default.

  --maxiothreads                                   (Default: 0) Maximum IO completion threads
                                                   in thread pool, 0 uses the
                                                   system default.

  --network-connection-limit                       (Default: -1) Maximum number of
                                                   simultaneously active network
                                                   connections.

  --use-azure-storage                              (Default: False) Use Azure Page Blobs for
                                                   storage instead of local
                                                   storage.

  --storage-service-uri                            The URI to use when
                                                   establishing connection to
                                                   Azure Blobs Storage.

  --storage-managed-identity                       The managed identity to use
                                                   when establishing connection
                                                   to Azure Blobs Storage.

  --storage-string                                 The connection string to use
                                                   when establishing connection
                                                   to Azure Blobs Storage.

  --checkpoint-throttle-delay                      (Default: 0) Whether and by how much
                                                   should we throttle the disk
                                                   IO for checkpoints: -1 -
                                                   disable throttling; >= 0 -
                                                   run checkpoint flush in
                                                   separate task, sleep for
                                                   specified time after each
                                                   WriteAsync

  --fast-commit                                    (Default: True) Use FastCommit when writing
                                                   AOF.

  --fast-commit-throttle                           (Default: 1000) Throttle FastCommit to write
                                                   metadata once every K
                                                   commits.

  --network-send-throttle                          (Default: 8) Throttle the maximum
                                                   outstanding network sends per
                                                   session.

  --sg-get                                         (Default: False) Whether we use scatter gather
                                                   IO for MGET or a batch of
                                                   contiguous GET operations -
                                                   useful to saturate disk
                                                   random read IO.

  --replica-sync-delay                             (Default: 5) Whether and by how much
                                                   (milliseconds) should we
                                                   throttle the replica sync: 0
                                                   - disable throttling

  --replica-offset-max-lag                         (Default: -1) Throttle ClusterAppendLog
                                                   when replica.AOFTailAddress -
                                                   ReplicationOffset >
                                                   ReplicationOffsetMaxLag. 0:
                                                   Synchronous replay,  >=1:
                                                   background replay with
                                                   specified lag, -1: infinite
                                                   lag

  --main-memory-replication                        (Default: False) Use main-memory replication
                                                   model.

  --fast-aof-truncate                              (Default: False) Use fast-aof-truncate
                                                   replication model.

  --on-demand-checkpoint                           (Default: True) Used with main-memory
                                                   replication model. Take on
                                                   demand checkpoint to avoid
                                                   missing data when attaching

  --repl-diskless-sync                             (Default: False) Whether diskless replication
                                                   is enabled or not.

  --repl-diskless-sync-delay                       (Default: 5) Delay in diskless replication
                                                   sync in seconds. =0:
                                                   Immediately start diskless
                                                   replication sync.

  --repl-attach-timeout                            (Default: 60) Timeout in seconds for
                                                   replication attach operation.

  --repl-sync-timeout                              (Default: 5) Timeout in seconds for
                                                   replication sync operations.

  --repl-diskless-sync-full-sync-aof-threshold     AOF replay size threshold for
                                                   diskless replication, beyond
                                                   which we will perform a full
                                                   sync even if a partial sync
                                                   is possible. Defaults to AOF
                                                   memory size if not specified.

  --aof-null-device                                (Default: False) With main-memory replication,
                                                   use null device for AOF.
                                                   Ensures no disk IO, but can
                                                   cause data loss during
                                                   replication.

  --config-import-path                             Import (load) configuration
                                                   options from the provided
                                                   path

  --config-import-format                           (Default: GarnetConf) Format of configuration
                                                   options in path specified by
                                                   config-import-path

  --config-export-format                           (Default: GarnetConf) Format to export
                                                   configuration options to path
                                                   specified by
                                                   config-export-path

  --use-azure-storage-for-config-import            (Default: false) Use Azure
                                                   storage to import config file

  --config-export-path                             Export (save) current
                                                   configuration options to the
                                                   provided path

  --use-azure-storage-for-config-export            (Default: false) Use Azure
                                                   storage to export config file

  --use-native-device-linux                        (Default: False) DEPRECATED: use DeviceType
                                                   (--device-type) of Native
                                                   instead.

  --device-type                                    (Default: Default) Device type (Default, Native,
                                                   RandomAccess, FileStream,
                                                   AzureStorage, Null)

  --reviv-bin-record-sizes                         #,#,...,#: For the main
                                                   store, the sizes of records
                                                   in each revivification bin,
                                                   in order of increasing size.
                                                   Supersedes the default
                                                   --reviv; cannot be used with
                                                   --reviv-in-chain-only

  --reviv-bin-record-counts                        #,#,...,#: For the main
                                                   store, the number of records
                                                   in each bin:    Default (not
                                                   specified): If
                                                   reviv-bin-record-sizes is
                                                   specified, each bin is 256
                                                   records    # (one value): If
                                                   reviv-bin-record-sizes is
                                                   specified, then all bins have
                                                   this number of records, else
                                                   error    #,#,...,# (multiple
                                                   values): If
                                                   reviv-bin-record-sizes is
                                                   specified, then it must be
                                                   the same size as that array,
                                                   else error
                                                   Supersedes the default
                                                   --reviv; cannot be used with
                                                   --reviv-in-chain-only

  --reviv-fraction                                 (Default: 1) #: Fraction of mutable
                                                   in-memory log space, from the
                                                   highest log address down to
                                                   the read-only region, that is
                                                   eligible for revivification.
                                                   Applies to both main and
                                                   object store.

  --reviv                                          (Default: False) A shortcut to specify
                                                   revivification with default
                                                   power-of-2-sized bins.
                                                   This default can be
                                                   overridden by
                                                   --reviv-in-chain-only (Default: False) or by
                                                   the combination of
                                                   reviv-bin-record-sizes and
                                                   reviv-bin-record-counts.

  --reviv-search-next-higher-bins                  (Default: 0) Search this number of
                                                   next-higher bins if the
                                                   search cannot be satisfied in
                                                   the best-fitting bin.
                                                   Requires --reviv or the
                                                   combination of
                                                   rconeviv-bin-record-sizes and
                                                   reviv-bin-record-counts

  --reviv-bin-best-fit-scan-limit                  (Default: 0) Number of records to scan for
                                                   best fit after finding first
                                                   fit.    Requires --reviv or
                                                   the combination of
                                                   reviv-bin-record-sizes and
                                                   reviv-bin-record-counts    0:
                                                   Use first fit    #: Limit
                                                   scan to this many records
                                                   after first fit, up to the
                                                   record count of the bin

  --reviv-in-chain-only                            (Default: False) Revivify tombstoned records
                                                   in tag chains only (do not
                                                   use free list).    Cannot be
                                                   used with
                                                   reviv-bin-record-sizes or
                                                   reviv-bin-record-counts.
                                                   Propagates to object store by
                                                   default.

  --reviv-obj-bin-record-count                     (Default: 256) Number of records in the
                                                   single free record bin for
                                                   the object store. The Object
                                                   store has only a single bin,
                                                   unlike the main store.
                                                   Ignored unless the main store
                                                   is using the free record
                                                   list.

  --object-scan-count-limit                        (Default: 1000) Limit of items to return in
                                                   one iteration of *SCAN
                                                   command

  --enable-debug-command                           (Default: No) Enable DEBUG command for
                                                   'no', 'local' or 'all'
                                                   connections

  --enable-module-command                          (Default: No) Enable MODULE command for
                                                   'no', 'local' or 'all'
                                                   connections. Command can only
                                                   load from paths listed in
                                                   ExtensionBinPaths

  --protected-mode                                 (Default: True) Enable protected mode.

  --extension-bin-paths                            List of directories on server
                                                   from which custom command
                                                   binaries can be loaded by
                                                   admin users. MODULE command
                                                   also requires
                                                   enable-module-command to be
                                                   set

  --loadmodulecs                                   List of modules to be loaded

  --extension-allow-unsigned                       (Default: False) Allow loading custom commands
                                                   from digitally unsigned
                                                   assemblies (not recommended)

  --index-resize-freq                              (Default: 60) Index resize check frequency
                                                   in seconds

  --index-resize-threshold                         (Default: 50) Overflow bucket count over
                                                   total index size in
                                                   percentage to trigger index
                                                   resize

  --fail-on-recovery-error                         (Default: False) Server bootup should fail if
                                                   errors happen during bootup
                                                   of AOF and checkpointing

  --skip-rdb-restore-checksum-validation           (Default: False) Skip RDB restore checksum
                                                   validation

  --lua-memory-management-mode                     (Default: Native) Memory management mode for
                                                   Lua scripts, must be set to
                                                   Tracked or Managed to impose
                                                   script limits

  --lua-script-memory-limit                        Memory limit for a Lua
                                                   instances while running a
                                                   script,
                                                   lua-memory-management-mode
                                                   must be set to something
                                                   other than Native to use this
                                                   flag

  --lua-script-timeout                             (Default: 0) Timeout for a Lua instance
                                                   while running a script,
                                                   specified in positive
                                                   milliseconds (0 = disabled)

  --lua-logging-mode                               (Default: Enable) Behavior of redis.log(...)
                                                   when called from Lua scripts.
                                                   Defaults to Enable.

  --lua-allowed-functions                          (Default: System.Collections.Generic.HashSet`1[System.String]) If set, restricts the
                                                   functions available in Lua
                                                   scripts to given list.

  --unixsocket                                     Unix socket address path to
                                                   bind server to

  --unixsocketperm                                 (Default: 0) Unix socket permissions in
                                                   octal (Unix platforms only)

  --max-databases                                  (Default: 16) Max number of logical
                                                   databases allowed in a single
                                                   Garnet server instance

  --expired-key-deletion-scan-freq                 (Default: -1) Frequency of background scan
                                                   for expired key deletion, in
                                                   seconds

  --cluster-replication-reestablishment-timeout    (Default: 0)

  --cluster-replica-resume-with-data               (Default: False) If a Cluster Replica resumes
                                                   with data, allow it to be
                                                   served prior to a Primary
                                                   being available

  --help                                           Display this help screen.

  --version                                        Display version information.

  value pos. 0

启动 Garent server 执行命令:

bash 复制代码
garnet-server

显示如下信息:

特别说明:启动 garnet server 时,开启支持 lua 脚本

依据 garnet-server 命令行参数帮助文档,要启用 Lua 脚本支持,您可以按照以下方式启动 Garnet Server

bash 复制代码
garnet-server --lua

或者如果您想要更详细的配置,例如设置 Lua 脚本的超时时间或内存限制,可以使用如下选项:

bash 复制代码
garnet-server --lua --lua-script-timeout 5000 --lua-script-memory-limit 1048576

以下是与 Lua 相关的几个重要参数说明:

  • --lua: 启用服务器上的 Lua 脚本功能。
  • --lua-transaction-mode: 将 Lua 脚本作为事务运行(锁定键 - 运行脚本 - 解锁键)。
  • --lua-script-timeout: 设置 Lua 实例运行脚本的超时时间(以毫秒为单位),0 表示禁用
  • --lua-script-memory-limit: 设置 Lua 脚本实例的内存限制。
  • --lua-memory-management-mode: 设置 Lua 脚本的内存管理模式,必须设为 Tracked 或 Managed 才能施加脚本限制

这些选项允许您根据需要定制 Lua 脚本的行为和资源限制。

如何遇到如下类似错误信息:

bash 复制代码
PS C:\Users\Jeffrey> garnet-server --lua --lua-script-timeout 5000 --lua-script-memory-limit 1048576
01::02::09 info: ArgParser[0] Configuration import from embedded resource succeeded. Path: defaults.conf.
01::02::09 info: ArgParser[0] Configuration file path not specified. Using default values with command-line switches.
01::02::09 fail: ArgParser[0] LuaScriptMemoryLimit cannot be set with LuaMemoryManagementMode has value 'Native'
01::02::09 fail: ArgParser[0] Configuration validation failed.
Unable to initialize server due to exception: Encountered an error when initializing Garnet server. Please see log messages above for more details.

错误原因:

  • 上面命令时设置了 --lua-script-memory-limit 和默认的 --lua-memory-management-mode(默认值为 Native
  • 当内存管理模式为 Native 时,不允许设置脚本内存限制

解决方案:

  • 需要显式指定 --lua-memory-management-modeTrackedManaged,才能使用 --lua-script-memory-limit 参数:
bash 复制代码
garnet-server --lua --lua-script-timeout 5000 --lua-memory-management-mode Tracked --lua-script-memory-limit 1048576
  • 或者使用 Managed 模式:
bash 复制代码
garnet-server --lua --lua-script-timeout 5000 --lua-memory-management-mode Managed --lua-script-memory-limit 1048576
  • 使用 docker 容器化运行:
bash 复制代码
docker run -d --name garnet \
-p 6380:6379 \
ghcr.io/microsoft/garnet:1.0.91 \ 
garnet-server \
--lua \
--lua-script-timeout 5000 \
--lua-memory-management-mode Managed \
--lua-script-memory-limit 1048576
  • 容器化运行 garnet 显示信息:

参数说明:

  • --lua: 启用 Lua 脚本支持
  • --lua-script-timeout 5000: 设置 Lua 脚本执行超时时间为 5000 毫秒
  • --lua-memory-management-mode Tracked|Managed: 设置 Lua 内存管理模式为 Tracked 或 Managed,这样才能应用内存限制
  • --lua-script-memory-limit 1048576: 设置每个 Lua 脚本实例的内存限制为 1MB (1048576 字节)

这样配置后,Garnet Server 应该能够正常启动并支持带有内存和时间限制的 Lua 脚本功能。

除了上面使用命令设置 Lua 配置项,还可以通过 C# 代码定义方式:

csharp 复制代码
using Garnet.server;

// 在配置 Garnet 服务器时,确保启用了 Lua 脚本支持
// 正确的配置方式 - 设置 LuaOptions
var serverOptions = new GarnetServerOptions
{
    LuaOptions = new LuaOptions(
        memoryMode: LuaMemoryManagementMode.Managed,
        memoryLimit: "10MB",
        timeout: TimeSpan.FromSeconds(30),
        logMode: LuaLoggingMode.Enable,
        allowedFunctions: [
            "redis.call",
            "redis.pcall",
            "redis.log"
            // 添加其他需要的函数
        ]
    )
};

更多信息,请查看 Garnet 官方文档:


代码实现

  • 分布式🔒接口定义 IDistributedLock
csharp 复制代码
namespace Ai4c.OperationsCenter.Abstraction.DistributedLock;

public interface IDistributedLock
{
    /// <summary>
    /// 尝试获取锁
    /// </summary>
    /// <param name="timeout">等待超时时间</param>
    /// <returns>是否成功获取锁</returns>
    Task<bool> TryAcquireAsync(TimeSpan timeout = default, CancellationToken cancellationToken = default);

    /// <summary>
    /// 释放锁
    /// </summary>
    /// <returns></returns>
    ValueTask ReleaseAsync();
}
  • 扩展方法用于 TimeSpan 计算
csharp 复制代码
public static class TimeSpanExtensions
{
    public static TimeSpan Multiply(this TimeSpan timeSpan, double factor)
    {
        return TimeSpan.FromTicks((long)(timeSpan.Ticks * factor));
    }
}
  • 基于 Garnet 的分布式锁实现 GarnetDistributedLock
csharp 复制代码
using Ai4c.OperationsCenter.Abstraction.DistributedLock;
using Garnet.client;

namespace Ai4c.OperationsCenter.Core.DistributedLock;

// 使用 IAsyncDisposable 对象符合现代化 .net 异步编程
public class GarnetDistributedLock(GarnetClient client, string lockKey, TimeSpan expiry)
    : IDistributedLock, IAsyncDisposable
{
    private readonly string _lockValue = Guid.CreateVersion7().ToString();
    private readonly CancellationTokenSource _renewalCts = new();
    private Task? _renewalTask;
    private bool _isLocked = false;

    /// <summary>
    /// 尝试获取锁
    /// </summary>
    /// <param name="timeout">等待超时时间</param>
    /// <param name="cancellationToken">取消令牌</param>
    /// <returns>是否成功获取锁</returns>
    public async Task<bool> TryAcquireAsync(TimeSpan timeout = default, CancellationToken cancellationToken = default)
    {
        var startTime = DateTimeOffset.UtcNow;
        var delayBetweenRetries = TimeSpan.FromMilliseconds(50);

        // 合并外部取消令牌和内部取消令牌
        using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _renewalCts.Token);

        while (!linkedCts.Token.IsCancellationRequested)
        {
            // 使用 SET 命令的 NX 和 EX 选项尝试获取锁
            // NX: 只有键不存在时才设置
            // EX: 设置过期时间
            var result = await client.ExecuteForStringResultAsync(
                "SET",
                [lockKey, _lockValue, "NX", "EX", ((int)expiry.TotalSeconds).ToString()]
            );

            if (result != null)
            {
                _isLocked = true;
                // 启动自动续期任务
                await StartRenewalTaskAsync(linkedCts.Token);
                return true;
            }

            // 检查是否超时
            if (timeout != default && DateTimeOffset.UtcNow - startTime > timeout)
            {
                return false;
            }

            // 等待下次重试,同时响应取消请求
            try
            {
                await Task.Delay(delayBetweenRetries, linkedCts.Token);
            }
            catch (OperationCanceledException)
            {
                // 如果被取消,直接返回false
                return false;
            }
        }

        return false;
    }

    /// <summary>
    /// 释放锁
    /// </summary>
    public async ValueTask ReleaseAsync()
    {
        if (!_isLocked) return;

        // 使用 Lua 脚本安全地释放锁,确保只有持有锁的客户端才能释放
        const string script = @"
            if redis.call('get', KEYS[1]) == ARGV[1] then
                return redis.call('del', KEYS[1])
            else
                return 0
            end";

        try
        {
            await client.ExecuteForStringResultAsync(
                "EVAL",
                [script, "1", lockKey, _lockValue]
            );
        }
        finally
        {
            _isLocked = false;
            await _renewalCts.CancelAsync();
        }
    }

    /// <summary>
    /// 启动锁自动续期任务(异步方法)
    /// </summary>
    private async Task StartRenewalTaskAsync(CancellationToken cancellationToken)
    {
        _renewalTask = Task.Run(async () =>
        {
            try
            {
                while (!cancellationToken.IsCancellationRequested)
                {
                    // 在锁过期前续期(提前1/3时间续期)
                    await Task.Delay(expiry.Multiply(0.66), cancellationToken);

                    if (_isLocked)
                    {
                        // 续期锁
                        await client.ExecuteForStringResultAsync(
                            "EXPIRE",
                            [lockKey, ((int)expiry.TotalSeconds).ToString()]
                        );
                    }
                }
            }
            catch (OperationCanceledException)
            {
                // 任务被取消,正常退出
                await Console.Out.WriteLineAsync("任务被取消,正常退出");
            }
        }, cancellationToken);
    }

    /// <summary>
    /// 异步释放资源
    /// </summary>
    public async ValueTask DisposeAsync()
    {
        if (_isLocked)
        {
            await ReleaseAsync();
        }

        await _renewalCts.CancelAsync();
        if (_renewalTask != null)
        {
            try
            {
                await _renewalTask;
            }
            catch (OperationCanceledException)
            {
                // 忽略取消异常
                await Console.Out.WriteLineAsync("任务被取消,正常释放资源");
            }
        }
        _renewalCts?.Dispose();
    }
}

使用 DateTimeOffset 的优势:

  • 明确包含时区信息(UTC
  • 避免因时区不同导致的时间计算错误
  • 更适合分布式系统中的时间比较
  • 提供更准确的时间戳信息

这样修改后,代码在分布式环境中会更加可靠,特别是在跨时区部署的应用场景中。

如何使用 ?

以下是使用 GarnetDistributedLock 的完整测试演示:

csharp 复制代码
using System;
using System.Threading.Tasks;
using Garnet.client;
using Ai4c.OperationsCenter.Core.DistributedLock;

class Program
{
    static async Task Main(string[] args)
    {
        // 初始化 Garnet 客户端
        var client = new GarnetClient("127.0.0.1", 6379);
        await client.ConnectAsync();
        
        Console.WriteLine("已连接到 Garnet 服务器");

        try
        {
            // 运行各种测试
            await TestBasicLockFunctionality(client);
            await TestConcurrentAccess(client);
            await TestLockTimeout(client);
            await TestAutomaticRenewal(client);
        }
        finally
        {
            client.Dispose();
        }
    }

    /// <summary>
    /// 测试基本锁功能
    /// </summary>
    static async Task TestBasicLockFunctionality(GarnetClient client)
    {
        Console.WriteLine("\n=== 测试基本锁功能 ===");
        
        await using var distributedLock = new GarnetDistributedLock(client, "basic_test_lock", TimeSpan.FromSeconds(10));
        
        var acquired = await distributedLock.TryAcquireAsync(TimeSpan.FromSeconds(5));
        if (acquired)
        {
            Console.WriteLine("✓ 成功获取锁");
            
            // 模拟业务处理
            await Task.Delay(2000);
            Console.WriteLine("✓ 业务处理完成");
            
            // 锁会在 using 语句结束时自动释放
        }
        else
        {
            Console.WriteLine("✗ 获取锁失败或超时");
        }
    }

    /// <summary>
    /// 测试并发访问控制
    /// </summary>
    static async Task TestConcurrentAccess(GarnetClient client)
    {
        Console.WriteLine("\n=== 测试并发访问控制 ===");
        
        int sharedCounter = 0;
        var tasks = new Task[5];
        var startTime = DateTime.Now;
        
        // 创建多个并发任务竞争同一个锁
        for (int i = 0; i < 5; i++)
        {
            int taskId = i;
            tasks[i] = Task.Run(async () =>
            {
                await using var distLock = new GarnetDistributedLock(client, "concurrent_test_lock", TimeSpan.FromSeconds(5));
                
                var acquired = await distLock.TryAcquireAsync(TimeSpan.FromSeconds(3));
                if (acquired)
                {
                    Console.WriteLine($"✓ 任务 {taskId} 获取到锁 at {DateTime.Now:ss.fff}");
                    
                    // 模拟临界区操作 - 增加共享计数器
                    int current = sharedCounter;
                    await Task.Delay(500); // 模拟处理时间
                    sharedCounter = current + 1;
                    
                    Console.WriteLine($"✓ 任务 {taskId} 完成操作,当前计数: {sharedCounter} at {DateTime.Now:ss.fff}");
                }
                else
                {
                    Console.WriteLine($"✗ 任务 {taskId} 未能获取到锁 at {DateTime.Now:ss.fff}");
                }
            });
        }
        
        await Task.WhenAll(tasks);
        var duration = DateTime.Now - startTime;
        
        Console.WriteLine($"最终计数结果: {sharedCounter} (期望值: 5)");
        Console.WriteLine($"总耗时: {duration.TotalSeconds:F2} 秒");
        
        // 验证所有任务都获得了锁(计数应该等于任务数)
        if (sharedCounter == 5)
        {
            Console.WriteLine("✓ 并发控制测试通过:所有任务都正确获得了锁");
        }
        else
        {
            Console.WriteLine("✗ 并发控制测试失败:部分任务未能获得锁");
        }
    }

    /// <summary>
    /// 测试锁超时功能
    /// </summary>
    static async Task TestLockTimeout(GarnetClient client)
    {
        Console.WriteLine("\n=== 测试锁超时功能 ===");
        
        // 先获取一个锁并保持一段时间
        await using var firstLock = new GarnetDistributedLock(client, "timeout_test_lock", TimeSpan.FromSeconds(10));
        var firstAcquired = await firstLock.TryAcquireAsync();
        
        if (firstAcquired)
        {
            Console.WriteLine("✓ 第一个客户端成功获取锁");
            
            // 尝试让另一个客户端获取同一个锁,但设置较短的超时时间
            var secondLockTask = Task.Run(async () =>
            {
                await using var secondLock = new GarnetDistributedLock(client, "timeout_test_lock", TimeSpan.FromSeconds(5));
                var secondAcquired = await secondLock.TryAcquireAsync(TimeSpan.FromSeconds(2));
                
                if (secondAcquired)
                {
                    Console.WriteLine("✓ 第二个客户端获取锁");
                    return true;
                }
                else
                {
                    Console.WriteLine("✓ 第二个客户端在超时时间内未能获取锁(预期行为)");
                    return false;
                }
            });
            
            // 等待第二个客户端尝试获取锁
            var secondResult = await secondLockTask;
            
            // 释放第一个锁
            Console.WriteLine("第一个客户端释放锁");
        }
    }

    /// <summary>
    /// 测试锁自动续期功能
    /// </summary>
    static async Task TestAutomaticRenewal(GarnetClient client)
    {
        Console.WriteLine("\n=== 测试锁自动续期功能 ===");
        
        // 设置很短的过期时间,但执行较长的任务来测试自动续期
        await using var distributedLock = new GarnetDistributedLock(client, "renewal_test_lock", TimeSpan.FromSeconds(3));
        
        var acquired = await distributedLock.TryAcquireAsync();
        if (acquired)
        {
            Console.WriteLine("✓ 获取锁成功,开始长时间任务...");
            
            // 执行比锁过期时间更长的任务
            // 由于自动续期功能,锁不会过期
            await Task.Delay(TimeSpan.FromSeconds(8));
            
            Console.WriteLine("✓ 长时间任务完成,锁仍然有效");
        }
        else
        {
            Console.WriteLine("✗ 获取锁失败");
        }
    }
}

另外,我们还可以创建一个更详细的测试来验证锁的安全性和可靠性:

csharp 复制代码
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Garnet.client;
using Ai4c.OperationsCenter.Core.DistributedLock;

/// <summary>
/// 分布式锁压力测试类
/// </summary>
public class DistributedLockStressTest
{
    private readonly GarnetClient _client;
    
    public DistributedLockStressTest(GarnetClient client)
    {
        _client = client;
    }
    
    /// <summary>
    /// 执行压力测试
    /// </summary>
    public async Task RunStressTest()
    {
        Console.WriteLine("\n=== 开始分布式锁压力测试 ===");
        
        const int threadCount = 10;
        const int operationsPerThread = 100;
        const string lockKey = "stress_test_lock";
        
        // 任务完成会自动释放锁🔒
        await using var distLock = new GarnetDistributedLock(_client, lockKey, TimeSpan.FromSeconds(5));

        var tasks = new List<Task>();
        var successCount = 0;
        var failureCount = 0;
        var sharedResource = 0;
        
        var startTime = DateTimeOffset.UtcNow;
        
        // 创建多个并发任务
        for (int i = 0; i < threadCount; i++)
        {
            int threadId = i;
            var task = Task.Run(async () =>
            {
                for (int j = 0; j < operationsPerThread; j++)
                {
                    var acquired = await distLock.TryAcquireAsync(TimeSpan.FromMilliseconds(100));
                    if (acquired)
                    {
                        // 临界区操作
                        var currentValue = sharedResource;
                        await Task.Delay(1); // 模拟工作负载
                        sharedResource = currentValue + 1;
                        
                        Interlocked.Increment(ref successCount);
                    }
                    else
                    {
                        Interlocked.Increment(ref failureCount);
                    }
                }
            });
            
            tasks.Add(task);
        }
        
        // 等待所有任务完成
        await Task.WhenAll(tasks);
        // 释放锁(等效上面方法)
        //await distLock.ReleaseAsync();
        
        var endTime = DateTimeOffset.UtcNow;
        var duration = endTime - startTime;
        
        Console.WriteLine($"测试完成:");
        Console.WriteLine($"  总操作数: {threadCount * operationsPerThread}");
        Console.WriteLine($"  成功获取锁: {successCount}");
        Console.WriteLine($"  获取锁失败: {failureCount}");
        Console.WriteLine($"  最终共享资源值: {sharedResource}");
        Console.WriteLine($"  执行时间: {duration.TotalSeconds:F2} 秒");
        Console.WriteLine($"  平均每秒操作数: {(threadCount * operationsPerThread) / duration.TotalSeconds:F2}");
        
        // 验证结果
        if (sharedResource == successCount)
        {
            Console.WriteLine("✓ 压力测试通过:没有数据竞争");
        }
        else
        {
            Console.WriteLine("✗ 压力测试失败:存在数据竞争");
        }
    }
}

// 在 Main 方法中添加压力测试
class ExtendedProgram
{
    static async Task Main(string[] args)
    {
        var client = new GarnetClient("127.0.0.1", 6379);
        await client.ConnectAsync();
        
        Console.WriteLine("已连接到 Garnet 服务器");
        
        try
        {
            // 运行基础测试
            await TestBasicScenarios(client);
            
            // 运行压力测试
            var stressTest = new DistributedLockStressTest(client);
            await stressTest.RunStressTest();
        }
        finally
        {
            client.Dispose();
        }
    }
    
    static async Task TestBasicScenarios(GarnetClient client)
    {
        Console.WriteLine("=== 基础功能测试 ===");
        
        // 测试锁的基本功能
        await using var lock1 = new GarnetDistributedLock(client, "test_lock", TimeSpan.FromSeconds(5));
        var acquired = await lock1.TryAcquireAsync();
        Console.WriteLine($"获取锁: {acquired}");
        
        if (acquired)
        {
            // 尝试再次获取同一个锁应该失败
            await using var lock2 = new GarnetDistributedLock(client, "test_lock", TimeSpan.FromSeconds(5));
            var acquired2 = await lock2.TryAcquireAsync(TimeSpan.FromMilliseconds(100));
            Console.WriteLine($"重复获取锁: {acquired2} (应为 False)");
        }
    }
}

测试用例

  • 测试基本锁功能
bash 复制代码
Hello, Garnet!
已连接到 Garnet 服务器

=== 测试基本锁功能 ===
? 成功获取锁
? 业务处理完成
  • 基础功能测试
bash 复制代码
Hello, Garnet!
已连接到 Garnet 服务器
=== 基础功能测试 ===
获取锁: True
重复获取锁: False (应为 False)
  • 测试并发访问控制
bash 复制代码
Hello, Garnet!
已连接到 Garnet 服务器

=== 测试并发访问控制 ===
? 任务 4 获取到锁 at 19.101
? 任务 4 完成操作,当前计数: 1 at 19.641
? 任务 0 获取到锁 at 19.657
? 任务 0 完成操作,当前计数: 2 at 20.157
? 任务 3 获取到锁 at 20.222
? 任务 3 完成操作,当前计数: 3 at 20.734
? 任务 2 获取到锁 at 20.783
? 任务 2 完成操作,当前计数: 4 at 21.283
? 任务 1 获取到锁 at 21.344
? 任务 1 完成操作,当前计数: 5 at 21.855
  • 测试锁超时功能
bash 复制代码
Hello, Garnet!
已连接到 Garnet 服务器

=== 测试锁超时功能 ===
? 第一个客户端成功获取锁
? 第二个客户端在超时时间内未能获取锁(预期行为)
第一个客户端释放锁
  • 测试锁自动续期功能
bash 复制代码
Hello, Garnet!
已连接到 Garnet 服务器

=== 测试锁自动续期功能 ===
? 获取锁成功,开始长时间任务...
? 长时间任务完成,锁仍然有效
  • 分布式锁压力测试
bash 复制代码
Hello, Garnet!
已连接到 Garnet 服务器

=== 开始分布式锁压力测试 ===
测试完成:
  总操作数: 1000
  成功获取锁: 1
  获取锁失败: 999
  最终共享资源值: 1
  执行时间: 12.47 秒
  平均每秒操作数: 80.22
? 压力测试通过:没有数据竞争
  • 压力测试截图:

以上测试仅供参考,感兴趣的小伙伴可自行测试,依据不同环境的电脑配置规格,测试效果也会有所差异化。


这些测试验证了以下功能:

  1. 基本锁功能:能够正确获取和释放锁
  2. 并发控制:多个客户端竞争同一把锁时的互斥性
  3. 超时机制:在指定时间内无法获取锁时正确超时
  4. 自动续期:长时间持有锁时自动延长过期时间
  5. 压力测试:高并发场景下的稳定性和正确性

运行这些测试可以帮助验证 GarnetDistributedLock 实现的正确性和可靠性。

相关推荐
原神启动15 小时前
Kafka详解
分布式·kafka
yumgpkpm5 小时前
Iceberg在Hadoop集群使用步骤(适配AI大模型)
大数据·hadoop·分布式·华为·zookeeper·开源·cloudera
羑悻的小杀马特5 小时前
Lua vs C++:核心设计哲学差异——从“系统基石”到“灵活工具”的思维碰撞
c++·lua
元气满满-樱6 小时前
分布式LNMP部署
分布式
Wang's Blog8 小时前
RabbitMQ: 声明式配置简化管理
分布式·rabbitmq
叫致寒吧8 小时前
zookeeper与kafka
分布式·zookeeper·云原生
赵得C8 小时前
2025下半年软件设计师考前几页纸
java·开发语言·分布式·设计模式·性能优化·软考·软件设计师
西***63478 小时前
全场景覆盖・全流程智控:分布式解决方案让多功能厅 “不止于多”
分布式