Spring Boot集成Redis

450 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第3天,点击查看活动详情

背景

项目中为了提升性能通常都会用到缓存,而java提供了丰富的缓存实现方式,有本地缓存(Cache),分布式缓存(Redis)等,本文将讲解Spring Boot如何集成Redis。

简介

Redis 全称是 Remote Dictionary Server,是完全开源免费的,是一个高性能的key-value类型的内存数据库。

特性

  1. Reid单线程IO复路、完全基于内存操作所以速度非常快。
  2. 支持丰富数据类型,支持String(字符串)、hash(哈希)、 list(列表)、set(集合)、zset(有序集合)、Bitmaps(位图)、 HyperLogLog、GEO(地理信息定位)等多种数据结构
  3. 丰富的特性:既可以做缓存使用,也可以用于消息队列(支持 publish/subscribe类型消息通知)。
  4. 丰富的数据持久化方案,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用;
  5. 丰富高可用的架构方案,主从、哨兵、集群等模式。

集成步骤

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即可 图片.png

总结

本文讲解了SpringBoot与Redis的简单集成,Redis还提供了其他丰富的功能,例如实现分布式锁、消息队列等,后续的文章中将一一讲解。