SubscriptionState分析

KafkaConsumer从Kafka拉取消息的时候发送的请求时FetchRequest,在其中需要指定消费者希望拉取的起始消息的offset。为了消费者快速获取这个值,KafkaConsumer使用SubscriptionState来追踪TopicPartition与offset的关系:


一 SubscriptionType

NONE: 没有状态

AUTO_TOPICS:按照指定的topic 名字进行订阅,自动分配分区

AUTO_PATTERN:按照正则表达式匹配topic名字进行订阅,自动分配分区

USER_ASSIGNED:用户自己指定订阅的topic以及分区号

 

二TopicPartitionState

表示TopicPartition的消费状态,有以下几个字段:

position: 下一次要从kafka服务器端获取消息的offset

committed: 最近一次提交的offset

paused: 当前TopicPartition是否暂停消费

resetStrategy:重置position策略,该字段是否为空也表示是否需要重置position

 

三 SubscriptionState

3.1 比较核心的字段:

SubscriptionType:订阅类型

Pattern subscribedPattern:使用AUTO_PATTERN订阅模式,按照该字段的正则表达式进行topic匹配

Set<String> subscription: 针对自动分配分区用户请求的topic列表,我们可以通过changeSubscription方法添加topic

Set<TopicPartition> userAssignment:针对USER_ASSIGNED订阅模式,该集合包含分配给当前消费者的TopicPartition集合

PartitionStates<TopicPartitionState>assignment: 维护了一个<Topic

Partition->TopicPartitionState>的映射,而且还可以让这个map的元素可以旋转

Set<String> groupSubscription: 我们知道消费者组会有一个leader,它会使用该集合记录消费者组中所有消费者的订阅的topic,

而其他follower的该集合只保存自己的订阅的topic

boolean needsFetchCommittedOffsets: 我们需要从coordinator获取最近的提交的offset吗?

OffsetResetStrategy defaultResetStrategy: 默认的offset重设策略

ConsumerRebalanceListener listener: 用于监听分配分区操作

 

3.2 核心的方法

/**
 *
设置AUTO_TOPICS订阅类型,初始化监听器,检测topic是否发生变化,如果发生变化重置
 * @param
topics
 
* @param listener
 
*/
public void subscribe(Set<String> topics, ConsumerRebalanceListener listener) {
    if (listener == null)
        throw new IllegalArgumentException("RebalanceListenercannot be null");
    // 设置订阅类型,是自动分配还是用户分配
   
setSubscriptionType(SubscriptionType.AUTO_TOPICS);
    // 初始化消费者再平衡监听器
   
this.listener = listener;
    // 当订阅的topic集合发生变化时,重置订阅的主题集合,并且把所有的topic添加到groupSubscription
   
changeSubscription(topics);
}

 

/**
 * 设置AUTO_PATTERN订阅类型,检测topic是否发生变化,如果发生变化重置
 * @param topics
 */
public void subscribeFromPattern(Set<String> topics) {
    if (subscriptionType != SubscriptionType.AUTO_PATTERN)
        throw new IllegalArgumentException("Attempt to subscribe from pattern while subscription type set to " +
                subscriptionType);
    // 当订阅的topic集合发生变化时,重置订阅的主题集合,并且把所有的topic添加到groupSubscription
    changeSubscription(topics);
}

/**
 * 当订阅的topic集合发生变化时,重置订阅的主题集合,并且把所有的topic添加到groupSubscription
 * @param topicsToSubscribe
 */
private void changeSubscription(Set<String> topicsToSubscribe) {
    if (!this.subscription.equals(topicsToSubscribe)) {
        this.subscription = topicsToSubscribe;
        // 消费者自身订阅的topic添加到groupSubscription
        this.groupSubscription.addAll(topicsToSubscribe);
    }
}
 
 
/**
 * leader收到JoinGroupResponse的时候,包含了全部消费者的topic,此时将topic添加到groupSubscription
 * @param topics
 */
public void groupSubscribe(Collection<String> topics) {
    if (this.subscriptionType == SubscriptionType.USER_ASSIGNED)
        throw new IllegalStateException(SUBSCRIPTION_EXCEPTION_MESSAGE);
    this.groupSubscription.addAll(topics);
}

/**
 * 重置groupSubscription的订阅,只包含该用户订阅的主题。
 */
public void resetGroupSubscription() {
    this.groupSubscription.retainAll(subscription);
}

 

/**获取分配的分区的状态,然后看哪些分区是可以fetch数据,返回那些可以fetch数据的topic partition*/
public List<TopicPartition> fetchablePartitions() {
    List<TopicPartition> fetchable = new ArrayList<>();
    for (PartitionStates.PartitionState<TopicPartitionState> state : assignment.partitionStates()) {
        if (state.value().isFetchable())
            fetchable.add(state.topicPartition());
    }
    return fetchable;
}

 

 

// 取消订阅
public void unsubscribe() {
    this.subscription = Collections.emptySet();
    this.userAssignment = Collections.emptySet();
    this.assignment.clear();
    this.subscribedPattern = null;
    this.subscriptionType = SubscriptionType.NONE;
}

 

/**根据coordinator返回的partitions改变这assignment */
public void assignFromSubscribed(Collection<TopicPartition> assignments) {
    if (!this.partitionsAutoAssigned())
        throw new IllegalArgumentException("Attempt to dynamically assign partitions while manual assignment in use");

    for (TopicPartition tp : assignments)
        if (!this.subscription.contains(tp.topic()))
            throw new IllegalArgumentException("Assigned partition " + tp + " for non-subscribed topic.");

    // after rebalancing, we always reinitialize the assignment value
    this.assignment.set(partitionToStateMap(assignments));
    this.needsFetchCommittedOffsets = true;
}
/**根据指定的partitions集合,构建一个<partition,TopicPartitionState>映射*/
private Map<TopicPartition, TopicPartitionState> partitionToStateMap(Collection<TopicPartition> assignments) {
    Map<TopicPartition, TopicPartitionState> map = new HashMap<>(assignments.size());
    for (TopicPartition tp : assignments)
        map.put(tp, new TopicPartitionState());
    return map;
}

 

/** 根据用户提供的指定的partitions 改变assignment */
public void assignFromUser(Set<TopicPartition> partitions) {
    setSubscriptionType(SubscriptionType.USER_ASSIGNED);

    if (!this.assignment.partitionSet().equals(partitions)) {
        this.userAssignment = partitions;

        Map<TopicPartition, TopicPartitionState> partitionToState = new HashMap<>();
        for (TopicPartition partition : partitions) {
            TopicPartitionState state = assignment.stateValue(partition);
            if (state == null)
                state = new TopicPartitionState();
            partitionToState.put(partition, state);
        }
        this.assignment.set(partitionToState);
        this.needsFetchCommittedOffsets = true;
    }
}

 

// 判断下一次消费的消息的offset是否为空
public boolean hasValidPosition() {
    return position != null;
}

 

// 判断下一次消费的位置是否为空
public boolean hasAllFetchPositions() {
    for (TopicPartitionState state : assignment.partitionStateValues())
        // 下一次消费的位置为空,返回false
        if (!state.hasValidPosition())
            return false;
    return true;
}

// 如果下一次消费位置没有,则返回那些TopicPartition集合
public Set<TopicPartition> missingFetchPositions() {
    Set<TopicPartition> missing = new HashSet<>();
    for (PartitionStates.PartitionState<TopicPartitionState> state : assignment.partitionStates()) {
        if (!state.value().hasValidPosition())
            missing.add(state.topicPartition());
    }
    return missing;
}

 

// 判断该分区是否分配了,而且是否可以fetch数据了
public boolean isFetchable(TopicPartition tp) {
    return isAssigned(tp) && assignedState(tp).isFetchable();
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
要在C#中进行读写松下PLC,你可以使用OPC(OLE for Process Control)来实现。OPC是用于在工业自动化系统中进行数据交换的标准化接口。你需要安装一个OPC服务器来连接到PLC,并使用OPC客户端库来在C#中进行读写操作。 以下是一个示例代码,展示了如何使用OPC客户端库与松下PLC进行通信: ```csharp using Opc.Da; using System; class OpcExample { static void Main(string[] args) { // 创建一个新的OPC服务器对象 Opc.Da.Server opcServer = new Opc.Da.Server(new OpcCom.Factory(), null); try { // 连接到OPC服务器 opcServer.Connect(new Opc.URL("opcda://localhost/PLC")); // 创建一个读取项集合 Item[] items = new Item[2]; items[0] = new Item() { ItemName = "Channel1.Device1.Tag1", ClientHandle = 1 }; items[1] = new Item() { ItemName = "Channel1.Device1.Tag2", ClientHandle = 2 }; // 读取数据 SubscriptionState state = new SubscriptionState(); state.UpdateRate = 1000; // 更新频率为1秒 Subscription subscription = (Subscription)opcServer.CreateSubscription(state); subscription.AddItems(items); while (true) { ItemValueResult[] values = subscription.Read(subscription.Items); foreach (ItemValueResult value in values) { Console.WriteLine("Item: {0}, Value: {1}", value.ItemName, value.Value); } System.Threading.Thread.Sleep(1000); // 等待1秒钟 } } catch (Exception ex) { Console.WriteLine("Error: " + ex.Message); } finally { // 断开与OPC服务器的连接 if (opcServer != null && opcServer.IsConnected) opcServer.Disconnect(); } Console.ReadLine(); } } ``` 请确保你已安装了适当的OPC服务器,并将代码中的 `localhost/PLC` 替换为你实际的PLC地址。你还需要引用 `OpcNetApi.dll` 和 `OpcNetApi.Com.dll` 这两个OPC客户端库。 这只是一个基本的示例,你可以根据你的具体需求进行扩展和修改。 希望对你有所帮助!如果还有其他问题,请随时提问。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

莫言静好、

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值