redis常见使用场景与实例

本文探讨了分布式系统中遇到的问题,包括多节点用户会话保存、订单定时取消、服务访问延迟、分布式任务重复执行和高并发接口处理。针对这些问题,提出了使用Redis进行会话管理、实现延时队列取消订单、使用分布式锁防止并发冲突以及利用SpringCache进行缓存优化的解决方案。此外,还介绍了Redis桌面管理工具和Apache JMeter等常用工具。
摘要由CSDN通过智能技术生成

问题(需求):
项目中一些常见问题
1.多节点用户会话保存
2.订单大规模定时取消
3.产品服务访问慢
4.分布式任务重复执行
5.高并发提交:设备状态上报接口

接口高并发

/**
 * 接到上报
 * 1
 * @param params
 * @return
 */
@Override
public int save(CarAccessQueryParams params){
    CarAccess carAccessExit = getOneCarAccessByCarnum(params.getCarnum());
    CarAccess carAccessParam = CarAccessQueryParams.convertToEntity(params);
    String key = String.format("app:%s",params.getCarnum());
    Long count=  redisCache.increment(key);
    if(count == 1) {
        redisCache.expire(key, 60);
        if(carAccessExit==null){
            carAccessDao.insert(carAccessParam);
        }else {
            carAccessDao.updateByCarnum(carAccessParam);
        }
    }else if((count>1)&&(carAccessParam.getPicUrl()!=null)){//<1分钟
        if(carAccessExit!=null){
            carAccessDao.updateByCarnum(carAccessParam);
        }else{
            try {
                TimeUnit.SECONDS.sleep(1);
            }catch (Exception e){
                logger.error(String.format("carAccess save延时失败==%s"),e.getMessage());
            }
            carAccessDao.updateByCarnum(carAccessParam);
        }
    }
    return 1;
}

分布式会话

@Configuration
@EnableRedisHttpSession(redisNamespace = "{app}",maxInactiveIntervalInSeconds=1800)  
public class RedisConfig {


}

延时队列

public abstract class AbstractRedisDelayQueue {

    protected Logger logger = LoggerFactory.getLogger(AbstractRedisDelayQueue.class);

    @Autowired
    protected RedisTemplate redisTemplate;

    /**
     * 插入任务id
     * @param taskId 任务id(队列内唯一)
     * @param time 延时时间(单位 :秒)
     * @return 是否插入成功
     */
    public boolean addTaskId(String taskId,  Integer time) {
        Calendar instance = Calendar.getInstance();
        instance.add(Calendar.SECOND, time);
        long delaySeconds = instance.getTimeInMillis() / 1000;
        try {
            String key = setDelayQueueName();
            boolean zadd = redisTemplate.opsForZSet().add(key, taskId, delaySeconds);
            return zadd;
        } catch (Exception e) {
            logger.warn("AbstractRedisDelayQueue addTaskId异常===",e);
            e.printStackTrace();
            return false;
        }
    }

    private void startDelayQueue() {
        logger.info("AbstractRedisDelayQueue---init----QueueName==> {}", setDelayQueueName());
        while (true) {
            try {
                long now = System.currentTimeMillis() / 1000;
                // 获取当前时间前的任务列表
                Set<Object> taskIds = redisTemplate.opsForZSet().rangeByScoreWithScores(setDelayQueueName(), 0, now);
                // 如果不为空则遍历判断其是否满足取消要求
                if (!CollectionUtils.isEmpty(taskIds)) {
                    for (Object obj : taskIds) {
                        JSONObject jsonObject = JSONObject.parseObject(JSON.toJSONString(obj));
                        String taskId = jsonObject.get("value").toString();
                        Long num = redisTemplate.opsForZSet().remove(setDelayQueueName(), taskId);
                        // 如果移除成功, 则取消订单
                        if (null != num && num > 0) {
                            invoke(taskId);
                        }
                    }
                }
            } catch (Exception e) {
                logger.error("处理延时任务发生异常,异常原因为 {}", e);
            } finally {
                // 间隔一分钟执行一次
                try {
                    TimeUnit.SECONDS.sleep(60L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        }
    }


    /**
     * 最终执行的任务方法
     * @param taskId 任务id
     */
    public abstract void invoke(String taskId);

    /**
     * 要实现延时队列的名字
     * @return
     */
    public abstract String setDelayQueueName();

    /**
     * 项目启动时执行初始化
     */
    @PostConstruct
    public void init(){
        ExecutorService singleThreadExecutor = new ThreadPoolExecutor(1, 1, 300L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(16),
                new BasicThreadFactory.Builder().namingPattern("start-delay-queue-thread-pool-%d").daemon(true).build(), new ThreadPoolExecutor.AbortPolicy());
        singleThreadExecutor.execute(()-> {
            logger.info("startDelayQueue--- {}", Thread.currentThread().getName());
            startDelayQueue();
        });
        singleThreadExecutor.shutdown();
    }


public class CancelOrderDelayQueue extends AbstractRedisDelayQueue {
    @Autowired
    private OrderServiceFacade orderServiceFacade;

    @Override
    public void invoke(String taskId) {
        log.info("超时未支付自动取消======================" + taskId);
        OrderResponse orderResponse = orderServiceFacade.queryOrderByOrderNo(taskId);
        if(Objects.nonNull(orderResponse)){
            Optional.ofNullable(orderResponse.getMain()).ifPresent(main -> {
                if(TradeStatusEnum.CREATE.equals(main.getOrderStatus())){
                    orderServiceFacade.cancleOrderByOrderNo(OrderConstant.ORDER_TYPE_MAIN,main.getOrderNo(),"超时自动关闭");
                }
            });
        }
    }

    @Override
    public String setDelayQueueName() {
        return RedisKeyConstant.CANCEL_ORDER_DELAY_QUEUE_NAME;
    }
}

分布式锁

@Scheduled(cron = "0 0 10 * * ?")
public void remindFollowCustomerNumber(){
    try{
        log.info("RemindTask remindUnFollow {}",new Date());
        if (!redisCache.setNX(DsopConstants.REMIND_FLAG_FOLLOW_CUSTOMER_NUMBER,DsopConstants.REMIND_FLAG_VALUE,DsopConstants.REMIND_FLAG_TIMEOUT, TimeUnit.SECONDS)){
            log.info("任务已执行");
            return;
        }
        List<FollowRemindNumberDTO> customerInfos=customerFollowService.queryRemindCustomerNumber();
        customerInfos.forEach(c->{
            wxPushService.pushRemindCustomerNumber(c.getManager(),c.getManagerPhone(),c.getCustomeraNumber(),c.getCustomerbNumber());

        });
    }catch (Exception e){
        log.error("RemindTask remind异常:{}",e.getMessage());
    }finally {
        redisCache.del(DsopConstants.REMIND_FLAG_FOLLOW_CUSTOMER_NUMBER);
    }
}

reids提效工具springcache

@Override
   @Cacheable(value = "newProductDetailList",key = "#root.target.getFormatKey(#p0)",cacheManager = "cacheManager5Minute")
   public Page<ItemResponse> quertNewProductDetailListCache(ItemOnsaleRequest itemOnsaleRequest) {
   CommResponse<Page<ItemResponse>> onsale = productFeign.getOnsale(itemOnsaleRequest);
   if(onsale.getCode()!=0){
      throw new BusinessException(onsale.getDesc());
   }
   return onsale.getData();
}

5.常见场景
5.1.分布式锁
5.2.缓存
5.3.统一会话
5.4.token认证

6.问题的解决方案代码示例

分布式缓存之缓存击穿+解决⽅案

简介:分布式缓存必考题之缓存击穿+解决⽅案

缓存击穿 (某个热点key缓存失效了)

缓存中没有但数据库中有的数据,假如是热点数据,那key在缓存过期的⼀刻,同时有⼤量的请求,这些请求都会击穿到DB,造成瞬时DB请求量⼤、压⼒增⼤。和缓存雪崩的区别在于这⾥针对某⼀key缓存,后者则是很多key。

预防

设置热点数据不过期

定时任务定时更新缓存

设置互斥锁

SpringCache解决⽅案

缓存的同步 sync

sync 可以指示底层将缓存锁住,使只有⼀个线程可以进⼊计算,⽽其他线程堵塞,直到返回结果更新到缓存中

@Cacheable(value = {“product”},key ="#root.args[0]", cacheManager =

“customCacheManagr”,sync=true)

分布式缓存缓存雪崩+解决⽅案

简介:分布式缓存之缓存雪崩+解决⽅案

缓存雪崩 (多个热点key都过期)

⼤量的key设置了相同的过期时间,导致在缓存在同⼀时刻全部失效,造成瞬时DB请求量⼤、压⼒骤增,引起雪崩

预防

存数据的过期时间设置随机,防⽌同⼀时间⼤量数据过期现象发⽣

设置热点数据永远不过期,定时任务定时更新

SpringCache解决⽅案

设置差别的过时时间

⽐如CacheManager配置多个过期时间维度

配置⽂件 time-to-live

cache:

#使⽤的缓存类型

type: redis

#过时时间

redis:

time-to-live: 3600000

开启前缀,默以为true

use-key-prefix: true

键的前缀,默认就是缓存名cacheNames

key-prefix: MIDDLE_CACHE

是否缓存空结果,防⽌缓存穿透,默以为true

cache-null-values: true
cache-null-values: true

分布式缓存之缓存穿透

+解决⽅案

简介:分布式缓存之缓存穿透+解决⽅案

缓存穿透(查询不存在数据)查询⼀个不存在的数据,由于缓存是不命中的,并且出于容错考虑,如发起为id为“-1”不存在的数据

如果从存储层查不到数据则不写⼊缓存这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。存在⼤量查询不存在的数据,可能DB就挂掉了,这也是⿊客利⽤不存在的key频繁攻击应⽤的⼀种⽅式。

预防

接⼝层增加校验,数据合理性校验

缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,设置短点的过期时间,防⽌同个key被⼀直攻击

SpringCache解决⽅案

空结果也缓存,默认不配置condition或者unless就⾏

cache:

#使⽤的缓存类型

type: redis

#过时时间

redis:

time-to-live: 3600000

开启前缀,默以为true

use-key-prefix: true

键的前缀,默认就是缓存名cacheNames

key-prefix: MIDDLE_CACHE

是否缓存空结果,防⽌缓存穿透,默以为true

 @Override
 @Cacheable(value = "mallPlantList", key = "0", condition = "#root.target.getFormatKey(#p0) != null", cacheManager = "cacheManager5Minute",unless="#result.size()==0")
    public List<PlantDTO> getListAll() {
        PlantQueryForm plantQueryForm = new PlantQueryForm();
        plantQueryForm.setSize(50);
        CommResponse<Page<PlantDTO>> search = plantFeign.search(plantQueryForm);
        if(search.getCode()!=0){
            throw new BusinessException(search.getDesc());
        }
        Page<PlantDTO> data = search.getData();
        if(data != null){
            List<PlantDTO> records = data.getRecords();
            return records;
        }
        return new ArrayList<>();
    }

常用工具:
1.RedisDesktopManager
2.ApacheJMeter

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值