久久精品人人爽,华人av在线,亚洲性视频网站,欧美专区一二三

redis的底層原理是什么

146次閱讀
沒有評論

共計 11174 個字符,預計需要花費 28 分鐘才能閱讀完成。

這篇文章主要介紹“redis 的底層原理是什么”,在日常操作中,相信很多人在 redis 的底層原理是什么問題上存在疑惑,丸趣 TV 小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”redis 的底層原理是什么”的疑惑有所幫助!接下來,請跟著丸趣 TV 小編一起來學習吧!

Redis 核心對象

在 Redis 中有一個「核心的對象」叫做 redisObject,是用來表示所有的 key 和 value 的,用 redisObject 結構體來表示 String、Hash、List、Set、ZSet 五種數(shù)據類型。

redisObject 的源代碼在 redis.h 中,使用 c 語言寫的,感興趣的可以自行查看,關于 redisObject 我這里畫了一張圖,表示 redisObject 的結構如下所示:

在 redisObject 中「type 表示屬于哪種數(shù)據類型,encoding 表示該數(shù)據的存儲方式」,也就是底層的實現(xiàn)的該數(shù)據類型的數(shù)據結構。因此這篇文章具體介紹的也是 encoding 對應的部分。

那么 encoding 中的存儲類型又分別表示什么意思呢?具體數(shù)據類型所表示的含義,如下圖所示:

可能看完這圖,還是覺得一臉懵。不慌,會進行五種數(shù)據結構的詳細介紹,這張圖只是讓你找到每種中數(shù)據結構對應的儲存類型有哪些,大概腦子里有個印象。

舉一個簡單的例子,你在 Redis 中設置一個字符串 key 234,然后查看這個字符串的存儲類型就會看到為 int 類型,非整數(shù)型的使用的是 embstr 儲存類型,具體操作如下圖所示:

String 類型

String 是 Redis 最基本的數(shù)據類型,上面的簡介中也說到 Redis 是用 c 語言開發(fā)的。但是 Redis 中的字符串和 c 語言中的字符串類型卻是有明顯的區(qū)別。

String 類型的數(shù)據結構存儲方式有三種 int、raw、embstr。那么這三種存儲方式有什么區(qū)別呢?

int

Redis 中規(guī)定假如存儲的是「整數(shù)型值」,比如 set num 123 這樣的類型,就會使用 int 的存儲方式進行存儲,在 redisObject 的「ptr 屬性」中就會保存該值。

SDS

假如存儲的「字符串是一個字符串值并且長度大于 32 個字節(jié)」就會使用 SDS(simple dynamic string)方式進行存儲,并且 encoding 設置為 raw;若是「字符串長度小于等于 32 個字節(jié)」就會將 encoding 改為 embstr 來保存字符串。

SDS 稱為「簡單動態(tài)字符串」,對于 SDS 中的定義在 Redis 的源碼中有的三個屬性 int len、int free、char buf[]。

len 保存了字符串的長度,free 表示 buf 數(shù)組中未使用的字節(jié)數(shù)量,buf 數(shù)組則是保存字符串的每一個字符元素。

因此當你在 Redsi 中存儲一個字符串 Hello 時,根據 Redis 的源代碼的描述可以畫出 SDS 的形式的 redisObject 結構圖如下圖所示:

SDS 與 c 語言字符串對比

Redis 使用 SDS 作為存儲字符串的類型肯定是有自己的優(yōu)勢,SDS 與 c 語言的字符串相比,SDS 對 c 語言的字符串做了自己的設計和優(yōu)化,具體優(yōu)勢有以下幾點:

(1)c 語言中的字符串并不會記錄自己的長度,因此「每次獲取字符串的長度都會遍歷得到,時間的復雜度是 O(n)」,而 Redis 中獲取字符串只要讀取 len 的值就可,時間復雜度變?yōu)?O(1)。

(2)「c 語言」中兩個字符串拼接,若是沒有分配足夠長度的內存空間就「會出現(xiàn)緩沖區(qū)溢出的情況」;而「SDS」會先根據 len 屬性判斷空間是否滿足要求,若是空間不夠,就會進行相應的空間擴展,所以「不會出現(xiàn)緩沖區(qū)溢出的情況」。

(3)SDS 還提供「空間預分配」和「惰性空間釋放」兩種策略。在為字符串分配空間時,分配的空間比實際要多,這樣就能「減少連續(xù)的執(zhí)行字符串增長帶來內存重新分配的次數(shù)」。

當字符串被縮短的時候,SDS 也不會立即回收不適用的空間,而是通過 free 屬性將不使用的空間記錄下來,等后面使用的時候再釋放。

具體的空間預分配原則是:「當修改字符串后的長度 len 小于 1MB,就會預分配和 len 一樣長度的空間,即 len=free;若是 len 大于 1MB,free 分配的空間大小就為 1MB」。

(4)SDS 是二進制安全的,除了可以儲存字符串以外還可以儲存二進制文件(如圖片、音頻,視頻等文件的二進制數(shù)據);而 c 語言中的字符串是以空字符串作為結束符,一些圖片中含有結束符,因此不是二進制安全的。

為了方便易懂,做了一個 c 語言的字符串和 SDS 進行對比的表格,如下所示:

c 語言字符串  SDS  獲取長度的時間復雜度為 O(n) 獲取長度的時間復雜度為 O(1) 不是二進制安全的 是二進制安全的 只能保存字符串 還可以保存二進制數(shù)據 n 次增長字符串必然會帶來 n 次的內存分配 n 次增長字符串內存分配的次數(shù) =n

String 類型應用

說到這里我相信很多人可以說已經精通 Redis 的 String 類型了,但是純理論的精通,理論還是得應用實踐,上面說到 String 可以用來存儲圖片,現(xiàn)在就以圖片存儲作為案例實現(xiàn)。

(1)首先要把上傳得圖片進行編碼,這里寫了一個工具類把圖片處理成了 Base64 得編碼形式,具體得實現(xiàn)代碼如下:

/** *  將圖片內容處理成 Base64 編碼格式  * @param file * @return */ public static String encodeImg(MultipartFile file) { byte[] imgBytes = null; try { imgBytes = file.getBytes(); } catch (IOException e) { e.printStackTrace(); } BASE64Encoder encoder = new BASE64Encoder(); return imgBytes==null?null:encoder.encode(imgBytes );

復制代碼

(2)第二步就是把處理后的圖片字符串格式存儲進 Redis 中,實現(xiàn)得代碼如下所示: 

/** * Redis 存儲圖片  * @param file * @return */ public void uploadImageServiceImpl(MultipartFile image) { String imgId = UUID.randomUUID().toString(); String imgStr= ImageUtils.encodeImg(image); redisUtils.set(imgId , imgStr); //  后續(xù)操作可以把 imgId 存進數(shù)據庫對應的字段,如果需要從 redis 中取出,只要獲取到這個字段后從 redis 中取出即可。 }

這樣就是實現(xiàn)了圖片得二進制存儲,當然 String 類型得數(shù)據結構得應用也還有常規(guī)計數(shù):「統(tǒng)計微博數(shù)、統(tǒng)計粉絲數(shù)」等。

Hash 類型

Hash 對象的實現(xiàn)方式有兩種分別是 ziplist、hashtable,其中 hashtable 的存儲方式 key 是 String 類型的,value 也是以 key value 的形式進行存儲。

字典類型的底層就是 hashtable 實現(xiàn)的,明白了字典的底層實現(xiàn)原理也就是明白了 hashtable 的實現(xiàn)原理,hashtable 的實現(xiàn)原理可以于 HashMap 的是底層原理相類比。

字典

兩者在新增時都會通過 key 計算出數(shù)組下標,不同的是計算法方式不同,HashMap 中是以 hash 函數(shù)的方式,而 hashtable 中計算出 hash 值后,還要通過 sizemask 屬性和哈希值再次得到數(shù)組下標。

我們知道 hash 表最大的問題就是 hash 沖突,為了解決 hash 沖突,假如 hashtable 中不同的 key 通過計算得到同一個 index,就會形成單向鏈表(「鏈地址法」),如下圖所示:

rehash

在字典的底層實現(xiàn)中,value 對象以每一個 dictEntry 的對象進行存儲,當 hash 表中的存放的鍵值對不斷的增加或者減少時,需要對 hash 表進行一個擴展或者收縮。

這里就會和 HashMap 一樣也會就進行 rehash 操作,進行重新散列排布。從上圖中可以看到有 ht[0] 和 ht[1] 兩個對象,先來看看對象中的屬性是干嘛用的。

在 hash 表結構定義中有四個屬性分別是 dictEntry **table、unsigned long size、unsigned long sizemask、unsigned long used,分別表示的含義就是「哈希表數(shù)組、hash 表大小、用于計算索引值,總是等于 size-1、hash 表中已有的節(jié)點數(shù)」。

ht[0] 是用來最開始存儲數(shù)據的,當要進行擴展或者收縮時,ht[0] 的大小就決定了 ht[1] 的大小,ht[0] 中的所有的鍵值對就會重新散列到 ht[1] 中。

擴展操作:ht[1] 擴展的大小是比當前 ht[0].used 值的二倍大的第一個 2 的整數(shù)冪;收縮操作:ht[0].used 的第一個大于等于的 2 的整數(shù)冪。

當 ht[0] 上的所有的鍵值對都 rehash 到 ht[1] 中,會重新計算所有的數(shù)組下標值,當數(shù)據遷移完后 ht[0] 就會被釋放,然后將 ht[1] 改為 ht[0],并新創(chuàng)建 ht[1],為下一次的擴展和收縮做準備。

漸進式 rehash

假如在 rehash 的過程中數(shù)據量非常大,Redis 不是一次性把全部數(shù)據 rehash 成功,這樣會導致 Redis 對外服務停止,Redis 內部為了處理這種情況采用「漸進式的 rehash」。

Redis 將所有的 rehash 的操作分成多步進行,直到都 rehash 完成,具體的實現(xiàn)與對象中的 rehashindex 屬性相關,「若是 rehashindex 表示為 - 1 表示沒有 rehash 操作」。

當 rehash 操作開始時會將該值改成 0,在漸進式 rehash 的過程「更新、刪除、查詢會在 ht[0] 和 ht[1] 中都進行」,比如更新一個值先更新 ht[0],然后再更新 ht[1]。

而新增操作直接就新增到 ht[1] 表中,ht[0] 不會新增任何的數(shù)據,這樣保證「ht[0] 只減不增,直到最后的某一個時刻變成空表」,這樣 rehash 操作完成。

上面就是字典的底層 hashtable 的實現(xiàn)原理,說完了 hashtable 的實現(xiàn)原理,我們再來看看 Hash 數(shù)據結構的兩一種存儲方式「ziplist(壓縮列表)」

ziplist

壓縮列表(ziplist)是一組連續(xù)內存塊組成的順序的數(shù)據結構,壓縮列表能夠節(jié)省空間,壓縮列表中使用多個節(jié)點來存儲數(shù)據。

壓縮列表是列表鍵和哈希鍵底層實現(xiàn)的原理之一,「壓縮列表并不是以某種壓縮算法進行壓縮存儲數(shù)據,而是它表示一組連續(xù)的內存空間的使用,節(jié)省空間」,壓縮列表的內存結構圖如下:

壓縮列表中每一個節(jié)點表示的含義如下所示:

zlbytes:4 個字節(jié)的大小,記錄壓縮列表占用內存的字節(jié)數(shù)。

zltail:4 個字節(jié)大小,記錄表尾節(jié)點距離起始地址的偏移量,用于快速定位到尾節(jié)點的地址。

zllen:2 個字節(jié)的大小,記錄壓縮列表中的節(jié)點數(shù)。

entry:表示列表中的每一個節(jié)點。

zlend:表示壓縮列表的特殊結束符號 0xFF。

再壓縮列表中每一個 entry 節(jié)點又有三部分組成,包括 previous_entry_ength、encoding、content。

previous_entry_ength 表示前一個節(jié)點 entry 的長度,可用于計算前一個節(jié)點的其實地址,因為他們的地址是連續(xù)的。

encoding:這里保存的是 content 的內容類型和長度。

content:content 保存的是每一個節(jié)點的內容。

說到這里相信大家已經都 hash 這種數(shù)據結構已經非常了解,若是第一次接觸 Redis 五種基本數(shù)據結構的底層實現(xiàn)的話,建議多看幾遍,下面來說一說 hash 的應用場景。

應用場景

哈希表相對于 String 類型存儲信息更加直觀,擦歐總更加方便,經常會用來做用戶數(shù)據的管理,存儲用戶的信息。

hash 也可以用作高并發(fā)場景下使用 Redis 生成唯一的 id。下面我們就以這兩種場景用作案例編碼實現(xiàn)。

存儲用戶數(shù)據

第一個場景比如我們要儲存用戶信息,一般使用用戶的 ID 作為 key 值,保持唯一性,用戶的其他信息(地址、年齡、生日、電話號碼等)作為 value 值存儲。

若是傳統(tǒng)的實現(xiàn)就是將用戶的信息封裝成為一個對象,通過序列化存儲數(shù)據,當需要獲取用戶信息的時候,就會通過反序列化得到用戶信息。

但是這樣必然會造成序列化和反序列化的性能的開銷,并且若是只修改其中的一個屬性值,就需要把整個對象序列化出來,操作的動作太大,造成不必要的性能開銷。

若是使用 Redis 的 hash 來存儲用戶數(shù)據,就會將原來的 value 值又看成了一個 k v 形式的存儲容器,這樣就不會帶來序列化的性能開銷的問題。

分布式生成唯一 ID

第二個場景就是生成分布式的唯一 ID,這個場景下就是把 redis 封裝成了一個工具類進行實現(xiàn),實現(xiàn)的代碼如下: 

// offset 表示的是 id 的遞增梯度值  public Long getId(String key,String hashKey,Long offset) throws BusinessException{ try { if (null == offset) { offset=1L; } //  生成唯一 id return redisUtil.increment(key, hashKey, offset); } catch (Exception e) { // 若是出現(xiàn)異常就是用 uuid 來生成唯一的 id 值  int randNo=UUID.randomUUID().toString().hashCode(); if (randNo   0) { randNo=-randNo; } return Long.valueOf(String.format( %16d , randNo)); } }

List 類型

Redis 中的列表在 3.2 之前的版本是使用 ziplist 和 linkedlist 進行實現(xiàn)的。在 3.2 之后的版本就是引入了 quicklist。

ziplist 壓縮列表上面已經講過了,我們來看看 linkedlist 和 quicklist 的結構是怎么樣的。

linkedlist 是一個雙向鏈表,他和普通的鏈表一樣都是由指向前后節(jié)點的指針。插入、修改、更新的時間復雜度尾 O(1),但是查詢的時間復雜度確實 O(n)。

linkedlist 和 quicklist 的底層實現(xiàn)是采用鏈表進行實現(xiàn),在 c 語言中并沒有內置的鏈表這種數(shù)據結構,Redis 實現(xiàn)了自己的鏈表結構。

redis 的底層原理是什么

Redis 中鏈表的特性:

每一個節(jié)點都有指向前一個節(jié)點和后一個節(jié)點的指針。

頭節(jié)點和尾節(jié)點的 prev 和 next 指針指向為 null,所以鏈表是無環(huán)的。

鏈表有自己長度的信息,獲取長度的時間復雜度為 O(1)。

Redis 中 List 的實現(xiàn)比較簡單,下面我們就來看看它的應用場景。

應用場景

Redis 中的列表可以實現(xiàn)「阻塞隊列」,結合 lpush 和 brpop 命令就可以實現(xiàn)。生產者使用 lupsh 從列表的左側插入元素,消費者使用 brpop 命令從隊列的右側獲取元素進行消費。

(1)首先配置 redis 的配置,為了方便我就直接放在 application.yml 配置文件中,實際中可以把 redis 的配置文件放在一個 redis.properties 文件單獨放置,具體配置如下:

spring redis: host: 127.0.0.1 port: 6379 password: user timeout: 0 database: 2 pool: max-active: 100 max-idle: 10 min-idle: 0 max-wait: 100000

(2)第二步創(chuàng)建 redis 的配置類,叫做 RedisConfig,并標注上 @Configuration 注解,表明他是一個配置類。

@Configuration public class RedisConfiguration { @Value( {spring.redis.port} ) private int port; @Value({spring.redis.pool.max-active} ) private int maxActive; @Value({spring.redis.pool.min-idle} ) private int minIdle; @Value({spring.redis.database} ) private int database; @Value(${spring.redis.timeout} ) private int timeout; @Bean public JedisPoolConfig getRedisConfiguration(){ JedisPoolConfig jedisPoolConfig= new JedisPoolConfig(); jedisPoolConfig.setMaxTotal(maxActive); jedisPoolConfig.setMaxIdle(maxIdle); jedisPoolConfig.setMinIdle(minIdle); jedisPoolConfig.setMaxWaitMillis(maxWait); return jedisPoolConfig; } @Bean public JedisConnectionFactory getConnectionFactory() { JedisConnectionFactory factory = new JedisConnectionFactory(); factory.setHostName(host); factory.setPort(port); factory.setPassword(password); factory.setDatabase(database); JedisPoolConfig jedisPoolConfig= getRedisConfiguration(); factory.setPoolConfig(jedisPoolConfig); return factory; } @Bean public RedisTemplate ?, ?  getRedisTemplate() { JedisConnectionFactory factory = getConnectionFactory(); RedisTemplate ?, ?  redisTemplate = new StringRedisTemplate(factory); return redisTemplate; } }

(3)第三步就是創(chuàng)建 Redis 的工具類 RedisUtil,自從學了面向對象后,就喜歡把一些通用的東西拆成工具類,好像一個一個零件,需要的時候,就把它組裝起來。

@Component public class RedisUtil { @Autowired private RedisTemplate String, Object  redisTemplate; /**  存消息到消息隊列中  @param key  鍵  @param value  值  @return */ public boolean lPushMessage(String key, Object value) { try { redisTemplate.opsForList().leftPush(key, value); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /**  從消息隊列中彈出消息  -  rpop:非阻塞式  @param key  鍵  @return */ public Object rPopMessage(String key) { try { return redisTemplate.opsForList().rightPop(key); } catch (Exception e) { e.printStackTrace(); return null; } } /**  查看消息  @param key  鍵  @param start  開始  @param end  結束  0  到  - 1 代表所有值   復制代碼 @return */ public List Object  getMessage(String key, long start, long end) { try { return redisTemplate.opsForList().range(key, start, end); } catch (Exception e) { e.printStackTrace(); return null; } }

這樣就完成了 Redis 消息隊列工具類的創(chuàng)建,在后面的代碼中就可以直接使用。

Set 集合

Redis 中列表和集合都可以用來存儲字符串,但是「Set 是不可重復的集合,而 List 列表可以存儲相同的字符串」,Set 集合是無序的這個和后面講的 ZSet 有序集合相對。

Set 的底層實現(xiàn)是「ht 和 intset」,ht(哈希表)前面已經詳細了解過,下面我們來看看 inset 類型的存儲結構。

inset 也叫做整數(shù)集合,用于保存整數(shù)值的數(shù)據結構類型,它可以保存 int16_t、int32_t 或者 int64_t 的整數(shù)值。

在整數(shù)集合中,有三個屬性值 encoding、length、contents[],分別表示編碼方式、整數(shù)集合的長度、以及元素內容,length 就是記錄 contents 里面的大小。

在整數(shù)集合新增元素的時候,若是超出了原集合的長度大小,就會對集合進行升級,具體的升級過程如下:

首先擴展底層數(shù)組的大小,并且數(shù)組的類型為新元素的類型。

然后將原來的數(shù)組中的元素轉為新元素的類型,并放到擴展后數(shù)組對應的位置。

整數(shù)集合升級后就不會再降級,編碼會一直保持升級后的狀態(tài)。

應用場景

Set 集合的應用場景可以用來「去重、抽獎、共同好友、二度好友」等業(yè)務類型。接下來模擬一個添加好友的案例實現(xiàn):

@RequestMapping(value =  /addFriend , method = RequestMethod.POST) public Long addFriend(User user, String friend) { String currentKey = null; //  判斷是否是當前用戶的好友  if (AppContext.getCurrentUser().getId().equals(user.getId)) { currentKey = user.getId.toString(); } // 若是返回 0 則表示不是該用戶好友  return currentKey==null?0l:setOperations.add(currentKey, friend); }

假如兩個用戶 A 和 B 都是用上上面的這個接口添加了很多的自己的好友,那么有一個需求就是要實現(xiàn)獲取 A 和 B 的共同好友,那么可以進行如下操作:

public Set intersectFriend(User userA, User userB) { return setOperations.intersect(userA.getId.toString(), userB.getId.toString()); }

舉一反三,還可以實現(xiàn) A 用戶自己的好友,或者 B 用戶自己的好友等,都可以進行實現(xiàn)。

ZSet 集合

ZSet 是有序集合,從上面的圖中可以看到 ZSet 的底層實現(xiàn)是 ziplist 和 skiplist 實現(xiàn)的,ziplist 上面已經詳細講過,這里來講解 skiplist 的結構實現(xiàn)。

skiplist 也叫做「跳躍表」,跳躍表是一種有序的數(shù)據結構,它通過每一個節(jié)點維持多個指向其它節(jié)點的指針,從而達到快速訪問的目的。

skiplist 由如下幾個特點:

有很多層組成,由上到下節(jié)點數(shù)逐漸密集,最上層的節(jié)點最稀疏,跨度也最大。

每一層都是一個有序鏈表,只掃包含兩個節(jié)點,頭節(jié)點和尾節(jié)點。

每一層的每一個每一個節(jié)點都含有指向同一層下一個節(jié)點和下一層同一個位置節(jié)點的指針。

如果一個節(jié)點在某一層出現(xiàn),那么該以下的所有鏈表同一個位置都會出現(xiàn)該節(jié)點。

具體實現(xiàn)的結構圖如下所示:

redis 的底層原理是什么

在跳躍表的結構中有 head 和 tail 表示指向頭節(jié)點和尾節(jié)點的指針,能后快速的實現(xiàn)定位。level 表示層數(shù),len 表示跳躍表的長度,BW 表示后退指針,在從尾向前遍歷的時候使用。

BW 下面還有兩個值分別表示分值(score)和成員對象(各個節(jié)點保存的成員對象)。

跳躍表的實現(xiàn)中,除了最底層的一層保存的是原始鏈表的完整數(shù)據,上層的節(jié)點數(shù)會越來越少,并且跨度會越來越大。

跳躍表的上面層就相當于索引層,都是為了找到最后的數(shù)據而服務的,數(shù)據量越大,條表所體現(xiàn)的查詢的效率就越高,和平衡樹的查詢效率相差無幾。

應用場景

因為 ZSet 是有序的集合,因此 ZSet 在實現(xiàn)排序類型的業(yè)務是比較常見的,比如在首頁推薦 10 個最熱門的帖子,也就是閱讀量由高到低,排行榜的實現(xiàn)等業(yè)務。

下面就選用獲取排行榜前前 10 名的選手作為案例實現(xiàn),實現(xiàn)的代碼如下所示:

@Autowired private RedisTemplate redisTemplate; /** *  獲取前 10 排名  * @return */ public static List levelVO   getZset(String key, long baseNum, LevelService levelService){ ZSetOperations Serializable, Object  operations = redisTemplate.opsForZSet(); //  根據 score 分數(shù)值獲取前 10 名的數(shù)據  Set ZSetOperations.TypedTuple Object  set = operations.reverseRangeWithScores(key,0,9); List LevelVO  list= new ArrayList LevelVO  int i=1; for (ZSetOperations.TypedTuple Object  o:set){ int uid = (int) o.getValue(); LevelCache levelCache = levelService.getLevelCache(uid); LevelVO levelVO = levelCache.getLevelVO(); long score = (o.getScore().longValue() - baseNum + levelVO .getCtime())/CommonUtil.multiplier; levelVO .setScore(score); levelVO .setRank(i); list.add( levelVO ); i++; } return list; }

以上的代碼實現(xiàn)大致邏輯就是根據 score 分數(shù)值獲取前 10 名的數(shù)據,然后封裝成 lawyerVO 對象的列表進行返回。

到此,關于“redis 的底層原理是什么”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續(xù)學習更多相關知識,請繼續(xù)關注丸趣 TV 網站,丸趣 TV 小編會繼續(xù)努力為大家?guī)砀鄬嵱玫奈恼拢?/p/>

正文完
 
丸趣
版權聲明:本站原創(chuàng)文章,由 丸趣 2023-07-15發(fā)表,共計11174字。
轉載說明:除特殊說明外本站除技術相關以外文章皆由網絡搜集發(fā)布,轉載請注明出處。
評論(沒有評論)
主站蜘蛛池模板: 新郑市| 南宁市| 工布江达县| 乌兰察布市| 页游| 郴州市| 玉门市| 彰武县| 吉林市| 石楼县| 棋牌| 海南省| 汕尾市| 益阳市| 延津县| 凉城县| 仪陇县| 沾化县| 本溪| 印江| 永福县| 昭觉县| 云龙县| 延寿县| 南充市| 平塘县| 赞皇县| 潜江市| 云龙县| 贡山| 苏尼特左旗| 通州市| 临湘市| 美姑县| 宝丰县| 安溪县| 淳安县| 晋城| 东丰县| 牟定县| 越西县|