SpringBoot--實戰(zhàn)開發(fā)--整合Redis(十四)

一、Redis簡介

Redis是當前比較熱門的NOSQL系統(tǒng)之一,它是一個開源的使用ANSI c語言編寫的key-value存儲系統(tǒng)(區(qū)別于MySQL的二維表格的形式存儲。)。和Memcache類似,但很大程度補償了Memcache的不足。和Memcache一樣,Redis數(shù)據(jù)都是緩存在計算機內存中,不同的是,Memcache只能將數(shù)據(jù)緩存到內存中,無法自動定期寫入硬盤,這就表示,一斷電或重啟,內存清空,數(shù)據(jù)丟失。所以Memcache的應用場景適用于緩存無需持久化的數(shù)據(jù)。而Redis不同的是它會周期性的把更新的數(shù)據(jù)寫入磁盤或者把修改操作寫入追加的記錄文件,實現(xiàn)數(shù)據(jù)的持久化。
https://spring.io/projects/spring-data-redis

Redis的特點:
Redis讀取的速度是110000次/s,寫的速度是81000次/s
原子 。Redis的所有操作都是原子性的,同時Redis還支持對幾個操作全并后的原子性執(zhí)行。
支持多種數(shù)據(jù)結構:string(字符串);list(列表);hash(哈希),set(集合);zset(有序集合)
持久化,主從復制(集群)
支持過期時間,支持事務,消息訂閱。
官方不支持window,但是又第三方版本。
Redis與memcache的區(qū)別:
1、Redis和Memcache都是將數(shù)據(jù)存放在內存中,都是內存數(shù)據(jù)庫。不過memcache還可用于緩存其他東西,例如圖片、視頻等等。
2、Redis不僅僅支持簡單的k/v類型的數(shù)據(jù),同時還提供list,set,hash等數(shù)據(jù)結構的存儲。
3、虛擬內存--Redis當物理內存用完時,可以將一些很久沒用到的value 交換到磁盤
4、過期策略--memcache在set時就指定,例如set key1 0 0 8,即永不過期。Redis可以通過例如expire 設定,例如expire name 10
5、分布式--設定memcache集群,利用magent做一主多從;redis可以做一主多從。都可以一主一從
6、存儲數(shù)據(jù)安全--memcache掛掉后,數(shù)據(jù)沒了;redis可以定期保存到磁盤(持久化)
7、災難恢復--memcache掛掉后,數(shù)據(jù)不可恢復; redis數(shù)據(jù)丟失后可以通過aof恢復
8、Redis支持數(shù)據(jù)的備份,即master-slave模式的數(shù)據(jù)備份。

Lettuce與Jedis 比較:
Jedis 是直連模式,在多個線程間共享一個 Jedis 實例時是線程不安全的,如果想要在多線程環(huán)境下使用 Jedis,需要使用連接池,
每個線程都去拿自己的 Jedis 實例,當連接數(shù)量增多時,物理連接成本就較高了。
Lettuce的連接是基于Netty的,連接實例可以在多個線程間共享,所以,一個多線程的應用可以使用同一個連接實例,而不用擔心并發(fā)線程的數(shù)量。當然這個也是可伸縮的設計,一個連接實例不夠的情況也可以按需增加連接實例。通過異步的方式可以讓我們更好的利用系統(tǒng)資源,而不用浪費線程等待網(wǎng)絡或磁盤I/O。

二、Maven依懶

<!-- lettuce pool 緩存連接池 -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>
<!-- spring boot redis 緩存引入 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

三、Redis配置

application.properties

# Redis數(shù)據(jù)庫索引(默認為0)
spring.redis.database=0  
# Redis服務器地址
spring.redis.host=127.0.0.1
# Redis服務器連接端口
spring.redis.port=6379  
# Redis服務器連接密碼(默認為空)
spring.redis.password=
# 連接池最大連接數(shù)(使用負值表示沒有限制)
spring.redis.lettuce.pool.max-active=200  
# 連接池最大阻塞等待時間(使用負值表示沒有限制)
spring.redis.lettuce.pool.max-wait=-1ms
# 連接池中的最大空閑連接
spring.redis.lettuce.pool.max-idle=10 
# 連接池中的最小空閑連接
spring.redis.lettuce.pool.min-idle=0  
# 連接超時時間(毫秒)
spring.redis.timeout=10000ms

三、Redis配置類

  1. 自動配置類:
@Configuration
public class RedisConfiguration {
    @Resource
    private LettuceConnectionFactory myLettuceConnectionFactory;
    @Bean
    public RedisTemplate<String, Serializable> redisTemplate() {
        RedisTemplate<String, Serializable> template = new RedisTemplate<>();
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        template.setConnectionFactory(myLettuceConnectionFactory);
        return template;
    }
}
  1. 實體類
@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
@Table(name = "t_user")
public class User implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(length = 20)
    private String username;

    @Column
    @DateTimeFormat(pattern="yyyy-MM-dd")
    private Date createtime;
}

  1. 緩存測試
@Slf4j
public class UserServiceTest extends BaseApplicationTests {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Resource
    private RedisTemplate<String, Serializable> redisCacheTemplate;

    @Test
    public void redisTest() {
        // TODO 測試線程安全
        ExecutorService executorService = Executors.newFixedThreadPool(1000);
        IntStream.range(0, 1000).forEach(i ->
                executorService.execute(() -> stringRedisTemplate.opsForValue().increment("kk", 1))
        );
        stringRedisTemplate.opsForValue().set("k1", "v1");
        final String k1 = stringRedisTemplate.opsForValue().get("k1");
        log.info("[字符緩存結果] - [{}]", k1);
        // TODO
        String key = "battcn:user:1";
        redisCacheTemplate.opsForValue().set(key, new User(1L, "hjj", new Date()));
        // TODO 對應 String(字符串)
        final User user = (User) redisCacheTemplate.opsForValue().get(key);
        log.info("[對象緩存結果] - [{}]", user);
    }
}
結果

Redis數(shù)據(jù)類型對應的操作方式:
opsForValue: 對應 String(字符串)
opsForZSet: 對應 ZSet(有序集合)
opsForHash: 對應 Hash(哈希)
opsForList: 對應 List(列表)
opsForSet: 對應 Set(集合)

四、 Redis工具類

直接用RedisTemplate操作Redis,需要很多行代碼,因此直接封裝好一個RedisUtils,這樣寫代碼更方便點。

import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.CollectionUtils;

import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * Redis工具類
 */
@Component
public class RedisUtil {
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    public void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }
    //=============================common============================

    /**
     * 指定緩存失效時間
     *
     * @param key  鍵
     * @param time 時間(秒)
     * @return
     */
    public 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;
        }
    }

    /**
     * 根據(jù)key 獲取過期時間
     *
     * @param key 鍵 不能為null
     * @return 時間(秒) 返回0代表為永久有效
     */
    public long getExpire(String key) {
        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
    }

    /**
     * 判斷key是否存在
     *
     * @param key 鍵
     * @return true 存在 false不存在
     */
    public boolean hasKey(String key) {
        try {
            return redisTemplate.hasKey(key);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 刪除緩存
     *
     * @param key 可以傳一個值 或多個
     */
    @SuppressWarnings("unchecked")
    public void del(String... key) {
        if (key != null && key.length > 0) {
            if (key.length == 1) {
                redisTemplate.delete(key[0]);
            } else {
                redisTemplate.delete(CollectionUtils.arrayToList(key));
            }
        }
    }

    //============================String=============================

    /**
     * 普通緩存獲取
     *
     * @param key 鍵
     * @return 值
     */
    public Object get(String key) {
        return key == null ? null : redisTemplate.opsForValue().get(key);
    }

    /**
     * 普通緩存放入
     *
     * @param key   鍵
     * @param value 值
     * @return true成功 false失敗
     */
    public 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 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
     */
    public long incr(String key, long delta) {
        if (delta < 0) {
            throw new RuntimeException("遞增因子必須大于0");
        }
        return redisTemplate.opsForValue().increment(key, delta);
    }

    /**
     * 遞減
     *
     * @param key   鍵
     * @param delta 要減少幾(小于0)
     * @return
     */
    public long decr(String key, long delta) {
        if (delta < 0) {
            throw new RuntimeException("遞減因子必須大于0");
        }
        return redisTemplate.opsForValue().increment(key, -delta);
    }

    //================================Map=================================

    /**
     * HashGet
     *
     * @param key  鍵 不能為null
     * @param item 項 不能為null
     * @return 值
     */
    public Object hget(String key, String item) {
        return redisTemplate.opsForHash().get(key, item);
    }

    /**
     * 獲取hashKey對應的所有鍵值
     *
     * @param key 鍵
     * @return 對應的多個鍵值
     */
    public Map<Object, Object> hmget(String key) {
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * HashSet
     *
     * @param key 鍵
     * @param map 對應多個鍵值
     * @return true 成功 false 失敗
     */
    public 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 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表中放入數(shù)據(jù),如果不存在將創(chuàng)建
     *
     * @param key   鍵
     * @param item  項
     * @param value 值
     * @return true 成功 false失敗
     */
    public 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表中放入數(shù)據(jù),如果不存在將創(chuàng)建
     *
     * @param key   鍵
     * @param item  項
     * @param value 值
     * @param time  時間(秒)  注意:如果已存在的hash表有時間,這里將會替換原有的時間
     * @return true 成功 false失敗
     */
    public 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 void hdel(String key, Object... item) {
        redisTemplate.opsForHash().delete(key, item);
    }

    /**
     * 判斷hash表中是否有該項的值
     *
     * @param key  鍵 不能為null
     * @param item 項 不能為null
     * @return true 存在 false不存在
     */
    public boolean hHasKey(String key, String item) {
        return redisTemplate.opsForHash().hasKey(key, item);
    }

    /**
     * hash遞增 如果不存在,就會創(chuàng)建一個 并把新增后的值返回
     *
     * @param key  鍵
     * @param item 項
     * @param by   要增加幾(大于0)
     * @return
     */
    public double hincr(String key, String item, double by) {
        return redisTemplate.opsForHash().increment(key, item, by);
    }

    /**
     * hash遞減
     *
     * @param key  鍵
     * @param item 項
     * @param by   要減少記(小于0)
     * @return
     */
    public double hdecr(String key, String item, double by) {
        return redisTemplate.opsForHash().increment(key, item, -by);
    }

    //============================set=============================

    /**
     * 根據(jù)key獲取Set中的所有值
     *
     * @param key 鍵
     * @return
     */
    public Set<Object> sGet(String key) {
        try {
            return redisTemplate.opsForSet().members(key);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 根據(jù)value從一個set中查詢,是否存在
     *
     * @param key   鍵
     * @param value 值
     * @return true 存在 false不存在
     */
    public boolean sHasKey(String key, Object value) {
        try {
            return redisTemplate.opsForSet().isMember(key, value);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 將數(shù)據(jù)放入set緩存
     *
     * @param key    鍵
     * @param values 值 可以是多個
     * @return 成功個數(shù)
     */
    public long sSet(String key, Object... values) {
        try {
            return redisTemplate.opsForSet().add(key, values);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 將set數(shù)據(jù)放入緩存
     *
     * @param key    鍵
     * @param time   時間(秒)
     * @param values 值 可以是多個
     * @return 成功個數(shù)
     */
    public 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 0;
        }
    }

    /**
     * 獲取set緩存的長度
     *
     * @param key 鍵
     * @return
     */
    public long sGetSetSize(String key) {
        try {
            return redisTemplate.opsForSet().size(key);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 移除值為value的
     *
     * @param key    鍵
     * @param values 值 可以是多個
     * @return 移除的個數(shù)
     */
    public long setRemove(String key, Object... values) {
        try {
            Long count = redisTemplate.opsForSet().remove(key, values);
            return count;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }
    //===============================list=================================

    /**
     * 獲取list緩存的內容
     *
     * @param key   鍵
     * @param start 開始
     * @param end   結束  0 到 -1代表所有值
     * @return
     */
    public 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
     */
    public long lGetListSize(String key) {
        try {
            return redisTemplate.opsForList().size(key);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 通過索引 獲取list中的值
     *
     * @param key   鍵
     * @param index 索引  index>=0時, 0 表頭,1 第二個元素,依次類推;index<0時,-1,表尾,-2倒數(shù)第二個元素,依次類推
     * @return
     */
    public 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
     */
    public 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
     */
    public 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
     */
    public 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
     */
    public 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;
        }
    }

    /**
     * 根據(jù)索引修改list中的某條數(shù)據(jù)
     *
     * @param key   鍵
     * @param index 索引
     * @param value 值
     * @return
     */
    public 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 移除的個數(shù)
     */
    public long lRemove(String key, long count, Object value) {
        try {
            Long remove = redisTemplate.opsForList().remove(key, count, value);
            return remove;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }
}

測試:

    @Autowired
    private RedisUtil redisUtil;
    @Test
    public void test(){
        redisUtil.set("test","1234");
    }

五、注解式緩存

(一) 配置

配置類上添加注解,啟用緩存:

@EnableCaching

(三) 注解使用

https://docs.spring.io/spring/docs/3.0.x/reference/expressions.html

1. @Cacheable

從緩存中查詢指定的key,如果有,從緩存中取,不再執(zhí)行方法.如果沒有則執(zhí)行方法,并且將方法的返回值和指定的key關聯(lián)起來,放入到緩存中:
主要參數(shù)說明:
(1)value :
緩存的名稱,在 spring 配置文件中定義,必須指定至少一個,例如:@Cacheable(value=”mycache”) 或者 @Cacheable(value={”cache1”,”cache2”}。
(2)key :緩存的 key,可以為空,如果指定要按照 SpEL 表達式編寫,如果不指定,則缺省按照方法的所有參數(shù)進行組合,例如:@Cacheable(value=”testcache”,key=”#userName”)。
(3)condition :(哪種情況緩存)緩存的條件,可以為空,使用 SpEL 編寫,返回 true 或者 false,只有為 true 才進行緩存,例如:@Cacheable(value=”testcache”,condition=”#userName.length()>2。
(4)unless (哪種情況不緩存)
//為空不緩存
unless = "#result==null"
//返回值為true進行緩存
unless = "!#result"
其中#result,代指函數(shù)的返回值。非(!)(返回值),都進行緩存。 就是說,當返回值不是false時,才進行緩存。
(5)Spring Cache提供的@Cacheable注解不支持配置過期時間,還有緩存的自動刷新。
https://github.com/yantrashala/spring-cache-self-refresh

    @Override
    @Cacheable(value="userredis", key="'users_'+#id")
    public User getUser(Long id) {
        Optional<User> optionalUser = userRepository.findById(id);
        if(optionalUser.isPresent()){
            return optionalUser.get();
        }
        return null;
    }

用@Cacheable的value屬性指定具體緩存,并通過key將其放入緩存中.這里key非常靈活,支持spring的el表達式,可以通過方法參數(shù)產生可變的key。

2. @CacheEvict

@CacheEvict則是從緩存中清除指定的key對應的數(shù)據(jù):
主要參數(shù)說明:
(1)value , key 和 condition 參數(shù)配置和@Cacheable一樣。
(2)allEntries :是否清空所有緩存內容,缺省為 false,如果指定為 true,則方法調用后將立即清空所有緩存,例如:@CachEvict(value=”testcache”,allEntries=true)。
(3)beforeInvocation :是否在方法執(zhí)行前就清空,缺省為 false,如果指定為 true,則在方法還沒有執(zhí)行的時候就清空緩存,缺省情況下,如果方法執(zhí)行拋出異常,則不會清空緩存,例如@CachEvict(value=”testcache”,beforeInvocation=true)。

 @CacheEvict(value="thisredis", key="'users_'+#id",condition="#id!=1")
  public void delUser(Integer id) {
        // 刪除user
        System.out.println("user刪除");
   }
3. @CachePut

@CachePut:作用是主要針對方法配置,能夠根據(jù)方法的請求參數(shù)對其結果進行緩存,和 @Cacheable 不同的是,它每次都會觸發(fā)真實方法的調用 :
主要參數(shù)說明:
(1)value , key 和 condition 參數(shù)配置和@Cacheable一樣。

 @CachePut("users")//每次都會執(zhí)行方法,并將結果存入指定的緩存中
   public User find(Integer id) {
      return null;
   }

附:

六、Redis集群配置

1. 配置application.properties

# (普通集群,不使用則不用開啟)以逗號分隔的“主機:端口”對列表進行引導。
spring.redis.cluster.nodes=127.0.0.1:6380,127.0.0.1:6381,127.0.0.1:6382,127.0.0.1:6383,127.0.0.1:6384,127.0.0.1:6385
spring.redis.cluster.timeout=1000
# (普通集群,不使用則不用開啟)在群集中執(zhí)行命令時要遵循的最大重定向數(shù)目。
spring.redis.cluster.max-redirects=3

注意:一旦開啟了集群模式,那么基于單機的配置就會覆蓋。

2. 配置類

RedisConfiguration.java

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import javax.annotation.Resource;
import java.io.Serializable;

@Configuration
public class RedisConfiguration {
    @Resource
    private LettuceConnectionFactory myLettuceConnectionFactory;
    @Bean
    public RedisTemplate<String, Serializable> redisTemplate() {
        RedisTemplate<String, Serializable> template = new RedisTemplate<>();
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        template.setConnectionFactory(myLettuceConnectionFactory);
        return template;
    }
}

RedisFactoryConfig.java

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.data.redis.connection.RedisClusterConfiguration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;

import java.util.HashMap;
import java.util.Map;

@Configuration
public class RedisFactoryConfig {
    @Autowired
    private Environment environment;
    @Bean
    public RedisConnectionFactory myLettuceConnectionFactory() {
        Map<String, Object> source = new HashMap<String, Object>();
        source.put("spring.redis.cluster.nodes", environment.getProperty("spring.redis.cluster.nodes"));
        source.put("spring.redis.cluster.timeout", environment.getProperty("spring.redis.cluster.timeout"));
        source.put("spring.redis.cluster.max-redirects", environment.getProperty("spring.redis.cluster.max-redirects"));
        RedisClusterConfiguration redisClusterConfiguration;
        redisClusterConfiguration = new RedisClusterConfiguration(new MapPropertySource("RedisClusterConfiguration", source));
        return new LettuceConnectionFactory(redisClusterConfiguration);
    }
}

3. 測試

@Slf4j
public class UserServiceTest extends BaseApplicationTests {
    @Autowired
    private RedisTemplate redisTemplate;
    @Test
    public void redisTemplate() throws Exception {
        redisTemplate.opsForValue().set("author", "houjianjun");
    }

}

結果:


測試結果

常見錯誤:
java.lang.NoClassDefFoundError: org/apache/commons/pool2/impl/GenericObjectPoolConfig
GenericObjectPoolConfig沒有找到是因為spring-data-redis版本太高,去掉指定的version即可。
pringboot2.0的redis整合包多出lettuce連接池,需要commons-pool2,所以項目pom依賴要添加commons-pool2

<!-- 高版本redis的lettuce需要commons-pool2 -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>
  1. io.lettuce.core.RedisException: Connection closed
    找到redis的配置文件 redis.conf
    vim redis.conf

修改 protected-mode yes
改為
protected-mode no

附:

SpringBoot集成redisson(單機,集群,哨兵)

  1. application.properties
spring.redis.database=0
spring.redis.password=
spring.redis.timeout=3000
#sentinel/cluster/single
spring.redis.mode=single
#連接池配置
spring.redis.pool.max-idle=16
spring.redis.pool.min-idle=8
spring.redis.pool.max-active=8
spring.redis.pool.max-wait=3000
spring.redis.pool.conn-timeout=3000
spring.redis.pool.so-timeout=3000
spring.redis.pool.size=10
#單機配置
spring.redis.single.address=192.168.60.23:6379
#集群配置
spring.redis.cluster.scan-interval=1000
spring.redis.cluster.nodes=
spring.redis.cluster.read-mode=SLAVE
spring.redis.cluster.retry-attempts=3
spring.redis.cluster.failed-attempts=3
spring.redis.cluster.slave-connection-pool-size=64
spring.redis.cluster.master-connection-pool-size=64
spring.redis.cluster.retry-interval=1500
#哨兵配置
spring.redis.sentinel.master=business-master
spring.redis.sentinel.nodes=
spring.redis.sentinel.master-onlyWrite=true
spring.redis.sentinel.fail-max=3

  1. 配置文件讀取
    RedisProperties.java
import lombok.Data;
import lombok.ToString;
import org.springframework.boot.context.properties.ConfigurationProperties;
 
@ConfigurationProperties(prefix = "spring.redis", ignoreUnknownFields = false)
@Data
@ToString
public class RedisProperties {
 
    private int database;
 
    /**
     * 等待節(jié)點回復命令的時間。該時間從命令發(fā)送成功時開始計時
     */
    private int timeout;
 
    private String password;
 
    private String mode;
 
    /**
     * 池配置
     */
    private RedisPoolProperties pool;
 
    /**
     * 單機信息配置
     */
    private RedisSingleProperties single;
 
    /**
     * 集群 信息配置
     */
    private RedisClusterProperties cluster;
 
    /**
     * 哨兵配置
     */
    private RedisSentinelProperties sentinel;
}

池配置RedisPool.Properties

import lombok.Data;
import lombok.ToString;
 
/**
 * @author Abbot
 * @des redis 池配置
 * @date 2018/10/18 10:43
 **/
@Data
@ToString
public class RedisPoolProperties {
 
    private int maxIdle;
 
    private int minIdle;
 
    private int maxActive;
 
    private int maxWait;
 
    private int connTimeout;
 
    private int soTimeout;
 
    /**
     * 池大小
     */
    private  int size;
 
}

RedisSingleProperties.java

@Data
@ToString
public class RedisSingleProperties {
    private  String address;
}

集群配置RedisClusterProperties.java

@Data
@ToString
public class RedisClusterProperties {
 
    /**
     * 集群狀態(tài)掃描間隔時間,單位是毫秒
     */
    private int scanInterval;
 
    /**
     * 集群節(jié)點
     */
    private String nodes;
 
    /**
     * 默認值: SLAVE(只在從服務節(jié)點里讀取)設置讀取操作選擇節(jié)點的模式。 可用值為: SLAVE - 只在從服務節(jié)點里讀取。
     * MASTER - 只在主服務節(jié)點里讀取。 MASTER_SLAVE - 在主從服務節(jié)點里都可以讀取
     */
    private String readMode;
    /**
     * (從節(jié)點連接池大小) 默認值:64
     */
    private int slaveConnectionPoolSize;
    /**
     * 主節(jié)點連接池大?。┠J值:64
     */
    private int masterConnectionPoolSize;
 
    /**
     * (命令失敗重試次數(shù)) 默認值:3
     */
    private int retryAttempts;
 
    /**
     *命令重試發(fā)送時間間隔,單位:毫秒 默認值:1500
     */
    private int retryInterval;
 
    /**
     * 執(zhí)行失敗最大次數(shù)默認值:3
     */
    private int failedAttempts;
}

哨兵配置 RedisSentinelProperties.java


@Data
@ToString
public class RedisSentinelProperties {
 
    /**
     * 哨兵master 名稱
     */
    private String master;
 
    /**
     * 哨兵節(jié)點
     */
    private String nodes;
 
    /**
     * 哨兵配置
     */
    private boolean masterOnlyWrite;
 
    /**
     *
     */
    private int failMax;
}

CacheConfiguration.java


@Configuration
@EnableConfigurationProperties(RedisProperties.class)
public class CacheConfiguration {
 
    @Autowired
    RedisProperties redisProperties;
 
    @Configuration
    @ConditionalOnClass({Redisson.class})
    @ConditionalOnExpression("'${spring.redis.mode}'=='single' or '${spring.redis.mode}'=='cluster' or '${spring.redis.mode}'=='sentinel'")
    protected class RedissonSingleClientConfiguration {
 
        /**
         * 單機模式 redisson 客戶端
         */
 
        @Bean
        @ConditionalOnProperty(name = "spring.redis.mode", havingValue = "single")
        RedissonClient redissonSingle() {
            Config config = new Config();
            String node = redisProperties.getSingle().getAddress();
            node = node.startsWith("redis://") ? node : "redis://" + node;
            SingleServerConfig serverConfig = config.useSingleServer()
                    .setAddress(node)
                    .setTimeout(redisProperties.getPool().getConnTimeout())
                    .setConnectionPoolSize(redisProperties.getPool().getSize())
                    .setConnectionMinimumIdleSize(redisProperties.getPool().getMinIdle());
            if (StringUtils.isNotBlank(redisProperties.getPassword())) {
                serverConfig.setPassword(redisProperties.getPassword());
            }
            return Redisson.create(config);
        }
 
 
        /**
         * 集群模式的 redisson 客戶端
         *
         * @return
         */
        @Bean
        @ConditionalOnProperty(name = "spring.redis.mode", havingValue = "cluster")
        RedissonClient redissonCluster() {
            System.out.println("cluster redisProperties:" + redisProperties.getCluster());
 
            Config config = new Config();
            String[] nodes = redisProperties.getCluster().getNodes().split(",");
            List<String> newNodes = new ArrayList(nodes.length);
            Arrays.stream(nodes).forEach((index) -> newNodes.add(
                    index.startsWith("redis://") ? index : "redis://" + index));
 
            ClusterServersConfig serverConfig = config.useClusterServers()
                    .addNodeAddress(newNodes.toArray(new String[0]))
                    .setScanInterval(
                            redisProperties.getCluster().getScanInterval())
                    .setIdleConnectionTimeout(
                            redisProperties.getPool().getSoTimeout())
                    .setConnectTimeout(
                            redisProperties.getPool().getConnTimeout())
                    .setFailedAttempts(
                            redisProperties.getCluster().getFailedAttempts())
                    .setRetryAttempts(
                            redisProperties.getCluster().getRetryAttempts())
                    .setRetryInterval(
                            redisProperties.getCluster().getRetryInterval())
                    .setMasterConnectionPoolSize(redisProperties.getCluster()
                            .getMasterConnectionPoolSize())
                    .setSlaveConnectionPoolSize(redisProperties.getCluster()
                            .getSlaveConnectionPoolSize())
                    .setTimeout(redisProperties.getTimeout());
            if (StringUtils.isNotBlank(redisProperties.getPassword())) {
                serverConfig.setPassword(redisProperties.getPassword());
            }
            return Redisson.create(config);
        }
 
       /**  
         * 哨兵模式 redisson 客戶端
         * @return
         */
 
        @Bean
        @ConditionalOnProperty(name = "spring.redis.mode", havingValue = "sentinel")
        RedissonClient redissonSentinel() {
            System.out.println("sentinel redisProperties:" + redisProperties.getSentinel());
            Config config = new Config();
            String[] nodes = redisProperties.getSentinel().getNodes().split(",");
            List<String> newNodes = new ArrayList(nodes.length);
            Arrays.stream(nodes).forEach((index) -> newNodes.add(
                    index.startsWith("redis://") ? index : "redis://" + index));
 
            SentinelServersConfig serverConfig = config.useSentinelServers()
                    .addSentinelAddress(newNodes.toArray(new String[0]))
                    .setMasterName(redisProperties.getSentinel().getMaster())
                    .setReadMode(ReadMode.SLAVE)
                    .setFailedAttempts(redisProperties.getSentinel().getFailMax())
                    .setTimeout(redisProperties.getTimeout())
                    .setMasterConnectionPoolSize(redisProperties.getPool().getSize())
                    .setSlaveConnectionPoolSize(redisProperties.getPool().getSize());
 
            if (StringUtils.isNotBlank(redisProperties.getPassword())) {
                serverConfig.setPassword(redisProperties.getPassword());
            }
 
            return Redisson.create(config);
        }
    }
}

使用:

   @Autowired
    RedissonClient redisson;
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容