You can use it: redisson distributed read-write lock component, with webfilter to release your hands

Time:2021-4-18

scene

Previously on: https://segmentfault.com/a/11…

In order to solve the problem of transaction visibility caused by Max (ID), the gap lock is used initially, but the gap lock locks the table, which has a great impact on concurrency, so we consider using distributed read-write lock with finer granularity.

Read write lock

Read write lock principle, read concurrent, read write, write not good

According to our business rules, you can write concurrently, but you can’t read while writing, and you can’t write while reading

Therefore, the read lock is acquired during the write operation, and the write lock is acquired during the read operation

Introducing redisson

pom.xml

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.12.0</version>
</dependency>

assembling

@Bean(destroyMethod = "shutdown")
public RedissonClient getRedissonClient() {

    //Create configuration
    Config config = new Config();

    //Specifies the encoding. The default encoding is org.redisson.codec .JsonJacksonCodec
    //The previous spring data redis client, jedis, is encoded as org.springframework.data . redis.serializer.StringRedisSerializer
    //After using redisson, in order to be compatible with each other's data, change the code here to org.redisson.client . codec.StringCodec
    config.setCodec(new org.redisson.client.codec.StringCodec());

    //Specify single node deployment
    config.useSingleServer()
            .setAddress(String.format("redis://%s:%s", redisConfiguration.getHost(), redisConfiguration.getPort()))
            .setPassword(redisConfiguration.getPassword())
            .setConnectionPoolSize(redisConfiguration.getPool().getMaxActive())
            .setConnectionMinimumIdleSize(redisConfiguration.getPool().getMinIdle())
            .setIdleConnectionTimeout(redisConfiguration.getPool().getIdleTimeout())
            .setConnectTimeout(redisConfiguration.getConnectTimeout())
            .setTimeout(redisConfiguration.getReadTimeout());


    return Redisson.create(config);
}

Lock service

Code logic is relatively simple, do not understand the comments area message, timely reply.

@Service
@Slf4j
public class LockService {


    @Resource
    private RedissonClient redissonClient;

    @Value("${LOCK_RETRY_TIME:3}")
    private Integer lockReTryTime;

    /**
     *Get the read lock for storing data
     *
     * @param computeUuids
     */
    public void getReadLock(List<String> computeUuids) {
        getLock(computeUuids, true);
    }

    /**
     *Release read lock
     *
     * @param computeUuids
     */
    public void unReadLock(List<String> computeUuids) {
        unLock(computeUuids, true);
    }

    /**
     *Get the read lock for storing data
     *
     * @param computeUuids
     */
    public void getWriteLock(List<String> computeUuids) {
        getLock(computeUuids, false);
    }

    /**
     *Release read lock
     *
     * @param computeUuids
     */
    public void unWriteLock(List<String> computeUuids) {
        unLock(computeUuids, false);
    }

    /**
     *Get the read lock for storing data
     *
     * @param computeUuids
     */
    public void getLock(List<String> computeUuids, boolean read) {
        log.info (batch acquire {} lock computeuuid: {}), read? Read: "write", JSON.toJSONString (computeUuids));
        List<RLock> lockedList = new ArrayList<>();
        try {
            for (String computeUuid : computeUuids) {
                String lockName = RedisCacheKey.DEVICE_AUTHORITY_MODIFY_SYNC_LOCK.build(computeUuid);
                RLock rLock;
                if (read) {
                    rLock = redissonClient.getReadWriteLock(lockName).readLock();
                } else {
                    rLock = redissonClient.getReadWriteLock(lockName).writeLock();
                }

                boolean isLocked = false;
                int count = 0;
                log.info ("lockname: {} start getting {} lock", lockname, read? "Read": "write");
                //Try to get 3 times, get failed, all released
                do {
                    try {
                        //Attempt to acquire lock (waiting time, expiration time, time unit)
                        isLocked = rLock.tryLock(RedisCacheKey.DEVICE_AUTHORITY_MODIFY_SYNC_LOCK.getWaitTime(), RedisCacheKey.DEVICE_AUTHORITY_MODIFY_SYNC_LOCK.getExpireSecs(), TimeUnit.MILLISECONDS);
                    } catch (InterruptedException e) {
                        log.info ("get lock failed: {}"), rLock.getName ());
                        count++;
                    }
                } while (!isLocked && count < lockReTryTime);

                if (count == 3) {
                    log.info ("= = = = = = {} lock application failed {}, start to release = = = = = = = = = = = = =", read? "Read": "write", lockname));
                    break;
                } else {
                    lockedList.add(rLock);
                    log.info ("= = = = = = {} successful lock application {} = = = = = = = =", read? "Read": "write", lockname) ";
                }
            }
        } finally {
            if (computeUuids.size() != lockedList.size()) {
                log.info ("= = = = = = = = there is {} lock application failed, start releasing = = = = = = = = = = = = =", read? "Read": "write");
                lockedList.forEach(lock -> {
                    lock.unlock();
                    log.info ("= = = = = = = {} lock released successfully {} = = = = = = = =", read? "Read": "write", lock.getName ());
                });
            }
        }


    }

    /**
     * @param computeUuids
     */
    public void unLock(List<String> computeUuids, boolean read) {
        log.info ("batch release {} lock computeuuid: {}", read? "Read": "write", JSON.toJSONString (computeUuids));
        for (String computeUuid : computeUuids) {
            String lockName = RedisCacheKey.DEVICE_AUTHORITY_MODIFY_SYNC_LOCK.build(computeUuid);
            log.info ("lockname: {} start releasing {} lock", lockname, read? "Read": "write");
            RLock rLock;
            try {
                if (read) {
                    rLock = redissonClient.getReadWriteLock(lockName).readLock();
                } else {
                    rLock = redissonClient.getReadWriteLock(lockName).writeLock();
                }
                rLock.unlock();
            } catch (Exception e) {
                log.info ("failed to get lock when releasing, continue to get next one: {}", lockname); ";
            }
            log.info ("lockname: {} release {} lock succeeded", lockname, read? "Read": "write");
        }
        log.info ("= = = = = = = = {} lock released successfully = = = = = = = = = = = = = = = = =", read? "Read": "write");
    }
}

Filter optimizes lock release

Data insertion can be performed at a fixed location through a unified entry, but lock release needs to be processed at each business exit

Because of the numerous exports, one by one writing is undoubtedly the most easy to think of, but also the most… Emmm… Naive

So I thought of webfilter, every request thread needs to go through the filter, so I thought of using filter + ThreadLocal processing

Global ThreadLocal

public class ComputeLockThreadLocal {
    private static final ThreadLocal<List<String>> threadLocal = new NamedThreadLocal<>("computeLockList");

    public static void set(List<String> computeLockList) {
        threadLocal.set(computeLockList);
    }

    public static List<String> get() {
        return threadLocal.get();
    }

    public static void remove() {
        threadLocal.remove();
    }

}

Define webfilter

@Slf4j
public class PassWebFilter implements Filter {


    @Resource
    private LockService lockService;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        try {
            filterChain.doFilter(servletRequest, servletResponse);
        } finally {
            List<String> computeLockList = ComputeLockThreadLocal.get();
            log.debug ("* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *, JSON.toJSONString (computeLockList));
            if (CollectionUtils.isNotEmpty(computeLockList)) {
                log.info ("* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *, JSON.toJSONString (computeLockList));

                lockService.unReadLock(computeLockList);
                ComputeLockThreadLocal.remove();
            }
        }
    }

    @Override
    public void destroy() {
    }
}

Configure webfilter

@Configuration
public class WebConfiguration {

    @Value("${web.filter.urlPatterns:/*}")
    private String webPrefix;

    @Bean("passWebFilter")
    public Filter webFilter() {
        return new PassWebFilter();
    }

    @Bean
    public FilterRegistrationBean webFilterRegistration() {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(new DelegatingFilterProxy("passWebFilter"));
        registration.setName("passWebFilter");
        registration.addUrlPatterns (webprefix); // configure the intercepted URL to block all
        registration.setOrder(5);
        return registration;
    }

    @Bean
    public RequestContextListener requestContextListener() {
        return new RequestContextListener();
    }
}

In this way, the lock key can be put when the lock is added, and then at the end of the session, the webfilter can be used to judge whether to put the lockTo reduce the amount of modification

Recommended Today

Review of SQL Sever basic command

catalogue preface Installation of virtual machine Commands and operations Basic command syntax Case sensitive SQL keyword and function name Column and Index Names alias Too long to see? Space Database connection Connection of SSMS Connection of command line Database operation establish delete constraint integrity constraint Common constraints NOT NULL UNIQUE PRIMARY KEY FOREIGN KEY DEFAULT […]