SpringBoot整合Redis

99 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第15天,点击查看活动详情 >>

SpringBoot整合Redis

  • 1、引入maven
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.10.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.1.0</version>
    </dependency>

    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>

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

    <!-- 高版本redis的lettuce需要commons-pool2 -->
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-pool2</artifactId>
        <version>2.7.0</version>
    </dependency>

    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.62</version>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
                <fork>true</fork>
            </configuration>
        </plugin>

        <!-- MyBatis 逆向工程 插件 -->
        <plugin>
            <groupId>org.mybatis.generator</groupId>
            <artifactId>mybatis-generator-maven-plugin</artifactId>
            <version>1.3.6</version>
            <configuration>
                <!--允许移动生成的文件 -->
                <verbose>true</verbose>
                <!-- 是否覆盖 -->
                <overwrite>true</overwrite>
                <!-- 自动生成的配置 -->
                <configurationFile>
                    ${basedir}/src/main/resources/generator/generatorConfig.xml
                </configurationFile>
            </configuration>
            <!--下面这两个可以不配置-->
            <dependencies>
                <dependency>
                    <groupId>mysql</groupId>
                    <artifactId>mysql-connector-java</artifactId>
                    <version>8.0.18</version>
                </dependency>
                <dependency>
                    <groupId>org.mybatis.generator</groupId>
                    <artifactId>mybatis-generator-core</artifactId>
                    <version>1.3.6</version>
                </dependency>
            </dependencies>
        </plugin>
    </plugins>
</build>
  • 2、新建RedisConfig作为配置类
@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String,Object> redisTemplate(LettuceConnectionFactory factory){
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new StringRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(new StringRedisSerializer());
        redisTemplate.setConnectionFactory(factory);
        return redisTemplate;
    }
}
  • 3、新建RedisUtil作为Redis工具类
@Component
public class RedisUtil {

    @Autowired
    private RedisTemplate<String,Object> redisTemplate;

    public boolean set(String key,Object value){
        try{
            redisTemplate.opsForValue().set(key,value);
            return true;
        }catch (Exception e){
            return false;
        }
    }

    public boolean set(String key,Object value,long time){
        try{
            redisTemplate.opsForValue().set(key,value,time, TimeUnit.SECONDS);
            return true;
        }catch (Exception e){
            return false;
        }
    }

    public Object get(String key){
        return key == null ? null : redisTemplate.opsForValue().get(key);
    }

    ......
}
  • 4、application.properties加入Redis相关配置
# redis
# Redis服务器地址
spring.redis.host=127.0.0.1
# Redis服务器连接端口
spring.redis.port=6379
# Redis数据库索引(默认为0)
spring.redis.database=0
# Redis服务器连接密码(默认为空)
spring.redis.password=
# 连接超时时间(毫秒)
spring.redis.timeout=10000

# Lettuce
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.lettuce.pool.max-active=8
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.lettuce.pool.max-wait=10000
# 连接池中的最大空闲连接
spring.redis.lettuce.pool.max-idle=8
# 连接池中的最小空闲连接
spring.redis.lettuce.pool.min-idle=0
# 关闭超时时间
spring.redis.lettuce.shutdown-timeout=100
  • 5、测试redis
@RunWith(SpringRunner.class)
@SpringBootTest
public class ApplicationTest {

    @Autowired
    private RedisUtil redisUtil;

    @Test
    public void test() throws InterruptedException {
        redisUtil.set("yibo","你好",3);
        System.out.println(redisUtil.get("yibo"));
        Thread.sleep(3000);
        System.out.println(redisUtil.get("yibo"));
    }
}

企业级缓存使用

  • 定义仓储层接口PersonRepository,采用数据库和缓存实现
public interface PersonRepository {

    Person getPersonById(Integer id);
}

@Component
public class PersonRepositoryImpl implements PersonRepository {

    @Autowired
    private PersonMapper personMapper;

    public Person getPersonById(Integer id){
        return personMapper.selectByPrimaryKey(id);
    }
}

@Component
@Slf4j
public class PersonRepositoryCacheImpl implements PersonRepository {

    private static final String CACHE_PREFIX="cache_prefix_";

    @Autowired
    private RedisUtil redisUtil;

    @Resource(name="personRepositoryImpl")
    private PersonRepository personRepository;

    //这个用作缓存穿透使用
    private Person nullPerson = new Person(-1);

    //用随机数防止缓存雪崩
    private static final Random random = new Random();

    @Override
    public Person getPersonById(Integer id) {
        Person person = getPersonFromCache(id);
        if(person == null){
            log.info("cache not hit");
            person = personRepository.getPersonById(id);
            cachePerson(id,person);
        }else if(-1 == person.getId()){
            log.warn("cache hit null");
            return null;
        }
        log.info("cache hit id");
        return person;
    }

    private void cachePerson(Integer id,Person person){
        if(person != null){
            redisUtil.set(generateCacheKey(id),JSON.toJSONString(person),random.nextInt(10)+5);
        }else {
            redisUtil.set(generateCacheKey(id),JSON.toJSONString(nullPerson),random.nextInt(10)+5);
        }
    }

    private Person getPersonFromCache(Integer id){
        String person = (String)redisUtil.get(generateCacheKey(id));
        if(StringUtils.isEmpty(person)){
            return null;
        }
        return JSON.parseObject(person,Person.class);
    }

    private String generateCacheKey(Integer id){
        return CACHE_PREFIX + id;
    }
}
  • 定义服务接口PersonService,实现服务接口api并调用仓储层
public interface PersonService {

    Person getPersonById(Integer id);
}

@Service
public class PersonServiceImpl implements PersonService {

    @Resource(name="personRepositoryCacheImpl")
    private PersonRepository personRepository;

    @Override
    public Person getPersonById(Integer id) {
        return personRepository.getPersonById(id);
    }
}

缓存击穿

  • 原因:一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,导致DB瞬间压力过大,压垮DB,就像在一个屏障上凿开了一个洞。
  • 方案:热点数据缓存永不过期

缓存穿透

  • 原因:恶意攻击去查数据库一定不存在的数据,对数据库造成压力,甚至压垮数据库
  • 方案:使用特殊缓存空值标识对象不存在

缓存雪崩

  • 原因:在某一个时间段,缓存集中过期失效,对于数据库而言,就会产生周期性的压力波峰
  • 方案:设置过期时间加上随机因子,尽可能分散缓存过期时间