All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.wl4g.infra.common.locks.JedisLockManager Maven / Gradle / Ivy

There is a newer version: 3.1.72
Show newest version
/*
 * Copyright 2017 ~ 2025 the original author or authors. James Wong 
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.wl4g.infra.common.locks;

import static com.wl4g.infra.common.lang.Assert2.hasTextOf;
import static com.wl4g.infra.common.lang.Assert2.isTrueOf;
import static com.wl4g.infra.common.lang.Assert2.notNullOf;
import static java.lang.String.format;
import static java.lang.Thread.currentThread;
import static java.lang.Thread.interrupted;
import static java.lang.Thread.sleep;
import static java.util.Collections.singletonList;
import static java.util.Objects.isNull;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;

import com.google.common.annotations.Beta;
import com.wl4g.infra.common.jedis.BasicJedisClient;

import lombok.CustomLog;
import lombok.ToString;

/**
 * JEDIS locks manager.
 *
 * @author wangl.sir
 * @version v1.0 2019年3月19日
 * @since
 */
@CustomLog
@ToString
public class JedisLockManager {
    protected static final String NAMESPACE = "reentrantUnfairLock.";
    protected static final String NXXX = "NX";
    protected static final String EXPX = "PX";
    protected static final long FRAME_INTERVAL_MS = 30L;
    protected static final String UNLOCK_LUA = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
    protected final BasicJedisClient jedisClient;

    public JedisLockManager(BasicJedisClient jedisClient) {
        this.jedisClient = notNullOf(jedisClient, "jedisClient");
    }

    /**
     * Get and create {@link FastReentrantUnfairDistributedRedLock} with name.
     * 
     * @param lockName
     * @return
     */
    public Lock getLock(@NotBlank String lockName) {
        return getLock(lockName, 10, TimeUnit.SECONDS);
    }

    /**
     * Get and create {@link FastReentrantUnfairDistributedRedLock} with name.
     * 
     * @param lockName
     * @param timeoutMs
     * @param unit
     * @return
     */
    public Lock getLock(@NotBlank String lockName, @Min(0) long timeoutMs, TimeUnit unit) {
        hasTextOf(lockName, "lockName");
        isTrueOf(timeoutMs > 0, "timeoutMs > 0");
        notNullOf(unit, "unit");
        return new FastReentrantUnfairDistributedRedLock(jedisClient, lockName, unit.toMillis(timeoutMs));
    }

    /**
     * Fast unsafe reentrant unfair redlock implemented by REDIS cluster.
*
* Note: This * implementation is not strictly strong consistency. It is recommended to * use this distributed lock for scenarios with high performance * requirements and low consistency requirements. On the contrary, for * scenarios with high consistency requirements (such as orders, payments, * etc.), please do not use this lock. You can use the lock of Raft/Paxos * strong consistency algorithm such as zookeeper distributed lock. Because * redis cluster distributed locks, for example, when the master dies before * copying to the slave or when the master and slave die together, there * will be serious consequences of locking. * * @author James Wong * @version v1.0 2019年3月21日 * @since * @see Discuss * Antirez failover analysis * @see Martin * Kleppmann analysis redlock * @see Antirez failover * analysis * @see VS Martin Kleppmann for * Redlock failover analysis */ @Beta @ToString(callSuper = true, exclude = { "jedisClient" }) public static class FastReentrantUnfairDistributedRedLock extends AbstractDistributedLock { private static final long serialVersionUID = -1909894475263151824L; final BasicJedisClient jedisClient; /** * Current locker reentrant counter.
* Special Note: assuming that the situation of retry to * obtain lock occurs, it must be in the same JVM process. */ final AtomicLong counter; public FastReentrantUnfairDistributedRedLock(final BasicJedisClient jedisClient, String lockName, long expiredMs) { this(jedisClient, lockName, expiredMs, 0L); } public FastReentrantUnfairDistributedRedLock(final BasicJedisClient jedisClient, String lockName, long expiredMs, long counterValue) { this(jedisClient, lockName, expiredMs, new AtomicLong(counterValue)); } public FastReentrantUnfairDistributedRedLock(final BasicJedisClient jedisClient, final String lockName, final long expiredMs, final AtomicLong counter) { super((NAMESPACE.concat(lockName)), getThreadCurrentProcessId(), expiredMs); this.jedisClient = notNullOf(jedisClient, "jedisClient"); isTrueOf(counter.get() >= 0, "counter >= 0"); this.counter = counter; } @Override public void lock() { try { lockInterruptibly(); } catch (InterruptedException e) { currentThread().interrupt(); } } @Override public void lockInterruptibly() throws InterruptedException { if (interrupted()) throw new InterruptedException(); while (true) { if (doTryAcquire()) break; sleep(FRAME_INTERVAL_MS); } } @Override public boolean tryLock() { return doTryAcquire(); } @Override public boolean tryLock(long tryTimeout, TimeUnit unit) throws InterruptedException { notNullOf(unit, "unit"); isTrueOf((tryTimeout > 0 && tryTimeout <= expiredMs), "TryTimeout must be > 0 && <= " + expiredMs); long t = unit.toMillis(tryTimeout) / FRAME_INTERVAL_MS, c = 0; while (t > ++c) { if (doTryAcquire()) return true; sleep(FRAME_INTERVAL_MS); } return false; } @Override public void unlock() { // Obtain locked processId. String acquiredProcessId = jedisClient.get(getLockName()); // Current thread is holder? if (!requestId.equals(acquiredProcessId)) { log.debug("No need to unlock of requestId: {}, acquiredProcessId: {}, counter: {}", requestId, acquiredProcessId, counter); return; } // Obtain lock record once decrement. counter.decrementAndGet(); log.debug("No need to unlock and reenter the stack lock layer, counter: {}", counter); if (counter.longValue() == 0L) { // All thread stack layers exited? Object res = jedisClient.eval(UNLOCK_LUA, singletonList(getLockName()), singletonList(requestId)); if (!assertValidity(res)) { log.debug("Failed to unlock for %{}@{}", requestId, getLockName()); } else { log.debug("Unlock successful for %{}@{}", requestId, getLockName()); } } } @Override public Condition newCondition() { throw new UnsupportedOperationException(); } /** * Execution try acquire locker by reentrant info.
* * @see JedisLockManager.java * @return */ private final boolean doTryAcquire() { String acquiredProcessId = jedisClient.get(getLockName()); // Locked-processId. if (requestId.equals(acquiredProcessId)) { // Obtain lock record once cumulatively. counter.incrementAndGet(); log.debug("Reuse acquire lock for name: {}, acquiredProcessId: {}, counter: {}", getLockName(), acquiredProcessId, counter); return true; } else { // Not currently locked? Lock expired? Local counter reset. counter.set(0L); } // Try to acquire a new lock from the server. if (assertValidity(jedisClient.setIfAbsent(getLockName(), requestId, expiredMs))) { // Obtain lock record once cumulatively. counter.incrementAndGet(); return true; } return false; } /** * Assertion validate lock result is acquired/UnAcquired success? * * @param res * @return */ private final boolean assertValidity(Object res) { if (isNull(res)) { return false; } if (res instanceof String) { String res0 = res.toString().trim(); return "1".equals(res0) || "OK".equalsIgnoreCase(res0); } else if (res instanceof Boolean) { return (boolean) res; } else if (res instanceof Number) { return ((Number) res).longValue() >= 1L; } else { throw new IllegalStateException(format("Unknown acquired state for %s", res)); } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy