Redis缓存中使用Kryo序列化工具并且修改内容同时更新缓存

时间:2022-03-01 14:09:33

背景

缓存做为加速数据访问的途径在许多的系统开发中已经是必不可少。在实际的使用中存在这样一个场景:

对象的的内容非常庞大,一次数据查询量比较大,在并发访问时频繁访问数据库会造成性能瓶颈,实际使用中就会使用缴存,对对象进行更新操作的时候需要对更新的内容入库。但是不希望删除缓存中的对象,而是只更新缓存中的内容,这样就可以减少数据库的访问。提升应用的访问性能

基于此场景,本示例做了一个用例,可以实用于真实例项目中。

项目路径【Redis使用Kryo序列化工具】

实现

下面只讲解了部分关键代码,完整理示例见项目路径
代码结构

Redis缓存中使用Kryo序列化工具并且修改内容同时更新缓存

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

<modelVersion>4.0.0</modelVersion>

<groupId>com.example.spring.boot.redis</groupId>
<artifactId>004-Redis使用Kryo序列化工具</artifactId>
<version>1.0-SNAPSHOT</version>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.3.RELEASE</version>
<relativePath/>
</parent>

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

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>

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

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

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

<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>

<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.2.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.8.4</version>
</dependency>

<dependency>
<groupId>com.esotericsoftware</groupId>
<artifactId>kryo</artifactId>
<version>4.0.0</version>
</dependency>

<dependency>
<groupId>de.javakaffee</groupId>
<artifactId>kryo-serializers</artifactId>
<version>0.41</version>
</dependency>

</dependencies>
</project>

AppConfig.java

package com.example.spring.boot.redis.common;

import com.example.spring.boot.redis.entity.RedisConfig;
import com.example.spring.boot.redis.mapper.RedisConfigMapper;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.data.redis.connection.RedisClusterConfiguration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import javax.sql.DataSource;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;


/**
* Author: 王俊超
* Date: 2017-05-07 10:02
* All Rights Reserved !!!
*/

@Configuration

@MapperScan(basePackages = "com.example.spring.boot.redis.mapper")
public class AppConfig {


/**
* 设置mybatis会话工厂
* @param ds
* @return
* @throws Exception
*/

@Primary
@Bean("sqlSessionFactory")
public SqlSessionFactory sqlSessionFactory(DataSource ds) throws Exception {
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
factory.setDataSource(ds);

// 设置mybatis xml文件扫描路径
factory.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath*:mapper/*Mapper.xml"));
return factory.getObject();
}

/**
* Redis连接工厂
* @param mapper 这个非常重要,必须在mapper被创建了之后才能创建Redis连接工厂
* @return
*/

@Primary
@Bean("redisConnectionFactory")
public RedisConnectionFactory redisConnectionFactory(RedisConfigMapper mapper) {

// 获取redis连接信息
List<RedisConfig> redisConfigs = mapper.getRedisConfig();
List<String> clusterNodes = new ArrayList<>();
for (RedisConfig rc : redisConfigs) {
clusterNodes.add(rc.getUrl() + ":" + rc.getPort());
}

// 获取Redis集群配置信息
RedisClusterConfiguration rcf = new RedisClusterConfiguration(clusterNodes);

return new JedisConnectionFactory(rcf);
}

/**
* 创建redis模板
*
* @param redisConnectionFactory
* @return
* @throws UnknownHostException
*/

@Primary
@Bean("redisTemplate")
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);

// redis value使用的序列化器
template.setValueSerializer(new KryoRedisSerializer<>());
// redis key使用的序列化器
template.setKeySerializer(new KryoRedisSerializer<>());
template.setHashKeySerializer(new KryoRedisSerializer<>());
template.setHashValueSerializer(new KryoRedisSerializer<>());

template.afterPropertiesSet();
return template;
}
}

KryoRedisSerializer.java

package com.example.spring.boot.redis.common;

import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;

import java.util.Arrays;

/**
* Author: 王俊超
* Date: 2017-05-31 21:43
* All Rights Reserved !!!
*/

public class KryoRedisSerializer<T> implements RedisSerializer<T> {
private Kryo kryo = new Kryo();

@Override
public byte[] serialize(T t) throws SerializationException {

System.out.println("[serialize]" + t);

byte[] buffer = new byte[2048];
Output output = new Output(buffer);
kryo.writeClassAndObject(output, t);
return output.toBytes();
}

@Override
public T deserialize(byte[] bytes) throws SerializationException {

System.out.println("[deserialize]" + Arrays.toString(bytes));

Input input = new Input(bytes);
@SuppressWarnings("unchecked")
T t = (T) kryo.readClassAndObject(input);
return t;
}

}

RedisClient.java

package com.example.spring.boot.redis.common;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.stereotype.Component;


/**
* Author: 王俊超
* Date: 2017-06-04 19:57
* All Rights Reserved !!!
*/

@Component("redisClient")
public class RedisClient {
@Autowired
RedisTemplate<Object, Object> redisTemplate;

/**
* 取redis连接
*
* @return
*/

private RedisConnection getConnection() {
return redisTemplate.getConnectionFactory().getConnection();
}

/**
* 获取缓存的key
*
* @param id
* @return
*/

private <T> byte[] getKey(T id) {
// 在spring data redis中,使用@CachePut,@CacheEvict,@Cacheable其缓存名都是
// CachePut,@CacheEvict,@Cacheable中的cacheNames(即value)值+“:”+key
// 因为本示例中使用了kryo对象作为redis的对象序列化的工具所以
// 对cacheName:key要使用序列化,先将“cacheNames:”的值序列化,再对key进行序列化,
// 将这两部分的byte数组合并起来做为key
RedisSerializer serializer = redisTemplate.getKeySerializer();
byte[] idBytes = serializer.serialize(id);
byte[] prefixBytes = (RedisConst.PERSON_CACHE_NAME + ":").getBytes();
byte[] key = new byte[prefixBytes.length + idBytes.length];
System.arraycopy(prefixBytes, 0, key, 0, prefixBytes.length);
System.arraycopy(idBytes, 0, key, prefixBytes.length, idBytes.length);

System.out.println(new String(key));
return key;
}

/**
* 删除redis中的对象
*
* @param t
*/

public <T> void delete(T t) {
getConnection().del(getKey(t));
}

/**
* 更新缓存中的对象,也可以在redis缓存中存入新的对象
*
* @param id
* @param t
* @param <T>
*/

public <T> void set(Long id, T t) {
byte[] key = getKey(id);
RedisSerializer serializer = redisTemplate.getValueSerializer();
byte[] val = serializer.serialize(t);
getConnection().set(key, val);

}

/**
* 从缓存中取对象
*
* @param id
* @param <T>
* @return
*/

public <T> T getObject(Long id) {
byte[] key = getKey(id);
byte[] result = getConnection().get(key);
return (T) redisTemplate.getValueSerializer().deserialize(result);
}


}

RedisServiceImpl.java

package com.example.spring.boot.redis.service.impl;

import com.example.spring.boot.redis.common.RedisClient;
import com.example.spring.boot.redis.common.RedisConst;
import com.example.spring.boot.redis.entity.Person;
import com.example.spring.boot.redis.mapper.PersonMapper;
import com.example.spring.boot.redis.service.RedisService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

/**
* Author: 王俊超
* Date: 2017-05-07 09:58
* All Rights Reserved !!!
*/

@Service
@Transactional(readOnly = true)
public class RedisServiceImpl implements RedisService {
@Autowired
private PersonMapper personRepository;

@Autowired
private RedisClient redisClient;

/**
* 创建对象,并且将person对象入缓存,key是person对象的id
*
* @param person
* @return
*/

@Override
@CachePut(value = RedisConst.PERSON_CACHE_NAME, key = "#person.id")
@Transactional(propagation = Propagation.REQUIRES_NEW)
public Person save(Person person) {
personRepository.save(person);
System.out.println("为id、key为:" + person.getId() + "数据做了缓存");
return person;
}

/**
* 从缓存中删除person对象,key是person对象的id
*
* @param id
*/

@Override
@CacheEvict(value = RedisConst.PERSON_CACHE_NAME) //2
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void remove(Long id) {
System.out.println("删除了id、key为" + id + "的数据缓存");
//这里不做实际删除操作
}

/**
* 查询对象,并且将person对象入缓存,key是person对象的id
*
* @param person
* @return
*/

@Override
@Cacheable(value = RedisConst.PERSON_CACHE_NAME, key = "#person.id") //3
public Person findOne(Person person) {
Person p = personRepository.findOne(person.getId());
System.out.println("为id、key为:" + p.getId() + "数据做了缓存");
return p;
}

/**
* 更新对象,并且将对象入缓存,减少入缓存需要重新查询
*
* @param person
*/

@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void update(Person person) {
personRepository.update(person);
redisClient.set(person.getId(), person);
}
}

测试

使用手工方式向redsi服务器中插入数据
Redis缓存中使用Kryo序列化工具并且修改内容同时更新缓存

Redis缓存中使用Kryo序列化工具并且修改内容同时更新缓存
使用手工方式取数据
Redis缓存中使用Kryo序列化工具并且修改内容同时更新缓存
使用redis托管方式取数据
Redis缓存中使用Kryo序列化工具并且修改内容同时更新缓存
使用手工方式删除数据
Redis缓存中使用Kryo序列化工具并且修改内容同时更新缓存