美文网首页
spring data redis自带锁机制

spring data redis自带锁机制

作者: 晚歌歌 | 来源:发表于2020-07-23 18:44 被阅读0次

背景

正在对某个接口做性能优化,通过pinpoint发现为了获取一次@Cacheable注解的数据,居然对redis发起了3次调用,分别是两次exists和一次get


image.png

源码分析

org.springframework.data.redis.cache.RedisCache

public RedisCacheElement get(final RedisCacheKey cacheKey) {

        Assert.notNull(cacheKey, "CacheKey must not be null!");

        Boolean exists = (Boolean) redisOperations.execute(new RedisCallback<Boolean>() {

            @Override
            public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
                return connection.exists(cacheKey.getKeyBytes());
            }
        });

        if (!exists) {
            return null;
        }

        byte[] bytes = doLookup(cacheKey);

        // safeguard if key gets deleted between EXISTS and GET calls.
        if (bytes == null) {
            return null;
        }

        return new RedisCacheElement(cacheKey, fromStoreValue(deserialize(bytes)));
    }

    private byte[] doLookup(Object key) {
        
        RedisCacheKey cacheKey = key instanceof RedisCacheKey ? (RedisCacheKey) key : getRedisCacheKey(key);

        return (byte[]) redisOperations.execute(new AbstractRedisCacheCallback<byte[]>(
                new BinaryRedisCacheElement(new RedisCacheElement(cacheKey, null), cacheValueAccessor), cacheMetadata) {

            @Override
            public byte[] doInRedis(BinaryRedisCacheElement element, RedisConnection connection) throws DataAccessException {
                return connection.get(element.getKeyBytes());
            }
        });
    }

通过以上方法可以很清楚的看出一次exists和一次get命令,那么另一次exists是什么操作?
通过进入doInRedis追踪org.springframework.data.redis.cache.RedisCache.AbstractRedisCacheCallback#waitForLock方法可以发现这就是另一次exists

protected boolean waitForLock(RedisConnection connection) {

            boolean retry;
            boolean foundLock = false;
            do {
                retry = false;
                if (connection.exists(cacheMetadata.getCacheLockKey())) {
                    foundLock = true;
                    try {
                        Thread.sleep(WAIT_FOR_LOCK_TIMEOUT);
                    } catch (InterruptedException ex) {
                        Thread.currentThread().interrupt();
                    }
                    retry = true;
                }
            } while (retry);

            return foundLock;
        }

        protected void lock(RedisConnection connection) {
            waitForLock(connection);
            connection.set(cacheMetadata.getCacheLockKey(), "locked".getBytes());
        }

继续追踪这个key可以发现RedisCacheMetadata

public RedisCacheMetadata(String cacheName, byte[] keyPrefix) {

            Assert.hasText(cacheName, "CacheName must not be null or empty!");
            this.cacheName = cacheName;
            this.keyPrefix = keyPrefix;

            StringRedisSerializer stringSerializer = new StringRedisSerializer();

            // name of the set holding the keys
            this.setOfKnownKeys = usesKeyPrefix() ? new byte[] {} : stringSerializer.serialize(cacheName + "~keys");
            this.cacheLockName = stringSerializer.serialize(cacheName + "~lock");
        }

这个分布式锁key则是cacheName + "~lock"

总结

通过上面的waitForLock和源码中的lock方法就可以得知:spring data redis框架(源码版本1.8.11 RELEASE)为redis的get set添加了锁机制,即通过程序保证了读写不能并发。对于某一组prefix,假设有线程正在对其中的某个key进行set操作,那么对于这组prefix的所有key都将无法进行其余任何操作。
但个人认为这是破坏redis单线程优势,无论是什么命令,达到redis都会变成单线程执行,程序只需要按命令达到redis的时间顺序执行即可,加锁是多此一举。
另外一次get就可以完成的业务被冗余成两次exists和一次get,并发量高的时候容易影响redis性能

相关文章

网友评论

      本文标题:spring data redis自带锁机制

      本文链接:https://www.haomeiwen.com/subject/sanhlktx.html