PlayFramework+Redis+websocketでプッシュ配信(データ圧縮編)

Websocketに最近はまっています。

前回プッシュ配信をRedisのPub/Subを用いて実現した訳ですが、データ量が増えてくるとどうしても配信するデータを圧縮したくなります。

特にAWSなんかだと、従量課金となっていますので、データ転送量は少なければ少ないほど○

Websocket自体はまだデータの圧縮はサポートされていないとのことですので、自前でデータの圧縮伸長を実装してみました。

また、それだけではつまらないので、Reidsにチャンネルを作成し、ReidsのチャンネルごとにPub/Subする仕組みを入れています

図解するとちょっとわかりにくいですがこんな感じ

			user1							userActor			 redisActor	 sub		Redis
		|code1,code2| ---	|	 user1	 | ---	|	code1	| --- |
											 |					 |			|				 |
			user2						|					 | ---	|	code2	| --- |
		|code2,code3| ---	|	 user2	 | ---	|	code3	| --- |

User別に作成したActorと、ユーザが選択したチャンネル(code1,code2,code3)別に作成したActorを別々に作成し、チャンネルがReidsから更新された際には、購読しているUserのActorへ更新をかけるという感じです

こうすることにより、チャンネル数がユーザ数より圧倒的に少ない場合には、Reidsに対するコネクションも削減でき、効率が良くなります。

  • route
GET		 /board													 controllers.Application.board(code:Option[String])	 # 画面作成用
GET		 /board/data											controllers.Application.data(code) #WSでデータ取得用
GET		 /asset/javascripts/board.js		 #Javascript controllers.Application.boardJs(code:String)
  • Compress.scala

データ圧縮用に作成します。String型をZIP圧縮し、BASE64でエンコードします

package models

import java.util.zip._
import java.io._
import org.apache.commons.codec.binary._

object Compress {
	def encode(str:String):String={
		val out = new ByteArrayOutputStream()
		val defl = new DeflaterOutputStream(out, new Deflater(Deflater.BEST_COMPRESSION, true))
		defl.write(str.getBytes())
		defl.close()
		val compressed = Base64.encodeBase64(out.toByteArray())
		new String(compressed)		
	}
}
  • UserActor.scala
...
	
	def notifyAll(code:String,data:String){
		// 実際にWebSocketでブラウザに送るデータ
		val msg=JsObject(
				Seq(
						"code"->JsString(code),
						"data"->JsString(Compress.encode(data)) // JSONの一部データ部分のみ圧縮
..
  • Javascriptで解凍

JavaScriptでZIP解凍、Base64でコードするために↓からinfrate.js,base64.js,utf.jsなどをダウンロードしておきます。

http://www.onicos.com/staff/iz/amuse/javascript/expert/

@(code:String)(implicit r: RequestHeader)

$(function() {

		var WS = window['MozWebSocket'] ? MozWebSocket : WebSocket
		var socket = new WS("@routes.Application.data(code).webSocketURL()")

		
		var receiveEvent = function(event) {
				var data = JSON.parse(event.data)

				var bs=base64decode(data.data) // Base64デコード
				var dec=zip_inflate(bs)				// ZIP解凍
				var p=JSON.parse(dec)
				$("#data").text(p.data);
			 
		}

		socket.onmessage = receiveEvent

})

サンプルの一式は以下においておきました

https://github.com/anagotan/play_websocket_compressed