Java8自带BlockingQueue阻塞队列 卡死服务问题

最近在做消息发送压测, 发现BlockingQueue会在堆积200w数据时卡死服务.

前情提要:

用BlockingQueue是为了做限速发送, 对方的接口每个账号只能每秒50进行发送, 超过会报错. 我们这里每个用户开放的200于是有差速就做了限速队列.

之前压测没发现这个问题, 是因为多个账号同时发消费也快. 没顶到单个队列200w. 这次用同一个账号放开限速压极限堆积量发现了这个问题.

之前用的BlockingQueue的put和take多线程生产和消费. 为了确定是不是某个特定版本java8导致的问题, 测了几台服务器上不同java8的微分版本都相同的问题, 被逼无奈自己写限速队列了.

用了线程安全的ConcurrentLinkedQueue, 自己加锁. 目前没设置队列上限, 后面看情况加个队列数量上限控制. 超过数量直接阻塞等待消费.


import cn.hutool.core.util.ObjectUtil;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RRateLimiter;
import org.redisson.api.RateIntervalUnit;
import org.redisson.api.RateType;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Function;

/**
 * 双队列先进先出redis速率控制器
 */
@Slf4j
@Data
@Component
public class LRURedisQueue<T> {

    /**
     * 令牌桶redisKey
     */
    private static final String RateLimiterKey = "RateLimiter:%s";

    /**
     * 初始化标识
     */
    private boolean isInit = false;

    /**
     * 发送速度 每秒speed
     */
    private int speed = 50;

    /**
     * 线程数量
     */
    private int threadCount = 32;

    /**
     * 最大值
     */
    private int maxSize = 1024 * 1024 * 10;

    /**
     * 数据列表1
     */
    private ConcurrentLinkedQueue<Package<T>> list = null;

    /**
     * 线程
     */
    private final List<Thread> threadList = new ArrayList<>();

    /**
     * 锁条件
     */
    private Condition notEmpty;

    /**
     * 锁
     */
    private ReentrantLock lock;

    /**
     * 执行方法
     */
    private Function<T, Void> func;

    /**
     * redis限速令牌生成器
     */
    private RRateLimiter rRateLimiter;



    /**
     * 增加速度
     */
    public void addSpeed(){
        if(rRateLimiter == null){
            log.info("队列未初始化");
            return;
        }
        speed += speed * 0.1;
        rRateLimiter.setRate(RateType.OVERALL, speed, 1, RateIntervalUnit.SECONDS);
    }

    /**
     * 减慢速度
     */
    public void subSpeed(){
        if(rRateLimiter == null){
            log.info("队列未初始化");
            return;
        }
        speed *= 0.9;
        rRateLimiter.setRate(RateType.OVERALL, speed, 1, RateIntervalUnit.SECONDS);
    }

    /**
     * 推入队列
     */
    public void push(T t, int count) {
        log.info("push");
        if (!ObjectUtil.isEmpty(t)) {
            final ReentrantLock lock = this.lock;
            lock.lock();
            try {
                list.offer(Package.<T>builder().item(t).count(count).build());
                notEmpty.signal();
                log.info("队列数量: {}", list.size());
            } catch (Exception e) {
                log.info("推入队列失败: {}, {}", e.getMessage(), e);
            } finally {
                lock.unlock();
            }
        }
    }

    /**
     * 初始化
     */
    public void init(Function<T, Void> func, RedissonClient redis, String key, int concurrent) throws Exception {
        log.info("初始化线程队列");
        if (func == null) {
            throw new Exception("线程调用方法为空");
        }
        lock = new ReentrantLock();
        notEmpty = lock.newCondition();
        this.func = func;
        // 先销毁旧的线程
        stop();
        // 获取令牌桶
        speed = concurrent;
        rRateLimiter = redis.getRateLimiter(String.format(RateLimiterKey, key));
        rRateLimiter.setRate(RateType.OVERALL, speed, 1, RateIntervalUnit.SECONDS);
        // 清空列表
        if (list != null) {
            list.clear();
        }
        // 创建新列表
        list = new ConcurrentLinkedQueue<>();
        // 创建新线程
        for (int i = 0; i < threadCount; i++) {
            int finalI = i;
            Thread thread = new Thread(()->{
                while (true) {
                    try {
                        // 加锁(可中断)
                        lock.lockInterruptibly();
                        log.info("pop" + finalI + "等待消费1");
                        Package<T> pack;
                        while ((pack = list.poll()) == null) {
                            log.info("等待获取" + finalI);
                            notEmpty.await();
                        }
                        // 解锁
                        lock.unlock();
                        rRateLimiter.acquire(1);
                        if (pack.count > 1) {
                            rRateLimiter.acquire(pack.count - 1);
                        }
                        func.apply(pack.item);
                        log.info("pop" + finalI + "消费完成");
                    } catch (Exception e) {
                        log.error("线程队列线程报错: {}, {}", e.getMessage(), e);
                    } finally {
                        if (lock.isHeldByCurrentThread()) {
                            lock.unlock();
                        }
                    }
                }
            });
            thread.start();
            threadList.add(thread);
        }
        this.isInit = true;
    }

    /**
     * 销毁所有线程
     */
    public void stop() {
        for (Thread thread : threadList) {
            try {
                if (thread != null) {
                    thread.stop();
                }
            } catch (Exception e) {
                log.info("主动关闭线程报错: {}, {}", e.getMessage(), e);
            }
        }
    }
}
/**
 * 封装类
 */
@Builder
@NoArgsConstructor
@AllArgsConstructor
class Package<T> {
    T item;
    int count;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值