Spring Boot - 集成Redis以及使用Apache AbTest進行壓力測試

Spring Boot - 集成Redis以及使用Apache AbTest進行壓力測試

 Spring Boot除了對我們常用的關係型數據庫支持外,對NOSQL也提供了十分不錯的封裝自動化。
在我們開發項目的過程中,由於業務越來越繁瑣,數據量越來越大,從而我們對一些程序的性能會有所要求,在一些場景下緩存就成了必不可少的一項策略。
 Redis是目前使用最爲廣泛的內存數據存儲,它是一個開源的使用ANSI C語言編寫、支持網絡、可基於內存亦可持久化的日誌型、Key-Value數據庫,並提供多種語言的API。相對於其他同種工具,例如Memcached,Redis支持更豐富的數據結構,並且支持數據持久化,除此之外Redis還具備一些數據庫的特性,例如事務、主從策略等。所以Redis兼有了緩存系統以及傳統數據庫的一些特性,有着更加豐富的應用場景,這也是Redis爲何從各種緩存工具中脫穎而出的原因,想必大家對Redis肯定都不陌生。從下面這張圖我們可以很明顯的看出Redis一路高歌的勢頭並且一直在保持,接下來我們就來看下Spring Boot中是如何集成Redis的。
在這裏插入圖片描述

1.Lettuce

 我們都知道在我們java開發中,jedis作爲redis的客戶端工具,很好地對我們代碼開發使用redis進行了支持,但是jedis本身是直連redis server的,所以在多線程的環境下可能會出現線程不安全的情況,然後我們需要通過線程池去對每一個jedis實例增加物理連接。
 而Spring Boot 在2.x的版本之後,我們若集成了redis則它默認將jedis替換成了Lettuce,它是基於Netty的連接實例,可以在多個線程間併發訪問並且線程安全的,同時它是可伸縮的設計,一個連接實例不夠的情況也可以按需增加連接實例,也就是說Lettuce是一種更高級Redis客戶端,彌補了jedis的一些缺點並且提供了更豐富的功能支持。關於Redis的幾個框架,大家可以參考下面這篇博客,作者總結得比較簡練易懂。
Redis的三個框架:Jedis,Redisson,Lettuce

2.集成Redis(lettuce客戶端)

redis官方文檔

2.1 引入pom依賴
pom.xml
<!-- SpringBoot整合Redis-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 池化連接 springboot2.x集成redis使用jedis操作需要加上該依賴-->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>
2.2 配置文件

 由於2.x版本默認連接池是lettuce,在這裏若需要採用jedis,則用spring.redis.jedis.pool替代spring.redis.lettuce.pool即可。

application.properties
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=root
# 連接超時時間(毫秒)
spring.redis.timeout=10000
# Redis默認情況下有16個分片,這裏配置具體使用的分片,默認是0
spring.redis.database=0
# 連接池最大連接數(使用負值表示沒有限制) 默認 8
spring.redis.lettuce.pool.max-active=8
# 連接池最大阻塞等待時間(使用負值表示沒有限制) 默認 -1
spring.redis.lettuce.pool.max-wait=-1
# 連接池中的最大空閒連接 默認 8
spring.redis.lettuce.pool.max-idle=8
# 連接池中的最小空閒連接 默認 0
spring.redis.lettuce.pool.min-idle=0
# 關閉超時時間
spring.redis.lettuce.shutdown-timeout=100
2.3 自定義redis配置類

 Spring Boot對redis的配置可以自動、手動以及傳統的xml都支持,但是我們爲了更靈活地使用,一般會自定義配置類去配置相關參數。默認提供的Redis集成使用起來會有幾個問題:

  1. 生成Key過於簡單,容易出現衝突。
  2. 無法設置過期時間,乃至無法個性化每個業務單獨的過期時間。
  3. 默認配置序列化採用JDK序列化,某些業務場景無法滿足。

 考慮以上幾個問題,我們通常在集成使用Redis時,會自定義一些配置,這裏我們來看看如何做自定義配置。

CacheExpiresMap .java
package com.springboot.config;

import java.util.HashMap;

/**
 * 緩存Cacheable cacheNames過期時間
 *
 * @author hzk
 * @Cacheable(value="xxxx")
 */
public class CacheExpiresMap {
    private static HashMap<String, Integer> map = new HashMap<>();

    static {
        map.put("dic", 60);
    }


    public static HashMap<String, Integer> get() {
        return map;
    }
}

LettuceAutoConfig.java
package com.springboot.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.CacheErrorHandler;
import org.springframework.cache.interceptor.KeyGenerator;
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.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.lang.reflect.Method;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;

/**
 * 自定義redis配置(lettuce)
 * @author hzk
 * @date 2019/1/7
*/

@Configuration
@EnableCaching //啓用緩存
public class LettuceAutoConfig extends CachingConfigurerSupport {

    private static final Logger LOGGER = LoggerFactory.getLogger(LettuceAutoConfig.class);

    /**
     * 自定義緩存key的生成策略。默認的生成策略是可讀性較差的
     * @return
     */
    @Bean
    @Override
    public KeyGenerator keyGenerator() {
        return new KeyGenerator() {
            /**
             *
             * @param target  當前被調用對象
             * @param method 當前被調用的方法
             * @param params 調用方法的參數列表
             * @return
             */
            @Override
            public Object generate(Object target, Method method, Object... params) {
                StringBuilder sb = new StringBuilder();
                sb.append(target.getClass().getName())
                        .append(":")
                        .append(method.getName());
                for (Object obj : params) {
                    sb.append(":")
                            .append(obj.toString());
                }
                LOGGER.info("自定義生成Redis Key -> [{}]", sb.toString());
                return sb.toString();
            }
        };
    }

    /**
     * 緩存配置管理器
     */
    @Bean
    public CacheManager cacheManager(LettuceConnectionFactory lettuceConnectionFactory) {
        //以鎖寫入的方式創建RedisCacheWriter對象
        RedisCacheWriter writer = RedisCacheWriter.lockingRedisCacheWriter(lettuceConnectionFactory);
        //創建默認緩存配置對象
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
        //設置默認超過期時間是30秒
        config.entryTtl(Duration.ofSeconds(30));
        //自定義cacheConfig
        Map<String, RedisCacheConfiguration> initialCacheConfigurations = new HashMap<>();
        for (Map.Entry<String, Integer> entry : CacheExpiresMap.get().entrySet()) {
            String key = entry.getKey();
            Integer seconds = entry.getValue();
            LOGGER.info("key{},value{}", key, seconds);
            RedisCacheConfiguration initialCacheConfig = RedisCacheConfiguration.defaultCacheConfig();
            initialCacheConfigurations.put(key, initialCacheConfig.entryTtl(Duration.ofSeconds(seconds)));
        }
        //初始化RedisCacheManager
        RedisCacheManager cacheManager = new RedisCacheManager(writer, config,initialCacheConfigurations);
        return cacheManager;
    }

    /**
     * 手動配置注入redisTemplate
     * @param lettuceConnectionFactory lettuce連接工廠
     * @return {@link RedisTemplate}
     */
    @Bean(name = "redisTemplate")
    public RedisTemplate<Object,Object> setRedisTemplate(LettuceConnectionFactory lettuceConnectionFactory){
        RedisTemplate<Object,Object> template = new RedisTemplate<>();
        template.setKeySerializer(new StringRedisSerializer());
        template.setConnectionFactory(lettuceConnectionFactory);
        //以下代碼爲將RedisTemplate的Value序列化方式由JdkSerializationRedisSerializer更換爲Jackson2JsonRedisSerializer
        //此種序列化方式結果清晰、容易閱讀、存儲字節少、速度快,所以推薦更換
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        template.setValueSerializer(jackson2JsonRedisSerializer);
        //template.setEnableTransactionSupport(true);//是否啓用事務
        template.afterPropertiesSet();
        return template;
    }

    @Override
    @Bean
    public CacheErrorHandler errorHandler() {
        // 異常處理,當Redis發生異常時,打印日誌,但是程序正常走
        LOGGER.info("初始化 -> [{}]", "Redis CacheErrorHandler");
        CacheErrorHandler cacheErrorHandler = new CacheErrorHandler() {
            @Override
            public void handleCacheGetError(RuntimeException e, Cache cache, Object key) {
                LOGGER.error("Redis occur handleCacheGetError:key -> [{}]", key, e);
            }

            @Override
            public void handleCachePutError(RuntimeException e, Cache cache, Object key, Object value) {
                LOGGER.error("Redis occur handleCachePutError:key -> [{}];value -> [{}]", key, value, e);
            }

            @Override
            public void handleCacheEvictError(RuntimeException e, Cache cache, Object key)    {
                LOGGER.error("Redis occur handleCacheEvictError:key -> [{}]", key, e);
            }

            @Override
            public void handleCacheClearError(RuntimeException e, Cache cache) {
                LOGGER.error("Redis occur handleCacheClearError:", e);
            }
        };
        return cacheErrorHandler;
    }
}

2.4 使用redis

 上面這些步驟完成了,我們就可以使用redis緩存去緩存我們的數據了,在Spring Boot中一般情況下有兩種使用方式,一種是傳統的手動存取,第二種是註解自動緩存。這裏我們先來了解幾個註解:

  1. @CacheConfig(cacheNames=“userInfoCache”) :在同個redis裏面必須唯一。
  2. @Cacheable(查詢):來劃分可緩存的方法即當前方法是否需要進行緩存,以便在後續調用(具有相同的參數)時,直接返回緩存數據。
  3. @CachePut(修改、添加): 當需要更新緩存而不干擾方法執行時,可以使用@CachePut註釋。就是始終會執行該方法並將其結果放入緩存中。
  4. @CacheEvict(刪除) : 對於從緩存中刪除陳舊或未使用的數據十分有效,指定緩存範圍內的驅逐是否需要執行而不僅僅是一個條目驅逐。
DictionaryServiceImpl.java
package com.springboot.service.impl;

import com.springboot.dao.read.BlogDictionaryMapper;
import com.springboot.repository.entity.BlogDictionary;
import com.springboot.repository.entity.BlogDictionaryExample;
import com.springboot.service.IDictionaryService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.Serializable;
import java.util.List;

/**
 * @author hzk
 * @date 2018/12/19
 */
@Service
public class DictionaryServiceImpl implements IDictionaryService{

    @Autowired
    private BlogDictionaryMapper blogDictionaryMapper_r;

    @Autowired
    private RedisTemplate<Object,Object> redisTemplate;

    /**
     * 手動緩存
     * @param id
     * @return
     */
    @Override
    public BlogDictionary getDictionary(Integer id) {
        //優先查詢緩存
        String key = "lettuce_dictionary_"+id;
        //字符串序列化器 用於給redis key做序列化增強可讀性 避免redis自身生成不規律的key(\xAC\xED\x00\x05t\x00\x0Ftest_dictionary)
        //redisTemplate.setKeySerializer(redisTemplate.getStringSerializer());
        BlogDictionary blogDictionary = (BlogDictionary)redisTemplate.opsForValue().get(key);
        //雙重檢測(解決緩存穿透)
        if(null == blogDictionary){
            synchronized (this){
                blogDictionary = (BlogDictionary)redisTemplate.opsForValue().get(key);
                if(null == blogDictionary){
                    blogDictionary = blogDictionaryMapper_r.selectByPrimaryKey(id);
                    if(null != blogDictionary){
                        //將數據存入緩存
                        redisTemplate.opsForValue().set(key,blogDictionary);
                    }
                    System.out.println("命中數據庫");
                }else{
                    System.out.println("命中緩存");
                }
            }
        }else{
            System.out.println("命中緩存");
        }

        return blogDictionary;
    }

    /**
     * 自動緩存
     * @param id
     * @return
     */
    @Override
    @Cacheable(value="dic")
    public BlogDictionary getDictionaryAuto(Integer id) {
        System.out.println("命中數據庫:"+id);
        BlogDictionary blogDictionary = blogDictionaryMapper_r.selectByPrimaryKey(id);
        return blogDictionary;
    }

}

 上面第一種和我們之前傳統地使用redis緩存基本沒區別,下面使用@Cacheable註解會自動將結果緩存進redis,生成key的規則和我們之前在自定義配置中KeyGenerator一致,例如我們這裏請求http://localhost:8080/dicauto/23會在redis中存儲key爲dic:com.springboot.service.impl.DictionaryServiceImpl:getDictionaryAuto:23的內容,真正的開發中操作redis大家可以根據需要自己做一些封裝使開發更完善便捷。

3.集成Redis(jedis客戶端)

3.1 引入pom依賴
pom.xml
<!-- SpringBoot整合Redis-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <!-- 2.x版本默認連接池是lettuce, 若我們要使用jedis客戶端,需要先排除lettuce的jar -->
    <exclusions>
        <exclusion>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
        </exclusion>
        <exclusion>
            <groupId>io.lettuce</groupId>
            <artifactId>lettuce-core</artifactId>
        </exclusion>
    </exclusions>
</dependency>

<!-- 添加jedis客戶端 -->
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
</dependency>
<!-- 池化連接 springboot2.x集成redis使用jedis操作需要加上該依賴-->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
    <version>2.5.0</version>
</dependency>

<!-- 將作爲Redis對象序列化器 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.47</version>
</dependency>

3.2 配置文件

 由於2.x版本默認連接池是lettuce,在這裏若需要採用jedis,則用spring.redis.jedis.pool 替代spring.redis.lettuce.pool即可。

application.properties
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=root
# 連接超時時間(毫秒)
spring.redis.timeout=10000
# Redis默認情況下有16個分片,這裏配置具體使用的分片,默認是0
spring.redis.database=0
# 連接池最大連接數(使用負值表示沒有限制) 默認 8
spring.redis.jedis.pool.max-active=8
# 連接池最大阻塞等待時間(使用負值表示沒有限制) 默認 -1
spring.redis.jedis.pool.max-wait=-1
# 連接池中的最大空閒連接 默認 8
spring.redis.jedis.pool.max-idle=8
# 連接池中的最小空閒連接 默認 0
spring.redis.jedis.pool.min-idle=0
# 關閉超時時間
spring.redis.jedis.shutdown-timeout=100
3.3 自定義redis配置類
RedisConfig .java
package com.springboot.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.CacheErrorHandler;
import org.springframework.cache.interceptor.KeyGenerator;
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.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

import java.lang.reflect.Method;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;

/**
 * 自定義redis配置(jedis)
 * @author hzk
 * @date 2019/1/7
*/

@Configuration
// 必須加,使配置生效
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {

    private static final Logger LOGGER = LoggerFactory.getLogger(RedisConfig.class);

    @Autowired
    private JedisConnectionFactory jedisConnectionFactory;

    /**
     * 自定義緩存key的生成策略。默認的生成策略是可讀性較差的
     * 通過Spring 的依賴注入特性進行自定義的配置注入並且此類是一個配置類可以更多程度的自定義配置
     * @return
     */
    @Bean
    @Override
    public KeyGenerator keyGenerator() {
        return new KeyGenerator() {
            /**
             *
             * @param target  當前被調用對象
             * @param method 當前被調用的方法
             * @param params 調用方法的參數列表
             * @return
             */
            @Override
            public Object generate(Object target, Method method, Object... params) {
                StringBuilder sb = new StringBuilder();
                sb.append(target.getClass().getName())
                        .append(":")
                        .append(method.getName());
                for (Object obj : params) {
                    sb.append(":")
                            .append(obj.toString());
                }
                LOGGER.info("自定義生成Redis Key -> [{}]", sb.toString());
                return sb.toString();
            }
        };
    }

    /**
     * 緩存配置管理器
     */
    @Bean
    public CacheManager cacheManager(JedisConnectionFactory jedisConnectionFactory) {
        //以鎖寫入的方式創建RedisCacheWriter對象
        RedisCacheWriter writer = RedisCacheWriter.lockingRedisCacheWriter(jedisConnectionFactory);
        //創建默認緩存配置對象
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
        //設置默認超過期時間是30秒
        config.entryTtl(Duration.ofSeconds(30));
        //自定義cacheConfig
        Map<String, RedisCacheConfiguration> initialCacheConfigurations = new HashMap<>();
        for (Map.Entry<String, Integer> entry : CacheExpiresMap.get().entrySet()) {
            String key = entry.getKey();
            Integer seconds = entry.getValue();
            LOGGER.info("key{},value{}", key, seconds);
            RedisCacheConfiguration initialCacheConfig = RedisCacheConfiguration.defaultCacheConfig();
            initialCacheConfigurations.put(key, initialCacheConfig.entryTtl(Duration.ofSeconds(seconds)));
        }
        //初始化RedisCacheManager
        RedisCacheManager cacheManager = new RedisCacheManager(writer, config,initialCacheConfigurations);
        return cacheManager;
    }

    /**
     * 手動配置注入redisTemplate
     * @param jedisConnectionFactory jedis連接工廠
     * @return {@link RedisTemplate}
     */
    @Bean(name = "redisTemplate")
    public RedisTemplate<Object,Object> setRedisTemplate(JedisConnectionFactory jedisConnectionFactory){
        RedisTemplate<Object,Object> template = new RedisTemplate<>();
        template.setKeySerializer(new StringRedisSerializer());
        template.setConnectionFactory(jedisConnectionFactory);
        //以下代碼爲將RedisTemplate的Value序列化方式由JdkSerializationRedisSerializer更換爲Jackson2JsonRedisSerializer
        //此種序列化方式結果清晰、容易閱讀、存儲字節少、速度快,所以推薦更換
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        template.setValueSerializer(jackson2JsonRedisSerializer);
        //template.setEnableTransactionSupport(true);//是否啓用事務
        template.afterPropertiesSet();
        return template;
    }

    @Override
    @Bean
    public CacheErrorHandler errorHandler() {
        // 異常處理,當Redis發生異常時,打印日誌,但是程序正常走
        LOGGER.info("初始化 -> [{}]", "Redis CacheErrorHandler");
        CacheErrorHandler cacheErrorHandler = new CacheErrorHandler() {
            @Override
            public void handleCacheGetError(RuntimeException e, Cache cache, Object key) {
                LOGGER.error("Redis occur handleCacheGetError:key -> [{}]", key, e);
            }

            @Override
            public void handleCachePutError(RuntimeException e, Cache cache, Object key, Object value) {
                LOGGER.error("Redis occur handleCachePutError:key -> [{}];value -> [{}]", key, value, e);
            }

            @Override
            public void handleCacheEvictError(RuntimeException e, Cache cache, Object key)    {
                LOGGER.error("Redis occur handleCacheEvictError:key -> [{}]", key, e);
            }

            @Override
            public void handleCacheClearError(RuntimeException e, Cache cache) {
                LOGGER.error("Redis occur handleCacheClearError:", e);
            }
        };
        return cacheErrorHandler;
    }

    /**
     * 創建JedisConnectionFactory和JedisPool
     */
    @ConfigurationProperties
    class DataJedisProperties{

        @Value("${spring.redis.host}")
        private  String host;
        @Value("${spring.redis.password}")
        private  String password;
        @Value("${spring.redis.port}")
        private  int port;
        @Value("${spring.redis.timeout}")
        private  int timeout;
        @Value("${spring.redis.jedis.pool.max-idle}")
        private int maxIdle;
        @Value("${spring.redis.jedis.pool.max-wait}")
        private long maxWaitMillis;

        @Bean
        JedisConnectionFactory jedisConnectionFactory() {
            LOGGER.info("Init jedisConnectionFactory success!");
            JedisConnectionFactory factory = new JedisConnectionFactory();
            factory.setHostName(host);
            factory.setPort(port);
            factory.setTimeout(timeout);
            factory.setPassword(password);
            return factory;
        }
        @Bean
        public JedisPool redisPoolFactory() {
            LOGGER.info("JedisPool init success,host -> [{}];port -> [{}]", host, port);
            JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
            jedisPoolConfig.setMaxIdle(maxIdle);
            jedisPoolConfig.setMaxWaitMillis(maxWaitMillis);

            JedisPool jedisPool = new JedisPool(jedisPoolConfig, host, port, timeout, password);
            return jedisPool;
        }
    }
}

 將lettuce換成jedis只有上面幾步差異化修改即可,使用方法一致。

4.Session共享

 在架構日益發展的現在,分佈式架構可以說是已經很常見的,而有一個我們肯定會遇到要解決的問題就是session共享,做到session共享的方式有很多種,當然使用緩存去管理就是一個很有效的解決方法。在我們使用了Spring Boot之後你就會發現,它對使用redis去做到session共享是如此簡單,讓我們不得不感嘆它的強大。

4.1 引入pom依賴

 除了引入redis本身的依賴外我們還需要引入redis對session共享支持的依賴。

<!-- redis session共享依賴-->
<dependency>
    <groupId>org.springframework.session</groupId>
    <artifactId>spring-session-data-redis</artifactId>
</dependency>
4.2 session共享配置類

 通過官方文檔,我們可以知道我們需要通過@EnableRedisHttpSession註解去開啓spring對session共享的支持,所以這裏我們需要註冊一個session共享的配置類,這裏我們可以配置session的過期時間。

package com.springboot.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;

/**
 * maxInactiveIntervalInSeconds默認1800s
 * @author hzk
 * @date 2019/1/7
 */
@Configuration
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 60*10)
public class RedisSessionConfig {


}

4.3 session共享使用

 就簡單幾步我們就可以利用redis緩存達到session共享的目的,我們來看下效果怎麼樣。

RedisSessionController .java
package com.springboot.controller;

import com.springboot.service.IDictionaryService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @author hzk
 * @date 2018/1/7
 */
@RestController
public class RedisSessionController {


    @GetMapping(value = "/session")
    public Object session(HttpServletRequest request){
        Map<String, Object> map = new HashMap<>();
        map.put("sessionId", request.getSession().getId());
        map.put("url", request.getRequestURL());
        return map;
    }

}

 我們通過利用兩個端口8080、8081去啓動兩臺同樣的服務,然後我們分別訪問http://localhost:8080/session以及http://localhost:8081/session接口,可以發現返回的sessionId一致但是url是兩個系統各自不同的,也就是說已經達到了session共享的目的,我們再來看下他給我們生成的數據在redis中是如何存儲的。

在這裏插入圖片描述

5.Redis哨兵模式配置

 實際項目中使用Redis需要考慮高併發的情況,Redis提供了主從的功能,大大減少主服務器數據訪問頻繁導致宕機的問題,但是如果主服務器依然沒有承受住大量的併發請求,例如處理不當造成的大量緩存雪崩以及緩存穿透等,但是Redis提供的主從功能並不具備主從切換和監控的功能,但是我們對Redis進行更多的瞭解就能發現它其實還提供了一個Sentinel哨兵模式就是用來解決由於服務器宕機所造成服務不可用的問題,那麼Spring Boot當然也能夠很好地去支持Redis哨兵模式的集成配置。

application.properties
# redis集羣配置並非主從配置,一定注意
# spring.redis.cluster.nodes=127.0.0.1:6379,127.0.0.1:6380,127.0.0.1:6381
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=admin
# 連接超時時間(毫秒)
spring.redis.timeout=10000
# Redis默認情況下有16個分片,這裏配置具體使用的分片,默認是0
spring.redis.database=0
# 連接池最大連接數(使用負值表示沒有限制) 默認 8
spring.redis.lettuce.pool.max-active=8
# 連接池最大阻塞等待時間(使用負值表示沒有限制) 默認 -1
spring.redis.lettuce.pool.max-wait=-1
# 連接池中的最大空閒連接 默認 8
spring.redis.lettuce.pool.max-idle=8
# 連接池中的最小空閒連接 默認 0
spring.redis.lettuce.pool.min-idle=0
# 關閉超時時間
spring.redis.lettuce.shutdown-timeout=100

# redis集羣中的哨兵模式配置
spring.redis.sentinel.master=mymaster
# 哨兵模式集羣 多臺哨兵監控一組redis集羣
spring.redis.sentinel.nodes=127.0.0.1:26380,127.0.0.1:26381,127.0.0.1:26382
spring.redis.password=admin

 這裏需要注意的是很多同學會把主從和集羣弄混淆,導致在配置集羣的地方配置上了主從所以無法啓動服務,其他的Redis相關內容我們在其他博客中會介紹到,這裏只做Spring Boot整合的簡單介紹。除了redis基本配置之外只需要配置三行哨兵的基本屬性配置就能整合哨兵模式了。

6.Apache AbTest壓力測試(Redis與Mysql比較)

6.1 Apache AbTest 是什麼?

  ab是Apache HTTP server benchmarking tool的縮寫,可以用以測試HTTP請求的服務器性能。其是通過命令行的不同參數從而提供簡單易用的性能測試工具命令組合。

6.2 Apache AbTest 安裝

 ab的安裝十分簡單,若是Linux環境直接運行以下命令即可:

yum install -y httpd-tools

 Windows環境同樣只要下載了對應的Apache Http安裝包即可。安裝完成後可通過ab -V查看當前abtest版本
在這裏插入圖片描述

6.3 Apache AbTest 壓測性能比較

 這裏我準備了兩個測試接口,第一個是直接走DB取數據,第二個則是會先經過Redis取數據。通過這兩個接口來進行DB和緩存的性能比較。

package com.springboot.controller;

import com.springboot.dao.BlogUserMapper;
import com.springboot.repository.entity.BlogUser;
import com.springboot.service.RedisService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author hzk
 * @date 2019/7/2
 */
@RestController
@RequestMapping("/api/")
public class RedisController {

    @Autowired
    private BlogUserMapper userMapper;

    @Autowired
    private RedisService redisService;

    public static final String PRE_USER_KEY = "user:";

    @RequestMapping("getUser/{id}")
    public BlogUser getUser(@PathVariable("id") Integer id){
        return userMapper.selectByPrimaryKey(id);
    }

    @RequestMapping("getUserByCache/{id}")
    public BlogUser getUserByCache(@PathVariable("id") Integer id){
        BlogUser user = (BlogUser) redisService.get(PRE_USER_KEY + String.valueOf(id));
        if(null == user){
            user = userMapper.selectByPrimaryKey(id);
            System.out.println("Freshen Redis Cache ID:" + id);

            if(null != user){
                redisService.set(PRE_USER_KEY + id,user);
            }
        }
        return user;
    }
}

 下面我們主要通過幾種情況來對性能進行比較。使用之前我們先對命令參數以及幾個概念做一個初步瞭解。
 ab -n1000 -c10 http://127.0.0.1:8080/api/getUser/1 我們下面會通過該命令對某個接口進行壓測,這裏對幾個常用參數先做幾個瞭解:

-n : 進行http請求的總個數,缺省是1
-c : 請求的client個數,也就是請求併發數,缺省是1
-t : 測試所進行的總時間,秒爲單位,缺省50000s
-p : POST時的數據文件
-w : 以HTML表的格式輸出結果

 下面我們對壓測結果中的一些相關指標進行一個瞭解:

  1. Requests per second(吞吐率) : 服務器的吞吐量,每秒請求處理量,是服務器併發處理能力的量化描述,單位是reqs/s,指的是在某個併發用戶數下單位時間內處理的請求數,等效於QPS。某個併發用戶數下單位時間內能處理的最大請求數,稱之爲最大吞吐率。
     note:吞吐率是基於併發用戶數的。這句話代表了兩個含義:
     a、吞吐率和併發用戶數相關
     b、不同的併發用戶數下,吞吐率一般是不同的
     計算公式:總請求數/處理完成這些請求數所花費的時間,即 Request per second=Complete requests/Time taken for tests
  2. The number of concurrent connections(併發連接數) : 併發連接數指的是某個時刻服務器所接受的請求數目,簡單的講,就是一個會話。
  3. Concurrency Level(併發用戶數) : 要注意區分這個概念和併發連接數之間的區別,一個用戶可能同時會產生多個會話,即連接數。在HTTP/1.1下,IE7支持兩個併發連接,IE8支持6個併發連接,FireFox3支持4個併發連接,所以相應的,我們的併發用戶數就得除以這個基數。
  4. Time per request(用戶平均請求等待時間) : 計算公式:處理完成所有請求數所花費的時間/(總請求數/併發用戶數),即:
    Time per request=Time taken for tests/(Complete requests/Concurrency Level)。
  5. Time per request:across all concurrent requests(服務器平均請求等待時間) : 服務器端單個請求的處理時間
a) 10個併發 1000次請求

 這裏我們區分清楚http://127.0.0.1:8080/api/getUser/1爲直接DB取數據,http://127.0.0.1:8080/api/getUserByCache/2緩存取數據,對比多種情況。

G:\JavaTools\Apache Http\Apache24\bin>ab -n1000 -c10 http://127.0.0.1:8080/api/getUser/1
This is ApacheBench, Version 2.3 <$Revision: 1843412 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking 127.0.0.1 (be patient)
Completed 100 requests
Completed 200 requests
Completed 300 requests
Completed 400 requests
Completed 500 requests
Completed 600 requests
Completed 700 requests
Completed 800 requests
Completed 900 requests
Completed 1000 requests
Finished 1000 requests


Server Software:
Server Hostname:        127.0.0.1
Server Port:            8080

Document Path:          /api/getUser/1
Document Length:        410 bytes

Concurrency Level:      10
Time taken for tests:   8.711 seconds
Complete requests:      1000
Failed requests:        0
Total transferred:      529000 bytes
HTML transferred:       410000 bytes
//服務器的吞吐量,每秒請求處理量
Requests per second:    114.80 [#/sec] (mean)
//用戶平均等待時間
Time per request:       87.107 [ms] (mean)
//服務器端單個請求的處理時間
Time per request:       8.711 [ms] (mean, across all concurrent requests)
Transfer rate:          59.31 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.5      0       1
Processing:    32   83 156.1     35    2167
Waiting:       32   83 156.1     34    2167
Total:         32   84 156.1     35    2167

Percentage of the requests served within a certain time (ms)
  50%     35
  66%     36
  75%     39
  80%     40
  90%    268
  95%    289
  98%    518
  99%    746
 100%   2167 (longest request)
G:\JavaTools\Apache Http\Apache24\bin>ab -n1000 -c10 http://127.0.0.1:8080/api/getUserByCache/2
This is ApacheBench, Version 2.3 <$Revision: 1843412 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking 127.0.0.1 (be patient)
Completed 100 requests
Completed 200 requests
Completed 300 requests
Completed 400 requests
Completed 500 requests
Completed 600 requests
Completed 700 requests
Completed 800 requests
Completed 900 requests
Completed 1000 requests
Finished 1000 requests


Server Software:
Server Hostname:        127.0.0.1
Server Port:            8080

Document Path:          /api/getUserByCache/2
Document Length:        376 bytes

Concurrency Level:      10
Time taken for tests:   2.301 seconds
Complete requests:      1000
Failed requests:        0
Total transferred:      495000 bytes
HTML transferred:       376000 bytes
Requests per second:    434.62 [#/sec] (mean)
Time per request:       23.009 [ms] (mean)
Time per request:       2.301 [ms] (mean, across all concurrent requests)
Transfer rate:          210.10 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.4      0       1
Processing:    13   21   5.2     21     172
Waiting:       13   20   5.1     20     168
Total:         13   21   5.2     21     172

Percentage of the requests served within a certain time (ms)
  50%     21
  66%     21
  75%     22
  80%     22
  90%     23
  95%     24
  98%     30
  99%     31
 100%    172 (longest request)
b) 100個併發 1000次請求
G:\JavaTools\Apache Http\Apache24\bin>ab -n1000 -c100 http://127.0.0.1:8080/api/getUser/1
This is ApacheBench, Version 2.3 <$Revision: 1843412 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking 127.0.0.1 (be patient)
Completed 100 requests
Completed 200 requests
Completed 300 requests
Completed 400 requests
Completed 500 requests
Completed 600 requests
Completed 700 requests
Completed 800 requests
Completed 900 requests
Completed 1000 requests
Finished 1000 requests


Server Software:
Server Hostname:        127.0.0.1
Server Port:            8080

Document Path:          /api/getUser/1
Document Length:        410 bytes

Concurrency Level:      100
Time taken for tests:   8.470 seconds
Complete requests:      1000
Failed requests:        0
Total transferred:      529000 bytes
HTML transferred:       410000 bytes
Requests per second:    118.06 [#/sec] (mean)
Time per request:       847.036 [ms] (mean)
Time per request:       8.470 [ms] (mean, across all concurrent requests)
Transfer rate:          60.99 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.5      0       2
Processing:    34  770 312.9    861    2730
Waiting:       34  770 313.1    861    2730
Total:         34  771 312.9    862    2730

Percentage of the requests served within a certain time (ms)
  50%    862
  66%    877
  75%    885
  80%    899
  90%   1065
  95%   1120
  98%   1553
  99%   1581
 100%   2730 (longest request)
G:\JavaTools\Apache Http\Apache24\bin>ab -n1000 -c100 http://127.0.0.1:8080/api/getUserByCache/2
This is ApacheBench, Version 2.3 <$Revision: 1843412 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking 127.0.0.1 (be patient)
Completed 100 requests
Completed 200 requests
Completed 300 requests
Completed 400 requests
Completed 500 requests
Completed 600 requests
Completed 700 requests
Completed 800 requests
Completed 900 requests
Completed 1000 requests
Finished 1000 requests


Server Software:
Server Hostname:        127.0.0.1
Server Port:            8080

Document Path:          /api/getUserByCache/2
Document Length:        376 bytes

Concurrency Level:      100
Time taken for tests:   0.770 seconds
Complete requests:      1000
Failed requests:        0
Total transferred:      495000 bytes
HTML transferred:       376000 bytes
Requests per second:    1298.80 [#/sec] (mean)
Time per request:       76.994 [ms] (mean)
Time per request:       0.770 [ms] (mean, across all concurrent requests)
Transfer rate:          627.84 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.5      0       4
Processing:    19   70  13.0     72     130
Waiting:       14   53  14.5     55     119
Total:         19   70  13.0     73     130

Percentage of the requests served within a certain time (ms)
  50%     73
  66%     75
  75%     78
  80%     79
  90%     82
  95%     83
  98%     84
  99%     93
 100%    130 (longest request)
c) 100個併發 10000次請求
G:\JavaTools\Apache Http\Apache24\bin>ab -n10000 -c100 http://127.0.0.1:8080/api/getUser/1
This is ApacheBench, Version 2.3 <$Revision: 1843412 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking 127.0.0.1 (be patient)
Completed 1000 requests
Completed 2000 requests
Completed 3000 requests
Completed 4000 requests
Completed 5000 requests
Completed 6000 requests
Completed 7000 requests
Completed 8000 requests
Completed 9000 requests
Completed 10000 requests
Finished 10000 requests


Server Software:
Server Hostname:        127.0.0.1
Server Port:            8080

Document Path:          /api/getUser/1
Document Length:        410 bytes

Concurrency Level:      100
Time taken for tests:   92.016 seconds
Complete requests:      10000
Failed requests:        0
Total transferred:      5290000 bytes
HTML transferred:       4100000 bytes
Requests per second:    108.68 [#/sec] (mean)
Time per request:       920.161 [ms] (mean)
Time per request:       9.202 [ms] (mean, across all concurrent requests)
Transfer rate:          56.14 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.5      0       1
Processing:    33  911 222.0    872    8344
Waiting:       33  911 222.0    871    8343
Total:         33  912 222.0    872    8344

Percentage of the requests served within a certain time (ms)
  50%    872
  66%    882
  75%    892
  80%    906
  90%   1088
  95%   1313
  98%   1560
  99%   1685
 100%   8344 (longest request)
G:\JavaTools\Apache Http\Apache24\bin>ab -n10000 -c100 http://127.0.0.1:8080/api/getUserByCache/2
This is ApacheBench, Version 2.3 <$Revision: 1843412 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking 127.0.0.1 (be patient)
Completed 1000 requests
Completed 2000 requests
Completed 3000 requests
Completed 4000 requests
Completed 5000 requests
Completed 6000 requests
Completed 7000 requests
Completed 8000 requests
Completed 9000 requests
Completed 10000 requests
Finished 10000 requests


Server Software:
Server Hostname:        127.0.0.1
Server Port:            8080

Document Path:          /api/getUserByCache/2
Document Length:        376 bytes

Concurrency Level:      100
Time taken for tests:   5.494 seconds
Complete requests:      10000
Failed requests:        0
Total transferred:      4950000 bytes
HTML transferred:       3760000 bytes
Requests per second:    1820.06 [#/sec] (mean)
Time per request:       54.943 [ms] (mean)
Time per request:       0.549 [ms] (mean, across all concurrent requests)
Transfer rate:          879.82 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.5      0       7
Processing:    16   54  30.1     50     369
Waiting:       14   42  31.3     39     356
Total:         17   54  30.1     51     370

Percentage of the requests served within a certain time (ms)
  50%     51
  66%     54
  75%     56
  80%     58
  90%     62
  95%     64
  98%     67
  99%    334
 100%    370 (longest request)
d) 500個併發 10000次請求
G:\JavaTools\Apache Http\Apache24\bin>ab -n10000 -c500 http://127.0.0.1:8080/api/getUser/1
This is ApacheBench, Version 2.3 <$Revision: 1843412 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking 127.0.0.1 (be patient)
Completed 1000 requests
Completed 2000 requests
Completed 3000 requests
Completed 4000 requests
Completed 5000 requests
Completed 6000 requests
Completed 7000 requests
Completed 8000 requests
Completed 9000 requests
Completed 10000 requests
Finished 10000 requests


Server Software:
Server Hostname:        127.0.0.1
Server Port:            8080

Document Path:          /api/getUser/1
Document Length:        410 bytes

Concurrency Level:      500
Time taken for tests:   101.002 seconds
Complete requests:      10000
Failed requests:        0
Total transferred:      5290000 bytes
HTML transferred:       4100000 bytes
Requests per second:    99.01 [#/sec] (mean)
Time per request:       5050.108 [ms] (mean)
Time per request:       10.100 [ms] (mean, across all concurrent requests)
Transfer rate:          51.15 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   5.0      0     502
Processing:   110 4446 845.3   4573   20074
Waiting:       61 4445 848.0   4572   20074
Total:        110 4447 845.1   4573   20075

Percentage of the requests served within a certain time (ms)
  50%   4573
  66%   4586
  75%   4599
  80%   4614
  90%   4793
  95%   5016
  98%   5274
  99%   5694
 100%  20075 (longest request)
G:\JavaTools\Apache Http\Apache24\bin>ab -n10000 -c500 http://127.0.0.1:8080/api/getUserByCache/2
This is ApacheBench, Version 2.3 <$Revision: 1843412 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking 127.0.0.1 (be patient)
Completed 1000 requests
Completed 2000 requests
Completed 3000 requests
Completed 4000 requests
Completed 5000 requests
Completed 6000 requests
Completed 7000 requests
Completed 8000 requests
Completed 9000 requests
Completed 10000 requests
Finished 10000 requests


Server Software:
Server Hostname:        127.0.0.1
Server Port:            8080

Document Path:          /api/getUserByCache/2
Document Length:        376 bytes

Concurrency Level:      500
Time taken for tests:   5.571 seconds
Complete requests:      10000
Failed requests:        0
Total transferred:      4950000 bytes
HTML transferred:       3760000 bytes
Requests per second:    1794.97 [#/sec] (mean)
Time per request:       278.556 [ms] (mean)
Time per request:       0.557 [ms] (mean, across all concurrent requests)
Transfer rate:          867.69 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   5.0      0     501
Processing:    22  264  91.0    248     721
Waiting:       15  175 104.9    166     676
Total:         22  265  91.1    249     721

Percentage of the requests served within a certain time (ms)
  50%    249
  66%    256
  75%    273
  80%    279
  90%    291
  95%    304
  98%    709
  99%    716
 100%    721 (longest request)
e) 性能比較
RPS/QPS Mysql Redis
10個併發 1000個請求 114.80 [#/sec] (mean) 434.62 [#/sec] (mean)
100個併發 1000個請求 118.06 [#/sec] (mean) 1298.80 [#/sec] (mean)
100個併發 10000個請求 108.68 [#/sec] (mean) 1820.06 [#/sec] (mean)
500個併發 10000個請求 99.01 [#/sec] (mean) 1794.97 [#/sec] (mean)

 通過我們自己的實踐對MysqlRedis進行了同樣標準的壓力測試,很明顯可以發現隨着併發數量以及請求數量的增加,Redis所維持的服務性能比Mysql要優秀得多。ab這款產品相比其他的測試工具更加簡煉,沒有過於複雜的功能但是對於很多情況下也能夠滿足需求,相對更加輕量級,簡單易用。大家也可以嘗試着去親自進行嘗試以便對性能測試方面有一個更加深刻的理解。

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