最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

DolphinScheduler容錯源碼分析之Worker

 更新時間:2023年02月06日 11:42:14   作者:leo的跟班  
這篇文章主要為大家介紹了DolphinScheduler容錯源碼分析之Worker,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

引言

上一篇文章介紹了DolphinScheduler中Master的容錯機制,作為去中心化的多Master和多Worker服務對等架構,Worker的容錯機制也是我們需要關注的。

和Master一樣源碼的版本基于3.1.3

Worker容錯源碼分析

worker啟動注冊

首先Worker的啟動入口是在WorkerServer中,在Worker啟動后就會執(zhí)行其run方法

@PostConstruct
public void run() {
	this.workerRpcServer.start();
	this.workerRpcClient.start();
	this.taskPluginManager.loadPlugin();
	this.workerRegistryClient.setRegistryStoppable(this);
	this.workerRegistryClient.start();
	this.workerManagerThread.start();
	this.messageRetryRunner.start();
	/*
	 * registry hooks, which are called before the process exits
	 */
	Runtime.getRuntime().addShutdownHook(new Thread(() -> {
		if (!ServerLifeCycleManager.isStopped()) {
			close("WorkerServer shutdown hook");
		}
	}));
}

這里我們只關心this.workerRegistryClient.start();方法所做的事情:注冊當前worker信息到Zookeeper,并且啟動了一個心跳任務定時更新worker的信息到Zookeeper。

/**
 * registry
 */
private void registry() {
	WorkerHeartBeat workerHeartBeat = workerHeartBeatTask.getHeartBeat();
	String workerZKPath = workerConfig.getWorkerRegistryPath();
	// remove before persist
	registryClient.remove(workerZKPath);
	registryClient.persistEphemeral(workerZKPath, JSONUtils.toJsonString(workerHeartBeat));
	log.info("Worker node: {} registry to ZK {} successfully", workerConfig.getWorkerAddress(), workerZKPath);
	while (!registryClient.checkNodeExists(workerConfig.getWorkerAddress(), NodeType.WORKER)) {
		ThreadUtils.sleep(SLEEP_TIME_MILLIS);
	}
	// sleep 1s, waiting master failover remove
	ThreadUtils.sleep(Constants.SLEEP_TIME_MILLIS);
	workerHeartBeatTask.start();
	log.info("Worker node: {} registry finished", workerConfig.getWorkerAddress());
}

這里和master的注冊流程基本一致,來看看worker注冊的目錄:

worker注冊到zk的路徑如下,并且和master都有相同的父級目錄名稱是/node:

// /nodes/worker/+ip:listenPortworkerConfig.setWorkerRegistryPath(REGISTRY_DOLPHINSCHEDULER_WORKERS + "/" + workerConfig.getWorkerAddress());

注冊的內(nèi)容就是當前worker節(jié)點的健康狀況,包含了cpu,內(nèi)存,負載,磁盤等信息,通過這些信息就可以標識當前worker是否健康,可以接收任務的分配并且去執(zhí)行。

@Override
public WorkerHeartBeat getHeartBeat() {
	double loadAverage = OSUtils.loadAverage();
	double cpuUsage = OSUtils.cpuUsage();
	int maxCpuLoadAvg = workerConfig.getMaxCpuLoadAvg();
	double reservedMemory = workerConfig.getReservedMemory();
	double availablePhysicalMemorySize = OSUtils.availablePhysicalMemorySize();
	int execThreads = workerConfig.getExecThreads();
	int workerWaitingTaskCount = this.workerWaitingTaskCount.get();
	int serverStatus = getServerStatus(loadAverage, maxCpuLoadAvg, availablePhysicalMemorySize, reservedMemory,
			execThreads, workerWaitingTaskCount);
	return WorkerHeartBeat.builder()
			.startupTime(ServerLifeCycleManager.getServerStartupTime())
			.reportTime(System.currentTimeMillis())
			.cpuUsage(cpuUsage)
			.loadAverage(loadAverage)
			.availablePhysicalMemorySize(availablePhysicalMemorySize)
			.maxCpuloadAvg(maxCpuLoadAvg)
			.memoryUsage(OSUtils.memoryUsage())
			.reservedMemory(reservedMemory)
			.diskAvailable(OSUtils.diskAvailable())
			.processId(processId)
			.workerHostWeight(workerConfig.getHostWeight())
			.workerWaitingTaskCount(this.workerWaitingTaskCount.get())
			.workerExecThreadCount(workerConfig.getExecThreads())
			.serverStatus(serverStatus)
			.build();
}

Master監(jiān)聽worker在zk節(jié)點的狀態(tài)

接下來,master就會對注冊的worker節(jié)點進行監(jiān)控,在上一篇的介紹中,master啟動注冊后對node節(jié)點已經(jīng)進行了監(jiān)聽,大家可以進行回顧一下,這里監(jiān)聽了/node/節(jié)點,當其下面的子路徑/master或者/worker有變動就會觸發(fā)回調(diào) :

//node
registryClient.subscribe(REGISTRY_DOLPHINSCHEDULER_NODE, new MasterRegistryDataListener());

因此當worker臨時節(jié)點異常后,master就會感知到其變化。最終會回調(diào)MasterRegistryDataListener中的notify方法,并根據(jù)變動的路徑來判斷是master還是worker:

@Override
public void notify(Event event) {
	final String path = event.path();
	if (Strings.isNullOrEmpty(path)) {
		return;
	}
	//monitor master
	if (path.startsWith(REGISTRY_DOLPHINSCHEDULER_MASTERS + Constants.SINGLE_SLASH)) {
		handleMasterEvent(event);
	} else if (path.startsWith(REGISTRY_DOLPHINSCHEDULER_WORKERS + Constants.SINGLE_SLASH)) {
		//monitor worker
		handleWorkerEvent(event);
	}
}

這段代碼在之前master的容錯中也見到過。這里是對于worker的容錯,就會觸發(fā)handleWorkerEvent方法。

private void handleWorkerEvent(Event event) {
	final String path = event.path();
	switch (event.type()) {
		case ADD:
			logger.info("worker node added : {}", path);
			break;
		case REMOVE:
			logger.info("worker node deleted : {}", path);
			masterRegistryClient.removeWorkerNodePath(path, NodeType.WORKER, true);
			break;
		default:
			break;
	}
}

接下來就是獲取到下線worker節(jié)點的host信息進行進一步的容錯處理了:

public void removeWorkerNodePath(String path, NodeType nodeType, boolean failover) {
	logger.info("{} node deleted : {}", nodeType, path);
	try {
                //獲取節(jié)點信息
		String serverHost = null;
		if (!StringUtils.isEmpty(path)) {
			serverHost = registryClient.getHostByEventDataPath(path);
			if (StringUtils.isEmpty(serverHost)) {
				logger.error("server down error: unknown path: {}", path);
				return;
			}
			if (!registryClient.exists(path)) {
				logger.info("path: {} not exists", path);
			}
		}
		// failover server
		if (failover) {
			failoverService.failoverServerWhenDown(serverHost, nodeType);
		}
	} catch (Exception e) {
		logger.error("{} server failover failed", nodeType, e);
	}
}

整個worker容錯的大致過程如下:

1-獲取需要容錯worker節(jié)點的啟動時間,用于后續(xù)判斷worker節(jié)點是否還在下線狀態(tài),或者是否已經(jīng)重新啟動 

2-根據(jù)異常的worker的信息查詢需要容錯的任務實例,獲取只屬于當前master節(jié)點需要容錯的任務實例信息,這里也是和master不同的,并且容錯沒加鎖的原因。 

3-遍歷所有要容錯的任務實例進行容錯 這里注意的是需要容錯的任務是在worker重新啟動之前的任務,之后worker異常重啟后分配的新任務不要容錯   

/**
 * Do the worker failover. Will find the SUBMITTED_SUCCESS/DISPATCH/RUNNING_EXECUTION/DELAY_EXECUTION/READY_PAUSE/READY_STOP tasks belong the given worker,
 * and failover these tasks.
 * <p>
 * Note: When we do worker failover, the master will only failover the processInstance belongs to the current master.
 *
 * @param workerHost worker host
 */
public void failoverWorker(@NonNull String workerHost) {
	LOGGER.info("Worker[{}] failover starting", workerHost);
	final StopWatch failoverTimeCost = StopWatch.createStarted();
	//獲取需要容錯worker節(jié)點的啟動時間,用于后續(xù)判斷worker節(jié)點是否還在下線狀態(tài),或者是否已經(jīng)重新啟動
	// we query the task instance from cache, so that we can directly update the cache
	final Optional<Date> needFailoverWorkerStartTime =
			getServerStartupTime(registryClient.getServerList(NodeType.WORKER), workerHost);
	//根據(jù)異常的worker的信息查詢需要容錯的任務實例,獲取只屬于當前master節(jié)點需要容錯的任務實例信息,這里也是和master不同的,并且容錯沒加鎖的原因。
	final List<TaskInstance> needFailoverTaskInstanceList = getNeedFailoverTaskInstance(workerHost);
	if (CollectionUtils.isEmpty(needFailoverTaskInstanceList)) {
		LOGGER.info("Worker[{}] failover finished there are no taskInstance need to failover", workerHost);
		return;
	}
	LOGGER.info(
			"Worker[{}] failover there are {} taskInstance may need to failover, will do a deep check, taskInstanceIds: {}",
			workerHost,
			needFailoverTaskInstanceList.size(),
			needFailoverTaskInstanceList.stream().map(TaskInstance::getId).collect(Collectors.toList()));
	final Map<Integer, ProcessInstance> processInstanceCacheMap = new HashMap<>();
	for (TaskInstance taskInstance : needFailoverTaskInstanceList) {
		LoggerUtils.setWorkflowAndTaskInstanceIDMDC(taskInstance.getProcessInstanceId(), taskInstance.getId());
		try {
			ProcessInstance processInstance = processInstanceCacheMap.computeIfAbsent(
					taskInstance.getProcessInstanceId(), k -> {
						WorkflowExecuteRunnable workflowExecuteRunnable = cacheManager.getByProcessInstanceId(
								taskInstance.getProcessInstanceId());
						if (workflowExecuteRunnable == null) {
							return null;
						}
						return workflowExecuteRunnable.getProcessInstance();
					});
			//這里注意的是需要容錯的任務是在worker重新啟動之前的任務,之后worker異常重啟后分配的新任務不要容錯
			if (!checkTaskInstanceNeedFailover(needFailoverWorkerStartTime, processInstance, taskInstance)) {
				LOGGER.info("Worker[{}] the current taskInstance doesn't need to failover", workerHost);
				continue;
			}
			LOGGER.info(
					"Worker[{}] failover: begin to failover taskInstance, will set the status to NEED_FAULT_TOLERANCE",
					workerHost);
			failoverTaskInstance(processInstance, taskInstance);
			LOGGER.info("Worker[{}] failover: Finish failover taskInstance", workerHost);
		} catch (Exception ex) {
			LOGGER.info("Worker[{}] failover taskInstance occur exception", workerHost, ex);
		} finally {
			LoggerUtils.removeWorkflowAndTaskInstanceIdMDC();
		}
	}
	failoverTimeCost.stop();
	LOGGER.info("Worker[{}] failover finished, useTime:{}ms",
			workerHost,
			failoverTimeCost.getTime(TimeUnit.MILLISECONDS));
}

4-更新taskInstance的狀態(tài)為TaskExecutionStatus.NEED_FAULT_TOLERANCE。并且構造TaskStateEvent事件,設置其狀態(tài)為需要容TaskExecutionStatus.NEED_FAULT_TOLERANCE的,其類型是TASK_STATE_CHANGE。最后提交需要容錯的event。

private void failoverTaskInstance(@NonNull ProcessInstance processInstance, @NonNull TaskInstance taskInstance) {
	TaskMetrics.incTaskInstanceByState("failover");
	boolean isMasterTask = TaskProcessorFactory.isMasterTask(taskInstance.getTaskType());
	taskInstance.setProcessInstance(processInstance);
	if (!isMasterTask) {
		LOGGER.info("The failover taskInstance is not master task");
		TaskExecutionContext taskExecutionContext = TaskExecutionContextBuilder.get()
				.buildTaskInstanceRelatedInfo(taskInstance)
				.buildProcessInstanceRelatedInfo(processInstance)
				.buildProcessDefinitionRelatedInfo(processInstance.getProcessDefinition())
				.create();
		if (masterConfig.isKillYarnJobWhenTaskFailover()) {
			// only kill yarn job if exists , the local thread has exited
			LOGGER.info("TaskInstance failover begin kill the task related yarn job");
			ProcessUtils.killYarnJob(logClient, taskExecutionContext);
		}
	} else {
		LOGGER.info("The failover taskInstance is a master task");
	}
	taskInstance.setState(TaskExecutionStatus.NEED_FAULT_TOLERANCE);
	taskInstance.setFlag(Flag.NO);
	processService.saveTaskInstance(taskInstance);
        //提交event
	TaskStateEvent stateEvent = TaskStateEvent.builder()
			.processInstanceId(processInstance.getId())
			.taskInstanceId(taskInstance.getId())
			.status(TaskExecutionStatus.NEED_FAULT_TOLERANCE)
			.type(StateEventType.TASK_STATE_CHANGE)
			.build();
	workflowExecuteThreadPool.submitStateEvent(stateEvent);
}

event的提交會去根據(jù)其所屬的工作流實例來選擇其對應的WorkflowExecuteRunnable進行提交容錯:

public void submitStateEvent(StateEvent stateEvent) {
	WorkflowExecuteRunnable workflowExecuteThread =
			processInstanceExecCacheManager.getByProcessInstanceId(stateEvent.getProcessInstanceId());
	if (workflowExecuteThread == null) {
		logger.warn("Submit state event error, cannot from workflowExecuteThread from cache manager, stateEvent:{}",
				stateEvent);
		return;
	}
	workflowExecuteThread.addStateEvent(stateEvent);
	logger.info("Submit state event success, stateEvent: {}", stateEvent);
}

處理容錯event事件

在上面的代碼中已經(jīng)對需要容錯的任務提交了一個event事件,那么肯定會有線程對這個event進行具體的處理。我們來看WorkflowExecuteRunnable類,submitStateEvent就是將event提交到了這個類中的stateEvents隊列中:

private final ConcurrentLinkedQueue<StateEvent> stateEvents = new ConcurrentLinkedQueue<>();

WorkflowExecuteRunnable在master啟動的時候就已經(jīng)啟動了,并且會不停的從stateEvents中獲取event進行處理:

/**
 * handle event
 */
public void handleEvents() {
	if (!isStart()) {
		logger.info(
				"The workflow instance is not started, will not handle its state event, current state event size: {}",
				stateEvents);
		return;
	}
	StateEvent stateEvent = null;
	while (!this.stateEvents.isEmpty()) {
		try {
			stateEvent = this.stateEvents.peek();
			LoggerUtils.setWorkflowAndTaskInstanceIDMDC(stateEvent.getProcessInstanceId(),
					stateEvent.getTaskInstanceId());
			// if state handle success then will remove this state, otherwise will retry this state next time.
			// The state should always handle success except database error.
			checkProcessInstance(stateEvent);
			StateEventHandler stateEventHandler =
					StateEventHandlerManager.getStateEventHandler(stateEvent.getType())
							.orElseThrow(() -> new StateEventHandleError(
									"Cannot find handler for the given state event"));
			logger.info("Begin to handle state event, {}", stateEvent);
			if (stateEventHandler.handleStateEvent(this, stateEvent)) {
				this.stateEvents.remove(stateEvent);
			}
		} catch (StateEventHandleError stateEventHandleError) {
			logger.error("State event handle error, will remove this event: {}", stateEvent, stateEventHandleError);
			this.stateEvents.remove(stateEvent);
			ThreadUtils.sleep(Constants.SLEEP_TIME_MILLIS);
		} catch (StateEventHandleException stateEventHandleException) {
			logger.error("State event handle error, will retry this event: {}",
					stateEvent,
					stateEventHandleException);
			ThreadUtils.sleep(Constants.SLEEP_TIME_MILLIS);
		} catch (Exception e) {
			// we catch the exception here, since if the state event handle failed, the state event will still keep
			// in the stateEvents queue.
			logger.error("State event handle error, get a unknown exception, will retry this event: {}",
					stateEvent,
					e);
			ThreadUtils.sleep(Constants.SLEEP_TIME_MILLIS);
		} finally {
			LoggerUtils.removeWorkflowAndTaskInstanceIdMDC();
		}
	}
}

根據(jù)提交事件的類型StateEventType.TASK_STATE_CHANGE 可以獲取到具體的StateEventHandler實現(xiàn)是TaskStateEventHandler。在TaskStateEventHandler的handleStateEvent方法中主要對需要容錯的任務做了如下處理:

 if (task.getState().isFinished()) {
		if (completeTaskMap.containsKey(task.getTaskCode())
				&& completeTaskMap.get(task.getTaskCode()) == task.getId()) {
			logger.warn("The task instance is already complete, stateEvent: {}", stateEvent);
			return true;
		}
		workflowExecuteRunnable.taskFinished(task);
		if (task.getTaskGroupId() > 0) {
			logger.info("The task instance need to release task Group: {}", task.getTaskGroupId());
			workflowExecuteRunnable.releaseTaskGroup(task);
		}
		return true;
	}

其中判斷是否完成的具體實現(xiàn)中就包含了是否是容錯的狀態(tài)。

public boolean isFinished() {
	return isSuccess() || isKill() || isFailure() || isPause();
}
public boolean isFailure() {
	return this == TaskExecutionStatus.FAILURE || this == NEED_FAULT_TOLERANCE;
}

接著就會調(diào)用workflowExecuteRunnable.taskFinished(task);方法去處理各種任務實例狀態(tài)變化后的事件。這里我們只關注容錯相關的代碼分支:

} else if (taskInstance.taskCanRetry() && !processInstance.getState().isReadyStop()) {
			// retry task
			logger.info("Retry taskInstance taskInstance state: {}", taskInstance.getState());
			retryTaskInstance(taskInstance);
}
//判斷了是否容錯的狀態(tài),前面對其已經(jīng)進行了更新
public boolean taskCanRetry() {
	if (this.isSubProcess()) {
		return false;
	}
	if (this.getState() == TaskExecutionStatus.NEED_FAULT_TOLERANCE) {
		return true;
	}
	return this.getState() == TaskExecutionStatus.FAILURE && (this.getRetryTimes() < this.getMaxRetryTimes());
}
/**
 * crate new task instance to retry, different objects from the original
 *
 */
private void retryTaskInstance(TaskInstance taskInstance) throws StateEventHandleException {
	if (!taskInstance.taskCanRetry()) {
		return;
	}
	TaskInstance newTaskInstance = cloneRetryTaskInstance(taskInstance);
	if (newTaskInstance == null) {
		logger.error("Retry task fail because new taskInstance is null, task code:{}, task id:{}",
				taskInstance.getTaskCode(),
				taskInstance.getId());
		return;
	}
	waitToRetryTaskInstanceMap.put(newTaskInstance.getTaskCode(), newTaskInstance);
	if (!taskInstance.retryTaskIntervalOverTime()) {
		logger.info(
				"Failure task will be submitted, process id: {}, task instance code: {}, state: {}, retry times: {} / {}, interval: {}",
				processInstance.getId(), newTaskInstance.getTaskCode(),
				newTaskInstance.getState(), newTaskInstance.getRetryTimes(), newTaskInstance.getMaxRetryTimes(),
				newTaskInstance.getRetryInterval());
		stateWheelExecuteThread.addTask4TimeoutCheck(processInstance, newTaskInstance);
		stateWheelExecuteThread.addTask4RetryCheck(processInstance, newTaskInstance);
	} else {
		addTaskToStandByList(newTaskInstance);
		submitStandByTask();
		waitToRetryTaskInstanceMap.remove(newTaskInstance.getTaskCode());
	}
}

最終將需要容錯的任務實例重新加入到了readyToSubmitTaskQueue隊列中,重新進行submit:

addTaskToStandByList(newTaskInstance);
submitStandByTask();

后面就是和正常任務一樣處理了通過submitTaskExec方法提交任務到具體的worker執(zhí)行。

總結

對于Worker的容錯流程大致如下:

1-Master基于ZK的監(jiān)聽來感知需要容錯的Worker節(jié)點信息

2-每個Master只負責容錯屬于自己調(diào)度的工作流實例,在容錯前會比較實例的開始時間和服務節(jié)點的啟動時間,在服務啟動時間之后的則跳過容錯;

3-需要容錯的任務實例會重新加入到readyToSubmitTaskQueue,并提交運行。

到此,對于Worker的容錯,就到這里了,更多關于DolphinScheduler容錯Worker的資料請關注腳本之家其它相關文章!

相關文章

  • JMeter導入自定義的Jar包的詳解教程

    JMeter導入自定義的Jar包的詳解教程

    這篇文章主要介紹了JMeter導入自定義的Jar包的詳解教程,本文通過圖文并茂的形式給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-07-07
  • Java中l(wèi)ength,length(),size()詳解及區(qū)別

    Java中l(wèi)ength,length(),size()詳解及區(qū)別

    這篇文章主要介紹了Java中l(wèi)ength,length(),size()詳解及區(qū)別的相關資料,需要的朋友可以參考下
    2016-11-11
  • 謹慎使用Java8的默認方法

    謹慎使用Java8的默認方法

    為什么要謹慎使用Java8的默認方法?本文給出了為什么要慎用Java8默認方法的原因,解釋的很詳細,感興趣的朋友可以參考一下
    2016-01-01
  • 理解Java垃圾回收

    理解Java垃圾回收

    這篇文章主要幫助大家理解Java垃圾回收,通過實例學習java垃圾回收,什么是垃圾回收,感興趣的小伙伴們可以參考一下
    2016-03-03
  • spring.factories文件的解析源碼API機制詳解

    spring.factories文件的解析源碼API機制詳解

    通過本文深入探討Spring?Boot的背景歷史、業(yè)務場景、功能點以及底層原理,使讀者對Spring?Boot有了更深入的了解,結合實例代碼給大家介紹的非常詳細,感興趣的朋友跟隨小編一起看看吧
    2024-11-11
  • 深入淺析Java反射機制

    深入淺析Java反射機制

    Java反射機制是在運行狀態(tài)中,對于任意一個類,都能夠知道這個類的所有屬性和方法;對于任意一個對象,都能夠調(diào)用它的任意一個方法和屬性;這種動態(tài)獲取的信息以及動態(tài)調(diào)用對象的方法的功能稱為Java語言的反射機制
    2015-11-11
  • 詳解如何使用SpringBoot實現(xiàn)下載JSON文件

    詳解如何使用SpringBoot實現(xiàn)下載JSON文件

    在?Spring?Boot?中實現(xiàn)文件下載功能,可以通過將?JSON?字符串作為文件內(nèi)容返回給客戶端從而實現(xiàn)JSON文件下載效果,下面我們就來看看具體操作吧
    2025-02-02
  • 解決IDEA中不能正常輸入光標變粗的問題

    解決IDEA中不能正常輸入光標變粗的問題

    這篇文章主要介紹了在IDEA中不能正常輸入光標變粗的解決方法,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友參考下吧
    2020-09-09
  • mvn中dependencyManagement的使用詳解

    mvn中dependencyManagement的使用詳解

    這篇文章主要介紹了mvn中dependencyManagement的使用,子項目中只是聲明使用此依賴即可,可不用指定版本(將使用父pom同一指定的版本),若指定了版本,將以子項目的版本號為主,需要的朋友可以參考下
    2022-08-08
  • SpringSecurity 自定義認證登錄的項目實踐

    SpringSecurity 自定義認證登錄的項目實踐

    本文主要介紹了SpringSecurity 自定義認證登錄的項目實踐,以手機驗證碼登錄為例,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2024-08-08

最新評論

罗平县| 烟台市| 焉耆| 通山县| 石景山区| 安宁市| 罗甸县| 绥阳县| 历史| 家居| 通化县| 临安市| 潢川县| 巢湖市| 盘山县| 高碑店市| 文安县| 武强县| 墨玉县| 巫溪县| 衡南县| 靖宇县| 青海省| 防城港市| 新安县| 根河市| 昌黎县| 惠来县| 阿城市| 若尔盖县| 黄浦区| 西盟| 霍山县| 定边县| 临清市| 玛纳斯县| 洛阳市| 弥渡县| 遂川县| 温宿县| 临安市|