RedissonClient 和 RedisTemplate 做Map的累加处理的一些特性

配置

Spring Boot 版本: 2.6.0
Spring -data-redis 版本: 2.6.0
Redisson 版本: 3.16.8

RedissonClient 配置

对于 RedissonClient的配置采取 实现RedissonAutoConfigurationCustomizer的方式来实现,具体原因可以参考RedissonAutoConfiguration 类的 RedissonClient自动配置代码

@Component
public class MyRedissonAutoConfigurationCustomizer implements RedissonAutoConfigurationCustomizer {
    /**
     * Customize the RedissonClient configuration.
     *
     * @param configuration the {@link Config} to customize
     */
    @Override public void customize(Config configuration) {
        System.out.println("设置默认的Redisson Codec 为 JsonJacksonCodec");
        JsonJacksonCodec jacksonCodec = new JsonJacksonCodec();
        //支持Java 8 新的日期类型序列化和反序列化
        jacksonCodec.getObjectMapper()
            .registerModule(new JavaTimeModule())
            .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
        configuration.setCodec(jacksonCodec);
    }
}

RedisTemplate 配置

	@Bean(name = "jackson2JsonRedisSerializer")
    public RedisSerializer<Object> jackson2JsonRedisSerializer() {
        //使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值
        ObjectMapper redisOm = new ObjectMapper();
        redisOm.registerModule(new JavaTimeModule())
            .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
        return new GenericJackson2JsonRedisSerializer(redisOm);
    }

    @Bean
    public StringRedisSerializer getStringRedisSerializer() {
        return new StringRedisSerializer();
    }


    @Bean
    public RedisTemplate<Object, Object> configRedisTemplate(RedisTemplate redisTemplate){
        redisTemplate.setKeySerializer(getStringRedisSerializer());
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer());
        redisTemplate.setHashKeySerializer(getStringRedisSerializer());
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer());
        System.out.println("自定义RedisTemplate加载成功");
        return redisTemplate;
    }

测试案例代码类


/**
 * Redis Map 累加测试
 */
@RunWith(SpringRunner.class)
@SpringBootTest
public class RedisMapIncrementTests {


    @Resource
    private RedissonClient redissonClient;

    @Resource
    private RedisTemplate redisTemplate;


    @After
    public void afterAll(){
        redissonClient.shutdown();
        System.out.println("关闭连接!");
    }


RedissonClient 案例1

测试代码

	@Test
    public void testIncrementWithRedisson1_1(){
        String key = "testIncrementWithRedisson1_1";
        String hashKey = "A1";
        IntStream.range(1,101).forEach(r->{
            System.out.print(incrementWithRedisson1(key,hashKey));
        });

        RMap<String,Long> accumulatorMap = redissonClient.getMap(key);
        System.out.println(String.format("累加的结果%d",accumulatorMap.get(hashKey)));
    }
    
    private long incrementWithRedisson1(String key,String hashKey){
        RMap<String,Long> accumulatorMap = redissonClient.getMap(key);
        return accumulatorMap.addAndGet(hashKey,1L);
    }

测试结果

Redis 的值:
在这里插入图片描述

控制台输出:

... 以上略
99
100
累加的结果100


RedissonClient 案例2

测试代码

    @Test
    public void testIncrementWithRedisson1_2(){
        String key = "testIncrementWithRedisson1_2";
        String hashKey = "A1";
        IntStream.range(1,101).forEach(r->{
            System.out.println(incrementWithRedisson1(key,hashKey));
        });

        RMap<String,Long> accumulatorMap = redissonClient.getMap(key);
        //下面这行代码会抛出异常 java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.Long
        Long value = accumulatorMap.get(hashKey);
        System.out.println(String.format("累加的结果%d",value));
    }
    
    private long incrementWithRedisson1(String key,String hashKey){
        RMap<String,Long> accumulatorMap = redissonClient.getMap(key);
        return accumulatorMap.addAndGet(hashKey,1L);
    }

测试结果

案例2的代码和案例1的代码唯一的区别就是取出累加后的值的方式不同,导致抛出java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.Long 异常,但累加结果是正确的。
原因是JsonJacksonCodec 对于 整数数字字符串默认转换为Integer,而不是按照RMap<String,Long>的泛型来转换,在取出值是强制转换IntegerLong 时,抛出异常。
相关代码:

com.fasterxml.jackson.core.json.UTF8StreamJsonParser#nextToken


...

 /* And should we now have a name? Always true for Object contexts
  * since the intermediate 'expect-value' state is never retained.
  */
 if (!_parsingContext.inObject()) {
     _updateLocation();
     return _nextTokenNotInObject(i);
 }
...

返回的Tokencom.fasterxml.jackson.core.JsonToken#VALUE_NUMBER_INT

Redis 的值:

在这里插入图片描述

控制台输出:

... 以上略
99
100

java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.Long

RedissonClient 案例3

解决掉案例2 的问题

测试代码


  @Test
    public void testIncrementWithRedisson2_1(){
        String key = "testIncrementWithRedisson2_1";
        String hashKey = "A1";
        IntStream.range(1,101).forEach(r->{
            System.out.println(incrementWithRedisson2(key,hashKey));
        });
	    //强制指定本Map的Codec 为LongCodec
        RMap<String,Long> accumulatorMap = redissonClient.getMap(key,LongCodec.INSTANCE);
        //下面这行代码不会抛出异常
        Long value = accumulatorMap.get(hashKey);
        System.out.println(String.format("累加的结果%d",value));
    }

    private long incrementWithRedisson2(String key,String hashKey){
        //强制指定本Map的Codec 为LongCodec
        RMap<String,Long> accumulatorMap = redissonClient.getMap(key, LongCodec.INSTANCE);
        return accumulatorMap.addAndGet(hashKey,1L);
    }

测试结果

Redis 的值:

在这里插入图片描述

注意,这里的Map的Key的值没有了双引号,这个也是由于LongCodec的处理导致的,因此如果切换代码时,记得处理Redis的历史数据,否则就会出现一个MapKey 有两个累加器。

控制台输出:

... 以上略
99
100
累加的结果100


RedissonClient 案例4

补一个多线程的累加案例

测试代码

    /**
     * 定义100个线程的线程池 
     */
    private static final ExecutorService
        testExecutorService = new ThreadPoolExecutor(100,100,0L, TimeUnit.MILLISECONDS,
        new LinkedBlockingQueue<>(1024),new ThreadFactoryBuilder()
        .setNameFormat("Test-pool-%d").get());

  @Test
    public void testIncrementWithRedisson2_2(){
        String key = "testIncrementWithRedisson2_2";
        String hashKey = "A1";
        List<CompletableFuture> cfList = IntStream.range(1,101).mapToObj(r->{
            return CompletableFuture.runAsync(()->
                        incrementWithRedisson2(key,hashKey)
                    ,testExecutorService)
                .exceptionally(ex->{
                    ex.printStackTrace();
                    return null;
                });
        }).collect(Collectors.toList());
        // 等待所有线程结束
        CompletableFuture.allOf(cfList.toArray(new CompletableFuture[cfList.size()])).whenComplete((k,e)->{
            //所有线程都结束后,取出累加的值
            //强制指定本Map的Codec 为LongCodec
            RMap<String,Long> accumulatorMap = redissonClient.getMap(key,LongCodec.INSTANCE);
            Long value = accumulatorMap.get(hashKey);
            System.out.println(String.format("累加的结果%d",value));
        }).join();
    }

    private long incrementWithRedisson2(String key,String hashKey){
        //强制指定本Map的Codec 为LongCodec
        RMap<String,Long> accumulatorMap = redissonClient.getMap(key, LongCodec.INSTANCE);
        return accumulatorMap.addAndGet(hashKey,1L);
    }

测试结果

Redis 的值:

在这里插入图片描述

控制台输出:

累加的结果100

RedisTemplate 案例1

测试代码

    @Test
    public void testIncrementWithRedisTemplate1_1(){
        String key = "testIncrementWithRedisTemplate1_1";
        String hashKey = "A1";
        IntStream.range(1,101).forEach(r->{
            System.out.println(incrementWithRedisTemplate(key,hashKey));
        });
        System.out.println(String.format("累加的结果%d",redisTemplate.opsForHash().get(key,hashKey)));
    }
    
    private long incrementWithRedisTemplate(String key,String hashKey){
        return redisTemplate.opsForHash().increment(key,hashKey,1L);
    }

测试结果

Redis 的值:

在这里插入图片描述

控制台输出:

... 以上略
99
100
累加的结果100

RedisTemplate 案例2

测试代码

    @Test
    public void testIncrementWithRedisTemplate1_2(){
        String key = "testIncrementWithRedisTemplate1_2";
        String hashKey = "A1";
        IntStream.range(1,101).forEach(r->{
            System.out.println(incrementWithRedisTemplate(key,hashKey));
        });

        //下面这行代码会抛出异常 java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.Long
        Long value = (Long)redisTemplate.opsForHash().get(key,hashKey);
        System.out.println(String.format("累加的结果%d",value));
    }

    private long incrementWithRedisTemplate(String key,String hashKey){
        return redisTemplate.opsForHash().increment(key,hashKey,1L);
    }

测试结果

Redis 的值:

在这里插入图片描述

控制台输出:

... 以上略
99
100

java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.Long

RedisTemplate 案例3

测试代码

    @Test
    public void testIncrementWithRedisTemplate1_3(){
        String key = "testIncrementWithRedisTemplate1_3";
        String hashKey = "A1";
        IntStream.range(1,101).forEach(r->{
            System.out.println(incrementWithRedisTemplate(key,hashKey));
        });

        //下面这行代码不会抛出异常
        Long value = Long.valueOf(redisTemplate.opsForHash().get(key,hashKey).toString());
        System.out.println(String.format("累加的结果%d",value));
    }
    
    private long incrementWithRedisTemplate(String key,String hashKey){
        return redisTemplate.opsForHash().increment(key,hashKey,1L);
    }

测试结果

Redis 的值:

在这里插入图片描述

控制台输出:

... 以上略
99
100
累加的结果100

RedisTemplate 案例4

补一个多线程的累加案例

测试代码

    /**
     * 定义100个线程的线程池
     */
    private static final ExecutorService
        testExecutorService = new ThreadPoolExecutor(100,100,0L, TimeUnit.MILLISECONDS,
        new LinkedBlockingQueue<>(1024),new ThreadFactoryBuilder()
        .setNameFormat("Test-pool-%d").get());
        
	@Test
    public void testIncrementWithRedisTemplate1_4(){
        String key = "testIncrementWithRedisTemplate1_4";
        String hashKey = "A1";
        List<CompletableFuture> cfList = IntStream.range(1,101).mapToObj(r->{
            return CompletableFuture.runAsync(()->
                    incrementWithRedisTemplate(key,hashKey)
                ,testExecutorService)
                .exceptionally(ex->{
                    ex.printStackTrace();
                    return null;
                });
        }).collect(Collectors.toList());
        // 等待所有线程结束
        CompletableFuture.allOf(cfList.toArray(new CompletableFuture[cfList.size()])).whenComplete((k,e)->{
            //所有线程都结束后,取出累加的值
            Long value = Long.valueOf(redisTemplate.opsForHash().get(key,hashKey).toString());
            System.out.println(String.format("累加的结果%d",value));
        }).join();
    }

    private long incrementWithRedisTemplate(String key,String hashKey){
        return redisTemplate.opsForHash().increment(key,hashKey,1L);
    }

测试结果

Redis 的值:

在这里插入图片描述

控制台输出:

累加的结果100
Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐