国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 開發 > 綜合 > 正文

編寫高性能Web應用程序的10個技巧

2024-07-21 02:24:51
字體:
來源:轉載
供稿:網友

作者:rob howard    譯:寒帶魚

這篇文章討論了:

·一般asp.net性能的秘密

·能提高asp.net表現的有用的技巧和竅門

·在asp.net中使用數據庫的建議

·asp.net中的緩存和后臺處理

  使用asp.net編寫一個web應用程序是難以置信的簡單的。太簡單了,以至于很多開發者都不花費時間來構建他們的應用程序來達到很好的表現。在這篇文章里,我將為編寫高性能的web應用程序推薦10個技巧。我不會講我的論述局限于asp.net應用程序,因為asp.net應用程序只是web應用程序的一個子集而已。這篇文章不會是針對優化web應用程序的性能的權威性指導——一本完整的書可以很容易的做到這一點。相反,我們應該把這篇文章當成一個好的起點。

  在成為一個工作狂以前,我會經常去攀巖。在做任何攀巖活動之前,我更愿意看看旅行指南里面的路線,再讀讀那些曾經到過峰頂的人做的推薦。但是,不管旅行指南寫的有多好,在嘗試一個有挑戰性的目標之前,您都需要有實際的攀巖經驗。與之相似,當您面臨修復性能問題或者運行一個高吞吐量站點的問題時,您只能學習如何編寫高性能 web 應用程序。

  我的個人經驗來自在微軟的asp.net團隊中擔任過一名基礎程序經理的經歷,維護和管理www.asp.net,還有幫助構架community server,它是幾個著名的asp.net應用程序(asp.net forums,.text,和連接到一個平臺的ngallery)的下一個版本。我相信這些曾經幫助過我技巧中的一些也會對您有用的。

  您應該考慮把您的應用程序分離為幾個邏輯層次。您可能已經聽說過3層(或者n層)體系結構。這些通常都是規定的結構模式,它們將業務和(或)硬件從物理上進行了功能劃分。如果系統需要更大的規模,更多的硬件可以輕松的加進來。然而,那會產生一個與業務和機器跳躍相關聯的性能下降,因此我們應該避免它。所以只要可能,盡量在同一個應用程序中運行asp.net頁面和頁面的相關組件。

  因為代碼的分離和層次之間的邊界,使用web服務或者遠程處理會降低性能20%甚至更多。

  數據層有點與眾不同,因為通常情況下,最好具有專用于數據庫的硬件。然而,然而進程跳躍到數據庫的成本依然很高,因此在數據層的性能是您優化代碼時應該首先考慮的。

  在投入到修復您的應用程序的性能問題之前,確保您要先分析您的應用程序來發現問題的根源所在。關鍵性能計數器(例如那個指示在執行垃圾收集過程中花費的時間百分比的計數器)在找出應用程序在哪里花費了主要的時間時也是非常有用的。雖然那些花費時間的地方經常是不那么直觀的。

  在這篇文章中我討論了兩種改進性能的方法:大塊的優化,例如使用asp.net緩存,還有小塊的優化,它們經常重復出現。這些小塊的優化有時是最有意思的。您對代碼的一個小的修改會被調用成千上萬次。對大塊的優化,您可能會發現整個的性能有了一個大的飛躍。對小塊的優化,您可能會縮減了對一個給定請求的幾微秒的時間,但是如果把每天的所有的請求累積起來,性能就會得到一個意想不到的改進。

數據層中的性能

  當您要開始優化一個應用程序的性能的時候,有一個決定性的測試您可以優先考慮使用:代碼是否要訪問數據庫?如果是,多長時間訪問一次?注意這個測試也可以應用到那些使用web服務或者遠程控制的代碼中,但是我不會在這篇文章中涉及那些內容。

  如果在您的代碼中的某個代碼路徑中要求一個數據庫請求,而您發現其他地方您想要優先優化,例如字符串操作,那么停下來然后先執行關鍵性的測試。除非您有一個性能實在糟糕的問題要處理,否則您的時間會得到更好的利用,如果您把時間花在優化數據庫連接的時間,返回的數據量,還有您作的往返數據庫的操作中。

  現在我已經總體介紹了相關的信息,下面讓我們看看10條幫您的應用程序表現更好的技巧。我會從那些對改善性能效果最明顯的地方開始說。

  技巧 1——返回多個結果集

  查看一下您的數據庫代碼,看看您是否有訪問數據庫多于一次的請求路徑(request paths)。每個這樣的往返都回降低您的應用程序每秒可以服務的請求的數量。通過在一次數據庫請求中返回多個結果集,您可以減少數據庫通信消耗的總時間。在您減少了數據庫服務器管理的請求之后,您也會使您的系統更具可升級性。

  一般您可以使用動態sql語句來返回多個結果集,我更喜歡用存儲過程。是否應該把業務邏輯放在存儲過程中是存在爭議的,但我認為如果一個存儲過程中的邏輯可以限制返回的數據(減少數據集的大小,花在網絡連接上的時間,并且不需要過濾邏輯層的數據),那它就是好東西。

  使用一個sqlcommand實例和它的executereader方法來生成強類型的業務類,您可以通過調用nextresult讓結果集指針向前移動。圖1展示了一個使用定義的類生成幾個arraylist的示例會話。只從數據庫返回您需要的數據會顯著地減少您服務器上的內存申請。

 1// read the first resultset2reader = command.executereader();34// read the data from that resultset5while (reader.read()) {6    suppliers.add(populatesupplierfromidatareader( reader ));7}89// read the next resultset
10reader.nextresult();
11
12// read the data from that second resultset
13while (reader.read()) {
14    products.add(populateproductfromidatareader( reader ));
15}
16
17

  技巧 2——分頁數據訪問

  asp.net的datagrid提供了一個非常棒的能力:對數據分頁的支持。當在datagrid中設置了分頁,那么將一次顯示一個特定數目的結果。此外,用來在結果之間導航的分頁ui也會在datagrid的底部顯示出來。分頁ui允許您在顯示的數據之間向前導航或者向后導航,每頁顯示特定數目的結果。

  但是有一個小問題。使用datagrid分頁時需要所有的數據都綁定到表格。例如,您的數據層會需要返回所有數據,然后datagrid要根據當前頁填充所有要顯示的記錄。如果當您在使用datagrid分頁時返回了100,000條記錄,每次請求都會丟棄99,975條記錄(假設每頁的容量是25條記錄)。當記錄的數量不斷增長時,應用程序的性能會受到很大的影響,因為每次請求都必須返回越來越多的數據。

  一個寫出更好的分頁代碼的辦法是使用存儲過程。圖2顯示了一個示例存儲過程,它為nothwind數據庫中的orders數據表分頁。總的來說,在這里所有您需要做的就是傳入頁的索引和頁的容量。數據庫會計算出適當的結果集然后返回它們。

 1create procedure northwind_orderspaged

2(

3    @pageindex int,

4    @pagesize int

5)

6as7begin

8declare @pagelowerbound int

9declare @pageupperbound int
10declare @rowstoreturn int
11
12-- first set the rowcount
13set @rowstoreturn = @pagesize * (@pageindex + 1)
14set rowcount @rowstoreturn
15
16-- set the page bounds
17set @pagelowerbound = @pagesize * @pageindex
18set @pageupperbound = @pagelowerbound + @pagesize + 1
19
20-- create a temp table to store the select results
21create table #pageindex
22(
23    indexid int identity (1, 1) not null,
24    orderid int
25)
26
27-- insert into the temp table
28insert into #pageindex (orderid)
29select
30    orderid
31from
32    orders
33order by
34    orderid desc
35
36-- return total count
37select count(orderid) from orders
38
39-- return paged results
40select
41    o.*
42from
43    orders o,
44    #pageindex pageindex
45where
46    o.orderid = pageindex.orderid and
47    pageindex.indexid > @pagelowerbound and
48    pageindex.indexid < @pageupperbound
49order by
50    pageindex.indexid
51
52end
53
54

  在社區服務期中,我們寫了一個分頁服務端控件來做這些數據分頁。您會發現我在使用技巧1中討論過的思想,從一個存儲過程返回兩個結果集:紀錄總數和請求的數據。

  返回的記錄總數可以根據執行的請求而有所不同。例如,一個where分句可以用來約束返回的數據。我們必須知道要返回的記錄總數,以計算要在分頁ui中顯示的總的頁數。例如,如果有1,000,000條總的記錄數,而一個where分句用來把這些記錄過濾為1,000條記錄,分頁邏輯需要知道總的記錄數來恰當的提交分頁ui。

  技巧 3——連接池

  在您的web應用程序和sql server之間建立tcp連接會是一個昂貴的操作。microsoft的開發者們已經利用連接池有一段時間了,這允許他們重用與數據庫的連接。與其為每個請求建立一個新的tcp連接,還不如只有在連接池中沒有一個可用的連接的時候才建立一個新的連接。當連接關閉后,它返回到連接池中——它還保持著與數據庫的連接,而不是完全銷毀那個tcp連接。

  

  當然您需要小心泄露的連接。總是關閉您的連接在您使用完它們時。我重復一遍:不管誰說了關于microsoft .net框架的垃圾回收機制的什么話,當您使用完時,您務必總是對您的連接顯式調用close或者dispose方法。不要相信通用語言運行時(clr)會在一個預定的時間為您清理和關閉您的連接。clr會最終銷毀類并且強迫連接關閉,但您不能保證什么時候在對象上的垃圾回收機制會真正執行。

  要想使用連接池達到最佳效果,您需要遵循幾條規則。第一,打開一個連接,完成工作,然后關閉連接。如果您不得不(最好應用技巧1)為每個請求打開和關閉幾次連接也是可以的,這比一直開著連接然后把它傳遞給幾個不同的方法要好得多。第二,使用同一個連接字符串(如果您在使用集成身份認證,當然還需要有相同的線程標識)。如果您不使用同一個連接字符串,例如基于登錄的用戶的不同自定義連接字符串,您就不能得到連接池提供的相同的最優值。而且如果您在模仿大量的用戶時使用了集成身份驗證,您的連接池的效率也會降低很多。在嘗試跟蹤任何與連接池有關的性能問題時,.net clr數據性能計數器會很有用的。

  不論何時您的應用程序連接一個資源,例如一個數據庫,或者在另一個進程中運行,您都應該通過把注意力集中到連接到資源所花費的時間上,發送和接受數據花費的時間,還有往返與數據庫的次數來進行優化。優化您的應用程序中的任何類型的進程跳轉(process hop)都是開始達到更好性能的第一步。

  應用層包含連接到您的數據層的邏輯,并且把數據轉換為有意義的類實例和邏輯過程。例如,在社區服務器中,這里是您生成一個論壇或者線程集合,并且應用業務規則例如許可的地方;更重要的是這里是執行緩沖邏輯的地方。

  技巧 4——asp.net緩沖api

  在您開始編寫應用程序的第一行代碼之前要考慮的第一件事情是,架構應用層來最大化并且利用asp.net的緩存特性。

  如果您的組件運行在一個asp.net應用程序之中,您只需要在您的應用程序項目中簡單的引用system.web.dll就可以了。當您需要訪問緩存時,使用httpruntime.cache屬性(這個對象也可以通過page.cache和httpcontext.cache來訪問)。

  使用緩存數據有幾條原則。第一,如果數據可以多次使用,那么緩存它就是一個好的選擇。第二,如果數據是通用的而不是給特定的請求或者用戶使用的,那么緩存它就是一個非常好的選擇。如果數據是用戶或者請求特定的,但是他的生存期是很長的,那么它也可以被緩存,但是可能不會經常使用到。第三,一個經常被忽視的原則是,有時候您可以緩存的太多了。通常在一臺x86計算機上,為了減少發生內存不足(out-of-memory)錯誤的可能性,您會希望運行一個使用不超過800mb私有字節的進程。因此,緩存應該受到限制。換句話說,您可能需要重新使用一次計算的結果,但是如果那個計算需要十個參數,您可能需要嘗試緩存10個排列,而這可能會給您帶來麻煩。由于過度緩存引起的內存不足錯誤是asp.net中最常見的,特別是對于大數據集的情況。

  緩存有幾個極佳的功能,您需要對它們有所了解。首先,緩存會實現最近最少使用的算法,使得 asp.net 能夠在內存運行效率較低的情況下強制緩存清除——從緩存自動刪除未使用過的項目。第二,緩存支持可以強制失效的過期依賴項。這些依賴項包括時間、鍵和文件。時間經常會用到,但是對于 asp.net 2.0,引入了一個功能更強的新失效類型:數據庫緩存失效。它指的是當數據庫中的數據發生變化時自動刪除緩存中的項。有關數據庫緩存失效的詳細信息,請參閱 msdn magazine 2004 年 7 月的 dino esposito cutting edge 專欄。要了解緩存的體系結構,請參閱圖 3。

  技巧 5 — 每請求緩存

  在本文前面部分,我提到了對經常遍歷代碼路徑的一些小改善可以獲得較大的整體性能收益。對于這些小改善,其中有一個絕對是我的最愛,我將其稱之為“每請求緩存”。

  緩存 api 的設計目的是為了將數據緩存較長的一段時間,或者緩存至滿足某些條件時,但每請求緩存則意味著只將數據緩存為該請求的持續時間。對于每個請求,要經常訪問某個特定的代碼路徑,但是數據卻只需提取、應用、修改或更新一次。這聽起來有些理論化,那么我們來舉一個具體的示例。

  在社區服務器的論壇應用程序中,頁面上使用的每個服務器控件都需要個性化的數據來確定使用什么外觀、使用什么樣式表,以及其他個性化數據。這些數據中有些可以長期緩存,但是有些數據卻只針對每個請求提取一次,然后在執行該請求期間對其重用多次,如要用于控件的外觀。

  為了達到每請求緩存,請使用 asp.net httpcontext。對于每個請求,都會創建一個 httpcontext 實例,在該請求期間從 httpcontext.current 屬性的任何位置都可訪問該實例。該 httpcontext 類具有一個特殊的 items 集合屬性;添加到此 items 集合的對象和數據只在該請求持續期間內進行緩存。正如您可以使用緩存來存儲經常訪問的數據一樣,您也可以使用 httpcontext.items 來存儲只基于每個請求使用的數據。它背后的邏輯非常簡單:數據在它不存在的時候添加到 httpcontext.items 集合,在后來的查找中,只是返回 httpcontext.items 中的數據。

  技巧 6 — 后臺處理

  通往代碼的路徑應該盡可能快速,是嗎?可能有時您會發現您正在執行的針對每個請求執行的或者每 n 個請求執行一次的任務所需資源非常多。發送電子郵件或者分析和驗證傳入數據就是這樣的一些例子。

  剖析 asp.net forums 1.0 并重新構建組成社區服務器的內容時,我們發現發布新帖子的代碼路徑非常慢。每次發布新帖子的時候,應用程序首先需要確保沒有重復的帖子,然后必須使用“壞詞”篩選器分析該帖子,分析帖子的字符圖釋,對帖子添加標記并進行索引,請求時將帖子添加到合適的隊列,驗證附件,最終在帖子發布之后,立即向所有訂閱者發出電子郵件通知。很清楚,這涉及很多操作。

  經研究發現,大多數時間都花在了索引邏輯和發送電子郵件上。對帖子進行索引是一個非常耗時的操作,人們發現內置的 system.web.mail 功能要連接 smtp 服務器,然后連續發送電子郵件。當某個特定帖子或主題領域的訂閱者數量增加時,執行 addpost 功能所需的時間也越來越長。

  并不需要針對每個請求都進行電子郵件索引。理想情況下,我們想要將此操作進行批處理,一次索引 25 個帖子或者每五分鐘發送一次所有電子郵件。我們決定使用我曾經用于對數據緩存失效進行原型設計的代碼,這個失效是最終被包含進了visual studio 2005之中。

  system.threading 命名空間中的 timer 類非常有用,但是在 .net framework 中不是很有名,至少對于 web 開發人員來說是這樣。創建之后,這個 timer 類將以一個可配置的間隔針對 threadpool 中的某個線程調用指定的回調。這就表示,您可以對代碼進行設置,使其能夠在沒有對 asp.net 應用程序進行傳入請求的情況下得以執行,這是后臺處理的理想情況。您還可以在此后臺進程中執行如索引或發送電子郵件之類的操作。

  但是,這一技術有幾個問題。如果應用程序域卸載,該計時器實例將停止激發事件。另外,因為 clr 對于每個進程的線程數量具有一個硬性標準,所以在負載很重的服務器可能會出現這樣的情形:其中的計時器可能不能保證線程繼續完成操作,并且在某種程度上可能會造成延遲。asp.net 通過在進程中保留一定數量的可用線程,并且僅使用總線程的一部分用于請求處理,試圖將上述情況發生的機會降到最低。但是,如果您具有很多異步操作時,這可能就是一個問題了。

  這里沒有足夠的空間來放置該代碼,但是您可以下載一個容易理解的示例,網址是www.rob-howard.net。請了解一下 blackbelt teched 2004 演示中的幻燈片和演示。

  技巧 7 — 頁輸出緩存和代理服務器

  asp.net 是您的表示層(或者說應該是您的表示層);它由頁、用戶控件、服務器控件(httphandlers 和 httpmodules)以及它們生成的內容組成。如果您具有一個 asp.net 頁,它會生成輸出(html、xml、圖像或任何其他數據),并且您針對每個請求運行此代碼時,它都會生成相同的輸出,那么您就擁有一個可用于頁輸出緩存的絕佳備選內容。

  通過將下面這行內容添加頁的最上端:

<%@ page outputcache varybyparams="none" duration="60" %>

  您就可以高效地為此頁生成一次輸出,然后對它進行多次重用,時間最長為 60 秒,此時該頁將重新執行,輸出也將再一次添加到 asp.net 緩存。通過使用一些低級別可編程api 也可以完成此行為。對于輸出緩存有幾個可配置的設置,如剛剛講到的 varybyparams 屬性。varybyparams 剛好被請求到,但還允許您指定 http get 或 http post 參數來更改緩存項。例如,只需設置 varybyparam="report" 即可對 default.aspx?report=1 或 default.aspx?report=2 進行輸出緩存。通過指定一個以分號分隔的列表,還可以指定其他參數。

  很多人還沒有意識到當使用了輸出緩存之后,asp.net 頁也會生成一些向下流到緩存服務器的 http 標題頭,如 microsoft internet security 和 acceleration server 或 akamai 使用的標題頭。設置了 http 緩存表題頭之后,可以在這些網絡資源上對文檔進行緩存,客戶端請求也可在不必返回原始服務器的情況下得以滿足。

  因此,使用頁輸出緩存不會使得您的應用程序效率更高,但是它可能會減少服務器上的負載,因為下行流緩存技術會緩存文檔。當然,這只能是匿名內容;一旦它成為下行流之后,您就再也不會看到這些請求,并且再也無法執行身份驗證以阻止對它的訪問了。

  技巧 8 — 運行 iis 6.0(哪怕只為了使用內核緩存也好)

  如果您未運行 iis 6.0 (windows server 2003),那么您就錯過了 microsoft web 服務器中的一些很好的性能增強。在技巧 7 中,我討論了輸出緩存。在 iis 5.0 中,請求是通過 iis 然后進入 asp.net 的。涉及到緩存時,asp.net 中的 httpmodule 會接收該請求,并返回緩存中的內容。

  如果您正在使用 iis 6.0,就會發現一個很好的小功能,稱為內核緩存,它不需要對 asp.net 進行任何代碼更改。當請求由 asp.net 進行輸出緩存時,iis 內核緩存會接收緩存數據的一個副本。當請求來自網絡驅動程序時,內核級別的驅動程序(無上下文切換到用戶模式)就會接收該請求,如果經過了緩存,則會將緩存的數據刷新到響應,然后完成執行。這就表示,當您將內核模式緩存與 iis 和 asp.net 輸出緩存一起使用時,就會看到令人不敢相信的性能結果。在 asp.net 的 visual studio 2005 開發過程中,我一度是負責 asp.net 性能的開發經理。開發人員完成具體工作,但是我要看到每天進行的所有報告。內核模式緩存結果總是最有意思的。最常見的特征是網絡充滿了請求/響應,而 iis 運行時的 cpu 使用率只有大約 5%。這太令人震驚了!當然使用 iis 6.0 還有一些其他原因,但是內核模式緩存是其中最明顯的一個。

  技巧 9 — 使用 gzip 壓縮

  雖然使用 gzip 并不一定是服務器性能技巧(因為您可能會看到 cpu 使用率的提高),但是使用 gzip 壓縮可以減少服務器發送的字節數量。這就使人們覺得頁速度加快了,并且還減少了帶寬的用量。根據所發送數據、可以壓縮的程度以及客戶端瀏覽器是否支持(iis 只會向支持 gzip 壓縮的客戶端發送經過 gzip 壓縮的內容,如 internet explorer 6.0 和 firefox),您的服務器每秒可以服務于更多的請求。實際上,幾乎每當您減少所返回數據的數量時,都會增加每秒請求數。

  gzip 壓縮已經內置到 iis 6.0 中,并且其性能比 iis 5.0 中使用的 gzip 壓縮要好的多,這是好消息。但不幸的是,當嘗試在 iis 6.0 中打開 gzip 壓縮時,您可能無法在 iis 的屬性對話中找到該設置。iis 小組在該服務器中置入了卓越的 gzip 功能,但是忘了包括一個用于啟用該功能的管理 ui。要啟用 gzip 壓縮,您必須深入到 iis 6.0 的 xml 配置設置內部(這樣不會引起心臟虛弱)。順便提一句,這歸功于 orcsweb 的 scott forsyth,他幫助我提出了在 orcsweb 上宿主的 www.asp.net 服務器的這個問題。

  本文就不講述步驟了,請閱讀 brad wilson 的文章,網址是 iis6 compression。還有一篇有關為 aspx 啟用壓縮的知識庫文章,網址是 enable aspx compression in iis。但是您應該注意,由于一些實施細節,iis 6.0 中不能同時存在動態壓縮和內核緩存。

  技巧 10 — 服務器控件視圖狀態

  視圖狀態是一個有趣的名稱,用于表示在所生成頁的隱藏輸出字段中存儲一些狀態數據的 asp.net。當該頁發回服務器時,服務器可以分析、驗證、并將此視圖狀態數據應用回該頁的控件樹。視圖狀態是一個非常強大的功能,因為它允許狀態與客戶端一起保持,并且它不需要 cookie 或服務器內存即可保存此狀態。很多 asp.net 服務器控件都使用視圖狀態來保持在與頁元素進行交互期間創建的設置,例如保存對數據進行分頁時顯示的當前頁。

  然而使用視圖狀態也有一些缺點。首先,當頁被服務或被請求時,它都會增加頁的總負載。對發回服務器的視圖狀態數據進行序列化或取消序列化時,也會發生額外的開銷。最后,視圖狀態會增加服務器上的內存分配。

  幾個服務器控件有過度使用視圖狀態的趨勢,即使在并不需要的情況下也要使用它,其中最著名的是 datagrid。viewstate 屬性的默認行為是啟用,但是如果您不需要,則可以在控件或頁級別關閉。在控件內,只需將 enableviewstate 屬性設置為 false,或者在頁中使用下列設置即可對其進行全局設置:

<%@ page enableviewstate="false" %>

  如果您不發回頁,或者總是針對每個請求重新生成頁上的控件,則應該在頁級別禁用視圖狀態。

  小結

  我為您講述了一些我認為在編寫高性能 asp.net 應用程序時有所幫助的技巧。正如我在本文前面部分提到的那樣,這是一個初步指南,并不是 asp.net 性能的最后定論。(有關改善 asp.net 應用程序性能的信息,請參閱 improving asp.net performance。)只有通過自己的親身體驗才能找出解決具體性能問題的最好方法。但是,在您的旅程中,這些技巧應該會為您提供一些好的指南。在軟件開發中,幾乎沒有絕對的東西;每個應用程序都是唯一的。

  請參閱提要欄“common performance myths”。

  rob howard 是 telligent systems 的創始人,專門從事高性能 web 應用程序、知識庫管理和協作系統方面的工作。rob 以前受雇于 microsoft,他在那里幫助設計了 asp.net 1.0、1.1 和 2.0 的基礎結構。要聯系 rob,請訪問 [email protected]

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 建平县| 寿阳县| 遂川县| 永寿县| 会宁县| 庆安县| 海原县| 循化| 浦江县| 迁西县| 荣昌县| 阳曲县| 辽阳县| 肇州县| 斗六市| 子洲县| 富宁县| 榆中县| 呼和浩特市| 汽车| 固原市| 九江县| 和平县| 中卫市| 松阳县| 峡江县| 牡丹江市| 高唐县| 孝昌县| 大埔县| 鄢陵县| 鸡西市| 上栗县| 泾川县| 汤阴县| 凌海市| 化德县| 雅安市| 敦化市| 江都市| 凤台县|