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

SpringBoot多級緩存實現(xiàn)方案總結(jié)

 更新時間:2023年08月08日 10:33:44   作者:shepherd111  
所謂多級緩存,是指在整個系統(tǒng)架構(gòu)的不同系統(tǒng)層面進行數(shù)據(jù)緩存,以提升訪問速度,多級緩存就是為了解決項目服務(wù)中單一緩存使用不足的缺點,本文我們將給大家總結(jié)了SpringBoot多級緩存實現(xiàn)方案,需要的朋友可以參考下

1.背景

緩存,就是讓數(shù)據(jù)更接近使用者,讓訪問速度加快,從而提升系統(tǒng)性能。工作機制大概是先從緩存中加載數(shù)據(jù),如果沒有,再從慢速設(shè)備(eg:數(shù)據(jù)庫)中加載數(shù)據(jù)并同步到緩存中。

所謂多級緩存,是指在整個系統(tǒng)架構(gòu)的不同系統(tǒng)層面進行數(shù)據(jù)緩存,以提升訪問速度。主要分為三層緩存:網(wǎng)關(guān)nginx緩存、分布式緩存、本地緩存。這里的多級緩存就是用redis分布式緩存+caffeine本地緩存整合而來。

平時我們在開發(fā)過程中,一般都是使用redis實現(xiàn)分布式緩存、caffeine操作本地緩存,但是發(fā)現(xiàn)只使用redis或者是caffeine實現(xiàn)緩存都有一些問題:

  • 一級緩存:Caffeine是一個一個高性能的 Java 緩存庫;使用 Window TinyLfu 回收策略,提供了一個近乎最佳的命中率。優(yōu)點數(shù)據(jù)就在應(yīng)用內(nèi)存所以速度快。缺點受應(yīng)用內(nèi)存的限制,所以容量有限;沒有持久化,重啟服務(wù)后緩存數(shù)據(jù)會丟失;在分布式環(huán)境下緩存數(shù)據(jù)數(shù)據(jù)無法同步;
  • 二級緩存:redis是一高性能、高可用的key-value數(shù)據(jù)庫,支持多種數(shù)據(jù)類型,支持集群,和應(yīng)用服務(wù)器分開部署易于橫向擴展。優(yōu)點支持多種數(shù)據(jù)類型,擴容方便;有持久化,重啟應(yīng)用服務(wù)器緩存數(shù)據(jù)不會丟失;他是一個集中式緩存,不存在在應(yīng)用服務(wù)器之間同步數(shù)據(jù)的問題。缺點每次都需要訪問redis存在IO浪費的情況。

綜上所述,我們可以通過整合redis和caffeine實現(xiàn)多級緩存,解決上面單一緩存的痛點,從而做到相互補足。

2.整合實現(xiàn)

2.1思路

Spring 本來就提供了Cache的支持,最核心的就是實現(xiàn)Cache和CacheManager接口。但是Spring Cache存在以下問題:

  • Spring Cache 僅支持單一的緩存來源,即:只能選擇 Redis 實現(xiàn)或者 Caffeine 實現(xiàn),并不能同時使用。
  • 數(shù)據(jù)一致性:各層緩存之間的數(shù)據(jù)一致性問題,如應(yīng)用層緩存和分布式緩存之前的數(shù)據(jù)一致性問題。

由此我們可以通過重新實現(xiàn)Cache和CacheManager接口,整合redis和caffeine,從而實現(xiàn)多級緩存。在講實現(xiàn)原理之前先看看多級緩存調(diào)用邏輯圖:

2.2實現(xiàn)

首先,我們需要一個多級緩存配置類,方便對緩存屬性的動態(tài)配置,通過開關(guān)做到可插拔。

@ConfigurationProperties(prefix = "multilevel.cache")
@Data
public class MultilevelCacheProperties {
?
 ? ?/**
 ? ? * 一級本地緩存最大比例
 ? ? */
 ? ?private Double maxCapacityRate = 0.2;
?
 ? ?/**
 ? ? * 一級本地緩存與最大緩存初始化大小比例
 ? ? */
 ? ?private Double initRate = 0.5;
?
 ? ?/**
 ? ? * 消息主題
 ? ? */
 ? ?private String topic = "multilevel-cache-topic";
?
 ? ?/**
 ? ? * 緩存名稱
 ? ? */
 ? ?private String name = "multilevel-cache";
?
 ? ?/**
 ? ? * 一級本地緩存名稱
 ? ? */
 ? ?private String caffeineName = "multilevel-caffeine-cache";
?
 ? ?/**
 ? ? * 二級緩存名稱
 ? ? */
 ? ?private String redisName = "multilevel-redis-cache";
?
 ? ?/**
 ? ? * 一級本地緩存過期時間
 ? ? */
 ? ?private Integer caffeineExpireTime = 300;
?
 ? ?/**
 ? ? * 二級緩存過期時間
 ? ? */
 ? ?private Integer redisExpireTime = 600;
?
?
 ? ?/**
 ? ? * 一級緩存開關(guān)
 ? ? */
 ? ?private Boolean caffeineSwitch = true;
?
}

在自動配置類使用 @EnableConfigurationProperties(MultilevelCacheProperties.class) 注入即可使用。

接下來就是重新實現(xiàn)spring的Cache接口,整合caffeine本地緩存和redis分布式緩存實現(xiàn)多級緩存

package com.plasticene.boot.cache.core.manager;
?
import com.plasticene.boot.cache.core.listener.CacheMessage;
import com.plasticene.boot.cache.core.prop.MultilevelCacheProperties;
import com.plasticene.boot.common.executor.plasticeneThreadExecutor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.caffeine.CaffeineCache;
import org.springframework.cache.support.AbstractValueAdaptingCache;
import org.springframework.data.redis.cache.RedisCache;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.lang.NonNull;
import org.springframework.util.Assert;
?
import javax.annotation.Resource;
import java.util.Objects;
import java.util.concurrent.*;
?
/**
 * @author fjzheng
 * @version 1.0
 * @date 2022/7/20 17:03
 */
@Slf4j
public class MultilevelCache extends AbstractValueAdaptingCache {
?
 ? ?@Resource
 ? ?private MultilevelCacheProperties multilevelCacheProperties;
 ? ?@Resource
 ? ?private RedisTemplate redisTemplate;
?
?
 ? ?ExecutorService cacheExecutor = new plasticeneThreadExecutor(
 ? ? ? ? ? ?Runtime.getRuntime().availableProcessors() * 2,
 ? ? ? ? ? ?Runtime.getRuntime().availableProcessors() * 20,
 ? ? ? ? ? ?Runtime.getRuntime().availableProcessors() * 200,
 ? ? ? ? ? "cache-pool"
 ?  );
?
 ? ?private RedisCache redisCache;
 ? ?private CaffeineCache caffeineCache;
?
 ? ?public MultilevelCache(boolean allowNullValues,RedisCache redisCache, CaffeineCache caffeineCache) {
 ? ? ? ?super(allowNullValues);
 ? ? ? ?this.redisCache = redisCache;
 ? ? ? ?this.caffeineCache = caffeineCache;
 ?  }
?
?
 ? ?@Override
 ? ?public String getName() {
 ? ? ? ?return multilevelCacheProperties.getName();
?
 ?  }
?
 ? ?@Override
 ? ?public Object getNativeCache() {
 ? ? ? ?return null;
 ?  }
?
 ? ?@Override
 ? ?public <T> T get(Object key, Callable<T> valueLoader) {
 ? ? ? ?Object value = lookup(key);
 ? ? ? ?return (T) value;
 ?  }
?
 ? ?/**
 ? ? *  注意:redis緩存的對象object必須序列化 implements Serializable, 不然緩存對象不成功。
 ? ? *  注意:這里asyncPublish()方法是異步發(fā)布消息,然后讓分布式其他節(jié)點清除本地緩存,防止當(dāng)前節(jié)點因更新覆蓋數(shù)據(jù)而其他節(jié)點本地緩存保存是臟數(shù)據(jù)
 ? ? *  這樣本地緩存數(shù)據(jù)才能成功存入
 ? ? * @param key
 ? ? * @param value
 ? ? */
 ? ?@Override
 ? ?public void put(@NonNull Object key, Object value) {
 ? ? ? ?redisCache.put(key, value);
 ? ? ? ?// 異步清除本地緩存
 ? ? ? ?if (multilevelCacheProperties.getCaffeineSwitch()) {
 ? ? ? ? ? ?asyncPublish(key, value);
 ? ? ?  }
 ?  }
?
 ? ?/**
 ? ? * key不存在時,再保存,存在返回當(dāng)前值不覆蓋
 ? ? * @param key
 ? ? * @param value
 ? ? * @return
 ? ? */
 ? ?@Override
 ? ?public ValueWrapper putIfAbsent(@NonNull Object key, Object value) {
 ? ? ? ?ValueWrapper valueWrapper = redisCache.putIfAbsent(key, value);
 ? ? ? ?// 異步清除本地緩存
 ? ? ? ?if (multilevelCacheProperties.getCaffeineSwitch()) {
 ? ? ? ? ? ?asyncPublish(key, value);
 ? ? ?  }
 ? ? ? ?return valueWrapper;
 ?  }
?
?
 ? ?@Override
 ? ?public void evict(Object key) {
 ? ? ? ?// 先清除redis中緩存數(shù)據(jù),然后通過消息推送清除所有節(jié)點caffeine中的緩存,
 ? ? ? ?// 避免短時間內(nèi)如果先清除caffeine緩存后其他請求會再從redis里加載到caffeine中
 ? ? ? ?redisCache.evict(key);
 ? ? ? ?// 異步清除本地緩存
 ? ? ? ?if (multilevelCacheProperties.getCaffeineSwitch()) {
 ? ? ? ? ? ?asyncPublish(key, null);
 ? ? ?  }
 ?  }
?
 ? ?@Override
 ? ?public boolean evictIfPresent(Object key) {
 ? ? ? ?return false;
 ?  }
?
 ? ?@Override
 ? ?public void clear() {
 ? ? ? ?redisCache.clear();
 ? ? ? ?// 異步清除本地緩存
 ? ? ? ?if (multilevelCacheProperties.getCaffeineSwitch()) {
 ? ? ? ? ? ?asyncPublish(null, null);
 ? ? ?  }
 ?  }
?
?
?
 ? ?@Override
 ? ?protected Object lookup(Object key) {
 ? ? ? ?Assert.notNull(key, "key不可為空");
 ? ? ? ?ValueWrapper value;
 ? ? ? ?if (multilevelCacheProperties.getCaffeineSwitch()) {
 ? ? ? ? ? ?// 開啟一級緩存,先從一級緩存緩存數(shù)據(jù)
 ? ? ? ? ? ?value = caffeineCache.get(key);
 ? ? ? ? ? ?if (Objects.nonNull(value)) {
 ? ? ? ? ? ? ? ?log.info("查詢caffeine 一級緩存 key:{}, 返回值是:{}", key, value.get());
 ? ? ? ? ? ? ? ?return value.get();
 ? ? ? ? ?  }
 ? ? ?  }
 ? ? ? ?value = redisCache.get(key);
 ? ? ? ?if (Objects.nonNull(value)) {
 ? ? ? ? ? ?log.info("查詢redis 二級緩存 key:{}, 返回值是:{}", key, value.get());
 ? ? ? ? ? ?// 異步將二級緩存redis寫到一級緩存caffeine
 ? ? ? ? ? ?if (multilevelCacheProperties.getCaffeineSwitch()) {
 ? ? ? ? ? ? ? ?ValueWrapper finalValue = value;
 ? ? ? ? ? ? ? ?cacheExecutor.execute(()->{
 ? ? ? ? ? ? ? ? ? ?caffeineCache.put(key, finalValue.get());
 ? ? ? ? ? ? ?  });
 ? ? ? ? ?  }
 ? ? ? ? ? ?return value.get();
 ? ? ?  }
 ? ? ? ?return null;
 ?  }
?
 ? ?/**
 ? ? * 緩存變更時通知其他節(jié)點清理本地緩存
 ? ? * 異步通過發(fā)布訂閱主題消息,其他節(jié)點監(jiān)聽到之后進行相關(guān)本地緩存操作,防止本地緩存臟數(shù)據(jù)
 ? ? */
 ? ?void asyncPublish(Object key, Object value) {
 ? ? ? ?cacheExecutor.execute(()->{
 ? ? ? ? ? ?CacheMessage cacheMessage = new CacheMessage();
 ? ? ? ? ? ?cacheMessage.setCacheName(multilevelCacheProperties.getName());
 ? ? ? ? ? ?cacheMessage.setKey(key);
 ? ? ? ? ? ?cacheMessage.setValue(value);
 ? ? ? ? ? ?redisTemplate.convertAndSend(multilevelCacheProperties.getTopic(), cacheMessage);
 ? ? ?  });
 ?  }
?
?
?
}
?

緩存消息監(jiān)聽:我們通監(jiān)聽caffeine鍵值的移除、打印日志方便排查問題,通過監(jiān)聽redis發(fā)布的消息,實現(xiàn)分布式集群多節(jié)點本地緩存清除從而達(dá)到數(shù)據(jù)一致性。

消息體

@Data
public class CacheMessage implements Serializable {
 ? ?private String cacheName;
 ? ?private Object key;
 ? ?private Object value;
 ? ?private Integer type;
}

caffeine移除監(jiān)聽:

@Slf4j
public class CaffeineCacheRemovalListener implements RemovalListener<Object, Object> {
 ? ?@Override
 ? ?public void onRemoval(@Nullable Object k, @Nullable Object v, @NonNull RemovalCause cause) {
 ? ? ? ?log.info("[移除緩存] key:{} reason:{}", k, cause.name());
 ? ? ? ?// 超出最大緩存
 ? ? ? ?if (cause == RemovalCause.SIZE) {
?
 ? ? ?  }
 ? ? ? ?// 超出過期時間
 ? ? ? ?if (cause == RemovalCause.EXPIRED) {
 ? ? ? ? ? ?// do something
 ? ? ?  }
 ? ? ? ?// 顯式移除
 ? ? ? ?if (cause == RemovalCause.EXPLICIT) {
 ? ? ? ? ? ?// do something
 ? ? ?  }
 ? ? ? ?// 舊數(shù)據(jù)被更新
 ? ? ? ?if (cause == RemovalCause.REPLACED) {
 ? ? ? ? ? ?// do something
 ? ? ?  }
 ?  }
}
?

redis消息監(jiān)聽:

@Slf4j
@Data
public class RedisCacheMessageListener implements MessageListener {
?
 ? ?private CaffeineCache caffeineCache;
 ? ?@Override
 ? ?public void onMessage(Message message, byte[] pattern) {
 ? ? ? ?log.info("監(jiān)聽的redis message: {}" + message.toString());
 ? ? ? ?CacheMessage cacheMessage = JsonUtils.parseObject(message.toString(), CacheMessage.class);
 ? ? ? ?if (Objects.isNull(cacheMessage.getKey())) {
 ? ? ? ? ? ?caffeineCache.invalidate();
 ? ? ?  } else {
 ? ? ? ? ? ?caffeineCache.evict(cacheMessage.getKey());
 ? ? ?  }
 ?  }
}

最后,通過自動配置類,注入相關(guān)bean:

**
 * @author fjzheng
 * @version 1.0
 * @date 2022/7/20 17:24
 */
@Configuration
@EnableConfigurationProperties(MultilevelCacheProperties.class)
public class MultilevelCacheAutoConfiguration {
?
 ? ?@Resource
 ? ?private MultilevelCacheProperties multilevelCacheProperties;
?
 ? ?ExecutorService cacheExecutor = new plasticeneThreadExecutor(
 ? ? ? ? ? ?Runtime.getRuntime().availableProcessors() * 2,
 ? ? ? ? ? ?Runtime.getRuntime().availableProcessors() * 20,
 ? ? ? ? ? ?Runtime.getRuntime().availableProcessors() * 200,
 ? ? ? ? ? ?"cache-pool"
 ?  );
?
 ? ?@Bean
 ? ?@ConditionalOnMissingBean({RedisTemplate.class})
 ? ?public ?RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory factory) {
 ? ? ? ?RedisTemplate<Object, Object> template = new RedisTemplate<Object, Object>();
 ? ? ? ?template.setConnectionFactory(factory);
 ? ? ? ?template.setKeySerializer(new StringRedisSerializer());
 ? ? ? ?template.setHashKeySerializer(new StringRedisSerializer());
 ? ? ? ?template.setDefaultSerializer(new Jackson2JsonRedisSerializer<>(Object.class));
 ? ? ? ?template.setHashValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class));
 ? ? ? ?return template;
 ?  }
?
 ? ?@Bean
 ? ?public RedisCache redisCache (RedisConnectionFactory redisConnectionFactory) {
 ? ? ? ?RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory);
 ? ? ? ?RedisCacheConfiguration redisCacheConfiguration = defaultCacheConfig();
 ? ? ? ?redisCacheConfiguration = redisCacheConfiguration.entryTtl(Duration.of(multilevelCacheProperties.getRedisExpireTime(), ChronoUnit.SECONDS));
 ? ? ? ?redisCacheConfiguration = redisCacheConfiguration.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()));
 ? ? ? ?redisCacheConfiguration = redisCacheConfiguration.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
 ? ? ? ?RedisCache redisCache = new CustomRedisCache(multilevelCacheProperties.getRedisName(), redisCacheWriter, redisCacheConfiguration);
 ? ? ? ?return redisCache;
 ?  }
?
 ? ?/**
 ? ? * 由于Caffeine 不會再值過期后立即執(zhí)行清除,而是在寫入或者讀取操作之后執(zhí)行少量維護工作,或者在寫入讀取很少的情況下,偶爾執(zhí)行清除操作。
 ? ? * 如果我們項目寫入或者讀取頻率很高,那么不用擔(dān)心。如果想入寫入和讀取操作頻率較低,那么我們可以通過Cache.cleanUp()或者加scheduler去定時執(zhí)行清除操作。
 ? ? * Scheduler可以迅速刪除過期的元素,***Java 9 +***后的版本,可以通過Scheduler.systemScheduler(), 調(diào)用系統(tǒng)線程,達(dá)到定期清除的目的
 ? ? * @return
 ? ? */
 ? ?@Bean
 ? ?@ConditionalOnClass(CaffeineCache.class)
 ? ?@ConditionalOnProperty(name = "multilevel.cache.caffeineSwitch", havingValue = "true", matchIfMissing = true)
 ? ?public CaffeineCache caffeineCache() {
 ? ? ? ?int maxCapacity = (int) (Runtime.getRuntime().totalMemory() * multilevelCacheProperties.getMaxCapacityRate());
 ? ? ? ?int initCapacity = (int) (maxCapacity * multilevelCacheProperties.getInitRate());
 ? ? ? ?CaffeineCache caffeineCache = new CaffeineCache(multilevelCacheProperties.getCaffeineName(), Caffeine.newBuilder()
 ? ? ? ? ? ? ? ?// 設(shè)置初始緩存大小
 ? ? ? ? ? ? ?  .initialCapacity(initCapacity)
 ? ? ? ? ? ? ? ?// 設(shè)置最大緩存
 ? ? ? ? ? ? ?  .maximumSize(maxCapacity)
 ? ? ? ? ? ? ? ?// 設(shè)置緩存線程池
 ? ? ? ? ? ? ?  .executor(cacheExecutor)
 ? ? ? ? ? ? ? ?// 設(shè)置定時任務(wù)執(zhí)行過期清除操作
// ? ? ? ? ? ? ?  .scheduler(Scheduler.systemScheduler())
 ? ? ? ? ? ? ? ?// 監(jiān)聽器(超出最大緩存)
 ? ? ? ? ? ? ?  .removalListener(new CaffeineCacheRemovalListener())
 ? ? ? ? ? ? ? ?// 設(shè)置緩存讀時間的過期時間
 ? ? ? ? ? ? ?  .expireAfterAccess(Duration.of(multilevelCacheProperties.getCaffeineExpireTime(), ChronoUnit.SECONDS))
 ? ? ? ? ? ? ? ?// 開啟metrics監(jiān)控
 ? ? ? ? ? ? ?  .recordStats()
 ? ? ? ? ? ? ?  .build());
 ? ? ? ?return caffeineCache;
 ?  }
?
 ? ?@Bean
 ? ?@ConditionalOnBean({CaffeineCache.class, RedisCache.class})
 ? ?public MultilevelCache multilevelCache(RedisCache redisCache, CaffeineCache caffeineCache) {
 ? ? ? ?MultilevelCache multilevelCache = new MultilevelCache(true, redisCache, caffeineCache);
 ? ? ? ?return multilevelCache;
 ?  }
?
 ? ?@Bean
 ? ?public RedisCacheMessageListener redisCacheMessageListener(@Autowired CaffeineCache caffeineCache) {
 ? ? ? ?RedisCacheMessageListener redisCacheMessageListener = new RedisCacheMessageListener();
 ? ? ? ?redisCacheMessageListener.setCaffeineCache(caffeineCache);
 ? ? ? ?return redisCacheMessageListener;
 ?  }
?
?
?
 ? ?@Bean
 ? ?public RedisMessageListenerContainer redisMessageListenerContainer(@Autowired RedisConnectionFactory redisConnectionFactory,
 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? @Autowired RedisCacheMessageListener redisCacheMessageListener) {
 ? ? ? ?RedisMessageListenerContainer redisMessageListenerContainer = new RedisMessageListenerContainer();
 ? ? ? ?redisMessageListenerContainer.setConnectionFactory(redisConnectionFactory);
 ? ? ? ?redisMessageListenerContainer.addMessageListener(redisCacheMessageListener, new ChannelTopic(multilevelCacheProperties.getTopic()));
 ? ? ? ?return redisMessageListenerContainer;
 ?  }
?
}
?

3.使用

使用非常簡單,只需要通過 multilevelCache 操作即可:

@RestController
@RequestMapping("/api/data")
@Api(tags = "api數(shù)據(jù)")
@Slf4j
public class ApiDataController {
?
 ? ?@Resource
 ? ?private MultilevelCache multilevelCache;
?
 ? ?@GetMapping("/put/cache")
 ? ?public void put() {
 ? ? ? ?DataSource ds = new DataSource();
 ? ? ? ?ds.setName("多級緩存");
 ? ? ? ?ds.setType(1);
 ? ? ? ?ds.setCreateTime(new Date());
 ? ? ? ?ds.setHost("127.0.0.1");
 ? ? ? ?multilevelCache.put("test-key", ds);
 ?  }
?
 ? ?@GetMapping("/get/cache")
 ? ?public DataSource get() {
 ? ? ? ?DataSource dataSource = multilevelCache.get("test-key", DataSource.class);
 ? ? ? ?return dataSource;
 ?  }
?
}

4.總結(jié)

以上全部就是關(guān)于多級緩存的實現(xiàn)方案總結(jié),多級緩存就是為了解決項目服務(wù)中單一緩存使用不足的缺點。應(yīng)用場景有:接口權(quán)限校驗,每次請求接口都需要根據(jù)當(dāng)前登錄人有哪些角色,角色有哪些權(quán)限,如果每次都去查數(shù)據(jù)庫性能開銷比較嚴(yán)重,再加上權(quán)限一般不怎么會頻繁變更,所以使用多級緩存是最合適不過了;還有就是很多管理系統(tǒng)列表界面都有組織架構(gòu)信息(所屬部門、小組等),這些信息同樣可以使用多級緩存來完美提升性能。

到此這篇關(guān)于SpringBoot多級緩存實現(xiàn)方案總結(jié)的文章就介紹到這了,更多相關(guān)SpringBoot實現(xiàn)多級緩存內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評論

泸定县| 资阳市| 白河县| 宁远县| 耒阳市| 文水县| 大厂| 开原市| 乐安县| 六枝特区| 民乐县| 天津市| 修文县| 荣成市| 获嘉县| 泗洪县| 油尖旺区| 同心县| 金平| 永登县| 莒南县| 山丹县| 东山县| 海林市| 镇原县| 开江县| 普安县| 苍南县| 本溪| 西峡县| 开阳县| 沿河| 新乡县| 合山市| 五原县| 中牟县| 台中市| 北宁市| 郑州市| 庆云县| 盐城市|