Springboot整合Redis(默認Lettuce版本)、自定義Reids客戶端


----------------------------導航目錄--------------------------------

一、簡單使用,序列化開啟緩存機制使用

二、lua腳本實現Reids分布式鎖

三、reids客戶端管道使用

四、reids訂閱發布應用

-------------------------------------------------------------------

一、簡單使用,序列化開啟緩存機制使用

1、引入pom文件

  注意版本號的區別哦~,小心采坑,根據需要選型版本號springboot客戶端reids版本選擇

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

2、添加配置文件application.yml或者properties

spring:
  redis:
    database: 0
    host: localhost
    port: 6379
   # 連接超時時間 單位 ms(毫秒)
    timeout: 3000
    password:
    lettuce:
      pool:
        #連接池中的最大空閑連接,默認值也是0。
        max-idle: 8
        #連接池中的最小空閑連接,默認值也是0。
        min-idle: 0
        # 如果賦值為-1,則表示不限制;如果pool已經分配了maxActive個jedis實例,則此時pool的狀態為exhausted(耗盡)。
        max-active: 8
        # 等待可用連接的最大時間,單位毫秒,默認值為-1,表示永不超時。如果超過等待時間,則直接拋出異常
        max-wait: -1

單純使用的話到這里就可以直接注入RedisTemplate對象使用了,我這里對接下來的使用序列化和緩存注解的使用進行了配置

3、創建Reids客戶端,並指定序列化方式並且開啟緩存注解的使用

  第一種,使用springboot封裝的RedisTemplate(推薦)

  這里引入了3個類如下:

  CacheTtl 枚舉類型用來定義緩存的時間

  FastJsonRedisSerializer指定序列化方式,注意引入的包用戶排除一下問題

  RedisConfig 配置RedisTemplate對象的序列化和cache管理對象 。注解使用:SpringBoot進階教程(五十三)整合Redis之@Cacheable、@CachePut、@CacheEvict的應用

package com.niu.reids;

/**
 * @author niunafei
 * @function
 * @email niunafei0315@163.com
 * @date 2020/5/19  3:26 PM
 */
public enum CacheTtl {

    /**
     * 1小時
     */
    ONE_HOUR("oneHour", 1),
    /**
     * 1天
     */
    ONE_DAY("oneDay", 24),
    /**
     * 2天
     */
    TWO_DAYS("twoDays", 48),
    /**
     * 1周
     */
    ONE_WEEK("oneWeek", 168);

    private String value;

    private Integer time;

    CacheTtl(String value, Integer time) {
        this.value = value;
        this.time = time;
    }

    public String getValue() {
        return value;
    }

    public Integer getTime() {
        return time;
    }
}
View Code
package com.niu.reids;

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 org.springframework.lang.Nullable;

import java.nio.charset.Charset;

/**
 * @author niunafei
 * @function
 * @email niunafei0315@163.com
 * @date 2020/5/19  3:19 PM
 */
public class FastJsonRedisSerializer<T> implements RedisSerializer<T> {

    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");

    private Class<T> clazz;

    /**
     *  解決fastJson autoType is not support錯誤
     */
    static {
        ParserConfig.getGlobalInstance().addAccept("com.niu");
    }

    public FastJsonRedisSerializer(Class<T> clazz) {
        super();
        this.clazz = clazz;
    }

    /**
     * 指定序列化方式,用與@Cacheable 注解序列化
     * @param t
     * @return
     * @throws SerializationException
     */
    @Nullable
    @Override
    public byte[] serialize(T t) throws SerializationException {
        if (t == null) {
            return new byte[0];
        }
        return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
    }

    /**
     * 反序列化方式
     * @param bytes
     * @return
     * @throws SerializationException
     */
    @Nullable
    @Override
    public T deserialize(byte[] bytes) throws SerializationException {
        if (bytes == null || bytes.length <= 0) {
            return null;
        }
        String str = new String(bytes, DEFAULT_CHARSET);
        return JSON.parseObject(str, clazz);
    }

}
View Code
package com.niu.reids;

import com.alibaba.fastjson.support.spring.FastJsonRedisSerializer;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.*;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.time.Duration;
import java.util.HashMap;
import java.util.Map;

/**
 * @author niunafei
 * @function
 * @email niunafei0315@163.com
 * @date 2020/5/19  3:19 PM
 */
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {

    /**
     * 配置reids 對象
     *
     * @param factory
     * @return
     */
    @Bean
    public RedisTemplate redisTemplate(LettuceConnectionFactory factory) {
        RedisTemplate redisTemplate = new RedisTemplate();
        redisTemplate.setConnectionFactory(factory);
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new StringRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(getJsonRedisSerializer());
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }

    /**
     * 創建緩存管理器對象
     *
     * @param factory
     * @return
     */
    @Bean
    public CacheManager cacheManager(LettuceConnectionFactory factory) {
        // 默認過期時間1小時
        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig().disableKeyPrefix()
                .entryTtl(Duration.ofHours(1))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(getJsonRedisSerializer()));
        return RedisCacheManager
                .builder(RedisCacheWriter.nonLockingRedisCacheWriter(factory))
                .cacheDefaults(redisCacheConfiguration)
                .withInitialCacheConfigurations(getRedisCacheConfigurationMap())
                .build();
    }

    /**
     * 創建緩存注解中的value屬性集合 @Cacheable(value = "oneDay", key = "'b_v'", unless = "#result == null")
     *
     * @return
     */
    private Map<String, RedisCacheConfiguration> getRedisCacheConfigurationMap() {
        Map<String, RedisCacheConfiguration> redisCacheConfigurationMap = new HashMap<>(CacheTtl.values().length);
        for (CacheTtl cacheTtl : CacheTtl.values()) {
            redisCacheConfigurationMap.put(cacheTtl.getValue(), this.getRedisCacheConfigurationWithTtl(1));
        }
        return redisCacheConfigurationMap;
    }

    /**
     * 通過事件創建緩存配置對象,並且指定序列化方式
     *
     * @param hours
     * @return
     */
    private RedisCacheConfiguration getRedisCacheConfigurationWithTtl(Integer hours) {
        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig().disableKeyPrefix()
                .entryTtl(Duration.ofHours(hours))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(getJsonRedisSerializer()));
        return redisCacheConfiguration;
    }

    /**
     * 創建序列化對象使用fastJson
     *
     * @return
     */
    private FastJsonRedisSerializer getJsonRedisSerializer() {
        return new FastJsonRedisSerializer<>(Object.class);
    }

}
View Code

  第二種、自定義客戶端,也是基於RedisTemplate自定義。

  由於自定義需要引入commons-pool2 創建鏈接池,廣告->Apache Commons Lang 3.10使用簡介 

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
            <version>2.5.0</version>
        </dependency>
View Code

  然后創建MyRedisTemplate類創建客戶端,當然這是簡單配置 

package com.niu.reids;

import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * @author niunafei
 * @function
 * @email niunafei0315@163.com
 * @date 2020/5/19  3:50 PM
 */
@Configuration
public class MyRedisTemplate {

    @Value("${spring.redis.database}")
    Integer database;
    @Value("${spring.redis.host}")
    String host;
    @Value("${spring.redis.port}")
    Integer port;
    @Value("${spring.redis.password}")
    String password;
    @Value("${spring.redis.lettuce.pool.max-active}")
    Integer maxActive;
    @Value("${spring.redis.lettuce.pool.max-wait}")
    Long maxWait;
    @Value("${spring.redis.lettuce.pool.max-idle}")
    Integer maxIdle;
    @Value("${spring.redis.lettuce.pool.min-idle}")
    Integer minIdle;
    @Value("${spring.redis.timeout}")
    String timeout;

    @Bean("backupRedisTemplate")
    public RedisTemplate backupRedisTemplate() {
        //定義redis鏈接池
        GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
        poolConfig.setMaxTotal(maxActive);
        poolConfig.setMaxWaitMillis(maxWait);
        poolConfig.setMaxIdle(maxIdle);
        poolConfig.setMinIdle(minIdle);
        //配置對象
        RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
        config.setDatabase(database);
        config.setHostName(host);
        config.setPort(port);
        config.setPassword(password);
        //創建Lettuce工廠
        LettuceClientConfiguration clientConfiguration = LettucePoolingClientConfiguration.builder().poolConfig(poolConfig).build();
        LettuceConnectionFactory factory = new LettuceConnectionFactory(config, clientConfiguration);
        factory.afterPropertiesSet();
        //創建客戶端並指定序列化方式
        RedisTemplate redisTemplate = new RedisTemplate();
        redisTemplate.setConnectionFactory(factory);
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new StringRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        //這里序列化方式自己定義,
        redisTemplate.setHashValueSerializer(new FastJsonRedisSerializer<>(Object.class));
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
}
View Code

4、這樣就可以使用了,自定義也可以添加 @Qualifier("myRedisTemplate")指定加載的客戶端對象

   @Autowired
    private RedisTemplate redisTemplate;

二、lua腳本實現Reids分布式鎖

分布式鎖就需要保持其原子性,這里使用lua腳本完成,Lua腳本在redis分布式鎖場景的運用

package com.niu.reids;

import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.connection.ReturnType;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.types.Expiration;

/**
 * @author niunafei
 * @function
 * @email niunafei0315@163.com
 * @date 2020/5/19  4:03 PM
 */
public class RedisLockService {

    private RedisTemplate<String, String> redisTemplate;

    private static final String UNLOCK_LUA;

    private final static int NUM_KEYS = 1;

    static {
        StringBuilder sb = new StringBuilder();
        sb.append("if redis.call('get', KEYS[1]) == ARGV[1] then");
        sb.append(" return redis.call('del', KEYS[1])");
        sb.append(" else return 0 end");
        UNLOCK_LUA = sb.toString();
    }

    public RedisLockService(RedisTemplate<String, String> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    /**
     * 嘗試獲取分布式鎖
     *
     * @param key        鎖名稱
     * @param val        請求標識
     * @param expireTime 超期時間 秒
     * @return 是否獲取成功
     */
    public boolean tryLock(String key, String val, int expireTime) {
        RedisCallback<Boolean> callback = (connection) ->
                connection.set(key.getBytes(), val.getBytes(), Expiration.seconds(expireTime), RedisStringCommands.SetOption.SET_IF_ABSENT);
        boolean result = redisTemplate.execute(callback);
        return result;
    }

    /**
     * 釋放分布式鎖
     *
     * @param key 鎖名稱
     * @param val 請求標識 只有標識相同才能解鎖
     * @return 是否釋放成功
     */
    public boolean releaseLock(String key, String val) {
        RedisCallback<Long> callback = (connection) ->
                connection.eval(UNLOCK_LUA.getBytes(), ReturnType.INTEGER, NUM_KEYS, key.getBytes(), val.getBytes());
        Long result = redisTemplate.execute(callback);
        return result != null && result > 0;
    }
}

三、reids客戶端管道使用

  使用executePipelined方法實現RedisCallback接口,並且指定泛型為Integer,具體業務根據業務對象來實現

    /**
     * 插入多條數據
     *
     * @param saveList
     * @param unit
     * @param timeout
     */
    public void batchInsert(List<Map<String, String>> saveList, TimeUnit unit, int timeout) {
        redisTemplate.executePipelined(new SessionCallback<Object>() {
            @Override
            public <K, V> Object execute(RedisOperations<K, V> redisOperations) throws DataAccessException {
                ValueOperations<String, String> value = (ValueOperations<String, String>) redisOperations.opsForValue();
                for (Map<String, String> needSave : saveList) {
                    value.set(needSave.get("key"), needSave.get("value"), timeout, unit);
                }
                return null;
            }
        });
    }

    /**
     * 批量獲取多條數據
     *
     * @param keyList
     * @return
     */
    public List<String> batchGet(List<String> keyList) {
        List<Object> objects = redisTemplate.executePipelined(new RedisCallback<String>() {
            @Override
            public String doInRedis(RedisConnection redisConnection) throws DataAccessException {
                StringRedisConnection stringRedisConnection = (StringRedisConnection) redisConnection;
                for (String key : keyList) {
                    stringRedisConnection.get(key);
                }
                return null;
            }
        }, redisTemplate.getValueSerializer());

        List<String> collect = objects.stream().map(val -> String.valueOf(val)).collect(Collectors.toList());
        return collect;
    }

 

  異常問題:java.lang.ClassCastException: com.sun.proxy.$Proxy77 cannot be cast to org.springframework.data.redis.connection.StringRedisConnection。

  原因是:RedisTemplate的泛型不正確,導致沒辦法強轉。

  處理方案:如下面一樣泛型的key和value為String類型。

  @Autowired
    private RedisTemplate<String,String> redisTemplate;

四、redis訂閱與發布

1、 會用到的方法與類

  *  RedisMessageListenerContainer Redis訂閱發布的監聽容器,你的消息發布、訂閱配置都必須在這里面實現

  * addMessageListener(MessageListenerAdapter,PatternTopic) 新增訂閱頻道及訂閱者,訂閱者必須有相關方法處理收到的消息。
  * setTopicSerializer(RedisSerializer) 對頻道內容進行序列化解析

  MessageListenerAdapter 監聽適配器

  MessageListenerAdapter(Object , defaultListenerMethod) 訂閱者及其方法

  redisTemplate redis模版類中:convertAndSend(String channel, Object message) 消息發布

 2、創建兩個Bean,一個是注冊監聽適配器訂閱頻道,一個創建監聽適配器

    /**
     * 注冊監聽適配器,訂閱頻道
     * @param connectionFactory
     * @param listenerAdapter
     * @return
     */
    @Bean
    public RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory,
                                            MessageListenerAdapter listenerAdapter) {
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        // 訂閱頻道
        container.addMessageListener(listenerAdapter, new PatternTopic("topic_test"));
        //指定頻道內容序列化
        container.setTopicSerializer(getJsonRedisSerializer());
        return container;
    }

    /**
     * 創建監聽適配器
     * @param receiver
     * @return
     */
    @Bean
    public MessageListenerAdapter listenerAdapter(MessageReceiver receiver) {
        //創建監聽適配器,指定訂閱者及其方法
        return new MessageListenerAdapter(receiver, "receiveMessage");
    }

3、創建訂閱者

package com.niu.reids;

import org.springframework.stereotype.Component;

/**
 * @author niunafei
 * @function
 * @email niunafei0315@163.com
 * @date 2020/5/19  6:17 PM
 */
@Component
public class MessageReceiver {

    public void receiveMessage(String message, String channel) {
        System.out.println(Thread.currentThread().getName()+" topic: "+channel + " 收到消息: " + message);
    }
}

4、進行發送消息,這里引入springboot的web包使用http請求添加信息,也可以使用單元測試。重點是for循環中的代碼,指定頻道進行發送

  @GetMapping("/send")
    public Object send() {
        List<String> keys = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            redisTemplate.convertAndSend("topic_test", "測試"+i);
        }
        return "ok";
    }

看看結果吧

 

maven配置阿里雲倉庫鏡像,加速下載maven依賴

SpringBoot進階教程(五十四)整合Redis之共享Session

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM