RocketMQ源码 Broker-RebalanceLockManager 重平衡锁定组件源码分析

前言

在queue队列数量弹性变化、consumer数量弹性变化时,需要对consumer客户端进行Rebalance重平衡,重新优化consumer和queue之间的关系,RebalanceLockManager就负责在重平衡时,进行重平衡锁定关系的管理。

源码版本:4.9.3

源码架构图

核心数据结构

RebalanceLockManager重平衡锁定组件,在内存中设计了一个table核心数据结构,Map<consumerGroup, Map<MessageQueue, LockEntry>>,维护了指定consumerGroup下所有队列的锁定状态。

// 核心数据结构,Map<consumerGroup, Map<MessageQueue, LockEntry>>,key为消费组,value为MessageQueue和LockEntry的映射
// 针对每个消费组的每个MessageQueue都有一份锁定信息
private final ConcurrentMap<String/* group */, ConcurrentHashMap<MessageQueue, LockEntry>> mqLockTable =
        new ConcurrentHashMap<String, ConcurrentHashMap<MessageQueue, LockEntry>>(1024);

核心数据行为

锁定、批量锁定、批量释放主要行为都在源码中添加了注释:


public class RebalanceLockManager {
    private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.REBALANCE_LOCK_LOGGER_NAME);
    // 重平衡锁定最大存活时间,60s
    private final static long REBALANCE_LOCK_MAX_LIVE_TIME = Long.parseLong(System.getProperty(
        "rocketmq.broker.rebalance.lockMaxLiveTime", "60000"));
    // lock
    private final Lock lock = new ReentrantLock();
    // 核心数据结构,Map<consumerGroup, Map<MessageQueue, LockEntry>>,key为消费组,value为MessageQueue和LockEntry的映射
    // 针对每个消费组的每个MessageQueue都有一份锁定信息
    private final ConcurrentMap<String/* group */, ConcurrentHashMap<MessageQueue, LockEntry>> mqLockTable =
        new ConcurrentHashMap<String, ConcurrentHashMap<MessageQueue, LockEntry>>(1024);

    public boolean tryLock(final String group, final MessageQueue mq, final String clientId) {
        // 先检查是否已经被当前客户端锁定,如果不是
        if (!this.isLocked(group, mq, clientId)) {
            try {
                // 加锁
                this.lock.lockInterruptibly();
                try {
                    ConcurrentHashMap<MessageQueue, LockEntry> groupValue = this.mqLockTable.get(group);
                    if (null == groupValue) {
                        groupValue = new ConcurrentHashMap<>(32);
                        this.mqLockTable.put(group, groupValue);
                    }

                    LockEntry lockEntry = groupValue.get(mq);
                    if (null == lockEntry) {
                        lockEntry = new LockEntry();
                        lockEntry.setClientId(clientId);
                        groupValue.put(mq, lockEntry);
                        log.info("tryLock, message queue not locked, I got it. Group: {} NewClientId: {} {}",
                            group,
                            clientId,
                            mq);
                    }
                    // 如果当前客户端已经锁定了,则直接返回
                    // 这里形成了双检锁,两次检查是不是被当前客户端锁定,是的话,跟新最后更新时间,返回true
                    if (lockEntry.isLocked(clientId)) {
                        lockEntry.setLastUpdateTimestamp(System.currentTimeMillis());
                        return true;
                    }

                    String oldClientId = lockEntry.getClientId();

                    // 如果当前客户端锁定时间超过了最大存活时间,则重新锁定
                    if (lockEntry.isExpired()) {
                        lockEntry.setClientId(clientId);
                        lockEntry.setLastUpdateTimestamp(System.currentTimeMillis());
                        log.warn(
                            "tryLock, message queue lock expired, I got it. Group: {} OldClientId: {} NewClientId: {} {}",
                            group,
                            oldClientId,
                            clientId,
                            mq);
                        return true;
                    }
                    // 如果不是当前客户端id,则返回false

                    log.warn(
                        "tryLock, message queue locked by other client. Group: {} OtherClientId: {} NewClientId: {} {}",
                        group,
                        oldClientId,
                        clientId,
                        mq);
                    return false;
                } finally {
                    this.lock.unlock();
                }
            } catch (InterruptedException e) {
                log.error("putMessage exception", e);
            }
        } else {

        }

        return true;
    }

    // 指定group-mq,是否被当前客户端锁定
    private boolean isLocked(final String group, final MessageQueue mq, final String clientId) {
        ConcurrentHashMap<MessageQueue, LockEntry> groupValue = this.mqLockTable.get(group);
        if (groupValue != null) {
            LockEntry lockEntry = groupValue.get(mq);
            if (lockEntry != null) {
                // 锁定状态,已被当前客户端锁定
                boolean locked = lockEntry.isLocked(clientId);
                if (locked) {
                    // 更新锁定的最后更新时间
                    lockEntry.setLastUpdateTimestamp(System.currentTimeMillis());
                }

                return locked;
            }
        }

        return false;
    }

    // 尝试对指定group-mqs批量锁定
    public Set<MessageQueue> tryLockBatch(final String group, final Set<MessageQueue> mqs,
        final String clientId) {
        // 构造批量返回结果
        Set<MessageQueue> lockedMqs = new HashSet<MessageQueue>(mqs.size());
        Set<MessageQueue> notLockedMqs = new HashSet<MessageQueue>(mqs.size());

        // 遍历mqs,进行分类,已当前客户端被锁定和未被锁定
        for (MessageQueue mq : mqs) {
            if (this.isLocked(group, mq, clientId)) {
                lockedMqs.add(mq);
            } else {
                notLockedMqs.add(mq);
            }
        }

        // 未被锁定的列表,才获取全局锁,进行锁定,并更新锁定信息。
        // 这样处理可以提高性能,减少锁定操作。
        if (!notLockedMqs.isEmpty()) {
            try {
                // 获取全局锁
                this.lock.lockInterruptibly();
                try {
                    ConcurrentHashMap<MessageQueue, LockEntry> groupValue = this.mqLockTable.get(group);
                    if (null == groupValue) {
                        groupValue = new ConcurrentHashMap<>(32);
                        this.mqLockTable.put(group, groupValue);
                    }

                    for (MessageQueue mq : notLockedMqs) {
                        LockEntry lockEntry = groupValue.get(mq);
                        // 如果当前队列未被锁定,创建一个lock对象
                        if (null == lockEntry) {
                            lockEntry = new LockEntry();
                            lockEntry.setClientId(clientId);
                            groupValue.put(mq, lockEntry);
                            log.info(
                                "tryLockBatch, message queue not locked, I got it. Group: {} NewClientId: {} {}",
                                group,
                                clientId,
                                mq);
                        }

                        // 如果当前客户端已经锁定了,则直接返回?
                        // 说明有并发的客户端在尝试锁定,且已经成功,则直接跳过
                        if (lockEntry.isLocked(clientId)) {
                            lockEntry.setLastUpdateTimestamp(System.currentTimeMillis());
                            lockedMqs.add(mq);
                            continue;
                        }

                        String oldClientId = lockEntry.getClientId();
                        // 如果被其他客户端锁定,但是已经过期,则重新锁定
                        if (lockEntry.isExpired()) {
                            lockEntry.setClientId(clientId);
                            lockEntry.setLastUpdateTimestamp(System.currentTimeMillis());
                            log.warn(
                                "tryLockBatch, message queue lock expired, I got it. Group: {} OldClientId: {} NewClientId: {} {}",
                                group,
                                oldClientId,
                                clientId,
                                mq);
                            lockedMqs.add(mq);
                            continue;
                        }
                        // 如果被其他客户端持续锁定,则不做任何处理

                        log.warn(
                            "tryLockBatch, message queue locked by other client. Group: {} OtherClientId: {} NewClientId: {} {}",
                            group,
                            oldClientId,
                            clientId,
                            mq);
                    }
                } finally {
                    this.lock.unlock();
                }
            } catch (InterruptedException e) {
                log.error("putMessage exception", e);
            }
        }
        // 返回成功锁定的列表
        return lockedMqs;
    }

    // 批量释放锁定
    public void unlockBatch(final String group, final Set<MessageQueue> mqs, final String clientId) {
        try {
            // 获取全局锁
            this.lock.lockInterruptibly();
            try {
                // 获取group-mqs
                ConcurrentHashMap<MessageQueue, LockEntry> groupValue = this.mqLockTable.get(group);
                if (null != groupValue) {
                    // 遍历mqs
                    for (MessageQueue mq : mqs) {
                        // 获取指定queue的锁定信息
                        LockEntry lockEntry = groupValue.get(mq);
                        if (null != lockEntry) {
                            // 如果是当前客户端锁定,则释放锁定
                            if (lockEntry.getClientId().equals(clientId)) {
                                groupValue.remove(mq);
                                log.info("unlockBatch, Group: {} {} {}",
                                    group,
                                    mq,
                                    clientId);
                            } else {
                                log.warn("unlockBatch, but mq locked by other client: {}, Group: {} {} {}",
                                    lockEntry.getClientId(),
                                    group,
                                    mq,
                                    clientId);
                            }
                        } else {
                            log.warn("unlockBatch, but mq not locked, Group: {} {} {}",
                                group,
                                mq,
                                clientId);
                        }
                    }
                } else {
                    log.warn("unlockBatch, group not exist, Group: {} {}",
                        group,
                        clientId);
                }
            } finally {
                this.lock.unlock();
            }
        } catch (InterruptedException e) {
            log.error("putMessage exception", e);
        }
    }

    static class LockEntry {
        // 客户端id
        private String clientId;
        // 最后更新时间
        private volatile long lastUpdateTimestamp = System.currentTimeMillis();

        public String getClientId() {
            return clientId;
        }

        public void setClientId(String clientId) {
            this.clientId = clientId;
        }

        public long getLastUpdateTimestamp() {
            return lastUpdateTimestamp;
        }

        public void setLastUpdateTimestamp(long lastUpdateTimestamp) {
            this.lastUpdateTimestamp = lastUpdateTimestamp;
        }

        public boolean isLocked(final String clientId) {
            boolean eq = this.clientId.equals(clientId);
            return eq && !this.isExpired();
        }

        public boolean isExpired() {
            boolean expired =
                (System.currentTimeMillis() - this.lastUpdateTimestamp) > REBALANCE_LOCK_MAX_LIVE_TIME;

            return expired;
        }
    }
}

  • 9
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
探索全栈前端技术的魅力:HTML+CSS+JS+JQ+Bootstrap网站源码深度解析 在这个数字化时代,构建一个既美观又功能强大的网站成为了许多开发者和企业追逐的目标。本份资源精心汇集了一套完整网站源码,融合了HTML的骨架搭建、CSS的视觉美化、JavaScript的交互逻辑、jQuery的高效操作以及Bootstrap的响应式设计,全方位揭秘了现代网页开发的精髓。 HTML,作为网页的基础,它构建了信息的框架;CSS则赋予网页生动的外观,让设计创意跃然屏上;JavaScript的加入,使网站拥有了灵动的交互体验;jQuery,作为JavaScript的强力辅助,简化了DOM操作与事件处理,让编码更为高效;而Bootstrap的融入,则确保了网站在不同设备上的完美呈现,响应式设计让访问无界限。 通过这份源码,你将: 学习如何高效组织HTML结构,提升页面加载速度与SEO友好度; 掌握CSS高级技巧,如Flexbox与Grid布局,打造适应各种屏幕的视觉盛宴; 理解JavaScript核心概念,动手实现动画、表单验证等动态效果; 利用jQuery插件快速增强用户体验,实现滑动效果、Ajax请求等; 深入Bootstrap框架,掌握移动优先的开发策略,响应式设计信手拈来。 无论是前端开发新手渴望系统学习,还是资深开发者寻求灵感与实用技巧,这份资源都是不可多得的宝藏。立即深入了解,开启你的全栈前端探索之旅,让每一个网页都成为技术与艺术的完美融合!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值