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这款产品相比其他的测试工具更加简炼,没有过于复杂的功能但是对于很多情况下也能够满足需求,相对更加轻量级,简单易用。大家也可以尝试着去亲自进行尝试以便对性能测试方面有一个更加深刻的理解。

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