Last active
January 13, 2024 16:56
-
-
Save mhewedy/d09cef74a614613be9709e38a2b5c5ae to your computer and use it in GitHub Desktop.
Revisions
-
mhewedy revised this gist
Jan 13, 2024 . 1 changed file with 6 additions and 4 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -105,9 +105,11 @@ private static void initRedisTemplate() { private static void writeKey(String key, Duration duration, String errorMessage) { log.trace("write key: {}", key); var ok = redisTemplate.opsForValue().setIfAbsent(KEY_PREFIX + key, "1", duration); if (ok == null) { throw new RuntimeException("Lock cannot used inside Redis pipeline/transaction"); } if (!ok) { throw new BusinessException(errorMessage, "key", key); } } @@ -123,4 +125,4 @@ private static Supplier<Void> runnableToSupplier(Runnable action) { return null; }; } } -
mhewedy renamed this gist
Jan 13, 2024 . 1 changed file with 14 additions and 3 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -6,8 +6,19 @@ import java.time.Duration; import java.util.function.Supplier; /** * A simple implementation for Distributed Lock based on Redis. * <p> * The implementation relies on Redis SETNX (SET if Not eXists). * if one process acquires the lock, the other processes won't wait, and simply an exception will be thrown. * <p> * Usually, you would use the Database as a lock mechanism (using unique keys for example). * However, sometimes, you cannot use the database (for any reason), or you need a quick solution, hence you can use this class. * * @see <a href="https://redis.com/glossary/redis-lock/">Redis Lock</a> */ @Slf4j public class Lock { private static final String KEY_PREFIX = "idempotent-"; private static final Options DEFAULT_OPTIONS = new Options(); @@ -47,7 +58,7 @@ public static <T> T run(String key, Options options, Supplier<T> action) { } /** * Use {@link Lock#options()} to create default {@link Options} object. */ @NoArgsConstructor(access = AccessLevel.PRIVATE) public static class Options { @@ -112,4 +123,4 @@ private static Supplier<Void> runnableToSupplier(Runnable action) { return null; }; } } -
mhewedy revised this gist
Jan 12, 2024 . 1 changed file with 11 additions and 27 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -34,16 +34,11 @@ public static <T> T run(String key, Supplier<T> action) { public static <T> T run(String key, Options options, Supplier<T> action) { initRedisTemplate(); writeKey(key, options.duration, options.errorKey); try { return action.get(); } finally { if (!options.keepLock) { removeKey(key); @@ -57,7 +52,6 @@ public static <T> T run(String key, Options options, Supplier<T> action) { @NoArgsConstructor(access = AccessLevel.PRIVATE) public static class Options { private Duration duration = Duration.ofMinutes(5); private boolean keepLock = false; private String errorKey = "operation_in_progress"; @@ -69,14 +63,6 @@ public Options duration(Duration d) { return this; } /** * keep the lock for the whole duration regardless of whether the execution is done or not, default is false. */ @@ -105,16 +91,14 @@ private static void initRedisTemplate() { } } private static void writeKey(String key, Duration duration, String errorMessage) { log.trace("write key: {}", key); Boolean wasAbsent = redisTemplate.opsForValue().setIfAbsent(KEY_PREFIX + key, "1", duration); if (Boolean.FALSE.equals(wasAbsent)) { throw new BusinessException(errorMessage, "key", key); } } private static void removeKey(String key) { -
mhewedy revised this gist
Jan 11, 2024 . 1 changed file with 89 additions and 18 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,3 +1,5 @@ import lombok.AccessLevel; import lombok.NoArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.redis.core.RedisTemplate; @@ -8,53 +10,122 @@ public class Idempotent { private static final String KEY_PREFIX = "idempotent-"; private static final Options DEFAULT_OPTIONS = new Options(); private static RedisTemplate<String, Object> redisTemplate; /** * Run with default options */ public static void run(String key, Runnable action) { run(key, DEFAULT_OPTIONS, action); } public static void run(String key, Options options, Runnable action) { run(key, options, runnableToSupplier(action)); } /** * Run with default options */ public static <T> T run(String key, Supplier<T> action) { return run(key, DEFAULT_OPTIONS, action); } public static <T> T run(String key, Options options, Supplier<T> action) { initRedisTemplate(); checkKeyExists(key, options.errorKey); if (options.lockBefore) { writeKey(key, options.duration); } try { T result = action.get(); if (!options.lockBefore) { writeKey(key, options.duration); } return result; } finally { if (!options.keepLock) { removeKey(key); } } } /** * Use {@link Idempotent#options()} to create default {@link Options} object. */ @NoArgsConstructor(access = AccessLevel.PRIVATE) public static class Options { private Duration duration = Duration.ofMinutes(5); private boolean lockBefore = true; private boolean keepLock = false; private String errorKey = "operation_in_progress"; /** * the maximum duration to lock, default is 5 minutes */ public Options duration(Duration d) { this.duration = d; return this; } /** * whether to lock before the execution or not, default is true. */ public Options lockBefore(boolean b) { this.lockBefore = b; return this; } /** * keep the lock for the whole duration regardless of whether the execution is done or not, default is false. */ public Options keepLock(boolean b) { this.keepLock = b; return this; } /** * error key to thrown in case of operation is in progress, default is "operation_in_progress". */ public Options errorKey(String key) { this.errorKey = key; return this; } } public static Options options() { return new Options(); } @SuppressWarnings({"unchecked"}) private static void initRedisTemplate() { if (redisTemplate == null) { redisTemplate = AppContextUtil.getBean(RedisTemplate.class, String.class, Object.class); } } private static void checkKeyExists(String key, String errorMessage) { boolean exists = redisTemplate.opsForValue().get(KEY_PREFIX + key) != null; if (exists) { throw new RuntimeException(errorMessage + ", key: " + key); } } private static void writeKey(String key, Duration duration) { log.trace("write key: {}", key); redisTemplate.opsForValue().set(KEY_PREFIX + key, "1", duration); } private static void removeKey(String key) { log.trace("remove key: {}", key); redisTemplate.opsForValue().getAndDelete(KEY_PREFIX + key); } private static Supplier<Void> runnableToSupplier(Runnable action) { return () -> { action.run(); return null; }; } } -
mhewedy created this gist
Mar 11, 2023 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,60 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.data.redis.core.RedisTemplate; import java.time.Duration; import java.util.function.Supplier; @Slf4j public class Idempotent { private static final String KEY_PREFIX = "idempotent-"; private static final Duration LOCK_TIMEOUT = Duration.ofMinutes(5); private static RedisTemplate<String, Object> redisTemplate; public static void run(String key, Runnable action) { initRedisTemplate(); checkKeyExists(key); writeKey(key); try { action.run(); } finally { removeKey(key); } } public static <T> T run(String key, Supplier<T> action) { initRedisTemplate(); checkKeyExists(key); writeKey(key); try { return action.get(); } finally { removeKey(key); } } @SuppressWarnings({"unchecked"}) private static void initRedisTemplate() { if (redisTemplate == null) { redisTemplate = AppContextUtil.getBean(RedisTemplate.class, String.class, Object.class); } } private static void checkKeyExists(String key) { boolean exists = redisTemplate.opsForValue().get(KEY_PREFIX + key) != null; if (exists) { throw new RuntimeException("operation_in_progress, key: "+ key); } } private static void writeKey(String key) { log.trace("write key: {}", key); redisTemplate.opsForValue().set(KEY_PREFIX + key, "1", LOCK_TIMEOUT); } private static void removeKey(String key) { log.trace("remove key: {}", key); redisTemplate.opsForValue().getAndDelete(KEY_PREFIX + key); } }