Topic消息查询支持Timestamp排序,接口支持按指定日期查询

This commit is contained in:
superspeedone
2022-09-09 15:45:31 +08:00
parent 8e50d145d5
commit 0e49002f42
6 changed files with 50 additions and 21 deletions

View File

@@ -16,7 +16,7 @@ import java.util.List;
public interface TopicStateManager { public interface TopicStateManager {
TopicBrokerAllVO getTopicBrokerAll(Long clusterPhyId, String topicName, String searchBrokerHost) throws NotExistException; TopicBrokerAllVO getTopicBrokerAll(Long clusterPhyId, String topicName, String searchBrokerHost) throws NotExistException;
Result<List<TopicRecordVO>> getTopicMessages(Long clusterPhyId, String topicName, TopicRecordDTO dto, PaginationSortDTO sortDto) throws AdminOperateException; Result<List<TopicRecordVO>> getTopicMessages(Long clusterPhyId, String topicName, TopicRecordDTO dto) throws AdminOperateException;
Result<TopicStateVO> getTopicState(Long clusterPhyId, String topicName); Result<TopicStateVO> getTopicState(Long clusterPhyId, String topicName);

View File

@@ -3,7 +3,6 @@ package com.xiaojukeji.know.streaming.km.biz.topic.impl;
import com.didiglobal.logi.log.ILog; import com.didiglobal.logi.log.ILog;
import com.didiglobal.logi.log.LogFactory; import com.didiglobal.logi.log.LogFactory;
import com.xiaojukeji.know.streaming.km.biz.topic.TopicStateManager; import com.xiaojukeji.know.streaming.km.biz.topic.TopicStateManager;
import com.xiaojukeji.know.streaming.km.common.bean.dto.pagination.PaginationSortDTO;
import com.xiaojukeji.know.streaming.km.common.bean.dto.topic.TopicRecordDTO; import com.xiaojukeji.know.streaming.km.common.bean.dto.topic.TopicRecordDTO;
import com.xiaojukeji.know.streaming.km.common.bean.entity.broker.Broker; import com.xiaojukeji.know.streaming.km.common.bean.entity.broker.Broker;
import com.xiaojukeji.know.streaming.km.common.bean.entity.cluster.ClusterPhy; import com.xiaojukeji.know.streaming.km.common.bean.entity.cluster.ClusterPhy;
@@ -40,10 +39,7 @@ import com.xiaojukeji.know.streaming.km.core.service.topic.TopicService;
import com.xiaojukeji.know.streaming.km.core.service.version.metrics.TopicMetricVersionItems; import com.xiaojukeji.know.streaming.km.core.service.version.metrics.TopicMetricVersionItems;
import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.ObjectUtils;
import org.apache.kafka.clients.admin.OffsetSpec; import org.apache.kafka.clients.admin.OffsetSpec;
import org.apache.kafka.clients.consumer.ConsumerConfig; import org.apache.kafka.clients.consumer.*;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import org.apache.kafka.common.TopicPartition; import org.apache.kafka.common.TopicPartition;
import org.apache.kafka.common.config.TopicConfig; import org.apache.kafka.common.config.TopicConfig;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@@ -123,7 +119,7 @@ public class TopicStateManagerImpl implements TopicStateManager {
} }
@Override @Override
public Result<List<TopicRecordVO>> getTopicMessages(Long clusterPhyId, String topicName, TopicRecordDTO dto, PaginationSortDTO sortDto) throws AdminOperateException { public Result<List<TopicRecordVO>> getTopicMessages(Long clusterPhyId, String topicName, TopicRecordDTO dto) throws AdminOperateException {
long startTime = System.currentTimeMillis(); long startTime = System.currentTimeMillis();
// 获取集群 // 获取集群
@@ -163,10 +159,29 @@ public class TopicStateManagerImpl implements TopicStateManager {
} }
maxMessage = Math.min(maxMessage, dto.getMaxRecords()); maxMessage = Math.min(maxMessage, dto.getMaxRecords());
kafkaConsumer.assign(partitionList); kafkaConsumer.assign(partitionList);
Map<TopicPartition, OffsetAndTimestamp> partitionOffsetAndTimestampMap = new HashMap<>();
// 获取指定时间每个分区的offset按指定开始时间查询消息时
if (GroupOffsetResetEnum.PRECISE_TIMESTAMP.getResetType() == dto.getFilterOffsetReset()) {
Map<TopicPartition, Long> timestampsToSearch = new HashMap<>();
partitionList.forEach(topicPartition -> {
timestampsToSearch.put(topicPartition, dto.getStartTimestampUnitMs());
});
partitionOffsetAndTimestampMap = kafkaConsumer.offsetsForTimes(timestampsToSearch);
}
for (TopicPartition partition : partitionList) { for (TopicPartition partition : partitionList) {
if (GroupOffsetResetEnum.EARLIEST.getResetType() == dto.getFilterOffsetReset()) { if (GroupOffsetResetEnum.EARLIEST.getResetType() == dto.getFilterOffsetReset()) {
// 重置到最旧
kafkaConsumer.seek(partition, beginOffsetsMapResult.getData().get(partition)); kafkaConsumer.seek(partition, beginOffsetsMapResult.getData().get(partition));
} else if (GroupOffsetResetEnum.PRECISE_TIMESTAMP.getResetType() == dto.getFilterOffsetReset()) {
// 重置到指定时间
kafkaConsumer.seek(partition, partitionOffsetAndTimestampMap.get(partition).offset());
} else if (GroupOffsetResetEnum.PRECISE_OFFSET.getResetType() == dto.getFilterOffsetReset()) {
// 重置到指定位置
} else { } else {
// 默认,重置到最新
kafkaConsumer.seek(partition, Math.max(beginOffsetsMapResult.getData().get(partition), endOffsetsMapResult.getData().get(partition) - dto.getMaxRecords())); kafkaConsumer.seek(partition, Math.max(beginOffsetsMapResult.getData().get(partition), endOffsetsMapResult.getData().get(partition) - dto.getMaxRecords()));
} }
} }
@@ -194,7 +209,7 @@ public class TopicStateManagerImpl implements TopicStateManager {
// 排序 // 排序
if (ObjectUtils.isNotEmpty(voList)) { if (ObjectUtils.isNotEmpty(voList)) {
PaginationUtil.pageBySort(voList, sortDto.getSortField(), sortDto.getSortField()); PaginationUtil.pageBySort(voList, dto.getSortField(), dto.getSortType());
} }
return Result.buildSuc(voList.subList(0, Math.min(dto.getMaxRecords(), voList.size()))); return Result.buildSuc(voList.subList(0, Math.min(dto.getMaxRecords(), voList.size())));

View File

@@ -2,6 +2,7 @@ package com.xiaojukeji.know.streaming.km.common.bean.dto.topic;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.xiaojukeji.know.streaming.km.common.bean.dto.BaseDTO; import com.xiaojukeji.know.streaming.km.common.bean.dto.BaseDTO;
import com.xiaojukeji.know.streaming.km.common.bean.dto.pagination.PaginationSortDTO;
import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiModelProperty;
import lombok.Data; import lombok.Data;
@@ -15,7 +16,7 @@ import javax.validation.constraints.NotNull;
@Data @Data
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
@ApiModel(description = "Topic记录") @ApiModel(description = "Topic记录")
public class TopicRecordDTO extends BaseDTO { public class TopicRecordDTO extends PaginationSortDTO {
@NotNull(message = "truncate不允许为空") @NotNull(message = "truncate不允许为空")
@ApiModelProperty(value = "是否截断", example = "true") @ApiModelProperty(value = "是否截断", example = "true")
private Boolean truncate; private Boolean truncate;
@@ -38,4 +39,10 @@ public class TopicRecordDTO extends BaseDTO {
@ApiModelProperty(value = "offset", example = "") @ApiModelProperty(value = "offset", example = "")
private Integer filterOffsetReset = 0; private Integer filterOffsetReset = 0;
@ApiModelProperty(value = "开始日期时间戳", example = "")
private Long startTimestampUnitMs;
@ApiModelProperty(value = "结束日期时间戳", example = "")
private Long utilTimestampUnitMs;
} }

View File

@@ -32,8 +32,8 @@ const TopicMessages = (props: any) => {
// 获取消息开始位置 // 获取消息开始位置
const offsetResetList = [ const offsetResetList = [
{ 'label': 'latest', value: '0' }, { 'label': 'latest', value: 0 },
{ 'label': 'earliest', value: '1' } { 'label': 'earliest', value: 1 }
]; ];
// 默认排序 // 默认排序
@@ -42,6 +42,8 @@ const TopicMessages = (props: any) => {
sortType: 'desc', sortType: 'desc',
}; };
const [sorter, setSorter] = useState<any>(defaultSorter);
// 请求接口获取数据 // 请求接口获取数据
const genData = async () => { const genData = async () => {
if (urlParams?.clusterId === undefined || hashData?.topicName === undefined) return; if (urlParams?.clusterId === undefined || hashData?.topicName === undefined) return;
@@ -56,7 +58,7 @@ const TopicMessages = (props: any) => {
}); });
setPartitionIdList(newPartitionIdList || []); setPartitionIdList(newPartitionIdList || []);
}); });
request(Api.getTopicMessagesList(hashData?.topicName, urlParams?.clusterId), { data: { ...params, ...defaultSorter }, method: 'POST' }) request(Api.getTopicMessagesList(hashData?.topicName, urlParams?.clusterId), { data: { ...params, ...sorter }, method: 'POST' })
.then((res: any) => { .then((res: any) => {
// setPagination({ // setPagination({
// current: res.pagination?.pageNo, // current: res.pagination?.pageNo,
@@ -94,11 +96,16 @@ const TopicMessages = (props: any) => {
history.push(`/cluster/${urlParams?.clusterId}/testing/consumer`); history.push(`/cluster/${urlParams?.clusterId}/testing/consumer`);
}; };
const onTableChange = (pagination: any, filters: any, sorter: any) => { const onTableChange = (pagination: any, filters: any, sorter: any, extra: any) => {
defaultSorter.sortField = sorter.field || '';
defaultSorter.sortType = sorter.order ? sorter.order.substring(0, sorter.order.indexOf('end')) : '';
setPagination(pagination); setPagination(pagination);
genData(); // 只有排序事件时,触发重新请求后端数据
if(extra.action === 'sort') {
setSorter({
sortField: sorter.field || '',
sortType: sorter.order ? sorter.order.substring(0, sorter.order.indexOf('end')) : ''
});
genData();
}
// const asc = sorter?.order && sorter?.order === 'ascend' ? true : false; // const asc = sorter?.order && sorter?.order === 'ascend' ? true : false;
// const sortColumn = sorter.field && toLine(sorter.field); // const sortColumn = sorter.field && toLine(sorter.field);
// genData({ pageNo: pagination.current, pageSize: pagination.pageSize, filters, asc, sortColumn, queryTerm: searchResult, ...allParams }); // genData({ pageNo: pagination.current, pageSize: pagination.pageSize, filters, asc, sortColumn, queryTerm: searchResult, ...allParams });
@@ -106,7 +113,7 @@ const TopicMessages = (props: any) => {
useEffect(() => { useEffect(() => {
props.positionType === 'Messages' && genData(); props.positionType === 'Messages' && genData();
}, [props, params]); }, [props, params, sorter]);
return ( return (
<> <>

View File

@@ -85,7 +85,8 @@ export const getTopicMessagesColmns = () => {
title: 'Timestamp', title: 'Timestamp',
dataIndex: 'timestampUnitMs', dataIndex: 'timestampUnitMs',
key: 'timestampUnitMs', key: 'timestampUnitMs',
render: (t: number) => (t ? moment(t).format(timeFormat) : '-'), sorter: true,
render: (t: number) => (t ? moment(t).format(timeFormat) + '.' + moment(t).millisecond() : '-'),
}, },
{ {
title: 'Key', title: 'Key',

View File

@@ -92,9 +92,8 @@ public class TopicStateController {
@ResponseBody @ResponseBody
public Result<List<TopicRecordVO>> getTopicMessages(@PathVariable Long clusterPhyId, public Result<List<TopicRecordVO>> getTopicMessages(@PathVariable Long clusterPhyId,
@PathVariable String topicName, @PathVariable String topicName,
@Validated @RequestBody TopicRecordDTO dto, @Validated @RequestBody TopicRecordDTO dto) throws Exception {
@Validated PaginationSortDTO sortDto) throws Exception { return topicStateManager.getTopicMessages(clusterPhyId, topicName, dto);
return topicStateManager.getTopicMessages(clusterPhyId, topicName, dto, sortDto);
} }
@ApiOperation(value = "Topic-ACL信息", notes = "") @ApiOperation(value = "Topic-ACL信息", notes = "")