一、Redis为什么这么快
参考Redis官方文档,我们可以知道Redis 是单进程单线程的模型,完全基于内存的操作,因此CPU不是Redis的瓶颈,Redis的瓶颈是内存及网络带宽,特点如下:
- 基于内存运行,且查询和操作的时间复杂度为O(1)。
- 数据结构简单,使用key-value存储系统。
- 单线程操作,不存在死锁问题,也没有CPU切换的现象。
二、为什么要使用缓存
主要是为了系统性能的提升,我们一般都会将部分数据放入缓存中,加速访问。而 db 承担数据落
盘工作。哪些数据适合放入缓存呢?总结一下就是:
- 即时性、数据一致性要求不高的
- 访问量大且更新频率不高的数据(读多,写少)
使用流程:
三、Redis持久化机制
我们知道Redis是基于内存的,如果Redis服务器挂了,数据就会丢失。为了避免数据丢失了,Redis提供了RDB和AOF两种持久化机制。
1、RDB(快照),就是把内存数据以快照的形式保存到磁盘上。
RDB持久化,是指在指定的时间间隔内,执行指定次数的写操作,将内存中的数据集快照写入磁盘中,它是Redis默认的持久化方式。执行完操作后,在指定目录下会生成一个dump.rdb
文件,Redis 重启的时候,通过加载dump.rdb
文件来恢复数据。
2、AOF(日志),采用日志的形式来记录。
采用日志的形式来记录每个写操作,追加到文件中,重启时再重新执行AOF文件中的命令来恢复数据。它主要解决数据持久化的实时性问题。默认是不开启的。
流程如下:
1)所有的写入命令会追加到aof_buf(缓冲区)中。
2)AOF缓冲区根据对应的策略向硬盘做同步操作。
3)随着AOF文件越来越大,需要定期对AOF文件进行重写,达到压缩的目的。
4)当Redis服务器重启时,可以加载AOF文件进行数据恢复。
四、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> redisTemplate
Redis客户端分别操作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,并在序列化时发挥作用。