netty 高併發實戰

分佈式IM架構


id="embed_dom" style="border:1px solid #000;display:block;" src="https://www.processon.com/embed/56ab6119e4b0ed3b6490f436" frameborder="0">

IM服務器開發,從功能抽象的角度看可能很是簡單,能夠認爲是管理大量的客戶端鏈接和在不一樣的客戶端之間傳遞消息,但具體到實現細節就比較複雜了。打個不恰當的比喻,OS的功能抽象也很是簡單,無非是進程間的調度和硬件資源的管理,但要是本身去實現一個,通常人也就只能呵呵了。java

因爲IM服務器裏面的內容比較多,這個能夠是一個系列的內容,因此這裏只介紹服務器的架構以及爲何選擇這樣的架構linux

咱們的設計原則是保持簡單,能夠作必定的擴容,無單點故障。不少時候開發人員常犯的錯誤是過分設計和提早優化,"When in doubt, use brute force", 有時簡單的方案纔是最好的方案。算法

架構設計須要考慮到服務器的業務邏輯和預計的用戶量,好比一樣的IM服務器設計,最高併發在線人數百萬和千萬的確定有很大的不一樣, 若是按千萬級別來設計一個只有百萬級別的系統,增長的複雜度和工做量是很是可觀的.安全

目前咱們的IM服務器架構設計的單機併發鏈接10萬用戶,總併發用戶量能夠達百萬級,對於這樣規模的服務器後臺,能夠採用很簡單的架構來處理。有一個DispatchServer來分配客戶端到一個消息服務器,消息服務器之間的數據交互經過一箇中心的RouteServer來轉發,和數據持久化層之間的交互由DBProxy服務器來處理.服務器

因爲IM消息服務器和客戶端之間交互很是頻繁,但處理單個數據包的邏輯比較簡單,沒有IO或CPU密集型的操做,因此消息服務器採用單線程來處理,這樣比較簡單,沒有死鎖,競爭條件,出現問題很是好定位。多線程

分離出一個DBProxy服務器的的理由是,因爲要操做DB和Redis,一個請求的回覆會比較耗時,可是交互很是簡單,一個請求對應一個回覆,與消息服務器之間的長鏈接通常很穩定,不太須要處理太多的斷連,因此這個能夠用多線程的來處理大的IO等待。並且因爲DBProxy服務器之間不須要交互,這樣能夠隨着業務量的增長,添加不少實例來分散每一個DBProxy的壓力。架構

DispatchServer和RouteServer的邏輯都很是簡單,並且能夠啓動多個實例,這樣就不存在單點故障。兩個DispatchServer或兩個RouteServer之間的狀態同步是經過消息服務器來實現的,好比用戶上下線時,須要把這個狀態通知全部的DispatchServer和RouteServer。併發

消息服務器支持TCP長鏈接和HTTP長輪詢兩種接入方式,目前的消息服務器承擔了接入和業務邏輯處理兩種角色,通常業界的作法是把這兩種功能分解開,有一個接入服務器來處理客戶端的接入,而後客戶端的請求被分配到不一樣的業務邏輯處理服務器來處理,好比登錄服務器,狀態通知服務器,好友管理服務器等。但這樣運維起來比較麻煩,對於百萬量級的IM來講,這樣有點過分設計的感受,因此咱們把這些功能融合在一個消息服務器裏面實現,但這樣的缺點是,更新一個業務功能時,須要把重啓消息服務器,這樣客戶端會有一個從新鏈接服務器的過程。之後能夠調研是否是能夠把業務邏輯寫成動態加載庫,這樣修改業務邏輯時,只要Reload動態庫就能夠了運維

搭建這些只是開了一個頭,之後能夠作不少有挑戰性的技術,好比用分佈式的消息存儲方案代替如今的MySQL,用分佈式的小文件系統存儲系統代替如今依賴蘑菇街主站的圖片存儲方案,用一些Unsupervised Learning的算法對用戶消息進行分析,來獲取一些用戶的profile信息。dom


linux 內核參數修改


「Cannot assign requested address.」是因爲linux分配的客戶端鏈接端口用盡,沒法創建socket鏈接所致,雖然socket正常關閉,可是端口不是當即釋放,而是處於TIME_WAIT狀態,默認等待60s後才釋放。

vi /etc/sysctl.conf

#fs.file-max:表示文件句柄的最大數量。文件句柄表示在Linux系統中能夠打開的文件數量。
fs.file-max = 1048576

#增長可用端口:

net.ipv4.ip_local_port_range = 1024 65535
net.ipv4.tcp_mem = 786432 2097152 3145728
net.ipv4.tcp_rmem = 4096 4096 16777216
net.ipv4.tcp_wmem = 4096 4096 16777216

#調低端口釋放後的等待時間,默認爲60s,修改成15~30s
net.ipv4.tcp_fin_timeout=20

#修改tcp/ip協議配置, 經過配置/proc/sys/net/ipv4/tcp_tw_resue, 默認爲0,修改成1,釋放TIME_WAIT端口給新鏈接使用
net.ipv4.tcp_tw_reuse = 1

#修改tcp/ip協議配置,快速回收socket資源,默認爲0,修改成1

#net.ipv4.tcp_timestamps開啓,tw_recycle纔會生效

net.ipv4.tcp_timestamps=1

net.ipv4.tcp_tw_recycle = 1


經過vi /etc/security/limits.conf 添加以下配置參數:修改以後保存,註銷當前用戶,從新登陸,經過

ulimit -a 查看修改的狀態是否生效。

*  soft  nofile  1000000
*  hard  nofile  1000000

經過 ulimit -a 查看最大句柄數

ulimit -n 10000000

改完後,執行命令「sysctl -p」使參數生效,不須要reboot。


netty設置

ServerBootstrap b = new ServerBootstrap(); // (2)
            b.group(bossGroup, workerGroup)
             .channel(NioServerSocketChannel.class) // (3)
             .childHandler(new SimpleChatServerInitializer())  //(4)
             .option(ChannelOption.SO_BACKLOG, 128)          // (5)
             .option(ChannelOption.TCP_NODELAY, true)
             .option(ChannelOption.SO_KEEPALIVE, true)
             .option(ChannelOption.SO_REUSEADDR, true)
             .option(ChannelOption.SO_RCVBUF, 10 * 1024)
             .option(ChannelOption.SO_SNDBUF, 10 * 1024)
             .option(EpollChannelOption.SO_REUSEPORT, true)
             .childOption(ChannelOption.SO_KEEPALIVE, true); // (6)
            System.out.println("SimpleChatServer 啓動了");


JVM監控

1. 經過jstatd啓動RMI服務
        配置java安全訪問,將以下的代碼存爲文件 jstatd.all.policy,放到JAVA_HOME/bin中,其內容以下,
        grant codebase "file:${java.home}/../lib/tools.jar" {

               permission java.security.AllPermission;

          };
            
          執行命令jstatd -J-Djava.security.policy=jstatd.all.policy -J-Djava.rmi.server.hostname=192.168.1.8 &

           (192.168.1.8  爲你服務器的ip地址,&表示用守護線程的方式運行)
          jstatd命令詳解 :http://hzl7652.iteye.com/blog/1183182 
         
          打開jvisualvm, 右鍵Remort,選擇 "Add Remort Host...",在彈出框中輸入你的遠端IP,好比192.168.1.8. 鏈接成功.


1.遠程主機

(1)修改JMX服務的配置文件:
  在JDK的根目錄/jre/lib/management中,將jmxremote.password.template另存爲jmxremote.password。
用文件編輯軟件按編輯jmxremote.password去掉
  # monitorRole QED
  # controlRole R&D
  前面的#註釋,保存。
  若是當前系統屬於AIX、Linux或者Solaris系統還須要更改jmxremote.access和jmxremote.password的權限
爲只讀寫,命令以下
  chmod 600 jmxremote.access jmxremote.password


(2)修改JVM的啓動配置信息:

 

Windows系統
set JAVA_OPTS=-Dcom.sun.management.jmxremote.port=<port> -Dcom.sun.management.jmxremote.ssl=false
-Dcom.sun.management.jmxremote.authenticate=false -Djava.rmi.server.hostname=<hostname>
-Dcom.sun.management.jmxremote.ssl=false

 

AIX、Linux或者Solaris
export JAVA_OPTS="-Dcom.sun.management.jmxremote.port=<port> -Dcom.sun.management.jmxremote.ssl=false
-Dcom.sun.management.jmxremote.authenticate=false -Djava.rmi.server.hostname=<hostname>  
-Dcom.sun.management.jmxremote.ssl=false"

例如:
set JAVA_OPTS=-Dcom.sun.management.jmxremote.port=1099 -Dcom.sun.management.jmxremote.ssl=false
-Dcom.sun.management.jmxremote.authenticate=false -Djava.rmi.server.hostname=192.168.1.24
-Dcom.sun.management.jmxremote.ssl=false

 

配置的說明以下:-Dcom.sun.management.jmxremote.port                           遠程主機端口號的 -Dcom.sun.management.jmxremote.ssl=false                   是否使用SSL鏈接 -Dcom.sun.management.jmxremote.authenticate=false   是否開啓遠程服務權限 -Djava.rmi.server.hostname                                              遠程主機名,使用IP地址