前言

之前在 秒杀系统简单实现 有提到 spring boot 整合 redis,有必要单独提出来讲一下.

说明

redis 一般在学习并发优化,分布式之类的东西时接触到,不严谨地讲,redis 在这些场景被用于提高服务端响应速度.而 redis 到底是啥,结合它是如何来解决前面这个问题的,能更好地理解.

redis 的官方定义是”一个把结构化的数据放在内存中的一个存储系统”.这个定义很开放,你可以用 redis 做数据库,也可以用来缓存或作消息中间件.

redis 的数据放在内存中,这就是它优化服务端响应速度的一个原因.像一般的数据库,如 MySQL,MySQL 的数据存放在磁盘,在不考虑其它原因的情况下,可以直观地认为内存中的数据当然比磁盘中的数据读取地快,所以 redis 起到了优化的作用.

在具体的优化中,redis 一般起缓存作用.在冷启动时,数据仍然通过原有方式获取,然后存放在 redis 中,而之后需要读取数据,就可以直接从 redis 获得.

安装

redis 版本 : 5.0.3

linux 版本 : centos 7

解压版安装,稍微详细点.

下载地址 : https://redis.io/download

解压,打开,执行 make. 可以用 j 参数,执行 make j8,

有可能缺少依赖(在我的环境下,只遇到过缺少依赖的报错),按提示安装缺少的依赖即可.

PS : 使用默认值执行了 make install,会在 /usr/local/bin 生成安装目录,可全局执行 redis 相关命令…

error: jemalloc/jemalloc.h: No such file or directory

执行 make MALLOC=libc

PS

19-7-29 腾讯云重装 *CentOS 7.5 *, 这次啥问题也没有…

使用

redis-server 是 Redis 服务器

redis-cli 是 Redis 命令行客户端

这俩也是最常使用的 redis 命令.

启动

运行 redis : redis-server

PS : 执行 redis-server 如果遇到某些错误信息,其实当前窗口已经给出了解决办法.看不明白可以搜索引擎查看.(我现在写这篇博文已经不晓得之前遇到过啥问题了…)

打开执行 redis 命令的窗口 : redis-cli

关于启动,主要就是这俩命令.我们需要启动 redis-server,然后在 redis-cli 中对 redis 进行操作.(如增删改数据…)

这里就会遇到问题.

在不进行任何配置的情况下,执行 redis-server 使用的是默认配置.

在默认情况下, redis-server 会在 前台 执行.而我们实际对 redis 进行操作(增删改数据之类的)需要打开 redis-cli.

由于 redis-server 是在前台运行的,如果在想在当前窗口运行 redis-cli 那就得杀掉这个前台进程,然后执行 redis-cli.

但此时, redis-server 已经被关闭了,所以在 redis-cli 执行的操作都没效果.

PS : 如果已经整合到 spring boot,进行数据操作时会报错NOAUTH Authentication required.

如何解决?

解决方案一 : 在一个终端执行 redis-server ,重开一个窗口执行 redis-cli ,然后执行相应操作.

解决方案二 : 通过修改配置文件使之后台启动 redis-server.

后台执行 redis-server ,然后就能在当前窗口执行 redis-cli 啦.

如何做?

修改 /redis/redis.conf 中的 daemonize nodaemonize yes 即可.

启动

redis-server redis.conf

比如 :

进入 /redis/src

执行 ./redis-server ../redis.conf 意思就是以自己的配置(后台执行)来运行 redis-server.

ok.

然后使用命令 ps -ef|grep redis 检查是否后台启动成功.

中文乱码

启动 redis-cli 时加上参数.

redis-cli --raw

关闭

Spring Boot 2.x 整合 Redis

我采用的是 gradle + yml.

配置需要在三个地方动手…

  1. gradle 中引入必要依赖.
  2. yml 中完善配置
  3. 添加一个 config 配置类

build.gradle

    // https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-redis
    compile group: 'org.springframework.boot', name: 'spring-boot-starter-data-redis', version: '2.1.1.RELEASE'

    // https://mvnrepository.com/artifact/com.alibaba/fastjson
    compile group: 'com.alibaba', name: 'fastjson', version: '1.2.47'

    //必要的
    compile('org.apache.commons:commons-pool2:2.6.0')
    
    //spring cache 效果:redis 里的数据删了,spring 仍能查到数据,就是通过它拿到的
    compile('org.springframework.boot:spring-boot-starter-cache')

第一个 spring-boot-starer-data-redis 是主要依赖,用于 redis 与 spring boot 交互.

第二个 fastjson 用于 java 对象和 json 对象之间序列化的转换.

第三个 commons-pool ,具体不知道是干嘛的…

搜索得到 : Commons Pool组件提供了一整套用于实现对象池化的框架

大概是维护对象池,提高数据读取速度…

第四个 spring-boot-starter-cache 不要也可以,猜测是 spring 框架的缓存.因为我进行排除对比,有这个依赖时,redis 的数据删除了,spring 仍能不通过数据库拿到数据.删除就不起作用了.

application.yml

server:
  port: 8082

spring:
  # 属性值可以为空,但是不能没有以下字段,否则启动报错
  datasource:
    url: jdbc:mysql://118.126.test.120:3306/test?serverTimezone=UTC
    username:
    password:
    driver-class-name: com.mysql.jdbc.Driver
  # spring boot 2.x 配置方式
  redis:
     # Redis数据库索引(默认为0)
     database: 0
     # Redis服务器地址
     host: localhost
     # Redis服务器连接端口(默认 6379)
     port: 6379
     # Redis服务器连接密码(默认为空)
     password:
     # pringboot2.x 需要这样配置
       lettuce:
         pool:
           max-active: 8
           max-wait: -1ms
           min-idle: 0

主要在后面的 redis 配置…

上面是 spring boot 2.x 的配置方式,下面是旧的配置方式.

# spring boot 旧配置方式
spring:
	redis:
		hostName: 
		port: 
		password: 
		database: 
		timeout: 0
		pool:
			max-active: 8
			max-wait: -1
			max-idle: 8
			min-idle: 0

不同点在新的配置方式里,需要手动指定 redis 连接池.

比如还可以:

 redis:
    port: 
    host: 
    password: 
    database: 
    timeout: 
    jedis:
      pool:
        max-active: 
        max-idle: 
        min-idle: 
        max-wait: 

指定 redis 连接池为 jedis…

Spring Config

/**
 * @description: TODO
 * Created by hqweay on 1/1/19 2:07 PM
 * 这个配置类仍然是必须的,不然向 redis 插入数据会失败
 */
@Configuration
@EnableCaching
public class RedisConfiguration {

  @Bean
  public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory connectionFactory) {
    RedisTemplate template = new RedisTemplate();
    template.setConnectionFactory(connectionFactory);

    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
    objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);

    Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
    jackson2JsonRedisSerializer.setObjectMapper(objectMapper);

    template.setValueSerializer(jackson2JsonRedisSerializer);
    template.setKeySerializer(new StringRedisSerializer());
    template.afterPropertiesSet();
    return template;
  }


  @Bean
  public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
    Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
    objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
    jackson2JsonRedisSerializer.setObjectMapper(objectMapper);

    RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory);

    RedisCacheConfiguration cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
    cacheConfiguration = cacheConfiguration.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer));
    cacheConfiguration.entryTtl(Duration.ofMinutes(10));
    CacheManager cacheManager = new RedisCacheManager(redisCacheWriter, cacheConfiguration);
    return cacheManager;

  }
}

这一块的使用我还没弄清楚,代码来自网络,整理不当,忘记从哪 copy 的了…

@EnableCaching 注解似乎是与前文在 gradle 引入的 spring-boot-starter-cache 相联系,开启 spring-cache,cacheManager 是为此实现的一个方法.

使用

直接看封装的这个操作类…

@Component
public class RedisDao {

  @Autowired
  private RedisTemplate redisTemplate;

  public boolean putProductList(ArrayList<Product> products) {
    if (redisTemplate.hasKey("productList")) {
      return false;
    } else {
      redisTemplate.opsForValue().set("productList", products);
    }
    return true;
  }

  public ArrayList<Product> getProductList() throws SkillException {
    try {
      return (ArrayList<Product>) redisTemplate.opsForValue().get("productList");
    } catch (SkillException e) {
      e.setStatusEnum(REDIS_GET_ERROR);
      throw e;
    }
  }


  public void test(){
    OrderKey orderKey = new OrderKey();
    orderKey.setPhone("1155555");
    redisTemplate.opsForValue().set("11",orderKey);
    redisTemplate.opsForValue().set("myse","mys");
    System.out.println(redisTemplate.opsForValue().get("33"));
  }
}

序列化

spring boot 与 redis 交互有两种方式,一个是直接序列化,一个是转化为 json,以字符串格式存储.

这里使用的是 json 存储对象,前面有提到,用 fastjson 把 对象转换为 json 对象(这也是序列化嘛),然后存入 redis.读取的时候,会自动把 json 转换为 Object 对象,自己再转型一下就行了.

比如上面代码中的:

(ArrayList<Product>) redisTemplate.opsForValue().get("productList");

redis 的连接池

在上面的配置中有提到,spring boot 使用 redis 的连接池有 Jedis 和 Lettuce.

这两个有啥区别,怎么选型呢?

我在练习秒杀系统时,视频教程用的是 Jedis,但是 spring boot 2.0 以后已经把之前的 Jedis 改为 Lettuce 了.(也是配置改动的原因)

具体有啥区别,搜索有答案,自己也没深入了解.

就用 Lettuce 吧…

其它

学习 redis,各种入门教程会有各种 set,get 等基本命令,但是只是简单使用的话,暂时没必要了解,因为已经被封装了一层,可以直接用 java 操作…