<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    coolfiry

    認認真真做人,兢兢業業做事!
    posts - 39, comments - 17, trackbacks - 0, articles - 0

    2006年10月16日

    在這篇文章中將我們一起來探討當前的API網關的作用。 

    一、API網關的用處

    API網關我的分析中會用到以下三種場景。 

    1. Open API。 企業需要將自身數據、能力等作為開發平臺向外開放,通常會以rest的方式向外提供,最好的例子就是淘寶開放平臺、騰訊公司的QQ開放平臺、微信開放平臺。 Open API開放平臺必然涉及到客戶應用的接入、API權限的管理、調用次數管理等,必然會有一個統一的入口進行管理,這正是API網關可以發揮作用的時候。
    2. 微服務網關。微服務的概念最早在2012年提出,在Martin Fowler的大力推廣下,微服務在2014年后得到了大力發展。 在微服務架構中,有一個組件可以說是必不可少的,那就是微服務網關,微服務網關處理了負載均衡,緩存,路由,訪問控制,服務代理,監控,日志等。API網關在微服務架構中正是以微服務網關的身份存在。 
    3. API服務管理平臺。上述的微服務架構對企業來說有可能實施上是困難的,企業有很多遺留系統,要全部抽取為微服務器改動太大,對企業來說成本太高。但是由于不同系統間存在大量的API服務互相調用,因此需要對系統間服務調用進行管理,清晰地看到各系統調用關系,對系統間調用進行監控等。 API網關可以解決這些問題,我們可以認為如果沒有大規模的實施微服務架構,那么對企業來說微服務網關就是企業的API服務管理平臺。

    二、API網關在企業整體架構中的地位

    一個企業隨著信息系統復雜度的提高,必然出現外部合作伙伴應用、企業自身的公網應用、企業內網應用等,在架構上應該將這三種應用區別開,三種應用的安排級別、訪問方式也不一樣。 因此在我的設計中將這三種應用分別用不同的網關進行API管理,分別是:API網關(OpenAPI合伙伙伴應用)、API網關(內部應用)、API網關(內部公網應用)。

     

    三、企業中在如何應用API網關

    1、對于OpenAPI使用的API網關來說,一般合作伙伴要以應用的形式接入到OpenAPI平臺,合作伙伴需要到 OpenAPI平臺申請應用。 因此在OpenAPI網關之外,需要有一個面向合作伙伴的使用的平臺用于合作伙伴,這就要求OpenAPI網關需要提供API給這個用戶平臺進行訪問。 如下架構:

     

    當然如果是在簡單的場景下,可能并不需要提供一個面向合作伙伴的門戶,只需要由公司的運營人員直接添加合作伙伴應用id/密鑰等,這種情況下也就不需要合作伙伴門戶子系統。 

    2、對于內網的API網關,在起到的作用上來說可以認為是微服務網關,也可以認為是內網的API服務治理平臺。 當企業將所有的應用使用微服務的架構管理起來,那么API網關就起到了微服務網關的作用。 而當企業只是將系統與系統之間的調用使用rest api的方式進行訪問時使用API網關對調用進行管理,那么API網關起到的就是API服務治理的作用。 架構參考如下:

    3、對于公司內部公網應用(如APP、公司的網站),如果管理上比較細致,在架構上是可能由獨立的API網關來處理這部分內部公網應用,如果想比較簡單的處理,也可以是使用面向合作伙伴的API網關。 如果使用獨立的API網關,有以下的好處:

    • 面向合作伙伴和面向公司主體業務的優先級不一樣,不同的API網關可以做到業務影響的隔離。
    • 內部API使用的管理流程和面向合作伙伴的管理流程可能不一樣。
    • 內部的API在功能擴展等方面的需求一般會大于OpenAPI對于功能的要求。

    基于以上的分析,如果公司有能力,那么還是建議分開使用合作伙伴OPEN API網關和內部公網應用網關。

    四、API網關有哪些競爭方案

    1、對于Open API平臺的API網關,我分析只能選擇API網關作為解決方案,業界沒有發現比較好的可以用來作為Open API平臺的入口的其他方案。 

    2、對于作為微服務網關的API網關,業界的選擇可以選擇的解決方案比較多,也取決于微服務器的實現方案,有一些微服務架構的實現方案是不需要微服務網關的。

    • Service Mesh,這是新興的基于無API網關的架構,通過在客戶端上的代理完成屏蔽網絡層的訪問,這樣達到對應用層最小的改動,當前Service Mesh的產品還正在開發中,并沒有非常成熟可直接應用的產品。 發展最迅速的產品是Istio。 建議大家密切關注相關產品的研發、業務使用進展。

    • 基于duboo架構,在這個架構中通常是不需要網關的,是由客戶端直接訪問服務提供方,由注冊中心向客戶端返回服務方的地址。

    五、API網關解決方案

    私有云開源解決方案如下:

    • Kong kong是基于Nginx+Lua進行二次開發的方案, https://konghq.com/
    • Netflix Zuul,zuul是spring cloud的一個推薦組件,https://github.com/Netflix/zuul
    • orange,這個開源程序是國人開發的, http://orange.sumory.com/

    公有云解決方案:

    • Amazon API Gateway,https://aws.amazon.com/cn/api-gateway/
    • 阿里云API網關,https://www.aliyun.com/product/apigateway/
    • 騰訊云API網關, https://cloud.tencent.com/product/apigateway

    自開發解決方案:

    • 基于Nginx+Lua+ OpenResty的方案,可以看到Kong,orange都是基于這個方案
    • 基于Netty、非阻塞IO模型。 通過網上搜索可以看到國內的宜人貸等一些公司是基于這種方案,是一種成熟的方案。
    • 基于Node.js的方案。 這種方案是應用了Node.js天生的非阻塞的特性。
    • 基于java Servlet的方案。 zuul基于的就是這種方案,這種方案的效率不高,這也是zuul總是被詬病的原因。

    六、企業怎么選擇API網關

    如果是要選擇一款已有的API網關,那么需要從以下幾個方面去考慮。 

    1、性能與可用性
    如果一旦采用了API網關,那么API網關就會作為企業應用核心,因此性能和可用性是必須要求的。

    • 從性能上來說,需要讓網關增加的時間消耗越短越好,個人覺得需要10ms以下。 系統需要采用非阻塞的IO,如epoll,NIO等。網關和各種依賴的交互也需要是非阻塞的,這樣才能保證整體系統的高可用性,如:Node.js的響應式編程和基于java體現的RxJava和Future。
    • 網關必須支持集群部署,任務一臺服務器的crash都應該不影響整體系統的可用性。
    • 多套網關應該支持同一管理平臺和同一監控中心。 如: 一個企業的OpenAPI網關和內部應用的多個系統群的不同的微服務網關可以在同一監控中心進行監控。

    2、可擴展性、可維護性
    一款產品總有不能滿足生產需求的地方,因此需求思考產品在如何進行二次開發和維護,是否方便公司團隊接手維護產品。 
    3、需求匹配度
    需要評估各API網關在需求上是否能滿足,如: 如果是OpenAPI平臺需要使用API網關,那么需要看API網關在合作伙伴應用接入、合作伙伴門戶集成、訪問次數限額等OpenAPI核心需求上去思考產品是否能滿足要求。 如果是微服務網關,那么要從微服務的運維、監控、管理等方面去思考產品是否足夠強大。
    4、是否開源?公司是否有自開發的能力?
    現有的開源產品如kong,zuul,orange都有基礎的API網關的核心功能,這些開源產品大多離很好的使用有一定的距離,如:沒有提供管理功能的UI界面、監控功能弱小,不支持OpenAPI平臺,沒有公司運營與運維的功能等。 當然開源產品能獲取源代碼,如果公司有比較強的研發能力,能hold住這些開源產品,經過二次開發kong、zuul應該還是適應一些公司,不過需求注意以下一些點:

    • kong是基于ngnix+lua的,從公司的角度比較難于找到能去維護這種架構產品的人。 需求評估當前公司是否有這個能力去維護這個產品。
    • zuul因為架構的原因在高并發的情況下性能不高,同時需要去基于研究整合開源的適配zuul的監控和管理系統。
    • orange由于沒有被大量使用,同時是國內個人在開源,在可持續性和社區資源上不夠豐富,出了問題后可能不容易找到人問。

    另外kong提供企業版本的API網關,當然也是基于ngnix+lua的,企業版本可以購買他們的技術支持、培訓等服務、以及擁有界面的管理、監控等功能。

    5、公有云還是私有云
    現在的亞馬遜、阿里、騰訊云都在提供基礎公有云的API網關,當然這些網關的基礎功能肯定是沒有問題,但是二次開發,擴展功能、監控功能可能就不能滿足部分用戶的定制需求了。另外很多企業因為自身信息安全的原因,不能使用外網公有網的API網關服務,這樣就只有選擇私有云的方案了。 
    在需求上如果基于公有云的API網關只能做到由內部人員為外網人員申請應用,無法做到定制的合作伙伴門戶,這也不適合于部分企業的需求。 
    如果作為微服務網關,大多數情況下是希望網關服務器和服務提供方服務器是要在內網的,在這里情況下也只有私有云的API網關才能滿足需求。 

    綜合上面的分析,基礎公有云的API網關只有滿足一部分簡單客戶的需求,對于很多企業來說私有云的API網關才是正確的選擇。


    文章作者介紹:
    來自于小豹科技的架構師-專注于軟件研發基于平臺性軟件的研發,目前我正在研發一款基于Netty、響應式架構的插件式的API網關,希望能對行業帶來一些改變。 我希望與對OpenAPI、微服務、API網關、Service Mesh等感興趣的朋友多交流。 有興趣的朋友請加我的QQ群244054462。

    posted @ 2018-01-05 13:42 Coolfiry 閱讀(4700) | 評論 (0)編輯 收藏

    虞美人 李煜
    春花秋月何時了,往事知多少?小樓昨夜又東風,故國不堪回首月明中。雕欄玉砌應猶在,只是朱顏改。問君能有幾多愁,恰似一江春水向東流。 

    posted @ 2009-01-19 10:49 Coolfiry 閱讀(266) | 評論 (0)編輯 收藏

    雨霖鈴 ·柳永


    寒蟬凄切。對長亭晚,驟雨初歇。都門帳飲無緒,留戀處、蘭舟催發。執手相看淚眼,竟無語凝噎。念去去、千里煙波,暮靄沉沉楚天闊。
    多情自古傷離別,更那堪冷落清秋節!今宵酒醒何處?楊柳岸、曉風殘月。此去經年,應是良辰好景虛設。便縱有千種風情,更與何人說?

    posted @ 2009-01-19 10:48 Coolfiry 閱讀(261) | 評論 (0)編輯 收藏

    1、python的入門級內容。
    2、java mail的使用基本用法和注意事項。
    3、CXF中相關BUG的解決方法。
    4、UNIX 網絡編程步步提升系列。

    posted @ 2008-12-11 15:48 Coolfiry 閱讀(1073) | 評論 (5)編輯 收藏

    轉自:http://bbs.chinaunix.net/viewthread.php?tid=691982&extra=&page=1
    snoop 抓包
    solaris自帶snoop抓包工具,抓所有數據流

    # snoop
    Using device /dev/pcn0 (promiscuous mode)
    192.168.8.18 -> 192.168.255.255 NBT NS Query Request for WORKGROUP[1c], Success
    192.168.253.35 -> solaris      TELNET C port=1246
         solaris -> 192.168.253.35 TELNET R port=1246 Using device /dev/pc
         solaris -> 192.168.253.35 TELNET R port=1246 Using device /dev/pc
    192.168.4.150 -> (broadcast)  ARP C Who is 192.168.4.200, 192.168.4.200 ?
    192.168.4.200 -> (broadcast)  ARP C Who is 192.168.4.150, 192.168.4.150 ?
    #

    抓源地址或目的為 202.101.98.55的數據流:

    # snoop 202.101.98.55
    Using device /dev/pcn0 (promiscuous mode)
    192.168.253.35 -> dns.fz.fj.cn DNS C www.163.com. Internet Addr ?
    dns.fz.fj.cn -> 192.168.253.35 DNS R www.163.com. Internet CNAME www.cache.split.netease.com.

    #

    說明:internet cname 后的為解析www.163.com的名字時,代表www.163.com回答的主機的域名。

    抓 192.168.253.35和202.101.98.55之間的數據流(雙向都抓)

    # snoop 192.168.253.35 202.101.98.55
    Using device /dev/pcn0 (promiscuous mode)
    192.168.253.35 -> dns.fz.fj.cn DNS C www.google.com. Internet Addr ?
    dns.fz.fj.cn -> 192.168.253.35 DNS R www.google.com. Internet CNAME www.l.google.com.
    #

    抓完存在當前目錄下的cap文件中并查看

    # snoop -o cap1 -P      -P表示處在非混雜模式抓數據,只抓廣播、主播、目的為本機的數據
    Using device /dev/pcn0 (non promiscuous)
    15 ^C                           15的含義是:顯示目前抓了多少個數據流
    #

    # snoop -i cap1
      1   0.00000 192.168.253.35 -> solaris      TELNET C port=1246
      2   0.18198 192.168.253.35 -> solaris      TELNET C port=1246
      3   0.37232 192.168.4.199 -> 192.168.255.255 NBT Datagram Service Type=17 Source=WB-200[20]
      4   0.00016            ? -> (multicast)  ETHER Type=EF08 (Unknown), size = 180bytes
      5   0.62546 192.168.253.35 -> solaris      TELNET C port=1246
      6   0.13822            ? -> (multicast)  ETHER Type=0000 (LLC/802.3), size = 52 bytes
      7   0.06283 192.168.253.35 -> solaris      TELNET C port=1246
      8   0.90301 192.168.253.35 -> solaris      TELNET C port=1246
      9   0.19781 192.168.253.35 -> solaris      TELNET C port=1246
    10   0.81493            ? -> (multicast)  ETHER Type=0000 (LLC/802.3), size = 52 bytes
    11   0.07018 192.168.253.35 -> solaris      TELNET C port=1246
    12   0.19939 192.168.253.35 -> solaris      TELNET C port=1246
    13   0.90151 192.168.253.35 -> solaris      TELNET C port=1246
    14   0.18904 192.168.253.35 -> solaris      TELNET C port=1246
    15   0.68422            ? -> (multicast)  ETHER Type=0000 (LLC/802.3), size = 52 bytes
    #snoop -i cap1 -p 10,12            只看10-12條記錄

    #snoop -i cap1 -p10                  只看第10條記錄

    # snoop -i cap1 -v -p101            查看第10條數據流的包頭的詳細內容

    #snoop -i cap1 -v -x 0 -p101   查看第10條數據流的全部的詳細內容

    抓主機192.168.253.35和202.101.98.55之間的tcp或者udp端口53的數據

    # snoop 192.168.253.35 and 202.101.98.55 and \(tcp or udp\) and port 53

    輸入(的時候要加轉義符號\


    snoop的詳細參數
    Snoop 是Solaris 系統中自帶的工具, 是一個用于顯示網絡通訊的程序, 它可捕獲IP 包并將其顯示或保存到指定文件. (限超級用戶使用snoop)
    Snoop 可將捕獲的包以一行的形式加以總結或用多行加以詳細的描述(有調用不同的參數–v -V來實現). 在總結方式下(-V ) , 將僅顯示最高層的相關協議, 例如一個NFS 包將僅顯示NFS 信息, 其低層的RPC, UDP, IP, Ethernet 幀信息將不會顯示, 但是當加上相應的參數(-v ), 這些信息都能被顯示出來.

    -C

    -D

    -N

    -P 在非混雜模式下抓包

    -S 抓包的時候顯示數據包的大小

    -V 半詳細的顯示抓的數據的信息

    -t [ r | a | d ] 顯示時間戳,-ta顯示當前系統時間,精確到毫秒

    -v 最詳細的顯示數據的信息

    -x offset [ , length] 以16進制或ACSII方式顯示某數據的部分內容,比如 -x 0,10 只顯示0-10字節

    #snoop -i cap1 -v -x 0 -p101 查看被抓獲的第101個數據流的全部內容


    表達式:

    根據地址:

    #snoop x.x.x.x         IPV4的IP

    #snoop 0XX:XX:XX:XX    ETHERNET的MAC地址

    數據的方向:

    from x.x.x.x 或者 src x.x.x.x

    to x.x.x.x 或者 dst x.x.x.x

    可用的數據類型的關鍵詞:

    ip, ip6, arp, rarp, pppoed, pppoes,pppoe,broadcast,multicast,apple,decnet

    udp, tcp, icmp, icmp6, ah, esp

    greater length
          True if the packet is longer than length.

    less length
          True if the packet is shorter than length.

    net net

    # snoop from net 192.168.1.0 抓來自192.168.1.0/24的數據

    # snoop from net 192.168.0.0 抓來自192.168.0.0/16的數據

    port xx XX為TCP或者UDP的端口號或者 /etc/services里定義的名字

    #snoop to udp and port 53    抓到UDP53的數據

    posted @ 2008-10-21 21:30 Coolfiry 閱讀(723) | 評論 (0)編輯 收藏

    在項目使用CXF的過程中,遇到了有關List作為傳輸參數的時候,如果WebService端沒有明確給出List的泛型類型會報錯。
    例如
    CXF的WebService端口接口的一個方法為為:
    1 public boolean updateMessageStatus(List batchIds);

    客戶端的的調用為:
    1 //預先初始化cxf對象cxfObj
    2 List<String> list=new ArrayList<String>();
    3 list.add("1");
    4 cxfObj.updateMessageStatus(list);


    在客戶端進行調用WebService時會發生錯誤,錯誤為:unexpected element (uri:"", local:"arg0")等,據分析生成的wsdl,這是因為CXF在進行數據marshal時不知道要將要轉換的類型。

    解決辦法是:在WebService端的接口必須用List的泛型類型參數,如:

    1 public boolean updateMessageStatus(List<String> batchIds);

    這樣就完全解決問題了。

    posted @ 2008-08-05 20:09 Coolfiry 閱讀(4956) | 評論 (1)編輯 收藏

    現在正在學習linux shell編程
    first.sh
    while read line
    do
            echo 
    "$line"
    done 
    <"$1"
    這是第一個shell程序小例子,就相當于一個學習其他語言的hello world了吧。用法first.sh test,將test文件中的每一行輸出到stdout中。

    second.sh
    number=0;
    while [ "$number" -lt 100 ]
    do
            echo 
    "$number"
            number
    ='expr $number + 1'
    done
    echo
    這是第二個shell程序小例子,作用是輸出0到99的數字到stdout中。其中用到的expr的作用是使expr的參數轉化為數字并相加。兩個單引號的作用是引號所包圍的命令被命令的標準輸出替換,并輸出賦值給我number,得到了如同java中number=number+1的效果。


    posted @ 2008-07-20 20:34 Coolfiry 閱讀(588) | 評論 (2)編輯 收藏

    在項目開發過程中,遇到在本機和windows環境中部署用CXF框架開發的的webService沒有任何問題,但是當將工程部署到solaris 的SUN ONE application上時,再用本機的cxf Web服務客戶端訪問對應的web服務時,如果傳輸的數據量小于大約4K不會出問題,否則則會報一些數據綁定的異常如:
    Marshalling Error: Error writing request body to server。
    解決這個問題花了我足足兩天時間,原因是有關CXF的資料太少了,而且有關于這個錯誤的解決都必須使用google才能search到,用baidu完全search不到相關的資料。
    解決方案:
    在客戶端的class-path中加上cxf.xml。cxf.xml的配置如下:
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi
    ="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:http
    ="http://cxf.apache.org/transports/http/configuration"
        xmlns:jaxws
    ="http://cxf.apache.org/jaxws"
        xsi:schemaLocation
    ="
    http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
    http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd
     http://cxf.apache.org/transports/http/configuration
    http://cxf.apache.org/schemas/configuration/http-conf.xsd"
    >
        
    <http:conduit name="*.http-conduit">
            
    <http:client AutoRedirect="true" />
        
    </http:conduit>
    </beans>
    這個問題的解決方案是我在cxf的官網上找了很久才找到的,雖然問題解決了,但是我感到很迷惑。主要在windows tomcat環境下沒有問題,而到了SUN ONE的環境就有問題,經過的思考和找了一資料,我認為問題出于solaris對于HTTP數據傳輸的某些限制,如果真要去搞清楚的話可能要去參看cxf的source code了,但是我不想花這個時間去研究這個問題了。

    我把這個解決方案寫出來,希望可以幫助到使用CXF的網友,也希望高手們能幫我解決我的迷惑。



    posted @ 2008-07-18 19:11 Coolfiry 閱讀(2568) | 評論 (0)編輯 收藏

    Internet的快速增長使多媒體網絡服務器,特別是Web服務器,面對的訪問者數量快速增加,網絡服務器需要具備提供大量并發訪問服務的能力。 例如Yahoo每天會收到數百萬次的訪問請求,因此對于提供大負載Web服務的服務器來講,CPU、I/O處理能力很快會成為瓶頸。

    簡單的 提高硬件性能并不能真正解決這個問題,因為單臺服務器的性能總是有限的,一般來講,一臺PC服務器所能提供的并發訪問處理能力大約為1000個,更為高檔 的專用服務器能夠支持3000-5000個并發訪問,這樣的能力還是無法滿足負載較大的網站的要求。尤其是網絡請求具有突發性,當某些重大事件發生時,網 絡訪問就會急劇上升,從而造成網絡瓶頸,例如在網上發布的克林頓彈劾書就是很明顯的例子。必須采用多臺服務器提供網絡服務,并將網絡請求分配給這些服務器 分擔,才能提供處理大量并發服務的能力。

    當使用多臺服務器來分擔負載的時候,最簡單的辦法是將不同的服務器用在不同的方面。 按提供的內容進行分割時,可以將一臺服務器用于提供新聞頁面,而另一臺用于提供游戲頁面;或者可以按服務器的功能進行分割,將一臺服務器用于提供靜態頁面 訪問,而另一些用于提供CGI等需要大量消耗資源的動態頁面訪問。然而由于網絡訪問的突發性,使得很難確定那些頁面造成的負載太大,如果將服務的頁面分割 的過細就會造成很大浪費。事實上造成負載過大的頁面常常是在變化中的,如果要經常按照負載變化來調整頁面所在的服務器,那么勢必對管理和維護造成極大的問 題。因此這種分割方法只能是大方向的調整,對于大負載的網站,根本的解決辦法還需要應用負載均衡技術。

    負載均衡的思路下多臺 服務器為對稱方式,每臺服務器都具備等價的地位,都可以單獨對外提供服務而無須其他服務器的輔助。然后通過某種負載分擔技術,將外部發送來的請求均勻分配 到對稱結構中的某一臺服務器上,而接收到請求的服務器都獨立回應客戶機的請求。由于建立內容完全一致的Web服務器并不復雜,可以使用服務器同步更新或者 共享存儲空間等方法來完成,因此負載均衡技術就成為建立一個高負載Web站點的關鍵性技術。

    1. 基于特定服務器軟件的負載均衡

      很 多網絡協議都支持“重定向”功能,例如在HTTP協議中支持Location指令,接收到這個指令的瀏覽器將自動重定向到Location指明的另一個 URL上。由于發送Location指令比起執行服務請求,對Web服務器的負載要小的多,因此可以根據這個功能來設計一種負載均衡的服務器。任何時候 Web服務器認為自己負載較大的時候,它就不再直接發送回瀏覽器請求的網頁,而是送回一個Locaction指令,讓瀏覽器去服務器集群中的其他服務器上 獲得所需要的網頁。

      在這種方式下,服務器本身必須支持這種功能,然而具體實現起來卻有很多困難,例如一臺服務器如何能保證它重定向過的服務 器是比較空閑的,并且不會再次發送Location指令?Location指令和瀏覽器都沒有這方面的支持能力,這樣很容易在瀏覽器上形成一種死循環。因 此這種方式實際應用當中并不多見,使用這種方式實現的服務器集群軟件也較少。有些特定情況下可以使用CGI(包括使用FastCGI或mod_perl擴 展來改善性能)來模擬這種方式去分擔負載,而Web服務器仍然保持簡潔、高效的特性,此時避免Location循環的任務將由用戶的CGI程序來承擔。

    2. 基于DNS的負載均衡

      由 于基于服務器軟件的負載均衡需要改動軟件,因此常常是得不償失,負載均衡最好是在服務器軟件之外來完成,這樣才能利用現有服務器軟件的種種優勢。最早的負 載均衡技術是通過DNS服務中的隨機名字解析來實現的,在DNS服務器中,可以為多個不同的地址配置同一個名字,而最終查詢這個名字的客戶機將在解析這個 名字時得到其中的一個地址。因此,對于同一個名字,不同的客戶機會得到不同的地址,他們也就訪問不同地址上的Web服務器,從而達到負載均衡的目的。

      例如如果希望使用三個Web服務器來回應對www.exampleorg.org.cn的HTTP請求,就可以設置該域的DNS服務器中關于該域的數據包括有與下面例子類似的結果:

      www1		IN		A 		192.168.1.1
      www2		IN		A 		192.168.1.2
      www3		IN		A 		192.168.1.3
      www		IN		CNAME		www1
      www		IN		CNAME		www2
      www		IN		CNAME		www3

      此后外部的客戶機就可能隨機的得到對應www的不同地址,那么隨后的HTTP請求也就發送給不同地址了。

      DNS 負載均衡的優點是簡單、易行,并且服務器可以位于互聯網的任意位置上,當前使用在包括Yahoo在內的Web站點上。然而它也存在不少缺點,一個缺點是為 了保證DNS數據及時更新,一般都要將DNS的刷新時間設置的較小,但太小就會造成太大的額外網絡流量,并且更改了DNS數據之后也不能立即生效;第二點 是DNS負載均衡無法得知服務器之間的差異,它不能做到為性能較好的服務器多分配請求,也不能了解到服務器的當前狀態,甚至會出現客戶請求集中在某一臺服 務器上的偶然情況。

    3. 反向代理負載均衡

      使用代理服務器可以將請求轉發給內部的Web服務器,使用這種加速 模式顯然可以提升靜態網頁的訪問速度。因此也可以考慮使用這種技術,讓代理服務器將請求均勻轉發給多臺內部Web服務器之一上,從而達到負載均衡的目的。 這種代理方式與普通的代理方式有所不同,標準代理方式是客戶使用代理訪問多個外部Web服務器,而這種代理方式是多個客戶使用它訪問內部Web服務器,因 此也被稱為反向代理模式。

      實現這個反向代理能力并不能算是一個特別復雜的任務,但是在負載均衡中要求特別高的效率,這樣實現起來就不是十分 簡單的了。每針對一次代理,代理服務器就必須打開兩個連接,一個為對外的連接,一個為對內的連接,因此對于連接請求數量非常大的時候,代理服務器的負載也 就非常之大了,在最后反向代理服務器會成為服務的瓶頸。例如,使用Apache的mod_rproxy模塊來實現負載均衡功能時,提供的并發連接數量受 Apache本身的并發連接數量的限制。一般來講,可以使用它來對連接數量不是特別大,但每次連接都需要消耗大量處理資源的站點進行負載均衡,例如搜尋。

      使 用反向代理的好處是,可以將負載均衡和代理服務器的高速緩存技術結合在一起,提供有益的性能,具備額外的安全性,外部客戶不能直接訪問真實的服務器。并且 實現起來可以實現較好的負載均衡策略,將負載可以非常均衡的分給內部服務器,不會出現負載集中到某個服務器的偶然現象。

    4. 基于NAT的負載均衡技術

      網 絡地址轉換為在內部地址和外部地址之間進行轉換,以便具備內部地址的計算機能訪問外部網絡,而當外部網絡中的計算機訪問地址轉換網關擁有的某一外部地址 時,地址轉換網關能將其轉發到一個映射的內部地址上。因此如果地址轉換網關能將每個連接均勻轉換為不同的內部服務器地址,此后外部網絡中的計算機就各自與 自己轉換得到的地址上服務器進行通信,從而達到負載分擔的目的。

      地 址轉換可以通過軟件方式來實現,也可以通過硬件方式來實現。使用硬件方式進行操作一般稱為交換,而當交換必須保存TCP連接信息的時候,這種針對OSI網 絡層的操作就被稱為第四層交換。支持負載均衡的網絡地址轉換為第四層交換機的一種重要功能,由于它基于定制的硬件芯片,因此其性能非常優秀,很多交換機聲 稱具備400MB-800MB的第四層交換能力,然而也有一些資料表明,在如此快的速度下,大部分交換機就不再具備第四層交換能力了,而僅僅支持第三層甚 至第二層交換。

      然而對于大部分站點來講,當前負載均衡主要是解決Web服務器處理能力瓶頸的,而非網絡傳輸能力,很多站點的互聯網連接帶寬總共也不過10MB,只有極少的站點能夠擁有較高速的網絡連接,因此一般沒有必要使用這些負載均衡器這樣的昂貴設備。

      使 用軟件方式來實現基于網絡地址轉換的負載均衡則要實際的多,除了一些廠商提供的解決方法之外,更有效的方法是使用免費的自由軟件來完成這項任務。其中包括 Linux Virtual Server Project中的NAT實現方式,或者本文作者在FreeBSD下對natd的修訂版本。一般來講,使用這種軟件方式來實現地址轉換,中心負載均衡器存 在帶寬限制,在100MB的快速以太網條件下,能得到最快達80MB的帶寬,然而在實際應用中,可能只有40MB-60MB的可用帶寬。

    5. 擴展的負載均衡技術

    上 面使用網絡地址轉換來實現負載分擔,毫無疑問所有的網絡連接都必須通過中心負載均衡器,那么如果負載特別大,以至于后臺的服務器數量不再在是幾臺、十幾 臺,而是上百臺甚至更多,即便是使用性能優秀的硬件交換機也回遇到瓶頸。此時問題將轉變為,如何將那么多臺服務器分布到各個互聯網的多個位置,分散網絡負 擔。當然這可以通過綜合使用DNS和NAT兩種方法來實現,然而更好的方式是使用一種半中心的負載均衡方式。

    在這種半中心的負載均衡方式下,即當客戶請求發送給負載均衡器的時候,中心負載均衡器將請求打包并發送給某個服務器,而服務器的回應請求不再返回給中心負載均衡器,而是直接返回給客戶,因此中心負載均衡器只負責接受并轉發請求,其網絡負擔就較小了。

    上圖來自Linux Virtual Server Project,為他們使用IP隧道實現的這種負載分擔能力的請求/回應過程,此時每個后臺服務器都需要進行特別的地址轉換,以欺騙瀏覽器客戶,認為它的回應為正確的回應。

    同樣,這種方式的硬件實現方式也非常昂貴,但是會根據廠商的不同,具備不同的特殊功能,例如對SSL的支持等。

    由于這種方式比較復雜,因此實現起來比較困難,它的起點也很高,當前情況下網站并不需要這么大的處理能力。

    比 較上面的負載均衡方式,DNS最容易,也最常用,能夠滿足一般的需求。但如果需要進一步的管理和控制,可以選用反向代理方式或NAT方式,這兩種之間進行 選擇主要依賴緩沖是不是很重要,最大的并發訪問數量是多少等條件。而如果網站上對負載影響很厲害的CGI程序是由網站自己開發的,也可以考慮在程序中自己 使用Locaction來支持負載均衡。半中心化的負載分擔方式至少在國內當前的情況下還不需要。
    http://galaxystar.javaeye.com/blog/50546

    posted @ 2008-07-18 14:23 Coolfiry 閱讀(255) | 評論 (0)編輯 收藏

    在Java版發表這篇文章,似乎有點把矛頭指向Java了。其實不是,GC是所有新一代語言共有的特征,
    Python, Eiffel,C#,Roby等無一例外地都使用了GC機制。但既然Java中的GC最為著名,所以天塌
    下來自然應該抗著。

    這篇短文源于comp.lang.java.programmer跟comp.lang.c++上發生的一場大辯論,支持C++和Java
    的兩派不同勢力展開了新世紀第一場沖突,跟貼發言超過350,兩派都有名角壓陣。C++陣營的擂主是
    Pete Becker,ACM會員,Dinkumware Ltd. 的技術副總監。此君精通C++和Java,開發過兩種語言的
    核心類庫,但是卻對C++狂熱之極,而對于Java頗不以為然。平時談到Java的時候還好,一旦有人膽
    敢用Java來批判C++,立刻忍不住火爆脾氣跳將出來,以堅韌不拔的毅力和大無畏精神與對手周旋,
    舌戰群儒,哪怕只剩下一個人也要血戰到底。這等奇人當真少見!我真奇怪他整天泡在usenet上,
    不用工作么?他的老板P.J. Plauger如此寬宏大量?Java陣營主角是一個網名Razzi的兄弟,另外有
    Sun公司大名鼎鼎的Peter van der Linden助陣,妙語連珠,寸土必爭,加上人多勢眾,一度占據優勢。
    C++陣營里大拿雖然很多,但是大多數沒有Pete那么多閑工夫,例如Greg Comeau,Comeau公司老板,
    每次來個只言片語,實在幫不了Pete多大忙。但是自從C++陣營中冒出一個無名小子,網名Courage(勇氣),
    發動對Java GC機制的批判,形勢為之一變。C++陣營眼下處于全攻之勢,Java陣營疲于防守,只能
    招架說:“你們沒有證據,沒有統計資料”,形勢很被動。

    垃圾收集(GC)不是一直被Java fans用來炫耀,引以為傲的優點么?怎么成了弱點了?我大惑不解,定睛
    一看,才覺得此中頗有道理。

    首先,Java Swing庫存在大量資源泄漏問題,這一點SUN非常清楚,稱之為bugs,正在極力修正。但是看來
    這里的問題恐怕不僅是庫編寫者的疏忽,可能根源在于深層的機制,未必能夠輕易解決,搞不好要傷筋動骨。
    不過這個問題不是那么根本,C++陣營覺得如果抓住對方的弱點攻擊,就算是占了上風也沒什么說服力。誰
    沒有缺點呢?于是反其道而行之,猛烈攻擊Java陣營覺得最得意的東西,Java的GC機制本身。

    首先來想一想,memory leak到底意味著什么。在C++中,new出來的對象沒有delete,這就導致了memory
    leak。但是C++早就有了克服這一問題的辦法——smart pointer。通過使用標準庫里設計精致的auto_ptr
    以及各種STL容器,還有例如boost庫(差不多是個準標準庫了)中的四個smart pointers,C++
    程序員只要
    花上一個星期的時間學習最新的資料,就可以拍著胸脯說:“我寫的
    程序沒有memory leak!”。

    相比之下,Java似乎更優秀,因為從一開始你就不用考慮什么特殊的機制,大膽地往前new,自有GC替你
    收拾殘局。Java的GC實際上是
    JVM中的一個獨立線程,采用不同的算法策略來收集heap中那些不再有
    reference指向的垃圾對象所占用的內存。但是,通常情況下,GC線程的優先級比較低,只有在當前
    程序
    空閑的時候才會被調度,收集垃圾。當然,如果JVM感到內存緊張了,JVM會主動調用GC來收集垃圾,獲取
    更多的內存。請注意,Java的GC工作的時機是:1. 當前
    程序不忙,有空閑時間。2. 空閑內存不足。
    現在我們考慮一種常見的情況,
    程序在緊張運行之中,沒喲空閑時間給GC來運行,同時機器內存很大,
    JVM也沒有感到內存不足,結果是什么?對了,GC形同虛設,得不到調用。于是,內存被不斷吞噬,而那些
    早已經用不著的垃圾對象仍在在寶貴的內存里睡大覺。例如:

    class BadGc {

        public void job1() {
            String garbage = "I am a garbage, and just sleeping in your precious memory, " +
                      "how do you think you can deal with me? Daydreaming! HAHA!!!";
            ....
        }

        public void job2() {...}

        ...
        ...

        public void job1000() {...}

        public static void main(String[] args) {
            bgc = new BadGc();
     bgc.job1();
     bgc.job2();
     ...
     bgc.job1000();
        }
    }

    運行中,雖然garbage對象在離開job1()之后,就再也沒有用了。但是因為程序忙,內存還夠用,所以GC得
    不到調度,garbage始終不會被回收,直到
    程序運行到bgc.job1000()時還躺在內存里嘲笑你。沒轍吧!

    好了,我承認這段程序很傻。但是你不要以為這只是理論上的假設,恰恰相反,大多數實用中的Java程序都有
    類似的效應。這就是為什么Java
    程序狂耗內存,而且好像給它多少內存吃都不夠。你花上大筆的銀子把內存
    從128升到256,再升到512,結果是,一旦執行復雜任務,內存還是被輕易填滿,而且多出來的這些內存只是
    用來裝垃圾,GC還是不給面子地千呼萬喚不出來。等到你的內存終于心力交瘁,GC才姍姍來遲,收拾殘局。而
    且GC工作的方式也很不好評價,一種方法是一旦有機會回收內存,就把所有的垃圾都回收。你可以想象,這要
    花很長時間(幾百M的垃圾啊!),如果你這時侯正在壓下開炮的按鈕,GC卻叫了暫定,好了,你等死吧!另一
    種方法,得到機會之后,回收一些內存,讓JVM感到內存不那么緊張時就收手。結果呢,內存里始終有大批垃
    圾,
    程序始終在半死不活的蕩著。最后,GC可以每隔一段時間就運行一次,每次只回收一部分垃圾,這是現在
    大部分JVM的方式,結果是內存也浪費了,還動不動暫停幾百毫秒。難啊!

    反過來看看C++利用smart pointer達成的效果,一旦某對象不再被引用,系統刻不容緩,立刻回收內存。這
    通常發生在關鍵任務完成后的清理(cleanup)時期,不會影響關鍵任務的實時性,同時,內存里所有的對象
    都是有用的,絕對沒有垃圾空占內存。怎么樣?傳統、樸素的C++是不是更勝一籌?

    據統計,目前的Java程序運行期間占用的內存通常為對應C++程序的4-20倍。除了其它的原因,上面所說的是一個
    非常主要的因素。我們對memory leak如此憤恨,不就是因為它導致大量的內存垃圾得不到清除嗎?如果有了
    GC之后,垃圾比以前還來勢洶洶,那么GC又有什么好處呢?

    當然,C++的smart pointer現在會使用的人不多,所以現在的C++程序普遍存在更嚴重的memory leak問題。
    但是,如果我奶奶跟舒馬赫比賽車輸掉了,你能夠埋怨那輛車子么?
    http://www.594k.com/java/html/y2007m1/12051/

    posted @ 2007-10-12 10:43 Coolfiry 閱讀(643) | 評論 (1)編輯 收藏

    從LiveJournal后臺發展看大規模網站性能優化方法

    一、LiveJournal發展歷程

    LiveJournal是99年始于校園中的項目,幾個人出于愛好做了這樣一個應用,以實現以下功能:

    • 博客,論壇
    • 社會性網絡,找到朋友
    • 聚合,把朋友的文章聚合在一起

    LiveJournal采用了大量的開源軟件,甚至它本身也是一個開源軟件。

    在上線后,LiveJournal實現了非常快速的增長:

    • 2004年4月份:280萬注冊用戶。
    • 2005年4月份:680萬注冊用戶。
    • 2005年8月份:790萬注冊用戶。
    • 達到了每秒鐘上千次的頁面請求及處理。
    • 使用了大量MySQL服務器。
    • 使用了大量通用組件。

    二、LiveJournal架構現狀概況

    livejournal_backend.png

    三、從LiveJournal發展中學習

     

    LiveJournal從1臺服務器發展到100臺服務器,這其中經歷了無數的傷痛,但同時也摸索出了解決這些問題的方法,通過對LiveJournal的學習,可以讓我們避免LJ曾經犯過的錯誤,并且從一開始就對系統進行良好的設計,以避免后期的痛苦。

    下面我們一步一步看LJ發展的腳步。

    1、一臺服務器

    一 臺別人捐助的服務器,LJ最初就跑在上面,就像Google開始時候用的破服務器一樣,值得我們尊敬。這個階段,LJ的人以驚人的速度熟悉的Unix的操 作管理,服務器性能出現過問題,不過還好,可以通過一些小修小改應付過去。在這個階段里LJ把CGI升級到了FastCGI。

    最終問題出現了,網站越來越慢,已經無法通過優過化來解決的地步,需要更多的服務器,這時LJ開始提供付費服務,可能是想通過這些錢來購買新的服務器,以解決當時的困境。
    毫無疑問,當時LJ存在巨大的單點問題,所有的東西都在那臺服務器的鐵皮盒子里裝著。

    LJ-backend-7.png

    2、兩臺服務器

    用付費服務賺來的錢LJ買了兩臺服務器:一臺叫做Kenny的Dell 6U機器用于提供Web服務,一臺叫做Cartman的Dell 6U服務器用于提供數據庫服務。

    LJ-backend-8.png

    LJ有了更大的磁盤,更多的計算資源。但同時網絡結構還是非常簡單,每臺機器兩塊網卡,Cartman通過內網為Kenny提供MySQL數據庫服務。

    暫時解決了負載的問題,新的問題又出現了:

    • 原來的一個單點變成了兩個單點。
    • 沒有冷備份或熱備份。
    • 網站速度慢的問題又開始出現了,沒辦法,增長太快了。
    • Web服務器上CPU達到上限,需要更多的Web服務器。

    3、四臺服務器

    又買了兩臺,Kyle和Stan,這次都是1U的,都用于提供Web服務。目前LJ一共有3臺Web服務器和一臺數據庫服務器。這時需要在3臺Web服務器上進行負載均橫。

    LJ-backend-9.png

    LJ把Kenny用于外部的網關,使用mod_backhand進行負載均橫。

    然后問題又出現了:

    • 單點故障。數據庫和用于做網關的Web服務器都是單點,一旦任何一臺機器出現問題將導致所有服務不可用。雖然用于做網關的Web服務器可以通過保持心跳同步迅速切換,但還是無法解決數據庫的單點,LJ當時也沒做這個。
    • 網站又變慢了,這次是因為IO和數據庫的問題,問題是怎么往應用里面添加數據庫呢?

    4、五臺服務器

    又買了一臺數據庫服務器。在兩臺數據庫服務器上使用了數據庫同步(Mysql支持的Master-Slave模式),寫操作全部針對主數據庫(通過Binlog,主服務器上的寫操作可以迅速同步到從服務器上),讀操作在兩個數據庫上同時進行(也算是負載均橫的一種吧)。

    LJ-backend-10.png

    實現同步時要注意幾個事項:

    • 讀操作數據庫選擇算法處理,要選一個當前負載輕一點的數據庫。
    • 在從數據庫服務器上只能進行讀操作
    • 準備好應對同步過程中的延遲,處理不好可能會導致數據庫同步的中斷。只需要對寫操作進行判斷即可,讀操作不存在同步問題。

    5、更多服務器

    有錢了,當然要多買些服務器。部署后快了沒多久,又開始慢了。這次有更多的Web服務器,更多的數據庫服務器,存在 IO與CPU爭用。于是采用了BIG-IP作為負載均衡解決方案。

    LJ-backend-11.png

    6、現在我們在哪里:

    LJ-backend-1.png

    現在服務器基本上夠了,但性能還是有問題,原因出在架構上。

    數據庫的架構是最大的問題。由于增加的數據庫都是以Slave模式添加到應用內,這樣唯一的好處就是將讀操作分布到了多臺機器,但這樣帶來的后果就是寫操作被大量分發,每臺機器都要執行,服務器越多,浪費就越大,隨著寫操作的增加,用于服務讀操作的資源越來越少。

    LJ-backend-2.png

    由一臺分布到兩臺

    LJ-backend-3.png

    最終效果

    現在我們發現,我們并不需要把這些數據在如此多的服務器上都保留一份。服務器上已經做了RAID,數據庫也進行了備份,這么多的備份完全是對資源的浪費,屬于冗余極端過度。那為什么不把數據分布存儲呢?

    問題發現了,開始考慮如何解決。現在要做的就是把不同用戶的數據分布到不同的服務器上進行存儲,以實現數據的分布式存儲,讓每臺機器只為相對固定的用戶服務,以實現平行的架構和良好的可擴展性。

    為 了實現用戶分組,我們需要為每一個用戶分配一個組標記,用于標記此用戶的數據存放在哪一組數據庫服務器中。每組數據庫由一個master及幾個slave 組成,并且slave的數量在2-3臺,以實現系統資源的最合理分配,既保證數據讀操作分布,又避免數據過度冗余以及同步操作對系統資源的過度消耗。

    LJ-backend-4.png

    由一臺(一組)中心服務器提供用戶分組控制。所有用戶的分組信息都存儲在這臺機器上,所有針對用戶的操作需要先查詢這臺機器得到用戶的組號,然后再到相應的數據庫組中獲取數據。

    這樣的用戶架構與目前LJ的架構已經很相像了。

    在具體的實現時需要注意幾個問題:

    • 在數據庫組內不要使用自增ID,以便于以后在數據庫組之間遷移用戶,以實現更合理的I/O,磁盤空間及負載分布。
    • 將userid,postid存儲在全局服務器上,可以使用自增,數據庫組中的相應值必須以全局服務器上的值為準。全局服務器上使用事務型數據庫InnoDB。
    • 在數據庫組之間遷移用戶時要萬分小心,當遷移時用戶不能有寫操作。

    7、現在我們在哪里

    LJ-backend-5.png

    問題:

    • 一個全局主服務器,掛掉的話所有用戶注冊及寫操作就掛掉。
    • 每個數據庫組一個主服務器,掛掉的話這組用戶的寫操作就掛掉。
    • 數據庫組從服務器掛掉的話會導致其它服務器負載過大。

    對于Master-Slave模式的單點問題,LJ采取了Master-Master模式來解決。所謂Master-Master實際上是人工實現的,并不是由MySQL直接提供的,實際上也就是兩臺機器同時是Master,也同時是Slave,互相同步。

    Master-Master實現時需要注意:

    • 一個Master出錯后恢復同步,最好由服務器自動完成。
    • 數字分配,由于同時在兩臺機器上寫,有些ID可能會沖突。

    解決方案:

    • 奇偶數分配ID,一臺機器上寫奇數,一臺機器上寫偶數
    • 通過全局服務器進行分配(LJ采用的做法)。

     

    Master-Master模式還有一種用法,這種方法與前一種相比,仍然保持兩臺機器的同步,但只有一臺機器提供服務(讀和寫),在每天晚上的時候進行輪換,或者出現問題的時候進行切換。

    8、現在我們在哪里

    LJ-backend-6.png

    現在插播一條廣告,MyISAM VS InnoDB。

    使用InnoDB:

    • 支持事務
    • 需要做更多的配置,不過值得,可以更安全的存儲數據,以及得到更快的速度。

    使用MyISAM:

    • 記錄日志(LJ用它來記網絡訪問日志)
    • 存儲只讀靜態數據,足夠快。
    • 并發性很差,無法同時讀寫數據(添加數據可以)
    • MySQL非正常關閉或死機時會導致索引錯誤,需要使用myisamchk修復,而且當訪問量大時出現非常頻繁。

    9、緩存

    去年我寫過一篇文章介紹memcached,它就是由LJ的團隊開發的一款緩存工具,以key-value的方式將數據存儲到分布的內存中。LJ緩存的數據:

    • 12臺獨立服務器(不是捐贈的)
    • 28個實例
    • 30GB總容量
    • 90-93%的命中率(用過squid的人可能知道,squid內存加磁盤的命中率大概在70-80%)

    如何建立緩存策略?

    想緩存所有的東西?那是不可能的,我們只需要緩存已經或者可能導致系統瓶頸的地方,最大程度的提交系統運行效率。通過對MySQL的日志的分析我們可以找到緩存的對象。

    緩存的缺點?

    • 沒有完美的事物,緩存也有缺點:
    • 增大開發量,需要針對緩存處理編寫特殊的代碼。
    • 管理難度增加,需要更多人參與系統維護。
    • 當然大內存也需要錢。

    10、Web訪問負載均衡

    在數據包級別使用BIG-IP,但BIG-IP并不知道我們內部的處理機制,無法判斷由哪臺服務器對這些請求進行處理。反向代理并不能很好的起到作用,不是已經夠快了,就是達不到我們想要的效果。

    所以,LJ又開發了Perlbal。特點:

    • 快,小,可管理的http web 服務器/代理
    • 可以在內部進行轉發
    • 使用Perl開發
    • 單線程,異步,基于事件,使用epoll , kqueue
    • 支持Console管理與http遠程管理,支持動態配置加載
    • 多種模式:web服務器,反向代理,插件
    • 支持插件:GIF/PNG互換?

    11、MogileFS

    LJ使用開源的MogileFS作為分布式文件存儲系統。MogileFS使用非常簡單,它的主要設計思想是:

    • 文件屬于類(類是最小的復制單位)
    • 跟蹤文件存儲位置
    • 在不同主機上存儲
    • 使用MySQL集群統一存儲分布信息
    • 大容易廉價磁盤

    到目前為止就這么多了,更多文檔可以在http://www.danga.com/words/找到。Danga.comLiveJournal.com的 同學們拿這個文檔參加了兩次MySQL Con,兩次OS Con,以及眾多的其它會議,無私的把他們的經驗分享出來,值得我們學習。在web2.0時代快速開發得到大家越來越多的重視,但良好的設計仍是每一個應 用的基礎,希望web2.0們在成長為Top500網站的路上,不要因為架構阻礙了網站的發展。

     http://blog.csdn.net/xmr_gxcfe/archive/2007/09/14/1785292.aspx

     

    posted @ 2007-09-29 21:26 Coolfiry 閱讀(550) | 評論 (0)編輯 收藏

    posted @ 2007-09-25 14:30 Coolfiry 閱讀(356) | 評論 (0)編輯 收藏

    UML類圖的各種標識法
    關鍵字:   UML    
    ·------>虛線箭頭表示依賴關系(dependency),一個類需要與另外一個類一起工作,是它一種最弱的關聯關系,常見于各種工具類之間的關系
    ·——實線表示聯合關系(association),一個類包含對另外一個類對象的引用,這個通常是使用屬性來實現的,為了表明之間的包含關系,有時候會在實線的一端加上箭頭(navigability arrow)來表示導航關系,如果關聯的雙方又都和第三個類有關聯關系,那么可以在實線的中間加一個虛線和第三個類關聯來表示這種association classes關系
    ·◇——空心菱形加實線表示聚合關系(aggregation),它是一種更強的關聯關系,表示一個類可以擁有或者享有一個類的實例對象,在java代碼表現上跟聯合是一樣的。
    ·◆——實心菱形加實線表示組合關系(composition),它的關聯性比聚合更強,被組合的對象是組合對象的一部分,沒法跟其他的對象共享,而且如果組合對象銷毀的話,被組合的對象也會同時被銷毀,其表現形式跟聯合一樣
    ·空心箭頭加實線,表示泛化generalization(繼承inheritance)關系,這個很簡單
    ·在rose中要建立enumeration,只需要在建立的class中將其stereotype設置為enumeration即可。stereotype只是用來做一個標記,并不包含別的意義

    posted @ 2007-06-10 18:03 Coolfiry 閱讀(480) | 評論 (0)編輯 收藏

    明天18號,要從沈陽回成都了

    posted @ 2007-05-17 09:46 Coolfiry 閱讀(212) | 評論 (0)編輯 收藏

    PO BO VO DTO POJO DAO概念及其作用(附轉換圖)

       J2EE開發中大量的專業縮略語很是讓人迷惑,尤其是跟一些高手討論問題的時候,三分鐘就被人家滿口的專業術語噴暈了,PO VO BO DTO POJO DAO,一大堆的就來了(聽過老羅對這種現象的批判的朋友會會心一笑)。

        首先聲明偶也不是什么高手,以下總結都是自己的體會。不對之處請您多指教。

    PO:
    persistant object持久對象

    最形象的理解就是一個PO就是數據庫中的一條記錄。
    好處是可以把一條記錄作為一個對象處理,可以方便的轉為其它對象。

     



    BO:
    business object業務對象

    主要作用是把業務邏輯封裝為一個對象。這個對象可以包括一個或多個其它的對象。
    比如一個簡歷,有教育經歷、工作經歷、社會關系等等。
    我們可以把教育經歷對應一個PO,工作經歷對應一個PO,社會關系對應一個PO。
    建立一個對應簡歷的BO對象處理簡歷,每個BO包含這些PO。
    這樣處理業務邏輯時,我們就可以針對BO去處理。

     



    VO :
    value object值對象
    ViewObject表現層對象

    主要對應界面顯示的數據對象。對于一個WEB頁面,或者SWT、SWING的一個界面,用一個VO對象對應整個界面的值。

     



    DTO :
    Data Transfer Object數據傳輸對象
    主要用于遠程調用等需要大量傳輸對象的地方。
    比如我們一張表有100個字段,那么對應的PO就有100個屬性。
    但是我們界面上只要顯示10個字段,
    客戶端用WEB service來獲取數據,沒有必要把整個PO對象傳遞到客戶端,
    這時我們就可以用只有這10個屬性的DTO來傳遞結果到客戶端,這樣也不會暴露服務端表結構.到達客戶端以后,如果用這個對象來對應界面顯示,那此時它的身份就轉為VO

     



    POJO :
    plain ordinary java object 簡單java對象
    個人感覺POJO是最常見最多變的對象,是一個中間對象,也是我們最常打交道的對象。

    一個POJO持久化以后就是PO
    直接用它傳遞、傳遞過程中就是DTO
    直接用來對應表示層就是VO

     


    DAO:
    data access object數據訪問對象
    這個大家最熟悉,和上面幾個O區別最大,基本沒有互相轉化的可能性和必要.
    主要用來封裝對數據庫的訪問。通過它可以把POJO持久化為PO,用PO組裝出來VO、DTO


          總結下我認為一個對象究竟是什么O要看具體環境,在不同的層、不同的應用場合,對象的身份也不一樣,而且對象身份的轉化也是很自然的。就像你對老婆來說就是老公,對父母來說就是子女。設計這些概念的初衷不是為了唬人而是為了更好的理解和處理各種邏輯,讓大家能更好的去用面向對象的方式處理問題.

          大家千萬不要陷入過度設計,大可不必為了設計而設計一定要在代碼中區分各個對象。一句話技術是為應用服務的。

    歡迎指正。



    畫了個圖,感覺沒有完全表達出自己的意思。。。。。誰幫忙完善下,最好能體現各個O在MVC中的位置
    snap20070108.jpg 


    轉自:http://www.tkk7.com/vip01/archive/2007/01/08/92430.html

    posted @ 2007-05-17 09:44 Coolfiry 閱讀(336) | 評論 (0)編輯 收藏

    I love English from then on.I study English hard form then on.I love it.It is very lovely.

    posted @ 2006-11-26 14:13 Coolfiry 閱讀(269) | 評論 (0)編輯 收藏

    一、引言

      隨著Internet的飛速發展,人們越來越依靠網絡來 查找他們所需要的信息,但是,由于網上的信息源多不勝數,也就是我們經常所說的"Rich Data, Poor Information"。所以如何有效的去發現我們所需要的信息,就成了一個很關鍵的問題。為了解決這個問題,搜索引擎就隨之誕生。

       現在在網上的搜索引擎也已經有很多,比較著名的有AltaVista, Yahoo, InfoSeek, Metacrawler, SavvySearch等等。國內也建立了很多的搜索引擎,比如:搜狐、新浪、北極星等等,當然由于它們建立的時間不長,在信息搜索的取全率和取準率上都 有待于改進和提高。

      Alta Vista是一個速度很快的搜索引擎,由于它強大的硬件配置,使它能夠做及其復雜的查詢。它主要是基于關鍵字進行查詢,它漫游的領域有Web和 Usenet。支持布爾查詢的"AND","OR"和"NOT",同時還加上最相近定位"NEAR",允許通配符和"向后"搜索(比如:你可以查找鏈接到 某一頁的所有Web站點)。你可以決定是否對搜索的短語加上權值,在文檔的什么部位去查找它們。能夠進行短語查詢而不是簡單的單詞查詢的優點是很明顯的, 比如,我們想要查找一個短語"to be or not to be",如果只是把它們分解成單詞的話,這些單詞都是屬于Stop Word,這樣這個查詢就不會有任何結果,但是把它當作一個整體來查詢,就很容易返回一些結果,比如關于哈姆雷特或者是莎士比亞等等的信息。系統對查詢結 果所得到的網頁的打分是根據在網頁中所包含的你的搜索短語的多少,它們在文檔的什么位置以及搜索短語在文檔內部之間的距離來決定的。同時可以把得到的搜索 結果翻譯成其他的語言。

      Exite是稱為具有"智能"的搜索引擎,因為它建立了一個基于概念的索引。當然,它所謂的"智能"是基 于對概率統計的靈活應用。它能夠同時進行基于概念和關鍵字的索引。它能夠索引Web,Usenet和分類的廣告。支持"AND","OR","NOT"等 布爾操作,同時也可以使用符號"+"和"-"。缺點是在返回的查詢結果中沒有指定網頁的尺寸和格式。

      InfoSeek是一個簡單 但是功能強大的索引,它的一個優點是有一個面向主題搜索的可擴展的分類。你可以把你的搜索短語和相似的分類目錄的主題短語相互參照,而那些主題短語會自動 加到你的查詢中去。使你的搜索有更好的主題相關性。同時它也支持對圖象的查詢。它能夠漫游Web,Usenet,Usenet FAQs等等。不支持布爾操作,但是可以使用符號"+"和"-"(相當于"AND"和"NOT")

      Yahoo實際上不能稱為是一 個搜索引擎站點,但是它提供了一個分層的主題索引,使你能夠從一個通常的主題進入到一個特定的主題,Yahoo對Web進行了有效的組織和分類。比如你想 要建立一個網頁,但是你不知道如何操作,為了在Yahoo上找到關于建立網頁的信息,你可以先在Yahoo上選擇一個主題:計算機和Internet,然 后在這個主題下,你可以發現一些子主題,比如:Web網頁制作,CGI編程,JAVA,HTML,網頁設計等,選擇一個和你要找的相關的子主題,最終你就 可以得到和該子主題相關的所有的網頁的鏈接。也就是說,如果你對要查找的內容屬于哪個主題十分清楚的話,通過目錄查詢的方法要比一般的使用搜索引擎有更好 的準確率。你可以搜索Yahoo的索引,但是事實上,你并沒有在搜索整個Web。但是Yahoo提供了選項使你可以同時搜索其他的搜索引擎,比如: Alta Vista。但是要注意的是Yahoo實際上只是對Web的一小部分進行了分類和組織,而且它的實效性也不是很好。

      搜索引擎的基本原理是通過網絡機器人定期在web網頁上爬行,然后發現新的網頁,把它們取回來放到本地的數據庫中,用戶的查詢請求可以通過查詢本地的數據庫來得到。如yahoo每天會找到大約500萬個新的網頁。

       搜索引擎的實現機制一般有兩種,一種是通過手工方式對網頁進行索引,比如yahoo的網頁是通過手工分類的方式實現的,它的缺點是Web的覆蓋率比較 低,同時不能保證最新的信息。查詢匹配是通過用戶寫入的關鍵字和網頁的描述和標題來進行匹配,而不是通過全文的匹配進行的。第二種是對網頁進行自動的索 引,象AltaVista則是完全通過自動索引實現的。這種能實現自動的文檔分類,實際上采用了信息提取的技術。但是在分類準確性上可能不如手工分類。

      搜索引擎一般都有一個Robot定期的訪問一些站點,來檢查這些站點的變化,同時查找新的站點。一般站點有一個robot.txt文 件用來說明服務器不希望Robot訪問的區域,Robot 都必須遵守這個規定。如果是自動索引的話,Robot在得到頁面以后,需要對該頁面根據其內容進行索引,根據它的關鍵字的情況把它歸到某一類中。頁面的信 息是通過元數據的形式保存的,典型的元數據包括標題、IP地址、一個該頁面的簡要的介紹,關鍵字或者是索引短語、文件的大小和最后的更新的日期。盡管元數 據有一定的標準,但是很多站點都采用自己的模板。文檔提取機制和索引策略對Web搜索引擎的有效性有很大的關系。高級的搜索選項一般包括:布爾方法或者是 短語匹配和自然語言處理。一個查詢所產生的結果按照提取機制被分成不同的等級提交給用戶。最相關的放在最前面。每一個提取出來的文檔的元數據被顯示給用 戶。同時包括該文檔所在的URL地址。

      另外有一些關于某一個主題的專門的引擎,它們只對某一個主題的內容進行搜索和處理,這樣信息的取全率和精度相對就比較高。

       同時,有一類搜索引擎,它本身不用Robot去定期的采集網頁。象SavvySearch 和 MetaCrawler是通過向多個搜索引擎同時發出詢問并對結果進行綜合返回給用戶實現搜索功能。當然實際上象SavvySearch能夠對各個搜索引 擎的功能進行分析和比較,根據不同的用戶查詢提交給不同的搜索引擎進行處理,當然用戶自己也可以指定利用哪一個搜索引擎。

      一個優秀的搜索引擎必須處理以下幾個問題:1 網頁的分類2 自然語言的處理3 搜索策略的調度和協作 4 面向特定用戶的搜索。所以很多搜索引擎不同程度的使用了一些人工智能的技術來解決這些方面的問題。

      二、網絡Spider的實現描述

       現在有很多文章對Web引擎做了大量的介紹和分析,但是很少有對它們的實現做一個詳細的描述,這里我們主要來介紹一個具有基本功能的Web引擎的實現。 本文,我們以類C++語言的形式來描述Web引擎如何采集網頁并存放到數據庫中的過程。同時描述了如何根據用戶輸入的關鍵字查詢數據庫并得到相關網頁的過 程。

      2.1數據庫結構

      首先,我們要建立一個數據庫表用來存放我們得到的網頁。這里一般需要建立如下的表:

      1.字典表的建立,事實上這里是用文檔中有意義的單詞和它們的出現頻率來代表一個文檔。

      該表(WordDictionaryTbl)主要要包括三個字段,主要是用來存放和一個網頁相關的單詞的情況

    ????url_id? ? 對每一個URL的唯一的ID號
    ? ? word? ? ? 該URL中的經過stem的單詞
    ? ? intag? ? 該單詞在該網頁中的出現的次數

      2.存儲每一個URL信息的表

      該表(URLTbl)中主要的關鍵字段有:

    ? rec_id? ? ? ? 每一條記錄的唯一的ID號
    ? status? ? 得到該URL內容的狀態,比如HTTP_STATUS_TIMEOUT表示
    ? ? ? ? ? ? 下載網頁的最大允許超時
    ? url? ? ? ? URL的字符串名稱
    ? content_type? ? ? 內容的類型
    ? last_modified? ? 最新的更改時間
    ? title? ? ? ? ? ? 該URL的標題
    ? docsize? ? ? ? ? 該URL的文件的尺寸
    ? last_index_time? 最近一次索引的時間
    ? next_index_time? 下一次索引的時間
    ? tag? ? 對于網頁,用來表示它的類型,比如:是text,或者是html,
    ? ? ? ? ? ? ? ? ? ? 或者是圖片等等
    ? hops? ? ? ? ? ? ? 得到文件時候的曾經失敗的次數
    ? keywords? ? ? ? ? 對于網頁,和該網頁相關的關鍵字
    ? description? ? ? 對于網頁,指網頁的內容的描述
    ? lang? ? ? ? ? ? ? 文檔所使用的語言

       3.因為網頁中有很多單詞是一些介詞和語氣助詞或者是非常常用的常用詞,它們本身沒有多少意義。比如:英語中的about,in,at,we,this 等等。中文中的如"和","一起","關于"等等。我們統一的把它們稱為停止詞(stop word)。所以我們要建立一個表,來包括所有這些停止詞。該表(StopWordTbl)主要有兩個字段。
    word char(32)? ? 表示那些停止詞
    lang char(2)? ? ? 表示所使用的語言

      4.我們要建立一個關于robot的表,我們在前面說過,所有的網站一般都有一個robot.txt文件用來表示網絡上的robot可以訪問的權限。該表(RobotTbl)主要有以下字段。
    ? ? hostinfo? ? ? ? ? Web站點主機的信息
    ? ? path? ? ? ? ? ? ? 不允許robot訪問的目錄

      5.建立我們需要屏蔽的那些網頁(比如一些內容不健康的或者沒有必要去搜索的站點)的一張表(ForbiddenWWWTbl),主要的字段就是網頁的URL。

       6.另外我們需要建立一個我們所要得到的文件類型的表(FileTypeTbl),比如,對于一個簡單的Web搜索引擎,我們可能只需要得到后綴為. html,htm,.shtml和txt的類型文件。其他的我們只是簡單的忽略它們。主要的字段就是文件的類型和說明。

      其中關于停止詞的表的內容是我們要實現要根據各種語言的統計結果,把那些意義不大的單詞放進去。關于文檔單詞、URL和Robot的表的內容都是在獲取Web網頁的時候動態增加記錄的。

      2.2 具體網頁獲取算法描述

      具體的網頁的獲取步驟是這樣的:

       我們可以設定我們的搜索程序最大可以開的線程的數目,然后這些線程可以同時在網上進行搜索,它們根據數據庫中已有的關于網頁的信息,找出那些需要更新的 網頁(如何判斷哪些網頁需要更新是一個值得研究的過程,現在有很多啟發式和智能的算法,基本上是基于統計規律進行建模。最簡單的當然是設定一個時間范圍, 在某個時間范圍以前的網頁被重新去搜索一遍),然后判斷那些網頁是否在屏蔽表中,如果是的話,就從關于URL的表中刪除該條記錄。否則,我們就到相應的 WWW站點去得到URL指定的文件(這里需要注意的是根據不同的URL的特點,需要使用不同的協議,比如對于FTP站點要采用FTP協議,對于HTTP站 點要采用HTTP協議,新聞站點要采用NNTP協議等等)事實上,我們先得到關于該網頁的頭信息,如果該網頁的最新修改時間和我們最近提取的時間是一樣的 話,表示該網頁內容沒有任何更新,則我們就不必去得到它的內容,只需要修改最近一次更新它的時間為當前的時間就可以了。如果該網頁最近做了修改,我們就要 得到該網頁,并對它的內容進行分析,主要要包括和它相關的鏈接,把它們加到相應的數據庫中,同時判斷網頁所包含的各種其他的文件,如文本文件、圖形文件、 聲音文件和其他多媒體文件是否是我們所需要的文件,如果是的話,就把它加到我們響應的數據庫中。同時要根據網頁的內容提取所有的有意義的單詞和它們的出現 的次數,放到相應的數據庫中。為了更好的描述這個過程,我們來看跟這個過程相關的主要的幾個對象和數據結構。對象主要是針對三個層次來講的。第一層是針對 WWW服務器,第二層是針對每一個頁面,第三層是針對每一個頁面的全文的索引。

      2.3 和實現相關的主要類對象和功能描述下面的結構是針對一個站點來說的。

    ? ? Class? CServer {
    ? ? 主要的屬性有:
    ????char *url;? ? ? ? ? ? //WWW站點的URL名稱
    ????char *proxy;? ? ? ? ? //使用的代理的名稱
    ????char *basic_auth;? ? ? //進行基本的HTTP認證
    ????int? proxy_port;? ? ? //代理的端口號
    ????int? period;? ? ? ? ? //再次索引的周期
    ????int? net_errors;? ? ? //網絡連接不通的次數
    ????int? max_net_errors;? //可以允許的最大的網絡錯誤
    ????int? read_timeout;? ? //下載文件允許的最大的延遲
    ????int? maxhops;? ? ? ? ? //表示URL可以最大跳轉的深度
    ????int? userobots;? ? ? ? //是否遵守robot.txt中的約定
    ????int? bodyweight;? // 在< body >....< /body >之間的單詞的權重
    ????int? titleweight; // 在< title >....< /title >之間的單詞的權重
    ????int? urlweight;? // 在文檔的URL中的單詞的權重
    ????int descweight;//在? ? < META
    NAME="Description"? ? ? ? Content="..." >之間單詞的權重
    ????int? keywordweight; //在< META NAME="Keywords" Content="..." >
    ? 之間的單詞的權重

      主要方法有:
    FindServer();//用來查找該服務器是否存在并可以連接
    FillDefaultAttribute() //用來針對所有的WWW服務器填寫默認的屬};

    以上的對象中的成員變量是和一個站點相關的參數的設置,我們對所有的站點有一個默認的設置,但是可以對某些站點做一些特殊的設置。這些設置可以在配置文件中設定。
      下面是關于文檔的結構的主要的數據成員:

    Class CNetDocument
    ? ? 主要屬性有:
    ? ? int????url_id; //該URL的ID號
    ? ? int????status;? //獲取該文檔時候的狀態
    ? ? int????size;? //文檔的尺寸
    int????tag;? //和該文檔相關的標簽,表示該文檔是
    HTML,TEXT或者是其他類型
    ? ? int????hops;? ? //URL跳轉的次數
    ? ? char????*url; //和該文檔相關的URL的名稱
    ? ? char????*content_type;? ? ? //該內容的類型
    ? ? char????*last_modified;? ? //最近一次的更新時間
    ? ? char????*title;? ? ? ? ? ? //該文檔的標題
    ? ? char????*last_index_time;? //上次索引的時間
    ? ? char????*next_index_time;? //下次索引的時間
    ? ? char????*keywords;? ? ? ? ? //該文檔中的關鍵字
    ? ? char????*description;? ? ? //該文檔的描述

    ? 主要方法有:
    ? FillDocInfo(…) //根據數據庫,得到該文檔相關信息
    ? AddHerf(…)? ? //加入網頁中存在的新的鏈接的網址
    ? DeleteURL(…)? //刪除一個存在的網址
    ? CanGetThisURL(…) //根據配置決定是否去得到該網頁
    ? //下面三個方法是根據不同的URL,用不同的協議去獲得文檔
    ? NNTPGet(…)? ? ?
    ? FTPGet(….)
    ? HTTPGet(….)
    ? ParseHead(…)? //如果是HTTP協議得到的話,分析頭信息
    ? ParseMainBody(…)? ? //對獲得的文檔的主體進行分析
    ? ServerResponseType (….)? //得到服務器端的響應消息
    ? UpdateURLDB(….)? //更新的數據入庫
    } ;

      事實上,我們在要提取一個網頁的時候,都要建立一個CNetDocument對象,然后再對這個網頁進行分析的時候,把相關的內容放到這個CNetDocument的成員變量里面。下面是關于頁面全文索引的結構的主要數據成員:
    Class CIndexer {
    主要屬性有:
    ? char????*url;? ? ? //我們要處理的文檔相關的URL的名稱
    ? int mwords;????? //? 我們事先設定的一個網頁的最大的單詞數目
    ????int nwords;????????? // 實際的得到的單詞的數目
    ????int swords;????????? // 我們已經排序的單詞的數目
    ????WORD *Word;????? //所有單詞的內容
    ????char *buf;? ? ? //我們為文檔所分配的空間
    主要方法有:
    ? InitIndexer(…)? ? //進行初始設置和分配
    ? ParseGetFile(…)? //對得到的網頁進行全文索引
    ? AddWord(…)? ? //把網頁的可以索引的單詞加到Word數組中去
    ? InToDB(….)? ? //關于網頁全文索引的信息入庫
    };

      進行網頁提取前,我們要建立一個CIndexer對象,它主要是用來對網頁進行全文的索引。一般來說我們只對兩種類型的URL進行全文索引,一個是text/html,另外一個是text/plain。其中WORD的數據結構如下:
    ? ? ? ? typedef struct word_struct {
    ????int count;? //該單詞出現的次數
    ????int code;? //該單詞的正常的形式,
    比如單詞可能為 encouraging,它的正常的形式應該為
    encourage,這其實是一種對單詞的stem。
    即我們只取單詞的主干部分。
    ????char *word;? //該單詞的內容
    } WORD;

      以下的結構是和網頁中的一些鏈接的對象相關的一個數據結構
    ? ? typedef struct href_struct {
    ????char *href;? ? //該鏈接的名稱
    ????int hops;? ? ? //發生的跳轉次數
    ????int stored;? ? //是否已經存儲到數據庫中
    } HREF;
    ?

      所有需要更新的和新產生的URL都被放到這個結構中,當它的數量超過一定的范圍以后,被一次性的存入數據庫。
      關于URL的一個數據結構如下:

    typedef struct url {
    char *schema; //表示該URL是通過什么協議得到的,比如HTTP,
    ? ? ? ? ? ? ? FTP,NNTP等。
    char *specific;? ? //主機的名稱加上路徑
    char *hostinfo;? ? //主機的名稱加上相關的協議端口
    char *hostname;? ? //主機的名稱
    char *path;? ? ? ? //在主機的具體的路徑
    char *filename;? ? //文件的名稱
    char *anchor;? ? ? //相關的anchor
    int? port;? ? ? ? //協議相關的端口
    } URL;

      這是針對URL的一些相關的屬性的描述的一個數據結構。事實上在數據庫中,我們存儲的只是對網頁的描述和對一些文本和HTML頁面的關鍵詞的索引信息。我們并不存儲網頁的實際的內容。

      三、用戶查詢實現描述

      關于對用戶提交的查詢請求的實現分析:

      用戶想要查詢某一方面的信息一般都是通過提供和該領域相關的幾個關鍵字來進行的。

      我們來看一下關于用戶查詢的相關的數據結構和類:

      下面是一個關于單詞和它的權值的基本結構:

    ? typedef struct word_weight_pair
    ? ? {
    ? ? ? char word[WORD_LEN];
    ? ? ? int weight;
    ? ? }word_weight_pair;
    ? ?

      下面的類主要是用來對用戶的查詢進行處理和分析:
    ? ? Class CUserQuery
    ? ? {
    char m_UserQuery[MAX_QUERYLEN];? //用戶的查詢表達式
    CPtrArray word_weight_col;
    //是關于結構word_weight_pair的動態數組
    int m_maxReturnSum;? //用戶希望返回的最多的網頁數
    int search_mode;
    CObArray m_returnDoc;? //是關于CNetDocument對象的一個動態數組
    NormalizeWord(char* OneWord);? //對單詞進行歸整化,即Stem.
    Find(char* odbcName);? //進行數據庫查找和匹配
    };

      系統實現的基本的步驟如下:

      1.對用戶輸入的查詢表達式進行分析。事實上,我們在前面的Spider搜索過程中對文檔的表示是通過關鍵字形式描述的,每一個文檔可以表示為這樣的一個集合

    ? ? 其中 ::=< 單詞或短語名稱 >< 單詞或短語的權值 >

      實際上就是采用矢量空間的表示方法來表示的文檔。

       我們對用戶輸入的查詢表達式也采用矢量空間的表示方法。我們認為用戶輸入的關鍵字的順序代表了它的重要性的程度,所以對于位置靠前的單詞有相對比較高的 優先級,同時我們對所有的內容以短語或者是單詞為最小原子,進行Stem操作,即象前面所提到的:比如單詞Encouraging就轉化成 Encourage的格式。然后去掉那些Stop Word,比如is ,as等等的單詞,這些單詞存放在StopWordTbl表中。 然后把所有歸整化后的內容放入動態數組word_weight_col中去。

      2.對于動態數組word_weight_col中 的每一個元素,即結構word_weight_pair(包括單詞和該單詞的權重),我們從表WordDictionaryTbl中可以找到和這些單詞相 關的記錄,這些記錄應該是包括了所有的在word_weight_col中的單詞。

      進行網頁是否和查詢相匹配的計算。匹配計算的 過程如下:首先我們對所有的記錄按URL地址進行排序。因為可能好幾條記錄對應的是一個URL,然后對每一個網頁進行打分,每一條記錄的單詞權值為 INITSCORE*WEIGHT+(TOTALTIMES-1)*WEIGHT* INCREMENT。其中INITSCORE為每一個單詞的基準分數,TOTALTIMES為該單詞在網頁中的出現的次數,WEIGHT是該單詞在不同的 內容段出現有不同的權值(比如在KEYWORD段,或者是標題段,或者是內容段等等)。INCREMENT是該單詞每多出現一次所增加的分數。

      3.根據用戶指定的m_maxReturnSum,顯示匹配程度最高的前m_maxReturnSum頁。

      四、結束語

       我們利用上面所討論的機制,在WINDOWS NT操作系統下,用VC++和SQL SERVER實現了一個Web搜索引擎的網頁搜集過程。在建立了一個基本的搜索引擎的框架以后,我們可以基于這個框架,實現一些我們自己設計的算法,比如 如何更好的進行Spider的調度,如何更好的進行文檔的歸類,如何更好的理解用戶的查詢,用來使Web搜索引擎具有更好的智能性和個性化的特點。

    posted @ 2006-11-11 21:37 Coolfiry 閱讀(471) | 評論 (0)編輯 收藏

         摘要: 1.?? 目標 使用 apache 和 tomcat 配置一個可以應用的 web 網站,要達到以下要求: 1、? Apache 做為 HttpServer ,后面連接多個 tomcat...  閱讀全文

    posted @ 2006-11-06 17:20 Coolfiry 閱讀(724) | 評論 (0)編輯 收藏

    JDBC學習筆記
    2004-9-13?????星期一?????小雨

    l.?連接到數據庫的方法
    答:1)?ODBC(Open?Database?Connectivity)
    ???????一個以C語言為基礎訪問SQL為基礎數據庫引擎的接口,它提供了一致的接口用于和數據庫溝通以及訪問數據。
    ????2)?JDBC
    ???????Java版本的ODBC

    2.?JDBC應用編程接口
    答:JDBC應用編程接口是:
    ????1)?標準的數據訪問接口,可以連到不同的數據庫;
    ????2)?JAVA編程語言的一組類和接口。
    ????JDBC應用編程接口能夠:
    ????1)?連接到數據庫;
    ????2)?發SQL查詢字符串到數據庫;
    ????3)?處理結果。
    ????JDBC應用編程接口有二個主要的部分:
    ????1)?JAVA應用程序開發接口面向JAVA應用程序開發者;
    ????2)?JDBC驅動程序開發接口
    ????
    3.?JDBC?Driver
    答:1)?一大堆實現了JDBC類和接口的類;
    ????2)?提供了一個實現java.sql.Driver接口的類。

    4.?JDBC?Driver的四種類型
    答:1)?JDBC-ODBC橋
    ????由ODBC驅動提供JDBC訪問
    ????2)?本地API
    ????部分Java?driver把JDBC調用轉化成本地的客戶端API
    ????3)?JDBC-net
    ????純的Java?driver,將JDBC調用轉入DBMS,與網絡協議無關。然后通過服務器將調用轉為DBMS協議。
    ????4)?本地協議
    ????純的java?driver,將JDBC調用直接轉為DBMS使用的網絡協議

    5.?JDBC開發者接口
    答:1)?java.sql--java?2平臺下JDBC的主要功能,標準版(J2SE)
    ????2)?javax.sql--java?2平臺下JDBC增強功能,企業版(J2EE)

    6.?使用URL確認數據庫
    答:我們使用URL來確定一個數據庫(正確的Driver,正確的主機,正確的協議,正確的協議,正確的用戶名和密碼);
    ????語法:protocol:subprotocol:subname
    ????范例:jdbc:db2:MyTest
    ??????????jdbc:db2://localhost:6789/MyTest

    7.?javax.sql包JDBC2.0的增強功能
    答:1)?數據源接口;
    ????2)?連接池;
    ????3)?分布式交易;
    ????4)?行集;

    8.?創建一個基本的JDBC應用
    答:1)?步驟一:注冊一個driver;
    ????2)?步驟二:建立一個到數據庫的連接;
    ????3)?步驟三:創建一個statement;
    ????4)?步驟四:執行SQL語句;
    ????5)?步驟五:處理結果;
    ????6)?步驟六:關閉JDBC對象

    9.?注冊一個Driver(步驟一)
    答:1)?driver被用于連接到數據庫;
    ????2)?JDBC應用編程接口使用第一個能成功連接到給定URL的driver;
    ????3)?在同一時間可以裝載多個driver

    10.注冊一個driver的方法:
    答:1)?使用類loader(裝載;實例化;注冊入DriverManager)
    ???????a.?Class.forName("Com.ibm.db2.jdbc.app.DB2Driver");
    ???????b.?Class.forName("Com.ibm.db2.jdbc.net.DB2Driver");
    ???????c.?Class.forName("Com.microsoft.jdbc.sqlServer.SQLServerDriver);
    ???????d.?Class.forName("oracl.jdbc.driver.OracleDriver");
    ???????e.?Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
    ????2)?實例化一個Driver
    ???????a.?Driver?drv?=?new?COM.cloudscape.core.RmiJdbcDriver();

    2004-9-14?????星期二?????陰

    1.?建立一個到數據庫的連接(步驟二)
    答:DriverManager調用getConnection(urlString)方法,實際上調用的是driver的connect(urlString)方法;
    ????1)?當一個driver肯定地對應到一個數據庫URL,DriverManager建立一個連接;
    ????2)?當沒有driver匹配,返回null然后下一個driver被檢驗;
    ????3)?假如沒有建立連接,拋出一個SQLExcepiton異常

    2.?經常使用的一些JDBC?URL
    答:1)?JDBC-ODBC:?jdbc:odbc:<DB>
    ????2)?Oracle:?jdbc:oracle:oci:@<sid>?or?jdbc:oracle:thin:@<SID>
    ????3)?Weblogic?MS-SQL:?jdbc:weblogic:mssqlserver4:<DB>@<HOST>:<PORT>
    ????4)?DB2:?jdbc:db2:MyTest?or?jdbc.db2://localhost:6789/MyTest(需要用戶名和密碼)

    3.?Driver連接方法
    答:1)?創建一個到指定Driver實例的直接調用;
    ????2)?避免一般訪問的問題
    ???????Driver?drv?=?new?COM.ibm.db2.jdbc.app.DB2Driver();
    ???????Connection?con?=?null;
    ???????try?{con?=?drv.connect("jdbc:db2:MyTest",new?Properties())}
    ???????catch(SQLException?e){}

    4.?創建一個Statement(步驟三)
    答:1)?Statement的三個接口:
    ???????a.?Statement;
    ???????b.?PreparedStatement(繼承自Statement);
    ???????c.?CallableStatement(繼承自PreparedStatement);
    ????2)?使用方法Connection.createStatement()得到一個Statement對象

    5.?PreparedStatement對象
    答:1)?調用ProparedStatement比statement更為高效;
    ????2)?繼承自Statement;
    ????3)?語法:PreparedStatement?pstm?=?connection.prepareStatement(sqlString);

    6.?CallableStatement對象
    答:1)?通過CallableStatement調用數據庫中的存儲過程;
    ????2)?繼承自PreparedStatement;
    ????3)?CallableStatement?cstm?=?connection.prepareCall("{call?return_student[?,?]}");
    ???????cstm.setString(1,"8623034");
    ???????cstm.registerOutparameter(2,?Types.REAL);
    ???????cstm.execute();
    ???????float?gpa?=?cstm.getFloat(2);

    7.?Statement接口的比較
    答:?????????????|?Statement???????????|?PreparedStatement?????????|??CallableStatement
    ????------------------------------------------------------------------------------
    ????寫代碼位置???|???客戶端????????????|?客戶端????????????????????|??服務器端
    ????------------------------------------------------------------------------------
    ????寫代碼位置???|???客戶端????????????|?服務器端??????????????????|??服務器端
    ????------------------------------------------------------------------------------
    ????編寫代碼技術?|Java,SQL操作????????|Java,SQL操作??????????????|??數據庫的程序語言,如PL/SQL
    ????------------------------------------------------------------------------------
    ????可配置性?????|???高????????????????|第一次高,以后低???????????|??低
    ????------------------------------------------------------------------------------
    ????可移植性?????|???高????????????????|假設支持PreparedStatement的話高????
    ????------------------------------------------------------------------------------
    ????傳輸效率?????|???低????????????????|第一次低,以后高???????????|??高

    8.?執行SQL?Statement(步驟四)
    答:通過接口方法將SQL語句傳輸至?認的數據庫連接,返回結果可能是一個數據表,可以通過java.sql.ResultSet訪問。
    ????1)?Statement的接口方法:
    ????a.?executeQuery(sqlString):?執行給定的SQL聲明,返回一個結果集(ResultSet)對象;
    ????b.?executeUpdate(sqlString):?執行給定的SQL聲明,可以是INSERT、UPDATE或DELETE聲明,也可以是SQL?DDL聲明;
    ????c.?execute(sqlString):?執行給定的SQL聲明。

    9.?處理結果(步驟五)
    答:1)?使用結果集(ResultSet)對象的訪問方法獲取數據;
    ???????a.?next():下一個記錄
    ???????b.?first():第一個記錄
    ???????c.?last():最后一個記錄
    ???????d.?previous():上一個記錄
    ????2)?通過字段名或索引取得數據
    ????3)?結果集保持了一個指向了當前行的指針,初始化位置為第一個記錄前。

    10.?關閉JDBC對象(步驟六)
    答:1)?首先關閉記錄集;
    ????2)?其次關閉聲明;
    ????3)?最后關閉連接對象。

    11.?數據表和類對應的三種關系:
    答:1)?一個表對應一個類;
    ????2)?一個表對應相關類;
    ????3)?一個表對應整個類關系層

    12.?類間關系的幾種表設計:
    答:1)?多對一,
    ????2)?一對一:?
    ????3)?一對多:
    ????4)?多對多:

    13.?SQL數據類型及其相應的Java數據類型
    答:SQL數據類型?????????????????????Java數據類型??????????????說明
    ????------------------------------------------------------------------
    ????INTEGER或者INT??????????????????int?????????????????????通常是個32位整數
    ????SMALLINT????????????????????????short???????????????????通常是個16位整數
    ????NUMBER(m,n)?DECIMAL(m,n)????????Java.sql.Numeric????????合計位數是m的定點十進制數,小數后面有n位數
    ????DEC(m,n)????????????????????????Java.sql.Numeric????????合計位數是m的定點十進制數,小數后面有n位數
    ????FLOAT(n)????????????????????????double??????????????????運算精度為n位二進制數的浮點數
    ????REAL????????????????????????????float???????????????????通常是32位浮點數
    ????DOUBLE??????????????????????????double??????????????????通常是64位浮點數
    ????CHARACTER(n)或CHAR(n)???????????String??????????????????長度為n的固定長度字符串
    ????VARCHAR(n)??????????????????????String??????????????????最大長度為n的可變長度字符串
    ????BOOLEAN?????????????????????????boolean?????????????????布爾值
    ????DATE????????????????????????????Java.sql.Date???????????根據具體設備而實現的日歷日期
    ????TIME????????????????????????????Java.sql.Time???????????根據具體設備而實現的時戳
    ????TIMESTAMP???????????????????????Java.sql.Timestamp??????根據具體設備而實現的當日日期和時間
    ????BLOB????????????????????????????Java.sql.Blob???????????二進制大型對象
    ????CLOB????????????????????????????Java.sql.Clob???????????字符大型對象
    ????ARRAY???????????????????????????Java.sql.Array
    ????

    2004-9-15?????星期三??????陰

    1.?元數據
    答:關于數據的信息,例如類型或者容量。通過JDBC?API可以訪問:
    ????1)?數據庫元數據;
    ???????a.?使用connection.getMetadata方法返回DataMetaData引用
    ???????b.?能夠使用isReadOnly此類方法獲取信息
    ????2)?結果集元數據;
    ???????a.?使用ResultSet.getMetadata方法返回ResultSetMetaData引用
    ???????b.?能夠使用getColumnCount此類方法獲取信息

    2.?事務處理
    答:1)?一系列的動作作為一個不可分的操作;
    ????2)?JDBC?API中使用事務處理步驟:
    ???????a.?用false作為參數調用setAutoCommit方法;
    ???????b.?執行一或多個關于數據庫的操作;
    ???????c.?調用commit方法完成改變;
    ???????d.?恢復上次提交后的改變,調用rollback方法.

    ???????try
    ???????{
    ??????????con.setAutoCommit(false);
    ??????????Statement?stm?=?con.createStatement();
    ??????????stm.executeUpdate("insert?into?student(name,?age,?gpa)?values('gzhu',?30,?4.8)");
    ??????????stm.commit();
    ???????}
    ???????catch(SQLException?e)
    ???????{
    ??????????try
    ??????????{
    ?????????????con.rollback();
    ??????????}
    ??????????catch(Exception?e)
    ??????????{
    ??????????}
    ???????}

    3.?并發控制
    答:1)?設置隔離級別方法:setTransactionIsolation
    ????2)?隔離級別靜態變量
    ???????a.?TRANSACTION_NONE:只讀的數據字典;
    ???????b.?TRANSACTION_READ_UNCOMMITTED:只讀未提交數據;
    ???????c.?TRANSACTION_READ_COMMITTED:只讀未提交數據;
    ???????d.?TRANSACTION_REPEATABLE_READ:重復讀取數據;
    ???????e.?TRANSACTION_SERIALIZABLE:無論做什么操作都不許別人動。
    ????3)?示例:con.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED);

    4.?JDBC?2.0?應用程序編程接口增強功能
    答:1)?ResultSet增強:
    ???????a.?可以回卷;
    ???????b.?可以修改;
    ???????設置示例:Statement?stm?=?con.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,ResultSet.CONCUR_UPDATABLE);
    ????2)?Statement增強了批量修改能力(batch?updates);
    ????3)?更高級的數據類型(例:Struct)。

    5.?JDBC?2.0標準擴展
    答:1)?JNDI(Java?Naming?and?Directory?Interface):?解決離散狀態下Object的查找;
    ????2)?連接池:在內存中保存了一個數據庫連接,不需要注冊驅動器,提高性能的重要方法。

    posted @ 2006-11-03 10:14 Coolfiry 閱讀(238) | 評論 (0)編輯 收藏

    問題引入:
    在實習過程中發現了一個以前一直默認的錯誤,同樣char *c = "abc"和char c[]="abc",前者改變其內

    容程序是會崩潰的,而后者完全正確。
    程序演示:
    測試環境Devc++
    代碼
    #include <iostream>
    using namespace std;

    main()
    {
    ?? char *c1 = "abc";
    ?? char c2[] = "abc";
    ?? char *c3 = ( char* )malloc(3);
    ?? c3 = "abc";
    ?? printf("%d %d %s\n",&c1,c1,c1);
    ?? printf("%d %d %s\n",&c2,c2,c2);
    ?? printf("%d %d %s\n",&c3,c3,c3);
    ?? getchar();
    }??
    運行結果
    2293628 4199056 abc
    2293624 2293624 abc
    2293620 4199056 abc

    參考資料:
    首先要搞清楚編譯程序占用的內存的分區形式:
    一、預備知識—程序的內存分配
    一個由c/C++編譯的程序占用的內存分為以下幾個部分
    1、棧區(stack)—由編譯器自動分配釋放,存放函數的參數值,局部變量的值等。其操作方式類似于

    數據結構中的棧。
    2、堆區(heap)—一般由程序員分配釋放,若程序員不釋放,程序結束時可能由OS回收。注意它與數據

    結構中的堆是兩回事,分配方式倒是類似于鏈表,呵呵。
    3、全局區(靜態區)(static)—全局變量和靜態變量的存儲是放在一塊的,初始化的全局變量和靜態

    變量在一塊區域,未初始化的全局變量和未初始化的靜態變量在相鄰的另一塊區域。程序結束后由系統

    釋放。
    4、文字常量區—常量字符串就是放在這里的。程序結束后由系統釋放。
    5、程序代碼區
    這是一個前輩寫的,非常詳細
    //main.cpp
    ? int a=0;??? //全局初始化區
    ? char *p1;?? //全局未初始化區
    ? main()
    ? {
    ?? int b;棧
    ?? char s[]="abc";?? //棧
    ?? char *p2;???????? //棧
    ?? char *p3="123456";?? //123456\0在常量區,p3在棧上。
    ?? static int c=0;?? //全局(靜態)初始化區
    ?? p1 = (char*)malloc(10);
    ?? p2 = (char*)malloc(20);?? //分配得來得10和20字節的區域就在堆區。
    ?? strcpy(p1,"123456");?? //123456\0放在常量區,編譯器可能會將它與p3所向"123456"優化成一個

    地方。
    }
    二、堆和棧的理論知識
    2.1申請方式
    stack:
    由系統自動分配。例如,聲明在函數中一個局部變量int b;系統自動在棧中為b開辟空間
    heap:
    需要程序員自己申請,并指明大小,在c中malloc函數
    如p1=(char*)malloc(10);
    在C++中用new運算符
    如p2=(char*)malloc(10);
    但是注意p1、p2本身是在棧中的。
    2.2
    申請后系統的響應
    棧:只要棧的剩余空間大于所申請空間,系統將為程序提供內存,否則將報異常提示棧溢出。
    堆:首先應該知道操作系統有一個記錄空閑內存地址的鏈表,當系統收到程序的申請時,
    會遍歷該鏈表,尋找第一個空間大于所申請空間的堆結點,然后將該結點從空閑結點鏈表中刪除,并將

    該結點的空間分配給程序,另外,對于大多數系統,會在這塊內存空間中的首地址處記錄本次分配的大

    小,這樣,代碼中的delete語句才能正確的釋放本內存空間。另外,由于找到的堆結點的大小不一定正

    好等于申請的大小,系統會自動的將多余的那部分重新放入空閑鏈表中。
    2.3申請大小的限制
    棧:在Windows下,棧是向低地址擴展的數據結構,是一塊連續的內存的區域。這句話的意思是棧頂的地

    址和棧的最大容量是系統預先規定好的,在WINDOWS下,棧的大小是2M(也有的說是1M,總之是一個編譯

    時就確定的常數),如果申請的空間超過棧的剩余空間時,將提示overflow。因此,能從棧獲得的空間

    較小。
    堆:堆是向高地址擴展的數據結構,是不連續的內存區域。這是由于系統是用鏈表來存儲的空閑內存地

    址的,自然是不連續的,而鏈表的遍歷方向是由低地址向高地址。堆的大小受限于計算機系統中有效的

    虛擬內存。由此可見,堆獲得的空間比較靈活,也比較大。
    2.4申請效率的比較:
    棧:由系統自動分配,速度較快。但程序員是無法控制的。
    堆:是由new分配的內存,一般速度比較慢,而且容易產生內存碎片,不過用起來最方便.
    另外,在WINDOWS下,最好的方式是用Virtual Alloc分配內存,他不是在堆,也不是在棧,而是直接在進

    程的地址空間中保留一塊內存,雖然用起來最不方便。但是速度快,也最靈活。
    2.5堆和棧中的存儲內容
    棧:在函數調用時,第一個進棧的是主函數中后的下一條指令(函數調用語句的下一條可執行語句)的

    地址,然后是函數的各個參數,在大多數的C編譯器中,參數是由右往左入棧的,然后是函數中的局部變

    量。注意靜態變量是不入棧的。
    當本次函數調用結束后,局部變量先出棧,然后是參數,最后棧頂指針指向最開始存的地址,也就是主

    函數中的下一條指令,程序由該點繼續運行。
    堆:一般是在堆的頭部用一個字節存放堆的大小。堆中的具體內容由程序員安排。
    2.6存取效率的比較
    char s1[]="aaaaaaaaaaaaaaa";
    char *s2="bbbbbbbbbbbbbbbbb";
    aaaaaaaaaaa是在運行時刻賦值的;
    而bbbbbbbbbbb是在編譯時就確定的;
    但是,在以后的存取中,在棧上的數組比指針所指向的字符串(例如堆)快。
    比如:
    #include
    voidmain()
    {
    char a=1;
    char c[]="1234567890";
    char *p="1234567890";
    a = c[1];
    a = p[1];
    return;
    }
    對應的匯編代碼
    10:a=c[1];
    004010678A4DF1movcl,byteptr[ebp-0Fh]
    0040106A884DFCmovbyteptr[ebp-4],cl
    11:a=p[1];
    0040106D8B55ECmovedx,dwordptr[ebp-14h]
    004010708A4201moval,byteptr[edx+1]
    004010738845FCmovbyteptr[ebp-4],al
    第一種在讀取時直接就把字符串中的元素讀到寄存器cl中,而第二種則要先把指針值讀到edx中,在根據

    edx讀取字符,顯然慢了。
    2.7小結:
    堆和棧的區別可以用如下的比喻來看出:
    使用棧就象我們去飯館里吃飯,只管點菜(發出申請)、付錢、和吃(使用),吃飽了就走,不必理會

    切菜、洗菜等準備工作和洗碗、刷鍋等掃尾工作,他的好處是快捷,但是自由度小。
    使用堆就象是自己動手做喜歡吃的菜肴,比較麻煩,但是比較符合自己的口味,而且自由度大。

    自我總結:
    char *c1 = "abc";實際上先是在文字常量區分配了一塊內存放"abc",然后在棧上分配一地址給c1并指向

    這塊地址,然后改變常量"abc"自然會崩潰

    然而char c2[] = "abc",實際上abc分配內存的地方和上者并不一樣,可以從
    4199056
    2293624 看出,完全是兩塊地方,推斷4199056處于常量區,而2293624處于棧區

    2293628
    2293624
    2293620 這段輸出看出三個指針分配的區域為棧區,而且是從高地址到低地址

    2293620 4199056 abc 看出編譯器將c3優化指向常量區的"abc"


    繼續思考:
    代碼:
    #include <iostream>
    using namespace std;

    main()
    {
    ?? char *c1 = "abc";
    ?? char c2[] = "abc";
    ?? char *c3 = ( char* )malloc(3);
    ?? //? *c3 = "abc" //error
    ?? strcpy(c3,"abc");
    ?? c3[0] = 'g';
    ?? printf("%d %d %s\n",&c1,c1,c1);
    ?? printf("%d %d %s\n",&c2,c2,c2);
    ?? printf("%d %d %s\n",&c3,c3,c3);
    ?? getchar();
    }??
    輸出:
    2293628 4199056 abc
    2293624 2293624 abc
    2293620 4012976 gbc
    寫成注釋那樣,后面改動就會崩潰
    可見strcpy(c3,"abc");abc是另一塊地方分配的,而且可以改變,和上面的參考文檔說法有些不一定,

    而且我不能斷定4012976是哪個區的,可能要通過算區的長度,希望高人繼續深入解釋,謝謝
    ?

    posted @ 2006-10-16 19:06 Coolfiry 閱讀(1147) | 評論 (2)編輯 收藏

    主站蜘蛛池模板: 国产精品亚洲色婷婷99久久精品| 一区二区三区免费电影| 国产又大又粗又硬又长免费| 久久成人18免费网站| 亚洲视频一区在线观看| 日韩一品在线播放视频一品免费| A国产一区二区免费入口| 亚洲女人初试黑人巨高清| 波多野结衣免费视频观看| 一级毛片免费视频| 国产一区二区三区亚洲综合| 久久亚洲国产中v天仙www | 又粗又大又黑又长的免费视频| 亚洲国产精品成人午夜在线观看| 亚洲国产精品无码久久一区二区| 最近中文字幕无吗免费高清| 中文字幕不卡高清免费| 亚洲人成色99999在线观看| 亚洲av无码潮喷在线观看| 啊灬啊灬别停啊灬用力啊免费看| 91香蕉在线观看免费高清| 亚洲精品黄色视频在线观看免费资源 | 亚洲国产精品成人精品无码区| 夜夜爽免费888视频| 午夜精品射精入后重之免费观看 | 国产精品小视频免费无限app| 亚洲国产成人久久| 亚洲AV无码国产精品麻豆天美 | 中文字幕影片免费在线观看| a级黄色毛片免费播放视频| 国产精品亚洲精品日韩动图| 精品亚洲成a人片在线观看少妇| 亚洲人成网站观看在线播放| 午夜两性色视频免费网站| 91香蕉国产线在线观看免费| 视频免费在线观看| 一级一级一片免费高清| 亚洲AV永久无码精品网站在线观看 | 在线观看亚洲精品国产| 免费日本黄色网址| 免费看AV毛片一区二区三区|