一、Redis为什么这么快

参考Redis官方文档,我们可以知道Redis 是单进程单线程的模型,完全基于内存的操作,因此CPU不是Redis的瓶颈,Redis的瓶颈是内存及网络带宽,特点如下:

  • 基于内存运行,且查询和操作的时间复杂度为O(1)。
  • 数据结构简单,使用key-value存储系统。
  • 单线程操作,不存在死锁问题,也没有CPU切换的现象。

二、为什么要使用缓存

主要是为了系统性能的提升,我们一般都会将部分数据放入缓存中,加速访问。而 db 承担数据落

盘工作。哪些数据适合放入缓存呢?总结一下就是:

  • 即时性、数据一致性要求不高的
  • 访问量大且更新频率不高的数据(读多,写少)

使用流程:

redis 存储时间 redis缓存时间_缓存

三、Redis持久化机制

我们知道Redis是基于内存的,如果Redis服务器挂了,数据就会丢失。为了避免数据丢失了,Redis提供了RDB和AOF两种持久化机制。

1、RDB(快照),就是把内存数据以快照的形式保存到磁盘上。

RDB持久化,是指在指定的时间间隔内,执行指定次数的写操作,将内存中的数据集快照写入磁盘中,它是Redis默认的持久化方式。执行完操作后,在指定目录下会生成一个dump.rdb文件,Redis 重启的时候,通过加载dump.rdb文件来恢复数据。

redis 存储时间 redis缓存时间_数据库_02

2、AOF(日志),采用日志的形式来记录。

采用日志的形式来记录每个写操作,追加到文件中,重启时再重新执行AOF文件中的命令来恢复数据。它主要解决数据持久化的实时性问题。默认是不开启的。

流程如下:

1)所有的写入命令会追加到aof_buf(缓冲区)中。

2)AOF缓冲区根据对应的策略向硬盘做同步操作。

3)随着AOF文件越来越大,需要定期对AOF文件进行重写,达到压缩的目的。

4)当Redis服务器重启时,可以加载AOF文件进行数据恢复。

redis 存储时间 redis缓存时间_spring boot_03

四、Redis部署模式

我们在项目中使用Redis,肯定不会是单点部署Redis服务的。单点部署一旦宕机,就不可用了。通常的做法,将数据库复制多个副本部署在不同的服务器上,即使其中一台挂了也可以继续提供服务。Redis 实现有三种部署模式:主从模式,哨兵模式,集群模式。这里我们主要讨论常用的主从和哨兵。

1、主从模式

主从模式中,Redis部署了多台机器,有主节点,负责读写操作,有从节点,只负责读操作。从节点的数据来自主节点,实现原理就是主从复制

主从复制包括全量复制,增量复制两种。一般当slave第一次启动连接master,或者认为是第一次连接,就采用全量复制,slave与master全量同步之后,master上的数据,如果再次发生更新,就会触发增量复制

2、哨兵模式

主从模式中,一旦主节点由于故障不能提供服务,需要人工将从节点晋升为主节点,同时还要通知应用方更新主节点地址。Redis从2.8开始正式提供了Redis Sentinel(哨兵)来解决这个问题。

哨兵模式基于主从模式,实现读写分离,它还可以自动切换,系统可用性更高。

五、在SpringBoot中使用Redis

1、引入 redis-starter

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

2、配置redis连接

spring:
  redis:
    host: 192.168.56.10
    port: 6379

3、使用 RedisTemplate 操作 redis

@Autowired
StringRedisTemplate stringRedisTemplate;
@Test
public void testStringRedisTemplate(){
    ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
    ops.set("hello","world_"+ UUID.randomUUID().toString());
    String hello = ops.get("hello");
    System.out.println(hello);
}

RedisAutoConfiguration向容器中导入了两个类RedisTemplate<Object, Object> redisTemplateRedis客户端分别操作k-v都为对象,和StringRedisTemplate,作为Redis客户端k-v都为字符串。

4、Redis缓存原理

配置类RedisCacheConfiguration向容器中导入了其定制的RedisCacheManager,在默认的RedisCacheManager的配置中,是使用jdk序列化value值

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisConnectionFactory.class)
@AutoConfigureAfter(RedisAutoConfiguration.class)
@ConditionalOnBean(RedisConnectionFactory.class)
@ConditionalOnMissingBean(CacheManager.class)
@Conditional(CacheCondition.class)
class RedisCacheConfiguration {
    
    //向容器中导入RedisCacheManager
	@Bean
	RedisCacheManager cacheManager(CacheProperties cacheProperties, CacheManagerCustomizers cacheManagerCustomizers,
			ObjectProvider<org.springframework.data.redis.cache.RedisCacheConfiguration> redisCacheConfiguration,
			ObjectProvider<RedisCacheManagerBuilderCustomizer> redisCacheManagerBuilderCustomizers,
			RedisConnectionFactory redisConnectionFactory, ResourceLoader resourceLoader) {
		//使用determineConfiguration()的返回值生成RedisCacheManagerBuilder
        //调用了RedisCacheManagerBuilder的cacheDefaults()方法(见下一代码块)
        RedisCacheManagerBuilder builder = RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(
				determineConfiguration(cacheProperties, redisCacheConfiguration, resourceLoader.getClassLoader()));
		List<String> cacheNames = cacheProperties.getCacheNames();
		if (!cacheNames.isEmpty()) {
			builder.initialCacheNames(new LinkedHashSet<>(cacheNames));
		}
		redisCacheManagerBuilderCustomizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
        //使用RedisCacheManagerBuilder的build()方法创建RedisCacheManager并进行定制操作
		return cacheManagerCustomizers.customize(builder.build());
	}
    
	private org.springframework.data.redis.cache.RedisCacheConfiguration determineConfiguration(
			CacheProperties cacheProperties,
			ObjectProvider<org.springframework.data.redis.cache.RedisCacheConfiguration> redisCacheConfiguration,
			ClassLoader classLoader) {
        //如果容器中存在RedisCacheConfiguration就直接返回,否则调用RedisCacheConfiguration()创建
		return redisCacheConfiguration.getIfAvailable(() -> createConfiguration(cacheProperties, classLoader));
	}
    
    //createConfiguration()定义了其序列化value的规则
	private org.springframework.data.redis.cache.RedisCacheConfiguration createConfiguration(
			CacheProperties cacheProperties, ClassLoader classLoader) {
		Redis redisProperties = cacheProperties.getRedis();
		org.springframework.data.redis.cache.RedisCacheConfiguration config = org.springframework.data.redis.cache.RedisCacheConfiguration
				.defaultCacheConfig();
        //使用jdk序列化器对value进行序列化
		config = config.serializeValuesWith(
				SerializationPair.fromSerializer(new JdkSerializationRedisSerializer(classLoader)));
        //设置properties文件中设置的各项属性
		if (redisProperties.getTimeToLive() != null) {
			config = config.entryTtl(redisProperties.getTimeToLive());
		}
		if (redisProperties.getKeyPrefix() != null) {
			config = config.prefixKeysWith(redisProperties.getKeyPrefix());
		}
		if (!redisProperties.isCacheNullValues()) {
			config = config.disableCachingNullValues();
		}
		if (!redisProperties.isUseKeyPrefix()) {
			config = config.disableKeyPrefix();
		}
		return config;
	}

}

RedisCacheManager的直接构造类,该类保存了配置类RedisCacheConfiguration,该配置在会传递给RedisCacheManager

public static class RedisCacheManagerBuilder {
    private final RedisCacheWriter cacheWriter;
    //默认缓存配置使用RedisCacheConfiguration的默认配置
    //该默认配置缓存时默认将k按字符串存储,v按jdk序列化数据存储(见下一代码块)
    private RedisCacheConfiguration defaultCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
    private final Map<String, RedisCacheConfiguration> initialCaches = new LinkedHashMap<>();
    private boolean enableTransactions;
    boolean allowInFlightCacheCreation = true;

    private RedisCacheManagerBuilder(RedisCacheWriter cacheWriter) {
        this.cacheWriter = cacheWriter;
    }


    //传入RedisCacheManagerBuilder使用的缓存配置规则RedisCacheConfiguration类
    public RedisCacheManagerBuilder cacheDefaults(RedisCacheConfiguration defaultCacheConfiguration) {
        Assert.notNull(defaultCacheConfiguration, "DefaultCacheConfiguration must not be null!");
        this.defaultCacheConfiguration = defaultCacheConfiguration;
        return this;
    }

    //使用默认defaultCacheConfiguration创建RedisCacheManager
    public RedisCacheManager build() {
        RedisCacheManager cm = new RedisCacheManager(cacheWriter, defaultCacheConfiguration, initialCaches,
                allowInFlightCacheCreation);
        cm.setTransactionAware(enableTransactions);
        return cm;
    }
}

RedisCacheConfiguration保存了许多缓存规则,这些规则都保存在RedisCacheManagerBuilder的RedisCacheConfiguration defaultCacheConfiguration属性中,并且当RedisCacheManagerBuilder创建RedisCacheManager传递过去

public class RedisCacheConfiguration {
    private final Duration ttl;
    private final boolean cacheNullValues;
    private final CacheKeyPrefix keyPrefix;
    private final boolean usePrefix;
    private final SerializationPair<String> keySerializationPair;
    private final SerializationPair<Object> valueSerializationPair;
    private final ConversionService conversionService;

    //默认缓存配置
    public static RedisCacheConfiguration defaultCacheConfig(@Nullable ClassLoader classLoader) {
        DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
        registerDefaultConverters(conversionService);
        return new RedisCacheConfiguration(Duration.ZERO, true, true, CacheKeyPrefix.simple(),
                SerializationPair.fromSerializer(RedisSerializer.string()),//key使用字符串
                SerializationPair.fromSerializer(RedisSerializer.java(classLoader)), conversionService);
        //value按jdk序列化存储
    }
}

RedisCacheManager在创建RedisCache时将RedisCacheConfiguration传递过去,并在创建RedisCache时通过createRedisCache()起作用

public class RedisCacheManager extends AbstractTransactionSupportingCacheManager {
    private final RedisCacheWriter cacheWriter;
    private final RedisCacheConfiguration defaultCacheConfig;
    private final Map<String, RedisCacheConfiguration> initialCacheConfiguration;
    private final boolean allowInFlightCacheCreation;
    protected RedisCache createRedisCache(String name, @Nullable RedisCacheConfiguration cacheConfig) {
        //如果调用该方法时RedisCacheConfiguration有值则使用定制的,否则则使用默认的RedisCacheConfiguration defaultCacheConfig,即RedisCacheManagerBuilder传递过来的配置
        return new RedisCache(name, cacheWriter, cacheConfig != null ? cacheConfig : defaultCacheConfig);
    }
}

RedisCache,Redis缓存,具体负责将缓存数据序列化的地方,将RedisCacheConfiguration的序列化对SerializationPair提取出来并使用其定义的序列化方式分别对k和v进行序列化操作

public class RedisCache extends AbstractValueAdaptingCache {
    private static final byte[] BINARY_NULL_VALUE = RedisSerializer.java().serialize(NullValue.INSTANCE);
    private final String name;
    private final RedisCacheWriter cacheWriter;
    private final RedisCacheConfiguration cacheConfig;
    private final ConversionService conversionService;
    public void put(Object key, @Nullable Object value) {
        Object cacheValue = preProcessCacheValue(value);
        if (!isAllowNullValues() && cacheValue == null) {
            throw new IllegalArgumentException(String.format(
                    "Cache '%s' does not allow 'null' values. Avoid storing null via '@Cacheable(unless=\"#result == null\")' or configure RedisCache to allow 'null' via RedisCacheConfiguration.",
                    name));
        }
        //在put k-v时使用cacheConfig中的k-v序列化器分别对k-v进行序列化
        cacheWriter.put(name, createAndConvertCacheKey(key), serializeCacheValue(cacheValue), cacheConfig.getTtl());
    }

    //从cacheConfig(即RedisCacheConfiguration)中获取KeySerializationPair并写入key值
    protected byte[] serializeCacheKey(String cacheKey) {
        return ByteUtils.getBytes(cacheConfig.getKeySerializationPair().write(cacheKey));
    }
    
    //从cacheConfig(即RedisCacheConfiguration)中获取ValueSerializationPair并写入key值
    protected byte[] serializeCacheValue(Object value) {
        if (isAllowNullValues() && value instanceof NullValue) {
            return BINARY_NULL_VALUE;
        }
        return ByteUtils.getBytes(cacheConfig.getValueSerializationPair().write(value));
    }
}

这也就不难理解,要使用json保存序列化数据时,需要自定义RedisCacheConfiguration,在RedisCacheConfiguration中定义序列化转化规则,并向RedisCacheManager传入我们自己定制的RedisCacheConfiguration了,我定制的序列化规则会跟随RedisCacheConfiguration一直传递到RedisCache,并在序列化时发挥作用。