springBoot+redis整合及分布式锁

一、springboot整合redis步骤

首先我们要知道什么是redis:springBoot+redis整合及分布式锁_第1张图片

第一步在pom.xml文件中加入redis依赖

基于2.1.6.RELEASE版本

        
        
            org.springframework.boot
            spring-boot-starter-data-redis
        

        
        
            org.redisson
            redisson-spring-boot-starter
            3.11.6
        

第二步在application.yml配置redis端口号连接信息

spring:
  redis:
    host: ip
    port: 6379
    password:  #没有密码就可以不填

第三步 创建config包RedisConfig类, 直接cv

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Value;
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.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Component;

import java.time.Duration;

@Component
@Configuration // 定义一个配置类
public class RedisConfig {
    @Value("${spring.redis.host}")
    private String address;

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

    @Bean
    public RedissonClient singletonModeRedisson() {
        Config config = new Config();
        // 使⽤"redis://"来启⽤SSL连接
        config.useSingleServer().setAddress("redis://"+address+":"+port);
        return Redisson.create(config);
    }

    @Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {

        RedisTemplate template = new RedisTemplate();

        template.setConnectionFactory(redisConnectionFactory);
        // 使用JSON格式序列化对象,对缓存数据key和value进行转换
        Jackson2JsonRedisSerializer jacksonSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        // 解决查询缓存转换异常的问题
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jacksonSerializer.setObjectMapper(om);
        // 设置RedisTemplate模板API的序列化方式为JSON
        template.setDefaultSerializer(jacksonSerializer);
        return template;
    }

    @Bean
    public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory){
        // 序列化value
        Jackson2JsonRedisSerializer jacksonSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        // 序列化key
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();

        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jacksonSerializer.setObjectMapper(om);

        RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofDays(1)) //配置缓存数据的默认存活时间1天
                .serializeKeysWith(
                        RedisSerializationContext.SerializationPair.fromSerializer(stringRedisSerializer)
                ) // 指定key进行序列化
                .serializeValuesWith(
                        RedisSerializationContext.SerializationPair.fromSerializer(jacksonSerializer)
                )
                .disableCachingNullValues(); // null不参与序列化操作

        RedisCacheManager cacheManager = RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(configuration).build();
        return cacheManager;
    }
}

补充:RedisTemplate对象提供了各种往redis数据库中存储数据的类型:

@Autowired
private RedisTemplate redisTemplate;

补充:RedissonClient提供了一些高级功能,如分布式锁、分布式集合、分布式对象等,可以帮助简化分布式系统中的开发和管理。

@Autowired
private RedissonClient redisson;

分布式锁也很简单,举个例子

这里是我自己写的秒杀商品的业务

controller层

    @GetMapping("buy/{vid}/{num}")
    public synchronized ResponseResult buyVeggies(@PathVariable String vid,@PathVariable Integer num){
        // 1.获取锁对象
        RLock redissonLock = redisson.getLock("order");
        // 2.加锁(设置锁的过期时间为30秒)
        // redissonLock.lock();
        redissonLock.lock(30, TimeUnit.SECONDS);

        ordersService.subShopNum(vid,num);
        // 3.释放锁
        redissonLock.unlock();

        return ResponseResult.getResponseResult("下单成功");
    }

service层

public interface OrdersService {

    String subShopNum(String id,Integer num);

    void updateOrdersStatus(String oid);

    void payVeggies(String oid);
}

业务逻辑层 这里我还用了RabbitMQ去实现订单超时的业务

@Override
    public String subShopNum(String vid, Integer num) {
        ValueOperations opsForValue = redisTemplate.opsForValue();
        // 获取到商品信息
        Veggies veggie = veggiesMapper.selectById(vid);
        // redis中查询该商品
        Object o = opsForValue.get("veggie:" + veggie.getId());
        // 如果没有该商品就从mysql中去查询并存入redis中
        if (o == null || o == ""){
            opsForValue.set("veggie:" + veggie.getId(),veggie);
            o = opsForValue.get("veggie:" + veggie.getId());
        }
        // 获取到redis中的商品
        Veggies redisVeggies = (Veggies) o;
        // 获取商品库存数
        Integer shopRepertory = redisVeggies.getShopRepertory();
        // 库存数<0时抛出异常  无库存
        if (shopRepertory-num<0){
            throw new RepertoryException();
        }else {
            // 每次下单刷新redis中的库存
            redisVeggies.setShopRepertory(shopRepertory - num);
            opsForValue.getAndSet("veggie:" + veggie.getId(),redisVeggies);

            // 将下单信息加入订单表中
            Orders orders = new Orders();
            orders.setVid(redisVeggies.getId());
            orders.setShopName(redisVeggies.getShopName());
            orders.setShopDesc(redisVeggies.getShopDesc());
            orders.setShopNum(num);
            orders.setShopPrice(redisVeggies.getShopPrice()*num);
            orders.setPayStatus("待支付");

            int i = ordersMapper.insert(orders);
            if (i == 1){
                // 将订单id传给延迟队列
                rabbitTemplate.convertAndSend("business.exchange","dl.delay",orders.getOid());
            }
            if (i < 0){
                throw new InsertException();
            }
        }

        return "下单成功";
    }

    @Override
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue("dead.letter.queue"),
            exchange = @Exchange("dead.letter.exchange"),
            key = "dead.letter"
    ))
    public void updateOrdersStatus(String oid) {

        // 超过规定时间未支付后的操作
        Orders order = ordersMapper.selectById(oid);
        if ("待支付".equals(order.getPayStatus())){
            order.setPayStatus("已超时");
            ordersMapper.updateById(order);
            System.out.println(order.getOid()+" : "+"该订单已超时");
        }
    }

做项目过程中遇到的问题:

1、redis

在业务中我将一些热数据缓存到redis里面,这时候数据量比较大的话,我们就要对这些热数据进行分页,分页的方式有2种:

第一:从redis拿出所有数据后,再做内存分页(不推荐),热点数据小的时候可以这样做,性能相差不是很大,但是当数据量大的时候,分页期间就会占用大量内存,或撑爆;

第二:就是先进行分页然后在存入redis中,每一页都有一个key

但是在进行增删改的时候会有数据不同步的问题

解决方案:

在增删改的时候将redis数据库中的分页key模糊查询,然后删除(这里会有一个雪崩的问题),在进行优化我加了分布式锁。

你可能感兴趣的:(spring,boot,redis,java)