----------------------------導航目錄--------------------------------
一、簡單使用,序列化開啟緩存機制使用
二、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; } }

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); } }

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); } }
第二種、自定義客戶端,也是基於RedisTemplate自定義。
由於自定義需要引入commons-pool2 創建鏈接池,廣告->Apache Commons Lang 3.10使用簡介

<dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> <version>2.5.0</version> </dependency>
然后創建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; } }
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"; }
看看結果吧
SpringBoot進階教程(五十四)整合Redis之共享Session