Springboot集成Redis

一、Redis讲解

1、redis入门

特点:基于 key-value 存储。支持多种数据结构:string(字符串)、list(列表)、set(集合)、zset(sorted set 有序集合)、hash(哈希)。通过内存存储、操作数据,支持持久化将数据存储在硬盘中。支持过期时间、事务。

1.1  数据类型

五种基础类型:string(字符串)、list(列表)、set(集合)、zset(sorted set 有序集合)、hash(哈希)

三种特殊类型:HyperLogLogs(基数统计)、Bitmap (位存储)、geospatial (地理位置)

        作用:

  • HyperLogLogs 举个例子,A = {1, 2, 3, 4, 5}, B = {3, 5, 6, 7, 9};那么基数(不重复的元素)= 1, 2, 4, 6, 7, 9;这个结构可以非常省内存的去统计各种计数
  • Bitmap 即位图数据结构,都是操作二进制位来进行记录,只有0 和 1 两个状态。
  • geospatial 可推算地理位置的信息: 两地之间的距离, 方圆几里的人

2.1  持久化:RDB和AOF机制详解

RDB 持久化

RDB 就是 Redis DataBase 的缩写,中文名为快照/内存快照,RDB持久化是把当前进程数据生成快照保存到磁盘上的过程,由于是某一时刻的快照,那么快照中的值要早于或者等于内存中的值

 触发方式

触发rdb持久化的方式有2种,分别是手动触发自动触发

 手动触发

手动触发分别对应save和bgsave命令

  • save命令:阻塞当前Redis服务器,直到RDB过程完成为止,对于内存 比较大的实例会造成长时间阻塞,线上环境不建议使用

  • bgsave命令:Redis进程执行fork操作创建子进程,RDB持久化过程由子 进程负责,完成后自动结束。阻塞只发生在fork阶段,一般时间很短

具体流程如下:

  • redis客户端执行bgsave命令或者自动触发bgsave命令;
  • 主进程判断当前是否已经存在正在执行的子进程,如果存在,那么主进程直接返回;
  • 如果不存在正在执行的子进程,那么就fork一个新的子进程进行持久化数据,fork过程是阻塞的,fork操作完成后主进程即可执行其他操作;
  • 子进程先将数据写入到临时的rdb文件中,待快照数据写入完成后再原子替换旧的rdb文件;
  • 同时发送信号给主进程,通知主进程rdb持久化完成,主进程更新相关的统计信息(info Persitence下的rdb_*相关选项)。

自动触发

在以下4种情况时会自动触发

  • redis.conf中配置save m n,即在m秒内有n次修改时,自动触发bgsave生成rdb文件;

  • 主从复制时,从节点要从主节点进行全量复制时也会触发bgsave操作,生成当时的快照发送到从节点;

  • 执行debug reload命令重新加载redis时也会触发bgsave操作;

  • 默认情况下执行shutdown命令时,如果没有开启aof持久化,那么也会触发bgsave操作;

 redis.conf中配置RDB

  • Redis中默认的周期新设置
# 周期性执行条件的设置格式为
save <seconds> <changes>

# 默认的设置为:
save 900 1
save 300 10
save 60 10000

# 以下设置方式为关闭RDB快照功能
save ""
  • 如果900秒内有1条Key信息发生变化,则进行快照;

  • 如果300秒内有10条Key信息发生变化,则进行快照;

  • 如果60秒内有10000条Key信息发生变化,则进行快照。读者可以按照这个规则,根据自己的实际请求压力进行设置调整

  • 其它相关配置

# 文件名称
dbfilename dump.rdb

# 文件保存路径
dir /home/work/app/redis/data/

# 如果持久化出错,主进程是否停止写入
stop-writes-on-bgsave-error yes

# 是否压缩
rdbcompression yes

# 导入时是否检查
rdbchecksum yes

RDB优缺点

  • 优点

    • RDB文件是某个时间节点的快照,默认使用LZF算法进行压缩,压缩后的文件体积远远小于内存大小,适用于备份、全量复制等场景;
    • Redis加载RDB文件恢复数据要远远快于AOF方式;
  • 缺点

    • RDB方式实时性不够,无法做到秒级的持久化;
    • 每次调用bgsave都需要fork子进程,fork子进程属于重量级操作,频繁执行成本较高;
    • RDB文件是二进制的,没有可读性,AOF文件在了解其结构的情况下可以手动修改或者补全;
    • 版本兼容RDB文件问题;

针对RDB不适合实时持久化的问题,Redis提供了AOF持久化方式来解决

AOF持久化

Redis是“写后”日志,Redis先执行命令,把数据写入内存,然后才记录日志。日志里记录的是Redis收到的每一条命令,这些命令是以文本形式保存。PS: 大多数的数据库采用的是写前日志(WAL),例如MySQL,通过写前日志和两阶段提交,实现数据和逻辑的一致性。

如何实现AOF

AOF日志记录Redis的每个写命令,步骤分为:命令追加(append)、文件写入(write)和文件同步(sync)

redis.conf中配置AOF

默认情况下,Redis是没有开启AOF的,可以通过配置redis.conf文件来开启AOF持久化,关于AOF的配置如下:

# appendonly参数开启AOF持久化
appendonly no

# AOF持久化的文件名,默认是appendonly.aof
appendfilename "appendonly.aof"

# AOF文件的保存位置和RDB文件的位置相同,都是通过dir参数设置的
dir ./

# 同步策略
# appendfsync always
appendfsync everysec
# appendfsync no

# aof重写期间是否同步
no-appendfsync-on-rewrite no

# 重写触发配置
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

# 加载aof出错如何处理
aof-load-truncated yes

# 文件重写策略
aof-rewrite-incremental-fsync yes

从持久化中恢复数据

其实想要从这些文件中恢复数据,只需要重新启动Redis即可

二、springboot集成Redis

目录

一、Redis讲解

1、redis入门

1.1  数据类型

2.1  持久化:RDB和AOF机制详解

RDB 持久化

RDB优缺点

AOF持久化

如何实现AOF

redis.conf中配置AOF

从持久化中恢复数据

二、springboot集成Redis

1、yml文件配置redis

2、添加依赖信息

3、新增一个 Redis 配置类 RedisConfig.java,用于自定义配置redis,由于官网给出的redisTemplate不是很好用

4、写一个RedisUtil工具类,方便后面直接调用

5、使用切面RedisAspect来处理redis的异常,保证redis在异常的时候,系统依然可以工作


1、yml文件配置redis

【yml:】
spring:
  # Redis 配置
  redis:
    # Redis 服务器地址
    host: 121.26.184.41
    # 连接端口号
    port: 6379
    # 数据库索引(0 - 15)
    database: 0
    # 连接超时时间(毫秒)
    timeout: 10000
    # lettuce 参数
    lettuce:
      pool:
        # 最大连接数(使用负值表示没有限制) 默认为 8
        max-active: 10
        # 最大阻塞等待时间(使用负值表示没有限制) 默认为 -1 ms
        max-wait: -1
        # 最大空闲连接 默认为 8
        max-idle: 5
        # 最小空闲连接 默认为 0
        min-idle: 0

【properties:】
# Redis 服务器地址
spring.redis.host=121.26.184.41
# 连接端口号
spring.redis.port=6379
# 数据库(0 - 15)
spring.redis.database= 0
# 超时时间(毫秒)
spring.redis.timeout=600000

# lettuce 参数
# 最大连接数(使用负值表示没有限制) 默认为 8
spring.redis.lettuce.pool.max-active=20
# 最大阻塞等待时间(使用负值表示没有限制) 默认为 -1
spring.redis.lettuce.pool.max-wait=-1
# 最大空闲连接 默认为 8
spring.redis.lettuce.pool.max-idle=5
# 最小空闲连接 默认为 0
spring.redis.lettuce.pool.min-idle=0

2、添加依赖信息

<!-- redis -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- spring2.X集成redis所需common-pool2-->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
    <version>2.6.0</version>
</dependency>

3、新增一个 Redis 配置类 RedisConfig.java,用于自定义配置redis,由于官网给出的redisTemplate不是很好用

@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);
        //Json序列化器
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
        jackson2JsonRedisSerializer.setObjectMapper(om);

        //String序列化器
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();

        //key采用String序列化器
        template.setKeySerializer(stringRedisSerializer);
        template.setHashKeySerializer(stringRedisSerializer);
        //value采用Json序列化器
        template.setValueSerializer(jackson2JsonRedisSerializer);
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }
}

4、写一个RedisUtil工具类,方便后面直接调用

/**
 * @author yu_xuanyuan
 * @date 2022/9/23 10:16
 */
@Component
public class RedisUtil {
    @Autowired
    RedisTemplate<String,Object> redisTemplate;
    /**
     * setex
     * @param key  key
     * @param value  value
     * @param time 过期时间
     */
    public void setex(String key,Object value,long time){
        redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
    }

    /**
     * set
     * String类型的set,无过期时间
     * @param key key
     * @param value value
     */
    public void set(String key, Object value){
        redisTemplate.opsForValue().set(key,value);
    }

    /**
     * 批量设置key和value
     * @param map key和value的集合
     */
    public void mset(Map<String,Object> map){
        redisTemplate.opsForValue().multiSet(map);
    }

    /**
     * 如果key不存在,则设置
     * @param key  key
     * @param value value
     * @return 返回是否成功
     */
    public Boolean setnx(String key,Object value){
        return redisTemplate.opsForValue().setIfAbsent(key, value);
    }

    /**
     * 批量插入key,如果key不存在的话
     * @param map key和value的集合
     * @return 是否成功
     */
    public Boolean msetnx(Map<String,Object> map){
        return redisTemplate.opsForValue().multiSetIfAbsent(map);
    }

    /**
     * String类型的get
     * @param key key
     * @return 返回value对应的对象
     */
    public Object get(String key){
        return redisTemplate.opsForValue().get(key);
    }

    /**
     * 删除对应key
     * @param key key
     * @return 返回是否删除成功
     */
    public Boolean del(String key){
        return redisTemplate.delete(key);
    }

    /**
     * 批量删除key
     * @param keys key的集合
     * @return 返回删除成功的个数
     */
    public Long del(List<String> keys){
        return redisTemplate.delete(keys);
    }

    /**
     * 给某个key设置过期时间
     * @param key key
     * @param time 过期时间
     * @return 返回是否设置成功
     */
    public Boolean expire(String key, long time){
        return redisTemplate.expire(key, time, TimeUnit.SECONDS);
    }

    /**
     * 返回某个key的过期时间
     * @param key key
     * @return 返回key剩余的过期时间
     */
    public Long ttl(String key){
        return redisTemplate.getExpire(key);
    }

    /**
     * 返回是否存在该key
     * @param key key
     * @return 是否存在该key
     */
    public Boolean exists(String key){
        return redisTemplate.hasKey(key);
    }

    /**
     * 给key的值加上delta值
     * @param key key
     * @param delta 参数
     * @return 返回key+delta的值
     */
    public Long incrby(String key, long delta){
        return redisTemplate.opsForValue().increment(key, delta);
    }

    /**
     * 给key的值减去delta
     * @param key key
     * @param delta 参数
     * @return 返回key - delta的值
     */
    public Long decrby(String key, long delta){
        return redisTemplate.opsForValue().decrement(key, delta);
    }

    //hash类型

    /**
     * set hash类型
     * @param key key
     * @param hashKey  hashKey
     * @param value value
     */
    public void hset(String key,String hashKey, Object value){
        redisTemplate.opsForHash().put(key, hashKey, value);
    }

    /**
     * set hash类型,并设置过期时间
     * @param key  key
     * @param hashKey hashKey
     * @param value value
     * @param time 过期时间
     * @return 返回是否成功
     */
    public Boolean hset(String key, String hashKey,Object value, long time){
        hset(key, hashKey, value);
        return expire(key, time);
    }

    /**
     * 批量设置hash
     * @param key key
     * @param map hashKey和value的集合
     * @param time 过期时间
     * @return 是否成功
     */
    public Boolean hmset(String key, Map<String,Object> map, long time){
        redisTemplate.opsForHash().putAll(key, map);
        return expire(key, time);
    }

    /**
     * 获取hash类型的值
     * @param key key
     * @param hashKey hashKey
     * @return 返回对应的value
     */
    public Object hget(String key, String hashKey){
        return redisTemplate.opsForHash().get(key, hashKey);
    }

    /**
     * 获取key下所有的hash值以及hashKey
     * @param key key
     * @return 返回数据
     */
    public Map<Object,Object> hgetall(String key){
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * 批量删除
     * @param key  key
     * @param hashKey hashKey数组集合
     */
    public void hdel(String key, Object... hashKey){
        redisTemplate.opsForHash().delete(key, hashKey);
    }

    /**
     * 判断是否存在hashKey
     * @param key  key
     * @param hashKey hashKey
     * @return 是否存在
     */
    public Boolean hexists(String key, String hashKey){
        return redisTemplate.opsForHash().hasKey(key, hashKey);
    }

}

5、使用切面RedisAspect来处理redis的异常,保证redis在异常的时候,系统依然可以工作

/**
 * @author yu_xuanyuan
 * @date 2022/9/23 10:28
 */
@Aspect
@Slf4j
public class RedisAspect {
    @Pointcut("execution(* com.study.springboot.common.utils.*(..))")
    public void pointcut(){
    }
    @Around("pointcut()")
    public Object handleException(ProceedingJoinPoint joinPoint){
        Object result = null;
        try {
            result= joinPoint.proceed();
        } catch (Throwable throwable) {
            log.error("redis may be some wrong");
        }
        return result;
    }

}

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值