6. SpringBoot整合Redis

女生的网名这么多〃 提交于 2020-01-26 00:05:39

6. SpringBoot整合Redis

Redis是NoSQL类型的数据库,我们也常称为内存型数据库类型.在SpringBoot中使用Redis非常简单.

6.1 添加包依赖

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

添加依赖后其实就直接可以使用redis了(SpringBoot提供了默认配置,也可以自己配置)

6.2 配置

spring.redis.host=localhost
spring.redis.port=6379
spring.redis.timeout=1000
spring.redis.lettuce.pool.max-active=10
spring.redis.lettuce.pool.max-idle=10
spring.redis.lettuce.pool.min-idle=5
spring.redis.lettuce.pool.max-wait=1000

6.3 编写Controller测试

@Controller
@RequestMapping("/redis")
public class RedisController {
    
    @Autowired
    private RedisTemplate redisTemplate;

    @RequestMapping("/set")
    @ResponseBody
    public String redisStringSet(String key, String value) {
        ValueOperations ops = redisTemplate.opsForValue();
        ops.set(key, value);
        return "OK";
    }
}

当执行了上面的代码后,使用Redis客户端可查看到redis中多出了一个记录.但是该记录是编码后的,不方面我们查看.这就需要我们进行相应的配置了.让redis直接以字符串的方式显示我们插入的内容.

@Configuration
public class RedisTemplateConfig {

    @Bean
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        // 自定义的string序列化器和fastjson序列化器
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        // jackson 序列化器
        GenericJackson2JsonRedisSerializer jsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
        // fastjson 序列化器
//        GenericFastJsonRedisSerializer jsonRedisSerializer = new GenericFastJsonRedisSerializer();
        // kv 序列化
        redisTemplate.setKeySerializer(stringRedisSerializer);
        redisTemplate.setValueSerializer(jsonRedisSerializer);
        // hash 序列化
        redisTemplate.setHashKeySerializer(stringRedisSerializer);
        redisTemplate.setHashValueSerializer(jsonRedisSerializer);
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
}

RedisTemplate是我们操作数据库的类,Spring也提供了StringRedisTemplate和HashKeyRedisTemplate分别用于处理内容序列化显示的问题。添加完上面的配置代码后,重新执行redis插入,就可以看到redis显示了真实的字符串内容。

Redis提供7种基本数据类型,可通过RedisTemplate提供的方法执行。

/获取地理位置操作接口
redisTemplate.opsForGeo();
/获取散列操作接口
redisTemplate.opsForHash();
//获取基数操作接口
redisTemplate.opsForHyperLogLog();
/获取列表操作接口
redisTemplate.opsForlist();
/获取集合操作接口
redisTemplate.opsForset();
//获取字符串操作接口
redisTemplate.opsForvalue();
//获取有序集合操作接口
redisTemplate.psForzset();

一个Redis连接中只执行一个redis命令。Redis也提供了方法让我们可以在一个连接中执行多个命令。就是SessionCallback和RedisCallback接口。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RllYlt4s-1578452712845)(C:\Users\mxlei\AppData\Roaming\Typora\typora-user-images\image-20200107151716183.png)]

Redis的各种数据类型的使用这里不做介绍。

6.4 Redis高级用法

6.4.1 redis事务

 @RequestMapping("/transaction")
    @ResponseBody
    public List redisTransaction() {
        //key1设置为字符串类型
        redisTemplate.opsForValue().set("key1", "value1");
        //Redis事务
        List list = (List) redisTemplate.execute(new SessionCallback() {
            @Override
            public Object execute(RedisOperations operations) throws DataAccessException {
                //设置要监控的KEY
                operations.watch("key1");
                //开启事务,在exec命令执行之前,全部都知识加入队列
                operations.multi();
                operations.opsForValue().set("key2", "value2");
//                operations.opsForValue().increment("key1",1); //①
                //取得的值为null,因为redis只是把命令加入队列
                Object value2 = operations.opsForValue().get("key2");
                System.out.println("取得的值为null,因为redis只是把命令加入队列 value2 = " + value2);
                operations.opsForValue().set("key3", "value3");
                Object value3 = operations.opsForValue().get("key3");
                System.out.println("取得的值为null,因为redis只是把命令加入队列 value3 = " + value3);
                //执行exec命令,将判断key1是否在监控后被修改过,如果是则不执行事务,否则就执行事务
                return operations.exec();  //②
            }
        });
        System.out.println(list);
        return list;
    }

  为了揭示 Redis事务的特性,我们对这段代码做以下两种测试先在 Redis客户端清空key2和key3两个键的数据,然后在②处设置断点,在调试的环境下让请求达到断点,此时在 Redis上修改keyl的值,然后再跳过断点,在请求完成后在 Redis上查询key2和key3值,可以发现key2、key3返回的值都为空(ni),因为程序中先使得 Redis的 watch命令监控了keyl的值,而后的 multi让之后的命令进入队列,而在exec方法运行前我们修改了keyl,根据 Redis事务的规则,它在exec方法后会探测keyl是否被修改过,如果没有则会执行事务,否则就取消事务,所以key2和key3没有被保存到Redis服务器中迷续把key2和key3两个值清空,把①处的注释取消,让代码可以运行,因为keyl是一个字符串,所以这里的代码是对字符串加一,这显然是不能运算的。同样地,我们运行这段代码后,可以看到服务器抛出了异常,然后我们去 Redis服务器查询key2和key3,可以看到它们已经有了值。注意,这就是 Redis事务和数据库事务的不一样,对于 Redis事务是先让命令进入队列,所以一开始它并没有检测这个加一命令是否能够成功,只有在exec命令执行的时候,才能发现错误,对于出错的命令 Redis只是报出错误,而错误后面的命令依旧被执行,所以key2和key3都存在数据,这就是 Redis事务的特点,也是使用Reds事务需要特别注意的地方。为了克服这个问题,一般我们要在执行 Redis事务前,严格地检查数据,以避免这样的情况发生。

6.4.2 Redis流水线

  在默认的情况下, Redis客户端是一条条命令发送给 Redis服务器的,这样显然性能不高。在关系数据库中我们可以使用批量,也就是只有需要执行SQL时,才一次性地发送所有的SQL去执行,这样性能就提高了许多。对于 Redis也是可以的,这便是流水线( pipline)技术,在很多情况下并不是 Redis性能不佳,而是网络传输的速度造成瓶颈,使用流水线后就可以大幅度地在需要执行很多命令时提升 Redis的性能。

  下面使用Redis流水线技术测试10万次读写功能

  @RequestMapping("/pipeline")
    @ResponseBody
    public Long pipeline(){
        long start = System.currentTimeMillis();
        List list = (List)redisTemplate.executePipelined(new SessionCallback() {
            @Override
            public Object execute(RedisOperations redisOperations) throws DataAccessException {
                for(int i=0;i<=100000;i++){
                    redisOperations.opsForValue().set("pipeline_"+i,"value_"+i);
                    String value = (String) redisOperations.opsForValue().get("pipeline_"+i);
                    if(i == 100000){
                        System.out.println("命令只是进入队列,所有值为空 = "+value);
                    }
                }
                return null;
            }
        });
        long end = System.currentTimeMillis();
        long cost = end - start;
        System.out.println("耗时:"+cost+" 毫秒");
        return cost;
    }

  为了测试性能,这里记录了开始执行时间和结束执行时间,并且打出了耗时。在我的测试中,这10万次读写基本在300600ms,大约平均值在400500ms,也就是不到s就能执行10万次读和写命令,这个速度还是十分快的。在使用非流水线的情况下,我的测试大约每秒只能执行2万~3万条命令,可见使用流水线后可以提升大约10倍的速度,它十分适合大数据量的执行。

6.4.3 Redis发布订阅

  发布订阅是消息的一种常用模式。例如,在企业分配任务之后,可以通过邮件、短信或者微信通知到相关的责任人,这就是一种典型的发布订阅模式。首先是 Redis提供一个渠道,让消息能够发送到这个渠道上,而多个系统可以监听这个渠道,如短信、微信和邮件系统都可以监听这个渠道,当一条消息发送到渠道,渠道就会通知它的监听者,这样短信、微信和邮件系统就能够得到这个渠道给它们的消息了,这些监听者会根据自己的需要去处理这个消息,于是我们就可以得到各种各样的通知了。

UTOOLS1578447716093.png

为了接收 Redis渠道发送过来的消息,我们先定义一个消息监听器( Messagelistener)

@Configuration
public class RedisMessageListenerConfig {

    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private RedisConnectionFactory connectionFactory;
    @Autowired
    private RedisMessageListener redisMessageListener;
    private ThreadPoolTaskExecutor taskExecutor;

    @Bean
    public RedisMessageListenerContainer redisMessageListenerContainer(){
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        container.setTaskExecutor(threadPoolTaskExecutor());
        Topic topic = new ChannelTopic("topic1");
        container.addMessageListener(redisMessageListener, topic);
        return container;
    }

    @Bean
    public ThreadPoolTaskExecutor threadPoolTaskExecutor(){
        if(taskExecutor != null){
            return taskExecutor;
        }
        taskExecutor = new ThreadPoolTaskExecutor();
        taskExecutor.setCorePoolSize(5);
        return taskExecutor;
    }
}

这里 RedisTemplate和 RedisConnection Factory对象都是 Spring Boot自动创建的,所以这里只是把它们注入进来,只需要使用@ Autowired注解即可。然后定义了一个任务池,并设置了任务池大小为20,这样它将可以运行线程,并进行阻塞,等待 Redis消息的传入。接着再定义了一个 Redis消息监听的容器 RedisMessageListenerContainer,并且往容器设置了 Redis连接工厂和指定运行消息的线程池,定义了接收“ topicI”渠道的消息,这样系统就可以监听 Redis关于“ topic1”渠道的消息了。启用 Spring Boot项目后,在 Redis的客户端输入命令:

publish topic msg

在 Spring中,我们也可以使用 Redis Template来发送消息,例如:

redisTemplate.convertAndSend(channel, message);

其中, channel代表渠道, message代表消息,这样就能够得到 Redis发送过来的消息了。

6.4.4 使用Lua脚本

Redis中有很多的命令,但是严格来说 Redis提供的计算能力还是比较有限的。为了增强 Redis的计算能力, Redis在2.6版本后提供了Lua脚本的支持,而且执行Lua脚本在 Redis中还具备原子性,所以在需要保证数据一致性的高并发环境中,我们也可以使用 Redis的Lua语言来保证数据的致性,且Lua脚本具备更加强大的运算功能,在高并发需要保证数据一致性时,Lua脚本方案比使用Redis自身提供的事务要更好一些。

在 Redis中有两种运行Lua的方法,一种是直接发送Lua到 Redis服务器去执行,另一种是先把Lua发送给 Redis, Redis会对Lua脚本进行缓存,然后返回一个SHA1的32位编码回来,之后只需要发送SHA1和相关参数给 Redis便可以执行了。这里需要解释的是为什么会存在通过32位编码执行的方法。如果Lua脚本很长,那么就需要通过网络传递脚本给 Redis去执行了,而现实的情况是网络的传递速度往往跟不上 Redis的执行速度,所以网络就会成为 Redis执行的瓶颈。如果只是传递32位编码和参数,那么需要传递的消息就少了许多,这样就可以极大地减少网络传输的内容,从而提供系统性能。

为了支持 Redis的Lua脚本, Spring提供了 Redis Script接口,与此同时也有一个 DefaultRedis Script
实现类。让我们先来看看 Redis script接口的源码

public interface RedisScript<T> {
    //获取脚本的SHA1
    String getSha1();
	//获取脚本的返回值类型
    @Nullable
    Class<T> getResultType();
	//获取脚本的字符串内容
    String getScriptAsString();
}

这里 Spring会将Lua脚本发送到 Redis服务器进行缓存,而此时 Redis服务器会返回一个32位的SHA编码,这时候通过 geiSha方法就可以得到 Redis返回的这个编码了; getResult Type方法是获取Lua脚本返回的Java类型; getScriptAs String是返回脚本的字符串,以便我们观看脚本。下面我们采用 Redis Script接口执行一个十分简单的Lua脚本,这个脚本只是简单地返回一个字符串

UTOOLS1578449579189.png

这里的代码,首先Lua只是定义了一个简单的字符串,然后就返回了,而返回类型则定义为字符串。这里必须定义返回类型,否则对于 Spring不会把脚本执行的结果返回。接着获取了由RedisTemplate自动创建的字符串序列化器,而后使用 Redis Template的 execute方法执行了脚本。在Redis Template中, execute方法执行脚本的方法有两种,其定义如下:

UTOOLS1578449643469.png

6.5 使用Spring缓存注解操作Redis

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!