springboot+ redis-cluster

我們現在的系統對高可用高併發的需求越來越高了。通常爲降低應用內存壓力,減少IO消耗我們會選用redis來做緩存。

springboot 應用接入redis

我這裏簡單提一下 也可以參考之前的redis入門

  1. pom.xml
 <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <!--<version>${redis.version}</version>-->
</dependency>
  1. 並在yml 中配置redis 信息

當然springboot 1.X 與springboot2.x 配置參數命名會稍許不同,這裏使用的1.5

spring.redis.host=***
spring.redis.port=6379
spring.redis.password=123
  1. 應用
    這時候你就可以在代碼中直接注入redisTemplate 了
public class IndexController {
@Autowired
	private StringRedisTemplate stringRedisTemplate;
@RequestMapping({ "/test" })
	@ResponseBody
	public String rediesRest(ServletRequest request) {
		stringRedisTemplate.opsForValue().set("test", "laowang");

		return stringRedisTemplate.opsForValue().get("test");
	}

redis的高可用

當然有了docker之後部署redis應用也就幾秒鐘的事了。

但是redis也會有性能瓶頸,這個時候redis的高可用就有必要了。

redis 的集羣方案:
參考這個文章redis集羣方案redis 集羣詳解

  1. 主從複製

當slave啓動後,主動向master發送SYNC命令。master接收到SYNC命令後在後臺保存快照(RDB持久化)和緩存保存快照這段時間的命令,然後將保存的快照文件和緩存的命令發送給slave。slave接收到快照文件和命令後加載快照文件和緩存的執行命令。
複製初始化後,master每次接收到的寫命令都會同步發送給slave,保證主從數據一致性。

  1. 哨兵模式

在主從的機制上引入哨兵進程:
1 監控主從數據庫是否正常運行
2 master出現故障時,自動將slave轉化爲master
3 多哨兵配置的時候,哨兵之間也會自動監控
4 多個哨兵可以監控同一個redis

  1. Redis-Cluster集羣

在redis的每一個節點上,都有這麼兩個東西,一個是插槽(slot),它的的取值範圍是:0-16383。還有一個就是cluster,可以理解爲是一個集羣管理的插件。當我們的存取的key到達的時候,redis會根據crc16的算法得出一個結果,然後把結果對 16384 求餘數,這樣每個 key 都會對應一個編號在 0-16383 之間的哈希槽,通過這個值,去找到對應的插槽所對應的節點,然後直接自動跳轉到這個對應的節點上進行存取操作。
爲了保證高可用,redis-cluster集羣引入了主從模式,一個主節點對應一個或者多個從節點,當主節點宕機的時候,就會啓用從節點。當其它主節點ping一個主節點A時,如果半數以上的主節點與A通信超時,那麼認爲主節點A宕機了。如果主節點A和它的從節點A1都宕機了,那麼該集羣就無法再提供服務了。

springboot 接入 redis cluster
yml 中配置 集羣信息

spring:
  redis:
    password: fuyun_redis
    cluster:
      nodes: 127.0.0.1:8001,127.0.0.1:8002,127.0.0.1:8003

在controller 使用就可以了

/**
 * demo
 *
 * @author huiliuliu
 * @date 2019-09-30
 */
@RestController
public class RedisTestController {
    @Autowired
    RedisTemplate redisTemplate;

    @RequestMapping("redisSet")
    public void test() {
        redisTemplate.opsForValue().set("huiliuliu",131231);
    }

    @RequestMapping("redisGet")
    public Object testGet() {
      Object a =  redisTemplate.opsForValue().get("huiliuliu");
      return a;
    }
}

如果你需要自己配置連接信息,則需要寫config

package com.fuyun.da.config.spring;

import org.apache.commons.lang3.StringUtils;
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.RedisClusterConfiguration;
import org.springframework.data.redis.connection.RedisNode;
//import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.session.data.redis.config.ConfigureRedisAction;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.JedisPoolConfig;

import java.util.HashSet;
import java.util.Set;

/**
 * game-analytics
 *
 * @author: huiliuliu
 * @date:2019-08-19
 */
@Configuration
@EnableRedisHttpSession(maxInactiveIntervalInSeconds=18000)
public class SessionConfig {
    @Value("${spring.redis.cluster.nodes}")
    private String clusterNodes;
    @Value("${spring.redis.cluster.max-redirects:0}")
    private int maxRedirects;
    @Value("${spring.redis.password: }")
    private String password;

    @Value("${spring.redis.cluster.password: }")
    private String clusterPassword;

    @Value("${spring.redis.host: }")
    private String host;

    @Value("${spring.redis.port:6379}")
    private int port;

    @Value("${spring.redis.jedis.pool.max-idle:0}")
    private int maxIdle;
//    @Value("${spring.redis.maxTotal}")
//    private int maxTotal;
//    @Value("${spring.redis.maxWaitMillis}")
//    private int maxWaitMillis;
//    @Value("${spring.redis.minEvictableIdleTimeMillis}")
//    private int minEvictableIdleTimeMillis;
//    @Value("${redis.numTestsPerEvictionRun}")
//    private int numTestsPerEvictionRun;
//    @Value("${redis.timeBetweenEvictionRunsMillis}")
//    private int timeBetweenEvictionRunsMillis;
//    @Value("${spring.redis.testOnBorrow}")
//    private boolean testOnBorrow;
//    @Value("${spring.redis.testWhileIdle}")
//    private boolean testWhileIdle;

    @Bean
    public JedisPoolConfig getJedisPoolConfig() {
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        // 最大空閒數
        jedisPoolConfig.setMaxIdle(5);
        // 連接池的最大數據庫連接數
        jedisPoolConfig.setMaxTotal(5);
        // 最大建立連接等待時間
        jedisPoolConfig.setMaxWaitMillis(500);
        // 逐出連接的最小空閒時間 默認1800000毫秒(30分鐘)
        jedisPoolConfig.setMinEvictableIdleTimeMillis(1800000);
        // 每次逐出檢查時 逐出的最大數目 如果爲負數就是 : 1/abs(n), 默認3
        jedisPoolConfig.setNumTestsPerEvictionRun(1);
        // 逐出掃描的時間間隔(毫秒) 如果爲負數,則不運行逐出線程, 默認-1
        jedisPoolConfig.setTimeBetweenEvictionRunsMillis(-1);
        // 是否在從池中取出連接前進行檢驗,如果檢驗失敗,則從池中去除連接並嘗試取出另一個
        jedisPoolConfig.setTestOnBorrow(true);
        // 在空閒時檢查有效性, 默認false
        jedisPoolConfig.setTestWhileIdle(false);
        return jedisPoolConfig;
    }

    /**
     * Redis集羣的配置
     *
     * @return RedisClusterConfiguration
     * @throws
     */
    @Bean
    public RedisClusterConfiguration redisClusterConfiguration() {
        RedisClusterConfiguration redisClusterConfiguration = new RedisClusterConfiguration();
        //Set<RedisNode> clusterNodes
        String[] serverArray = clusterNodes.split(",");
        Set<RedisNode> nodes = new HashSet<RedisNode>();
        for (String ipPort : serverArray) {
            String[] ipAndPort = ipPort.split(":");
            nodes.add(new RedisNode(ipAndPort[0].trim(), Integer.valueOf(ipAndPort[1])));
        }
        redisClusterConfiguration.setClusterNodes(nodes);
        redisClusterConfiguration.setMaxRedirects(maxRedirects);
//        redisClusterConfiguration.setPassword(RedisPassword.of(password));
        return redisClusterConfiguration;
    }

    /**
     * @param
     * @return
     * @Description:redis連接工廠類
     * @date 2018/10/25 19:45
     */
    @Bean
    public JedisConnectionFactory jedisConnectionFactory() {

        if(StringUtils.isNoneBlank(host)){
            JedisConnectionFactory connection = new JedisConnectionFactory();
            connection.setHostName(host);
            connection.setDatabase(1);
            if (StringUtils.isNoneBlank(password)){
                connection.setPassword(password);
            }
            connection.setPort(port);
            return connection;
        }

        //集羣模式
        JedisConnectionFactory factory = new JedisConnectionFactory(redisClusterConfiguration(), getJedisPoolConfig());
        factory.setDatabase(0);
        factory.setUsePool(true);
        factory.setPassword(clusterPassword);
        return factory;
    }

    /**
     * 實例化 RedisTemplate 對象
     *
     * @return
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate() {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        initDomainRedisTemplate(redisTemplate);
        return redisTemplate;
    }

    /**
     * 設置數據存入 redis 的序列化方式,並開啓事務
     * 使用默認的序列化會導致key亂碼
     */
    private void initDomainRedisTemplate(RedisTemplate<String, Object> redisTemplate) {
        //如果不配置Serializer,那麼存儲的時候缺省使用String,如果用User類型存儲,那麼會提示錯誤User can't cast to String!
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        //這個地方有一個問題,這種序列化器會將value序列化成對象存儲進redis中,如果
        //你想取出value,然後進行自增的話,這種序列化器是不可以的,因爲對象不能自增;
        //需要改成StringRedisSerializer序列化器。
        redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
        redisTemplate.setEnableTransactionSupport(false);
        redisTemplate.setConnectionFactory(jedisConnectionFactory());
    }
    @Bean
    public static ConfigureRedisAction configureRedisAction() {
        return ConfigureRedisAction.NO_OP;
    }
}

成功了,試一試吧

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章