天天看點

SpringBoot2.x學習之路(四)Redis內建以及哨兵模式的配置

今天小七給大夥介紹一下,如何在Spring Boot項目中使用redis,大家都知道搭建redis有4種方式,分别是單節點執行個體、主從模式、sentinel模式、cluster模式。今天來給大夥介紹一下sentinel模式的環境搭建以及如何內建到Spring Boot中。

先來簡單介紹一下sentinel模式,也就是哨兵模式。哨兵模式是一種特殊的模式,首先Redis提供了哨兵的指令,哨兵是一個獨立的程序,作為程序,它會獨立運作。其原理是哨兵通過發送指令,等待Redis伺服器響應,進而監控運作的多個Redis執行個體。

哨兵有兩個作用:

  • 通過發送指令,讓Redis伺服器傳回監控其運作狀态,包括主伺服器和從伺服器;
  • 當哨兵監測到master當機,會自動将slave切換成master,然後通過釋出訂閱模式通知其他的從伺服器,修改配置檔案,讓它們切換主機。

下面,小七分以下幾個步驟給大家介紹一下:

(一)Windows下哨兵模式的安裝

因為redis官網沒有提供windows安裝,是以咱們從下面的位址下載下傳即可,https://github.com/microsoftarchive/redis/releases/tag/win-3.2.100。

下載下傳Redis-x64-3.2.100.zip後,進行解壓,檔案夾内容如下:

SpringBoot2.x學習之路(四)Redis內建以及哨兵模式的配置

标紅的10個檔案是小七新加的,下面小七來分别介紹一下:

主節點:redis.conf、redis-startup.bat

從節點1:redis2.conf、redis2-startup.bat

從節點2:redis3.conf、redis3-startup.bat

哨兵1:sentinel.conf、sentinel-startup.bat

哨兵2:sentinel2.conf、sentinel2-startup.bat

說明一下,.conf是配置檔案,.bat是友善啟動弄的一個批處理檔案。

redis.conf、redis2.conf、redis3.conf直接拷貝redis.windows.conf即可,需要改下裡面的端口,然後從節點需要指定主節點。

redis.conf(主):

port 6379
           

redis2.conf(從1):

port 6380
slaveof 127.0.0.1 6379
           

redis3.conf(從2):

port 6381
slaveof 127.0.0.1 6379
           

然後,再來看下相應的批處理檔案,如下:

redis-startup.bat:

redis-server.exe redis.conf
           

redis2-startup.bat:

redis-server.exe redis2.conf
           

redis3-startup.bat:

redis-server.exe redis3.conf
           

下面,再來看看哨兵的配置檔案。

sentinel.conf:

#目前Sentinel服務運作的端口
port 26379
#關閉保護模式
protected-mode no
# 哨兵監聽的主伺服器
sentinel monitor myMaster 127.0.0.1 6379 2
# 3s内myMaster無響應,則認為myMaster當機了
sentinel down-after-milliseconds myMaster 3000
# 執行故障轉移時,最多有1個從伺服器同時對新的主伺服器進行同步
sentinel parallel-syncs myMaster 1
#如果10秒後,myMaster仍沒啟動過來,則啟動failover
sentinel failover-timeout myMaster 10000


           

sentinel2.conf:

#目前Sentinel服務運作的端口
port 36379
#關閉保護模式
protected-mode no
# 哨兵監聽的主伺服器
sentinel monitor myMaster 127.0.0.1 6379 2
# 3s内myMaster無響應,則認為myMaster當機了
sentinel down-after-milliseconds myMaster 3000
# 執行故障轉移時,最多有1個從伺服器同時對新的主伺服器進行同步
sentinel parallel-syncs myMaster 1
#如果10秒後,myMaster仍沒啟動過來,則啟動failover
sentinel failover-timeout myMaster 10000


           

兩個哨兵的配置除了端口不一樣,其它都一樣即可。

然後,相應的批處理檔案如下。

sentinel-startup.bat:

redis-server.exe sentinel.conf --sentinel
           

sentinel2-startup.bat:

redis-server.exe sentinel2.conf --sentinel
           

好啦,接下來按以下順序執行,直接輕按兩下即可:

redis-startup.bat

redis2-startup.bat

redis3-startup.bat

sentinel-startup.bat

sentinel2-startup.bat

下面,小七截個圖看看啟動日志:

主節點:

SpringBoot2.x學習之路(四)Redis內建以及哨兵模式的配置

從1:

SpringBoot2.x學習之路(四)Redis內建以及哨兵模式的配置

哨1:

SpringBoot2.x學習之路(四)Redis內建以及哨兵模式的配置

(二)如何測試哨兵模式是否正常啟動

先來檢查一些Master是否正常,用redis用戶端連上,指令如下:

redis-cli.exe -h 127.0.0.1 -p 6379
           

然後,執行指令:

info replication
           

結果如下:

SpringBoot2.x學習之路(四)Redis內建以及哨兵模式的配置

說明主從節點啟動都是正常的。

下面再來看看哨兵是否正常,執行以下指令連上sentinel:

redis-cli.exe -h 127.0.0.1 -p 26379
           

接着執行:

info sentinel
           

結果如下:

SpringBoot2.x學習之路(四)Redis內建以及哨兵模式的配置

OK,一切正常,說明哨兵模式啟動沒有問題。

然後,我們把主節點給關掉,再來看看哨兵的日志:

SpringBoot2.x學習之路(四)Redis內建以及哨兵模式的配置

可以看到,自動選舉了一個從節點為主節點了,說明哨兵的作用生效了。

(三)pom.xml依賴配置

這個小七直接貼出來,如下:

<!-- redis -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>

    <!-- Jedis 用戶端 -->
    <dependency>
      <groupId>redis.clients</groupId>
      <artifactId>jedis</artifactId>
    </dependency>

    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-pool2</artifactId>
    </dependency>
           

這裡注意,Spring Boot2.X預設選擇使用lettuce用戶端,下面簡單介紹一下:

Lettuce是一個高性能基于Java編寫的Redis驅動架構,底層內建了Project Reactor提供天然的反應式程式設計,通信架構內建了Netty使用了非阻塞IO,5.x版本之後融合了JDK1.8的異步程式設計特性,在保證高性能的同時提供了十分豐富易用的API。

commons-pool2是用于連接配接池。

(四)代碼測試是否能連接配接哨兵模式

下面,咱們再來寫個測試代碼,看能否連接配接到哨兵模式,代碼如下:

@Test
    public void testConnectSentinel() {
        Set<String> sentinels = new HashSet<>();
        sentinels.add("127.0.0.1:26379");
        sentinels.add("127.0.0.1:36379");
        String clusterName = "myMaster";
        JedisSentinelPool redisSentinelPool = new JedisSentinelPool(clusterName, sentinels);
        Jedis jedis = null;
        try {
            jedis = redisSentinelPool.getResource();
            jedis.set("name01", "aaa");
            System.out.println(jedis.get("name01"));
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            jedis.close();
            redisSentinelPool.close();
        }
    }
           

運作結果如下:

SpringBoot2.x學習之路(四)Redis內建以及哨兵模式的配置

咱們也可以下載下傳一個Redis GUI來看一下,如下圖:

SpringBoot2.x學習之路(四)Redis內建以及哨兵模式的配置

說明連接配接哨兵模式沒有問題。

(五)Spring Boot中如何配置哨兵模式

首先,咱們看一下.yml配置,如下圖:

spring:
  redis:
#    host: 127.0.0.1
#    port: 6379
    password:
    database: 0  # Redis資料庫索引(預設為0)
    timeout: 2000ms  # 連接配接逾時時間(毫秒)預設是2000ms
    lettuce:
      pool:
        max-active: 200  # 連接配接池最大連接配接數(使用負值表示沒有限制)
        max-wait: -1ms # 連接配接池最大阻塞等待時間(使用負值表示沒有限制)
        max-idle: 100 # 連接配接池中的最大空閑連接配接
        min-idle: 50 # 連接配接池中的最小空閑連接配接
      shutdown-timeout: 100ms
    sentinel:  # 哨兵模式
      master: myMaster
      nodes: 127.0.0.1:26379,127.0.0.1:36379

           

這裡如果不想使用哨兵模式,把sentinel注釋,放開host和port就可以啦。

(六)自定義redis的key和value的序列化和反序列化

因為redis是以鍵值對的方式儲存資料,儲存的時候會進行序列化,如果不指定的話,就會按對象預設的序列化方式進行,這時咱們去redis檢視的時候,會完全看不懂是啥。一般,這咱們可以JSON格式進行序列化和反序列化,容易進行管理。

首先,咱們建立一個配置類RedisConfig,代碼如下:

package org.qyk.springboot.redis;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * <Change to the actual description of this class>
 *
 * @version 1.0
 * <pre>
 * Author       Date            Changes
 * yongkang.qi   2020年04月06日   Created
 *
 * </pre>
 * @since 1.7
 */
@Configuration
@EnableCaching //開啟注解
public class RedisConfig extends CachingConfigurerSupport {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        // 配置連接配接工廠
        template.setConnectionFactory(redisConnectionFactory);

        //使用Jackson2JsonRedisSerializer來序列化和反序列化redis的value值(預設使用JDK的序列化方式)
        Jackson2JsonRedisSerializer jacksonSerial = new Jackson2JsonRedisSerializer(Object.class);

        ObjectMapper om = new ObjectMapper();
        // 指定要序列化的域,field,get和set,以及修飾符範圍,ANY是都有包括private和public
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        // 指定序列化輸入的類型,類必須是非final修飾的,final修飾的類,比如String,Integer等會跑出異常
        om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
        jacksonSerial.setObjectMapper(om);

        // 值采用json序列化
        template.setValueSerializer(jacksonSerial);
        //使用StringRedisSerializer來序列化和反序列化redis的key值
        template.setKeySerializer(new StringRedisSerializer());

        // 設定hash key 和value序列化模式
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(jacksonSerial);
        template.afterPropertiesSet();

        return template;
    }

    @Bean
    public KeyGenerator keyGenerator() {
        return (target, method, params) -> {
            StringBuilder sb = new StringBuilder();
            sb.append(target.getClass().getName());
            sb.append(method.getName());
            for (Object obj : params) {
                sb.append(obj.toString());
            }
            return sb.toString();
        };
    }
}

           

這裡,主要是看redisTemplate這個方法即可。

咱們寫個單元測試類,如下:

package org.qyk.springboot.redis;

import java.util.*;

import com.alibaba.fastjson.JSON;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.qyk.springboot.dao3.PersonDao;
import org.qyk.springboot.entity.PersonEntity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.test.context.junit4.SpringRunner;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisSentinelPool;

import javax.annotation.Resource;

/**
 * <Change to the actual description of this class>
 *
 * @version 1.0
 * <pre>
 * Author       Date            Changes
 * yongkang.qi   2020年04月06日   Created
 *
 * </pre>
 * @since 1.7
 */
@RunWith(SpringRunner.class)
@SpringBootTest
public class RedisTest {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    @Resource
    private PersonDao personDao;

    @Test
    public void testConnectSentinel() {
        Set<String> sentinels = new HashSet<>();
        sentinels.add("127.0.0.1:26379");
        sentinels.add("127.0.0.1:36379");
        String clusterName = "myMaster";
        JedisSentinelPool redisSentinelPool = new JedisSentinelPool(clusterName, sentinels);
        Jedis jedis = null;
        try {
            jedis = redisSentinelPool.getResource();
            jedis.set("name01", "aaa");
            System.out.println(jedis.get("name01"));
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            jedis.close();
            redisSentinelPool.close();
        }
    }

    @Test
    public void testStringRedisTemplate() {
        stringRedisTemplate.opsForValue().set("name02", "小七2");
        Assert.assertEquals("小七2", stringRedisTemplate.opsForValue().get("name02"));
    }

    @Test
    public void testRedisTemplate() {
        PersonEntity entity = new PersonEntity();
        entity.setUsername("小七");
        entity.setAge(18);
        entity.setBirth(new Date());
        redisTemplate.opsForValue().set("name03", entity);
//        PersonEntity entity = (PersonEntity) redisTemplate.opsForValue().get("name03");
//        System.out.println(JSON.toJSONString(entity));
    }

    @Test
    public void testCacheable() {
        List<Map<String, Object>> maps = personDao.queryList("%%");
        System.out.println(JSON.toJSONString(maps));
    }

}

           

運作一下testRedisTemplate方法,然後我們來看一下圖形化界面,結果如下:

SpringBoot2.x學習之路(四)Redis內建以及哨兵模式的配置

這裡可以看到,key和value都是按咱們自定義的序列化方式進行儲存的。

(七)如何給方法添加緩存

有的時候,咱們可能需要給某個查詢方法增加緩存,無須一直查詢資料庫,提升查詢速度。那麼這個時候該怎麼辦呢,Spring Boot提供了注解的方式,使用起來非常友善。

首先咱們先配置一下keyGenerator,就是RedisConfig類的keyGenerator,待會兒示範一下就知道是幹嘛用的了。

然後咱們要開啟緩存注解,也就是@EnableCaching,在RedisConfig加上既可以。

下面,咱們來給一個方法加上緩存,如下:

SpringBoot2.x學習之路(四)Redis內建以及哨兵模式的配置

即加上@Cacheable(value = “sampleCache”)就行了。

下面咱們運作一下單元測試類中的testCacheable方法,然後檢查一下緩存,如下:

SpringBoot2.x學習之路(四)Redis內建以及哨兵模式的配置

看到上面的key,大家就應該能明白keyGenerator的作用了吧。

然後,咱們再來運作一次,就會發現queryList的代碼沒有被再執行,直接取了緩存中的值。這個小七就不示範了。

(八)結語

好了,今天就可以介紹到這裡了。其實,涉及到redis的技術點還是相當多的,小七主要是給大夥介紹在Spring Boot項目中內建redis,具體的詳細使用,大家可以去自己研究研究。總之,Spring Boot大大提升了咱們內建使用一些技術架構的效率,減少了很多繁瑣的配置,用起來确實很舒服,不過,就是得多查查改如何使用~

繼續閱讀