1. 程式人生 > >基於Netty的百萬級推送服務設計要點

基於Netty的百萬級推送服務設計要點

1. 背景

1.1. 話題來源

最近很多從事移動網際網路和物聯網開發的同學給我發郵件或者微博私信我,諮詢推送服務相關的問題。問題五花八門,在幫助大家答疑解惑的過程中,我也對問題進行了總結,大概可以歸納為如下幾類:

1,Netty是否可以做推送伺服器?

2,如果使用Netty開發推送服務,一個伺服器最多可以支撐多少個客戶端?

3,使用Netty開發推送服務遇到的各種技術問題。

由於諮詢者眾多,關注點也比較集中,我希望通過本文的案例分析和對推送服務設計要點的總結,幫助大家在實際工作中少走彎路。

1.2. 推送服務

移動網際網路時代,推送(Push)服務成為App應用不可或缺的重要組成部分,推送服務可以提升使用者的活躍度和留存率。我們的手機每天接收到各種各樣的廣告和提示訊息等大多數都是通過推送服務實現的。

隨著物聯網的發展,大多數的智慧家居都支援移動推送服務,未來所有接入物聯網的智慧裝置都將是推送服務的客戶端,這就意味著推送服務未來會面臨海量的裝置和終端接入。

1.3. 推送服務的特點

移動推送服務的主要特點如下:

1,使用的網路主要是運營商的無線行動網路,網路質量不穩定,例如在地鐵上訊號就很差,容易發生網路閃斷;

2,海量的客戶端接入,而且通常使用長連線,無論是客戶端還是服務端,資源消耗都非常大;

3,由於谷歌的推送框架無法在國內使用,Android的長連線是由每個應用各自維護的,這就意味著每檯安卓裝置上會存在多個長連線。即便沒有訊息需要推送,長連線本身的心跳訊息量也是非常巨大的,這就會導致流量和耗電量的增加;

4,不穩定:訊息丟失、重複推送、延遲送達、過期推送時有發生;

5,垃圾訊息滿天飛,缺乏統一的服務治理能力。

為了解決上述弊端,一些企業也給出了自己的解決方案,例如京東雲推出的推送服務,可以實現多應用單服務單連線模式,使用AlarmManager定時心跳節省電量和流量。

2. 智慧家居領域一個真實案例

2.1. 問題描述

智慧家居MQTT訊息服務中介軟體,保持10萬用戶線上長連線,2萬用戶併發做訊息請求。程式執行一段時間之後,發現記憶體洩露,懷疑是Netty的Bug。其它相關資訊如下:

1,MQTT訊息服務中介軟體伺服器記憶體16G,8個核心CPU;

2,Netty中boss執行緒池大小為1,worker執行緒池大小為6,其餘執行緒分配給業務使用。該分配方式後來調整為worker執行緒池大小為11,問題依舊;

3,Netty版本為4.0.8.Final。

2.2. 問題定位

首先需要dump記憶體堆疊,對疑似記憶體洩露的物件和引用關係進行分析,如下所示:

我們發現Netty的ScheduledFutureTask增加了9076%,達到110W個左右的例項,通過對業務程式碼的分析發現使用者使用IdleStateHandler用於在鏈路空閒時進行業務邏輯處理,但是空閒時間設定的比較大,為15分鐘。

Netty的IdleStateHandler會根據使用者的使用場景,啟動三類定時任務,分別是:ReaderIdleTimeoutTask、WriterIdleTimeoutTask和AllIdleTimeoutTask,它們都會被加入到NioEventLoop的Task佇列中被排程和執行。

由於超時時間過長,10W個長連結鏈路會建立10W個ScheduledFutureTask物件,每個物件還儲存有業務的成員變數,非常消耗記憶體。使用者的持久代設定的比較大,一些定時任務被老化到持久代中,沒有被JVM垃圾回收掉,記憶體一直在增長,使用者誤認為存在記憶體洩露。

事實上,我們進一步分析發現,使用者的超時時間設定的非常不合理,15分鐘的超時達不到設計目標,重新設計之後將超時時間設定為45秒,記憶體可以正常回收,問題解決。

2.3. 問題總結

如果是100個長連線,即便是長週期的定時任務,也不存在記憶體洩露問題,在新生代通過minor GC就可以實現記憶體回收。正是因為十萬級的長連線,導致小問題被放大,引出了後續的各種問題。

事實上,如果使用者確實有長週期執行的定時任務,該如何處理?對於海量長連線的推送服務,程式碼處理稍有不慎,就滿盤皆輸,下面我們針對Netty的架構特點,介紹下如何使用Netty實現百萬級客戶端的推送服務。

3. Netty海量推送服務設計要點

作為高效能的NIO框架,利用Netty開發高效的推送服務技術上是可行的,但是由於推送服務自身的複雜性,想要開發出穩定、高效能的推送服務並非易事,需要在設計階段針對推送服務的特點進行合理設計。

3.1. 最大控制代碼數修改

百萬長連線接入,首先需要優化的就是Linux核心引數,其中Linux最大檔案控制代碼數是最重要的調優引數之一,預設單程序開啟的最大控制代碼數是1024,通過ulimit -a可以檢視相關引數,示例如下:

[[email protected] ~]# ulimit -a

core file size (blocks, -c) 0

data seg size (kbytes, -d) unlimited

scheduling priority (-e) 0

file size (blocks, -f) unlimited

pending signals (-i) 256324

max locked memory (kbytes, -l) 64

max memory size (kbytes, -m) unlimited

open files (-n) 1024

......後續輸出省略

當單個推送服務接收到的連結超過上限後,就會報“too many open files”,所有新的客戶端接入將失敗。

通過vi /etc/security/limits.conf 新增如下配置引數:修改之後儲存,登出當前使用者,重新登入,通過ulimit -a 檢視修改的狀態是否生效。

* soft nofile 1000000

* hard nofile 1000000

需要指出的是,儘管我們可以將單個程序開啟的最大控制代碼數修改的非常大,但是當控制代碼數達到一定數量級之後,處理效率將出現明顯下降,因此,需要根據伺服器的硬體配置和處理能力進行合理設定。如果單個伺服器效能不行也可以通過叢集的方式實現。

3.2. 當心CLOSE_WAIT

從事移動推送服務開發的同學可能都有體會,移動無線網路可靠性非常差,經常存在客戶端重置連線,網路閃斷等。

在百萬長連線的推送系統中,服務端需要能夠正確處理這些網路異常,設計要點如下:

1,客戶端的重連間隔需要合理設定,防止連線過於頻繁導致的連線失敗(例如埠還沒有被釋放);

2,客戶端重複登陸拒絕機制;

3,服務端正確處理I/O異常和解碼異常等,防止控制代碼洩露。

最後特別需要注意的一點就是close_wait 過多問題,由於網路不穩定經常會導致客戶端斷連,如果服務端沒有能夠及時關閉socket,就會導致處於close_wait狀態的鏈路過多。close_wait狀態的鏈路並不釋放控制代碼和記憶體等資源,如果積壓過多可能會導致系統控制代碼耗盡,發生“Too many open files”異常,新的客戶端無法接入,涉及建立或者開啟控制代碼的操作都將失敗。

下面對close_wait狀態進行下簡單介紹,被動關閉TCP連線狀態遷移圖如下所示:

圖3-1 被動關閉TCP連線狀態遷移圖

close_wait是被動關閉連線是形成的,根據TCP狀態機,伺服器端收到客戶端傳送的FIN,TCP協議棧會自動傳送ACK,連結進入close_wait狀態。但如果伺服器端不執行socket的close()操作,狀態就不能由close_wait遷移到last_ack,則系統中會存在很多close_wait狀態的連線。通常來說,一個close_wait會維持至少2個小時的時間(系統預設超時時間的是7200秒,也就是2小時)。如果服務端程式因某個原因導致系統造成一堆close_wait消耗資源,那麼通常是等不到釋放那一刻,系統就已崩潰。

導致close_wait過多的可能原因如下:

1,程式處理Bug,導致接收到對方的fin之後沒有及時關閉socket,這可能是Netty的Bug,也可能是業務層Bug,需要具體問題具體分析;

2,關閉socket不及時:例如I/O執行緒被意外阻塞,或者I/O執行緒執行的使用者自定義Task比例過高,導致I/O操作處理不及時,鏈路不能被及時釋放。

下面我們結合Netty的原理,對潛在的故障點進行分析。

設計要點1:不要在Netty的I/O執行緒上處理業務(心跳傳送和檢測除外)。

Why? 對於Java程序,執行緒不能無限增長,這就意味著Netty的Reactor執行緒數必須收斂。Netty的預設值是CPU核數 * 2,通常情況下,I/O密集型應用建議執行緒數儘量設定大些,但這主要是針對傳統同步I/O而言,對於非阻塞I/O,執行緒數並不建議設定太大,儘管沒有最優值,但是I/O執行緒數經驗值是[CPU核數 + 1,CPU核數*2 ]之間。

假如單個伺服器支撐100萬個長連線,伺服器核心數為32,則單個I/O執行緒處理的連結數L = 100/(32 * 2) = 15625。 假如每5S有一次訊息互動(新訊息推送、心跳訊息和其它管理訊息),則平均CAPS = 15625 / 5 = 3125條/秒。這個數值相比於Netty的處理效能而言壓力並不大,但是在實際業務處理中,經常會有一些額外的複雜邏輯處理,例如效能統計、記錄介面日誌等,這些業務操作效能開銷也比較大,如果在I/O執行緒上直接做業務邏輯處理,可能會阻塞I/O執行緒,影響對其它鏈路的讀寫操作,這就會導致被動關閉的鏈路不能及時關閉,造成close_wait堆積。

設計要點2:在I/O執行緒上執行自定義Task要當心。Netty的I/O處理執行緒NioEventLoop支援兩種自定義Task的執行:

1,普通的Runnable: 通過呼叫NioEventLoop的execute(Runnable task)方法執行;

2,定時任務ScheduledFutureTask:通過呼叫NioEventLoop的schedule(Runnable command, long delay, TimeUnit unit)系列介面執行。

為什麼NioEventLoop要支援使用者自定義Runnable和ScheduledFutureTask的執行,並不是本文要討論的重點,後續會有專題文章進行介紹。本文重點對它們的影響進行分析。

在NioEventLoop中執行Runnable和ScheduledFutureTask,意味著允許使用者在NioEventLoop中執行非I/O操作類的業務邏輯,這些業務邏輯通常用訊息報文的處理和協議管理相關。它們的執行會搶佔NioEventLoop I/O讀寫的CPU時間,如果使用者自定義Task過多,或者單個Task執行週期過長,會導致I/O讀寫操作被阻塞,這樣也間接導致close_wait堆積。

所以,如果使用者在程式碼中使用到了Runnable和ScheduledFutureTask,請合理設定ioRatio的比例,通過NioEventLoop的setIoRatio(int ioRatio)方法可以設定該值,預設值為50,即I/O操作和使用者自定義任務的執行時間比為1:1。

我的建議是當服務端處理海量客戶端長連線的時候,不要在NioEventLoop中執行自定義Task,或者非心跳類的定時任務。

設計要點3:IdleStateHandler使用要當心。很多使用者會使用IdleStateHandler做心跳傳送和檢測,這種用法值得提倡。相比於自己啟定時任務傳送心跳,這種方式更高效。但是在實際開發中需要注意的是,在心跳的業務邏輯處理中,無論是正常還是異常場景,處理時延要可控,防止時延不可控導致的NioEventLoop被意外阻塞。例如,心跳超時或者發生I/O異常時,業務呼叫Email傳送介面告警,由於Email服務端處理超時,導致郵件傳送客戶端被阻塞,級聯引起IdleStateHandler的AllIdleTimeoutTask任務被阻塞,最終NioEventLoop多路複用器上其它的鏈路讀寫被阻塞。

對於ReadTimeoutHandler和WriteTimeoutHandler,約束同樣存在。

3.3. 合理的心跳週期

百萬級的推送服務,意味著會存在百萬個長連線,每個長連線都需要靠和App之間的心跳來維持鏈路。合理設定心跳週期是非常重要的工作,推送服務的心跳週期設定需要考慮移動無線網路的特點。

當一臺智慧手機連上行動網路時,其實並沒有真正連線上Internet,運營商分配給手機的IP其實是運營商的內網IP,手機終端要連線上Internet還必須通過運營商的閘道器進行IP地址的轉換,這個閘道器簡稱為NAT(NetWork Address Translation),簡單來說就是手機終端連線Internet 其實就是移動內網IP,埠,外網IP之間相互對映。

GGSN(GateWay GPRS Support Note)模組就實現了NAT功能,由於大部分的移動無線網路運營商為了減少閘道器NAT對映表的負荷,如果一個鏈路有一段時間沒有通訊時就會刪除其對應表,造成鏈路中斷,正是這種刻意縮短空閒連線的釋放超時,原本是想節省通道資源的作用,沒想到讓網際網路的應用不得以遠高於正常頻率傳送心跳來維護推送的長連線。以中移動的2.5G網路為例,大約5分鐘左右的基帶空閒,連線就會被釋放。

由於移動無線網路的特點,推送服務的心跳週期並不能設定的太長,否則長連線會被釋放,造成頻繁的客戶端重連,但是也不能設定太短,否則在當前缺乏統一心跳框架的機制下很容易導致信令風暴(例如微信心跳信令風暴問題)。具體的心跳週期並沒有統一的標準,180S也許是個不錯的選擇,微信為300S。

在Netty中,可以通過在ChannelPipeline中增加IdleStateHandler的方式實現心跳檢測,在建構函式中指定鏈路空閒時間,然後實現空閒回撥介面,實現心跳的傳送和檢測,程式碼如下:

public void initChannel({@link Channel} channel) {

channel.pipeline().addLast('idleStateHandler', new {@link IdleStateHandler}(0, 0, 180));

channel.pipeline().addLast('myHandler', new MyHandler());

}

攔截鏈路空閒事件並處理心跳:

public class MyHandler extends {@link ChannelHandlerAdapter} {

{@code @Override}

public void userEventTriggered({@link ChannelHandlerContext} ctx, {@link Object} evt) throws {@link Exception} {

if (evt instanceof {@link IdleStateEvent}} {

//心跳處理

}

}

}

3.4. 合理設定接收和傳送緩衝區容量

對於長連結,每個鏈路都需要維護自己的訊息接收和傳送緩衝區,JDK原生的NIO類庫使用的是java.nio.ByteBuffer,它實際是一個長度固定的Byte陣列,我們都知道陣列無法動態擴容,ByteBuffer也有這個限制,相關程式碼如下:

public abstract class ByteBuffer

extends Buffer

implements Comparable{

final byte[] hb; // Non-null only for heap buffers

final int offset;

boolean isReadOnly;

容量無法動態擴充套件會給使用者帶來一些麻煩,例如由於無法預測每條訊息報文的長度,可能需要預分配一個比較大的ByteBuffer,這通常也沒有問題。但是在海量推送服務系統中,這會給服務端帶來沉重的記憶體負擔。假設單條推送訊息最大上限為10K,訊息平均大小為5K,為了滿足10K訊息的處理,ByteBuffer的容量被設定為10K,這樣每條鏈路實際上多消耗了5K記憶體,如果長連結鏈路數為100萬,每個鏈路都獨立持有ByteBuffer接收緩衝區,則額外損耗的總記憶體 Total(M) = 1000000 * 5K = 4882M。記憶體消耗過大,不僅僅增加了硬體成本,而且大記憶體容易導致長時間的Full GC,對系統穩定性會造成比較大的衝擊。

實際上,最靈活的處理方式就是能夠動態調整記憶體,即接收緩衝區可以根據以往接收的訊息進行計算,動態調整記憶體,利用CPU資源來換記憶體資源,具體的策略如下:

1,ByteBuffer支援容量的擴充套件和收縮,可以按需靈活調整,以節約記憶體;

2,接收訊息的時候,可以按照指定的演算法對之前接收的訊息大小進行分析,並預測未來的訊息大小,按照預測值靈活調整緩衝區容量,以做到最小的資源損耗滿足程式正常功能。

幸運的是,Netty提供的ByteBuf支援容量動態調整,對於接收緩衝區的記憶體分配器,Netty提供了兩種:

1,FixedRecvByteBufAllocator:固定長度的接收緩衝區分配器,由它分配的ByteBuf長度都是固定大小的,並不會根據實際資料報的大小動態收縮。但是,如果容量不足,支援動態擴充套件。動態擴充套件是Netty ByteBuf的一項基本功能,與ByteBuf分配器的實現沒有關係;

2,AdaptiveRecvByteBufAllocator:容量動態調整的接收緩衝區分配器,它會根據之前Channel接收到的資料報大小進行計算,如果連續填充滿接收緩衝區的可寫空間,則動態擴充套件容量。如果連續2次接收到的資料報都小於指定值,則收縮當前的容量,以節約記憶體。

相對於FixedRecvByteBufAllocator,使用AdaptiveRecvByteBufAllocator更為合理,可以在建立客戶端或者服務端的時候指定RecvByteBufAllocator,程式碼如下:

Bootstrap b = new Bootstrap();

b.group(group)

.channel(NioSocketChannel.class)

.option(ChannelOption.TCP_NODELAY, true)

.option(ChannelOption.RCVBUF_ALLOCATOR, AdaptiveRecvByteBufAllocator.DEFAULT)

如果預設沒有設定,則使用AdaptiveRecvByteBufAllocator。

另外值得注意的是,無論是接收緩衝區還是傳送緩衝區,緩衝區的大小建議設定為訊息的平均大小,不要設定成最大訊息的上限,這會導致額外的記憶體浪費。通過如下方式可以設定接收緩衝區的初始大小:

/**

相關推薦

Netty 系列之 Netty 百萬服務設計要點

Netty 系列之 Netty 百萬級推送服務設計要點 李林鋒 2015 年 1 月 4 日 話題:語言 & 開發架構 1. 背景 1.1. 話題來源 最近很多從事移動網際網路和物聯網開發的同學給我發郵件或者微博私信我,諮詢推送服務相關的問題。問題五花八門,

Netty系列之Netty百萬服務設計要點-轉載

1. 背景 1.1. 話題來源 最近很多從事移動網際網路和物聯網開發的同學給我發郵件或者微博私信我,諮詢推送服務相關的問題。問題五花八門,在幫助大家答疑解惑的過程中,我也對問題進行了總結,大概可以歸納為如下幾類: Netty是否可以做推送伺服器? 如果使用Netty開

基於Netty百萬服務設計要點

1. 背景 1.1. 話題來源 最近很多從事移動網際網路和物聯網開發的同學給我發郵件或者微博私信我,諮詢推送服務相關的問題。問題五花八門,在幫助大家答疑解惑的過程中,我也對問題進行了總結,大概可以歸納為如下幾類: 1,Netty是否可以做推送伺服器? 2,如果使用Netty開發推送服務,一個伺服器

如何用 Netty 設計一個百萬服務

1. 背景 1.1. 話題來源 最近很多從事移動網際網路和物聯網開發的同學給我發郵件或者微博私信我,諮詢推送服務相關的問題。問題五花八門,在幫助大家答疑解惑的過程中,我也對問題進行了總結,大概可以歸納為如下幾類: 1,Netty是否可以做推送伺服器? 2,如果使用Net

Android 基於Netty的訊息方案之物件的傳遞(四)

在上一篇文章中《Android 基於Netty的訊息推送方案之字串的接收和傳送(三)》我們介紹了Netty的字串傳遞,我們知道了Netty的訊息傳遞都是基於流,通過ChannelBuffer傳遞的,那麼自然,Object也需要轉換成ChannelBuffer來傳遞。好在Netty本身已經給我們寫好了

Android 基於Netty的訊息方案之字串的接收和傳送(三)

在上一篇文章中《Android 基於Netty的訊息推送方案之概念和工作原理(二)》 ,我們介紹過一些關於Netty的概念和工作原理的內容,今天我們先來介紹一個叫做ChannelBuffer的東東。 ChannelBuffer  Netty中的訊息傳遞,都必須以位元

Android 基於Netty的訊息方案之概念和工作原理(二)

上一篇文章中我講述了關於訊息推送的方案以及一個基於Netty實現的一個簡單的Hello World,為了更好的理解Hello World中的程式碼,今天我來講解一下關於Netty中一些概念和工作原理的內容,如果你覺得本篇文章有些枯燥,請先去閱讀《Android 基於Netty的訊息推送方案之Hell

Android 基於Netty的訊息方案之Hello World(一)

訊息推送方案(輪詢、長連線) 輪詢 輪詢:比較簡單的,最容易理解和實現的就是客戶端去伺服器上拉資訊,資訊的及時性要求越高則拉資訊的頻率越高。客戶端拉資訊的觸發可以是一些事件,也可以是一個定時器,不斷地去查詢伺服器。所以這個方案的弊端也是顯而易見的,在輪詢的頻率較高時,伺服器

Android基於Netty的訊息方案(一)

訊息推送方案(輪詢、長連線) 輪詢 輪詢:比較簡單的,最容易理解和實現的就是客戶端去伺服器上拉資訊,資訊的及時性要求越高則拉資訊的頻率越高。客戶端拉資訊的觸發可以是一些事件,也可以是一個定時器,不斷地去查詢伺服器。所以這個方案的弊端也是顯而易見的,在輪詢的頻率較高時,

基於netty-socketio的web服務

hub href 相關 發生 推送消息 數據 特定 使用 github   在WEB項目中,服務器向WEB頁面推送消息是一種常見的業務需求。PC端的推送技術可以使用socket建立一個長連接來實現。傳統的web服務都是客戶端發出請求,服務端給出響應。但是現在直觀的要求是允許

開發訊息服務基於Netty protobuf--fpush(含github原始碼)

開發訊息推送服務,基於Netty protobuf--fpush-含github原始碼 技術棧 程式碼簡介 系統架構 1.系統部署架構圖如下: 2. 移動客戶端鑑定許可權原理 3. server端推送

iOS 服務的簡易原理與配置

補充 com 思考 lib email p12 sig 導出 文件的 最近的項目需要用到iOS的push功能,在配置push功能的過程中遇到了一些不清楚的地方,經過查閱資料和思考,已有初步認識,下面進行一下梳理,我們的服務器端用的是Facebook的Parse。 完整的

SignalR Self Host+MVC等多端消息服務(2)

寫到 後端 local -1 顯示 rgs 代碼 發送 登錄 一、概述 上次的文章中我們簡單的實現了SignalR自托管的服務端,今天我們來實現控制臺程序調用SignalR服務端來實現推送信息,由於之前我們是打算做審批消息推送,所以我們的demo方向是做指定人發送信息,至

用 Go 編寫一個簡單的 WebSocket 服務

年輕 sync 狀態 升級 ati .com 客戶端 我們 png 用 Go 編寫一個簡單的 WebSocket 推送服務 本文中代碼可以在 github.com/alfred-zhong/wserver 獲取。 背景 最近拿到需求要在網頁上展示報警信息。以往報警信息都

阿裏雲服務

移動終端 dev ndt 3D 隨機數 hmac 獲得 通知欄 接口調用 移動推送 提供從雲端到移動終端的優質推送服務,支持Android和iOS平臺的通知/消息的推送功能. 推送內容及模式 通知:會自動在手機端彈出通知欄,用戶可以打開或者清除通知欄。iOS的通知走

MQTT協議及服務(二)

broker 消息發布 常見 google ios roi 服務端 蘋果 ios端 MQTT簡介 MQTT全稱叫做Message Queuing Telemetry Transport,意為消息隊列遙測傳輸,是IBM開發的一個即時通訊協議。由於其維護一個長連接以輕量級低消耗

Android實戰——第三方服務之Bmob後端雲的服務的集成和使用(三)

第一篇 文章 href 第三方服務 log 集成 android實戰 https 分享 第三方服務之Bmob後端雲的推送服務的集成和使用(三) 事先說明:這裏的一切操作都是在集成了BmobSDK之後實現的,如果對Bmob還不了解的話,請關註我第一篇Bmob文章 步驟

服務

由於這段時間做專案,需要到訊息推送,所以專門做一個推送訊息,包括裝置邀請,鎖的報警資訊等,做這個訊息推送,主要是前面tcp層響應要快,所以把一些資料庫查詢,或者訊息推送,寫到mq裡面,這樣子提高前面併發的連結,把這些訊息推送拿出來,作為一個專案。 專案的構造如上圖,沒有web.xml檔

Java開發微信小程式(三)用小程式給使用者服務訊息

第三篇 用小程式給使用者推送服務訊息 1.小程式登入獲取,小程式的openId和unionId。 2.獲取並解密小程式的加密資訊包括使用者和手機資訊。 3.用小程式給使用者推送服務訊息。 4.給繫結小程式而且又關注微信公眾號的使用者推送公眾號訊息。 小程式訊息推送機制有

使用websocket進行訊息服務

Websocket主要做訊息推送,簡單,輕巧,比comet好用 入門瞭解:https://www.cnblogs.com/xdp-gacl/p/5193279.html   /** * A Web Socket session represents a conversation bet