SpringBoot2.0整合Redis

SpringBoot2.0整合Redis

相关知识

Redis 简介

Redis 是一个开源的,基于内存中的,高性能的数据存储系统,它可以用作数据库、缓存和消息中间件。 Redis 支持多种类型的数据结构,如:string、hashes、lists、sets、sortedSets等。 Redis 内置了复制(replication)、LUA脚本(Lua scripting)、事务(transactions)、磁盘持久化(persistence)、LRU驱动事件(LRU eviction)等功能。 Redis 可以通过哨兵(Sentinel)以及集群(Cluster)提供高可用性

Lettuce 和 Jedis

Lettuce 和 Jedis 都是连接 Redis Server 的客户端程序, SpringBoot2.x 之前默认使用 Jedis 作为与 Redis 进行交互的组件,SpringBoot2.x 则换成了 Lettuce(生菜)。 Jedis 在实现上是直连 redis server,多线程环境下非线程安全,除非使用连接池,为每个 Jedis 实例增加物理连接。 Lettuce 基于 Netty 的连接实例(StatefulRedisConnection),可以在多个线程间并发访问,且线程安全,满足多线程环境下的并发访问, 同时它是可伸缩的设计,一个连接实例不够的情况也可以按需增加连接实例。

安装 Redis

这里采用docker-compose方式安装、启动

注意目录结构

1、创建docker-compose.yml

docker-compose.yml

# docker-compose.yml文件的版本
version: "3"
# 管理的服务
services:
  redis:
    # 指定镜像
    image: redis:4
    container_name: redis
    ports:
      # 端口映射
      - 6379:6379
    volumes:
      # 目录映射
      - ./conf:/usr/local/etc/redis
      - ./data:/data
    command:
      # 执行的命令
      redis-server /usr/local/etc/redis/redis.conf

2、更改redis.conf

vim redis.conf

# 保护模式 关闭保护模式 否则其他机器无法连接到redis
protected-mode no

# 如需要在后台运行,把该项的值改为yes 
# 改成yes时,在docker中无法启动,最后发现,将配置文件redis.conf中的“daemonize yes”这一行注释
# 掉 或者改为no,redis就顺利启动
# 了。

daemonize no

#bind:指定redis只接收来自该IP的请求,如果不设置,那么将处理所有请求,在生产环节中最好设置该项 如果需要对其他机器开放 需要注销 #bind 127.0.0.1
# bind 127.0.0.1

# requirepass:设置客户端连接后进行任何其他指定前需要使用的密码
requirepass foobared
package com.foo.bar.redis;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.foo.bar.utils.JsonUtil;
import com.google.common.collect.Lists;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.StringRedisTemplate;

import java.io.Serializable;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;

/**
 *
 * @author foobar
 * @version V1.0 2020/8/27 12:58 上午
 */
@DisplayName("Redis测试类")
@Slf4j
@SpringBootTest
class RedisJsonStrTest {

    private static final String TEST = "TEST:JSON:";
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 测试 Redis 操作
     */
    // @DisplayName("测试Redis线程安全")
    // @Test
    void testRedisOps() throws InterruptedException {
        // 测试线程安全,程序结束查看redis中count的值是否为1000
        final String key = TEST + "count";
        stringRedisTemplate.delete(key);
        ExecutorService executorService = Executors.newFixedThreadPool(1000);
        IntStream.range(0, 1000)
            .forEach(i -> executorService.execute(() -> stringRedisTemplate.opsForValue().increment(key, 1)));
        TimeUnit.SECONDS.sleep(5L);
        Assertions.assertEquals("1000", stringRedisTemplate.opsForValue().get(key));
    }

    @DisplayName("测试String对象序列化与反序列化")
    @Test
    void testString() {
        final String expectValue = "v1";
        final String key = TEST + "string-key";
        stringRedisTemplate.opsForValue().set(key, expectValue);
        String actualValue = stringRedisTemplate.opsForValue().get(key);
        log.info("【key】= {}", actualValue);
        Assertions.assertEquals(expectValue, actualValue);
    }

    @DisplayName("stringRedisTemplate:String对象转义问题")
    @Test
    void testStringEscape() {
        String expect = "string-escape";
        final String key = TEST + "string-escape";
        stringRedisTemplate.opsForValue().set(key, expect);
        String actual = stringRedisTemplate.opsForValue().get(key);
        // 手工插入的非json数据 采用GenericJackson2JsonRedisSerializer时,序列化失败
        String serverInsert = stringRedisTemplate.opsForValue().get(TEST + "manualInsertValue");
        log.info("serverInsert:{}", serverInsert);
        Assertions.assertEquals(expect, actual);
    }

    @DisplayName("stringRedisTemplate:String对象转义问题")
    @Test
    void testStringEscape2() {
        String expect = "string-escape";
        final String key = TEST + "string-escape2";
        stringRedisTemplate.opsForValue().set(key, expect);
        String actual = (String)stringRedisTemplate.opsForValue().get(key);
        // 手工插入的非json数据 采用GenericJackson2JsonRedisSerializer时,序列化失败
        String serverInsert = (String)stringRedisTemplate.opsForValue().get(TEST + "manualInsertValue");
        log.info("serverInsert:{}", serverInsert);
        Assertions.assertEquals(expect, actual);
    }

    /**
     * redis Long类型转化异常
     */
    @DisplayName("测试Long-Integer对象转换异常问题")
    @Test
    void testLong() {
        final Long expectValue = 1L;
        Long.valueOf(stringRedisTemplate.opsForValue().get(TEST + "long-key"));

        stringRedisTemplate.opsForValue().set(TEST + "long-key", String.valueOf(expectValue));
        Long actualValue = Long.valueOf(stringRedisTemplate.opsForValue().get(TEST + "long-key"));
        log.info("【key】= {}", actualValue);
        Assertions.assertEquals(expectValue, actualValue);
    }

    @DisplayName("测试Java对象序列化与反序列化")
    @Test
    void testJavaObject() {
        // 以下演示整合,具体Redis命令可以参考官方文档
        String key = TEST + "user:1";
        final User expectUser = new User();
        expectUser.setId(1L);
        expectUser.setName("张三");
        expectUser.setActiveTime(new Date());
        stringRedisTemplate.opsForValue().set(key, JsonUtil.toJsonString(expectUser));
        // 反序列化丢失了毫秒 {"id":1,"name":"张三","activeTime":"2020-08-28 18:01:57"}
        // 默认JsonUtil配置时间序列化格式为yyyy-MM-dd HH:mm:ss,对有毫秒需求的时间可以加上注解 @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss,SSS", timezone = "GMT+8")
        User actualUser = JsonUtil.toBean(stringRedisTemplate.opsForValue().get(key), User.class);
        log.info("【user】= {}", JsonUtil.toJsonString(actualUser));
        Assertions.assertEquals(expectUser, actualUser);
    }

    @DisplayName("测试List<JavaBean>序列化与反序列化")
    @Test
    void testListObj() {
        // 以下演示整合,具体Redis命令可以参考官方文档
        String key = TEST + "LIST";
        final User user = new User();
        user.setId(1L);
        user.setName("张三");
        user.setActiveTime(new Date());
        final List<User> expect = Lists.newArrayList(user);
        stringRedisTemplate.opsForValue().set(key, JsonUtil.toJsonString(expect));
        // 对应 String(字符串)
        List<User> actual = JsonUtil.toList(stringRedisTemplate.opsForValue().get(key), User.class);
        log.info("【user】= {}", actual);
        Assertions.assertEquals(expect, actual);
    }

    @DisplayName("测试List<String>序列化与反序列化")
    @Test
    void testListString() {
        // 以下演示整合,具体Redis命令可以参考官方文档
        String key = TEST + "LIST-string";
        final List<String> expect = Lists.newArrayList("user");
        stringRedisTemplate.opsForValue().set(key, JsonUtil.toJsonString(expect));
        // 对应 String(字符串)
        List<String> actual = JsonUtil.toList(stringRedisTemplate.opsForValue().get(key), String.class);
        log.info("【user】= {}", actual);
        Assertions.assertEquals(expect, actual);
    }

    @DisplayName("测试List<Long>序列化与反序列化")
    @Test
    void testListLong() {
        // 以下演示整合,具体Redis命令可以参考官方文档
        String key = TEST + "LIST-long";
        final List<Long> expect = Lists.newArrayList(1L);
        stringRedisTemplate.opsForValue().set(key, JsonUtil.toJsonString(expect));
        // 对应 String(字符串)
        List<Long> actual = JsonUtil.toList(stringRedisTemplate.opsForValue().get(key), Long.class);
        log.info("【user】= {}", actual);
        Assertions.assertEquals(expect, actual);
    }

    @DisplayName("测试OpsForList-Long序列化与反序列化")
    @Test
    void testOpsForListLong() {
        // 以下演示整合,具体Redis命令可以参考官方文档
        String key = TEST + "OPSLIST-long";

        stringRedisTemplate.opsForList().leftPush(key, "1");
        stringRedisTemplate.opsForList().leftPush(key, "2");
        // 对应 String(字符串)
        Long actual = Long.parseLong(stringRedisTemplate.opsForList().leftPop(key));
        Assertions.assertEquals(Long.valueOf("2"), actual);
        Long actual2 = Long.valueOf(stringRedisTemplate.opsForList().leftPop(key));
        Assertions.assertEquals(Long.valueOf("1"), actual2);
    }

    @DisplayName("测试OpsForList-String序列化与反序列化")
    @Test
    void testOpsForListString() {
        // 以下演示整合,具体Redis命令可以参考官方文档
        String key = TEST + "OPSLIST-string";
        stringRedisTemplate.delete(key);
        stringRedisTemplate.opsForList().leftPush(key, "1");
        stringRedisTemplate.opsForList().leftPush(key, "2");
        stringRedisTemplate.opsForList().leftPush(key, "3");
        stringRedisTemplate.opsForList().leftPush(key, "4");
        // 对应 String(字符串)
        String actual = stringRedisTemplate.opsForList().leftPop(key);
        Assertions.assertEquals("4", actual);
        String actual2 = stringRedisTemplate.opsForList().leftPop(key);
        Assertions.assertEquals("3", actual2);
        List<String> actual3 = stringRedisTemplate.opsForList().range(key, 0L, 1L);
        Assertions.assertEquals(Lists.newArrayList("2", "1"), actual3);

    }

    @Data
    @NoArgsConstructor
    private static class User implements Serializable {
        private static final long serialVersionUID = 2892248514883451461L;
        /**
         * 主键id
         */
        private Long id;

        /**
         * 姓名
         */
        private String name;

        /**
         * 激活时间
         */
        @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss,SSS", timezone = "GMT+8")
        private Date activeTime;
    }
}
package com.foo.bar.redis;

import com.google.common.collect.Lists;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.util.NumberUtils;

import java.io.Serializable;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;

/**
 *
 * @author foobar
 * @version V1.0
 * 2020/8/27 12:58 上午
 */
@DisplayName("Redis测试类")
@Slf4j
@SpringBootTest
class RedisTest  {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 测试 Redis 操作
     */
//    @DisplayName("测试Redis线程安全")
//    @Test
    void testRedisOps() throws InterruptedException {
        // 测试线程安全,程序结束查看redis中count的值是否为1000
        final String key = "TEST:count";
        stringRedisTemplate.delete(key);
        ExecutorService executorService = Executors.newFixedThreadPool(1000);
        IntStream.range(0, 1000).forEach(i -> executorService.execute(() -> stringRedisTemplate.opsForValue().increment(key, 1)));
        TimeUnit.SECONDS.sleep(5L);
        Assertions.assertEquals("1000", stringRedisTemplate.opsForValue().get(key));
    }

    @DisplayName("测试String对象序列化与反序列化")
    @Test
    void testString(){
        final String expectValue = "v1";
        final String key = "TEST:string-key";
        stringRedisTemplate.opsForValue().set(key, expectValue);
        String actualValue = stringRedisTemplate.opsForValue().get(key);
        log.info("【key】= {}", actualValue);
        Assertions.assertEquals(expectValue, actualValue);
    }

    @DisplayName("stringRedisTemplate:String对象转义问题")
    @Test
    void testStringEscape() {
        String expect = "string-escape";
        final String key = "TEST:string-escape";
        stringRedisTemplate.opsForValue().set(key, expect);
        String actual = stringRedisTemplate.opsForValue().get(key);
        // 手工插入的非json数据 采用GenericJackson2JsonRedisSerializer时,序列化失败
        String serverInsert = stringRedisTemplate.opsForValue().get("TEST:manualInsertValue");
        log.info("serverInsert:{}", serverInsert);
        Assertions.assertEquals(expect,actual);
    }

    @DisplayName("redisTemplate:String对象转义问题")
    @Test
    void testStringEscape2() {
        String expect = "string-escape";
        final String key = "TEST:string-escape2";
        redisTemplate.opsForValue().set(key, expect);
        String actual = (String) redisTemplate.opsForValue().get(key);
        // 手工插入的非json数据 采用GenericJackson2JsonRedisSerializer时,序列化失败
        String serverInsert = (String) redisTemplate.opsForValue().get("TEST:manualInsertValue");
        log.info("serverInsert:{}", serverInsert);
        Assertions.assertEquals(expect,actual);
    }

    /**
     * redis Long类型转化异常
     */
    @DisplayName("测试Long-Integer对象转换异常问题")
    @Test
    void testLong(){
        final Long expectValue = 1L;
        redisTemplate.opsForValue().set("TEST:long-key", expectValue);
        Long actualValue = NumberUtils.convertNumberToTargetClass((Number) redisTemplate.opsForValue().get("TEST:long-key"), Long.class);
        log.info("【key】= {}", actualValue);
        Assertions.assertEquals(expectValue, actualValue);
    }
    
    @DisplayName("测试Java对象序列化与反序列化")
    @Test
    void testJavaObject(){
        // 以下演示整合,具体Redis命令可以参考官方文档
        String key = "TEST:user:1";
        final User expectUser = new User();
        expectUser.setId(1L);
        expectUser.setName("张三");
        expectUser.setActiveTime(new Date());
        redisTemplate.opsForValue().set(key, expectUser);
        // 对应 String(字符串)
        User actualUser = (User) redisTemplate.opsForValue().get(key);
        log.info("【user】= {}", actualUser);
        Assertions.assertEquals(expectUser, actualUser);
    }

    @DisplayName("测试List<JavaBean>序列化与反序列化")
    @Test
    void testListObj(){
        // 以下演示整合,具体Redis命令可以参考官方文档
        String key = "TEST:LIST";
        final User user = new User();
        user.setId(1L);
        user.setName("张三");
        user.setActiveTime(new Date());
        final List<User> expect = Lists.newArrayList(user);
        redisTemplate.opsForValue().set(key, expect);
        // 对应 String(字符串)
        List<User> actual = (List<User>) redisTemplate.opsForValue().get(key);
        log.info("【user】= {}", actual);
        Assertions.assertEquals(expect, actual);
    }

    @DisplayName("测试List<String>序列化与反序列化")
    @Test
    void testListString(){
        // 以下演示整合,具体Redis命令可以参考官方文档
        String key = "TEST:LIST-string";
        final List<String> expect = Lists.newArrayList("user");
        redisTemplate.opsForValue().set(key, expect);
        // 对应 String(字符串)
        List<String> actual = (List<String>) redisTemplate.opsForValue().get(key);
        log.info("【user】= {}", actual);
        Assertions.assertEquals(expect, actual);
    }

    @DisplayName("测试List<Long>序列化与反序列化")
    @Test
    void testListLong(){
        // 以下演示整合,具体Redis命令可以参考官方文档
        String key = "TEST:LIST-long";
        final List<Long> expect = Lists.newArrayList(1L);
        redisTemplate.opsForValue().set(key, expect);
        // 对应 String(字符串)
        List<Long> actual = (List<Long>) redisTemplate.opsForValue().get(key);
        log.info("【user】= {}", actual);
        Assertions.assertEquals(expect, actual);
    }

    @DisplayName("测试OpsForList-Long序列化与反序列化")
    @Test
    void testOpsForListLong(){
        // 以下演示整合,具体Redis命令可以参考官方文档
        String key = "TEST:OPSLIST-long";

        redisTemplate.opsForList().leftPush(key, 1L);
        redisTemplate.opsForList().leftPush(key, 2L);
        // 对应 String(字符串)
        Long actual = NumberUtils.convertNumberToTargetClass((Number)redisTemplate.opsForList().leftPop(key), Long.class);
        Assertions.assertEquals(Long.valueOf("2"), actual);
        Long actual2 = NumberUtils.convertNumberToTargetClass((Number)redisTemplate.opsForList().leftPop(key), Long.class);
        Assertions.assertEquals(Long.valueOf("1"), actual2);
        Long actual3 = NumberUtils.convertNumberToTargetClass((Number)redisTemplate.opsForList().leftPop(key), Long.class);
        log.info("actual3:{}",actual3);

    }

    @DisplayName("测试OpsForList-String序列化与反序列化")
    @Test
    void testOpsForListString(){
        // 以下演示整合,具体Redis命令可以参考官方文档
        String key = "TEST:OPSLIST-string";
        redisTemplate.delete(key);
        redisTemplate.opsForList().leftPush(key, "1");
        redisTemplate.opsForList().leftPush(key, "2");
        redisTemplate.opsForList().leftPush(key, "3");
        redisTemplate.opsForList().leftPush(key, "4");
        // 对应 String(字符串)
        String actual = (String) redisTemplate.opsForList().leftPop(key);
        Assertions.assertEquals("4", actual);
        String actual2 = (String) redisTemplate.opsForList().leftPop(key);
        Assertions.assertEquals("3", actual2);
        List<Object> actual3 = redisTemplate.opsForList().range(key,0L, 1L);
        Assertions.assertEquals(Lists.newArrayList("2", "1"), actual3);

    }

    @Data
    @NoArgsConstructor
    private static class User implements Serializable {
        private static final long serialVersionUID = 2892248514883451461L;
        /**
         * 主键id
         */
        private Long id;

        /**
         * 姓名
         */
        private String name;

        /**
         * 激活时间
         */
        private Date activeTime;
    }
}

3、在yml文件所在的目录下执行下面命令启动容器

docker-compose up -d

4、查看容器

docker ps

 

docker-compose.yml文件详解
image: redis:4 表示使用redis4的最新版镜像
container_name: redis 指定容器的名字为redis
command: redis-server --requirepass 123456 指定redis的密码为123456
ports 将容器的6379端口映射到主机的16379端口上
volumes 用于指定存储卷,容器和宿主机可以通过存储卷共享文件

docker hub redis:https://hub.docker.com/_/redis

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值