foreword
Recently in the development project, redis is used as a cache to improve system access speed and relieve system pressure, improve user response and access speed, here are a few problems to summarize and sort out
quick configuration
It is very convenient for SpringBoot to integrate redis with a special scene starter.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
If you use redis connection pool to introduce
<!-- redis连接池 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
Integration Profile
#------------------redis缓存配置------------
# Redis数据库索引(默认为 0)
spring.redis.database=1
# Redis服务器地址
spring.redis.host= 127.0.0.1
# Redis服务器连接端口
spring.redis.port=6379
# Redis 密码
spring.redis.password
# 连接超时时间(毫秒)
spring.redis.timeout= 5000
# redis连接池
# 连接池中的最小空闲连接
spring.redis.lettuce.pool.min-idle=10
# 连接池中的最大空闲连接
spring.redis.lettuce.pool.max-idle= 500
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.lettuce.pool.max-active=2000
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.lettuce.pool.max-wait=10000
JSON serialization
Since the cached data uses the serialized binary that comes with jdk
by default , the entity class that needs to be serialized inherits the Serializable interface. And the serialized content doesn't look very convenient in redis either.
\xAC\xED\x00\x05sr\x00Lorg.springframework.security.oauth2.common.DefaultExpiringOAuth2RefreshToken/\xDFGc\x9D\xD0\xC9\xB7\x02\x00\x01L\x00\x0Aexpirationt\x00\x10Ljava/util/Date;xr\x00Dorg.springframework.security.oauth2.common.DefaultOAuth2RefreshTokens\xE1\x0E\x0AcT\xD4^\x02\x00\x01L\x00\x05valuet\x00\x12Ljava/lang/String;xpt\x00$805a75f7-2ee2-4a27-a598-591bfa1cf17dsr\x00\x0Ejava.util.Datehj\x81\x01KYt\x19\x03\x00\x00xpw\x08\x00\x00\x01}y\x81\xDB\x9Ax
So the idea of serializing the data into json was born.
jackson serialization
When using spring-data-redis , by default, the class org.springframework.data.redis.serializer.JdkSerializationRedisSerializer is used for serialization. Jackson redis serialization comes with spring. We use the jackson method
@Bean
@ConditionalOnClass(RedisOperations.class)
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
//序列化包括类型描述 否则反向序列化实体会报错,一律都为JsonObject
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
mapper.activateDefaultTyping(mapper.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(mapper);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// key采用 String的序列化方式
template.setKeySerializer(stringRedisSerializer);
// hash的 key也采用 String的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
// value序列化方式采用 jackson
template.setValueSerializer(jackson2JsonRedisSerializer);
// hash的 value序列化方式采用 jackson
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
The content stored in redis after serialization
[
"com.qhong.test.dependBean.Person",
{
"age": 20,
"name": "name0",
"iss": true
}
]
[
"java.util.ArrayList",
[
[
"com.qhong.test.dependBean.Person",
{
"age": 20,
"name": "name0",
"iss": true
}
],
[
"com.qhong.test.dependBean.Person",
{
"age": 21,
"name": "name1",
"iss": true
}
],
[
"com.qhong.test.dependBean.Person",
{
"age": 22,
"name": "name2",
"iss": true
}
]
]
]
The above is not strictly in line with the json format specification, although it is better than the default binary
Note that the serialized json generation type here
"com.qhong.test.dependBean.Person"
will report a type conversion exception error without this deserialization
That is to say, this section of the code must be set. I didn’t set it before. Deserialization is JsonObject , and the type must be converted by itself, otherwise an error will be reported.
//序列化包括类型描述 否则反向序列化实体会报错,一律都为JsonObject
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
mapper.activateDefaultTyping(mapper.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(mapper);
Fastjson serialization
- Need to import Fastjson to dependency
<!-- JSON工具 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.76</version>
</dependency>
- Implement the RedisSerializer interface
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
import com.alibaba.fastjson.serializer.SerializerFeature;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T> {
public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
static {
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
}
private final Class<T> clazz;
public FastJson2JsonRedisSerializer(Class<T> clazz) {
super();
this.clazz = clazz;
}
/**
* 序列化
*/
@Override
public byte[] serialize(T t) throws SerializationException {
if (null == t) {
return new byte[0];
}
return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
}
/**
* 反序列化
*/
@Override
public T deserialize(byte[] bytes) throws SerializationException {
if (null == bytes || bytes.length <= 0) {
return null;
}
String str = new String(bytes, DEFAULT_CHARSET);
return (T) JSON.parseObject(str, clazz);
}
}
- Configure redisTemplate
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
@AutoConfigureAfter(RedisAutoConfiguration.class)
public class RedisCacheAutoConfiguration {
@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
FastJson2JsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJson2JsonRedisSerializer<>(Object.class);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// key采用String的序列化方式
template.setKeySerializer(stringRedisSerializer);
// hash的key也采用String的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
// value序列化方式采用fastJson
template.setValueSerializer(fastJsonRedisSerializer);
// hash的value序列化方式采用fastJson
template.setHashValueSerializer(fastJsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
Note that this is a way to implement the RedisSerializer serialization interface yourself,
but FastJson version 1.2.36 does not need to implement RedisSerializer yourself
To provide serialization support for us, there are two implementation classes in com.alibaba.fastjson.support.spring ,GenericFastJsonRedisSerializer
FastJsonRedisSerializer
The difference is that
GenericFastJsonRedisSerializer
the object type can be automatically converted, andFastJsonRedisSerializer
the type required for the conversion needs to be customized.
Usually using GenericFastJsonRedisSerializer can satisfy most scenarios, if you want to define a specific type of dedicated RedisTemplate, you can use FastJsonRedisSerializer instead of GenericFastJsonRedisSerializer”
FastJson github has a corresponding problem description lssues I have been in the pit, and I have been using FastJsonRedisSerializer at the beginning ****Cannot be deserialized automatically
The content stored in redis after serialization
{
"@type": "com.qhong.test.dependBean.Person",
"age": 20,
"iss": true,
"name": "name0"
}
[
{
"@type": "com.qhong.test.dependBean.Person",
"age": 20,
"iss": true,
"name": "name0"
},
{
"@type": "com.qhong.test.dependBean.Person",
"age": 21,
"iss": true,
"name": "name1"
},
{
"@type": "com.qhong.test.dependBean.Person",
"age": 22,
"iss": true,
"name": "name2"
}
]
The normal situation is that the format is correct, but if your stored content has a set or double type, it will bring a Set, and the D type is described as follows
There will be problems that cannot be parsed, but it can be deserialized in the program
Analysis reference comparison
-
jdkSerializationRedisSerializer:
Use the serialization functionality provided by the JDK. The advantage is that there is no need to provide type information (class) when deserializing, but the disadvantage is that the Serializable interface needs to be implemented, and the result after serialization is very large, which is about 5 times that of JSON format, which will consume a lot of memory on the redis server . -
Jackson2JsonRedisSerializer:
Use the Jackson library to serialize the object into a JSON string. The advantage is that the speed is fast, the serialized string is short and compact, and there is no need to implement the Serializable interface. But the disadvantage is also very fatal, that is, there is a type parameter in the constructor of this class, and the type information (.class object) of the object to be serialized must be provided. By looking at the source code, it is found that it only uses type information in the deserialization process. -
FastJsonRedisSerializer
The best performance is known as the fastest json parsing library, but after deserialization, the order of the class fields is inconsistent with the original entity class and changes. In the case of some set and double fields, the json format is incorrect, but it can be parsed in the program
More question reference
Interpretation of RedisTemplate serialization method
redis database operation
After the integration, spring-boot-starter-data-redis
it will automatically inject redisTemplate
objects for us, which are specially used to operate the reids database
If you want to store keys in folders in reids, it looks like this
We only need to use :: to represent folders in storage
redisTemplate.opsForValue().set("userLoginCache::Kenx_6003783582be4c368af14daf3495559c", "user");
If you need to fuzzy query key, use it *
to express such as
- Get all keys
public static Set<String> getAllKey(String keys) {
Set<String> key = redisTemplate.keys(keys + "*");
return key;
}
- Fuzzy Batch Delete
/**
* 删除缓存
*
* @param key 可以传一个值 或多个
*/
public static void del(String... key) {
if (key != null && key.length > 0) {
if (key.length == 1) {
redisTemplate.delete(key[0]);
} else {
redisTemplate.delete(Arrays.asList(key));
}
}
}
public static void delByPrefix(String key) {
if (key != null) {
Set<String> keys = redisTemplate.keys(key + "*");
redisTemplate.delete(keys);
}
}
public static void delBySuffix(String key) {
if (key != null) {
Set<String> keys = redisTemplate.keys("*" + key);
redisTemplate.delete(keys);
}
}
public static void clean(){
Set<String> keys = redisTemplate.keys("*");
redisTemplate.delete(keys);
}
Because it is used frequently, I wrote it as a tool library RedisUtil
and called it through a static method.
Basically contains all the methods used in the work, the source code is attached here
package cn.soboys.kmall.cache.utils;
import cn.hutool.extra.spring.SpringUtil;
import org.springframework.data.redis.core.RedisTemplate;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* 定义常用的 Redis操作
*
* @author kenx
*/
public class RedisUtil {
private static final RedisTemplate<String, Object> redisTemplate = SpringUtil.getBean("redisTemplate", RedisTemplate.class);
/**
* 指定缓存失效时间
*
* @param key 键
* @param time 时间(秒)
* @return Boolean
*/
public static Boolean expire(String key, Long time) {
try {
if (time > 0) {
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根据key获取过期时间
*
* @param key 键 不能为 null
* @return 时间(秒) 返回 0代表为永久有效
*/
public static Long getExpire(String key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
/**
* 判断 key是否存在
*
* @param key 键
* @return true 存在 false不存在
*/
public static Boolean hasKey(String key) {
try {
return redisTemplate.hasKey(key);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除缓存
*
* @param key 可以传一个值 或多个
*/
public static void del(String... key) {
if (key != null && key.length > 0) {
if (key.length == 1) {
redisTemplate.delete(key[0]);
} else {
redisTemplate.delete(Arrays.asList(key));
}
}
}
public static void delByPrefix(String key) {
if (key != null) {
Set<String> keys = redisTemplate.keys(key + "*");
redisTemplate.delete(keys);
}
}
public static void delBySuffix(String key) {
if (key != null) {
Set<String> keys = redisTemplate.keys("*" + key);
redisTemplate.delete(keys);
}
}
public static void clean(){
Set<String> keys = redisTemplate.keys("*");
redisTemplate.delete(keys);
}
/**
* 普通缓存获取
*
* @param key 键
* @return 值
*/
public static Object get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}
/**
* 普通缓存放入
*
* @param key 键
* @param value 值
* @return true成功 false失败
*/
public static Boolean set(String key, Object value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 普通缓存放入并设置时间
*
* @param key 键
* @param value 值
* @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
* @return true成功 false 失败
*/
public static Boolean set(String key, Object value, Long time) {
try {
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
} else {
set(key, value);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 递增
*
* @param key 键
* @param delta 要增加几(大于0)
* @return Long
*/
public static Long incr(String key, Long delta) {
if (delta < 0) {
throw new RuntimeException("递增因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, delta);
}
/**
* 递减
*
* @param key 键
* @param delta 要减少几
* @return Long
*/
public static Long decr(String key, Long delta) {
if (delta < 0) {
throw new RuntimeException("递减因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, -delta);
}
/**
* HashGet
*
* @param key 键 不能为 null
* @param item 项 不能为 null
* @return 值
*/
public static Object hget(String key, String item) {
return redisTemplate.opsForHash().get(key, item);
}
/**
* 获取 hashKey对应的所有键值
*
* @param key 键
* @return 对应的多个键值
*/
public static Map<Object, Object> hmget(String key) {
return redisTemplate.opsForHash().entries(key);
}
/**
* 获取 hashKey对应的所有键
*
* @param key 键
* @return 对应的多个键
*/
public static Set<String> hmgetKey(String key) {
Map map = redisTemplate.opsForHash().entries(key);
return map.keySet();
}
/**
* HashSet
*
* @param key 键
* @param map 对应多个键值
* @return true 成功 false 失败
*/
public static Boolean hmset(String key, Map<String, Object> map) {
try {
redisTemplate.opsForHash().putAll(key, map);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* HashSet 并设置时间
*
* @param key 键
* @param map 对应多个键值
* @param time 时间(秒)
* @return true成功 false失败
*/
public static Boolean hmset(String key, Map<String, Object> map, Long time) {
try {
redisTemplate.opsForHash().putAll(key, map);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一张hash表中放入数据,如果不存在将创建
*
* @param key 键
* @param item 项
* @param value 值
* @return true 成功 false失败
*/
public static Boolean hset(String key, String item, Object value) {
try {
redisTemplate.opsForHash().put(key, item, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一张hash表中放入数据,如果不存在将创建
*
* @param key 键
* @param item 项
* @param value 值
* @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
* @return true 成功 false失败
*/
public static Boolean hset(String key, String item, Object value, Long time) {
try {
redisTemplate.opsForHash().put(key, item, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除hash表中的值
*
* @param key 键 不能为 null
* @param item 项 可以使多个不能为 null
*/
public static void hdel(String key, Object... item) {
redisTemplate.opsForHash().delete(key, item);
}
/**
* 判断hash表中是否有该项的值
*
* @param key 键 不能为 null
* @param item 项 不能为 null
* @return true 存在 false不存在
*/
public static Boolean hHasKey(String key, String item) {
return redisTemplate.opsForHash().hasKey(key, item);
}
/**
* hash递增 如果不存在,就会创建一个 并把新增后的值返回
*
* @param key 键
* @param item 项
* @param by 要增加几(大于0)
* @return Double
*/
public static Double hincr(String key, String item, Double by) {
return redisTemplate.opsForHash().increment(key, item, by);
}
/**
* hash递减
*
* @param key 键
* @param item 项
* @param by 要减少记(小于0)
* @return Double
*/
public static Double hdecr(String key, String item, Double by) {
return redisTemplate.opsForHash().increment(key, item, -by);
}
/**
* 根据 key获取 Set中的所有值
*
* @param key 键
* @return Set
*/
public static Set<Object> sGet(String key) {
try {
return redisTemplate.opsForSet().members(key);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 根据value从一个set中查询,是否存在
*
* @param key 键
* @param value 值
* @return true 存在 false不存在
*/
public static Boolean sHasKey(String key, Object value) {
try {
return redisTemplate.opsForSet().isMember(key, value);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将数据放入set缓存
*
* @param key 键
* @param values 值 可以是多个
* @return 成功个数
*/
public static Long sSet(String key, Object... values) {
try {
return redisTemplate.opsForSet().add(key, values);
} catch (Exception e) {
e.printStackTrace();
return 0L;
}
}
/**
* 将set数据放入缓存
*
* @param key 键
* @param time 时间(秒)
* @param values 值 可以是多个
* @return 成功个数
*/
public static Long sSetAndTime(String key, Long time, Object... values) {
try {
Long count = redisTemplate.opsForSet().add(key, values);
if (time > 0) {
expire(key, time);
}
return count;
} catch (Exception e) {
e.printStackTrace();
return 0L;
}
}
/**
* 获取set缓存的长度
*
* @param key 键
* @return Long
*/
public static Long sGetSetSize(String key) {
try {
return redisTemplate.opsForSet().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0L;
}
}
/**
* 移除值为value的
*
* @param key 键
* @param values 值 可以是多个
* @return 移除的个数
*/
public static Long setRemove(String key, Object... values) {
try {
return redisTemplate.opsForSet().remove(key, values);
} catch (Exception e) {
e.printStackTrace();
return 0L;
}
}
/**
* 获取list缓存的内容
*
* @param key 键
* @param start 开始
* @param end 结束 0 到 -1代表所有值
* @return List
*/
public static List<Object> lGet(String key, Long start, Long end) {
try {
return redisTemplate.opsForList().range(key, start, end);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 获取list缓存的长度
*
* @param key 键
* @return Long
*/
public static Long lGetListSize(String key) {
try {
return redisTemplate.opsForList().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0L;
}
}
/**
* 通过索引 获取list中的值
*
* @param key 键
* @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;
* index<0时,-1,表尾,-2倒数第二个元素,依次类推
* @return Object
*/
public static Object lGetIndex(String key, Long index) {
try {
return redisTemplate.opsForList().index(key, index);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @return Boolean
*/
public static Boolean lSet(String key, Object value) {
try {
redisTemplate.opsForList().rightPush(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return Boolean
*/
public static Boolean lSet(String key, Object value, Long time) {
try {
redisTemplate.opsForList().rightPush(key, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @return Boolean
*/
public static Boolean lSet(String key, List<Object> value) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return Boolean
*/
public static Boolean lSet(String key, List<Object> value, Long time) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根据索引修改list中的某条数据
*
* @param key 键
* @param index 索引
* @param value 值
* @return Boolean
*/
public static Boolean lUpdateIndex(String key, Long index, Object value) {
try {
redisTemplate.opsForList().set(key, index, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 移除N个值为value
*
* @param key 键
* @param count 移除多少个
* @param value 值
* @return 移除的个数
*/
public static Long lRemove(String key, Long count, Object value) {
try {
return redisTemplate.opsForList().remove(key, count, value);
} catch (Exception e) {
e.printStackTrace();
return 0L;
}
}
public static Set<String> getAllKey(String keys) {
Set<String> key = redisTemplate.keys(keys + "*");
return key;
}
}