All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.redismq.core.RedisMqClient Maven / Gradle / Ivy

There is a newer version: 1.2.1
Show newest version
package com.redismq.core;


import com.aventrix.jnanoid.jnanoid.NanoIdUtils;
import com.redismq.common.constant.RedisMQConstant;
import com.redismq.common.pojo.Client;
import com.redismq.common.pojo.PushMessage;
import com.redismq.common.pojo.Queue;
import com.redismq.common.connection.RedisMQClientUtil;
import com.redismq.container.RedisMQListenerContainer;
import com.redismq.id.MsgIDGenerator;
import com.redismq.id.WorkIdGenerator;
import com.redismq.queue.QueueManager;
import com.redismq.rebalance.ClientConfig;
import com.redismq.rebalance.RebalanceImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.listener.ChannelTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.util.CollectionUtils;

import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static com.redismq.common.config.GlobalConfigCache.GLOBAL_CONFIG;
import static com.redismq.common.constant.GlobalConstant.CLIENT_EXPIRE;
import static com.redismq.common.constant.GlobalConstant.CLIENT_RABALANCE_TIME;
import static com.redismq.common.constant.GlobalConstant.CLIENT_REGISTER_TIME;
import static com.redismq.common.constant.GlobalConstant.SPLITE;
import static com.redismq.common.constant.RedisMQConstant.getRebalanceLock;
import static com.redismq.common.constant.RedisMQConstant.getVirtualQueueLock;


/**
 * @Author: hzh
 * @Date: 2022/11/4 16:44 RedisMQ客户端  实现负载均衡
 */
public class RedisMqClient {
    
    protected static final Logger log = LoggerFactory.getLogger(RedisMqClient.class);
    
    /**
     * 注册线程客户端维持心跳线程
     */
    private final ScheduledThreadPoolExecutor registerThread = new ScheduledThreadPoolExecutor(1);
    
    /**
     * 负载均衡心跳线程
     */
    private final ScheduledThreadPoolExecutor rebalanceThread = new ScheduledThreadPoolExecutor(1);
    
    /**
     * 容器管理者
     */
    private final RedisListenerContainerManager redisListenerContainerManager;
    
    /**
     * redisClient客户端 可以是jedis luccute 和spring
     */
    private final RedisMQClientUtil redisMQStoreUtil;
    
    /**
     * 客户端id
     */
    private final String clientId;
    /**
     * 应用名
     */
    private final String applicationName;
    /**
     * 机器id
     */
    private Integer workId;
    
    /**
     * 负载均衡机制
     */
    private final RebalanceImpl rebalance;
    /**
     * 工作id生成器
     */
    private final WorkIdGenerator workIdGenerator;
    
    /**
     * 容器
     */
    private RedisMessageListenerContainer redisMessageListenerContainer;
    
    /**
     * 是否订阅消息
     */
    private boolean isSub;
    
    public RedisMqClient(RedisMQClientUtil redisMQStoreUtil, RedisListenerContainerManager redisListenerContainerManager,
            RebalanceImpl rebalance,String applicationName,WorkIdGenerator workIdGenerator) {
        this.redisMQStoreUtil = redisMQStoreUtil;
        this.clientId = ClientConfig.getLocalAddress() + SPLITE + NanoIdUtils.randomNanoId();
        this.redisListenerContainerManager = redisListenerContainerManager;
        this.rebalance = rebalance;
        this.applicationName = applicationName;
        this.workIdGenerator = workIdGenerator;
    }
    
    public void setRedisMessageListenerContainer(RedisMessageListenerContainer redisMessageListenerContainer) {
        this.redisMessageListenerContainer = redisMessageListenerContainer;
    }
    
    public String getClientId() {
        return clientId;
    }
    
    public RedisListenerContainerManager getRedisListenerContainerManager() {
        return redisListenerContainerManager;
    }
    
    
    public void registerClient() {
        if (workId == null){
            workId = workIdGenerator.getSnowId();
            List clients = redisMQStoreUtil.getClients();
            List workIds = clients.stream().map(Client::getWorkId).collect(Collectors.toList());
            while (workIds.contains(workId)){
                log.error("redis-mq registerClient workId duplicate");
                try {
                    Thread.sleep(1000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                workId = workIdGenerator.getSnowId();
            }
            MsgIDGenerator.init(workId);
        }
       
        log.debug("registerClient :{} applicationName:{} workId:{}", clientId, applicationName,workId);
        Client client = new Client();
        client.setClientId(clientId);
        client.setApplicationName(applicationName);
        client.setWorkId(workId);
        //注册客户端
        redisMQStoreUtil.registerClient(client);
    }
    
    public List allClient() {
        return redisMQStoreUtil.getClients();
    }
    
    public Long removeExpireClients() {
        // 过期的客户端
        long max = System.currentTimeMillis() - CLIENT_EXPIRE * 1000L;
        return redisMQStoreUtil.removeClient(0, max);
    }
    
    public Long removeAllClient() {
        log.info("redismq removeAllClient");
        return redisMQStoreUtil.removeClient(0, Double.MAX_VALUE);
    }
    
    public void destory() {
        //停止任务
        redisMQStoreUtil.removeClient(clientId);
        redisListenerContainerManager.stopAll();
        publishRebalance();
        log.info("redismq client remove currentVirtualQueues:{} ", QueueManager.getCurrentVirtualQueues());
    }
    
    public void start() {
        // 清理所有客户端
        removeAllClient();
        // 当前客户端暂时监听所有队列  等待下次重平衡所有队列.防止新加入客户端时.正好有客户端退出.而出现有几个队列在1分钟内没有客户端监听的情况 doReblance已经注册
        //        registerClient();
        // 先订阅平衡消息,以免平衡的消息没有收到
        rebalanceSubscribe();
        // 订阅服务器消息
        serverSubscribe();
        // 重平衡
        rebalance();
        // 30秒自动注册
        startRegisterClientTask();
        // 20秒自动重平衡
        startRebalanceTask();
        //启动队列监控
        redisListenerContainerManager.startRedisListener();
        //启动延时队列监控
        redisListenerContainerManager.startDelayRedisListener();
    }
    
    private void serverSubscribe() {
        redisMessageListenerContainer.addMessageListener(new RedisServerListener(),
                new ChannelTopic(RedisMQConstant.getServerTopic()));
    }
    
    
    // 多个服务应该只有一个执行重平衡
    public void rebalanceTask() {
        String lockKey = getRebalanceLock();
        Boolean success = redisMQStoreUtil.lock(lockKey, Duration.ofSeconds(CLIENT_RABALANCE_TIME));
        if (success != null && success) {
            Long count = removeExpireClients();
            if (count != null && count > 0) {
                log.info("doRebalance removeExpireClients count=:{}", count);
                rebalance();
                // 消费锁是30秒 这个值和消费所相关联
                // 延时指定消费锁锁定的时间再去重新拉取一次消息,防止服务下线重启导致的消息没有被其他队列消费的问题
                new ScheduledThreadPoolExecutor(1)
                        .schedule(this::repush, GLOBAL_CONFIG.virtualLockTime, TimeUnit.SECONDS);
            }
        }
    }
    
    /**
     * 平衡
     */
    public void rebalance() {
        // 发布重平衡 会让其他服务暂停拉取消息
        publishRebalance();
        // 在执行重平衡.当前服务暂停重新分配拉取消息 放到注册客户端中
        doRebalance();
    }
    
    
    /**
     * 暂停消息分配.重新负载均衡后.重新拉取消息
     */
    public void doRebalance() {
        registerClient();
        redisListenerContainerManager.pauseAll();
        //临时解决重平衡问题.这里主要是因为有可能出现某些客户端还没注册进来 ,等200毫秒等他们都注册进来。不是好方法但是行得通。主要是这个时间不好确定。
        // 如果redis有延迟那么重平衡就有问题,那么后果就是消息分配不平均
        try {
            Thread.sleep(200L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        rebalance.rebalance(allClient(), clientId);
        repush();
    }
    
    private void publishRebalance() {
        redisMQStoreUtil.publishRebalance(clientId);
    }
    
    
    /**
     * 重平衡时对任务重新进行拉取
     */
    public void repush() {
        Map> queues = QueueManager.getCurrentVirtualQueues();
        boolean isEmpty = queues.values().stream().allMatch(CollectionUtils::isEmpty);
        
        //没有监听的队列取消订阅
        if (isEmpty) {
            unSubscribe();
            return;
        }
        
        //监听队列消息的订阅
        subscribe();
        
        //此操作 On2 如果有几千个虚拟队列的话。那么最少也要有几百个Queue 这里性能不会慢。但是另一边监听到会去redis中获取。线程数不多可能会阻塞
        queues.forEach((k, v) -> {
            Queue queue = QueueManager.getQueue(k);
            if (queue == null) {
                log.error("repush queue is null");
                return;
            }
            List virtualQueues = QueueManager.getCurrentVirtualQueues().get(k);
            if (CollectionUtils.isEmpty(virtualQueues)) {
                return;
            }
            // 先获取队列锁删除
            List list = new ArrayList<>();
            virtualQueues.forEach(virtualQueue -> list.add(getVirtualQueueLock(virtualQueue)));
            RedisMQListenerContainer redisistenerContainer = redisListenerContainerManager
                    .getRedisistenerContainer(k);
            redisistenerContainer.pause();
        
            //获取虚拟队列重新推送到阻塞队列
            virtualQueues.forEach(vq -> {
                PushMessage pushMessage = new PushMessage();
                pushMessage.setQueue(vq);
                pushMessage.setTimestamp(System.currentTimeMillis());
                
                //推送到指定的队列
                LinkedBlockingQueue delayBlockingQueue = redisListenerContainerManager
                        .getDelayBlockingQueue();
                LinkedBlockingQueue linkedBlockingQueue = redisListenerContainerManager
                        .getLinkedBlockingQueue();
                if (queue.isDelayState()) {
                    delayBlockingQueue.add(pushMessage);
                } else {
                    linkedBlockingQueue.add(vq);
                }
            });
        });
    }
    
    /**
     * 监听队列消息的订阅
     */
    public synchronized void subscribe() {
        if (!isSub) {
            RedisMqClient redisMqClient = this;
            redisMessageListenerContainer.addMessageListener(new RedisPullListener(redisMqClient),
                    new ChannelTopic(RedisMQConstant.getTopic()));
            isSub = true;
        }
    }
    
    /**
     * 取消监听队列消息的订阅
     */
    public synchronized void unSubscribe() {
        if (isSub) {
            RedisMqClient redisMqClient = this;
            redisMessageListenerContainer.removeMessageListener(new RedisPullListener(redisMqClient),
                    new ChannelTopic(RedisMQConstant.getTopic()));
            isSub = false;
        }
    }
    
    /**
     * 负载均衡订阅
     */
    public void rebalanceSubscribe() {
        RedisMqClient redisMqClient = this;
        redisMessageListenerContainer.addMessageListener(new RedisRebalanceListener(redisMqClient),
                new ChannelTopic(RedisMQConstant.getRebalanceTopic()));
    }
    
    /**
     * 开始注册客户任务   心跳任务
     */
    public void startRegisterClientTask() {
        registerThread.scheduleAtFixedRate(this::registerClient, CLIENT_REGISTER_TIME, CLIENT_REGISTER_TIME,
                TimeUnit.SECONDS);
    }
    
    /**
     * 开始负载均衡任务
     */
    public void startRebalanceTask() {
        rebalanceThread.scheduleAtFixedRate(this::rebalanceTask, CLIENT_RABALANCE_TIME, CLIENT_RABALANCE_TIME,
                TimeUnit.SECONDS);
    }
    
    /**
     * 队列寄存器
     *
     * @param queue 队列
     */
    public Queue registerQueue(Queue queue) {
        Set allQueue = getAllQueue();
        allQueue.stream().filter(redisQueue -> redisQueue.getQueueName().equals(queue.getQueueName()))
                .forEach(redisMQStoreUtil::removeQueue);
        redisMQStoreUtil.registerQueue(queue);
        return queue;
    }
    
    /**
     * 获取所有队列
     *
     * @return {@link Set}<{@link Queue}>
     */
    public Set getAllQueue() {
        Set queueList = redisMQStoreUtil.getQueueList();
        return queueList;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy