/** * Copyright 2016 vip.com. * <p> * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * </p> */ package com.vip.saturn.job.internal.sharding; import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Map.Entry; import org.apache.curator.framework.CuratorFramework; import org.apache.curator.framework.api.CuratorWatcher; import org.apache.curator.framework.api.transaction.CuratorTransactionFinal; import org.apache.zookeeper.KeeperException.BadVersionException; import org.apache.zookeeper.data.Stat; import org.quartz.JobExecutionException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.vip.saturn.job.basic.AbstractSaturnService; import com.vip.saturn.job.basic.JobScheduler; import com.vip.saturn.job.basic.SaturnConstant; import com.vip.saturn.job.exception.JobShuttingDownException; import com.vip.saturn.job.internal.election.LeaderElectionService; import com.vip.saturn.job.internal.execution.ExecutionService; import com.vip.saturn.job.internal.server.ServerService; import com.vip.saturn.job.internal.storage.JobNodePath; import com.vip.saturn.job.sharding.service.NamespaceShardingContentService; import com.vip.saturn.job.utils.BlockUtils; import com.vip.saturn.job.utils.ItemUtils; /** * 作业分片服务. * * */ public class ShardingService extends AbstractSaturnService { static Logger log = LoggerFactory.getLogger(ShardingService.class); private LeaderElectionService leaderElectionService; private ServerService serverService; private ExecutionService executionService; private NamespaceShardingContentService namespaceShardingContentService; public final static String SHARDING_UN_NECESSARY = "0"; private volatile boolean isShutdown; private CuratorWatcher necessaryWatcher; public ShardingService(final JobScheduler jobScheduler) { super(jobScheduler); } @Override public synchronized void start(){ leaderElectionService = jobScheduler.getLeaderElectionService(); serverService = jobScheduler.getServerService(); executionService = jobScheduler.getExecutionService(); namespaceShardingContentService = new NamespaceShardingContentService((CuratorFramework)coordinatorRegistryCenter.getRawClient()); } /** * 判断是否需要重分片. * @return 是否需要重分片 */ public boolean isNeedSharding() { return getJobNodeStorage().isJobNodeExisted(ShardingNode.NECESSARY) && !SHARDING_UN_NECESSARY.equals(getJobNodeStorage().getJobNodeDataDirectly(ShardingNode.NECESSARY)); } public void registerNecessaryWatcher(CuratorWatcher necessaryWatcher) { this.necessaryWatcher = necessaryWatcher; registerNecessaryWatcher(); } public void registerNecessaryWatcher() { try { if (necessaryWatcher != null) { getJobNodeStorage().getClient().checkExists().usingWatcher(necessaryWatcher).forPath(JobNodePath.getNodeFullPath(jobName, ShardingNode.NECESSARY)); } } catch (Exception e) { log.error(e.getMessage(), e); } } private GetDataStat getNecessaryDataStat() { String data = null; int version = -1; try { Stat stat = new Stat(); byte[] bs = null; if(necessaryWatcher != null) { bs = getJobNodeStorage().getClient().getData().storingStatIn(stat).usingWatcher(necessaryWatcher).forPath(JobNodePath.getNodeFullPath(jobName, ShardingNode.NECESSARY)); } else { bs = getJobNodeStorage().getClient().getData().storingStatIn(stat).forPath(JobNodePath.getNodeFullPath(jobName, ShardingNode.NECESSARY)); } if(bs != null) { data = new String(bs, "UTF-8"); } version = stat.getVersion(); } catch (Exception e) { log.error(e.getMessage(), e); } return new GetDataStat(data, version); } /** * 如果需要分片且当前节点为主节点, 则作业分片. */ public synchronized void shardingIfNecessary() throws JobExecutionException { if(isShutdown) { return; } GetDataStat getDataStat = null; if(getJobNodeStorage().isJobNodeExisted(ShardingNode.NECESSARY)) { getDataStat = getNecessaryDataStat(); } if(getDataStat == null || SHARDING_UN_NECESSARY.equals(getDataStat.getData())) { return; } if(blockUntilShardingComplatedIfNotLeader()) { return; } waitingOtherJobCompleted(); getJobNodeStorage().fillEphemeralJobNode(ShardingNode.PROCESSING, ""); try { clearShardingInfo(); int retryCount = 3; while(!isShutdown) { boolean needRetry = false; int version = getDataStat.getVersion(); Map<String, List<Integer>> shardingItems = namespaceShardingContentService.getShardContent(jobName, getDataStat.getData()); try { CuratorTransactionFinal curatorTransactionFinal = getJobNodeStorage().getClient().inTransaction().check().forPath("/").and(); for (Entry<String, List<Integer>> entry : shardingItems.entrySet()) { curatorTransactionFinal.create().forPath(JobNodePath.getNodeFullPath(jobName, ShardingNode.getShardingNode(entry.getKey())), ItemUtils.toItemsString(entry.getValue()).getBytes(StandardCharsets.UTF_8)).and(); } curatorTransactionFinal.setData().withVersion(version).forPath(JobNodePath.getNodeFullPath(jobName, ShardingNode.NECESSARY), SHARDING_UN_NECESSARY.getBytes(StandardCharsets.UTF_8)).and(); curatorTransactionFinal.commit(); } catch(BadVersionException e) { needRetry = true; retryCount--; } catch(Exception e) { log.error(String.format(SaturnConstant.ERROR_LOG_FORMAT, jobName, "Commit shards failed"), e); } if(needRetry) { if(retryCount >= 0) { log.info(String.format(SaturnConstant.ERROR_LOG_FORMAT, jobName, "Bad version because of concurrency, will retry to get shards later")); Thread.sleep(200L); // NOSONAR getDataStat = getNecessaryDataStat(); } else { log.warn(String.format(SaturnConstant.ERROR_LOG_FORMAT, jobName, "Bad version because of concurrency, give up to retry")); break; } } else { break; } } } catch(Exception e) { log.error(String.format(SaturnConstant.ERROR_LOG_FORMAT, jobName, e.getMessage()), e); } finally { getJobNodeStorage().removeJobNodeIfExisted(ShardingNode.PROCESSING); } } /** * 如果不是leader,等待leader分片完成,返回true;如果期间变为leader,返回false。 * @return true or false * @throws JobShuttingDownException */ private boolean blockUntilShardingComplatedIfNotLeader() throws JobShuttingDownException { for(;;) { if(isShutdown) { throw new JobShuttingDownException(); } if(leaderElectionService.isLeader()) { return false; } if(!(isNeedSharding() || getJobNodeStorage().isJobNodeExisted(ShardingNode.PROCESSING))) { return true; } log.debug("[{}] msg=Sleep short time until sharding completed", jobName); BlockUtils.waitingShortTime(); } } private void waitingOtherJobCompleted() { while (!isShutdown && executionService.hasRunningItems()) { log.info("[{}] msg=Sleep short time until other job completed.", jobName); BlockUtils.waitingShortTime(); } } private void clearShardingInfo() { for (String each : serverService.getAllServers()) { getJobNodeStorage().removeJobNodeIfExisted(ShardingNode.getShardingNode(each)); } } /** * 获取运行在本作业服务器的分片序列号. * * @return 运行在本作业服务器的分片序列号 */ public List<Integer> getLocalHostShardingItems() { if (!getJobNodeStorage().isJobNodeExisted(ShardingNode.getShardingNode(executorName))) { return Collections.<Integer>emptyList(); } return ItemUtils.toItemList(getJobNodeStorage().getJobNodeDataDirectly(ShardingNode.getShardingNode(executorName))); } @Override public void shutdown() { isShutdown = true; necessaryWatcher = null; // cannot registerNecessaryWatcher } }