持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第3天,点击查看活动详情
背景
项目中为了提升性能通常都会用到缓存,而java提供了丰富的缓存实现方式,有本地缓存(Cache),分布式缓存(Redis)等,本文将讲解Spring Boot如何集成Redis。
简介
Redis 全称是 Remote Dictionary Server,是完全开源免费的,是一个高性能的key-value类型的内存数据库。
特性
- Reid单线程IO复路、完全基于内存操作所以速度非常快。
- 支持丰富数据类型,支持String(字符串)、hash(哈希)、 list(列表)、set(集合)、zset(有序集合)、Bitmaps(位图)、 HyperLogLog、GEO(地理信息定位)等多种数据结构
- 丰富的特性:既可以做缓存使用,也可以用于消息队列(支持 publish/subscribe类型消息通知)。
- 丰富的数据持久化方案,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用;
- 丰富高可用的架构方案,主从、哨兵、集群等模式。
集成步骤
pom文件添加相关依赖
<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>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
Redis连接池配置
redis:
host: localhost
port: 6379
password: 1234
database: 5
lettuce:
pool:
max-idle: 16
max-active: 32
min-idle: 8
说明:Redis1.5X版本默认的连接池采用的是Jedis,而Spring2.0X版本默认采用的是Lettuce,关于Jedis和Lettuce连接池的区别如下:
-
Jedis 在实现上是直连 redis server,多线程环境下非线程安全,除非使用连接池,为每个 redis实例增加物理连接。
-
Lettuce 是 一种可伸缩,线程安全,完全非阻塞的Redis客户端,多个线程可以共享一个RedisConnection,它利用Netty NIO 框架来高效地管理多个连接,从而提供了异步和同步数据访问方式,用于构建非阻塞的反应性应用程序。
所以默认情况下还是选择Lettuce连接池。
Redis核心配置
@Configuration
public class RedisConfig
{
@Autowired
private RedisConnectionFactory factory;
@Bean("redisTemplate")
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new StringRedisSerializer());
redisTemplate.setConnectionFactory(factory);
return redisTemplate;
}
}
说明: Redis支持多种序列化的方式如下:
- GenericToStringSerializer: 可以将任何对象泛化为字符串并序列化
- Jackson2JsonRedisSerializer: 跟JacksonJsonRedisSerializer实际上是一样的
- JacksonJsonRedisSerializer: 序列化object对象为json字符串
- JdkSerializationRedisSerializer: 序列化java对象
- StringRedisSerializer: 简单的字符串序列化
默认redis采用的是String字符串的方式,也支持自定义的序列化方式,例如采用Protostuff序列化,客户端实现RedisSerializer接口,重写相关方法即可,实现代码如下:
public class ProtoSerializer implements RedisSerializer<Object>
{
static final byte[] EMPTY_ARRAY = new byte[0];
static final boolean isEmpty(byte[] data)
{
return (data == null || data.length == 0);
}
private final Schema<ProtoWrapper> schema;
private final ProtoWrapper wrapper;
private final LinkedBuffer buffer;
public ProtoSerializer()
{
this.wrapper = new ProtoWrapper();
this.schema = RuntimeSchema.createFrom(ProtoWrapper.class);
this.buffer = LinkedBuffer.allocate();
}
@Override
public byte[] serialize(Object t)
{
if (t == null)
{
return EMPTY_ARRAY;
}
wrapper.data = t;
try
{
return ProtostuffIOUtil.toByteArray(wrapper, schema, buffer);
}
catch(Exception ex)
{
return EMPTY_ARRAY;
}
finally
{
buffer.clear();
}
}
@Override
public Object deserialize(byte[] bytes) throws SerializationException
{
if (isEmpty(bytes))
{
return null;
}
ProtoWrapper newMessage = schema.newMessage();
try
{
ProtostuffIOUtil.mergeFrom(bytes, newMessage, schema);
}
catch (Exception e)
{
return null;
}
return newMessage.data;
}
}
Redis工具类
/**
* Redis工具类
*
*/
@Component
public class RedisUtil
{
private static final Logger logger = LoggerFactory.getLogger(RedisUtil.class);
@Resource
private RedisTemplate<String, Object> redisTemplate;
@Transactional
public void set(String key, Serializable value)
{
SessionCallback<Boolean> sessionCallback = new SessionCallback<Boolean>()
{
@SuppressWarnings({ "unchecked" })
@Override
public Boolean execute(RedisOperations operations) throws DataAccessException
{
try
{
do
{
operations.watch(key);
operations.hasKey(key);
operations.multi();
operations.opsForValue().set(key, value);
}
while (operations.exec() == null);
}
catch (Exception e)
{
logger.error("==>redis operation [set] key[{}] value[{}] error",key,value);
logger.error(e.getMessage(),e);
}
return true;
}
};
redisTemplate.execute(sessionCallback);
}
@Transactional
public void set(String key, Object value)
{
SessionCallback<Boolean> sessionCallback = new SessionCallback<Boolean>()
{
@SuppressWarnings({ "unchecked" })
public Boolean execute(RedisOperations operations) throws DataAccessException
{
try
{
do
{
operations.watch(key);
if (operations.hasKey(key))
{
operations.multi();
operations.opsForValue().set(key, value);
}
else
{
operations.multi();
operations.opsForValue().setIfAbsent(key, value);
}
}
while (operations.exec() == null);
}
catch (Exception e)
{
logger.error("==>redis operation [set] key[{}] value[{}] error",key,JSON.toJSON(value).toString());
logger.error(e.getMessage(),e);
}
return true;
}
};
redisTemplate.execute(sessionCallback);
}
public void set(String key, Serializable value, int alive)
{
try
{
redisTemplate.opsForValue().set(key, value, alive * 1000L, TimeUnit.MILLISECONDS);
}
catch (Exception e)
{
logger.error("==>redis operation [set] key[{}] value[{}] alive[{}] error ",key,JSON.toJSON(value),alive);
logger.error(e.getMessage(),e);
}
}
@Transactional
public void set(String key, Object value, int alive)
{
SessionCallback<Boolean> sessionCallback = new SessionCallback<Boolean>()
{
@SuppressWarnings({ "unchecked" })
@Override
public Boolean execute(RedisOperations operations) throws DataAccessException
{
try
{
do
{
operations.watch(key);
operations.hasKey(key);
operations.multi();
operations.opsForValue().set(key, value, alive * 1000L, TimeUnit.MILLISECONDS);
}
while (operations.exec() == null);
}
catch (Exception e)
{
logger.error("==>redis operation [set] key[{}] value[{}] alive[{}] error ",key,JSON.toJSON(value),alive);
logger.error(e.getMessage(),e);
}
return true;
}
};
redisTemplate.execute(sessionCallback);
}
public Serializable get(String key)
{
return (Serializable) redisTemplate.opsForValue().get(key);
}
public void remove(String key)
{
try
{
redisTemplate.delete(key);
}
catch (Exception e)
{
logger.error("==> redis remove error key [{}]",key);
logger.error(e.getMessage(),e);
}
}
public boolean addCounter(String key)
{
try
{
redisTemplate.opsForValue().increment(key, 1);
}
catch (Exception e)
{
logger.error("==> redis addCounter error key [{}]",key);
logger.error(e.getMessage(),e);
}
return true;
}
public boolean addCounter(String key, long c)
{
try
{
redisTemplate.opsForValue().increment(key, c);
}
catch (Exception e)
{
logger.error("==>redis addCounter error param key [{}] c [{}]",key,c);
logger.error(e.getMessage(), e);
}
return true;
}
public long getCounter(String key)
{
long increment = -1;
try
{
increment = redisTemplate.opsForValue().increment(key, 0);
}
catch (Exception e)
{
logger.error("==> redis getCounter error key [{}]",key);
logger.error(e.getMessage(), e);
}
return increment;
}
public long increment(String key)
{
long increment = -1;
try
{
increment = redisTemplate.opsForValue().increment(key, 1);
}
catch (Exception e)
{
logger.error("==> redis getCounter error key [{}]",key);
logger.error(e.getMessage(), e);
}
return increment;
}
public long incr(String key, long incr)
{
long increment = -1;
try
{
increment = redisTemplate.opsForValue().increment(key, incr);
}
catch (Exception e)
{
logger.error("==> redis getCounter error key [{}] incr [{}]",key,incr);
logger.error(e.getMessage(), e);
}
return increment;
}
public long addOrIncr(String key)
{
SessionCallback<Long> sessionCallback = new SessionCallback<Long>()
{
@SuppressWarnings({ "unchecked"})
@Override
public Long execute(RedisOperations operations) throws DataAccessException
{
Long result = 0L;
try
{
do
{
operations.watch(key);
if (operations.hasKey(key))
{
operations.multi();
operations.opsForValue().increment(key, 1L);
}
else
{
operations.multi();
operations.opsForValue().increment(key, 0L);
}
}
while (operations.exec() == null);
}
catch (Exception e)
{
logger.error("==> redis addOrIncr error key [{}]",key);
logger.error(e.getMessage(), e);
}
return result;
}
};
return redisTemplate.execute(sessionCallback);
}
public long addOrIncr(String key, long incr)
{
long increment = -1;
try
{
increment = redisTemplate.opsForValue().increment(key, incr);
}
catch (Exception e)
{
logger.error("==> redis getCounter error key [{}] incr [{}]",key,incr);
logger.error(e.getMessage(), e);
}
return increment;
}
public long decr(String key)
{
final byte[] rawKey = redisTemplate.getStringSerializer().serialize(key);
return redisTemplate.execute(new RedisCallback<Long>() {
@Override
public Long doInRedis(RedisConnection connection) {
Long decr = -1L;
try
{
decr = connection.decr(rawKey);
}
catch (Exception e)
{
logger.error("==> redis decr error key [{}]",key);
logger.error(e.getMessage(), e);
}
return decr;
}
}, true);
}
public long decr(String key, long decr)
{
final byte[] rawKey = redisTemplate.getStringSerializer().serialize(key);
return redisTemplate.execute(new RedisCallback<Long>() {
@Override
public Long doInRedis(RedisConnection connection) {
Long result = -1L;
try
{
result = connection.decrBy(rawKey, decr);
}
catch (Exception e)
{
logger.error("==> redis decr error key [{}] decr [{}]",key,decr);
logger.error(e.getMessage(), e);
}
return result;
}
}, true);
}
public long addOrDecr(String key)
{
Long decr = 1L;
try
{
decr = redisTemplate.opsForValue().increment(key, 1);
}
catch (Exception e)
{
logger.error("==> redis decr error key [{}] ",key);
logger.error(e.getMessage(), e);
}
return decr;
}
public long addOrDecr(String key, long decr)
{
Long result = -1L;
try
{
result = redisTemplate.opsForValue().increment(key, decr);
}
catch (Exception e)
{
logger.error("==> redis decr error key [{}] decr [{}]",key,decr);
logger.error(e.getMessage(), e);
}
return result;
}
@SuppressWarnings({ "unchecked" })
public void multiSet(Map map)
{
try
{
redisTemplate.opsForValue().multiSet(map);
}
catch (Exception e)
{
logger.error("==> redis multiSet error map [{}]",JSON.toJSON(map).toString());
logger.error(e.getMessage(), e);
}
}
@SuppressWarnings({ "unchecked" })
public List<?> multiGet(List keys)
{
List<?> result = null;
try
{
result = (List<?>) redisTemplate.opsForValue().multiGet(keys);
}
catch (Exception e)
{
logger.error("==> redis multiGet keys [{}]",JSON.toJSON(keys).toString());
logger.error(e.getMessage(), e);
}
return result;
}
@SuppressWarnings("unchecked")
public List<?> getList(String key)
{
List<?> result = null;
try
{
result = (List<?>)redisTemplate.opsForList().range(key, 0, -1);
}
catch (Exception e)
{
logger.error("==> redis multiGet key [{}]",key);
logger.error(e.getMessage(), e);
}
return result;
}
@SuppressWarnings("unchecked")
public void setList(String key, List lst)
{
if(lst != null && !lst.isEmpty())
{
try
{
}
catch (Exception e)
{
logger.error("==> redis setList error key [{}] lst [{}] ",key,JSON.toJSON(lst).toString());
logger.error(e.getMessage(), e);
}
redisTemplate.opsForList().leftPushAll(key, lst);
}
}
public void setObjList(String key, Object obj)
{
try
{
redisTemplate.opsForList().rightPush(key, obj);
}
catch (Exception e)
{
logger.error("==> redis setObjList error key [{}] obj [{}]",key ,JSON.toJSON(obj).toString());
logger.error(e.getMessage(), e);
}
}
public void removeObjList(String key, Object obj)
{
try
{
redisTemplate.delete(key);
}
catch (Exception e)
{
logger.error("==> redis removeObjList error key [{}] obj [{}]",key ,JSON.toJSON(obj).toString());
logger.error(e.getMessage(), e);
}
}
public Set keys(String pattern)
{
Set<String> keys = null;
try
{
keys = redisTemplate.keys(pattern);
}
catch (Exception e)
{
logger.error("==> redis keys error pattern [{}] obj [{}]",pattern);
logger.error(e.getMessage(), e);
}
return keys;
}
public boolean expire(String key, long alive)
{
Boolean result = false;
try
{
result = redisTemplate.expire(key, alive, TimeUnit.SECONDS);
}
catch (Exception e)
{
logger.error("==> redis expire error key [{}] alive [{}]",key,alive);
logger.error(e.getMessage(), e);
}
return result;
}
public void setForDay(String key,Object value,Long timeOut)
{
redisTemplate.opsForValue().set(key,value,timeOut, TimeUnit.DAYS);
}
}
工具类中提供丰富的Api方法,针对不同的需求直接调用即可。
测试
@RestController
@RequestMapping("/redis")
public class TestController
{
@Autowired
private RedisUtil redisUtil;
@RequestMapping("/setandGetValue")
public String setandGetValue(@RequestParam String value)
{
redisUtil.set("fw.redis.test", value);
return (String) redisUtil.get("fw.redis.test");
}
}
运行程序,访问setandGetValue即可
总结
本文讲解了SpringBoot与Redis的简单集成,Redis还提供了其他丰富的功能,例如实现分布式锁、消息队列等,后续的文章中将一一讲解。