{"id":15292551,"url":"https://github.com/alturkovic/distributed-lock","last_synced_at":"2025-04-05T03:11:32.663Z","repository":{"id":40245902,"uuid":"93310060","full_name":"alturkovic/distributed-lock","owner":"alturkovic","description":"Distributed locking with Spring","archived":false,"fork":false,"pushed_at":"2024-06-09T17:03:47.000Z","size":385,"stargazers_count":275,"open_issues_count":1,"forks_count":91,"subscribers_count":10,"default_branch":"master","last_synced_at":"2025-03-29T02:06:36.167Z","etag":null,"topics":["distributed-locking","java","spring"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/alturkovic.png","metadata":{"files":{"readme":"README.adoc","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-06-04T11:50:37.000Z","updated_at":"2025-01-24T06:22:28.000Z","dependencies_parsed_at":"2023-11-28T16:40:57.470Z","dependency_job_id":"4ffd0d93-3ece-403a-b9d2-95f75ebda945","html_url":"https://github.com/alturkovic/distributed-lock","commit_stats":{"total_commits":129,"total_committers":13,"mean_commits":9.923076923076923,"dds":"0.10077519379844957","last_synced_commit":"4a81bae58116694c578043e99d50b69a35384d00"},"previous_names":[],"tags_count":23,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alturkovic%2Fdistributed-lock","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alturkovic%2Fdistributed-lock/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alturkovic%2Fdistributed-lock/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alturkovic%2Fdistributed-lock/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/alturkovic","download_url":"https://codeload.github.com/alturkovic/distributed-lock/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247280272,"owners_count":20912967,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["distributed-locking","java","spring"],"created_at":"2024-09-30T16:18:45.176Z","updated_at":"2025-04-05T03:11:32.643Z","avatar_url":"https://github.com/alturkovic.png","language":"Java","readme":"image:https://img.shields.io/badge/Java-17%2B-ED8B00?style=for-the-badge\u0026labelColor=ED8B00\u0026logo=java\u0026color=808080[Java] image:https://img.shields.io/jitpack/v/github/alturkovic/distributed-lock?style=for-the-badge\u0026labelColor=007ec5\u0026color=808080\u0026logo=Git\u0026logoColor=white[JitPack] image:https://img.shields.io/badge/Spring%20Boot-3.1.5-ED8B00?style=for-the-badge\u0026labelColor=6db33f\u0026color=808080\u0026logo=Spring%20Boot\u0026logoColor=white[Spring Boot] image:https://img.shields.io/github/license/alturkovic/distributed-lock?style=for-the-badge\u0026color=808080\u0026logo=Open%20Source%20Initiative\u0026logoColor=white[License]\n\n\n= Distributed Lock\n\nDistributed lock ensures your method cannot be run in parallel from multiple JVMs (cluster of servers, microservices, ...).\nIt uses a common store to keep track of used locks and your method needs to acquire one or more locks to run.\n\nBy default, locks follow methods lifecycle.They are obtained at the start of the method and released at the end of the method.\nManual controlling is supported and explained later in this document.\n\nAll locks acquired by lock implementations in this project will expire after 10 seconds, timeout after 1 second if unable to acquire lock and sleep for 50 ms between retries.\nThese options are customizable per annotation.\n\n== Using locks\n\nTo lock your methods you need to first enable locking as described in the previous section.\n\nSpring `https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/beans/factory/config/BeanPostProcessor.html[BeanPostProcessor]` will handle all `@Locked` methods including\ntheir aliases. The `type` field describes which implementation of the lock to use.\nTo prevent repeating yourself if you plan on using the same implementation (as most people usually will), I've added alias support.\nThey wrap the `@Locked` annotation and define the type used.\n\nEach lock needs to define a https://docs.spring.io/spring/docs/current/spring-framework-reference/html/expressions.html[SpEL] expression used to acquire the lock.\nTo learn more about Spring aliases visit https://github.com/spring-projects/spring-framework/wiki/Spring-Annotation-Programming-Model[this] link.\n\nBy default, upon failure to acquire the lock `@Locked` will throw `DistributedLockException`. If you need to only log this failure without raising the exception add `throwing = false` to your\n`@Locked` annotation. Using this option on non-void methods will make the method return null - using primitives as return type with this option is not advised. This is useful if you need to lock\nan action across multiple application instances, for example cron.\n\n=== Lock refresh\n\nLocks can be refreshed automatically on a regular interval. This allows methods that occasionally need to run longer than their expiration.\nRefreshing the lock periodically prolongs the expiration of its key(s). This means that the lock cannot be acquired by another resource as long as the resource using the lock does not\nend successfully. In case the resource holding the lock fails unexpectedly without releasing the lock, the lock will expire according to the last expiration that was written (that the last refresh\nhas set).\n\n=== Manually controlled locks\n\nSometimes you might want lock to be acquired when calling a specific method and get released only when it expires (throttling).\n\nTo acquire a lock that doesn't get released automatically set `manuallyReleased` to `true` on `@Locked` annotation.\n\nFor more grained control (e.g., locking in the middle of the method and releasing later in the code), inject the lock in your service and acquire the lock manually.\n\n==== Example\n\n[source,java]\n----\n@Component\npublic class Example {\n\n    @Autowired\n    @Qualifier(\"simpleRedisLock\")\n    private Lock lock;\n\n    // other fields...\n\n    private void manuallyLocked() {\n        // code before locking...\n\n        final String token = lock.acquire(keys, storeId, expiration);\n\n        // check if you acquired a token\n        if (StringUtils.isEmpty(token)) {\n            throw new IllegalStateException(\"Lock not acquired!\");\n        }\n\n        // code after locking...\n\n        lock.release(keys, storeId, token);\n\n        // code after releasing the lock...\n    }\n}\n----\n\n=== Unsuccessful locks\n\nIf method cannot be locked, `DistributedLockException` will be thrown.\n\nMethod might not acquire the lock if:\n\n. keys from SpEL expression cannot be resolved\n. another method acquired the lock\n. Lock implementation threw an exception\n\n== Examples\n\nLocking a method with the name _aliased_ in the document called _lock_ in MongoDB:\n\n[source,java]\n----\n@MongoLocked(expression = \"'aliased'\", storeId = \"distributed_lock\")\npublic void runLockedWithMongo() {\n    // locked code\n}\n----\n\nLocking with multiple keys determined in runtime, use SpEL, for an example:\n\n[source,java]\n----\n@RedisMultiLocked(expression = \"T(com.example.MyUtils).getNamesWithId(#p0)\")\npublic void runLockedWithRedis(final int id) {\n    // locked code\n}\n----\n\nThis means that the `runLockedWithRedis` method will execute only if all keys evaluated by expression were acquired.\n\nLocking with a custom lock implementation based on value of integer field `count`:\n\n[source,java]\n----\n@Locked(type = MyCustomLock.class, expression = \"getCount\", prefix = \"using:\")\npublic void runLockedWithMyCustomLock() {\n    // locked code\n}\n----\n\n== Enabling locking\n\nThe project contains several configurations and annotations to help you enable locking and customize it.\n\nTo enable locking you must first include `@EnableDistributedLock`.\nThis will import the configuration that will autoconfigure the\n`https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/beans/factory/config/BeanPostProcessor.html[BeanPostProcessor]` required for locking.\n\nProject provides the following out-of-the-box lock implementations:\n\n* JDBC\n* Mongo\n* Redis\n\n=== JDBC locks\n\nJDBC locks are provided in the `distributed-lock-jdbc` project.\n\n.Mongo lock implementations\n|===\n|Implementation |Alias |Multiple key support\n\n|`SimpleJdbcLock`\n|`@JdbcLocked`\n|No\n|===\n\nInclude `@EnableJdbcDistributedLock` to enable JDBC locks.\nThis will also include `@EnableDistributedLock` for you.\n\n[source,java]\n----\n@Configuration\n@EnableJdbcDistributedLock\npublic class LockConfiguration {\n}\n----\n\n[NOTE]\n====\nMake sure you create the table and configure the table ID incrementer.\n====\n\nExample how to create table:\n[source, sql]\n----\nCREATE TABLE IF NOT EXISTS `distributed_lock` (\n    id       INT NOT NULL AUTO_INCREMENT,\n    lock_key VARCHAR(255),\n    token    VARCHAR(255),\n    expireAt TIMESTAMP,\n    PRIMARY KEY(`id`),\n    UNIQUE KEY `uk_lock_lock_key` (`lock_key`)\n);\n----\n\n=== MongoDB locks\n\nMongoDB locks are provided in the `distributed-lock-mongo` project.\n\n.Mongo lock implementations\n|===\n|Implementation |Alias |Multiple key support\n\n|`SimpleMongoLock`\n|`@MongoLocked`\n|No\n|===\n\nInclude `@EnableMongoDistributedLock` to enable MongoDB locks.\nThis will also include `@EnableDistributedLock` for you.\n\n[source,java]\n----\n@Configuration\n@EnableMongoDistributedLock\npublic class LockConfiguration {\n}\n----\n\n[NOTE]\n====\nMake sure you create TTL index in your `@Locked#storeId()` collection on `expireAt` field to enable lock expiration.\n====\n\n=== Redis locks\n\nRedis locks are provided in the `distributed-lock-redis` project.\n\n.Redis lock implementations\n|===\n|Implementation |Alias |Multiple key support\n\n|`SimpleRedisLock`\n|`@RedisLocked`\n|No\n\n|`MultiRedisLock`\n|`@RedisMultiLocked`\n|Yes\n|===\n\nInclude `@EnableRedisDistributedLock` to enable Redis locks.\nThis will also include `@EnableDistributedLock` for you.\n\n[source,java]\n----\n@Configuration\n@EnableRedisDistributedLock\npublic class LockConfiguration {\n}\n----\n\n== Importing into your project\n\n=== Maven\n\nAdd the JitPack repository into your `pom.xml`.\n\n[source,xml]\n----\n\u003crepositories\u003e\n  \u003crepository\u003e\n    \u003cid\u003ejitpack.io\u003c/id\u003e\n    \u003curl\u003ehttps://jitpack.io\u003c/url\u003e\n  \u003c/repository\u003e\n\u003c/repositories\u003e\n----\n\nJitPack builds multi-modules by appending the repo name in the `groupId`.\nTo add the Redis dependency for an example, add the following under your `\u003cdependencies\u003e`:\n\n[source,xml]\n----\n\u003cdependencies\u003e\n  \u003cdependency\u003e\n    \u003cgroupId\u003ecom.github.alturkovic.distributed-lock\u003c/groupId\u003e\n    \u003cartifactId\u003edistributed-lock-redis\u003c/artifactId\u003e\n    \u003cversion\u003e[insert latest version here]\u003c/version\u003e\n  \u003c/dependency\u003e\n\u003c/dependencies\u003e\n----\n\n=== Compatibility\n\nFully compatible with Spring 3. For earlier version support check the compatibility table below.\nOlder versions will not be maintained or bugfixed.\n\n|===\n|Version |Spring Boot version\n\n|2.0.0+\n|3.1.5\n\n|1.4.1+\n|2.4.3\n\n|1.3.0+\n|2.2.7.RELEASE\n\n|1.2.0+\n|2.1.0.RELEASE\n\n|1.1.8+\n|2.0.4.RELEASE\n\n|1.1.7+\n|2.0.3.RELEASE\n\n|1.1.6-\n|1.5.6.RELEASE\n\n|===\n\n== SpEL key generator\n\nThis is the default key generator the advice uses. If you wish to use your own, simply write your own and define it as a `@Bean`.\n\nThe default key generator has access to the currently executing context, meaning you can access your fields and methods from SpEL.\nIt uses the `https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/core/DefaultParameterNameDiscoverer.html[DefaultParameterNameDiscoverer]` to discover parameter names, so you can access your parameters in several different ways:\n\n1. using `p#` syntax, where `#` is the position of the parameter, for an example: `p0` for the first parameter\n2. using `a#` syntax, where `#` is the position of the parameter, for an example: `a2` for the third parameter\n3. using the parameter name, for an example, `#message` -- *REQUIRES `-parameters` compiler flag*\n\nA special variable named `executionPath` is used to define the method called.\nThis is the default `expression` used to describe the annotated method.\n\nAll validated expressions that result in an `Iterable` or an array will be converted to `List\u003cString\u003e` and all other values will be wrapped with `Collections.singletonList`.\nElements of `Iterable` or array will also be converted to Strings using the\n`https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/core/convert/ConversionService.html[ConversionService]`.\nCustom converters can be registered.\nMore about Spring conversion can be found https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#core-convert[here].\n\nFor more examples, take a look at `com.github.alturkovic.lock.key.SpelKeyGeneratorTest`.\n\n== Customization\n\nIf you want to use custom lock implementations, simply implement `Lock` interface and register it in a configuration.\nYou can also create an alias for your lock so you don't have to specify `@Locked` type field.\n\n== Changelog\n\nStarted tracking the changes since 1.2.0 so no changelogs available for earlier versions.\n\n==== 2.1.0\n\n- FEATURE: Added option `throwing` to `@Locked` annotations\n\n==== 2.0.0\n\n- CHANGE: Upgraded Spring Boot version to 3.1.5\n\n==== 1.5.5\n\n- BUGFIX: Add initial refresh delay to avoid calling `refresh` immediately\n- BUGFIX: Changed default `storeId` to `distributed_lock`\n\n==== 1.5.4\n\n- BUGFIX: Do not execute locked method if token is not acquired after all retries\n\n==== 1.5.3\n\n- BUGFIX: `RetriableLock` should return `null` if lock is not acquired after the last retry\n\n==== 1.5.2\n\n- BUGFIX: Use dedicated task scheduler for DistributedLock, avoid trying to override custom default scheduler\n\n==== 1.5.1\n\n- BUGFIX: Removed semicolon from SQL statements for PSQL compatibility\n\n==== 1.5.0\n\n- CHANGE: Changed the default SQL table name from `lock` to `distributed_lock` to avoid issues with reserved database keywords\n\n==== 1.4.4\n\n- BUGFIX: No retries will be attempted if `retry` or `timeout` are zero or negative\n- BUGFIX: Handle Redis interruptions in Redis locks better\n- BUGFIX: SQL script updated in README\n\n==== 1.4.3\n\n- BUGFIX: Use Spring scheduler if enabled instead of overriding\n- BUGFIX: Escape `lock` keyword in SQL locks since MySQL uses it as a keyword\n\n==== 1.4.2\n\n- CHANGE: `KeyGenerator` will not declare `ConversionService` but reuse the shared instance if missing\n\n==== 1.4.1\n\n- CHANGE: Upgraded Spring Boot version to 2.4.3\n- CHANGE: Migrated test to JUnit 5\n- CHANGE: Migrated Redis tests to use Docker container\n- BUGFIX: Injecting the user-defined `LockTypeResolver` properly\n- BUGFIX: Fixed `BeanPostProcessor` initialization warning messages\n- BUGFIX: Minor javadoc fix\n\n==== 1.4.0\n\n- CHANGE: Switched back to Java 1.8 from 11 since most projects don't yet use 11\n\n==== 1.3.0\n\n- CHANGE: Updated Java from 1.8 to 11\n- CHANGE: Refactored lots of coupled code\n- CHANGE: Extracted lots of reusable components such as retriable locks for easier manual control of locks\n- BUGFIX: `LockBeanPostProcessor` will now fire after existing advisors to support transactional advisors\n\n==== 1.2.2\n\n- CHANGE: Removed explicit `ParameterNameDiscoverer` from `SpelKeyGenerator` which now uses the one provided by the `CachedExpressionEvaluator`\n- CHANGE: Used `AopUtils` once and passed the evaluated method to `SpelKeyGenerator` so it doesn't have to evaluate the same thing as `LockMethodInterceptor`\n\n==== 1.2.1\n\n- FEATURE: Lock refreshing has been added. Check the 'Lock refresh' chapter for more details\n- BUGFIX: `@RedisMultiLocked` was using `#executionPath` as prefix instead of an expression\n- BUGFIX: `@RedisMultiLocked` was using `expiration` and `timeout` in milliseconds instead of seconds\n\n==== 1.2.0\n- FEATURE: Added a JavaDoc description to `com.github.alturkovic.lock.Lock.release()` method\n- CHANGE: Rearranged the parameters of the `com.github.alturkovic.lock.Lock.release()` method to be more consistent\n- CHANGE: Rearranged the parameters of the `com.github.alturkovic.lock.jdbc.service.JdbcLockSingleKeyService` methods to be more consistent\n- CHANGE: `EvaluationConvertException` and `LockNotAvailableException` now extend the `DistributedLockException`\n","funding_links":[],"categories":["Java","分布式开发","\u003ca name=\"Java\"\u003e\u003c/a\u003eJava"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falturkovic%2Fdistributed-lock","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Falturkovic%2Fdistributed-lock","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falturkovic%2Fdistributed-lock/lists"}