Talk about the computeIfAbsent operation of RMap of redisson

Keywords: Redis Java codec

order

This paper mainly studies the computeIfAbsent operation of RMap of redisson

Example

    @Test
    public void testRMapComputeIfAbsent(){
        Config config = new Config();
        config.useSingleServer()
                .setAddress("redis://192.168.99.100:6379");
        RedissonClient redisson = Redisson.create(config);
        RMap<String, CityInfo> map = redisson.getMap("anyMap");

        CityInfo bj = CityInfo.builder().name("bj").build();
        CityInfo currentObject = map.computeIfAbsent("bj", k -> bj);
        Assert.assertEquals(bj.toString(),currentObject.toString());
    }

Source code analysis

ConcurrentMap.computeIfAbsent

java/util/concurrent/ConcurrentMap.java

    /**
     * {@inheritDoc}
     *
     * @implSpec
     * The default implementation is equivalent to the following steps for this
     * {@code map}, then returning the current value or {@code null} if now
     * absent:
     *
     * <pre> {@code
     * if (map.get(key) == null) {
     *     V newValue = mappingFunction.apply(key);
     *     if (newValue != null)
     *         return map.putIfAbsent(key, newValue);
     * }
     * }</pre>
     *
     * The default implementation may retry these steps when multiple
     * threads attempt updates including potentially calling the mapping
     * function multiple times.
     *
     * <p>This implementation assumes that the ConcurrentMap cannot contain null
     * values and {@code get()} returning null unambiguously means the key is
     * absent. Implementations which support null values <strong>must</strong>
     * override this default implementation.
     *
     * @throws UnsupportedOperationException {@inheritDoc}
     * @throws ClassCastException {@inheritDoc}
     * @throws NullPointerException {@inheritDoc}
     * @since 1.8
     */
    @Override
    default V computeIfAbsent(K key,
            Function<? super K, ? extends V> mappingFunction) {
        Objects.requireNonNull(mappingFunction);
        V v, newValue;
        return ((v = get(key)) == null &&
                (newValue = mappingFunction.apply(key)) != null &&
                (v = putIfAbsent(key, newValue)) == null) ? newValue : v;
    }
  • computeIfAbsent when the key does not exist, it returns a new value instead of null
  • putIfAbsent is called in the computeifasent method

RedissonMap.putIfAbsent

redisson-3.8.1-sources.jar!/org/redisson/RedissonMap.java

    @Override
    public V putIfAbsent(K key, V value) {
        return get(putIfAbsentAsync(key, value));
    }

    @Override
    public RFuture<V> putIfAbsentAsync(final K key, final V value) {
        checkKey(key);
        checkValue(key);
        
        RFuture<V> future = putIfAbsentOperationAsync(key, value);
        if (hasNoWriter()) {
            return future;
        }
        
        MapWriterTask<V> listener = new MapWriterTask<V>() {
            @Override
            public void execute() {
                options.getWriter().write(key, value);
            }
            
            @Override
            protected boolean condition(Future<V> future) {
                return future.getNow() == null;
            }

        };
        return mapWriterFuture(future, listener);
    }

    protected boolean hasNoWriter() {
        return options == null || options.getWriter() == null;
    }

    protected RFuture<V> putIfAbsentOperationAsync(K key, V value) {
        return commandExecutor.evalWriteAsync(getName(key), codec, RedisCommands.EVAL_MAP_VALUE,
                 "if redis.call('hsetnx', KEYS[1], ARGV[1], ARGV[2]) == 1 then "
                    + "return nil "
                + "else "
                    + "return redis.call('hget', KEYS[1], ARGV[1]) "
                + "end",
                Collections.<Object>singletonList(getName(key)), encodeMapKey(key), encodeMapValue(value));
    }

    protected <M> RFuture<M> mapWriterFuture(RFuture<M> future, final MapWriterTask<M> listener) {
        if (options != null && options.getWriteMode() == WriteMode.WRITE_BEHIND) {
            future.addListener(new MapWriteBehindListener<M>(commandExecutor, listener, writeBehindCurrentThreads, writeBehindTasks, options.getWriteBehindThreads()));
            return future;
        }        

        final RPromise<M> promise = new RedissonPromise<M>();
        future.addListener(new FutureListener<M>() {
            @Override
            public void operationComplete(final Future<M> future) throws Exception {
                if (!future.isSuccess()) {
                    promise.tryFailure(future.cause());
                    return;
                }

                if (listener.condition(future)) {
                    commandExecutor.getConnectionManager().getExecutor().execute(new Runnable() {
                        @Override
                        public void run() {
                            try {
                                listener.execute();
                            } catch (Exception e) {
                                promise.tryFailure(e);
                                return;
                            }
                            promise.trySuccess(future.getNow());
                        }
                    });
                } else {
                    promise.trySuccess(future.getNow());
                }
            }
        });

        return promise;
    }
  • RedissonMap covers the putIfAbsent method, which calls putIfAbsentAsync. This method mainly calls putIfAbsentOperationAsync
  • putIfAbsentOperationAsync uses a lua script to ensure atomicity. If the key before hsetnx does not exist and is set successfully, nil will be returned. Otherwise, the existing value will be returned
  • putIfAbsentAsync not only calls putIfAbsentOperationAsync, but also adds the logic of writer. If the writer is set, the writer will be triggered by callback when the future of putIfAbsentOperationAsync is successful
  • writer is mainly used for external storage, such as bypass storage, one copy to local disk, with two modes of synchronous operation and asynchronous operation

Summary

Redisson further encapsulates the data structure operations of redis. For example, the RMap of redisson implements the java.util.concurrent.ConcurrentMap interface and the java.util.Map interface, implements methods such as putIfAbsent, and ensures the atomicity of operations on the server side with lua script.

doc

Posted by desenhistas on Wed, 25 Dec 2019 12:16:43 -0800