• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            Fork me on GitHub
            隨筆 - 215  文章 - 13  trackbacks - 0
            <2016年7月>
            262728293012
            3456789
            10111213141516
            17181920212223
            24252627282930
            31123456


            專注即時通訊及網游服務端編程
            ------------------------------------
            Openresty 官方模塊
            Openresty 標準模塊(Opm)
            Openresty 三方模塊
            ------------------------------------
            本博收藏大部分文章為轉載,并在文章開頭給出了原文出處,如有再轉,敬請保留相關信息,這是大家對原創作者勞動成果的自覺尊重!!如為您帶來不便,請于本博下留言,謝謝配合。

            常用鏈接

            留言簿(1)

            隨筆分類

            隨筆檔案

            相冊

            Awesome

            Blog

            Book

            GitHub

            Link

            搜索

            •  

            積分與排名

            • 積分 - 215465
            • 排名 - 118

            最新評論

            閱讀排行榜

            http://wqtn22.iteye.com/blog/1820587

            1. 進程標志設置:

                   消息和binary內存:erlang:process_flag(min_bin_vheap_size, 1024*1024),減少大量消息到達或處理過程中產生大量binary時的gc次數

                   堆內存:erlang:process_flag(min_heap_size, 1024*1024),減少處理過程中產生大量term,尤其是list時的gc次數

                   進程優先級:erlang:process_flag(priority, high),防止特殊進程被其它常見進程強制執行reductions

                   進程調度器綁定:erlang:process_flag(scheduler, 1),當進程使用了port時,還需要port綁定支持,防止進程在不同調度器間遷移引起性能損失,如cache、跨numa node拷貝等,當進程使用了port時,主要是套接字,若進程與port不在一個scheduler上,可能會引發嚴重的epoll fd鎖競爭及跨numa node拷貝,導致性能嚴重下降

              2. 虛擬機參數:

                 +S X:X :啟用調度器數量,多個調度器使用多線程,有大量鎖爭用

                 -smp disable :取消smp,僅使用單線程,16個-smp_disabled虛擬機性能高于+S 16:16

                 +sbt db :將scheduler綁定到具體的cpu核心上,再配合erlang進程和port綁定,可以顯著提升性能,但是如果綁定錯誤,反而會有反效果

              3. 消息隊列:

                 消息隊列長度對性能的影響主要體現在以下兩個方面:進程binary堆的gc和進程內消息匹配,前者可以通過放大堆內存來減少gc影響,后者需要謹慎處理。

                 若進程在處理消息時是通過消息匹配方式取得消息,同時又允許其它進程無限制投遞消息到本進程,此時會引發災難,匹配方式取得消息會引發遍歷進程消息隊列,如果此時仍然有其它進程投遞消息,會導致進程消息隊列暴漲,遍歷過程也將增大代價,引發惡性循環。已知模式有:在gen_server中使用file:write(raw模式)或gen_tcp:send等,這些操作都是erlang虛擬機內部通過port driver實現的,均有內部receive匹配接收,對于這些操作,最好的辦法是將其改寫為nif,直接走進程堆進行操作,次之為將file:write或gen_tcp:send改寫為兩階段,第一階段為port_command,第二階段由gen_server接收返回結果,這種異步化可能有些正確性問題,對于gen_tcp:send影響不大,因為網絡請求本身要么同步化要么異步化,都需要內部的確認機制;對于file:write影響較大,file:write的錯誤通常為目錄不存在或磁盤空間不足,確保這兩個錯誤不造成影響即可,同時如果進程的其它部分需要使用file的其它操作,必須首先清空之前file:write產生的所有file的port消息,否則有可能產生消息序列紊亂的問題。

                 對于套接字的接口調用,可以參考rabbitmq的兩階段套接字發送方法,而對于文件接口調用,可以參考riak的bitcask引擎將文件讀寫封裝為nif的方法

              4. 內存及ets表:

                 ets表可以用于進程間交換大數據,或充當緩存,以及復雜匹配代理等,其性能頗高,并發讀寫可達千萬級qps,并有兩個并發選項,在建立表時設置,分別是{write_concurrency, true} | {read_concurrency, true},以允許ets的并發讀寫

                 使用ets表可以繞過進程消息機制,從而在一定程度上提高性能,并將編程模式從面向消息模式變為面向共享內存模式

              5. CPU密集型操作:

                 erlang執行流程的問題:

                   1. 其指令都是由其虛擬機執行的,一條指令可能需要cpu執行3-4條指令,一些大規模的匹配或遍歷操作會嚴重影響性能;

                   2. 其bif調用執行過程類似于操作系統的系統調用,需要對傳入參數進行轉換,在大量小操作時損失性能較為嚴重

                   3. 其port driver流程較為繁冗復雜,需要經歷大量的回調等,一般的小功能操作,不要通過port driver實現

                 建議:

                   字符串匹配不要通過list進行,最好通過binary;單字節匹配,尤其是語法解析,如xmerl、mochijson2、lexx等,盡管使用binary,但是它們是一個字節一個字節匹配的,性能會退化到list的水平,應該盡量將其nif化;

                   對于一些小操作,反而應該去bif化、去nif化、去port driver化,因為進入erlang內部函數的執行代價也不小;

                   已知的性能瓶頸:re、xmerl、mochijson2、lexx、erlang:now、calendar:local_time_to_universal_time_dst等

              6. 數據結構:

                 減少遍歷,盡量使用API提供的操作

                 由于各種類型的變量實際可以當做c的指針,因此erlang語言級的操作并不會有太大代價

                 lists:reverse為c代碼實現,性能較高,依賴于該接口實現的lists API性能都不差,避免list遍歷,[||]和foreach性能是foldl的2倍,不在非必要的時候遍歷list

                 dict:find為微秒級操作,內部通過動態hash實現,數據結構先有若干槽位,后根據數據規模變大而逐步增加槽位,fold遍歷性能低下

                 gb_trees:lookup為微秒級操作,內部通過一個大的元組實現,iterator+next遍歷性能低下,比list的foldl還要低2個數量級

                 其它常用結構:queue,set,graph等

              7. 計時器:

                 erlang的計時器timer是通過一個唯一的timer進程實現的,該進程是一個gen_server,用戶通過timer:send_after和timer:apply_after在指定時間間隔后收到指定消息或執行某個函數,每個用戶的計時器都是一條記錄,保存在timer的ets表timer_tab中,timer的時序驅動通過gen_server的超時機制實現。若同時使用timer的用戶過多,則tiemr將響應不過來,成為瓶頸。

                 更好的方法是使用erlang的原生計時器erlang:send_after和erlang:start_timer,它們把計時器附著在進程自己身上。

              8. 尾調用和尾遞歸:

                 尾調用和尾遞歸是erlang函數式語言最強大的優化,盡量保持函數尾部有尾調用或尾遞歸

              9. 文件預讀,批量寫,緩存:

                 這些方式都是局部性的體現:

                 預讀:讀空間局部性,文件提供了read_ahead選項

                 批量寫:寫空間局部性

                   對于文件寫或套接字發送,存在若干級別的批量寫:

                     1. erlang進程級:進程內部通過list緩存數據

                     2. erlang虛擬機:不管是efile還是inet的driver,都提供了批量寫的選項delayed_write|delay_send,

                        它們對大量的異步寫性能提升很有效

                     3. 操作系統級:操作系統內部有文件寫緩沖及套接字寫緩沖

                     4. 硬件級:cache等

                 緩存:讀寫時間局部性,讀寫空間局部性,主要通過操作系統系統,erlang虛擬機沒有內部的緩存

             10.套接字標志設置:

                 延遲發送:{delay_send, true},聚合若干小消息為一個大消息,性能提升顯著

                 發送高低水位:{high_watermark, 128 * 1024} | {low_watermark, 64 * 1024},輔助delay_send使用,delay_send的聚合緩沖區大小為high_watermark,數據緩存到high_watermark后,將阻塞port_command,使用send發送數據,直到緩沖區大小降低到low_watermark后,解除阻塞,通常這些值越大越好,但erlang虛擬機允許設置的最大值不超過128K

                 發送緩沖大小:{sndbuf, 16 * 1024},操作系統對套接字的發送緩沖大小,在延遲發送時有效,越大越好,但有極值

                 接收緩沖大小:{recbuf, 16 * 1024},操作系統對套接字的接收緩沖大小

             11. 序列化/反序列化:

                 通常情況下,為了簡化實現,一般將erlang的term序列化為binary,傳遞到目的地后,在將binary反序列化為term,這通常涉及到兩個操作:

                 term_to_binary及binary_to_term,這兩個操作性能消耗極為嚴重,應至多只做一次,減少甚至消除它們是最正確的,例如直接構造binary進行跨虛擬機數據交換;

                 但對比與其它的序列化和反序列化方式,如利用protobuf等,term_to_binary和binary_to_term的性能是高于這些方式的,畢竟是erlang原生格式,對于力求簡單的應用,其序列化和反序列化方式推薦term_to_binary和binary_to_term

             12. 并發化

                 在一些場景下,如web請求、數據庫請求、分布式文件系統等,單個接入接口已經不能滿足性能需求,需要有多個接入接口,多個數據通道,等等,這要求所有請求處理過程必須是無狀態的,或者狀態更改同步進入一個公共存儲,而公共存儲也必須是支持并發處理的,如并發數據庫、類hdfs、類dynamo存儲等,若一致性要求較高,最好選用并發數據庫,如mysql等,若在此基礎上還要求高可用,最好選擇同步多結點存儲,

                 mnesia、zk都是這方面的典型;若不需要較高的一致性,類hdfs、類dynamo這類no sql存儲即可滿足

             13. hipe

                 將erlang匯編翻譯成機器碼,減少一條erlang指令對應的cpu指令數

            http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=429659&id=5205993

            本人主要從事游戲后端開發,所以本文只從游戲開發角度分析Erlang使用中應注意的問題和優化點。

            1.  單節點還是多節點

            Erlang節點之間的通信是透明的,節點內部和外部之間的調用一致。基于這樣的特性,很多人會選用多節點,把各子系統(登陸節點,玩家節點,地圖節點,全局節點等)分配到不同的節點中,以支持更多的在線玩家。這樣做的出發點是好點是好的,但會引起一列表的問題:登陸、轉場邏輯復雜,節點間的消息廣播頻繁,玩家數據同步、一致問題,內存消耗,運維復雜化等。相比之下,單節點就簡單多了,不用考慮節點通信,玩家數據保證一致,運維方便,一機多服。在開啟SMP的情間下,單節點的性能已經很好。對頁游的業務,同時在線達到5000人已經非常少見,即使達到也是首服當天才會出現,單節點完全可以應付這樣的情況,所以沒必要用多節點,增加系統復雜性。

            2. 消息廣播

            消息廣播是游戲中的性能消耗大頭,主要包括地圖的行走、PK廣播,世界聊天廣播。世界聊天廣播可以通過CD等策劃手段限制,行走和PK包的廣播實時性高,只能通過技術手段解決。地圖中的廣播包,只需發給視野內的玩家就可以,不用全地圖廣播。視野內的玩家可以通過九宮格劃分,以 X,Y為主鍵,映射到對應的玩家數據。以九宮格方式查找玩家非常高效,我第一個游戲,地圖中的玩家起初是保存在一個列表中,每次廣播時都要遍歷列表,找出同屏玩家,消息廣播非常低效,特別是在PK時,CPU占用高。用九宮格優化后,一切問題都解決了。還有一個優化廣播問題的方法是數據包緩存。

            3. 緩存-數據庫,網絡

            緩存是用空間換時間,它是性能優化中常用的方法。數據庫緩存,開服時把玩家的必要數據加載到內存中,可以減少玩家的登陸延時,應對玩家并發登陸,刷新也很有效。同時玩家數據沒必要實現存庫,對于坐標,經驗,金錢等變化頻繁的值,如果實時存在,會很容易壓跨數據庫或對存庫進程造成消息阻塞。玩家改變的數據可以緩在內存中,定時存庫,或下線時再存庫。網絡中的消息包也可以在應用層給緩存起來,達到一定長度或延時一定時間后再發出去。雖然虛擬機和TCP層會做緩存,最好還是在應用層做一次緩存。

            4. 進程-每玩家應該有幾個進程

            其實每玩家一個進程已經足夠,代碼簡單,方便維護,性能開銷小。沒必要為每個玩家開啟了網絡,物品、任務等進程,多個進程不但造成進程間通信開銷,還不好維護。

            5. 善用進程字典

            Erlang中是不建議用進程字典的,但進程字典是數據存取最快的方式,對于游戲這種高性能要求的應用,進程字典是不二的選擇。使用進程字典時要切記在對應的進程中操作,最好按功能把put,get操作封裝到模塊接口中,避免誤用。

            6. 代碼規范

            a. 代碼應該簡單,邏輯清晰,把功能細分到函數中。函數一般不多于30行,每個模塊不多于1000行。

            b. 寫尾遞歸函數一定要有清淅的退出條件,不要在函數中改變退出條件。一個退出條件不明確的尾遞歸,是造成消息阻塞,內存耗盡的主要原因之一。


            1. %% 一個明確的尾遞歸函數:
            2. loop([H |T]) ->
            3.     do_something,
            4.     loop(T);
            5. loop([]) ->
            6.     ok.

            7. %% 存在錯誤風險的寫法
            8. %% NewLiist不可預期,存在死循環風險
            9. loop([H | T]) ->
            10.     NewList = do_something(H,T),
            11.     loop(NewList);
            12. loop([]) ->
            13.     ok.

            c.  不要相信客戶端,上行的數據都需要驗證,前端的請求都要做合法性判斷,防止出現外掛、刷錢刷物品、刷金幣的情況。

            d 不要寫過多的case ,if嵌套,最好不要大于3個嵌套,通過 try catch 方法寫扁平化的代碼。

            7. 自動化工具

            自動化工具可以避免出錯,還把開發人員解放出來,提高生產效率。對于重復性,有規律的代碼(如數據存取,通信協議),可以分離出來,讓工具自動生成。有了生成工具后,修改協議,新加字段等操作,簡單方便,不用為增加數據表中一個字段,而改十多個函數接口的修改;也不用擔心前后端協議不一致的問題。

            8. 監控系統

            通過erlang:system_monitor/2,監控系統long_gc,large_heap等情況。

            9. 性能分析工具

            準備好top memory,top message_queue等查看系統屬性的工具,出問題時可以隨時查看。

            本文鏈接:http://www.kongqingquan.com/archives/221 

             

            這幾天在弄個ERLANG的長連接測試程序,主要是要在一個服務器上建20萬條長連接.

            于是找到了以下內容.

            ---------------------------------------------

            前些天給echo_server寫了個非常簡單的連接壓力測試程序,

            代碼:
            1. -module(stress_test).
            2.   
            3. -export([start/0, tests/1]).
            4.   
            5. start() ->
            6.     tests(12345).
            7.   
            8. tests(Port) ->
            9.     io:format("starting~n"),
            10.     spawn(fun() -> test(Port) end),
            11.     spawn(fun() -> test(Port) end),
            12.     spawn(fun() -> test(Port) end),
            13.     spawn(fun() -> test(Port) end).
            14.   
            15. test(Port) ->
            16.      case gen_tcp:connect("192.168.0.217", Port, [binary,{packet, 0}]) of
            17.     {ok, _} ->
            18.             test(Port);
            19.     _ ->
            20.         test(Port)
            21.     end.
             
            一開始我的這個stress_test客戶端運行在windows上面, echo_server服務器端運行在linux上面。 結果接受了1016個連接就停止了. 于是我用ulimit -n 改了服務器端的文件描述符數量為10240. 接著還是如此,折騰了幾天,最終還是沒有搞明白。

            于是就求助于公司的linux編程牛人,結果讓我一倒...  客戶端沒有修改文件描述符個數. windows上得在注冊表里面改.
            牛人開始對這東西的性能感興趣了,剛好我摸了一陣子erlang的文檔,于是我倆就走向了erlang網絡連接的性能調優之旅啦~~過程真是讓人興奮。 我們很快通過了1024這一關~~到了4999個連接,很興奮.

            但為什么4999個連接呢, 檢查一下代碼終于發現echo_server.erl定義了一個宏, 最大連接數為5000. 我又倒~~
            修改編譯之后, 連接數跑到101xx多了, 太哈皮了!
            再測102400個連接時,到32767個連接數erl掛了~說是進程開得太多了. 好在記得這個erl的參數+P,可以定義erlang能生成的進程數. 默認是32768. 改了!

            后面不知怎么著,在81231個連接停止了. 新的性能瓶頸又卡了我們.  好在牛人對linux熟, 用strace(這東西會莫名地退出), stap查出一些苗頭.   我也想到在otp文檔好像提過另一個limit,那就是端口數...在此同時我們發現erlang在linux上是用的傳統poll模型. 但查erlang的源代碼發現是支持epoll的. 在網上搜了半天,終于搜到了個maillist的帖子.
            代碼
            1. $./configure --enable-kernel-poll  

            由于我們的測試服務器是雙核的,我們在配置的時候也打開了smp支持.  歡快的make  & make install之后....
            把 /proc/sys/net/ipv4/ip_local_port_range 的內容改成了1024到65535.  最多也也能改成65535 :)

            代碼
            1. $echo 1024 65535 > ip_local_port_range  

            另外再添加一個erl的環境變量

            代碼
            1. $export ERL_MAX_PORTS=102400  

            于是開始跑了,不過這次跑不一樣了

            echo_server
            1. $erl -noshell  +P 102400 +K true +S 2 -smp -s echo_server start  
            stress_test
            1. $erl -noshell  +P 102400 +K true +S 2 -smp -s stress_test start  

            這里的+K true,表示使用內核poll,+S 2 表示兩個核. 這樣可歡快啦~~~ 10w大關過咯! 而且比剛才沒用epoll的速度快暴多~~
            于是我們又開始了204800個連接發測試了~~~

            用top一看cpu占用率極低,服務器只在5%左右。 內存也不是很大~~

            來源:  http://www.lupaworld.com/tutorial-view-aid-10191.html


            posted on 2016-03-28 08:21 思月行云 閱讀(3514) 評論(0)  編輯 收藏 引用 所屬分類: Erlang
            久久久久四虎国产精品| 精品国产日韩久久亚洲| 久久97精品久久久久久久不卡| 人人狠狠综合久久88成人| 69久久精品无码一区二区| 久久精品国产一区二区电影| 久久强奷乱码老熟女网站| 日韩人妻无码一区二区三区久久 | 久久久精品午夜免费不卡| 亚洲乱亚洲乱淫久久| 久久人妻少妇嫩草AV蜜桃| 久久精品国产91久久综合麻豆自制 | 久久精品国产亚洲沈樵| 伊人色综合久久天天网| 久久天堂AV综合合色蜜桃网| 亚洲国产精品久久久久婷婷老年| 国产精品久久新婚兰兰| 久久99精品久久久久久不卡| 久久精品国产亚洲AV香蕉| 大蕉久久伊人中文字幕| 精品国产VA久久久久久久冰| 一本一道久久a久久精品综合| 亚洲欧美日韩精品久久| 久久夜色精品国产噜噜亚洲AV | 国产一区二区精品久久凹凸| 久久综合久久自在自线精品自| 亚洲午夜无码久久久久小说| 91精品国产综合久久香蕉 | 久久精品中文字幕一区| 久久精品国产99久久丝袜| 91久久精品国产91性色也| 国产精品视频久久久| 久久久久人妻一区精品色| 国内高清久久久久久| 亚洲精品高清一二区久久| 久久精品国产99国产精品| 亚洲综合精品香蕉久久网97| 久久国产精品久久| 色综合久久精品中文字幕首页 | 天天躁日日躁狠狠久久 | 伊人情人综合成人久久网小说 |