v2.8.0_e初始化

1、测试代码,开源用户尽量不要使用;
2、包含Kafka-HA的相关功能;
3、并非基于2.6.0拉的分支,是基于master分支的 commit-id: 462303fca0 拉的2.8.0_e的分支。出现这个情况的原因是v2.6.0的代码并不是最新的,2.x最新的代码是 462303fca0 这个commit对应的代码;
This commit is contained in:
zengqiao
2023-02-13 16:35:43 +08:00
parent 462303fca0
commit e81c0f3040
178 changed files with 9938 additions and 1674 deletions

View File

@@ -0,0 +1,32 @@
package com.xiaojukeji.kafka.manager.service.biz.ha;
import com.xiaojukeji.kafka.manager.common.entity.Result;
import com.xiaojukeji.kafka.manager.common.entity.pojo.ha.HaASRelationDO;
import com.xiaojukeji.kafka.manager.common.entity.vo.ha.HaClusterTopicVO;
import com.xiaojukeji.kafka.manager.common.entity.vo.normal.topic.HaClusterTopicHaStatusVO;
import java.util.List;
public interface HaASRelationManager {
/**
* 获取集群主备信息
*/
List<HaClusterTopicVO> getHATopics(Long firstClusterPhyId, Long secondClusterPhyId, boolean filterSystemTopics);
/**
* 获取集群Topic的主备状态信息
*/
Result<List<HaClusterTopicHaStatusVO>> listHaStatusTopics(Long clusterPhyId, Boolean checkMetadata);
/**
* 获取获取集群topic高可用关系 0备topic, 1:主topic, -1非高可用
*/
Integer getRelation(Long clusterId, String topicName);
/**
* 获取获取集群topic高可用关系
*/
HaASRelationDO getASRelation(Long clusterId, String topicName);
}

View File

@@ -0,0 +1,16 @@
package com.xiaojukeji.kafka.manager.service.biz.ha;
import com.xiaojukeji.kafka.manager.common.entity.Result;
import com.xiaojukeji.kafka.manager.common.entity.vo.rd.app.AppRelateTopicsVO;
import java.util.List;
/**
* Ha App管理
*/
public interface HaAppManager {
Result<List<AppRelateTopicsVO>> appRelateTopics(Long clusterPhyId, List<String> filterTopicNameList);
boolean isContainAllRelateAppTopics(Long clusterPhyId, List<String> filterTopicNameList);
}

View File

@@ -0,0 +1,19 @@
package com.xiaojukeji.kafka.manager.service.biz.ha;
import com.xiaojukeji.kafka.manager.common.entity.Result;
import com.xiaojukeji.kafka.manager.common.entity.ao.ClusterDetailDTO;
import com.xiaojukeji.kafka.manager.common.entity.pojo.ClusterDO;
import java.util.List;
/**
* Ha Cluster管理
*/
public interface HaClusterManager {
List<ClusterDetailDTO> getClusterDetailDTOList(Boolean needDetail);
Result<Void> addNew(ClusterDO clusterDO, Long activeClusterId, String operator);
Result<Void> deleteById(Long clusterId, String operator);
}

View File

@@ -0,0 +1,44 @@
package com.xiaojukeji.kafka.manager.service.biz.ha;
import com.xiaojukeji.kafka.manager.common.entity.Result;
import com.xiaojukeji.kafka.manager.common.entity.TopicOperationResult;
import com.xiaojukeji.kafka.manager.common.entity.ao.ha.HaSwitchTopic;
import com.xiaojukeji.kafka.manager.common.entity.dto.op.topic.HaTopicRelationDTO;
import com.xiaojukeji.kafka.manager.common.entity.pojo.ha.JobLogDO;
import java.util.List;
/**
* Ha Topic管理
*/
public interface HaTopicManager {
/**
* 批量更改主备关系
*/
Result<List<TopicOperationResult>> batchCreateHaTopic(HaTopicRelationDTO dto, String operator);
/**
* 批量更改主备关系
*/
Result<List<TopicOperationResult>> batchRemoveHaTopic(HaTopicRelationDTO dto, String operator);
/**
* 可重试的执行主备切换
* @param newActiveClusterPhyId 主集群
* @param newStandbyClusterPhyId 备集群
* @param switchTopicNameList 切换的Topic列表
* @param focus 强制切换
* @param firstTriggerExecute 第一次触发执行
* @param switchLogTemplate 切换日志模版
* @param operator 操作人
* @return 操作结果
*/
Result<HaSwitchTopic> switchHaWithCanRetry(Long newActiveClusterPhyId,
Long newStandbyClusterPhyId,
List<String> switchTopicNameList,
boolean focus,
boolean firstTriggerExecute,
JobLogDO switchLogTemplate,
String operator);
}

View File

@@ -0,0 +1,140 @@
package com.xiaojukeji.kafka.manager.service.biz.ha.impl;
import com.xiaojukeji.kafka.manager.common.bizenum.TopicAuthorityEnum;
import com.xiaojukeji.kafka.manager.common.bizenum.ha.HaRelationTypeEnum;
import com.xiaojukeji.kafka.manager.common.bizenum.ha.HaResTypeEnum;
import com.xiaojukeji.kafka.manager.common.constant.KafkaConstant;
import com.xiaojukeji.kafka.manager.common.entity.Result;
import com.xiaojukeji.kafka.manager.common.entity.ResultStatus;
import com.xiaojukeji.kafka.manager.common.entity.pojo.ClusterDO;
import com.xiaojukeji.kafka.manager.common.entity.pojo.TopicDO;
import com.xiaojukeji.kafka.manager.common.entity.pojo.gateway.AuthorityDO;
import com.xiaojukeji.kafka.manager.common.entity.pojo.ha.HaASRelationDO;
import com.xiaojukeji.kafka.manager.common.entity.vo.ha.HaClusterTopicVO;
import com.xiaojukeji.kafka.manager.common.entity.vo.normal.topic.HaClusterTopicHaStatusVO;
import com.xiaojukeji.kafka.manager.common.utils.ValidateUtils;
import com.xiaojukeji.kafka.manager.service.biz.ha.HaASRelationManager;
import com.xiaojukeji.kafka.manager.service.cache.PhysicalClusterMetadataManager;
import com.xiaojukeji.kafka.manager.service.service.TopicManagerService;
import com.xiaojukeji.kafka.manager.service.service.gateway.AuthorityService;
import com.xiaojukeji.kafka.manager.service.service.ha.HaASRelationService;
import com.xiaojukeji.kafka.manager.service.service.ha.HaTopicService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@Service
public class HaASRelationManagerImpl implements HaASRelationManager {
@Autowired
private HaASRelationService haASRelationService;
@Autowired
private TopicManagerService topicManagerService;
@Autowired
private HaTopicService haTopicService;
@Autowired
private AuthorityService authorityService;
@Override
public List<HaClusterTopicVO> getHATopics(Long firstClusterPhyId, Long secondClusterPhyId, boolean filterSystemTopics) {
List<HaASRelationDO> doList = haASRelationService.listAllHAFromDB(firstClusterPhyId, secondClusterPhyId, HaResTypeEnum.TOPIC);
if (ValidateUtils.isEmptyList(doList)) {
return new ArrayList<>();
}
List<HaClusterTopicVO> voList = new ArrayList<>();
for (HaASRelationDO relationDO: doList) {
if (filterSystemTopics
&& (relationDO.getActiveResName().startsWith("__") || relationDO.getStandbyResName().startsWith("__"))) {
// 过滤掉系统Topic && 存在系统Topic则过滤掉
continue;
}
HaClusterTopicVO vo = new HaClusterTopicVO();
vo.setClusterId(firstClusterPhyId);
if (firstClusterPhyId.equals(relationDO.getActiveClusterPhyId())) {
vo.setTopicName(relationDO.getActiveResName());
} else {
vo.setTopicName(relationDO.getStandbyResName());
}
vo.setProduceAclNum(0);
vo.setConsumeAclNum(0);
vo.setActiveClusterId(relationDO.getActiveClusterPhyId());
vo.setStandbyClusterId(relationDO.getStandbyClusterPhyId());
vo.setStatus(relationDO.getStatus());
// 补充ACL信息
List<AuthorityDO> authorityDOList = authorityService.getAuthorityByTopicFromCache(relationDO.getActiveClusterPhyId(), relationDO.getActiveResName());
authorityDOList.forEach(elem -> {
if ((elem.getAccess() & TopicAuthorityEnum.WRITE.getCode()) > 0) {
vo.setProduceAclNum(vo.getProduceAclNum() + 1);
}
if ((elem.getAccess() & TopicAuthorityEnum.READ.getCode()) > 0) {
vo.setConsumeAclNum(vo.getConsumeAclNum() + 1);
}
});
voList.add(vo);
}
return voList;
}
@Override
public Result<List<HaClusterTopicHaStatusVO>> listHaStatusTopics(Long clusterPhyId, Boolean checkMetadata) {
ClusterDO clusterDO = PhysicalClusterMetadataManager.getClusterFromCache(clusterPhyId);
if (clusterDO == null){
return Result.buildFrom(ResultStatus.CLUSTER_NOT_EXIST);
}
List<TopicDO> topicDOS = topicManagerService.getByClusterId(clusterPhyId);
if (ValidateUtils.isEmptyList(topicDOS)) {
return Result.buildSuc(new ArrayList<>());
}
Map<String, Integer> haRelationMap = haTopicService.getRelation(clusterPhyId);
List<HaClusterTopicHaStatusVO> statusVOS = new ArrayList<>();
topicDOS.stream().filter(topicDO -> !topicDO.getTopicName().startsWith("__"))//过滤引擎自带topic
.forEach(topicDO -> {
if(checkMetadata && !PhysicalClusterMetadataManager.isTopicExist(clusterPhyId, topicDO.getTopicName())){
return;
}
HaClusterTopicHaStatusVO statusVO = new HaClusterTopicHaStatusVO();
statusVO.setClusterId(clusterPhyId);
statusVO.setClusterName(clusterDO.getClusterName());
statusVO.setTopicName(topicDO.getTopicName());
statusVO.setHaRelation(haRelationMap.get(topicDO.getTopicName()));
statusVOS.add(statusVO);
});
return Result.buildSuc(statusVOS);
}
@Override
public Integer getRelation(Long clusterId, String topicName) {
HaASRelationDO relationDO = haASRelationService.getHAFromDB(clusterId, topicName, HaResTypeEnum.TOPIC);
if (relationDO == null){
return HaRelationTypeEnum.UNKNOWN.getCode();
}
if (topicName.equals(KafkaConstant.COORDINATOR_TOPIC_NAME)){
return HaRelationTypeEnum.MUTUAL_BACKUP.getCode();
}
if (clusterId.equals(relationDO.getActiveClusterPhyId())){
return HaRelationTypeEnum.ACTIVE.getCode();
}
if (clusterId.equals(relationDO.getStandbyClusterPhyId())){
return HaRelationTypeEnum.STANDBY.getCode();
}
return HaRelationTypeEnum.UNKNOWN.getCode();
}
@Override
public HaASRelationDO getASRelation(Long clusterId, String topicName) {
return haASRelationService.getHAFromDB(clusterId, topicName, HaResTypeEnum.TOPIC);
}
}

View File

@@ -0,0 +1,94 @@
package com.xiaojukeji.kafka.manager.service.biz.ha.impl;
import com.xiaojukeji.kafka.manager.common.bizenum.ha.HaResTypeEnum;
import com.xiaojukeji.kafka.manager.common.entity.Result;
import com.xiaojukeji.kafka.manager.common.entity.vo.rd.app.AppRelateTopicsVO;
import com.xiaojukeji.kafka.manager.service.biz.ha.HaAppManager;
import com.xiaojukeji.kafka.manager.service.service.gateway.AuthorityService;
import com.xiaojukeji.kafka.manager.service.service.ha.HaASRelationService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.stream.Collectors;
@Service
public class HaAppManagerImpl implements HaAppManager {
@Autowired
private AuthorityService authorityService;
@Autowired
private HaASRelationService haASRelationService;
@Override
public Result<List<AppRelateTopicsVO>> appRelateTopics(Long clusterPhyId, List<String> filterTopicNameList) {
// 获取关联的Topic列表
Map<String, Set<String>> userTopicMap = this.appRelateTopicsMap(clusterPhyId, filterTopicNameList);
// 获取集群已建立HA的Topic列表
Set<String> haTopicNameSet = haASRelationService.listAllHAFromDB(clusterPhyId, HaResTypeEnum.TOPIC)
.stream()
.map(elem -> elem.getActiveResName())
.collect(Collectors.toSet());
Set<String> filterTopicNameSet = new HashSet<>(filterTopicNameList);
List<AppRelateTopicsVO> voList = new ArrayList<>();
for (Map.Entry<String, Set<String>> entry: userTopicMap.entrySet()) {
AppRelateTopicsVO vo = new AppRelateTopicsVO();
vo.setClusterPhyId(clusterPhyId);
vo.setKafkaUser(entry.getKey());
vo.setSelectedTopicNameList(new ArrayList<>());
vo.setNotSelectTopicNameList(new ArrayList<>());
vo.setNotHaTopicNameList(new ArrayList<>());
entry.getValue().forEach(elem -> {
if (elem.startsWith("__")) {
// ignore
return;
}
if (!haTopicNameSet.contains(elem)) {
vo.getNotHaTopicNameList().add(elem);
} else if (filterTopicNameSet.contains(elem)) {
vo.getSelectedTopicNameList().add(elem);
} else {
vo.getNotSelectTopicNameList().add(elem);
}
});
voList.add(vo);
}
return Result.buildSuc(voList);
}
@Override
public boolean isContainAllRelateAppTopics(Long clusterPhyId, List<String> filterTopicNameList) {
Map<String, Set<String>> userTopicMap = this.appRelateTopicsMap(clusterPhyId, filterTopicNameList);
Set<String> relateTopicSet = new HashSet<>();
userTopicMap.values().forEach(elem -> relateTopicSet.addAll(elem));
return filterTopicNameList.containsAll(relateTopicSet);
}
private Map<String, Set<String>> appRelateTopicsMap(Long clusterPhyId, List<String> filterTopicNameList) {
Map<String, Set<String>> userTopicMap = new HashMap<>();
for (String topicName: filterTopicNameList) {
authorityService.getAuthorityByTopicFromCache(clusterPhyId, topicName)
.stream()
.map(elem -> elem.getAppId())
.filter(item -> !userTopicMap.containsKey(item))
.forEach(kafkaUser ->
userTopicMap.put(
kafkaUser,
authorityService.getAuthority(kafkaUser).stream().map(authorityDO -> authorityDO.getTopicName()).collect(Collectors.toSet())
)
);
}
return userTopicMap;
}
}

View File

@@ -0,0 +1,169 @@
package com.xiaojukeji.kafka.manager.service.biz.ha.impl;
import com.xiaojukeji.kafka.manager.common.bizenum.ClusterModeEnum;
import com.xiaojukeji.kafka.manager.common.bizenum.DBStatusEnum;
import com.xiaojukeji.kafka.manager.common.bizenum.ha.HaResTypeEnum;
import com.xiaojukeji.kafka.manager.common.constant.MsgConstant;
import com.xiaojukeji.kafka.manager.common.entity.Result;
import com.xiaojukeji.kafka.manager.common.entity.ResultStatus;
import com.xiaojukeji.kafka.manager.common.entity.ao.ClusterDetailDTO;
import com.xiaojukeji.kafka.manager.common.entity.pojo.ClusterDO;
import com.xiaojukeji.kafka.manager.common.entity.pojo.LogicalClusterDO;
import com.xiaojukeji.kafka.manager.common.entity.pojo.RegionDO;
import com.xiaojukeji.kafka.manager.common.entity.pojo.ha.HaASRelationDO;
import com.xiaojukeji.kafka.manager.common.utils.ListUtils;
import com.xiaojukeji.kafka.manager.service.biz.ha.HaClusterManager;
import com.xiaojukeji.kafka.manager.service.service.ClusterService;
import com.xiaojukeji.kafka.manager.service.service.LogicalClusterService;
import com.xiaojukeji.kafka.manager.service.service.RegionService;
import com.xiaojukeji.kafka.manager.service.service.ZookeeperService;
import com.xiaojukeji.kafka.manager.service.service.ha.HaASRelationService;
import com.xiaojukeji.kafka.manager.service.service.ha.HaClusterService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
import java.util.List;
@Component
public class HaClusterManagerImpl implements HaClusterManager {
private static final Logger LOGGER = LoggerFactory.getLogger(HaClusterManagerImpl.class);
@Autowired
private ClusterService clusterService;
@Autowired
private HaClusterService haClusterService;
@Autowired
private ZookeeperService zookeeperService;
@Autowired
private LogicalClusterService logicalClusterService;
@Autowired
private RegionService regionService;
@Autowired
private HaASRelationService haASRelationService;
@Override
public List<ClusterDetailDTO> getClusterDetailDTOList(Boolean needDetail) {
return clusterService.getClusterDetailDTOList(needDetail);
}
@Override
@Transactional
public Result<Void> addNew(ClusterDO clusterDO, Long activeClusterId, String operator) {
if (activeClusterId == null) {
// 普通集群直接写入DB
Long clusterPhyId = zookeeperService.getClusterIdAndNullIfFailed(clusterDO.getZookeeper());
if (clusterPhyId != null && clusterService.getById(clusterPhyId) == null) {
// 该集群ID不存在时则进行设置如果已经存在了则忽略
clusterDO.setId(clusterPhyId);
}
return Result.buildFrom(clusterService.addNew(clusterDO, operator));
}
//高可用集群
ClusterDO activeClusterDO = clusterService.getById(activeClusterId);
if (activeClusterDO == null) {
// 主集群不存在
return Result.buildFromRSAndMsg(ResultStatus.RESOURCE_NOT_EXIST, MsgConstant.getClusterPhyNotExist(activeClusterId));
}
HaASRelationDO oldRelationDO = haClusterService.getHA(activeClusterId);
if (oldRelationDO != null){
return Result.buildFromRSAndMsg(ResultStatus.RESOURCE_ALREADY_USED,
MsgConstant.getActiveClusterDuplicate(activeClusterDO.getId(), activeClusterDO.getClusterName()));
}
Long standbyClusterPhyId = zookeeperService.getClusterIdAndNullIfFailed(clusterDO.getZookeeper());
if (standbyClusterPhyId != null && clusterService.getById(standbyClusterPhyId) == null) {
// 该集群ID不存在时则进行设置如果已经存在了则忽略
clusterDO.setId(standbyClusterPhyId);
}
ResultStatus rs = clusterService.addNew(clusterDO, operator);
if (!ResultStatus.SUCCESS.equals(rs)) {
return Result.buildFrom(rs);
}
Result<List<Integer>> rli = zookeeperService.getBrokerIds(clusterDO.getZookeeper());
if (!rli.hasData()){
return Result.buildFrom(ResultStatus.BROKER_NOT_EXIST);
}
// 备集群创建region
RegionDO regionDO = new RegionDO(DBStatusEnum.ALIVE.getStatus(), clusterDO.getClusterName(), clusterDO.getId(), ListUtils.intList2String(rli.getData()));
rs = regionService.createRegion(regionDO);
if (!ResultStatus.SUCCESS.equals(rs)){
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
return Result.buildFrom(rs);
}
// 备集群创建逻辑集群
List<LogicalClusterDO> logicalClusterDOS = logicalClusterService.getByPhysicalClusterId(activeClusterId);
if (!logicalClusterDOS.isEmpty()) {
// 有逻辑集群,则对应创建逻辑集群
Integer mode = logicalClusterDOS.get(0).getMode();
LogicalClusterDO logicalClusterDO = new LogicalClusterDO(
clusterDO.getClusterName(),
clusterDO.getClusterName(),
ClusterModeEnum.INDEPENDENT_MODE.getCode().equals(mode)?mode:ClusterModeEnum.SHARED_MODE.getCode(),
ClusterModeEnum.INDEPENDENT_MODE.getCode().equals(mode)?logicalClusterDOS.get(0).getAppId(): "",
clusterDO.getId(),
regionDO.getId().toString()
);
ResultStatus clcRS = logicalClusterService.createLogicalCluster(logicalClusterDO);
if (clcRS.getCode() != ResultStatus.SUCCESS.getCode()){
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
return Result.buildFrom(clcRS);
}
}
return haClusterService.createHA(activeClusterId, clusterDO.getId(), operator);
}
@Override
@Transactional
public Result<Void> deleteById(Long clusterId, String operator) {
HaASRelationDO haRelationDO = haClusterService.getHA(clusterId);
if (haRelationDO == null){
return clusterService.deleteById(clusterId, operator);
}
Result rv = checkForDelete(haRelationDO, clusterId);
if (rv.failed()){
return rv;
}
//解除高可用关系
Result result = haClusterService.deleteHA(haRelationDO.getActiveClusterPhyId(), haRelationDO.getStandbyClusterPhyId());
if (result.failed()){
return result;
}
//删除集群
result = clusterService.deleteById(clusterId, operator);
if (result.failed()){
return result;
}
return Result.buildSuc();
}
private Result<Void> checkForDelete(HaASRelationDO haRelationDO, Long clusterId){
List<HaASRelationDO> relationDOS = haASRelationService.listAllHAFromDB(haRelationDO.getActiveClusterPhyId(),
haRelationDO.getStandbyClusterPhyId(),
HaResTypeEnum.TOPIC);
if (relationDOS.stream().filter(relationDO -> !relationDO.getActiveResName().startsWith("__")).count() > 0){
return Result.buildFromRSAndMsg(ResultStatus.OPERATION_FORBIDDEN, "集群还存在高可topic");
}
return Result.buildSuc();
}
}

View File

@@ -0,0 +1,559 @@
package com.xiaojukeji.kafka.manager.service.biz.ha.impl;
import com.xiaojukeji.kafka.manager.common.bizenum.ha.HaResTypeEnum;
import com.xiaojukeji.kafka.manager.common.bizenum.ha.HaStatusEnum;
import com.xiaojukeji.kafka.manager.common.constant.MsgConstant;
import com.xiaojukeji.kafka.manager.common.entity.Result;
import com.xiaojukeji.kafka.manager.common.entity.ResultStatus;
import com.xiaojukeji.kafka.manager.common.entity.TopicOperationResult;
import com.xiaojukeji.kafka.manager.common.entity.ao.ha.HaSwitchTopic;
import com.xiaojukeji.kafka.manager.common.entity.dto.op.topic.HaTopicRelationDTO;
import com.xiaojukeji.kafka.manager.common.entity.pojo.ClusterDO;
import com.xiaojukeji.kafka.manager.common.entity.pojo.TopicDO;
import com.xiaojukeji.kafka.manager.common.entity.pojo.ha.HaASRelationDO;
import com.xiaojukeji.kafka.manager.common.entity.pojo.ha.JobLogDO;
import com.xiaojukeji.kafka.manager.common.utils.BackoffUtils;
import com.xiaojukeji.kafka.manager.common.utils.ConvertUtil;
import com.xiaojukeji.kafka.manager.common.utils.ValidateUtils;
import com.xiaojukeji.kafka.manager.service.biz.ha.HaTopicManager;
import com.xiaojukeji.kafka.manager.service.cache.PhysicalClusterMetadataManager;
import com.xiaojukeji.kafka.manager.service.service.ClusterService;
import com.xiaojukeji.kafka.manager.service.service.JobLogService;
import com.xiaojukeji.kafka.manager.service.service.TopicManagerService;
import com.xiaojukeji.kafka.manager.service.service.gateway.AuthorityService;
import com.xiaojukeji.kafka.manager.service.service.ha.HaASRelationService;
import com.xiaojukeji.kafka.manager.service.service.ha.HaKafkaUserService;
import com.xiaojukeji.kafka.manager.service.service.ha.HaTopicService;
import com.xiaojukeji.kafka.manager.service.utils.ConfigUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.*;
import java.util.stream.Collectors;
@Component
public class HaTopicManagerImpl implements HaTopicManager {
private static final Logger LOGGER = LoggerFactory.getLogger(HaTopicManagerImpl.class);
@Autowired
private ClusterService clusterService;
@Autowired
private AuthorityService authorityService;
@Autowired
private HaTopicService haTopicService;
@Autowired
private HaKafkaUserService haKafkaUserService;
@Autowired
private HaASRelationService haASRelationService;
@Autowired
private TopicManagerService topicManagerService;
@Autowired
private ConfigUtils configUtils;
@Autowired
private JobLogService jobLogService;
@Override
public Result<HaSwitchTopic> switchHaWithCanRetry(Long newActiveClusterPhyId,
Long newStandbyClusterPhyId,
List<String> switchTopicNameList,
boolean focus,
boolean firstTriggerExecute,
JobLogDO switchLogTemplate,
String operator) {
LOGGER.info(
"method=switchHaWithCanRetry||newActiveClusterPhyId={}||newStandbyClusterPhyId={}||switchTopicNameList={}||focus={}||operator={}",
newActiveClusterPhyId, newStandbyClusterPhyId, ConvertUtil.obj2Json(switchTopicNameList), focus, operator
);
// 1、获取集群
ClusterDO newActiveClusterPhyDO = clusterService.getById(newActiveClusterPhyId);
if (ValidateUtils.isNull(newActiveClusterPhyDO)) {
return Result.buildFromRSAndMsg(ResultStatus.RESOURCE_NOT_EXIST, MsgConstant.getClusterPhyNotExist(newActiveClusterPhyId));
}
ClusterDO newStandbyClusterPhyDO = clusterService.getById(newStandbyClusterPhyId);
if (ValidateUtils.isNull(newStandbyClusterPhyDO)) {
return Result.buildFromRSAndMsg(ResultStatus.RESOURCE_NOT_EXIST, MsgConstant.getClusterPhyNotExist(newStandbyClusterPhyId));
}
// 2、进行参数检查
Result<List<HaASRelationDO>> doListResult = this.checkParamAndGetASRelation(newActiveClusterPhyId, newStandbyClusterPhyId, switchTopicNameList);
if (doListResult.failed()) {
LOGGER.error(
"method=switchHaWithCanRetry||newActiveClusterPhyId={}||newStandbyClusterPhyId={}||switchTopicNameList={}||paramErrResult={}||operator={}",
newActiveClusterPhyId, newStandbyClusterPhyId, ConvertUtil.obj2Json(switchTopicNameList), doListResult, operator
);
return Result.buildFromIgnoreData(doListResult);
}
List<HaASRelationDO> doList = doListResult.getData();
// 3、如果是第一次触发执行且状态是stable则修改状态
for (HaASRelationDO relationDO: doList) {
if (firstTriggerExecute && relationDO.getStatus().equals(HaStatusEnum.STABLE_CODE)) {
relationDO.setStatus(HaStatusEnum.SWITCHING_PREPARE_CODE);
haASRelationService.updateRelationStatus(relationDO.getId(), HaStatusEnum.SWITCHING_PREPARE_CODE);
}
}
// 4、进行切换预处理
HaSwitchTopic switchTopic = this.prepareSwitching(newStandbyClusterPhyDO, doList, focus, switchLogTemplate);
// 5、直接等待10秒使得相关数据有机会同步完成
BackoffUtils.backoff(10000);
// 6、检查数据同步情况
for (HaASRelationDO relationDO: doList) {
switchTopic.addHaSwitchTopic(this.checkTopicInSync(newActiveClusterPhyDO, newStandbyClusterPhyDO, relationDO, focus, switchLogTemplate));
}
// 7、删除旧的备Topic的同步配置
for (HaASRelationDO relationDO: doList) {
switchTopic.addHaSwitchTopic(this.oldStandbyTopicDelFetchConfig(newActiveClusterPhyDO, newStandbyClusterPhyDO, relationDO, focus, switchLogTemplate, operator));
}
// 8、增加新的备Topic的同步配置
switchTopic.addHaSwitchTopic(this.newStandbyTopicAddFetchConfig(newActiveClusterPhyDO, newStandbyClusterPhyDO, doList, focus, switchLogTemplate, operator));
// 9、进行切换收尾
switchTopic.addHaSwitchTopic(this.closeoutSwitching(newActiveClusterPhyDO, newStandbyClusterPhyDO, configUtils.getDKafkaGatewayZK(), doList, focus, switchLogTemplate));
// 10、状态结果汇总记录
doList.forEach(elem -> switchTopic.addActiveTopicStatus(elem.getActiveResName(), elem.getStatus()));
// 11、日志记录并返回
LOGGER.info(
"method=switchHaWithCanRetry||newActiveClusterPhyId={}||newStandbyClusterPhyId={}||switchTopicNameList={}||switchResult={}||operator={}",
newActiveClusterPhyId, newStandbyClusterPhyId, ConvertUtil.obj2Json(switchTopicNameList), switchTopic, operator
);
return Result.buildSuc(switchTopic);
}
@Override
public Result<List<TopicOperationResult>> batchCreateHaTopic(HaTopicRelationDTO dto, String operator) {
List<HaASRelationDO> relationDOS = haASRelationService.listAllHAFromDB(dto.getActiveClusterId(), dto.getStandbyClusterId(), HaResTypeEnum.CLUSTER);
if (relationDOS.isEmpty()){
return Result.buildFromRSAndMsg(ResultStatus.RESOURCE_NOT_EXIST, "集群高可用关系未建立");
}
//获取主集群已有的高可用topic
Map<String, Integer> haRelationMap = haTopicService.getRelation(dto.getActiveClusterId());
List<String> topicNames = dto.getTopicNames();
if (dto.getAll()){
topicNames = topicManagerService.getByClusterId(dto.getActiveClusterId())
.stream()
.filter(topicDO -> !topicDO.getTopicName().startsWith("__"))//过滤掉kafka自带topic
.filter(topicDO -> !haRelationMap.keySet().contains(topicDO.getTopicName()))//过滤调已成为高可用topic的topic
.filter(topicDO -> PhysicalClusterMetadataManager.isTopicExist(dto.getActiveClusterId(), topicDO.getTopicName()))
.map(TopicDO::getTopicName)
.collect(Collectors.toList());
}
List<TopicOperationResult> operationResultList = new ArrayList<>();
topicNames.forEach(topicName->{
Result<Void> rv = haTopicService.createHA(dto.getActiveClusterId(), dto.getStandbyClusterId(),topicName, operator);
operationResultList.add(TopicOperationResult.buildFrom(dto.getActiveClusterId(), topicName, rv));
});
return Result.buildSuc(operationResultList);
}
@Override
public Result<List<TopicOperationResult>> batchRemoveHaTopic(HaTopicRelationDTO dto, String operator) {
List<HaASRelationDO> relationDOS = haASRelationService.listAllHAFromDB(dto.getActiveClusterId(), dto.getStandbyClusterId(), HaResTypeEnum.CLUSTER);
if (relationDOS.isEmpty()){
return Result.buildFromRSAndMsg(ResultStatus.RESOURCE_NOT_EXIST, "集群高可用关系未建立");
}
List<TopicOperationResult> operationResultList = new ArrayList<>();
for(String topicName : dto.getTopicNames()){
HaASRelationDO relationDO = haASRelationService.getHAFromDB(
dto.getActiveClusterId(),
topicName,
HaResTypeEnum.TOPIC
);
if (relationDO == null) {
return Result.buildFromRSAndMsg(ResultStatus.RESOURCE_NOT_EXIST, "主备关系不存在");
}
Result<Void> rv = haTopicService.deleteHA(relationDO.getActiveClusterPhyId(), relationDO.getStandbyClusterPhyId(), topicName, operator);
operationResultList.add(TopicOperationResult.buildFrom(dto.getActiveClusterId(), topicName, rv));
}
return Result.buildSuc(operationResultList);
}
/**************************************************** private method ****************************************************/
private void saveLogs(JobLogDO switchLogTemplate, String content) {
jobLogService.addLogAndIgnoreException(switchLogTemplate.setAndCopyNew(new Date(), content));
}
/**
* 切换预处理
* 1、在主集群上将Topic关联的KafkaUser的active集群设置为None
*/
private HaSwitchTopic prepareSwitching(ClusterDO oldActiveClusterPhyDO, List<HaASRelationDO> doList, boolean focus, JobLogDO switchLogTemplate) {
// 暂停HA的KafkaUser
Set<String> stoppedHaKafkaUserSet = new HashSet<>();
HaSwitchTopic haSwitchTopic = new HaSwitchTopic(true);
boolean allSuccess = true; // 所有都成功
boolean needLog = false; // 需要记录日志
for (HaASRelationDO relationDO: doList) {
if (!relationDO.getStatus().equals(HaStatusEnum.SWITCHING_PREPARE_CODE)) {
// 当前不处于prepare状态
haSwitchTopic.setFinished(true);
continue;
}
needLog = true;
// 获取关联的KafkaUser
Set<String> relatedKafkaUserSet = authorityService.getAuthorityByTopic(relationDO.getActiveClusterPhyId(), relationDO.getActiveResName())
.stream()
.map(elem -> elem.getAppId())
.filter(kafkaUser -> !stoppedHaKafkaUserSet.contains(kafkaUser))
.collect(Collectors.toSet());
// 暂停kafkaUser HA
for (String kafkaUser: relatedKafkaUserSet) {
Result<Void> rv = haKafkaUserService.setNoneHAInKafka(oldActiveClusterPhyDO.getZookeeper(), kafkaUser);
if (rv.failed() && !focus) {
haSwitchTopic.setFinished(false);
this.saveLogs(switchLogTemplate, String.format("%s:\t失败1分钟后再进行重试", HaStatusEnum.SWITCHING_PREPARE.getMsg(oldActiveClusterPhyDO.getClusterName())));
return haSwitchTopic;
} else if (rv.failed() && focus) {
allSuccess = false;
}
}
// 记录操作过的user
stoppedHaKafkaUserSet.addAll(relatedKafkaUserSet);
// 修改Topic主备状态
relationDO.setStatus(HaStatusEnum.SWITCHING_WAITING_IN_SYNC_CODE);
haASRelationService.updateRelationStatus(relationDO.getId(), HaStatusEnum.SWITCHING_WAITING_IN_SYNC_CODE);
}
if (needLog) {
this.saveLogs(switchLogTemplate, String.format("%s:\t%s", HaStatusEnum.SWITCHING_PREPARE.getMsg(oldActiveClusterPhyDO.getClusterName()), allSuccess? "成功": "存在失败,但进行强制执行,跳过该操作"));
}
haSwitchTopic.setFinished(true);
return haSwitchTopic;
}
/**
* 等待主备Topic同步
*/
private HaSwitchTopic checkTopicInSync(ClusterDO newActiveClusterPhyDO, ClusterDO newStandbyClusterPhyDO, HaASRelationDO relationDO, boolean focus, JobLogDO switchLogTemplate) {
HaSwitchTopic haSwitchTopic = new HaSwitchTopic(true);
if (!relationDO.getStatus().equals(HaStatusEnum.SWITCHING_WAITING_IN_SYNC_CODE)) {
// 状态错误,直接略过
haSwitchTopic.setFinished(true);
return haSwitchTopic;
}
if (focus) {
// 无需等待inSync
// 修改Topic主备状态
relationDO.setStatus(HaStatusEnum.SWITCHING_CLOSE_OLD_STANDBY_TOPIC_FETCH_CODE);
haASRelationService.updateRelationStatus(relationDO.getId(), HaStatusEnum.SWITCHING_CLOSE_OLD_STANDBY_TOPIC_FETCH_CODE);
haSwitchTopic.setFinished(true);
this.saveLogs(switchLogTemplate, String.format(
"%s:\tTopic:[%s] 强制切换,跳过等待主备同步完成,直接进入下一步",
HaStatusEnum.SWITCHING_WAITING_IN_SYNC.getMsg(newActiveClusterPhyDO.getClusterName()),
relationDO.getActiveResName()
));
return haSwitchTopic;
}
Result<Long> lagResult = haTopicService.getStandbyTopicFetchLag(newStandbyClusterPhyDO.getId(), relationDO.getStandbyResName());
if (lagResult.failed()) {
// 获取Lag信息失败
this.saveLogs(switchLogTemplate, String.format(
"%s:\tTopic:[%s] 获取同步的Lag信息失败1分钟后再检查是否主备同步完成",
HaStatusEnum.SWITCHING_WAITING_IN_SYNC.getMsg(newActiveClusterPhyDO.getClusterName()),
relationDO.getActiveResName()
));
haSwitchTopic.setFinished(false);
return haSwitchTopic;
}
if (lagResult.getData().longValue() > 0) {
this.saveLogs(switchLogTemplate, String.format(
"%s:\tTopic:[%s] 还存在 %d 条数据未同步完成1分钟后再检查是否主备同步完成",
HaStatusEnum.SWITCHING_WAITING_IN_SYNC.getMsg(newActiveClusterPhyDO.getClusterName()),
relationDO.getActiveResName(),
lagResult.getData()
));
haSwitchTopic.setFinished(false);
return haSwitchTopic;
}
// 修改Topic主备状态
relationDO.setStatus(HaStatusEnum.SWITCHING_CLOSE_OLD_STANDBY_TOPIC_FETCH_CODE);
haASRelationService.updateRelationStatus(relationDO.getId(), HaStatusEnum.SWITCHING_CLOSE_OLD_STANDBY_TOPIC_FETCH_CODE);
haSwitchTopic.setFinished(true);
this.saveLogs(switchLogTemplate, String.format(
"%s:\tTopic:[%s] 主备同步完成",
HaStatusEnum.SWITCHING_WAITING_IN_SYNC.getMsg(newActiveClusterPhyDO.getClusterName()),
relationDO.getActiveResName()
));
return haSwitchTopic;
}
/**
* 备Topic删除拉取主Topic数据的配置
*/
private HaSwitchTopic oldStandbyTopicDelFetchConfig(ClusterDO newActiveClusterPhyDO, ClusterDO newStandbyClusterPhyDO, HaASRelationDO relationDO, boolean focus, JobLogDO switchLogTemplate, String operator) {
HaSwitchTopic haSwitchTopic = new HaSwitchTopic(true);
if (!relationDO.getStatus().equals(HaStatusEnum.SWITCHING_CLOSE_OLD_STANDBY_TOPIC_FETCH_CODE)) {
// 状态不对
haSwitchTopic.setFinished(true);
return haSwitchTopic;
}
Result<Void> rv = haTopicService.stopHAInKafka(
newActiveClusterPhyDO, relationDO.getStandbyResName(), // 旧的备
operator
);
if (rv.failed() && !focus) {
this.saveLogs(switchLogTemplate, String.format("%s:\tTopic:[%s] 失败1分钟后再进行重试", HaStatusEnum.SWITCHING_CLOSE_OLD_STANDBY_TOPIC_FETCH.getMsg(newActiveClusterPhyDO.getClusterName()), relationDO.getActiveResName()));
haSwitchTopic.setFinished(false);
return haSwitchTopic;
} else if (rv.failed() && focus) {
this.saveLogs(switchLogTemplate, String.format("%s:\tTopic:[%s] 失败,但进行强制执行,跳过该操作", HaStatusEnum.SWITCHING_CLOSE_OLD_STANDBY_TOPIC_FETCH.getMsg(newActiveClusterPhyDO.getClusterName()), relationDO.getActiveResName()));
} else {
this.saveLogs(switchLogTemplate, String.format("%s:\tTopic:[%s] 成功", HaStatusEnum.SWITCHING_CLOSE_OLD_STANDBY_TOPIC_FETCH.getMsg(newActiveClusterPhyDO.getClusterName()), relationDO.getActiveResName()));
}
// 修改Topic主备状态
relationDO.setStatus(HaStatusEnum.SWITCHING_OPEN_NEW_STANDBY_TOPIC_FETCH_CODE);
haASRelationService.updateRelationStatus(relationDO.getId(), HaStatusEnum.SWITCHING_OPEN_NEW_STANDBY_TOPIC_FETCH_CODE);
haSwitchTopic.setFinished(true);
return haSwitchTopic;
}
/**
* 新的备Topic创建拉取新主Topic数据的配置
*/
private HaSwitchTopic newStandbyTopicAddFetchConfig(ClusterDO newActiveClusterPhyDO,
ClusterDO newStandbyClusterPhyDO,
List<HaASRelationDO> doList,
boolean focus,
JobLogDO switchLogTemplate,
String operator) {
boolean forceAndFailed = false;
for (HaASRelationDO relationDO: doList) {
if (!relationDO.getStatus().equals(HaStatusEnum.SWITCHING_OPEN_NEW_STANDBY_TOPIC_FETCH_CODE)) {
// 状态不对
continue;
}
Result<Void> rv = null;
if (!forceAndFailed) {
// 非 强制切换并且失败了
rv = haTopicService.activeHAInKafka(
newActiveClusterPhyDO, relationDO.getStandbyResName(),
newStandbyClusterPhyDO, relationDO.getStandbyResName(),
operator
);
}
if (forceAndFailed) {
// 强制切换并且失败了,记录该日志
this.saveLogs(switchLogTemplate, String.format("%s:\tTopic:[%s] 失败,但因为是强制执行且强制执行时依旧出现操作失败,因此直接跳过该操作", HaStatusEnum.SWITCHING_OPEN_NEW_STANDBY_TOPIC_FETCH.getMsg(newStandbyClusterPhyDO.getClusterName()), relationDO.getActiveResName()));
} else if (rv.failed() && !focus) {
// 如果失败了,并且非强制切换,则直接返回
this.saveLogs(switchLogTemplate, String.format("%s:\tTopic:[%s] 失败1分钟后再进行重试", HaStatusEnum.SWITCHING_OPEN_NEW_STANDBY_TOPIC_FETCH.getMsg(newStandbyClusterPhyDO.getClusterName()), relationDO.getActiveResName()));
return new HaSwitchTopic(false);
} else if (rv.failed() && focus) {
// 如果失败了,但是是强制切换,则记录日志并继续
this.saveLogs(switchLogTemplate, String.format("%s:\tTopic:[%s] 失败,但因为是强制执行,因此跳过该操作", HaStatusEnum.SWITCHING_OPEN_NEW_STANDBY_TOPIC_FETCH.getMsg(newStandbyClusterPhyDO.getClusterName()), relationDO.getActiveResName()));
forceAndFailed = true;
} else {
// 记录成功日志
this.saveLogs(switchLogTemplate, String.format("%s:\tTopic:[%s] 成功", HaStatusEnum.SWITCHING_OPEN_NEW_STANDBY_TOPIC_FETCH.getMsg(newStandbyClusterPhyDO.getClusterName()), relationDO.getActiveResName()));
}
// 修改Topic主备状态
relationDO.setStatus(HaStatusEnum.SWITCHING_CLOSEOUT_CODE);
haASRelationService.updateRelationStatus(relationDO.getId(), HaStatusEnum.SWITCHING_CLOSEOUT_CODE);
}
return new HaSwitchTopic(true);
}
/**
* 切换收尾
* 1、原先的主集群-修改user的active集群指向新的主集群
* 2、原先的备集群-修改user的active集群指向新的主集群
* 3、网关-修改user的active集群指向新的主集群
*/
private HaSwitchTopic closeoutSwitching(ClusterDO newActiveClusterPhyDO, ClusterDO newStandbyClusterPhyDO, String gatewayZK, List<HaASRelationDO> doList, boolean focus, JobLogDO switchLogTemplate) {
// 暂停HA的KafkaUser
Set<String> activeHaKafkaUserSet = new HashSet<>();
boolean allSuccess = true;
boolean needLog = false;
boolean forceAndNewStandbyFailed = false; // 强制切换,但是新的备依旧操作失败
HaSwitchTopic haSwitchTopic = new HaSwitchTopic(true);
for (HaASRelationDO relationDO: doList) {
if (!relationDO.getStatus().equals(HaStatusEnum.SWITCHING_CLOSEOUT_CODE)) {
// 当前不处于closeout状态
haSwitchTopic.setFinished(false);
continue;
}
needLog = true;
// 获取关联的KafkaUser
Set<String> relatedKafkaUserSet = authorityService.getAuthorityByTopic(relationDO.getActiveClusterPhyId(), relationDO.getActiveResName())
.stream()
.map(elem -> elem.getAppId())
.filter(kafkaUser -> !activeHaKafkaUserSet.contains(kafkaUser))
.collect(Collectors.toSet());
for (String kafkaUser: relatedKafkaUserSet) {
// 操作新的主集群
Result<Void> rv = haKafkaUserService.activeHAInKafka(newActiveClusterPhyDO.getZookeeper(), newActiveClusterPhyDO.getId(), kafkaUser);
if (rv.failed() && !focus) {
haSwitchTopic.setFinished(false);
this.saveLogs(switchLogTemplate, String.format("%s:\t失败1分钟后再进行重试", HaStatusEnum.SWITCHING_CLOSEOUT.getMsg(newActiveClusterPhyDO.getClusterName())));
return haSwitchTopic;
} else if (rv.failed() && focus) {
allSuccess = false;
}
// 操作新的备集群如果出现错误则下次就不再进行操作ZK。新的备的Topic不是那么重要因此这里允许出现跳过
rv = null;
if (!forceAndNewStandbyFailed) {
// 如果对备集群的操作过程中,出现了失败,则直接跳过
rv = haKafkaUserService.activeHAInKafka(newStandbyClusterPhyDO.getZookeeper(), newActiveClusterPhyDO.getId(), kafkaUser);
}
if (rv != null && rv.failed() && !focus) {
haSwitchTopic.setFinished(false);
this.saveLogs(switchLogTemplate, String.format("%s:\t失败1分钟后再进行重试", HaStatusEnum.SWITCHING_CLOSEOUT.getMsg(newActiveClusterPhyDO.getClusterName())));
return haSwitchTopic;
} else if (rv != null && rv.failed() && focus) {
allSuccess = false;
forceAndNewStandbyFailed = true;
}
// 操作网关
rv = haKafkaUserService.activeHAInKafka(gatewayZK, newActiveClusterPhyDO.getId(), kafkaUser);
if (rv.failed() && !focus) {
haSwitchTopic.setFinished(false);
this.saveLogs(switchLogTemplate, String.format("%s:\t失败1分钟后再进行重试", HaStatusEnum.SWITCHING_CLOSEOUT.getMsg(newActiveClusterPhyDO.getClusterName())));
return haSwitchTopic;
} else if (rv.failed() && focus) {
allSuccess = false;
}
}
// 记录已经激活的User
activeHaKafkaUserSet.addAll(relatedKafkaUserSet);
// 修改Topic主备信息
HaASRelationDO newHaASRelationDO = new HaASRelationDO(
newActiveClusterPhyDO.getId(), relationDO.getActiveResName(),
newStandbyClusterPhyDO.getId(), relationDO.getStandbyResName(),
HaResTypeEnum.TOPIC.getCode(),
HaStatusEnum.STABLE_CODE
);
newHaASRelationDO.setId(relationDO.getId());
haASRelationService.updateById(newHaASRelationDO);
}
if (!needLog) {
return haSwitchTopic;
}
this.saveLogs(switchLogTemplate, String.format("%s:\t%s", HaStatusEnum.SWITCHING_CLOSEOUT.getMsg(newActiveClusterPhyDO.getClusterName()), allSuccess? "成功": "存在失败,但进行强制执行,跳过该操作"));
return haSwitchTopic;
}
/**
* 检查参数,并获取主备关系信息
*/
private Result<List<HaASRelationDO>> checkParamAndGetASRelation(Long activeClusterPhyId, Long standbyClusterPhyId, List<String> switchTopicNameList) {
List<HaASRelationDO> doList = new ArrayList<>();
for (String topicName: switchTopicNameList) {
Result<HaASRelationDO> doResult = this.checkParamAndGetASRelation(activeClusterPhyId, standbyClusterPhyId, topicName);
if (doResult.failed()) {
return Result.buildFromIgnoreData(doResult);
}
doList.add(doResult.getData());
}
return Result.buildSuc(doList);
}
/**
* 检查参数,并获取主备关系信息
*/
private Result<HaASRelationDO> checkParamAndGetASRelation(Long activeClusterPhyId, Long standbyClusterPhyId, String topicName) {
// newActiveTopic必须存在新的备Topic可以不存在
if (!PhysicalClusterMetadataManager.isTopicExist(activeClusterPhyId, topicName)) {
return Result.buildFromRSAndMsg(
ResultStatus.RESOURCE_NOT_EXIST,
String.format("新的主集群ID:[%d]-Topic:[%s] 不存在", activeClusterPhyId, topicName)
);
}
// 查询主备关系是否存在
HaASRelationDO relationDO = haASRelationService.getSpecifiedHAFromDB(
standbyClusterPhyId,
topicName,
activeClusterPhyId,
topicName,
HaResTypeEnum.TOPIC
);
if (relationDO == null) {
// 查询切换后的关系是否存在,如果已经存在,则后续会重新建立一遍
relationDO = haASRelationService.getSpecifiedHAFromDB(
activeClusterPhyId,
topicName,
standbyClusterPhyId,
topicName,
HaResTypeEnum.TOPIC
);
}
if (relationDO == null) {
// 主备关系不存在
return Result.buildFromRSAndMsg(
ResultStatus.RESOURCE_NOT_EXIST,
String.format("主集群ID:[%d]-Topic:[%s], 备集群ID:[%d] Topic:[%s] 的主备关系不存在,因此无法切换", activeClusterPhyId, topicName, standbyClusterPhyId, topicName)
);
}
return Result.buildSuc(relationDO);
}
}

View File

@@ -0,0 +1,41 @@
package com.xiaojukeji.kafka.manager.service.biz.job;
import com.xiaojukeji.kafka.manager.common.entity.Result;
import com.xiaojukeji.kafka.manager.common.entity.ao.ha.job.HaJobState;
import com.xiaojukeji.kafka.manager.common.entity.dto.ha.ASSwitchJobActionDTO;
import com.xiaojukeji.kafka.manager.common.entity.dto.ha.ASSwitchJobDTO;
import com.xiaojukeji.kafka.manager.common.entity.vo.ha.job.HaJobDetailVO;
import java.util.List;
public interface HaASSwitchJobManager {
/**
* 创建任务
*/
Result<Long> createJob(ASSwitchJobDTO dto, String operator);
/**
* 执行job
* @param jobId 任务ID
* @param focus 强制切换
* @param firstTriggerExecute 第一次触发执行
* @return
*/
Result<Void> executeJob(Long jobId, boolean focus, boolean firstTriggerExecute);
Result<HaJobState> jobState(Long jobId);
/**
* 刷新扩展数据
*/
void flushExtendData(Long jobId);
/**
* 对Job执行操作
*/
Result<Void> actionJob(Long jobId, ASSwitchJobActionDTO dto);
Result<List<HaJobDetailVO>> jobDetail(Long jobId);
}

View File

@@ -0,0 +1,452 @@
package com.xiaojukeji.kafka.manager.service.biz.job.impl;
import com.xiaojukeji.kafka.manager.common.bizenum.JobLogBizTypEnum;
import com.xiaojukeji.kafka.manager.common.bizenum.TaskActionEnum;
import com.xiaojukeji.kafka.manager.common.bizenum.ha.HaResTypeEnum;
import com.xiaojukeji.kafka.manager.common.bizenum.ha.HaStatusEnum;
import com.xiaojukeji.kafka.manager.common.bizenum.ha.job.HaJobStatusEnum;
import com.xiaojukeji.kafka.manager.common.constant.ConfigConstant;
import com.xiaojukeji.kafka.manager.common.constant.Constant;
import com.xiaojukeji.kafka.manager.common.constant.MsgConstant;
import com.xiaojukeji.kafka.manager.common.entity.Result;
import com.xiaojukeji.kafka.manager.common.entity.ResultStatus;
import com.xiaojukeji.kafka.manager.common.entity.ao.ha.HaSwitchTopic;
import com.xiaojukeji.kafka.manager.common.entity.ao.ha.job.HaJobDetail;
import com.xiaojukeji.kafka.manager.common.entity.ao.ha.job.HaJobState;
import com.xiaojukeji.kafka.manager.common.entity.ao.ha.job.HaSubJobExtendData;
import com.xiaojukeji.kafka.manager.common.entity.dto.ha.ASSwitchJobActionDTO;
import com.xiaojukeji.kafka.manager.common.entity.dto.ha.ASSwitchJobDTO;
import com.xiaojukeji.kafka.manager.common.entity.pojo.ClusterDO;
import com.xiaojukeji.kafka.manager.common.entity.pojo.ha.HaASRelationDO;
import com.xiaojukeji.kafka.manager.common.entity.pojo.ha.HaASSwitchJobDO;
import com.xiaojukeji.kafka.manager.common.entity.pojo.ha.HaASSwitchSubJobDO;
import com.xiaojukeji.kafka.manager.common.entity.pojo.ha.JobLogDO;
import com.xiaojukeji.kafka.manager.common.entity.vo.ha.job.HaJobDetailVO;
import com.xiaojukeji.kafka.manager.common.utils.BackoffUtils;
import com.xiaojukeji.kafka.manager.common.utils.ConvertUtil;
import com.xiaojukeji.kafka.manager.common.utils.FutureUtil;
import com.xiaojukeji.kafka.manager.common.utils.ValidateUtils;
import com.xiaojukeji.kafka.manager.service.biz.ha.HaAppManager;
import com.xiaojukeji.kafka.manager.service.biz.ha.HaTopicManager;
import com.xiaojukeji.kafka.manager.service.biz.job.HaASSwitchJobManager;
import com.xiaojukeji.kafka.manager.service.cache.PhysicalClusterMetadataManager;
import com.xiaojukeji.kafka.manager.service.service.ClusterService;
import com.xiaojukeji.kafka.manager.service.service.ConfigService;
import com.xiaojukeji.kafka.manager.service.service.JobLogService;
import com.xiaojukeji.kafka.manager.service.service.ha.HaASSwitchJobService;
import com.xiaojukeji.kafka.manager.service.service.ha.HaASRelationService;
import com.xiaojukeji.kafka.manager.service.service.ha.HaTopicService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.stream.Collectors;
@Service
public class HaASSwitchJobManagerImpl implements HaASSwitchJobManager {
private static final Logger LOGGER = LoggerFactory.getLogger(HaASSwitchJobManagerImpl.class);
@Autowired
private JobLogService jobLogService;
@Autowired
private ClusterService clusterService;
@Autowired
private ConfigService configService;
@Autowired
private HaASRelationService haASRelationService;
@Autowired
private HaASSwitchJobService haASSwitchJobService;
@Autowired
private HaTopicManager haTopicManager;
@Autowired
private HaTopicService haTopicService;
@Autowired
private HaAppManager haAppManager;
private static final Long BACK_OFF_TIME = 3000L;
private static final FutureUtil<Void> asyncExecuteJob = FutureUtil.init(
"HaASSwitchJobManager",
10,
10,
5000
);
@Override
public Result<Long> createJob(ASSwitchJobDTO dto, String operator) {
LOGGER.info("method=createJob||activeClusterPhyId={}||switchTopicParam={}||operator={}", dto.getActiveClusterPhyId(), ConvertUtil.obj2Json(dto), operator);
// 1、检查参数是否合法并获取需要执行主备切换的Topics
Result<Set<String>> haTopicSetResult = this.checkParamLegalAndGetNeedSwitchHaTopics(dto);
if (haTopicSetResult.failed()) {
// 检查失败,则直接返回
return Result.buildFromIgnoreData(haTopicSetResult);
}
LOGGER.info("method=createJob||activeClusterPhyId={}||switchTopics={}||operator={}", dto.getActiveClusterPhyId(), ConvertUtil.obj2Json(haTopicSetResult.getData()), operator);
// 2、查看是否将KafkaUser关联的Topic都涵盖了
if (dto.getMustContainAllKafkaUserTopics() != null
&& dto.getMustContainAllKafkaUserTopics()
&& (dto.getAll() == null || !dto.getAll())
&& !haAppManager.isContainAllRelateAppTopics(dto.getActiveClusterPhyId(), dto.getTopicNameList())) {
return Result.buildFromRSAndMsg(ResultStatus.OPERATION_FORBIDDEN, "存在KafkaUser关联的Topic未选中");
}
// 3、创建任务
Result<Long> longResult = haASSwitchJobService.createJob(
dto.getActiveClusterPhyId(),
dto.getStandbyClusterPhyId(),
new ArrayList<>(haTopicSetResult.getData()),
operator
);
if (longResult.failed()) {
// 创建失败
return longResult;
}
LOGGER.info("method=createJob||activeClusterPhyId={}||jobId={}||operator={}||msg=create-job success", dto.getActiveClusterPhyId(), longResult.getData(), operator);
// 4、为了加快执行效率这里在创建完成任务之后会直接异步执行HA切换任务
asyncExecuteJob.directSubmitTask(
() -> {
BackoffUtils.backoff(BACK_OFF_TIME);
this.executeJob(longResult.getData(), false, true);
// 更新扩展数据
this.flushExtendData(longResult.getData());
}
);
// 5、返回结果
return longResult;
}
@Override
public Result<Void> executeJob(Long jobId, boolean focus, boolean firstTriggerExecute) {
LOGGER.info("method=executeJob||jobId={}||msg=execute job start", jobId);
// 查询job
HaASSwitchJobDO jobDO = haASSwitchJobService.getJobById(jobId);
if (jobDO == null) {
LOGGER.warn("method=executeJob||jobId={}||msg=job not exist", jobId);
return Result.buildFromRSAndMsg(ResultStatus.RESOURCE_NOT_EXIST, String.format("jobId:[%d] 不存在", jobId));
}
// 检查job状态
if (!HaJobStatusEnum.isRunning(jobDO.getJobStatus())) {
LOGGER.warn("method=executeJob||jobId={}||jobStatus={}||msg=job status illegal", jobId, HaJobStatusEnum.valueOfStatus(jobDO.getJobStatus()));
return this.buildActionForbidden(jobId, jobDO.getJobStatus());
}
// 查询子job列表
List<HaASSwitchSubJobDO> subJobDOList = haASSwitchJobService.listSubJobsById(jobId);
if (ValidateUtils.isEmptyList(subJobDOList)) {
// 无子任务,则设置任务状态为成功
haASSwitchJobService.updateJobStatus(jobId, HaJobStatusEnum.SUCCESS.getStatus());
return Result.buildSuc();
}
Set<Integer> statusSet = new HashSet<>();
subJobDOList.forEach(elem -> statusSet.add(elem.getJobStatus()));
if (statusSet.size() == 1 && statusSet.contains(HaJobStatusEnum.SUCCESS.getStatus())) {
// 无子任务,则设置任务状态为成功
haASSwitchJobService.updateJobStatus(jobId, HaJobStatusEnum.SUCCESS.getStatus());
return Result.buildSuc();
}
if (firstTriggerExecute) {
this.saveLogs(jobDO.getId(), "主备切换开始...");
this.saveLogs(jobDO.getId(), "如果主备集群或网关的ZK存在问题则可能会出现1分钟左右日志不刷新的情况");
}
// 进行主备切换
Result<HaSwitchTopic> haSwitchTopicResult = haTopicManager.switchHaWithCanRetry(
jobDO.getActiveClusterPhyId(),
jobDO.getStandbyClusterPhyId(),
subJobDOList.stream().map(elem -> elem.getActiveResName()).collect(Collectors.toList()),
focus,
firstTriggerExecute,
new JobLogDO(JobLogBizTypEnum.HA_SWITCH_JOB_LOG.getCode(), String.valueOf(jobId)),
jobDO.getOperator()
);
if (haSwitchTopicResult.failed()) {
// 出现错误
LOGGER.error("method=executeJob||jobId={}||executeResult={}||msg=execute job failed", jobId, haSwitchTopicResult);
return Result.buildFromIgnoreData(haSwitchTopicResult);
}
// 执行结果
HaSwitchTopic haSwitchTopic = haSwitchTopicResult.getData();
Long timeoutUnitSec = this.getTimeoutUnitSecConfig(jobDO.getActiveClusterPhyId());
// 存储日志
if (haSwitchTopic.isFinished()) {
this.saveLogs(jobDO.getId(), "主备切换完成.");
}
// 更新状态
for (HaASSwitchSubJobDO subJobDO: subJobDOList) {
if (haSwitchTopic.isActiveTopicSwitchFinished(subJobDO.getActiveResName()) || haSwitchTopic.isFinished()) {
// 执行完成
haASSwitchJobService.updateSubJobStatus(subJobDO.getId(), HaJobStatusEnum.SUCCESS.getStatus());
} else if (runningInTimeout(subJobDO.getCreateTime().getTime(), timeoutUnitSec)) {
// 超时运行中
haASSwitchJobService.updateSubJobStatus(subJobDO.getId(), HaJobStatusEnum.RUNNING_IN_TIMEOUT.getStatus());
}
}
if (haSwitchTopic.isFinished()) {
// 任务执行完成
LOGGER.info("method=executeJob||jobId={}||executeResult={}||msg=execute job success", jobId, haSwitchTopicResult);
// 更新状态
haASSwitchJobService.updateJobStatus(jobId, HaJobStatusEnum.SUCCESS.getStatus());
} else {
LOGGER.info("method=executeJob||jobId={}||executeResult={}||msg=execute job not finished", jobId, haSwitchTopicResult);
}
// 返回结果
return Result.buildSuc();
}
@Override
public Result<HaJobState> jobState(Long jobId) {
List<HaASSwitchSubJobDO> doList = haASSwitchJobService.listSubJobsById(jobId);
if (ValidateUtils.isEmptyList(doList)) {
return Result.buildFromRSAndMsg(ResultStatus.RESOURCE_NOT_EXIST, String.format("jobId:[%d] 不存在", jobId));
}
if (System.currentTimeMillis() - doList.get(0).getCreateTime().getTime() <= (BACK_OFF_TIME.longValue() * 2)) {
// 进度0
return Result.buildSuc(new HaJobState(doList.size(), 0));
}
// 这里会假设主备Topic的名称是一样的
Map<String, Integer> progressMap = new HashMap<>();
haASRelationService.listAllHAFromDB(doList.get(0).getActiveClusterPhyId(), HaResTypeEnum.TOPIC).stream().forEach(
elem -> progressMap.put(elem.getActiveResName(), elem.getStatus())
);
HaJobState haJobState = new HaJobState(
doList.stream().map(elem -> elem.getJobStatus()).collect(Collectors.toList()),
0
);
// 计算细致的进度信息
Integer progress = 0;
for (HaASSwitchSubJobDO elem: doList) {
if (HaJobStatusEnum.isFinished(elem.getJobStatus())) {
progress += 100;
continue;
}
progress += HaStatusEnum.calProgress(progressMap.get(elem.getActiveResName()));
}
haJobState.setProgress(ConvertUtil.double2Int(progress * 1.0 / doList.size()));
return Result.buildSuc(haJobState);
}
@Override
public void flushExtendData(Long jobId) {
// 因为仅仅是刷新扩展数据因此不会对jobId等进行严格检查
// 查询子job列表
List<HaASSwitchSubJobDO> subJobDOList = haASSwitchJobService.listSubJobsById(jobId);
if (ValidateUtils.isEmptyList(subJobDOList)) {
// 无任务,直接返回
return;
}
for (HaASSwitchSubJobDO subJobDO: subJobDOList) {
try {
this.flushExtendData(subJobDO);
} catch (Exception e) {
LOGGER.error("method=flushExtendData||jobId={}||subJobDO={}||errMsg=exception", jobId, subJobDO, e);
}
}
}
@Override
public Result<Void> actionJob(Long jobId, ASSwitchJobActionDTO dto) {
if (!TaskActionEnum.FORCE.getAction().equals(dto.getAction())) {
// 不存在,或者不支持
return Result.buildFromRSAndMsg(ResultStatus.PARAM_ILLEGAL, "action不存在");
}
// 强制执行,异步执行
this.saveLogs(jobId, "开始执行强制切换...");
this.saveLogs(jobId, "强制切换过程中可能出现日志1分钟不刷新情况");
this.saveLogs(jobId, "强制切换过程中,因可能与正常切换任务同时执行,因此可能出现日志重复问题");
asyncExecuteJob.directSubmitTask(
() -> this.executeJob(jobId, true, false)
);
return Result.buildSuc();
}
@Override
public Result<List<HaJobDetailVO>> jobDetail(Long jobId) {
// 获取详情
Result<List<HaJobDetail>> haResult = haASSwitchJobService.jobDetail(jobId);
if (haResult.failed()) {
return Result.buildFromIgnoreData(haResult);
}
List<HaJobDetailVO> voList = ConvertUtil.list2List(haResult.getData(), HaJobDetailVO.class);
if (voList.isEmpty()) {
return Result.buildSuc(voList);
}
ClusterDO activeClusterDO = clusterService.getById(voList.get(0).getActiveClusterPhyId());
ClusterDO standbyClusterDO = clusterService.getById(voList.get(0).getStandbyClusterPhyId());
// 获取超时配置
Long timeoutUnitSecConfig = this.getTimeoutUnitSecConfig(voList.get(0).getActiveClusterPhyId());
voList.forEach(elem -> {
elem.setTimeoutUnitSecConfig(timeoutUnitSecConfig);
elem.setActiveClusterPhyName(activeClusterDO != null? activeClusterDO.getClusterName(): "");
elem.setStandbyClusterPhyName(standbyClusterDO != null? standbyClusterDO.getClusterName(): "");
});
// 返回结果
return Result.buildSuc(voList);
}
/**************************************************** private method ****************************************************/
/**
* 检查参数是否合法并返回需要进行主备切换的Topic
*/
private Result<Set<String>> checkParamLegalAndGetNeedSwitchHaTopics(ASSwitchJobDTO dto) {
// 1、检查主集群是否存在
ClusterDO activeClusterDO = clusterService.getById(dto.getActiveClusterPhyId());
if (ValidateUtils.isNull(activeClusterDO)) {
return Result.buildFromRSAndMsg(ResultStatus.RESOURCE_NOT_EXIST, MsgConstant.getClusterPhyNotExist(dto.getActiveClusterPhyId()));
}
// 2、检查备集群是否存在
ClusterDO standbyClusterDO = clusterService.getById(dto.getStandbyClusterPhyId());
if (ValidateUtils.isNull(standbyClusterDO)) {
return Result.buildFromRSAndMsg(ResultStatus.RESOURCE_NOT_EXIST, MsgConstant.getClusterPhyNotExist(dto.getStandbyClusterPhyId()));
}
// 3、检查集群是否建立了主备关系
List<HaASRelationDO> clusterDOList = haASRelationService.listAllHAFromDB(dto.getActiveClusterPhyId(), dto.getStandbyClusterPhyId(), HaResTypeEnum.CLUSTER);
if (ValidateUtils.isEmptyList(clusterDOList)) {
return Result.buildFromRSAndMsg(ResultStatus.RESOURCE_NOT_EXIST, "集群主备关系未建立");
}
// 4、获取集群当前已经建立主备关系的Topic列表
List<HaASRelationDO> topicDOList = haASRelationService.listAllHAFromDB(dto.getActiveClusterPhyId(), dto.getStandbyClusterPhyId(), HaResTypeEnum.TOPIC);
if (dto.getAll() != null && dto.getAll()) {
// 5.1、对集群所有已经建立主备关系的Topic进行主备切换
// 过滤掉 __打头的Topic
// 过滤掉 当前主集群已经是切换后的主集群的Topic即这部分Topic已经是切换后的状态了
return Result.buildSuc(
topicDOList.stream()
.filter(elem -> !elem.getActiveResName().startsWith("__"))
.filter(elem -> !elem.getActiveClusterPhyId().equals(dto.getActiveClusterPhyId()))
.map(elem -> elem.getActiveResName())
.collect(Collectors.toSet())
);
}
// 5.2、指定Topic进行主备切换
// 当前已经有主备关系的Topic
Set<String> relationTopicNameSet = new HashSet<>();
topicDOList.forEach(elem -> relationTopicNameSet.add(elem.getActiveResName()));
// 逐个检查Topic此时这里不进行过滤如果进行过滤之后会导致一些用户提交的信息丢失。
// 比如提交了10个Topic我过滤成9个用户就会比较奇怪。
// 上一步进行过滤是减少不必要的Topic的刚扰PS也可以考虑增加这些干扰从而让用户明确知道Topic已进行主备切换
for (String topicName: dto.getTopicNameList()) {
if (!relationTopicNameSet.contains(topicName)) {
return Result.buildFromRSAndMsg(ResultStatus.RESOURCE_NOT_EXIST, String.format("Topic:[%s] 主备关系不存在,需要先建立主备关系", topicName));
}
// 检查新的主Topic是否存在如果不存在则直接返回错误不检查新的备Topic是否存在
if (!PhysicalClusterMetadataManager.isTopicExist(dto.getActiveClusterPhyId(), topicName)) {
return Result.buildFromRSAndMsg(ResultStatus.RESOURCE_NOT_EXIST, MsgConstant.getTopicNotExist(dto.getActiveClusterPhyId(), topicName));
}
}
return Result.buildSuc(
dto.getTopicNameList().stream().collect(Collectors.toSet())
);
}
private void saveLogs(Long jobId, String content) {
jobLogService.addLogAndIgnoreException(new JobLogDO(
JobLogBizTypEnum.HA_SWITCH_JOB_LOG.getCode(),
String.valueOf(jobId),
new Date(),
content
));
}
private void flushExtendData(HaASSwitchSubJobDO subJobDO) {
HaSubJobExtendData extendData = new HaSubJobExtendData();
Result<Long> sumLagResult = haTopicService.getStandbyTopicFetchLag(subJobDO.getActiveClusterPhyId(), subJobDO.getActiveResName());
if (sumLagResult.failed()) {
extendData.setSumLag(Constant.INVALID_CODE.longValue());
} else {
extendData.setSumLag(sumLagResult.getData());
}
haASSwitchJobService.updateSubJobExtendData(subJobDO.getId(), extendData);
}
private Result<Void> buildActionForbidden(Long jobId, Integer jobStatus) {
return Result.buildFromRSAndMsg(
ResultStatus.OPERATION_FORBIDDEN,
String.format("jobId:[%d] 当前 status:[%s], 不允许被执行", jobId, HaJobStatusEnum.valueOfStatus(jobStatus))
);
}
private boolean runningInTimeout(Long startTimeUnitMs, Long timeoutUnitSec) {
if (timeoutUnitSec == null) {
// 配置为空,则返回未超时
return false;
}
// 开始时间 + 超时时间 > 当前时间,则为超时
return startTimeUnitMs + timeoutUnitSec * 1000 > System.currentTimeMillis();
}
private Long getTimeoutUnitSecConfig(Long activeClusterPhyId) {
// 获取该集群配置
Long durationUnitSec = configService.getLongValue(
ConfigConstant.HA_SWITCH_JOB_TIMEOUT_UNIT_SEC_CONFIG_PREFIX + "_" + activeClusterPhyId,
null
);
if (durationUnitSec == null) {
// 当前集群配置不存在,则获取默认配置
durationUnitSec = configService.getLongValue(
ConfigConstant.HA_SWITCH_JOB_TIMEOUT_UNIT_SEC_CONFIG_PREFIX + "_" + Constant.INVALID_CODE,
null
);
}
return durationUnitSec;
}
}

View File

@@ -43,7 +43,7 @@ public interface ClusterService {
ClusterNameDTO getClusterName(Long logicClusterId);
ResultStatus deleteById(Long clusterId, String operator);
Result<Void> deleteById(Long clusterId, String operator);
/**
* 获取优先被选举为controller的broker

View File

@@ -0,0 +1,15 @@
package com.xiaojukeji.kafka.manager.service.service;
import com.xiaojukeji.kafka.manager.common.entity.pojo.ha.JobLogDO;
import java.util.List;
/**
* Job相关的日志
*/
public interface JobLogService {
void addLogAndIgnoreException(JobLogDO jobLogDO);
List<JobLogDO> listLogs(Integer bizType, String bizKeyword, Long startId);
}

View File

@@ -2,11 +2,14 @@ package com.xiaojukeji.kafka.manager.service.service;
import com.xiaojukeji.kafka.manager.common.entity.Result;
import com.xiaojukeji.kafka.manager.common.entity.ResultStatus;
import com.xiaojukeji.kafka.manager.common.entity.TopicOperationResult;
import com.xiaojukeji.kafka.manager.common.entity.ao.RdTopicBasic;
import com.xiaojukeji.kafka.manager.common.entity.ao.topic.MineTopicSummary;
import com.xiaojukeji.kafka.manager.common.entity.ao.topic.TopicAppData;
import com.xiaojukeji.kafka.manager.common.entity.ao.topic.TopicBusinessInfo;
import com.xiaojukeji.kafka.manager.common.entity.ao.topic.TopicDTO;
import com.xiaojukeji.kafka.manager.common.entity.ao.topic.MineTopicSummary;
import com.xiaojukeji.kafka.manager.common.entity.dto.op.topic.TopicExpansionDTO;
import com.xiaojukeji.kafka.manager.common.entity.dto.op.topic.TopicModificationDTO;
import com.xiaojukeji.kafka.manager.common.entity.pojo.TopicDO;
import com.xiaojukeji.kafka.manager.common.entity.pojo.TopicExpiredDO;
import com.xiaojukeji.kafka.manager.common.entity.pojo.TopicStatisticsDO;
@@ -130,5 +133,15 @@ public interface TopicManagerService {
* @return
*/
ResultStatus addAuthority(AuthorityDO authorityDO);
/**
* 修改topic
*/
Result modifyTopic(TopicModificationDTO dto);
/**
* topic扩分区
*/
TopicOperationResult expandTopic(TopicExpansionDTO dto);
}

View File

@@ -65,6 +65,7 @@ public interface TopicService {
* 获取Topic的分区的offset
*/
Map<TopicPartition, Long> getPartitionOffset(ClusterDO clusterDO, String topicName, OffsetPosEnum offsetPosEnum);
Map<TopicPartition, Long> getPartitionOffset(Long clusterPhyId, String topicName, OffsetPosEnum offsetPosEnum);
/**
* 获取Topic概览信息

View File

@@ -42,4 +42,13 @@ public interface ZookeeperService {
* @return
*/
Result deleteControllerPreferredCandidate(Long clusterId, Integer brokerId);
/**
* 获取集群的brokerId
* @param zookeeper zookeeper
* @return 操作结果
*/
Result<List<Integer>> getBrokerIds(String zookeeper);
Long getClusterIdAndNullIfFailed(String zookeeper);
}

View File

@@ -51,6 +51,13 @@ public interface AppService {
*/
List<AppDO> getByPrincipal(String principal);
/**
* 通过负责人&集群id(排除已被其他集群绑定的app)来查找
* @param principal 负责人
* @return List<AppDO>
*/
List<AppDO> getByPrincipalAndClusterId(String principal, Long phyClusterId);
/**
* 通过appId来查,需要check当前登录人是否有权限.
* @param appId appId

View File

@@ -46,6 +46,8 @@ public interface AuthorityService {
*/
List<AuthorityDO> getAuthorityByTopic(Long clusterId, String topicName);
List<AuthorityDO> getAuthorityByTopicFromCache(Long clusterId, String topicName);
List<AuthorityDO> getAuthority(String appId);
/**

View File

@@ -4,24 +4,27 @@ import com.alibaba.fastjson.JSONObject;
import com.xiaojukeji.kafka.manager.common.bizenum.ModuleEnum;
import com.xiaojukeji.kafka.manager.common.bizenum.OperateEnum;
import com.xiaojukeji.kafka.manager.common.bizenum.OperationStatusEnum;
import com.xiaojukeji.kafka.manager.common.bizenum.ha.HaResTypeEnum;
import com.xiaojukeji.kafka.manager.common.entity.ResultStatus;
import com.xiaojukeji.kafka.manager.common.entity.ao.AppTopicDTO;
import com.xiaojukeji.kafka.manager.common.entity.dto.normal.AppDTO;
import com.xiaojukeji.kafka.manager.common.entity.pojo.LogicalClusterDO;
import com.xiaojukeji.kafka.manager.common.entity.pojo.OperateRecordDO;
import com.xiaojukeji.kafka.manager.common.entity.pojo.TopicDO;
import com.xiaojukeji.kafka.manager.common.entity.pojo.gateway.AppDO;
import com.xiaojukeji.kafka.manager.common.entity.pojo.gateway.AuthorityDO;
import com.xiaojukeji.kafka.manager.common.entity.pojo.gateway.KafkaUserDO;
import com.xiaojukeji.kafka.manager.common.entity.pojo.ha.HaASRelationDO;
import com.xiaojukeji.kafka.manager.common.utils.ListUtils;
import com.xiaojukeji.kafka.manager.common.utils.ValidateUtils;
import com.xiaojukeji.kafka.manager.common.entity.pojo.LogicalClusterDO;
import com.xiaojukeji.kafka.manager.common.entity.pojo.TopicDO;
import com.xiaojukeji.kafka.manager.dao.gateway.AppDao;
import com.xiaojukeji.kafka.manager.dao.gateway.KafkaUserDao;
import com.xiaojukeji.kafka.manager.service.cache.LogicalClusterMetadataManager;
import com.xiaojukeji.kafka.manager.service.service.OperateRecordService;
import com.xiaojukeji.kafka.manager.service.service.TopicManagerService;
import com.xiaojukeji.kafka.manager.service.service.gateway.AppService;
import com.xiaojukeji.kafka.manager.service.service.gateway.AuthorityService;
import com.xiaojukeji.kafka.manager.service.service.TopicManagerService;
import com.xiaojukeji.kafka.manager.service.service.ha.HaASRelationService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@@ -60,6 +63,9 @@ public class AppServiceImpl implements AppService {
@Autowired
private OperateRecordService operateRecordService;
@Autowired
private HaASRelationService haASRelationService;
@Override
public ResultStatus addApp(AppDO appDO, String operator) {
try {
@@ -181,6 +187,52 @@ public class AppServiceImpl implements AppService {
return new ArrayList<>();
}
@Override
public List<AppDO> getByPrincipalAndClusterId(String principal, Long phyClusterId) {
try {
List<AppDO> appDOs = appDao.getByPrincipal(principal);
if (ValidateUtils.isEmptyList(appDOs)){
return new ArrayList<>();
}
List<HaASRelationDO> has = haASRelationService.listAllHAFromDB(phyClusterId, HaResTypeEnum.CLUSTER);
List<AuthorityDO> authorityDOS;
if (has.isEmpty()){
authorityDOS = authorityService.listAll().stream()
.filter(authorityDO -> !authorityDO.getClusterId().equals(phyClusterId))
.collect(Collectors.toList());
}else {
authorityDOS = authorityService.listAll().stream()
.filter(authorityDO -> !(has.get(0).getActiveClusterPhyId().equals(authorityDO.getClusterId())
|| has.get(0).getStandbyClusterPhyId().equals(authorityDO.getClusterId())))
.collect(Collectors.toList());
}
Map<String,List<AuthorityDO>> appClusterIdMap = authorityDOS
.stream().filter(authorityDO -> !authorityDO.getClusterId().equals(phyClusterId))
.collect(Collectors.groupingBy(AuthorityDO::getAppId));
//过滤已被其他集群topic使用的app
appDOs = appDOs.stream()
.filter(appDO -> ListUtils.string2StrList(appDO.getPrincipals()).contains(principal))
.filter(appDO -> appClusterIdMap.get(appDO.getAppId()) == null)
.collect(Collectors.toList());
//过滤已被其他集群使用的app
List<String> clusterAppIds = logicClusterMetadataManager.getLogicalClusterList()
.stream().filter(logicalClusterDO -> !logicalClusterDO.getClusterId().equals(phyClusterId) )
.map(LogicalClusterDO::getAppId).collect(Collectors.toList());
appDOs = appDOs.stream()
.filter(appDO -> !clusterAppIds.contains(appDO.getAppId()))
.collect(Collectors.toList());
return appDOs;
} catch (Exception e) {
LOGGER.error("get app list failed, principals:{}.", principal);
}
return new ArrayList<>();
}
@Override
public AppDO getAppByUserAndId(String appId, String curUser) {
AppDO appDO = this.getByAppId(appId);

View File

@@ -4,6 +4,7 @@ import com.xiaojukeji.kafka.manager.common.bizenum.ModuleEnum;
import com.xiaojukeji.kafka.manager.common.bizenum.OperateEnum;
import com.xiaojukeji.kafka.manager.common.bizenum.OperationStatusEnum;
import com.xiaojukeji.kafka.manager.common.bizenum.TopicAuthorityEnum;
import com.xiaojukeji.kafka.manager.common.constant.Constant;
import com.xiaojukeji.kafka.manager.common.entity.ResultStatus;
import com.xiaojukeji.kafka.manager.common.entity.ao.gateway.TopicQuota;
import com.xiaojukeji.kafka.manager.common.entity.pojo.OperateRecordDO;
@@ -75,8 +76,10 @@ public class AuthorityServiceImpl implements AuthorityService {
return kafkaAclDao.insert(kafkaAclDO);
} catch (Exception e) {
LOGGER.error("add authority failed, authorityDO:{}.", authorityDO, e);
// 返回-1表示出错
return Constant.INVALID_CODE;
}
return result;
}
@Override
@@ -124,7 +127,10 @@ public class AuthorityServiceImpl implements AuthorityService {
operateRecordService.insert(operateRecordDO);
} catch (Exception e) {
LOGGER.error("delete authority failed, authorityDO:{}.", authorityDO, e);
return ResultStatus.MYSQL_ERROR;
}
return ResultStatus.SUCCESS;
}
@@ -152,6 +158,11 @@ public class AuthorityServiceImpl implements AuthorityService {
return Collections.emptyList();
}
@Override
public List<AuthorityDO> getAuthorityByTopicFromCache(Long clusterId, String topicName) {
return authorityDao.getAuthorityByTopicFromCache(clusterId, topicName);
}
@Override
public List<AuthorityDO> getAuthority(String appId) {
List<AuthorityDO> doList = null;

View File

@@ -0,0 +1,61 @@
package com.xiaojukeji.kafka.manager.service.service.ha;
import com.xiaojukeji.kafka.manager.common.bizenum.ha.HaResTypeEnum;
import com.xiaojukeji.kafka.manager.common.entity.Result;
import com.xiaojukeji.kafka.manager.common.entity.pojo.ha.HaASRelationDO;
import java.util.List;
public interface HaASRelationService {
Result<Void> replaceTopicRelationsToDB(Long standbyClusterPhyId, List<HaASRelationDO> topicRelationDOList);
Result<Void> addHAToDB(HaASRelationDO haASRelationDO);
Result<Void> deleteById(Long id);
int updateRelationStatus(Long relationId, Integer newStatus);
int updateById(HaASRelationDO haASRelationDO);
/**
* 获取主集群关系
*/
HaASRelationDO getActiveClusterHAFromDB(Long activeClusterPhyId);
/**
* 获取主备关系
*/
HaASRelationDO getSpecifiedHAFromDB(Long activeClusterPhyId,
String activeResName,
Long standbyClusterPhyId,
String standbyResName,
HaResTypeEnum resTypeEnum);
/**
* 获取主备关系
*/
HaASRelationDO getHAFromDB(Long firstClusterPhyId,
String firstResName,
HaResTypeEnum resTypeEnum);
/**
* 获取备集群主备关系
*/
List<HaASRelationDO> getStandbyHAFromDB(Long standbyClusterPhyId, HaResTypeEnum resTypeEnum);
List<HaASRelationDO> getActiveHAFromDB(Long activeClusterPhyId, HaResTypeEnum resTypeEnum);
/**
* 获取主备关系
*/
List<HaASRelationDO> listAllHAFromDB(HaResTypeEnum resTypeEnum);
/**
* 获取主备关系
*/
List<HaASRelationDO> listAllHAFromDB(Long firstClusterPhyId, HaResTypeEnum resTypeEnum);
/**
* 获取主备关系
*/
List<HaASRelationDO> listAllHAFromDB(Long firstClusterPhyId, Long secondClusterPhyId, HaResTypeEnum resTypeEnum);
}

View File

@@ -0,0 +1,57 @@
package com.xiaojukeji.kafka.manager.service.service.ha;
import com.xiaojukeji.kafka.manager.common.entity.Result;
import com.xiaojukeji.kafka.manager.common.entity.ao.ha.job.HaJobDetail;
import com.xiaojukeji.kafka.manager.common.entity.ao.ha.job.HaSubJobExtendData;
import com.xiaojukeji.kafka.manager.common.entity.pojo.ha.HaASSwitchJobDO;
import com.xiaojukeji.kafka.manager.common.entity.pojo.ha.HaASSwitchSubJobDO;
import java.util.List;
import java.util.Map;
public interface HaASSwitchJobService {
/**
* 创建任务
*/
Result<Long> createJob(Long activeClusterPhyId, Long standbyClusterPhyId, List<String> topicNameList, String operator);
/**
* 更新任务状态
*/
int updateJobStatus(Long jobId, Integer jobStatus);
/**
* 更新子任务状态
*/
int updateSubJobStatus(Long subJobId, Integer jobStatus);
/**
* 更新子任务扩展数据
*/
int updateSubJobExtendData(Long subJobId, HaSubJobExtendData extendData);
/**
* 任务详情
*/
Result<List<HaJobDetail>> jobDetail(Long jobId);
/**
* 正在运行中的job
*/
List<Long> listRunningJobs(Long ignoreAfterTime);
/**
* 集群近期的任务ID
*/
Map<Long/*集群ID*/, HaASSwitchJobDO> listClusterLatestJobs();
HaASSwitchJobDO getJobById(Long jobId);
List<HaASSwitchSubJobDO> listSubJobsById(Long jobId);
/**
* 获取所有切换任务
*/
List<HaASSwitchSubJobDO> listAll(Boolean isAsc);
}

View File

@@ -0,0 +1,45 @@
package com.xiaojukeji.kafka.manager.service.service.ha;
import com.xiaojukeji.kafka.manager.common.entity.Result;
import com.xiaojukeji.kafka.manager.common.entity.pojo.ClusterDO;
import com.xiaojukeji.kafka.manager.common.entity.pojo.ha.HaASRelationDO;
import com.xiaojukeji.kafka.manager.common.entity.vo.ha.HaClusterVO;
import java.util.List;
import java.util.Map;
/**
* 集群主备关系
*/
public interface HaClusterService {
/**
* 创建主备关系
*/
Result<Void> createHA(Long activeClusterPhyId, Long standbyClusterPhyId, String operator);
Result<Void> createHAInKafka(String zookeeper, ClusterDO needWriteToZKClusterDO, String operator);
/**
* 切换主备关系
*/
Result<Void> switchHA(Long newActiveClusterPhyId, Long newStandbyClusterPhyId);
/**
* 删除主备关系
*/
Result<Void> deleteHA(Long activeClusterPhyId, Long standbyClusterPhyId);
/**
* 获取主备关系
*/
HaASRelationDO getHA(Long activeClusterPhyId);
/**
* 获取集群主备关系
*/
Map<Long, Integer> getClusterHARelation();
/**
* 获取主备关系
*/
Result<List<HaClusterVO>> listAllHA();
}

View File

@@ -0,0 +1,23 @@
package com.xiaojukeji.kafka.manager.service.service.ha;
import com.xiaojukeji.kafka.manager.common.entity.Result;
/**
* Topic主备关系管理
* 不包括ACLGateway等信息
*/
public interface HaKafkaUserService {
Result<Void> setNoneHAInKafka(String zookeeper, String kafkaUser);
/**
* 暂停HA
*/
Result<Void> stopHAInKafka(String zookeeper, String kafkaUser);
/**
* 激活HA
*/
Result<Void> activeHAInKafka(String zookeeper, Long activeClusterPhyId, String kafkaUser);
}

View File

@@ -0,0 +1,43 @@
package com.xiaojukeji.kafka.manager.service.service.ha;
import com.xiaojukeji.kafka.manager.common.entity.Result;
import com.xiaojukeji.kafka.manager.common.entity.pojo.ClusterDO;
import java.util.List;
import java.util.Map;
/**
* Topic主备关系管理
* 不包括ACLGateway等信息
*/
public interface HaTopicService {
/**
* 创建主备关系
*/
Result<Void> createHA(Long activeClusterPhyId, Long standbyClusterPhyId, String topicName, String operator);
Result<Void> activeHAInKafkaNotCheck(ClusterDO activeClusterDO, String activeTopicName, ClusterDO standbyClusterDO, String standbyTopicName, String operator);
Result<Void> activeHAInKafka(ClusterDO activeClusterDO, String activeTopicName, ClusterDO standbyClusterDO, String standbyTopicName, String operator);
/**
* 删除主备关系
*/
Result<Void> deleteHA(Long activeClusterPhyId, Long standbyClusterPhyId, String topicName, String operator);
Result<Void> stopHAInKafka(ClusterDO standbyClusterDO, String standbyTopicName, String operator);
/**
* 获取集群topic的主备关系
*/
Map<String, Integer> getRelation(Long clusterId);
/**
* 获取所有集群的备topic名称
*/
Map<Long, List<String>> getClusterStandbyTopicMap();
/**
* 激活kafkaUserHA
*/
Result<Void> activeUserHAInKafka(ClusterDO activeClusterDO, ClusterDO standbyClusterDO, String kafkaUser, String operator);
Result<Long> getStandbyTopicFetchLag(Long standbyClusterPhyId, String topicName);
}

View File

@@ -0,0 +1,199 @@
package com.xiaojukeji.kafka.manager.service.service.ha.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.xiaojukeji.kafka.manager.common.bizenum.ha.HaResTypeEnum;
import com.xiaojukeji.kafka.manager.common.bizenum.ha.HaStatusEnum;
import com.xiaojukeji.kafka.manager.common.entity.Result;
import com.xiaojukeji.kafka.manager.common.entity.ResultStatus;
import com.xiaojukeji.kafka.manager.common.entity.pojo.ha.HaASRelationDO;
import com.xiaojukeji.kafka.manager.common.utils.ValidateUtils;
import com.xiaojukeji.kafka.manager.dao.ha.HaASRelationDao;
import com.xiaojukeji.kafka.manager.service.service.ha.HaASRelationService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
@Service
public class HaASRelationServiceImpl implements HaASRelationService {
private static final Logger LOGGER = LoggerFactory.getLogger(HaASRelationServiceImpl.class);
@Autowired
private HaASRelationDao haASRelationDao;
@Override
public Result<Void> replaceTopicRelationsToDB(Long standbyClusterPhyId, List<HaASRelationDO> topicRelationDOList) {
try {
LambdaQueryWrapper<HaASRelationDO> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.eq(HaASRelationDO::getResType, HaResTypeEnum.TOPIC.getCode());
lambdaQueryWrapper.eq(HaASRelationDO::getStandbyClusterPhyId, standbyClusterPhyId);
Map<String, HaASRelationDO> dbRelationMap = haASRelationDao.selectList(lambdaQueryWrapper).stream().collect(Collectors.toMap(HaASRelationDO::getUniqueField, Function.identity()));
for (HaASRelationDO relationDO: topicRelationDOList) {
HaASRelationDO dbRelationDO = dbRelationMap.remove(relationDO.getUniqueField());
if (dbRelationDO == null) {
// DB中不存在则插入新的
haASRelationDao.insert(relationDO);
}
}
// dbRelationMap 中剩余的,是需要进行删除的
for (HaASRelationDO dbRelationDO: dbRelationMap.values()) {
if (System.currentTimeMillis() - dbRelationDO.getModifyTime().getTime() >= 5 * 1000L) {
// 修改时间超过了5分钟了则进行删除
haASRelationDao.deleteById(dbRelationDO.getId());
}
}
return Result.buildSuc();
} catch (Exception e) {
LOGGER.error("method=replaceTopicRelationsToDB||standbyClusterPhyId={}||errMsg=exception.", standbyClusterPhyId, e);
return Result.buildFromRSAndMsg(ResultStatus.MYSQL_ERROR, e.getMessage());
}
}
@Override
public Result<Void> addHAToDB(HaASRelationDO haASRelationDO) {
try{
int count = haASRelationDao.insert(haASRelationDO);
if (count < 1){
LOGGER.error("add ha to db failed! haASRelationDO:{}" , haASRelationDO);
return Result.buildFrom(ResultStatus.MYSQL_ERROR);
}
} catch (Exception e) {
LOGGER.error("add ha to db failed! haASRelationDO:{}" , haASRelationDO);
return Result.buildFrom(ResultStatus.MYSQL_ERROR);
}
return Result.buildSuc();
}
@Override
public Result<Void> deleteById(Long id) {
try {
haASRelationDao.deleteById(id);
} catch (Exception e){
LOGGER.error("class=HaASRelationServiceImpl||method=deleteById||id={}||errMsg=exception", id, e);
return Result.buildFrom(ResultStatus.MYSQL_ERROR);
}
return Result.buildSuc();
}
@Override
public int updateRelationStatus(Long relationId, Integer newStatus) {
return haASRelationDao.updateById(new HaASRelationDO(relationId, newStatus));
}
@Override
public int updateById(HaASRelationDO haASRelationDO) {
return haASRelationDao.updateById(haASRelationDO);
}
@Override
public HaASRelationDO getActiveClusterHAFromDB(Long activeClusterPhyId) {
LambdaQueryWrapper<HaASRelationDO> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.eq(HaASRelationDO::getActiveClusterPhyId, activeClusterPhyId);
lambdaQueryWrapper.eq(HaASRelationDO::getResType, HaResTypeEnum.CLUSTER.getCode());
return haASRelationDao.selectOne(lambdaQueryWrapper);
}
@Override
public HaASRelationDO getSpecifiedHAFromDB(Long activeClusterPhyId, String activeResName,
Long standbyClusterPhyId, String standbyResName,
HaResTypeEnum resTypeEnum) {
LambdaQueryWrapper<HaASRelationDO> lambdaQueryWrapper = new LambdaQueryWrapper<>();
HaASRelationDO relationDO = new HaASRelationDO(
activeClusterPhyId,
activeResName,
standbyClusterPhyId,
standbyResName,
resTypeEnum.getCode(),
HaStatusEnum.UNKNOWN.getCode()
);
lambdaQueryWrapper.eq(HaASRelationDO::getUniqueField, relationDO.getUniqueField());
return haASRelationDao.selectOne(lambdaQueryWrapper);
}
@Override
public HaASRelationDO getHAFromDB(Long firstClusterPhyId, String firstResName, HaResTypeEnum resTypeEnum) {
List<HaASRelationDO> haASRelationDOS = listAllHAFromDB(firstClusterPhyId, resTypeEnum);
for(HaASRelationDO haASRelationDO : haASRelationDOS){
if (haASRelationDO.getActiveResName().equals(firstResName)
|| haASRelationDO.getActiveResName().equals(firstResName)){
return haASRelationDO;
}
}
return null;
}
@Override
public List<HaASRelationDO> getStandbyHAFromDB(Long standbyClusterPhyId, HaResTypeEnum resTypeEnum) {
LambdaQueryWrapper<HaASRelationDO> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.eq(HaASRelationDO::getResType, resTypeEnum.getCode());
lambdaQueryWrapper.eq(HaASRelationDO::getStandbyClusterPhyId, standbyClusterPhyId);
return haASRelationDao.selectList(lambdaQueryWrapper);
}
@Override
public List<HaASRelationDO> getActiveHAFromDB(Long activeClusterPhyId, HaResTypeEnum resTypeEnum) {
LambdaQueryWrapper<HaASRelationDO> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.eq(HaASRelationDO::getResType, resTypeEnum.getCode());
lambdaQueryWrapper.eq(HaASRelationDO::getActiveClusterPhyId, activeClusterPhyId);
return haASRelationDao.selectList(lambdaQueryWrapper);
}
@Override
public List<HaASRelationDO> listAllHAFromDB(HaResTypeEnum resTypeEnum) {
LambdaQueryWrapper<HaASRelationDO> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.eq(HaASRelationDO::getResType, resTypeEnum.getCode());
return haASRelationDao.selectList(lambdaQueryWrapper);
}
@Override
public List<HaASRelationDO> listAllHAFromDB(Long firstClusterPhyId, HaResTypeEnum resTypeEnum) {
LambdaQueryWrapper<HaASRelationDO> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.eq(HaASRelationDO::getResType, resTypeEnum.getCode());
lambdaQueryWrapper.and(lambda ->
lambda.eq(HaASRelationDO::getActiveClusterPhyId, firstClusterPhyId).or().eq(HaASRelationDO::getStandbyClusterPhyId, firstClusterPhyId)
);
// 查询HA列表
List<HaASRelationDO> doList = haASRelationDao.selectList(lambdaQueryWrapper);
if (ValidateUtils.isNull(doList)) {
return new ArrayList<>();
}
return doList;
}
@Override
public List<HaASRelationDO> listAllHAFromDB(Long firstClusterPhyId, Long secondClusterPhyId, HaResTypeEnum resTypeEnum) {
// 查询HA列表
List<HaASRelationDO> doList = this.listAllHAFromDB(firstClusterPhyId, resTypeEnum);
if (ValidateUtils.isNull(doList)) {
return new ArrayList<>();
}
if (secondClusterPhyId == null) {
// 如果为null则直接返回全部
return doList;
}
// 手动过滤掉不需要的集群
return doList.stream()
.filter(elem -> elem.getActiveClusterPhyId().equals(secondClusterPhyId) || elem.getStandbyClusterPhyId().equals(secondClusterPhyId))
.collect(Collectors.toList());
}
}

View File

@@ -0,0 +1,190 @@
package com.xiaojukeji.kafka.manager.service.service.ha.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.xiaojukeji.kafka.manager.common.bizenum.ha.HaResTypeEnum;
import com.xiaojukeji.kafka.manager.common.bizenum.ha.job.HaJobStatusEnum;
import com.xiaojukeji.kafka.manager.common.entity.Result;
import com.xiaojukeji.kafka.manager.common.entity.ResultStatus;
import com.xiaojukeji.kafka.manager.common.entity.ao.ha.job.*;
import com.xiaojukeji.kafka.manager.common.entity.pojo.ha.HaASSwitchJobDO;
import com.xiaojukeji.kafka.manager.common.entity.pojo.ha.HaASSwitchSubJobDO;
import com.xiaojukeji.kafka.manager.common.utils.ConvertUtil;
import com.xiaojukeji.kafka.manager.common.utils.ValidateUtils;
import com.xiaojukeji.kafka.manager.dao.ha.HaASSwitchJobDao;
import com.xiaojukeji.kafka.manager.dao.ha.HaASSwitchSubJobDao;
import com.xiaojukeji.kafka.manager.service.service.ha.HaASSwitchJobService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
import java.util.*;
import java.util.stream.Collectors;
@Service
public class HaASSwitchJobServiceImpl implements HaASSwitchJobService {
private static final Logger LOGGER = LoggerFactory.getLogger(HaASSwitchJobServiceImpl.class);
@Autowired
private HaASSwitchJobDao haASSwitchJobDao;
@Autowired
private HaASSwitchSubJobDao haASSwitchSubJobDao;
@Override
@Transactional
public Result<Long> createJob(Long activeClusterPhyId, Long standbyClusterPhyId, List<String> topicNameList, String operator) {
try {
// 父任务
HaASSwitchJobDO jobDO = new HaASSwitchJobDO(activeClusterPhyId, standbyClusterPhyId, HaJobStatusEnum.RUNNING.getStatus(), operator);
haASSwitchJobDao.insert(jobDO);
// 子任务
for (String topicName: topicNameList) {
haASSwitchSubJobDao.insert(new HaASSwitchSubJobDO(
jobDO.getId(),
activeClusterPhyId,
topicName,
standbyClusterPhyId,
topicName,
HaResTypeEnum.TOPIC.getCode(),
HaJobStatusEnum.RUNNING.getStatus(),
""
));
}
return Result.buildSuc(jobDO.getId());
} catch (Exception e) {
LOGGER.error(
"method=createJob||activeClusterPhyId={}||standbyClusterPhyId={}||topicNameList={}||operator={}||errMsg=exception",
activeClusterPhyId, standbyClusterPhyId, ConvertUtil.obj2Json(topicNameList), operator, e
);
// 如果这一步出错了,则对上一步进行手动回滚
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
return Result.buildFromRSAndMsg(ResultStatus.MYSQL_ERROR, e.getMessage());
}
}
@Override
public int updateJobStatus(Long jobId, Integer jobStatus) {
HaASSwitchJobDO jobDO = new HaASSwitchJobDO();
jobDO.setId(jobId);
jobDO.setJobStatus(jobStatus);
return haASSwitchJobDao.updateById(jobDO);
}
@Override
public int updateSubJobStatus(Long subJobId, Integer jobStatus) {
HaASSwitchSubJobDO subJobDO = new HaASSwitchSubJobDO();
subJobDO.setId(subJobId);
subJobDO.setJobStatus(jobStatus);
return haASSwitchSubJobDao.updateById(subJobDO);
}
@Override
public int updateSubJobExtendData(Long subJobId, HaSubJobExtendData extendData) {
HaASSwitchSubJobDO subJobDO = new HaASSwitchSubJobDO();
subJobDO.setId(subJobId);
subJobDO.setExtendData(ConvertUtil.obj2Json(extendData));
return haASSwitchSubJobDao.updateById(subJobDO);
}
@Override
public Result<List<HaJobDetail>> jobDetail(Long jobId) {
LambdaQueryWrapper<HaASSwitchSubJobDO> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.eq(HaASSwitchSubJobDO::getJobId, jobId);
List<HaASSwitchSubJobDO> doList = haASSwitchSubJobDao.selectList(lambdaQueryWrapper);
if (ValidateUtils.isEmptyList(doList)) {
return Result.buildFromRSAndMsg(ResultStatus.RESOURCE_NOT_EXIST, String.format("jobId:[%d] 不存在", jobId));
}
List<HaJobDetail> detailList = new ArrayList<>();
doList.stream().forEach(elem -> {
HaJobDetail detail = new HaJobDetail();
detail.setTopicName(elem.getActiveResName());
detail.setActiveClusterPhyId(elem.getActiveClusterPhyId());
detail.setStandbyClusterPhyId(elem.getStandbyClusterPhyId());
detail.setStatus(elem.getJobStatus());
// Lag信息
HaSubJobExtendData extendData = ConvertUtil.str2ObjByJson(elem.getExtendData(), HaSubJobExtendData.class);
detail.setSumLag(extendData != null? extendData.getSumLag(): null);
detailList.add(detail);
});
return Result.buildSuc(detailList);
}
@Override
public List<Long> listRunningJobs(Long ignoreAfterTime) {
return new ArrayList<>(new HashSet<>(
this.listAfterTimeRunningJobs(ignoreAfterTime).values()
));
}
@Override
public Map<Long, HaASSwitchJobDO> listClusterLatestJobs() {
List<HaASSwitchJobDO> doList = haASSwitchJobDao.listAllLatest();
Map<Long, HaASSwitchJobDO> doMap = new HashMap<>();
for (HaASSwitchJobDO jobDO: doList) {
HaASSwitchJobDO inMapJobDO = doMap.get(jobDO.getActiveClusterPhyId());
if (inMapJobDO == null || inMapJobDO.getId() <= jobDO.getId()) {
doMap.put(jobDO.getActiveClusterPhyId(), jobDO);
}
inMapJobDO = doMap.get(jobDO.getStandbyClusterPhyId());
if (inMapJobDO == null || inMapJobDO.getId() <= jobDO.getId()) {
doMap.put(jobDO.getStandbyClusterPhyId(), jobDO);
}
}
return doMap;
}
@Override
public HaASSwitchJobDO getJobById(Long jobId) {
return haASSwitchJobDao.selectById(jobId);
}
@Override
public List<HaASSwitchSubJobDO> listSubJobsById(Long jobId) {
LambdaQueryWrapper<HaASSwitchSubJobDO> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.eq(HaASSwitchSubJobDO::getJobId, jobId);
return haASSwitchSubJobDao.selectList(lambdaQueryWrapper);
}
@Override
public List<HaASSwitchSubJobDO> listAll(Boolean isAsc) {
LambdaQueryWrapper<HaASSwitchSubJobDO> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.orderBy(isAsc != null, isAsc, HaASSwitchSubJobDO::getId);
return haASSwitchSubJobDao.selectList(lambdaQueryWrapper);
}
/**************************************************** private method ****************************************************/
private Map<Long, Long> listAfterTimeRunningJobs(Long ignoreAfterTime) {
LambdaQueryWrapper<HaASSwitchJobDO> jobLambdaQueryWrapper = new LambdaQueryWrapper<>();
jobLambdaQueryWrapper.eq(HaASSwitchJobDO::getJobStatus, HaJobStatusEnum.RUNNING.getStatus());
List<HaASSwitchJobDO> jobDOList = haASSwitchJobDao.selectList(jobLambdaQueryWrapper);
if (jobDOList == null) {
return new HashMap<>();
}
// 获取指定时间之前的任务
jobDOList = jobDOList.stream().filter(job -> job.getCreateTime().getTime() <= ignoreAfterTime).collect(Collectors.toList());
Map<Long, Long> clusterPhyIdAndJobIdMap = new HashMap<>();
jobDOList.forEach(elem -> {
clusterPhyIdAndJobIdMap.put(elem.getActiveClusterPhyId(), elem.getId());
clusterPhyIdAndJobIdMap.put(elem.getStandbyClusterPhyId(), elem.getId());
});
return clusterPhyIdAndJobIdMap;
}
}

View File

@@ -0,0 +1,389 @@
package com.xiaojukeji.kafka.manager.service.service.ha.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.xiaojukeji.kafka.manager.common.bizenum.ha.HaRelationTypeEnum;
import com.xiaojukeji.kafka.manager.common.bizenum.ha.HaResTypeEnum;
import com.xiaojukeji.kafka.manager.common.bizenum.ha.HaStatusEnum;
import com.xiaojukeji.kafka.manager.common.bizenum.ha.job.HaJobStatusEnum;
import com.xiaojukeji.kafka.manager.common.constant.KafkaConstant;
import com.xiaojukeji.kafka.manager.common.constant.MsgConstant;
import com.xiaojukeji.kafka.manager.common.entity.Result;
import com.xiaojukeji.kafka.manager.common.entity.ResultStatus;
import com.xiaojukeji.kafka.manager.common.entity.ao.ClusterDetailDTO;
import com.xiaojukeji.kafka.manager.common.entity.pojo.ClusterDO;
import com.xiaojukeji.kafka.manager.common.entity.pojo.ha.HaASRelationDO;
import com.xiaojukeji.kafka.manager.common.entity.pojo.ha.HaASSwitchJobDO;
import com.xiaojukeji.kafka.manager.common.entity.vo.ha.HaClusterVO;
import com.xiaojukeji.kafka.manager.common.utils.JsonUtils;
import com.xiaojukeji.kafka.manager.dao.ha.HaASRelationDao;
import com.xiaojukeji.kafka.manager.service.cache.PhysicalClusterMetadataManager;
import com.xiaojukeji.kafka.manager.service.service.ClusterService;
import com.xiaojukeji.kafka.manager.service.service.ZookeeperService;
import com.xiaojukeji.kafka.manager.service.service.ha.HaASRelationService;
import com.xiaojukeji.kafka.manager.service.service.ha.HaASSwitchJobService;
import com.xiaojukeji.kafka.manager.service.service.ha.HaClusterService;
import com.xiaojukeji.kafka.manager.service.service.ha.HaTopicService;
import com.xiaojukeji.kafka.manager.service.utils.ConfigUtils;
import com.xiaojukeji.kafka.manager.service.utils.HaClusterCommands;
import com.xiaojukeji.kafka.manager.service.utils.HaTopicCommands;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* 集群主备关系
*/
@Service
public class HaClusterServiceImpl implements HaClusterService {
private static final Logger LOGGER = LoggerFactory.getLogger(HaClusterServiceImpl.class);
@Autowired
private ClusterService clusterService;
@Autowired
private HaASRelationService haASRelationService;
@Autowired
private HaASRelationDao haActiveStandbyRelationDao;
@Autowired
private HaTopicService haTopicService;
@Autowired
private PhysicalClusterMetadataManager physicalClusterMetadataManager;
@Autowired
private HaASSwitchJobService haASSwitchJobService;
@Autowired
private ConfigUtils configUtils;
@Autowired
private ZookeeperService zookeeperService;
@Override
public Result<Void> createHA(Long activeClusterPhyId, Long standbyClusterPhyId, String operator) {
ClusterDO activeClusterDO = clusterService.getById(activeClusterPhyId);
if (activeClusterDO == null){
return Result.buildFrom(ResultStatus.CLUSTER_NOT_EXIST);
}
ClusterDO standbyClusterDO = clusterService.getById(standbyClusterPhyId);
if (standbyClusterDO == null){
return Result.buildFrom(ResultStatus.CLUSTER_NOT_EXIST);
}
HaASRelationDO oldRelationDO = getHA(activeClusterPhyId);
if (oldRelationDO != null){
return Result.buildFromRSAndMsg(ResultStatus.RESOURCE_ALREADY_USED,
MsgConstant.getActiveClusterDuplicate(activeClusterDO.getId(), activeClusterDO.getClusterName()));
}
//更新集群配置
Result<Void> rv = this.modifyHaClusterConfig(activeClusterDO, standbyClusterDO, operator);
if (rv.failed()){
return rv;
}
//更新__consumer_offsets配置
rv = this.modifyHaTopicConfig(activeClusterDO, standbyClusterDO, operator);
if (rv.failed()){
return rv;
}
//添加db数据
return haASRelationService.addHAToDB(
new HaASRelationDO(
activeClusterPhyId,
activeClusterPhyId.toString(),
standbyClusterPhyId,
standbyClusterPhyId.toString(),
HaResTypeEnum.CLUSTER.getCode(),
HaStatusEnum.STABLE.getCode()
)
);
}
@Override
public Result<Void> createHAInKafka(String zookeeper, ClusterDO needWriteToZKClusterDO, String operator) {
Properties props = new Properties();
props.putAll(getSecurityProperties(needWriteToZKClusterDO.getSecurityProperties()));
props.put(KafkaConstant.BOOTSTRAP_SERVERS, needWriteToZKClusterDO.getBootstrapServers());
props.put(KafkaConstant.DIDI_KAFKA_ENABLE, "false");
Result<List<Integer>> rli = zookeeperService.getBrokerIds(needWriteToZKClusterDO.getZookeeper());
if (rli.failed()){
return Result.buildFromIgnoreData(rli);
}
String kafkaVersion = physicalClusterMetadataManager.getKafkaVersion(needWriteToZKClusterDO.getId(), rli.getData());
if (kafkaVersion != null && kafkaVersion.contains("-d-")){
int dVersion = Integer.valueOf(kafkaVersion.split("-")[2]);
if (dVersion > 200){
props.put(KafkaConstant.DIDI_KAFKA_ENABLE, "true");
}
}
ResultStatus rs = HaClusterCommands.modifyHaClusterConfig(zookeeper, needWriteToZKClusterDO.getId(), props);
if (!ResultStatus.SUCCESS.equals(rs)) {
LOGGER.error("class=HaClusterServiceImpl||method=createHAInKafka||zookeeper={}||firstClusterDO={}||operator={}||msg=add ha-cluster config failed!", zookeeper, needWriteToZKClusterDO, operator);
return Result.buildFailure("add ha-cluster config failed");
}
return Result.buildFrom(rs);
}
@Override
public Result<Void> switchHA(Long newActiveClusterPhyId, Long newStandbyClusterPhyId) {
return Result.buildSuc();
}
@Override
public Result<Void> deleteHA(Long activeClusterPhyId, Long standbyClusterPhyId) {
ClusterDO clusterDO = clusterService.getById(activeClusterPhyId);
if (clusterDO == null){
return Result.buildFrom(ResultStatus.CLUSTER_NOT_EXIST);
}
ClusterDO standbyClusterDO = clusterService.getById(standbyClusterPhyId);
if (standbyClusterDO == null){
return Result.buildFrom(ResultStatus.CLUSTER_NOT_EXIST);
}
HaASRelationDO relationDO = getHA(activeClusterPhyId);
if (relationDO == null){
return Result.buildSuc();
}
//删除配置
Result delResult = delClusterHaConfig(clusterDO, standbyClusterDO);
if (delResult.failed()){
return delResult;
}
//删除db
Result delDbResult = delDBHaCluster(activeClusterPhyId, standbyClusterPhyId);
if (delDbResult.failed()){
return delDbResult;
}
return Result.buildSuc();
}
@Override
public HaASRelationDO getHA(Long activeClusterPhyId) {
return haASRelationService.getActiveClusterHAFromDB(activeClusterPhyId);
}
@Override
public Map<Long, Integer> getClusterHARelation() {
Map<Long, Integer> relationMap = new HashMap<>();
List<HaASRelationDO> haASRelationDOS = haASRelationService.listAllHAFromDB(HaResTypeEnum.CLUSTER);
if (haASRelationDOS.isEmpty()){
return relationMap;
}
haASRelationDOS.forEach(haASRelationDO -> {
relationMap.put(haASRelationDO.getActiveClusterPhyId(), HaRelationTypeEnum.ACTIVE.getCode());
relationMap.put(haASRelationDO.getStandbyClusterPhyId(), HaRelationTypeEnum.STANDBY.getCode());
});
return relationMap;
}
@Override
public Result<List<HaClusterVO>> listAllHA() {
//高可用集群
List<HaASRelationDO> clusterRelationDOS = haASRelationService.listAllHAFromDB(HaResTypeEnum.CLUSTER);
Map<Long, HaASRelationDO> activeMap = clusterRelationDOS.stream().collect(Collectors.toMap(HaASRelationDO::getActiveClusterPhyId, Function.identity()));
List<Long> standbyList = clusterRelationDOS.stream().map(HaASRelationDO::getStandbyClusterPhyId).collect(Collectors.toList());
//高可用topic
List<HaASRelationDO> topicRelationDOS = haASRelationService.listAllHAFromDB(HaResTypeEnum.TOPIC);
//主集群topic数
Map<Long,Long> activeTopicCountMap = topicRelationDOS.stream()
.filter(haASRelationDO -> !haASRelationDO.getActiveResName().startsWith("__"))
.collect(Collectors.groupingBy(HaASRelationDO::getActiveClusterPhyId, Collectors.counting()));
Map<Long,Long> standbyTopicCountMap = topicRelationDOS.stream()
.filter(haASRelationDO -> !haASRelationDO.getStandbyResName().startsWith("__"))
.collect(Collectors.groupingBy(HaASRelationDO::getStandbyClusterPhyId, Collectors.counting()));
//切换job
Map<Long/*集群ID*/, HaASSwitchJobDO> jobDOS = haASSwitchJobService.listClusterLatestJobs();
List<HaClusterVO> haClusterVOS = new ArrayList<>();
Map<Long,ClusterDetailDTO> clusterDetailDTOMap = clusterService.getClusterDetailDTOList(Boolean.TRUE).stream().collect(Collectors.toMap(ClusterDetailDTO::getClusterId, Function.identity()));
for (Map.Entry<Long,ClusterDetailDTO> entry : clusterDetailDTOMap.entrySet()){
ClusterDetailDTO clusterDetailDTO = entry.getValue();
//高可用集群
if (activeMap.containsKey(entry.getKey())){
//主集群
HaASRelationDO relationDO = activeMap.get(clusterDetailDTO.getClusterId());
HaClusterVO haClusterVO = new HaClusterVO();
BeanUtils.copyProperties(clusterDetailDTO,haClusterVO);
haClusterVO.setHaStatus(relationDO.getStatus());
haClusterVO.setActiveTopicCount(activeTopicCountMap.get(clusterDetailDTO.getClusterId())==null
?0L:activeTopicCountMap.get(clusterDetailDTO.getClusterId()));
haClusterVO.setStandbyTopicCount(standbyTopicCountMap.get(clusterDetailDTO.getClusterId())==null
?0L:standbyTopicCountMap.get(clusterDetailDTO.getClusterId()));
HaASSwitchJobDO jobDO = jobDOS.get(haClusterVO.getClusterId());
haClusterVO.setHaStatus(jobDO != null && HaJobStatusEnum.isRunning(jobDO.getJobStatus())
?HaStatusEnum.SWITCHING_CODE: HaStatusEnum.STABLE_CODE);
ClusterDetailDTO standbyClusterDetail = clusterDetailDTOMap.get(relationDO.getStandbyClusterPhyId());
if (standbyClusterDetail != null){
//备集群
HaClusterVO standbyCluster = new HaClusterVO();
BeanUtils.copyProperties(standbyClusterDetail,standbyCluster);
standbyCluster.setActiveTopicCount(activeTopicCountMap.get(standbyClusterDetail.getClusterId())==null
?0L:activeTopicCountMap.get(standbyClusterDetail.getClusterId()));
standbyCluster.setStandbyTopicCount(standbyTopicCountMap.get(standbyClusterDetail.getClusterId())==null
?0L:standbyTopicCountMap.get(standbyClusterDetail.getClusterId()));
standbyCluster.setHaASSwitchJobId(jobDO != null ? jobDO.getId() : null);
standbyCluster.setHaStatus(haClusterVO.getHaStatus());
haClusterVO.setHaClusterVO(standbyCluster);
}
haClusterVOS.add(haClusterVO);
}else if(!standbyList.contains(clusterDetailDTO.getClusterId())){
//普通集群
HaClusterVO haClusterVO = new HaClusterVO();
BeanUtils.copyProperties(clusterDetailDTO,haClusterVO);
haClusterVOS.add(haClusterVO);
}
}
return Result.buildSuc(haClusterVOS);
}
private Result<Void> modifyHaClusterConfig(ClusterDO activeClusterDO, ClusterDO standbyClusterDO, String operator){
//更新A集群配置信息
Result<Void> activeResult = createHAInKafka(activeClusterDO.getZookeeper(), standbyClusterDO, operator);
if (activeResult.failed()){
return activeResult;
}
//更新gateway上A集群的配置
Result<Void> activeGatewayResult = this.createHAInKafka(configUtils.getDKafkaGatewayZK(), activeClusterDO, operator);
if (activeGatewayResult.failed()){
return activeGatewayResult;
}
//更新B集群配置信息
Result<Void> standbyResult = this.createHAInKafka(standbyClusterDO.getZookeeper(), activeClusterDO, operator);
if (standbyResult.failed()){
return standbyResult;
}
//更新gateway上B集群的配置
Result<Void> standbyGatewayResult = this.createHAInKafka(configUtils.getDKafkaGatewayZK(), standbyClusterDO, operator);
if (standbyGatewayResult.failed()){
return activeGatewayResult;
}
return Result.buildSuc();
}
private Result<Void> modifyHaTopicConfig(ClusterDO activeClusterDO, ClusterDO standbyClusterDO, String operator){
//添加B集群拉取A集群offsets的配置信息
Result aResult = haTopicService.activeHAInKafkaNotCheck(activeClusterDO, KafkaConstant.COORDINATOR_TOPIC_NAME,
standbyClusterDO, KafkaConstant.COORDINATOR_TOPIC_NAME, operator);
if (aResult.failed()){
return aResult;
}
//添加A集群拉取B集群offsets的配置信息
return haTopicService.activeHAInKafkaNotCheck(standbyClusterDO, KafkaConstant.COORDINATOR_TOPIC_NAME,
activeClusterDO, KafkaConstant.COORDINATOR_TOPIC_NAME, operator);
}
private Result<Void> delClusterHaConfig(ClusterDO clusterDO, ClusterDO standbyClusterDO){
//删除A集群同步B集群Offset配置
ResultStatus resultStatus = HaTopicCommands.deleteHaTopicConfig(
clusterDO,
KafkaConstant.COORDINATOR_TOPIC_NAME,
Arrays.asList(KafkaConstant.DIDI_HA_REMOTE_CLUSTER, KafkaConstant.DIDI_HA_SYNC_TOPIC_CONFIGS_ENABLED)
);
if (resultStatus.getCode() != 0){
LOGGER.error("delete active cluster config failed! clusterId:{} standbyClusterId:{}" , clusterDO.getId(), standbyClusterDO.getId());
return Result.buildFailure("删除主集群__consumer_offsets高可用配置失败,请重试!");
}
//删除A集群配置信息
resultStatus = HaClusterCommands.coverHaClusterConfig(clusterDO.getZookeeper(), standbyClusterDO.getId(), new Properties());
if (resultStatus.getCode() != 0){
LOGGER.error("delete cluster config failed! clusterId:{} standbyClusterId:{}" , clusterDO.getId(), standbyClusterDO.getId());
return Result.buildFailure("删除主集群高可用配置失败,请重试!");
}
//删除B集群同步A集群Offset配置
resultStatus = HaTopicCommands.deleteHaTopicConfig(
standbyClusterDO,
KafkaConstant.COORDINATOR_TOPIC_NAME,
Arrays.asList(KafkaConstant.DIDI_HA_REMOTE_CLUSTER, KafkaConstant.DIDI_HA_SYNC_TOPIC_CONFIGS_ENABLED)
);
if (resultStatus.getCode() != 0){
LOGGER.error("delete standby cluster config failed! clusterId:{} standbyClusterId:{}" , clusterDO.getId(), standbyClusterDO.getId());
}
//删除B集群配置信息
resultStatus = HaClusterCommands.coverHaClusterConfig(standbyClusterDO.getZookeeper(), standbyClusterDO.getId(), new Properties());
if (resultStatus.getCode() != 0){
LOGGER.error("delete standby cluster config failed! clusterId:{} standbyClusterId:{}" , clusterDO.getId(), standbyClusterDO.getId());
}
//更新gateway中备集群配置信息
resultStatus = HaClusterCommands.coverHaClusterConfig(configUtils.getDKafkaGatewayZK(), standbyClusterDO.getId(), new Properties());
if (resultStatus.getCode() != 0){
LOGGER.error("delete spare gateway config failed! clusterId:{} standbyClusterId:{}" , clusterDO.getId(), standbyClusterDO.getId());
}
//删除gateway中A集群配置信息
resultStatus = HaClusterCommands.coverHaClusterConfig(configUtils.getDKafkaGatewayZK(), clusterDO.getId(), new Properties());
if (resultStatus.getCode() != 0){
LOGGER.error("delete host gateway config failed! clusterId:{} standbyClusterId:{}" , clusterDO.getId(), standbyClusterDO.getId());
}
return Result.buildSuc();
}
private Result delDBHaCluster(Long activeClusterPhyId, Long standbyClusterPhyId){
LambdaQueryWrapper<HaASRelationDO> topicQueryWrapper = new LambdaQueryWrapper();
topicQueryWrapper.eq(HaASRelationDO::getResType, HaResTypeEnum.TOPIC.getCode());
topicQueryWrapper.eq(HaASRelationDO::getActiveClusterPhyId, activeClusterPhyId);
List<HaASRelationDO> relationDOS = haActiveStandbyRelationDao.selectList(topicQueryWrapper);
if (!relationDOS.isEmpty()){
return Result.buildFrom(ResultStatus.HA_CLUSTER_DELETE_FORBIDDEN);
}
try {
LambdaQueryWrapper<HaASRelationDO> queryWrapper = new LambdaQueryWrapper();
queryWrapper.eq(HaASRelationDO::getActiveClusterPhyId, activeClusterPhyId);
queryWrapper.eq(HaASRelationDO::getResType, HaResTypeEnum.CLUSTER.getCode());
int count = haActiveStandbyRelationDao.delete(queryWrapper);
if (count < 1){
LOGGER.error("delete HA failed! clusterId:{} standbyClusterId:{}" , activeClusterPhyId, standbyClusterPhyId);
return Result.buildFrom(ResultStatus.MYSQL_ERROR);
}
}catch (Exception e){
LOGGER.error("delete HA failed! clusterId:{} standbyClusterId:{}" , activeClusterPhyId, standbyClusterPhyId);
return Result.buildFrom(ResultStatus.MYSQL_ERROR);
}
return Result.buildSuc();
}
private Properties getSecurityProperties(String securityPropertiesStr){
Properties securityProperties = new Properties();
if (StringUtils.isBlank(securityPropertiesStr)){
return securityProperties;
}
securityProperties.putAll(JsonUtils.stringToObj(securityPropertiesStr, Properties.class));
securityProperties.put(KafkaConstant.SASL_JAAS_CONFIG, securityProperties.getProperty(KafkaConstant.SASL_JAAS_CONFIG)==null
?"":securityProperties.getProperty(KafkaConstant.SASL_JAAS_CONFIG).replaceAll("\"","\\\\\""));
return securityProperties;
}
}

View File

@@ -0,0 +1,42 @@
package com.xiaojukeji.kafka.manager.service.service.ha.impl;
import com.xiaojukeji.kafka.manager.common.constant.KafkaConstant;
import com.xiaojukeji.kafka.manager.common.entity.Result;
import com.xiaojukeji.kafka.manager.common.entity.ResultStatus;
import com.xiaojukeji.kafka.manager.service.service.ha.HaKafkaUserService;
import com.xiaojukeji.kafka.manager.service.utils.HaKafkaUserCommands;
import org.springframework.stereotype.Service;
import java.util.Arrays;
import java.util.Properties;
@Service
public class HaKafkaUserServiceImpl implements HaKafkaUserService {
@Override
public Result<Void> setNoneHAInKafka(String zookeeper, String kafkaUser) {
Properties props = new Properties();
props.put(KafkaConstant.DIDI_HA_ACTIVE_CLUSTER, KafkaConstant.NONE);
return HaKafkaUserCommands.modifyHaUserConfig(zookeeper, kafkaUser, props)?
Result.buildSuc(): // 修改成功
Result.buildFrom(ResultStatus.ZOOKEEPER_OPERATE_FAILED); // 修改失败
}
@Override
public Result<Void> stopHAInKafka(String zookeeper, String kafkaUser) {
return HaKafkaUserCommands.deleteHaUserConfig(zookeeper, kafkaUser, Arrays.asList(KafkaConstant.DIDI_HA_ACTIVE_CLUSTER))?
Result.buildSuc(): // 修改成功
Result.buildFrom(ResultStatus.ZOOKEEPER_OPERATE_FAILED); // 修改失败
}
@Override
public Result<Void> activeHAInKafka(String zookeeper, Long activeClusterPhyId, String kafkaUser) {
Properties props = new Properties();
props.put(KafkaConstant.DIDI_HA_ACTIVE_CLUSTER, String.valueOf(activeClusterPhyId));
return HaKafkaUserCommands.modifyHaUserConfig(zookeeper, kafkaUser, props)?
Result.buildSuc(): // 修改成功
Result.buildFrom(ResultStatus.ZOOKEEPER_OPERATE_FAILED); // 修改失败
}
}

View File

@@ -0,0 +1,469 @@
package com.xiaojukeji.kafka.manager.service.service.ha.impl;
import com.xiaojukeji.kafka.manager.common.bizenum.ha.HaRelationTypeEnum;
import com.xiaojukeji.kafka.manager.common.bizenum.ha.HaResTypeEnum;
import com.xiaojukeji.kafka.manager.common.bizenum.ha.HaStatusEnum;
import com.xiaojukeji.kafka.manager.common.constant.Constant;
import com.xiaojukeji.kafka.manager.common.constant.KafkaConstant;
import com.xiaojukeji.kafka.manager.common.constant.MsgConstant;
import com.xiaojukeji.kafka.manager.common.constant.TopicCreationConstant;
import com.xiaojukeji.kafka.manager.common.entity.Result;
import com.xiaojukeji.kafka.manager.common.entity.ResultStatus;
import com.xiaojukeji.kafka.manager.common.entity.ao.gateway.TopicQuota;
import com.xiaojukeji.kafka.manager.common.entity.pojo.ClusterDO;
import com.xiaojukeji.kafka.manager.common.entity.pojo.TopicDO;
import com.xiaojukeji.kafka.manager.common.entity.pojo.gateway.AuthorityDO;
import com.xiaojukeji.kafka.manager.common.entity.pojo.ha.HaASRelationDO;
import com.xiaojukeji.kafka.manager.common.utils.ValidateUtils;
import com.xiaojukeji.kafka.manager.common.utils.jmx.JmxAttributeEnum;
import com.xiaojukeji.kafka.manager.common.utils.jmx.JmxConnectorWrap;
import com.xiaojukeji.kafka.manager.common.zookeeper.znode.brokers.PartitionState;
import com.xiaojukeji.kafka.manager.common.zookeeper.znode.brokers.TopicMetadata;
import com.xiaojukeji.kafka.manager.service.cache.PhysicalClusterMetadataManager;
import com.xiaojukeji.kafka.manager.service.service.AdminService;
import com.xiaojukeji.kafka.manager.service.service.ClusterService;
import com.xiaojukeji.kafka.manager.service.service.TopicManagerService;
import com.xiaojukeji.kafka.manager.service.service.gateway.AuthorityService;
import com.xiaojukeji.kafka.manager.service.service.gateway.QuotaService;
import com.xiaojukeji.kafka.manager.service.service.ha.HaASRelationService;
import com.xiaojukeji.kafka.manager.service.service.ha.HaKafkaUserService;
import com.xiaojukeji.kafka.manager.service.service.ha.HaTopicService;
import com.xiaojukeji.kafka.manager.service.utils.ConfigUtils;
import com.xiaojukeji.kafka.manager.service.utils.HaTopicCommands;
import com.xiaojukeji.kafka.manager.service.utils.KafkaZookeeperUtils;
import com.xiaojukeji.kafka.manager.service.utils.TopicCommands;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
import javax.management.Attribute;
import javax.management.ObjectName;
import java.util.*;
import java.util.stream.Collectors;
@Service
public class HaTopicServiceImpl implements HaTopicService {
private static final Logger LOGGER = LoggerFactory.getLogger(HaTopicServiceImpl.class);
@Autowired
private ClusterService clusterService;
@Autowired
private QuotaService quotaService;
@Autowired
private AdminService adminService;
@Autowired
private HaASRelationService haASRelationService;
@Autowired
private AuthorityService authorityService;
@Autowired
private HaKafkaUserService haKafkaUserService;
@Autowired
private ConfigUtils configUtils;
@Autowired
private TopicManagerService topicManagerService;
@Override
public Result<Void> createHA(Long activeClusterPhyId, Long standbyClusterPhyId, String topicName, String operator) {
ClusterDO activeClusterDO = PhysicalClusterMetadataManager.getClusterFromCache(activeClusterPhyId);
if (activeClusterDO == null) {
return Result.buildFromRSAndMsg(ResultStatus.CLUSTER_NOT_EXIST, "主集群不存在");
}
ClusterDO standbyClusterDO = PhysicalClusterMetadataManager.getClusterFromCache(standbyClusterPhyId);
if (standbyClusterDO == null) {
return Result.buildFromRSAndMsg(ResultStatus.CLUSTER_NOT_EXIST, "备集群不存在");
}
// 查询关系是否已经存在
HaASRelationDO relationDO = haASRelationService.getSpecifiedHAFromDB(
activeClusterPhyId,
topicName,
standbyClusterPhyId,
topicName,
HaResTypeEnum.TOPIC
);
if (relationDO != null) {
// 如果已存在该高可用Topic则直接返回成功
return Result.buildSuc();
}
Result<TopicDO> checkResult = this.checkHaTopicAndGetBizInfo(activeClusterPhyId, standbyClusterPhyId, topicName);
if (checkResult.failed()){
return Result.buildFromIgnoreData(checkResult);
}
//更新高可用Topic配置
Result<Void> rv = this.modifyHaConfig(
activeClusterDO,
topicName,
standbyClusterDO,
topicName,
operator
);
if (rv.failed()){
return rv;
}
// 新增备Topic
rv = this.addStandbyTopic(checkResult.getData(), activeClusterDO, standbyClusterDO, operator);
if (rv.failed()) {
return rv;
}
// 备topic添加权限以及quota
rv = this.addStandbyTopicAuthorityAndQuota(activeClusterPhyId, standbyClusterPhyId, topicName);
if (rv.failed()){
return rv;
}
//添加db业务信息
return haASRelationService.addHAToDB(
new HaASRelationDO(
activeClusterPhyId,
topicName,
standbyClusterPhyId,
topicName,
HaResTypeEnum.TOPIC.getCode(),
HaStatusEnum.STABLE.getCode()
)
);
}
private Result<Void> addStandbyTopic(TopicDO activeTopicDO, ClusterDO activeClusterDO, ClusterDO standbyClusterDO, String operator){
// 获取主Topic配置信息
Properties activeTopicProps = TopicCommands.fetchTopicConfig(activeClusterDO, activeTopicDO.getTopicName());
if (activeTopicProps == null){
return Result.buildFromRSAndMsg(ResultStatus.FAIL, "创建备Topic时获取主Topic配置失败");
}
TopicDO newTopicDO = new TopicDO(
activeTopicDO.getAppId(),
standbyClusterDO.getId(),
activeTopicDO.getTopicName(),
activeTopicDO.getDescription(),
TopicCreationConstant.DEFAULT_QUOTA
);
TopicMetadata topicMetadata = PhysicalClusterMetadataManager.getTopicMetadata(activeClusterDO.getId(), activeTopicDO.getTopicName());
ResultStatus rs = adminService.createTopic(standbyClusterDO,
newTopicDO,
topicMetadata.getPartitionNum(),
topicMetadata.getReplicaNum(),
null,
PhysicalClusterMetadataManager.getBrokerIdList(standbyClusterDO.getId()),
activeTopicProps,
operator,
operator
);
if (ResultStatus.SUCCESS.equals(rs)) {
LOGGER.error(
"method=createHA||activeClusterPhyId={}||standbyClusterPhyId={}||activeTopicDO={}||result={}||msg=create haTopic create topic failed.",
activeClusterDO.getId(), standbyClusterDO.getId(), activeTopicDO, rs
);
return Result.buildFromRSAndMsg(rs, String.format("创建备Topic失败原因%s", rs.getMessage()));
}
return Result.buildSuc();
}
@Override
public Result<Void> activeHAInKafka(ClusterDO activeClusterDO, String activeTopicName, ClusterDO standbyClusterDO, String standbyTopicName, String operator) {
if (!PhysicalClusterMetadataManager.isTopicExist(activeClusterDO.getId(), activeTopicName)) {
// 主Topic不存在
return Result.buildFrom(ResultStatus.TOPIC_NOT_EXIST);
}
if (!PhysicalClusterMetadataManager.isTopicExist(standbyClusterDO.getId(), standbyTopicName)) {
// 备Topic不存在
return Result.buildFrom(ResultStatus.TOPIC_NOT_EXIST);
}
return this.activeTopicHAConfigInKafka(activeClusterDO, activeTopicName, standbyClusterDO, standbyTopicName);
}
@Override
public Result<Void> activeHAInKafkaNotCheck(ClusterDO activeClusterDO, String activeTopicName, ClusterDO standbyClusterDO, String standbyTopicName, String operator) {
//更新开启topic高可用配置并将备集群的配置信息指向主集群
Result<Void> rv = activeTopicHAConfigInKafka(activeClusterDO, activeTopicName, standbyClusterDO, standbyTopicName);
if (rv.failed()){
return rv;
}
return Result.buildSuc();
}
@Override
@Transactional
public Result<Void> deleteHA(Long activeClusterPhyId, Long standbyClusterPhyId, String topicName, String operator) {
ClusterDO activeClusterDO = clusterService.getById(activeClusterPhyId);
if (activeClusterDO == null){
return Result.buildFromRSAndMsg(ResultStatus.CLUSTER_NOT_EXIST, "主集群不存在");
}
ClusterDO standbyClusterDO = clusterService.getById(standbyClusterPhyId);
if (standbyClusterDO == null){
return Result.buildFromRSAndMsg(ResultStatus.CLUSTER_NOT_EXIST, "备集群不存在");
}
HaASRelationDO relationDO = haASRelationService.getHAFromDB(
activeClusterPhyId,
topicName,
HaResTypeEnum.TOPIC
);
if (relationDO == null) {
return Result.buildFromRSAndMsg(ResultStatus.RESOURCE_NOT_EXIST, "主备关系不存在");
}
if (!relationDO.getStatus().equals(HaStatusEnum.STABLE_CODE)) {
return Result.buildFromRSAndMsg(ResultStatus.OPERATION_FORBIDDEN, "主备切换中,不允许解绑");
}
// 删除高可用配置信息
Result<Void> rv = this.stopHAInKafka(standbyClusterDO, topicName, operator);
if(rv.failed()){
return rv;
}
rv = haASRelationService.deleteById(relationDO.getId());
if(rv.failed()){
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
return rv;
}
return rv;
}
@Override
public Result<Void> stopHAInKafka(ClusterDO standbyClusterDO, String standbyTopicName, String operator) {
//删除副集群同步主集群topic配置
ResultStatus rs = HaTopicCommands.deleteHaTopicConfig(
standbyClusterDO,
standbyTopicName,
Arrays.asList(KafkaConstant.DIDI_HA_SYNC_TOPIC_CONFIGS_ENABLED, KafkaConstant.DIDI_HA_REMOTE_CLUSTER)
);
if (!ResultStatus.SUCCESS.equals(rs)) {
LOGGER.error(
"method=deleteHAInKafka||standbyClusterId={}||standbyTopicName={}||rs={}||msg=delete topic ha failed.",
standbyClusterDO.getId(), standbyTopicName, rs
);
return Result.buildFromRSAndMsg(rs, "delete topic ha failed");
}
return Result.buildSuc();
}
@Override
public Map<String, Integer> getRelation(Long clusterId) {
Map<String, Integer> relationMap = new HashMap<>();
List<HaASRelationDO> relationDOS = haASRelationService.listAllHAFromDB(clusterId, HaResTypeEnum.TOPIC);
if (relationDOS.isEmpty()){
return relationMap;
}
//主topic
List<String> activeTopics = relationDOS.stream().filter(haASRelationDO -> haASRelationDO.getActiveClusterPhyId().equals(clusterId)).map(HaASRelationDO::getActiveResName).collect(Collectors.toList());
activeTopics.stream().forEach(topicName -> relationMap.put(topicName, HaRelationTypeEnum.ACTIVE.getCode()));
//备topic
List<String> standbyTopics = relationDOS.stream().filter(haASRelationDO -> haASRelationDO.getStandbyClusterPhyId().equals(clusterId)).map(HaASRelationDO::getStandbyResName).collect(Collectors.toList());
standbyTopics.stream().forEach(topicName -> relationMap.put(topicName, HaRelationTypeEnum.STANDBY.getCode()));
//互备
relationMap.put(KafkaConstant.COORDINATOR_TOPIC_NAME, HaRelationTypeEnum.MUTUAL_BACKUP.getCode());
return relationMap;
}
@Override
public Map<Long, List<String>> getClusterStandbyTopicMap() {
Map<Long, List<String>> clusterStandbyTopicMap = new HashMap<>();
List<HaASRelationDO> relationDOS = haASRelationService.listAllHAFromDB(HaResTypeEnum.TOPIC);
if (relationDOS.isEmpty()){
return clusterStandbyTopicMap;
}
return relationDOS.stream().collect(Collectors.groupingBy(HaASRelationDO::getStandbyClusterPhyId, Collectors.mapping(HaASRelationDO::getStandbyResName, Collectors.toList())));
}
@Override
public Result<Void> activeUserHAInKafka(ClusterDO activeClusterDO, ClusterDO standbyClusterDO, String kafkaUser, String operator) {
Result<Void> rv;
rv = haKafkaUserService.activeHAInKafka(activeClusterDO.getZookeeper(), activeClusterDO.getId(), kafkaUser);
if (rv.failed()) {
return rv;
}
rv = haKafkaUserService.activeHAInKafka(standbyClusterDO.getZookeeper(), activeClusterDO.getId(), kafkaUser);
if (rv.failed()) {
return rv;
}
rv = haKafkaUserService.activeHAInKafka(configUtils.getDKafkaGatewayZK(), activeClusterDO.getId(), kafkaUser);
if (rv.failed()) {
return rv;
}
return rv;
}
@Override
public Result<Long> getStandbyTopicFetchLag(Long standbyClusterPhyId, String topicName) {
TopicMetadata metadata = PhysicalClusterMetadataManager.getTopicMetadata(standbyClusterPhyId, topicName);
if (metadata == null) {
return Result.buildFromRSAndMsg(ResultStatus.RESOURCE_NOT_EXIST, MsgConstant.getTopicNotExist(standbyClusterPhyId, topicName));
}
List<Integer> partitionIdList = new ArrayList<>(metadata.getPartitionMap().getPartitions().keySet());
List<PartitionState> partitionStateList = KafkaZookeeperUtils.getTopicPartitionState(
PhysicalClusterMetadataManager.getZKConfig(standbyClusterPhyId),
topicName,
partitionIdList
);
if (partitionStateList.size() != partitionIdList.size()) {
return Result.buildFromRSAndMsg(ResultStatus.ZOOKEEPER_READ_FAILED, "读取ZK的分区元信息失败");
}
Long sumLag = 0L;
for (Integer leaderBrokerId: partitionStateList.stream().map(elem -> elem.getLeader()).collect(Collectors.toSet())) {
JmxConnectorWrap jmxConnectorWrap = PhysicalClusterMetadataManager.getJmxConnectorWrap(standbyClusterPhyId, leaderBrokerId);
if (jmxConnectorWrap == null || !jmxConnectorWrap.checkJmxConnectionAndInitIfNeed()) {
return Result.buildFromRSAndMsg(ResultStatus.OPERATION_FAILED, String.format("获取BrokerId=%d的jmx客户端失败", leaderBrokerId));
}
try {
ObjectName objectName = new ObjectName(
"kafka.server:type=FetcherLagMetrics,name=ConsumerLag,clientId=MirrorFetcherThread-*" + "-" + standbyClusterPhyId + "*" + ",topic=" + topicName + ",partition=*"
);
Set<ObjectName> objectNameSet = jmxConnectorWrap.queryNames(objectName, null);
for (ObjectName name: objectNameSet) {
List<Attribute> attributeList = jmxConnectorWrap.getAttributes(name, JmxAttributeEnum.VALUE_ATTRIBUTE.getAttribute()).asList();
for (Attribute attribute: attributeList) {
sumLag += Long.valueOf(attribute.getValue().toString());
}
}
} catch (Exception e) {
LOGGER.error(
"class=HaTopicServiceImpl||method=getStandbyTopicFetchLag||standbyClusterPhyId={}||topicName={}||leaderBrokerId={}||errMsg=exception.",
standbyClusterPhyId, topicName, leaderBrokerId, e
);
return Result.buildFromRSAndMsg(ResultStatus.OPERATION_FAILED, e.getMessage());
}
}
return Result.buildSuc(sumLag);
}
/**************************************************** private method ****************************************************/
private Result<Void> activeTopicHAConfigInKafka(ClusterDO activeClusterDO, String activeTopicName, ClusterDO standbyClusterDO, String standbyTopicName) {
//更新ha-topic配置
Properties standbyTopicProps = new Properties();
standbyTopicProps.put(KafkaConstant.DIDI_HA_SYNC_TOPIC_CONFIGS_ENABLED, Boolean.TRUE.toString());
standbyTopicProps.put(KafkaConstant.DIDI_HA_REMOTE_CLUSTER, activeClusterDO.getId().toString());
if (!activeTopicName.equals(standbyTopicName)) {
standbyTopicProps.put(KafkaConstant.DIDI_HA_REMOTE_TOPIC, activeTopicName);
}
ResultStatus rs = HaTopicCommands.modifyHaTopicConfig(standbyClusterDO, standbyTopicName, standbyTopicProps);
if (!ResultStatus.SUCCESS.equals(rs)) {
LOGGER.error(
"method=createHAInKafka||activeClusterId={}||activeTopicName={}||standbyClusterId={}||standbyTopicName={}||rs={}||msg=create topic ha failed.",
activeClusterDO.getId(), activeTopicName, standbyClusterDO.getId(), standbyTopicName, rs
);
return Result.buildFromRSAndMsg(rs, "modify ha topic config failed");
}
return Result.buildSuc();
}
public Result<Void> addStandbyTopicAuthorityAndQuota(Long activeClusterPhyId, Long standbyClusterPhyId, String topicName) {
List<AuthorityDO> authorityDOS = authorityService.getAuthorityByTopic(activeClusterPhyId, topicName);
try {
for (AuthorityDO authorityDO : authorityDOS) {
//权限
AuthorityDO newAuthorityDO = new AuthorityDO();
newAuthorityDO.setAppId(authorityDO.getAppId());
newAuthorityDO.setClusterId(standbyClusterPhyId);
newAuthorityDO.setTopicName(topicName);
newAuthorityDO.setAccess(authorityDO.getAccess());
//quota
TopicQuota activeTopicQuotaDO = quotaService.getQuotaFromZk(
activeClusterPhyId,
topicName,
authorityDO.getAppId()
);
TopicQuota standbyTopicQuotaDO = new TopicQuota();
standbyTopicQuotaDO.setTopicName(topicName);
standbyTopicQuotaDO.setAppId(activeTopicQuotaDO.getAppId());
standbyTopicQuotaDO.setClusterId(standbyClusterPhyId);
standbyTopicQuotaDO.setConsumeQuota(activeTopicQuotaDO.getConsumeQuota());
standbyTopicQuotaDO.setProduceQuota(activeTopicQuotaDO.getProduceQuota());
int result = authorityService.addAuthorityAndQuota(newAuthorityDO, standbyTopicQuotaDO);
if (Constant.INVALID_CODE == result){
return Result.buildFrom(ResultStatus.OPERATION_FAILED);
}
}
} catch (Exception e) {
LOGGER.error(
"method=addStandbyTopicAuthorityAndQuota||activeClusterPhyId={}||standbyClusterPhyId={}||topicName={}||errMsg=exception.",
activeClusterPhyId, standbyClusterPhyId, topicName, e
);
return Result.buildFailure("备Topic复制主Topic权限及配额失败");
}
return Result.buildSuc();
}
private Result<TopicDO> checkHaTopicAndGetBizInfo(Long activeClusterPhyId, Long standbyClusterPhyId, String topicName){
if (PhysicalClusterMetadataManager.isTopicExist(standbyClusterPhyId, topicName)) {
return Result.buildFromRSAndMsg(ResultStatus.TOPIC_ALREADY_EXIST, "备集群已存在该Topic请先删除,再行绑定!");
}
if (!PhysicalClusterMetadataManager.isTopicExist(activeClusterPhyId, topicName)) {
return Result.buildFromRSAndMsg(ResultStatus.TOPIC_NOT_EXIST, "主集群不存在该Topic");
}
TopicDO topicDO = topicManagerService.getByTopicName(activeClusterPhyId, topicName);
if (ValidateUtils.isNull(topicDO)) {
return Result.buildFromRSAndMsg(ResultStatus.RESOURCE_NOT_EXIST, "主集群Topic所属KafkaUser信息不存在");
}
return Result.buildSuc(topicDO);
}
private Result<Void> modifyHaConfig(ClusterDO activeClusterDO, String activeTopic, ClusterDO standbyClusterDO, String standbyTopic, String operator){
//更新副集群同步主集群topic配置
Result<Void> rv = activeHAInKafkaNotCheck(activeClusterDO, activeTopic, standbyClusterDO, standbyTopic, operator);
if (rv.failed()){
LOGGER.error("method=createHA||activeTopic:{} standbyTopic:{}||msg=create haTopic modify standby topic config failed!.", activeTopic, standbyTopic);
return Result.buildFailure("modify standby topic config failed,please try again");
}
//更新user配置通知用户指向主集群
Set<String> relatedKafkaUserSet = authorityService.getAuthorityByTopic(activeClusterDO.getId(), activeTopic)
.stream()
.map(elem -> elem.getAppId())
.collect(Collectors.toSet());
for(String kafkaUser: relatedKafkaUserSet) {
rv = this.activeUserHAInKafka(activeClusterDO, standbyClusterDO, kafkaUser, operator);
if (rv.failed()) {
return rv;
}
}
return Result.buildSuc();
}
}

View File

@@ -2,21 +2,27 @@ package com.xiaojukeji.kafka.manager.service.service.impl;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.xiaojukeji.kafka.manager.common.bizenum.*;
import com.xiaojukeji.kafka.manager.common.entity.pojo.OperateRecordDO;
import com.xiaojukeji.kafka.manager.common.entity.pojo.gateway.AuthorityDO;
import com.xiaojukeji.kafka.manager.common.entity.ao.gateway.TopicQuota;
import com.xiaojukeji.kafka.manager.common.bizenum.ModuleEnum;
import com.xiaojukeji.kafka.manager.common.bizenum.OperateEnum;
import com.xiaojukeji.kafka.manager.common.bizenum.TaskStatusEnum;
import com.xiaojukeji.kafka.manager.common.bizenum.TopicAuthorityEnum;
import com.xiaojukeji.kafka.manager.common.constant.Constant;
import com.xiaojukeji.kafka.manager.common.entity.ResultStatus;
import com.xiaojukeji.kafka.manager.common.entity.ao.gateway.TopicQuota;
import com.xiaojukeji.kafka.manager.common.entity.pojo.ClusterDO;
import com.xiaojukeji.kafka.manager.common.entity.pojo.OperateRecordDO;
import com.xiaojukeji.kafka.manager.common.entity.pojo.TopicDO;
import com.xiaojukeji.kafka.manager.common.entity.pojo.gateway.AuthorityDO;
import com.xiaojukeji.kafka.manager.common.entity.pojo.ha.HaASRelationDO;
import com.xiaojukeji.kafka.manager.common.utils.ValidateUtils;
import com.xiaojukeji.kafka.manager.common.zookeeper.ZkConfigImpl;
import com.xiaojukeji.kafka.manager.common.zookeeper.znode.brokers.BrokerMetadata;
import com.xiaojukeji.kafka.manager.common.entity.pojo.ClusterDO;
import com.xiaojukeji.kafka.manager.common.entity.pojo.TopicDO;
import com.xiaojukeji.kafka.manager.common.zookeeper.znode.brokers.TopicMetadata;
import com.xiaojukeji.kafka.manager.service.biz.ha.HaASRelationManager;
import com.xiaojukeji.kafka.manager.service.cache.PhysicalClusterMetadataManager;
import com.xiaojukeji.kafka.manager.service.service.*;
import com.xiaojukeji.kafka.manager.service.service.gateway.AuthorityService;
import com.xiaojukeji.kafka.manager.service.service.ha.HaTopicService;
import com.xiaojukeji.kafka.manager.service.utils.KafkaZookeeperUtils;
import com.xiaojukeji.kafka.manager.service.utils.TopicCommands;
import kafka.admin.AdminOperationException;
@@ -55,6 +61,12 @@ public class AdminServiceImpl implements AdminService {
@Autowired
private AuthorityService authorityService;
@Autowired
private HaTopicService haTopicService;
@Autowired
private HaASRelationManager haASRelationManager;
@Autowired
private OperateRecordService operateRecordService;
@@ -123,15 +135,22 @@ public class AdminServiceImpl implements AdminService {
}
@Override
public ResultStatus deleteTopic(ClusterDO clusterDO,
String topicName,
String operator) {
// 1. 集群中删除topic
public ResultStatus deleteTopic(ClusterDO clusterDO, String topicName, String operator) {
// 1. 若存在高可用topic先解除高可用关系才能删除topic
HaASRelationDO haASRelationDO = haASRelationManager.getASRelation(clusterDO.getId(), topicName);
if (haASRelationDO != null){
//高可用topic不允许删除
if (haASRelationDO.getStandbyClusterPhyId().equals(clusterDO.getId())){
return ResultStatus.HA_TOPIC_DELETE_FORBIDDEN;
}
}
// 2. 集群中删除topic
ResultStatus rs = TopicCommands.deleteTopic(clusterDO, topicName);
if (!ResultStatus.SUCCESS.equals(rs)) {
return rs;
}
// 2. 记录操作
// 3. 记录操作
Map<String, Object> content = new HashMap<>(2);
content.put("clusterId", clusterDO.getId());
content.put("topicName", topicName);
@@ -144,12 +163,13 @@ public class AdminServiceImpl implements AdminService {
operateRecordDO.setOperator(operator);
operateRecordService.insert(operateRecordDO);
// 3. 数据库中删除topic
// 4. 数据库中删除topic
topicManagerService.deleteByTopicName(clusterDO.getId(), topicName);
topicExpiredService.deleteByTopicName(clusterDO.getId(), topicName);
// 4. 数据库中删除authority
// 5. 数据库中删除authority
authorityService.deleteAuthorityByTopic(clusterDO.getId(), topicName);
return rs;
}
@@ -346,7 +366,6 @@ public class AdminServiceImpl implements AdminService {
@Override
public ResultStatus modifyTopicConfig(ClusterDO clusterDO, String topicName, Properties properties, String operator) {
ResultStatus rs = TopicCommands.modifyTopicConfig(clusterDO, topicName, properties);
return rs;
return TopicCommands.modifyTopicConfig(clusterDO, topicName, properties);
}
}

View File

@@ -3,13 +3,16 @@ package com.xiaojukeji.kafka.manager.service.service.impl;
import com.xiaojukeji.kafka.manager.common.bizenum.DBStatusEnum;
import com.xiaojukeji.kafka.manager.common.bizenum.ModuleEnum;
import com.xiaojukeji.kafka.manager.common.bizenum.OperateEnum;
import com.xiaojukeji.kafka.manager.common.bizenum.ha.HaRelationTypeEnum;
import com.xiaojukeji.kafka.manager.common.bizenum.ha.HaResTypeEnum;
import com.xiaojukeji.kafka.manager.common.entity.Result;
import com.xiaojukeji.kafka.manager.common.entity.ResultStatus;
import com.xiaojukeji.kafka.manager.common.entity.ao.ClusterDetailDTO;
import com.xiaojukeji.kafka.manager.common.entity.ao.cluster.ControllerPreferredCandidate;
import com.xiaojukeji.kafka.manager.common.entity.pojo.*;
import com.xiaojukeji.kafka.manager.common.entity.pojo.ha.HaASRelationDO;
import com.xiaojukeji.kafka.manager.common.entity.vo.normal.cluster.ClusterNameDTO;
import com.xiaojukeji.kafka.manager.common.utils.ListUtils;
import com.xiaojukeji.kafka.manager.common.entity.pojo.*;
import com.xiaojukeji.kafka.manager.common.utils.ValidateUtils;
import com.xiaojukeji.kafka.manager.common.zookeeper.znode.brokers.BrokerMetadata;
import com.xiaojukeji.kafka.manager.dao.ClusterDao;
@@ -18,15 +21,16 @@ import com.xiaojukeji.kafka.manager.dao.ControllerDao;
import com.xiaojukeji.kafka.manager.service.cache.LogicalClusterMetadataManager;
import com.xiaojukeji.kafka.manager.service.cache.PhysicalClusterMetadataManager;
import com.xiaojukeji.kafka.manager.service.service.*;
import com.xiaojukeji.kafka.manager.service.service.ha.HaASRelationService;
import com.xiaojukeji.kafka.manager.service.service.ha.HaClusterService;
import com.xiaojukeji.kafka.manager.service.utils.ConfigUtils;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
@@ -42,6 +46,9 @@ public class ClusterServiceImpl implements ClusterService {
@Autowired
private ClusterDao clusterDao;
@Autowired
private HaClusterService haClusterService;
@Autowired
private ClusterMetricsDao clusterMetricsDao;
@@ -69,6 +76,9 @@ public class ClusterServiceImpl implements ClusterService {
@Autowired
private OperateRecordService operateRecordService;
@Autowired
private HaASRelationService haASRelationService;
@Override
public ResultStatus addNew(ClusterDO clusterDO, String operator) {
if (ValidateUtils.isNull(clusterDO) || ValidateUtils.isNull(operator)) {
@@ -96,6 +106,7 @@ public class ClusterServiceImpl implements ClusterService {
LOGGER.error("add new cluster failed, operate mysql failed, clusterDO:{}.", clusterDO, e);
return ResultStatus.MYSQL_ERROR;
}
physicalClusterMetadataManager.addNew(clusterDO);
return ResultStatus.SUCCESS;
}
@@ -253,9 +264,11 @@ public class ClusterServiceImpl implements ClusterService {
Map<Long, Integer> consumerGroupNumMap =
needDetail? consumerService.getConsumerGroupNumMap(doList): new HashMap<>(0);
Map<Long, Integer> haRelationMap = haClusterService.getClusterHARelation();
List<ClusterDetailDTO> dtoList = new ArrayList<>();
for (ClusterDO clusterDO: doList) {
ClusterDetailDTO dto = getClusterDetailDTO(clusterDO, needDetail);
dto.setHaRelation(haRelationMap.get(clusterDO.getId()));
dto.setConsumerGroupNum(consumerGroupNumMap.get(clusterDO.getId()));
dto.setRegionNum(regionNumMap.get(clusterDO.getId()));
dtoList.add(dto);
@@ -281,10 +294,11 @@ public class ClusterServiceImpl implements ClusterService {
}
@Override
public ResultStatus deleteById(Long clusterId, String operator) {
@Transactional
public Result<Void> deleteById(Long clusterId, String operator) {
List<RegionDO> regionDOList = regionService.getByClusterId(clusterId);
if (!ValidateUtils.isEmptyList(regionDOList)) {
return ResultStatus.OPERATION_FORBIDDEN;
return Result.buildFrom(ResultStatus.OPERATION_FORBIDDEN);
}
try {
Map<String, String> content = new HashMap<>();
@@ -292,13 +306,14 @@ public class ClusterServiceImpl implements ClusterService {
operateRecordService.insert(operator, ModuleEnum.CLUSTER, String.valueOf(clusterId), OperateEnum.DELETE, content);
if (clusterDao.deleteById(clusterId) <= 0) {
LOGGER.error("delete cluster failed, clusterId:{}.", clusterId);
return ResultStatus.MYSQL_ERROR;
return Result.buildFrom(ResultStatus.MYSQL_ERROR);
}
} catch (Exception e) {
LOGGER.error("delete cluster failed, clusterId:{}.", clusterId, e);
return ResultStatus.MYSQL_ERROR;
return Result.buildFrom(ResultStatus.MYSQL_ERROR);
}
return ResultStatus.SUCCESS;
return Result.buildSuc();
}
private ClusterDetailDTO getClusterDetailDTO(ClusterDO clusterDO, Boolean needDetail) {
@@ -318,6 +333,21 @@ public class ClusterServiceImpl implements ClusterService {
dto.setStatus(clusterDO.getStatus());
dto.setGmtCreate(clusterDO.getGmtCreate());
dto.setGmtModify(clusterDO.getGmtModify());
List<HaASRelationDO> haASRelationDOS = haASRelationService
.listAllHAFromDB(clusterDO.getId(), HaResTypeEnum.CLUSTER);
if (!haASRelationDOS.isEmpty()){
ClusterDO mbCluster;
if (haASRelationDOS.get(0).getActiveClusterPhyId().equals(clusterDO.getId())){
dto.setHaRelation(HaRelationTypeEnum.ACTIVE.getCode());
mbCluster = PhysicalClusterMetadataManager.getClusterFromCache(haASRelationDOS.get(0).getStandbyClusterPhyId());
}else {
dto.setHaRelation(HaRelationTypeEnum.STANDBY.getCode());
mbCluster = PhysicalClusterMetadataManager.getClusterFromCache(haASRelationDOS.get(0).getActiveClusterPhyId());
}
dto.setMutualBackupClusterName(mbCluster != null ? mbCluster.getClusterName() : null);
}
if (ValidateUtils.isNull(needDetail) || !needDetail) {
return dto;
}

View File

@@ -0,0 +1,42 @@
package com.xiaojukeji.kafka.manager.service.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.xiaojukeji.kafka.manager.common.entity.pojo.ha.JobLogDO;
import com.xiaojukeji.kafka.manager.dao.ha.JobLogDao;
import com.xiaojukeji.kafka.manager.service.service.JobLogService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class JobLogServiceImpl implements JobLogService {
private static final Logger LOGGER = LoggerFactory.getLogger(JobLogServiceImpl.class);
@Autowired
private JobLogDao jobLogDao;
@Override
public void addLogAndIgnoreException(JobLogDO jobLogDO) {
try {
jobLogDao.insert(jobLogDO);
} catch (Exception e) {
LOGGER.error("method=addLogAndIgnoreException||jobLogDO={}||errMsg=exception", jobLogDO);
}
}
@Override
public List<JobLogDO> listLogs(Integer bizType, String bizKeyword, Long startId) {
LambdaQueryWrapper<JobLogDO> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.eq(JobLogDO::getBizType, bizType);
lambdaQueryWrapper.eq(JobLogDO::getBizKeyword, bizKeyword);
if (startId != null) {
lambdaQueryWrapper.ge(JobLogDO::getId, startId);
}
return jobLogDao.selectList(lambdaQueryWrapper);
}
}

View File

@@ -5,19 +5,20 @@ import com.xiaojukeji.kafka.manager.common.entity.ResultStatus;
import com.xiaojukeji.kafka.manager.common.entity.ao.cluster.LogicalCluster;
import com.xiaojukeji.kafka.manager.common.entity.ao.cluster.LogicalClusterMetrics;
import com.xiaojukeji.kafka.manager.common.entity.metrics.BrokerMetrics;
import com.xiaojukeji.kafka.manager.common.entity.pojo.BrokerMetricsDO;
import com.xiaojukeji.kafka.manager.common.entity.pojo.LogicalClusterDO;
import com.xiaojukeji.kafka.manager.common.entity.pojo.gateway.AppDO;
import com.xiaojukeji.kafka.manager.common.utils.ListUtils;
import com.xiaojukeji.kafka.manager.common.utils.ValidateUtils;
import com.xiaojukeji.kafka.manager.common.zookeeper.znode.brokers.BrokerMetadata;
import com.xiaojukeji.kafka.manager.common.zookeeper.znode.brokers.TopicMetadata;
import com.xiaojukeji.kafka.manager.dao.LogicalClusterDao;
import com.xiaojukeji.kafka.manager.common.entity.pojo.BrokerMetricsDO;
import com.xiaojukeji.kafka.manager.common.entity.pojo.LogicalClusterDO;
import com.xiaojukeji.kafka.manager.service.cache.LogicalClusterMetadataManager;
import com.xiaojukeji.kafka.manager.service.cache.PhysicalClusterMetadataManager;
import com.xiaojukeji.kafka.manager.service.service.gateway.AppService;
import com.xiaojukeji.kafka.manager.service.service.BrokerService;
import com.xiaojukeji.kafka.manager.service.service.LogicalClusterService;
import com.xiaojukeji.kafka.manager.service.service.gateway.AppService;
import com.xiaojukeji.kafka.manager.service.service.ha.HaClusterService;
import com.xiaojukeji.kafka.manager.service.utils.MetricsConvertUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -45,6 +46,9 @@ public class LogicalClusterServiceImpl implements LogicalClusterService {
@Autowired
private AppService appService;
@Autowired
private HaClusterService haClusterService;
@Autowired
private LogicalClusterMetadataManager logicClusterMetadataManager;

View File

@@ -4,38 +4,41 @@ import com.xiaojukeji.kafka.manager.common.bizenum.KafkaClientEnum;
import com.xiaojukeji.kafka.manager.common.bizenum.ModuleEnum;
import com.xiaojukeji.kafka.manager.common.bizenum.OperateEnum;
import com.xiaojukeji.kafka.manager.common.bizenum.TopicAuthorityEnum;
import com.xiaojukeji.kafka.manager.common.bizenum.ha.HaRelationTypeEnum;
import com.xiaojukeji.kafka.manager.common.constant.KafkaConstant;
import com.xiaojukeji.kafka.manager.common.constant.KafkaMetricsCollections;
import com.xiaojukeji.kafka.manager.common.constant.TopicCreationConstant;
import com.xiaojukeji.kafka.manager.common.entity.Result;
import com.xiaojukeji.kafka.manager.common.entity.ResultStatus;
import com.xiaojukeji.kafka.manager.common.entity.TopicOperationResult;
import com.xiaojukeji.kafka.manager.common.entity.ao.RdTopicBasic;
import com.xiaojukeji.kafka.manager.common.entity.ao.topic.MineTopicSummary;
import com.xiaojukeji.kafka.manager.common.entity.ao.topic.TopicAppData;
import com.xiaojukeji.kafka.manager.common.entity.ao.topic.TopicBusinessInfo;
import com.xiaojukeji.kafka.manager.common.entity.ao.topic.TopicDTO;
import com.xiaojukeji.kafka.manager.common.entity.dto.op.topic.TopicExpansionDTO;
import com.xiaojukeji.kafka.manager.common.entity.dto.op.topic.TopicModificationDTO;
import com.xiaojukeji.kafka.manager.common.entity.metrics.TopicMetrics;
import com.xiaojukeji.kafka.manager.common.entity.metrics.TopicThrottledMetrics;
import com.xiaojukeji.kafka.manager.common.entity.pojo.*;
import com.xiaojukeji.kafka.manager.common.entity.pojo.gateway.AppDO;
import com.xiaojukeji.kafka.manager.common.entity.pojo.gateway.AuthorityDO;
import com.xiaojukeji.kafka.manager.common.utils.DateUtils;
import com.xiaojukeji.kafka.manager.common.utils.JsonUtils;
import com.xiaojukeji.kafka.manager.common.utils.NumberUtils;
import com.xiaojukeji.kafka.manager.common.utils.SpringTool;
import com.xiaojukeji.kafka.manager.common.utils.ValidateUtils;
import com.xiaojukeji.kafka.manager.common.utils.*;
import com.xiaojukeji.kafka.manager.common.zookeeper.znode.brokers.TopicMetadata;
import com.xiaojukeji.kafka.manager.common.zookeeper.znode.config.TopicQuotaData;
import com.xiaojukeji.kafka.manager.dao.TopicDao;
import com.xiaojukeji.kafka.manager.dao.TopicExpiredDao;
import com.xiaojukeji.kafka.manager.dao.TopicStatisticsDao;
import com.xiaojukeji.kafka.manager.common.entity.metrics.TopicThrottledMetrics;
import com.xiaojukeji.kafka.manager.common.entity.pojo.*;
import com.xiaojukeji.kafka.manager.service.biz.ha.HaASRelationManager;
import com.xiaojukeji.kafka.manager.service.cache.KafkaMetricsCache;
import com.xiaojukeji.kafka.manager.service.cache.LogicalClusterMetadataManager;
import com.xiaojukeji.kafka.manager.service.cache.PhysicalClusterMetadataManager;
import com.xiaojukeji.kafka.manager.service.service.*;
import com.xiaojukeji.kafka.manager.service.service.gateway.AppService;
import com.xiaojukeji.kafka.manager.service.service.gateway.AuthorityService;
import com.xiaojukeji.kafka.manager.service.service.ha.HaTopicService;
import com.xiaojukeji.kafka.manager.service.utils.KafkaZookeeperUtils;
import com.xiaojukeji.kafka.manager.service.utils.TopicCommands;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@@ -87,6 +90,15 @@ public class TopicManagerServiceImpl implements TopicManagerService {
@Autowired
private OperateRecordService operateRecordService;
@Autowired
private HaTopicService haTopicService;
@Autowired
private AdminService adminService;
@Autowired
private HaASRelationManager haASRelationManager;
@Override
public List<TopicDO> listAll() {
try {
@@ -188,6 +200,7 @@ public class TopicManagerServiceImpl implements TopicManagerService {
Map<String, Map<Long, Map<String, AuthorityDO>>> appMap = authorityService.getAllAuthority();
// 增加权限信息和App信息
List<MineTopicSummary> summaryList = new ArrayList<>();
Map<Long, List<String>> clusterStandbyTopicMap = haTopicService.getClusterStandbyTopicMap();
for (AppDO appDO : appDOList) {
// 查权限
for (Map<String, AuthorityDO> subMap : appMap.getOrDefault(appDO.getAppId(), Collections.emptyMap()).values()) {
@@ -196,6 +209,11 @@ public class TopicManagerServiceImpl implements TopicManagerService {
|| TopicAuthorityEnum.DENY.getCode().equals(authorityDO.getAccess())) {
continue;
}
//过滤备topic
List<String> standbyTopics = clusterStandbyTopicMap.get(authorityDO.getClusterId());
if (standbyTopics != null && standbyTopics.contains(authorityDO.getTopicName())){
continue;
}
MineTopicSummary mineTopicSummary = convert2MineTopicSummary(
appDO,
@@ -224,6 +242,7 @@ public class TopicManagerServiceImpl implements TopicManagerService {
TopicDO topicDO = topicDao.getByTopicName(mineTopicSummary.getPhysicalClusterId(), mineTopicSummary.getTopicName());
mineTopicSummary.setDescription(topicDO.getDescription());
}
return summaryList;
}
@@ -302,8 +321,9 @@ public class TopicManagerServiceImpl implements TopicManagerService {
}
List<TopicDTO> dtoList = new ArrayList<>();
Map<Long, List<String>> clusterStandbyTopicMap = haTopicService.getClusterStandbyTopicMap();
for (ClusterDO clusterDO: clusterDOList) {
dtoList.addAll(getTopics(clusterDO, appMap, topicMap.getOrDefault(clusterDO.getId(), new HashMap<>())));
dtoList.addAll(getTopics(clusterDO, appMap, topicMap.getOrDefault(clusterDO.getId(), new HashMap<>()),clusterStandbyTopicMap.get(clusterDO.getId())));
}
return dtoList;
}
@@ -311,13 +331,18 @@ public class TopicManagerServiceImpl implements TopicManagerService {
private List<TopicDTO> getTopics(ClusterDO clusterDO,
Map<String, AppDO> appMap,
Map<String, TopicDO> topicMap) {
Map<String, TopicDO> topicMap,
List<String> standbyTopicNames) {
List<TopicDTO> dtoList = new ArrayList<>();
for (String topicName: PhysicalClusterMetadataManager.getTopicNameList(clusterDO.getId())) {
if (topicName.equals(KafkaConstant.COORDINATOR_TOPIC_NAME) || topicName.equals(KafkaConstant.TRANSACTION_TOPIC_NAME)) {
continue;
}
//过滤备topic
if (standbyTopicNames != null && standbyTopicNames.contains(topicName)){
continue;
}
LogicalClusterDO logicalClusterDO = logicalClusterMetadataManager.getTopicLogicalCluster(
clusterDO.getId(),
topicName
@@ -590,12 +615,12 @@ public class TopicManagerServiceImpl implements TopicManagerService {
TopicDO topicDO = getByTopicName(physicalClusterId, topicName);
if (ValidateUtils.isNull(topicDO)) {
return new Result<>(convert2RdTopicBasic(clusterDO, topicName, null, null, regionNameList, properties));
return new Result<>(convert2RdTopicBasic(clusterDO, topicName, null, null, regionNameList, properties, HaRelationTypeEnum.UNKNOWN.getCode()));
}
AppDO appDO = appService.getByAppId(topicDO.getAppId());
return new Result<>(convert2RdTopicBasic(clusterDO, topicName, topicDO, appDO, regionNameList, properties));
Integer haRelation = haASRelationManager.getRelation(physicalClusterId, topicName);
return new Result<>(convert2RdTopicBasic(clusterDO, topicName, topicDO, appDO, regionNameList, properties, haRelation));
}
@Override
@@ -656,12 +681,56 @@ public class TopicManagerServiceImpl implements TopicManagerService {
return ResultStatus.MYSQL_ERROR;
}
@Override
public Result modifyTopic(TopicModificationDTO dto) {
ClusterDO clusterDO = clusterService.getById(dto.getClusterId());
if (ValidateUtils.isNull(clusterDO)) {
return Result.buildFrom(ResultStatus.CLUSTER_NOT_EXIST);
}
// 获取属性
Properties properties = dto.getProperties();
if (ValidateUtils.isNull(properties)) {
properties = new Properties();
}
properties.put(KafkaConstant.RETENTION_MS_KEY, String.valueOf(dto.getRetentionTime()));
// 操作修改
String operator = SpringTool.getUserName();
ResultStatus rs = TopicCommands.modifyTopicConfig(clusterDO, dto.getTopicName(), properties);
if (!ResultStatus.SUCCESS.equals(rs)) {
return Result.buildFrom(rs);
}
modifyTopicByOp(dto.getClusterId(), dto.getTopicName(), dto.getAppId(), dto.getDescription(), operator);
return Result.buildSuc();
}
@Override
public TopicOperationResult expandTopic(TopicExpansionDTO dto) {
ClusterDO clusterDO = clusterService.getById(dto.getClusterId());
if (ValidateUtils.isNull(clusterDO)) {
return TopicOperationResult.buildFrom(dto.getClusterId(), dto.getTopicName(), ResultStatus.CLUSTER_NOT_EXIST);
}
// 参数检查合法, 开始对Topic进行扩分区
ResultStatus statusEnum = adminService.expandPartitions(
clusterDO,
dto.getTopicName(),
dto.getPartitionNum(),
dto.getRegionId(),
dto.getBrokerIdList(),
SpringTool.getUserName()
);
return TopicOperationResult.buildFrom(dto.getClusterId(), dto.getTopicName(), statusEnum);
}
private RdTopicBasic convert2RdTopicBasic(ClusterDO clusterDO,
String topicName,
TopicDO topicDO,
AppDO appDO,
List<String> regionNameList,
Properties properties) {
Properties properties,
Integer haRelation) {
RdTopicBasic rdTopicBasic = new RdTopicBasic();
rdTopicBasic.setClusterId(clusterDO.getId());
rdTopicBasic.setClusterName(clusterDO.getClusterName());
@@ -676,6 +745,7 @@ public class TopicManagerServiceImpl implements TopicManagerService {
rdTopicBasic.setRegionNameList(regionNameList);
rdTopicBasic.setProperties(properties);
rdTopicBasic.setRetentionTime(KafkaZookeeperUtils.getTopicRetentionTime(properties));
rdTopicBasic.setHaRelation(haRelation);
return rdTopicBasic;
}
}

View File

@@ -1,18 +1,19 @@
package com.xiaojukeji.kafka.manager.service.service.impl;
import com.xiaojukeji.kafka.manager.common.bizenum.TopicOffsetChangedEnum;
import com.xiaojukeji.kafka.manager.common.entity.Result;
import com.xiaojukeji.kafka.manager.common.entity.ResultStatus;
import com.xiaojukeji.kafka.manager.common.entity.pojo.gateway.AppDO;
import com.xiaojukeji.kafka.manager.common.bizenum.OffsetPosEnum;
import com.xiaojukeji.kafka.manager.common.bizenum.TopicOffsetChangedEnum;
import com.xiaojukeji.kafka.manager.common.constant.Constant;
import com.xiaojukeji.kafka.manager.common.constant.KafkaMetricsCollections;
import com.xiaojukeji.kafka.manager.common.constant.TopicSampleConstant;
import com.xiaojukeji.kafka.manager.common.entity.Result;
import com.xiaojukeji.kafka.manager.common.entity.ResultStatus;
import com.xiaojukeji.kafka.manager.common.entity.ao.PartitionAttributeDTO;
import com.xiaojukeji.kafka.manager.common.entity.ao.PartitionOffsetDTO;
import com.xiaojukeji.kafka.manager.common.entity.ao.topic.*;
import com.xiaojukeji.kafka.manager.common.entity.dto.normal.TopicDataSampleDTO;
import com.xiaojukeji.kafka.manager.common.entity.metrics.TopicMetrics;
import com.xiaojukeji.kafka.manager.common.entity.pojo.*;
import com.xiaojukeji.kafka.manager.common.entity.pojo.gateway.AppDO;
import com.xiaojukeji.kafka.manager.common.utils.ValidateUtils;
import com.xiaojukeji.kafka.manager.common.utils.jmx.JmxConstant;
import com.xiaojukeji.kafka.manager.common.zookeeper.znode.brokers.BrokerMetadata;
@@ -22,13 +23,14 @@ import com.xiaojukeji.kafka.manager.common.zookeeper.znode.brokers.TopicMetadata
import com.xiaojukeji.kafka.manager.dao.TopicAppMetricsDao;
import com.xiaojukeji.kafka.manager.dao.TopicMetricsDao;
import com.xiaojukeji.kafka.manager.dao.TopicRequestMetricsDao;
import com.xiaojukeji.kafka.manager.common.entity.pojo.*;
import com.xiaojukeji.kafka.manager.service.biz.ha.HaASRelationManager;
import com.xiaojukeji.kafka.manager.service.cache.KafkaClientPool;
import com.xiaojukeji.kafka.manager.service.cache.KafkaMetricsCache;
import com.xiaojukeji.kafka.manager.service.cache.LogicalClusterMetadataManager;
import com.xiaojukeji.kafka.manager.service.cache.PhysicalClusterMetadataManager;
import com.xiaojukeji.kafka.manager.service.service.*;
import com.xiaojukeji.kafka.manager.service.service.gateway.AppService;
import com.xiaojukeji.kafka.manager.service.service.ha.HaTopicService;
import com.xiaojukeji.kafka.manager.service.strategy.AbstractHealthScoreStrategy;
import com.xiaojukeji.kafka.manager.service.utils.KafkaZookeeperUtils;
import com.xiaojukeji.kafka.manager.service.utils.MetricsConvertUtils;
@@ -90,6 +92,12 @@ public class TopicServiceImpl implements TopicService {
@Autowired
private KafkaClientPool kafkaClientPool;
@Autowired
private HaTopicService haTopicService;
@Autowired
private HaASRelationManager haASRelationManager;
@Override
public List<TopicMetricsDO> getTopicMetricsFromDB(Long clusterId, String topicName, Date startTime, Date endTime) {
try {
@@ -244,6 +252,9 @@ public class TopicServiceImpl implements TopicService {
basicDTO.setTopicCodeC(jmxService.getTopicCodeCValue(clusterId, topicName));
basicDTO.setScore(healthScoreStrategy.calTopicHealthScore(clusterId, topicName));
basicDTO.setHaRelation(haASRelationManager.getRelation(clusterId, topicName));
return basicDTO;
}
@@ -325,6 +336,11 @@ public class TopicServiceImpl implements TopicService {
return jmxService.getTopicMetrics(clusterId, topicName, metricsCode, byAdd);
}
@Override
public Map<TopicPartition, Long> getPartitionOffset(Long clusterPhyId, String topicName, OffsetPosEnum offsetPosEnum) {
return this.getPartitionOffset(clusterService.getById(clusterPhyId), topicName, offsetPosEnum);
}
@Override
public Map<TopicPartition, Long> getPartitionOffset(ClusterDO clusterDO,
String topicName,
@@ -403,6 +419,7 @@ public class TopicServiceImpl implements TopicService {
appDOMap.put(appDO.getAppId(), appDO);
}
Map<String, Integer> haRelationMap = haTopicService.getRelation(clusterId);
List<TopicOverview> dtoList = new ArrayList<>();
for (String topicName : topicNameList) {
TopicMetadata topicMetadata = PhysicalClusterMetadataManager.getTopicMetadata(clusterId, topicName);
@@ -417,7 +434,8 @@ public class TopicServiceImpl implements TopicService {
logicalClusterMetadataManager.getTopicLogicalCluster(clusterId, topicName),
topicMetadata,
topicDO,
appDO
appDO,
haRelationMap.get(topicName)
);
dtoList.add(overview);
}
@@ -429,13 +447,15 @@ public class TopicServiceImpl implements TopicService {
LogicalClusterDO logicalClusterDO,
TopicMetadata topicMetadata,
TopicDO topicDO,
AppDO appDO) {
AppDO appDO,
Integer haRelation) {
TopicOverview overview = new TopicOverview();
overview.setClusterId(physicalClusterId);
overview.setTopicName(topicMetadata.getTopic());
overview.setPartitionNum(topicMetadata.getPartitionNum());
overview.setReplicaNum(topicMetadata.getReplicaNum());
overview.setUpdateTime(topicMetadata.getModifyTime());
overview.setHaRelation(haRelation);
overview.setRetentionTime(
PhysicalClusterMetadataManager.getTopicRetentionTime(physicalClusterId, topicMetadata.getTopic())
);

View File

@@ -17,6 +17,7 @@ import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
/**
* @author zengqiao
@@ -124,4 +125,43 @@ public class ZookeeperServiceImpl implements ZookeeperService {
}
return Result.buildFrom(ResultStatus.ZOOKEEPER_DELETE_FAILED);
}
@Override
public Result<List<Integer>> getBrokerIds(String zookeeper) {
if (ValidateUtils.isNull(zookeeper)) {
return Result.buildFrom(ResultStatus.PARAM_ILLEGAL);
}
ZkConfigImpl zkConfig = new ZkConfigImpl(zookeeper);
if (ValidateUtils.isNull(zkConfig)) {
return Result.buildFrom(ResultStatus.ZOOKEEPER_CONNECT_FAILED);
}
try {
if (!zkConfig.checkPathExists(ZkPathUtil.BROKER_IDS_ROOT)) {
return Result.buildSuc(new ArrayList<>());
}
List<String> brokerIdList = zkConfig.getChildren(ZkPathUtil.BROKER_IDS_ROOT);
if (ValidateUtils.isEmptyList(brokerIdList)) {
return Result.buildSuc(new ArrayList<>());
}
return Result.buildSuc(ListUtils.string2IntList(ListUtils.strList2String(brokerIdList)));
} catch (Exception e) {
LOGGER.error("class=ZookeeperServiceImpl||method=getBrokerIds||zookeeper={}||errMsg={}", zookeeper, e.getMessage());
}
return Result.buildFrom(ResultStatus.ZOOKEEPER_READ_FAILED);
}
@Override
public Long getClusterIdAndNullIfFailed(String zookeeper) {
try {
ZkConfigImpl zkConfig = new ZkConfigImpl(zookeeper);
Properties props = zkConfig.get(ZkPathUtil.CLUSTER_ID_NODE, Properties.class);
return Long.valueOf(props.getProperty("id"));
} catch (Exception e) {
LOGGER.error("class=ZookeeperServiceImpl||method=getClusterIdAndNullIfFailed||zookeeper={}||errMsg=exception", zookeeper, e);
}
return null;
}
}

View File

@@ -20,4 +20,7 @@ public class ConfigUtils {
@Value(value = "${spring.profiles.active:dev}")
private String kafkaManagerEnv;
@Value(value = "${d-kafka.gateway-zk:}")
private String dKafkaGatewayZK;
}

View File

@@ -0,0 +1,112 @@
package com.xiaojukeji.kafka.manager.service.utils;
import com.xiaojukeji.kafka.manager.common.constant.Constant;
import com.xiaojukeji.kafka.manager.common.entity.ResultStatus;
import kafka.admin.AdminUtils;
import kafka.admin.AdminUtils$;
import kafka.utils.ZkUtils;
import org.apache.kafka.common.security.JaasUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Properties;
/**
* @author fengqiongfeng
* @date 21/4/11
*/
public class HaClusterCommands {
private static final Logger LOGGER = LoggerFactory.getLogger(HaClusterCommands.class);
private static final String HA_CLUSTERS = "ha-clusters";
/**
* 修改HA集群配置
*/
public static ResultStatus modifyHaClusterConfig(String zookeeper, Long clusterPhyId, Properties modifiedProps) {
ZkUtils zkUtils = null;
try {
zkUtils = ZkUtils.apply(
zookeeper,
Constant.DEFAULT_SESSION_TIMEOUT_UNIT_MS,
Constant.DEFAULT_SESSION_TIMEOUT_UNIT_MS,
JaasUtils.isZkSecurityEnabled()
);
// 获取当前配置
Properties props = AdminUtils.fetchEntityConfig(zkUtils, HA_CLUSTERS, clusterPhyId.toString());
// 补充变更的配置
props.putAll(modifiedProps);
AdminUtils$.MODULE$.kafka$admin$AdminUtils$$changeEntityConfig(zkUtils, HA_CLUSTERS, clusterPhyId.toString(), props);
} catch (Exception e) {
LOGGER.error("method=modifyHaClusterConfig||zookeeper={}||clusterPhyId={}||modifiedProps={}||errMsg=exception", zookeeper, clusterPhyId, modifiedProps, e);
return ResultStatus.ZOOKEEPER_OPERATE_FAILED;
} finally {
if (null != zkUtils) {
zkUtils.close();
}
}
return ResultStatus.SUCCESS;
}
/**
* 获取集群高可用配置
*/
public static Properties fetchHaClusterConfig(String zookeeper, Long clusterPhyId) {
ZkUtils zkUtils = null;
try {
zkUtils = ZkUtils.apply(
zookeeper,
Constant.DEFAULT_SESSION_TIMEOUT_UNIT_MS,
Constant.DEFAULT_SESSION_TIMEOUT_UNIT_MS,
JaasUtils.isZkSecurityEnabled()
);
// 获取配置
return AdminUtils.fetchEntityConfig(zkUtils, HA_CLUSTERS, clusterPhyId.toString());
}catch (Exception e){
LOGGER.error("method=fetchHaClusterConfig||zookeeper={}||clusterPhyId={}||errMsg=exception", zookeeper, clusterPhyId, e);
return null;
} finally {
if (null != zkUtils) {
zkUtils.close();
}
}
}
/**
* 删除 高可用集群的动态配置
*/
public static ResultStatus coverHaClusterConfig(String zookeeper, Long clusterPhyId, Properties properties){
ZkUtils zkUtils = null;
try {
zkUtils = ZkUtils.apply(
zookeeper,
Constant.DEFAULT_SESSION_TIMEOUT_UNIT_MS,
Constant.DEFAULT_SESSION_TIMEOUT_UNIT_MS,
JaasUtils.isZkSecurityEnabled()
);
AdminUtils$.MODULE$.kafka$admin$AdminUtils$$changeEntityConfig(zkUtils, HA_CLUSTERS, clusterPhyId.toString(), properties);
return ResultStatus.SUCCESS;
}catch (Exception e){
LOGGER.error("method=deleteHaClusterConfig||zookeeper={}||clusterPhyId={}||delProps={}||errMsg=exception", zookeeper, clusterPhyId, properties, e);
return ResultStatus.FAIL;
} finally {
if (null != zkUtils) {
zkUtils.close();
}
}
}
private HaClusterCommands() {
}
}

View File

@@ -0,0 +1,93 @@
package com.xiaojukeji.kafka.manager.service.utils;
import com.xiaojukeji.kafka.manager.common.constant.Constant;
import com.xiaojukeji.kafka.manager.common.utils.ListUtils;
import kafka.admin.AdminUtils;
import kafka.admin.AdminUtils$;
import kafka.server.ConfigType;
import kafka.utils.ZkUtils;
import org.apache.kafka.common.security.JaasUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.Properties;
/**
* @author fengqiongfeng
* @date 21/4/11
*/
public class HaKafkaUserCommands {
private static final Logger LOGGER = LoggerFactory.getLogger(HaKafkaUserCommands.class);
/**
* 修改User配置
*/
public static boolean modifyHaUserConfig(String zookeeper, String kafkaUser, Properties modifiedProps) {
ZkUtils zkUtils = null;
try {
zkUtils = ZkUtils.apply(
zookeeper,
Constant.DEFAULT_SESSION_TIMEOUT_UNIT_MS,
Constant.DEFAULT_SESSION_TIMEOUT_UNIT_MS,
JaasUtils.isZkSecurityEnabled()
);
// 获取当前配置
Properties props = AdminUtils.fetchEntityConfig(zkUtils, ConfigType.User(), kafkaUser);
// 补充变更的配置
props.putAll(modifiedProps);
// 修改配置, 这里不使用changeUserOrUserClientIdConfig方法的原因是changeUserOrUserClientIdConfig这个方法会进行参数检查
AdminUtils$.MODULE$.kafka$admin$AdminUtils$$changeEntityConfig(zkUtils, ConfigType.User(), kafkaUser, props);
} catch (Exception e) {
LOGGER.error("method=changeHaUserConfig||zookeeper={}||kafkaUser={}||modifiedProps={}||errMsg=exception", zookeeper, kafkaUser, modifiedProps, e);
return false;
} finally {
if (null != zkUtils) {
zkUtils.close();
}
}
return true;
}
/**
* 删除 高可用集群的动态配置
*/
public static boolean deleteHaUserConfig(String zookeeper, String kafkaUser, List<String> needDeleteConfigNameList){
ZkUtils zkUtils = null;
try {
zkUtils = ZkUtils.apply(
zookeeper,
Constant.DEFAULT_SESSION_TIMEOUT_UNIT_MS,
Constant.DEFAULT_SESSION_TIMEOUT_UNIT_MS,
JaasUtils.isZkSecurityEnabled()
);
Properties presentProps = AdminUtils.fetchEntityConfig(zkUtils, ConfigType.User(), kafkaUser);
//删除需要删除的的配置
for (String configName : needDeleteConfigNameList) {
presentProps.remove(configName);
}
// 修改配置, 这里不使用changeUserOrUserClientIdConfig方法的原因是changeUserOrUserClientIdConfig这个方法会进行参数检查
AdminUtils$.MODULE$.kafka$admin$AdminUtils$$changeEntityConfig(zkUtils, ConfigType.User(), kafkaUser, presentProps);
return true;
}catch (Exception e){
LOGGER.error("method=deleteHaUserConfig||zookeeper={}||kafkaUser={}||delProps={}||errMsg=exception", zookeeper, kafkaUser, ListUtils.strList2String(needDeleteConfigNameList), e);
} finally {
if (null != zkUtils) {
zkUtils.close();
}
}
return false;
}
private HaKafkaUserCommands() {
}
}

View File

@@ -0,0 +1,136 @@
package com.xiaojukeji.kafka.manager.service.utils;
import com.xiaojukeji.kafka.manager.common.constant.Constant;
import com.xiaojukeji.kafka.manager.common.entity.ResultStatus;
import com.xiaojukeji.kafka.manager.common.entity.pojo.ClusterDO;
import com.xiaojukeji.kafka.manager.common.utils.ListUtils;
import kafka.admin.AdminOperationException;
import kafka.admin.AdminUtils;
import kafka.admin.AdminUtils$;
import kafka.utils.ZkUtils;
import org.apache.kafka.common.errors.*;
import org.apache.kafka.common.security.JaasUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import scala.collection.JavaConversions;
import java.util.*;
/**
* HA-Topic Commands
*/
public class HaTopicCommands {
private static final Logger LOGGER = LoggerFactory.getLogger(HaTopicCommands.class);
private static final String HA_TOPICS = "ha-topics";
/**
* 修改HA配置
*/
public static ResultStatus modifyHaTopicConfig(ClusterDO clusterDO, String topicName, Properties props) {
ZkUtils zkUtils = null;
try {
zkUtils = ZkUtils.apply(
clusterDO.getZookeeper(),
Constant.DEFAULT_SESSION_TIMEOUT_UNIT_MS,
Constant.DEFAULT_SESSION_TIMEOUT_UNIT_MS,
JaasUtils.isZkSecurityEnabled()
);
AdminUtils$.MODULE$.kafka$admin$AdminUtils$$changeEntityConfig(zkUtils, HA_TOPICS, topicName, props);
} catch (AdminOperationException aoe) {
LOGGER.error("method=modifyHaTopicConfig||clusterPhyId={}||topicName={}||props={}||errMsg=exception", clusterDO.getId(), topicName, props, aoe);
return ResultStatus.TOPIC_OPERATION_UNKNOWN_TOPIC_PARTITION;
} catch (InvalidConfigurationException ice) {
LOGGER.error("method=modifyHaTopicConfig||clusterPhyId={}||topicName={}||props={}||errMsg=exception", clusterDO.getId(), topicName, props, ice);
return ResultStatus.TOPIC_OPERATION_TOPIC_CONFIG_ILLEGAL;
} catch (Exception e) {
LOGGER.error("method=modifyHaTopicConfig||clusterPhyId={}||topicName={}||props={}||errMsg=exception", clusterDO.getId(), topicName, props, e);
return ResultStatus.TOPIC_OPERATION_UNKNOWN_ERROR;
} finally {
if (zkUtils != null) {
zkUtils.close();
}
}
return ResultStatus.SUCCESS;
}
/**
* 删除指定HA配置
*/
public static ResultStatus deleteHaTopicConfig(ClusterDO clusterDO, String topicName, List<String> neeDeleteConfigNameList){
ZkUtils zkUtils = null;
try {
zkUtils = ZkUtils.apply(
clusterDO.getZookeeper(),
Constant.DEFAULT_SESSION_TIMEOUT_UNIT_MS,
Constant.DEFAULT_SESSION_TIMEOUT_UNIT_MS,
JaasUtils.isZkSecurityEnabled()
);
// 当前配置
Properties presentProps = AdminUtils.fetchEntityConfig(zkUtils, HA_TOPICS, topicName);
//删除需要删除的的配置
for (String configName : neeDeleteConfigNameList) {
presentProps.remove(configName);
}
AdminUtils$.MODULE$.kafka$admin$AdminUtils$$changeEntityConfig(zkUtils, HA_TOPICS, topicName, presentProps);
} catch (Exception e){
LOGGER.error("method=deleteHaTopicConfig||clusterPhyId={}||topicName={}||delProps={}||errMsg=exception", clusterDO.getId(), topicName, ListUtils.strList2String(neeDeleteConfigNameList), e);
return ResultStatus.FAIL;
} finally {
if (null != zkUtils) {
zkUtils.close();
}
}
return ResultStatus.SUCCESS;
}
public static Properties fetchHaTopicConfig(ClusterDO clusterDO, String topicName){
ZkUtils zkUtils = null;
try {
zkUtils = ZkUtils.apply(
clusterDO.getZookeeper(),
Constant.DEFAULT_SESSION_TIMEOUT_UNIT_MS,
Constant.DEFAULT_SESSION_TIMEOUT_UNIT_MS,
JaasUtils.isZkSecurityEnabled()
);
return AdminUtils.fetchEntityConfig(zkUtils, HA_TOPICS, topicName);
} catch (Exception e){
LOGGER.error("method=fetchHaTopicConfig||clusterPhyId={}||topicName={}||errMsg=exception", clusterDO.getId(), topicName, e);
return null;
} finally {
if (null != zkUtils) {
zkUtils.close();
}
}
}
public static Map<String, Properties> fetchAllHaTopicConfig(ClusterDO clusterDO) {
ZkUtils zkUtils = null;
try {
zkUtils = ZkUtils.apply(
clusterDO.getZookeeper(),
Constant.DEFAULT_SESSION_TIMEOUT_UNIT_MS,
Constant.DEFAULT_SESSION_TIMEOUT_UNIT_MS,
JaasUtils.isZkSecurityEnabled()
);
return JavaConversions.asJavaMap(AdminUtils.fetchAllEntityConfigs(zkUtils, HA_TOPICS));
} catch (Exception e){
LOGGER.error("method=fetchAllHaTopicConfig||clusterPhyId={}||errMsg=exception", clusterDO.getId(), e);
return null;
} finally {
if (null != zkUtils) {
zkUtils.close();
}
}
}
private HaTopicCommands() {
}
}

View File

@@ -8,6 +8,7 @@ import kafka.admin.AdminOperationException;
import kafka.admin.AdminUtils;
import kafka.admin.BrokerMetadata;
import kafka.common.TopicAndPartition;
import kafka.server.ConfigType;
import kafka.utils.ZkUtils;
import org.I0Itec.zkclient.exception.ZkNodeExistsException;
import org.apache.kafka.common.errors.*;
@@ -27,6 +28,8 @@ import java.util.*;
public class TopicCommands {
private static final Logger LOGGER = LoggerFactory.getLogger(TopicCommands.class);
private TopicCommands() {
}
public static ResultStatus createTopic(ClusterDO clusterDO,
String topicName,
@@ -51,7 +54,7 @@ public class TopicCommands {
replicaNum,
randomFixedStartIndex(),
-1
);
);
// 写ZK
AdminUtils.createOrUpdateTopicPartitionAssignmentPathInZK(
@@ -129,6 +132,11 @@ public class TopicCommands {
Constant.DEFAULT_SESSION_TIMEOUT_UNIT_MS,
JaasUtils.isZkSecurityEnabled()
);
if(!zkUtils.pathExists(zkUtils.getTopicPath(topicName))){
return ResultStatus.TOPIC_NOT_EXIST;
}
AdminUtils.changeTopicConfig(zkUtils, topicName, config);
} catch (AdminOperationException e) {
LOGGER.error("class=TopicCommands||method=modifyTopicConfig||errMsg={}||clusterDO={}||topicName={}||config={}", e.getMessage(), clusterDO, topicName,config, e);
@@ -209,6 +217,31 @@ public class TopicCommands {
return ResultStatus.SUCCESS;
}
/**
* 获取Topic的动态配置
*/
public static Properties fetchTopicConfig(ClusterDO clusterDO, String topicName){
ZkUtils zkUtils = null;
try {
zkUtils = ZkUtils.apply(
clusterDO.getZookeeper(),
Constant.DEFAULT_SESSION_TIMEOUT_UNIT_MS,
Constant.DEFAULT_SESSION_TIMEOUT_UNIT_MS,
JaasUtils.isZkSecurityEnabled()
);
return AdminUtils.fetchEntityConfig(zkUtils, ConfigType.Topic(), topicName);
} catch (Exception e){
LOGGER.error("get topic config failed, zk:{},topic:{} .err:{}", clusterDO.getZookeeper(), topicName, e);
} finally {
if (null != zkUtils) {
zkUtils.close();
}
}
return null;
}
private static Seq<BrokerMetadata> convert2BrokerMetadataSeq(List<Integer> brokerIdList) {
List<BrokerMetadata> brokerMetadataList = new ArrayList<>();
for (Integer brokerId: brokerIdList) {

View File

@@ -22,12 +22,10 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.dao.DuplicateKeyException;
import org.testng.Assert;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import java.util.*;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.when;
/**
@@ -327,8 +325,8 @@ public class ClusterServiceTest extends BaseTest {
@Test(description = "测试删除集群时该集群下还有region禁止删除")
public void deleteById2OperationForbiddenTest() {
when(regionService.getByClusterId(Mockito.anyLong())).thenReturn(Arrays.asList(new RegionDO()));
ResultStatus resultStatus = clusterService.deleteById(1L, "admin");
Assert.assertEquals(resultStatus.getCode(), ResultStatus.OPERATION_FORBIDDEN.getCode());
Result result = clusterService.deleteById(1L, "admin");
Assert.assertEquals(result.successful(), ResultStatus.OPERATION_FORBIDDEN.getCode());
}
@Test(description = "测试删除集群成功")
@@ -337,18 +335,18 @@ public class ClusterServiceTest extends BaseTest {
when(regionService.getByClusterId(Mockito.anyLong())).thenReturn(Collections.emptyList());
Mockito.when(operateRecordService.insert(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any())).thenReturn(1);
Mockito.when(clusterDao.deleteById(Mockito.any())).thenReturn(1);
ResultStatus resultStatus = clusterService.deleteById(clusterDO.getId(), "admin");
Assert.assertEquals(resultStatus.getCode(), ResultStatus.SUCCESS.getCode());
Result result = clusterService.deleteById(clusterDO.getId(), "admin");
Assert.assertEquals(result.successful(), ResultStatus.SUCCESS.getCode());
}
@Test(description = "测试MYSQL_ERROR")
public void deleteById2MysqlErrorTest() {
when(regionService.getByClusterId(Mockito.anyLong())).thenReturn(Collections.emptyList());
ResultStatus resultStatus = clusterService.deleteById(100L, "admin");
Result result = clusterService.deleteById(100L, "admin");
Mockito.when(operateRecordService.insert(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any())).thenReturn(1);
Mockito.when(clusterDao.deleteById(Mockito.any())).thenReturn(-1);
Assert.assertEquals(resultStatus.getCode(), ResultStatus.MYSQL_ERROR.getCode());
Assert.assertEquals(result.successful(), ResultStatus.MYSQL_ERROR.getCode());
}
@Test(description = "测试从zk中获取被选举的broker")

View File

@@ -371,7 +371,7 @@ public class TopicServiceTest extends BaseTest {
private void getPartitionOffset2EmptyTest() {
ClusterDO clusterDO = getClusterDO();
Map<TopicPartition, Long> partitionOffset = topicService.getPartitionOffset(
null, null, OffsetPosEnum.BEGINNING);
clusterDO, null, OffsetPosEnum.BEGINNING);
Assert.assertTrue(partitionOffset.isEmpty());
Map<TopicPartition, Long> partitionOffset2 = topicService.getPartitionOffset(