美文网首页java 设计Redis
0121 spring-boot-redis的使用

0121 spring-boot-redis的使用

作者: 李福春carter | 来源:发表于2020-01-21 19:21 被阅读0次

    redis是什么呢?redis,属于NoSQL的一种,在互联网时代,起到加速系统的作用。

    redis是一种内存数据库,支持7种数据类型的存储,性能1S 10w次读写;
    redis提供的简单的事务保证了高并发场景下数的一致性。
    redis在2.6版本之后增加了lua支持,命令是原子性的;

    本篇文章主要基于springboot的redis-starter。<br />HELLO, 性能利器Redis.

    spring-boot-starter-redis


    这个是springboot提供的redis操作工具包,底层的redis驱动使用的是lettus,而不是jedis;

    依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    

    序列化

    主要通过RedisTemplate来操作redis;<br />

    当然也支持自定义序列化器,比如效率比较高的kyto序列化器;
    StringRedisTemplate:key,value都是按照字符串存储的。
    TypedTuple 保存集合中的有序元素;
    可以查看一下StringRedisTemplate的源码:

    public StringRedisTemplate() {
        setKeySerializer(RedisSerializer.string());
        setValueSerializer(RedisSerializer.string());
        setHashKeySerializer(RedisSerializer.string());
        setHashValueSerializer(RedisSerializer.string());
    }
    

    数据类型操作接口

    功能 单个操作接口 批量操作接口
    有序集合 ZSetOperations BoundZsetOperations
    字符串 ValueOperations BoundValueOpetations
    集合 SetOperations BoundSetOperations
    列表 ListOperations BoundListOperations
    散列 HashOperations BoundHashOperations
    基数 HyperLogLogOperations BoundHyperLogLogOperaions
    地理位置 GeoOperations BoundGeoOperations

    使用代码

     @Autowired
        private RedisTemplate redisTemplate;
    
        @Test
        void stringRedisTest() {
            final ValueOperations valueOperations = redisTemplate.opsForValue();
            valueOperations
                    .set("key1", "value1", Duration.ofMinutes(1));
    
            final Object value = valueOperations.get("key1");
    
            Assert.isTrue(Objects.equals("value1", value), "set失败");
    
            final HashOperations hashOperations = redisTemplate.opsForHash();
    
            hashOperations.put("hash1", "f1", "v1");
            hashOperations.put("hash1", "f2", "v2");
    
            hashOperations.values("hash1").forEach(System.out::println);
        }
    

    在同一条连接中进行多次操作

    1. SessionCallback 高级操作对象
    2. RedisCallback 低级操作对象<br />
      代码中直接使用的java8的lambda表达式。

    使用代码

    @Test
    void redisCallbackTest() {
        redisTemplate.execute((RedisCallback) connection -> {
            connection.set("rkey1".getBytes(), "rv1".getBytes());
            connection.set("rkey2".getBytes(), "rv2".getBytes());
            return null;
        });
    }
    
    @Test
    void sessionCallbackTest() {
        redisTemplate.execute(new SessionCallback() {
            @Override
            public Object execute(RedisOperations operations) throws DataAccessException {
                final ListOperations listOperations = operations.opsForList();
                listOperations.leftPush("sk1", "sv1");
                listOperations.leftPush("sk1", "sv2");
                listOperations.getOperations().expire("sk1", 1, TimeUnit.MINUTES);
    
                listOperations.range("sk1", 0, 2).forEach(System.out::println);
                return 1;
            }
        });
    }
    

    字符串操作

    最为常用的数据类型
    实际情况使用的不多,现实的场景一般是放一个对象或者对象列表 转换为字符串 进行存储,取出的时候再转换为对象;

    代码:

     @Test
        void stringTest() {
            redisTemplate.opsForValue().set("stringKey1", "value1", 5, TimeUnit.MINUTES);
    
            //字符串类型的整数,不能进行数字运算;
            redisTemplate.opsForValue().set("stringKey2", "1", 5, TimeUnit.MINUTES);
    
            //进行数字运算,增加,减少
            redisTemplate.opsForValue().set("stringKey3", 1, 5, TimeUnit.MINUTES);
            redisTemplate.opsForValue().increment("stringKey3",1);
            redisTemplate.opsForValue().decrement("stringKey3",1);
    
            //其它操作方法
            final Long keySize = redisTemplate.opsForValue().size("stringKey1");
            System.out.println(keySize);
            
            //批量设置
            Map<String,Long> map = new HashMap<>(4);
            map.put("sk1",1L);
            map.put("sk2",2L);
            map.put("sk3",3L);
            map.put("sk4",4L);
            redisTemplate.opsForValue().multiSet(map);
            redisTemplate.opsForValue().multiSetIfAbsent(map);
            //批量获取
            redisTemplate.opsForValue().multiGet(map.keySet()).forEach(System.out::println);
    
    
            //getAndSet
            final Object sk5Value = redisTemplate.opsForValue().getAndSet("sk5", 100);
            System.out.println("sk5Value:"+sk5Value);
            
            redisTemplate.opsForValue().append("sk5","hello redis");
            System.out.println("sk5Value2:"+redisTemplate.opsForValue().get("sk5"));
            
            //按照情况设置,可以省去了之前查询出来之后判断是否存在再操作的代码;
            redisTemplate.opsForValue().setIfAbsent("sk6",1000,5,TimeUnit.MINUTES);
            redisTemplate.opsForValue().setIfPresent("sk6",100,5,TimeUnit.MINUTES);
    
        }
    

    其它方法:

    更多提供的方法需要在业务场景中多使用

    列表操作

     @Test
        void listTest() {
    
            stringRedisTemplate.opsForList().leftPush("lk1","lkv1");
            stringRedisTemplate.opsForList().leftPushAll("lk2","lk2v1","lk2v2");
            stringRedisTemplate.opsForList().leftPushAll("lk2",Arrays.asList("lk2v3","lk2v4"));
            stringRedisTemplate.opsForList().leftPushIfPresent("lk3","lk3v1");
    
            final List<String> lk2ValuesList = stringRedisTemplate.opsForList().range("lk2", 0, 3);
            System.out.println(lk2ValuesList);
        }
    

    集合操作

    @Test
        void setTest() {
            stringRedisTemplate.opsForSet().add("sk1","sk1v1","sk1v2","sk1v3");
            stringRedisTemplate.opsForSet().add("sk2","sk1v1","sk2v2","sk2v3");
    
            final Set<String> sk1 = stringRedisTemplate.opsForSet().members("sk1");
            final Set<String> sk2 = stringRedisTemplate.opsForSet().members("sk2");
    
            System.out.println("sk1: "+sk1);
            System.out.println("sk2: "+sk2);
    
            final Set<String> intersect = stringRedisTemplate.opsForSet().intersect("sk1", "sk2");
            System.out.println("交集是:" + intersect);
    
            final Set<String> union = stringRedisTemplate.opsForSet().union("sk1", "sk2");
            System.out.println("并集:" + union);
    
            final Set<String> difference = stringRedisTemplate.opsForSet().difference("sk1", "sk2");
            System.out.println("差集:"+ difference);
    
            final Long size = stringRedisTemplate.opsForSet().size("sk1");
    
            System.out.println("size for sk1 : " + size);
    
            stringRedisTemplate.delete("sk1");
            stringRedisTemplate.delete("sk2");
    
        }
    

    有序集合操作

     @Test
        void zsetTest() {
            IntStream.rangeClosed(1,100).forEach(i->{
                stringRedisTemplate.opsForZSet().add("zsk1",String.valueOf(i),i*10);
            });
            final Set<ZSetOperations.TypedTuple<String>> typedTupleSet = IntStream.rangeClosed(1, 100).mapToObj(i -> new DefaultTypedTuple<String>(String.valueOf(i), (double) i * 11)).collect(Collectors.toSet());
            stringRedisTemplate.opsForZSet().add("zsk2",typedTupleSet);
    
            final Set<String> zsk1 = stringRedisTemplate.opsForZSet().rangeByLex("zsk1", RedisZSetCommands.Range.range().gte(20).lte(100));
            System.out.println("范围内的集合:" + zsk1);
    
        }
    

    散列表操作

     @Test
        void hashTest() {
            stringRedisTemplate.opsForHash().put("hashk1","k1","v1");
            stringRedisTemplate.opsForHash().put("hashk1","k2","v1");
            stringRedisTemplate.opsForHash().put("hashk1","k3","v1");
    
            stringRedisTemplate.opsForHash().putIfAbsent("hashk1","k4","new V1");
    
            final List<Object> multiGet = stringRedisTemplate.opsForHash().multiGet("hashk1", Arrays.asList("k1", "k2"));
            System.out.println("一次获取多个:" + multiGet);
    
     }
    

    springboot中redis的配置

    配置分两个部分,连接池和连接信息;下表列出必须给出的配置:

    spring.redis.port=6379
    spring.redis.host=localhost
    spring.redis.password=
    spring.redis.timeout=1000
    
    #最小空闲连接数
    spring.redis.lettuce.pool.min-idle=2
    #最大空闲连接数
    spring.redis.lettuce.pool.max-idle=4
    #最大活跃连接数
    spring.redis.lettuce.pool.max-active=8
    #连接最长分配等待时间
    spring.redis.lettuce.pool.max-wait=2000
    #回收线程间隔毫秒数
    spring.redis.lettuce.pool.time-between-eviction-runs=100
    

    注解操作redis

    配置CacheManager
    spring.redis.cache.type=redis
    spring.redis.cache.name=redisCache

    通过注解@EnableCaching启用;
    @CachePut 更新缓存
    @CacheEvicat 清除缓存
    @CacheAble 使用查询缓存

    缓存在一个类中互相调用失效 : 基于AOP的动态代理,没有生成代理类;

    package com.springbootpractice.demo.redis.config;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.data.redis.cache.RedisCacheConfiguration;
    import org.springframework.data.redis.cache.RedisCacheManager;
    import org.springframework.data.redis.cache.RedisCacheWriter;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
    import org.springframework.data.redis.serializer.RedisSerializationContext;
    
    import java.time.Duration;
    
    /**
     * 说明:代码方式配置缓存管理器
     * @author carter
     * 创建时间: 2020年01月21日 7:00 下午
     **/
    @Configuration
    public class RedisConfig {
    
        @Autowired private RedisTemplate redisTemplate;
    
    
        @Bean
        public RedisCacheManager redisCacheManager(){
            RedisCacheWriter redisWrite = RedisCacheWriter.lockingRedisCacheWriter(redisTemplate.getConnectionFactory());
    
            RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig();
    
            configuration.prefixKeysWith("_demo_redis_");
            configuration.entryTtl(Duration.ofMinutes(10));
            configuration.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new JdkSerializationRedisSerializer()));
    
            RedisCacheManager redisCacheManager = new RedisCacheManager(redisWrite,configuration);
    
            return redisCacheManager;
        }
    }
    
    

    用法

    package com.springbootpractice.demo.redis.biz;
    
    import com.springbootpractice.demo.redis.dao.entity.UserLoginExtEntity;
    import com.springbootpractice.demo.redis.dao.mapper.UserLoginExtEntityMapper;
    import org.springframework.cache.annotation.CacheEvict;
    import org.springframework.cache.annotation.CachePut;
    import org.springframework.cache.annotation.Cacheable;
    import org.springframework.stereotype.Service;
    
    /**
     * 说明:操作user的数据增强层
     * @author carter
     * 创建时间: 2020年01月21日 6:40 下午
     **/
    @Service
    public class UserLoginExtBiz {
    
        private final UserLoginExtEntityMapper userLoginExtEntityMapper;
    
        public UserLoginExtBiz(UserLoginExtEntityMapper userLoginExtEntityMapper) {
            this.userLoginExtEntityMapper = userLoginExtEntityMapper;
        }
    
        @Cacheable(value = "redisCache",key = "'getById:'+#id")
        public UserLoginExtEntity getById(Integer id){
            return userLoginExtEntityMapper.selectByPrimaryKey(id);
        }
    
        @CachePut(value = "redisCache",key = "'getById:'+#param.id")
        public UserLoginExtEntity updateUserLoginExt(UserLoginExtEntity param){
            userLoginExtEntityMapper.updateByPrimaryKeySelective(param);
            return param;
        }
    
        @CacheEvict(value = "redisCache",key = "'getById:'+#id")
        public int deleteUserLoginExt(Integer id){
           return userLoginExtEntityMapper.logicalDeleteByPrimaryKey(id);
        }
    
    }
    
    

    redis的特殊用法

    redis中事务的用法

    利用的是SessionCallback的RedisOperations 的 watch-multi-exec 连环操作;
    watch: 监控某些key;
    multi:开始事务;
    exec: 执行事务
    如果watch的key对应的值发生变化(设置为原值也是发生了变化),则会回滚事务;否则,正常的执行事务 ;
    redis在执行事务的时候,要么全部执行,要么全部失败,不会被其它的redis客户端打断,保证了redis事务下数据的一致性;

     @Test
        void transactionTest() {
            final String ttk1 = "ttk1";
            stringRedisTemplate.opsForValue().set(ttk1,"ttk1v1");
            final List list = stringRedisTemplate.execute(new SessionCallback<List>() {
                @Override
                public List execute(RedisOperations operations) throws DataAccessException {
                    System.out.println("监听"+ttk1);
                    //如果ttk1的值发生了变化,重新set一样的值也是发生了变化,则回滚事务,否则正常执行
                    operations.watch(ttk1);
    
                    //开启事务
                    System.out.println("开启事务");
                    operations.multi();
                    operations.opsForList().leftPushAll("xxx_lk1", "v1", "v2", "v3");
                    final List xxx_lk1 = operations.opsForList().range("xxx_lk1", 0, 2);
                    System.out.println(xxx_lk1);
    
                    operations.opsForSet().add("xxx_sk1", "v1", "v2", "v3");
                    final Set xxx_sk1 = operations.opsForSet().members("xxx_sk1");
                    System.out.println(xxx_sk1);
                    //提交事务
                    final List list = operations.exec();
                    System.out.println("提交事务");
                    return list;
                }
            });
    
            System.out.println("执行结果:"+list);
    
    
        }
    

    批量执行redis操作

    redisTemplate.executePipelined();

     @Test
        void pipelineTest() {
    
            StopWatch stopWatch = new StopWatch("pipelineTest");
            stopWatch.start();
            final List<Object> objectList = stringRedisTemplate.executePipelined(new SessionCallback<Object>() {
                @Override
                public Object execute(RedisOperations operations) throws DataAccessException {
    
                    for (int i = 1; i <= 10000; i++) {
                        operations.opsForValue().set("pk" + i, "pkv" + i, 5, TimeUnit.MINUTES);
                    }
    
                    return null;
                }
            });
    
            stopWatch.stop();
            System.out.println(stopWatch.prettyPrint());
        }
    

    消息队列

    需要定义一个一个RedisMessageListenerContainer,配置topic和监听器; 作为消费者;
    通过redisTemplate.convertAndSend方法发送消息;

    定义监听器

    package com.springbootpractice.demo.redis.listener;
    
    import org.springframework.data.redis.connection.Message;
    import org.springframework.stereotype.Component;
    
    /**
     * 说明:redis的监听器
     * @author carter
     * 创建时间: 2020年01月21日 5:51 下午
     **/
    @Component
    public class MyRedisMessageListener implements org.springframework.data.redis.connection.MessageListener {
        
        @Override
        public void onMessage(Message message, byte[] pattern) {
    
            System.out.println("MyRedisMessageListener topic:"+new String(pattern) +" 消息:"+ new String(message.getBody()));
    
        }
    }
    
    

    注册监听器容器

    package com.springbootpractice.demo.redis.listener;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.data.redis.listener.ChannelTopic;
    import org.springframework.data.redis.listener.RedisMessageListenerContainer;
    import org.springframework.data.redis.listener.Topic;
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.LinkedBlockingDeque;
    import java.util.concurrent.ThreadPoolExecutor;
    import java.util.concurrent.TimeUnit;
    
    /**
     * 说明:配置队列监听器,对应的主题
     * @author carter
     * 创建时间: 2020年01月21日 5:55 下午
     **/
    @Configuration
    public class RedisListenerConfig {
    
        public static final String MY_CHANNEL = "myChannel";
        private final MyRedisMessageListener myRedisMessageListener;
        private final MyRedisMessageListener2 myRedisMessageListener2;
        private final RedisTemplate redisTemplate;
    
        public RedisListenerConfig(MyRedisMessageListener myRedisMessageListener, MyRedisMessageListener2 myRedisMessageListener2, RedisTemplate redisTemplate) {
            this.myRedisMessageListener = myRedisMessageListener;
            this.myRedisMessageListener2 = myRedisMessageListener2;
            this.redisTemplate = redisTemplate;
        }
    
        @Bean
        public RedisMessageListenerContainer redisMessageListenerContainer() {
            RedisMessageListenerContainer redisMessageListenerContainer = new RedisMessageListenerContainer();
    
            redisMessageListenerContainer.setConnectionFactory(redisTemplate.getConnectionFactory());
            final ExecutorService taskExecutor = new ThreadPoolExecutor(1,
                    2, 30, TimeUnit.SECONDS, new LinkedBlockingDeque<>(2000));
            redisMessageListenerContainer.setTaskExecutor(taskExecutor);
    
            final Topic myChannel = new ChannelTopic(MY_CHANNEL);
    
            redisMessageListenerContainer.addMessageListener(myRedisMessageListener, myChannel);
            redisMessageListenerContainer.addMessageListener(myRedisMessageListener2, myChannel);
    
            System.out.println("注册redis的消息队列成功!");
            return redisMessageListenerContainer;
        }
    
    }
    

    测试代码

    1. publish myChannel helloworld
    2. redisTemplate.convertAndSend(channel,message);
    package com.springbootpractice.demo.redis.web;
    
    import com.springbootpractice.demo.redis.listener.RedisListenerConfig;
    import org.springframework.data.redis.core.StringRedisTemplate;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RestController;
    
    /**
     * 说明:TODO
     * @author carter
     * 创建时间: 2020年01月21日 6:22 下午
     **/
    @RestController
    public class TestController {
    
        private final StringRedisTemplate stringRedisTemplate;
    
        public TestController(StringRedisTemplate stringRedisTemplate) {
            this.stringRedisTemplate = stringRedisTemplate;
        }
    
        @GetMapping(path = "/send/{message}")
        public void publishMessage(@PathVariable("message") String message){
            stringRedisTemplate.convertAndSend(RedisListenerConfig.MY_CHANNEL,message);
        }
    }
    
    

    使用lua脚本

    使用的redisTemplate.execute(RedisScript,List,List);

    @GetMapping(path = "/lua/{k1}/{v1}/{k2}/{v2}")
        public Long publishMessage(@PathVariable("k1") String k1,@PathVariable("k2") String k2,@PathVariable("v1") String v1,@PathVariable("v2") String v2){
            DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
            redisScript.setScriptText(LuaScript.lua1);
            redisScript.setResultType(Long.class);
    
            return stringRedisTemplate.execute(redisScript, Arrays.asList(k1, k2), v1, v2);
        }
    

    小结

    通过本篇文章,你可以学会:

    1. 学会使用spring-boot-redis-starter熟练的进行各种数据类型的操作;
    2. 学会了使用注解的方式使用redis缓存;
    3. redis的特殊用法,事务,消息队列,批量操作,lua脚本支持;

    代码点我获取!

    美女还是要给看的。<br /> image.png

    原创不易,转载请注明出处。

    相关文章

      网友评论

        本文标题:0121 spring-boot-redis的使用

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