运维kafka时,会有时需要删除无效或已下线的consumer group,若设置的offset保存位置是zookeepr,则数据都在zookeeper中,可自行删除,方式就是删除zookeeper相应节点。
kafka的offset若想按照生产时间进行恢复,事实严重不准,具体可参见:关于kafka中的timestamp与offset的对应关系
恢复步骤:
1. 获取该consumer group消费topic的各partition该时间的offset
2. 在zookeeper中修改这些offset
进行操作期间需要暂停该group对topic的消费,恢复offset后再重启,否则修改不生效。
上代码!
ResetOffsetOperator.resetOffset(topic, group, null, whichTime);
ZookeeperOperator zookeeperOperator = new ZookeeperOperator();
zookeeperOperator.deleteUselessConsumer("test1");
public class ZookeeperOperator implements Watcher {
private ZooKeeper zooKeeper = null;
private static final Logger log = LoggerFactory
.getLogger(ZookeeperOperator.class);
public ZookeeperOperator() throws IOException {
this(KafkaConf.ZOOKEEPERHOST);
}
public ZookeeperOperator(String zookeeperHost) throws IOException {
zooKeeper = new ZooKeeper(zookeeperHost, 5000, ZookeeperOperator.this);
}
public void close() {
try {
zooKeeper.close();
} catch (InterruptedException e) {
log.error("Failed to close zookeeper:", e);
}
}
/**
* 读取指定节点下孩子节点数目
*
* @param path 节点path
* @return
*/
private int readChildren(String path) {
try {
return zooKeeper.getChildren(path, false, null).size();
} catch (KeeperException e) {
log.error("Read error,KeeperException:" + path, e);
return 0;
} catch (InterruptedException e) {
log.error("Read error,InterruptedException:" + path, e);
return 0;
}
}
private List getChildrenList(String path) {
try {
return zooKeeper.getChildren(path, false, null);
} catch (KeeperException e) {
log.error("Read error,KeeperException:" + path, e);
return null;
} catch (InterruptedException e) {
log.error("Read error,InterruptedException:" + path, e);
return null;
}
}
private boolean setData(String path, String data) {
try {
zooKeeper.setData(path, data.getBytes(), -1);
} catch (KeeperException e) {
log.error("Set error,KeeperException:" + path + " data:" + data, e);
return false;
} catch (InterruptedException e) {
log.error("Set error,InterruptedException:" + path + " data:" + data, e);
return false;
}
return true;
}
private boolean deleteData(String path) {
try {
zooKeeper.delete(path, -1);
} catch (InterruptedException e) {
log.error("delete error,InterruptedException:" + path, e);
return false;
} catch (KeeperException e) {
log.error("delete error,KeeperException:" + path, e);
return false;
}
return true;
}
private boolean recursivelyDeleteData(String path) {
List childList = getChildrenList(path);
if (childList == null) {
return false;
} else if (childList.isEmpty()) {
deleteData(path);
} else {
for (String childName : childList) {
String childPath = path + "/" + childName;
List grandChildList = getChildrenList(childPath);
if (grandChildList == null) {
return false;
} else if (grandChildList.isEmpty()) {
deleteData(childPath);
} else {
recursivelyDeleteData(childPath);
}
}
deleteData(path);
}
return true;
}
private String getData(String path) {
try {
return new String(zooKeeper.getData(path, false, null));
} catch (KeeperException e) {
log.error("Read error,KeeperException:" + path, e);
return "";
} catch (InterruptedException e) {
log.error("Read error,InterruptedException:" + path, e);
return "";
}
}
/**
* 读取指定节点下孩子节点数目
*
* @param topic kafka topic 名称
* @return
*/
public int readTopicChildren(String topic) {
StringBuilder sb = new StringBuilder().append("/brokers/topics/")
.append(topic).append("/partitions");
return readChildren(sb.toString());
}
public boolean setTopicGroupOffset(String topic, String group,
String partition, String data) {
StringBuilder sb = new StringBuilder().append("/consumers/").append(group)
.append("/offsets/").append(topic).append("/").append(partition);
return setData(sb.toString(), data);
}
public String getTopicGroupOffset(String topic, String group,
String partition) {
StringBuilder sb = new StringBuilder().append("/consumers/").append(group)
.append("/offsets/").append(topic).append("/").append(partition);
System.out.println(sb.toString());
return getData(sb.toString());
}
public boolean deleteUselessConsumer(String topic, String group) {
if (topic.endsWith("-1")) {
StringBuilder sb = new StringBuilder().append("/consumers/")
.append(group);
return recursivelyDeleteData(sb.toString());
} else {
StringBuilder sb = new StringBuilder().append("/consumers/").append(group)
.append("/offsets/").append(topic);
return recursivelyDeleteData(sb.toString());
}
}
public boolean deleteUselessLikeConsumer(String topic, String group) {
String path = "/consumers";
List childList = getChildrenList(path);
int success = 0;
int count = 0;
for (String child : childList) {
if (child.startsWith(group)) {
count++;
if (deleteUselessConsumer(topic, child)) {
success++;
}
}
}
if (success == count) {
return true;
} else {
return false;
}
}
public boolean deleteUselessLikeConsumer(String group) {
return deleteUselessLikeConsumer("-1", group);
}
public boolean deleteUselessConsumer(String group) {
return deleteUselessConsumer("-1", group);
}
@Override
public void process(WatchedEvent event) {
log.info("Receive Event:" + event.getState());
}
}
public class ResetOffsetOperator {
public static boolean resetOffset(final String topic, String group,
Properties properties, long whichTime) throws IOException {
if (StringUtils.isBlank(topic) || StringUtils.isBlank(group)) {
System.err.println("topic or group can not be null or empty!");
System.exit(2);
}
if (properties == null) {
properties = new Properties();
properties.setProperty("zookeeper.connect", KafkaConf.ZOOKEEPERHOST);
properties.setProperty("group.id", group);
// zookeeper最大超时时间,就是心跳的间隔,若是没有反映,那么认为已经死了,不易过大
properties.setProperty("zookeeper.session.timeout.ms", "10000");
// ZooKeeper集群中leader和follower之间的同步时间
properties.setProperty("zookeeper.sync.time.ms", "2000");
// 自动提交offset到zookeeper的时间间隔
properties.setProperty("auto.commit.interval.ms", "3000");
// 当zookeeper中没有初始的offset时候的处理方式 。smallest :重置为最小值 largest:重置为最大值 anything else:抛出异常
properties.setProperty("auto.offset.reset", "largest");
}
ZookeeperOperator zookeeper = new ZookeeperOperator(
properties.getProperty("zookeeper.connect"));
GroupOperator groupOperator = new GroupOperator(topic, group, whichTime,
zookeeper);
return groupOperator.retryResetGroupOffset();
}
}
public class GroupOperator {
private static final Logger LOG = LoggerFactory
.getLogger(GroupOperator.class);
private List m_replicaBrokers;
private ZookeeperOperator zookeeper;
private String topic;
private List seeds;
private int port;
private long whichTime;
private int partitionNum;
private String group;
private int retryNum = 5;
/**
* 初始化
*
* @param topic
* @param whichTime timestamp(13位)/-1(latest)/-2(earliest)
* @throws java.io.IOException
*/
public GroupOperator(String topic, String group, long whichTime, ZookeeperOperator zookeeper)
throws IOException {
this.topic = topic;
this.group = group;
this.whichTime = whichTime;
m_replicaBrokers = new ArrayList();
seeds = KafkaConf.getBrokerHost();
port = Integer.parseInt(KafkaConf.BROKERPORT);
this.zookeeper = zookeeper;
partitionNum = zookeeper.readTopicChildren(topic);
}
/**
* 将zookeeper中该group对应该topic下的所有分区的offset恢复为所希望的时间
*/
public boolean resetGroupOffset() {
List offsetList = new ArrayList();
for (int partition = 0; partition < partitionNum; partition++) {
long offset = getOffset(partition);
if (offset == -1) {
LOG.error("Failed to get offset of " + group + " with partition:"
+ partition);
return false;
} else {
offsetList.add(offset);
}
}
for (int partition = 0; partition < partitionNum; partition++) {
boolean isSuccess = zookeeper
.setTopicGroupOffset(topic, group, String.valueOf(partition),
String.valueOf(offsetList.get(partition)));
if (!isSuccess) {
LOG.error("Failed to reset offset of topic:" + topic + " group:" + group
+ " partition" + partition + " value:" + offsetList.get(partition));
return false;
}
}
return true;
}
public boolean retryResetGroupOffset() {
for (int retry = 0; retry < retryNum; retry++) {
if (resetGroupOffset()) {
return true;
}
}
return false;
}
/**
* 获取该partition要恢复时间的offset
*
* @param partition
* @return
*/
public long getOffset(int partition) {
// find the meta data about the topic and partition we are interested in
PartitionMetadata metadata = findLeader(partition);
if (metadata == null) {
LOG.error("Can't find metadata for Topic and Partition. Exiting");
return -1;
}
if (metadata.leader() == null) {
LOG.error("Can't find Leader for Topic and Partition. Exiting");
return -1;
}
String leadBroker = metadata.leader().host();
String clientName = "Client_" + topic + "_" + partition;
SimpleConsumer consumer = new SimpleConsumer(leadBroker, port, 100000,
64 * 1024, clientName);
long readOffset = getAssignedOffset(consumer, partition, clientName);
if (consumer != null) {
consumer.close();
}
return readOffset;
}
/**
* Finding the Lead Broker for a Topic and Partition
*
* @param a_partition 分区id,从0开始
* @return
*/
private PartitionMetadata findLeader(int a_partition) {
PartitionMetadata returnMetaData = null;
loop:
for (String seed : seeds) {
SimpleConsumer consumer = null;
try {
consumer = new SimpleConsumer(seed, port, 100000, 64 * 1024,
"leaderLookup");
List topics = Collections.singletonList(topic);
TopicMetadataRequest req = new TopicMetadataRequest(topics);
kafka.javaapi.TopicMetadataResponse resp = consumer.send(req);
List metaData = resp.topicsMetadata();
for (TopicMetadata item : metaData) {
for (PartitionMetadata part : item.partitionsMetadata()) {
if (part.partitionId() == a_partition) {
returnMetaData = part;
break loop;
}
}
}
} catch (Exception e) {
LOG.error("Error communicating with Broker [" + seed
+ "] to find Leader for [" + topic + ", " + a_partition
+ "] Reason: " + e);
} finally {
if (consumer != null)
consumer.close();
}
}
if (returnMetaData != null) {
m_replicaBrokers.clear();
for (kafka.cluster.Broker replica : returnMetaData.replicas()) {
m_replicaBrokers.add(replica.host());
}
}
return returnMetaData;
}
public long getAssignedOffset(SimpleConsumer consumer, int partition,
String clientName) {
TopicAndPartition topicAndPartition = new TopicAndPartition(topic,
partition);
Map requestInfo = new HashMap();
requestInfo
.put(topicAndPartition, new PartitionOffsetRequestInfo(whichTime, 1));
kafka.javaapi.OffsetRequest request = new kafka.javaapi.OffsetRequest(
requestInfo, kafka.api.OffsetRequest.CurrentVersion(), clientName);
OffsetResponse response = consumer.getOffsetsBefore(request);
if (response.hasError()) {
System.out.println(
"Error fetching data Offset Data the Broker. Reason: " + response
.errorCode(topic, partition));
return -1;
}
long[] offsets = response.offsets(topic, partition);
return offsets[0];
}
}