Software development of explosion! -夢の破片(カケラ)たちの日々-

ソフトウェア開発を中心としたコンピューター関連のネタを扱ったブログです

Software development is passion and explosion!

JMeterを使ってRedisの性能テストをする方法(JMeter TCP SamplerからCRLFで終わるリクエストを送る

Redisが使えるか(うちのシステム的にって意味で)検証するため、JMeter使って性能を検証しましょうってことで環境構築しました。
まぁ、仕事でやってることなので、検証結果とかは公開できませんが、
とりあえず、JMeter TCP Samplerを使って、Redisと通信するための方法を自分の備忘録も兼ねて残しておきます。

JMeterを使ってRedisと通信するには、JMeter TCP Smplerを使います。
JMeter 2.7 r1342401には標準でTCP Samplerが付属しているので、
Apache JMeter - Downloads落としてきて、適当なところに展開するだけで使えます。

このTCP Sampler日本語での解説サイトが少ないので、ちょっとだけ細かく記述します。
スレッドグループやコントローラー配下に、追加→サンプラー→TCPサンプラーを選択して追加します。
すると、名前以外の欄が空の状態のTCPサンプラーの画面が表示されます。
f:id:poad1010:20120728221330p:image

TCPClient classnameには、org.apache.jmeter.protocol.tcp.sampler.AbstractTCPClientを実装したクラスを指定します。
JMeter 2.7 r1342401には標準でorg.apache.jmeter.protocol.tcp.sampler.TCPClientImpl
org.apache.jmeter.protocol.tcp.sampler.BinaryTCPClientImplなどが付属しており、TCPClient classnameに何も入力しないと、jmeter.propertiesのtcp.handlerで指定されているクラスが使われるようです。
(コメントアウトされているが、TCPClientImplが使われるようです)
他の欄は。。。まぁ、そのままの内容なので説明は割愛します。

Redisの場合、標準設定ではポート6379を使うので、ポート番号には「6379」を入力します。
今回は解説ということもあって、Windows版のRedisをDownloads · dmajkic/redis · GitHubから落として来て使いますので、サーバ名またはIPには、「localhost」を指定しています。
f:id:poad1010:20120728215543p:image

先ずはTCPClientImplを使ってみます。
Redisのプロトコルはプロトコル仕様 — redis 2.0.3 documentationにあるように、UTF-8のテキストをコマンドとして送受信しますので、
送信するテキストには「PING」コマンドを送信するように「PING<改行>」を入力します。
これでRedisサーバーを起動すればテスト実施準備が終わります。

テストを実行してみると分るのですが、このままだとテストが終わりません。(タイムアウトを指定している場合はReadExceptionでタイムアウトします)
ログを見てみると、デフォルトでキャラクターセットは「Windows-31j」となっています。
が、コマンド自体はASCII文字だけなので、SETコマンドやLPUSHコマンドのキー名やデータ部分に非ASCII文字を送らない限りは問題はないでしょうから、
とりあえずこのままでいきます。
そもそも、PuttyからRaw接続で繋いでmonitorコマンドを入力して監視しても、Redis側には何も送られていないように見えます。

設定ファイルを弄っても変わらないので、Redisサーバーを終了して、Scalaで試しにポート6379に送られてくる内容を標準出力にダンプしてみました。

DummyServer.scala

/**
 *
 */
package tv.dyndns.poad
import java.nio.channels.ServerSocketChannel
import java.net.InetSocketAddress
import scala.actors.Actor
import java.nio.channels.SocketChannel
import java.nio.ByteBuffer
import java.nio.charset.Charset

/**
 * @author Ken
 *
 */
class DummyServer(host : String, port : Int) extends Actor {
	private val channel = ServerSocketChannel.open();
	def act : Unit = {
		try {
			channel.socket().bind(new InetSocketAddress(host, port))
			println("start server " + host + ":" + port)
			while (true) {
				new Acceptor(channel.accept()).start()
			}
		} finally {
			channel.close()
		}
	}
	def close() : Unit = {
		if (channel.isOpen()) {
			channel.close()
		}
	}
}

class Acceptor(channel : SocketChannel) extends Actor {
	def act : Unit = {
		val buf = ByteBuffer.allocate(4096 * 2)
		loop {
			if (channel.read(buf) < 0) {
				exit
			}
			buf.flip()
			val decoded = Charset.forName("UTF-8").decode(buf).toString()
			println(decoded)
			decoded.getBytes().foreach { e => print("0x%02X ".format(e)) }
			println()

			buf.clear()
			buf.put("+OK".getBytes())
			channel.close()
			exit
		}
	}
}

DummyServerMain.scala

package tv.dyndns.poad
import scala.io.Source

object DummyServerMain extends App {
	val server = new DummyServer("localhost", 6379)
	try {
		server.start()
		while(true) {
			val line = readLine()
			if (line == "exit") {
				exit
			}
		}
	} finally {
		server.close()
	}
}

出力結果はこんな感じ。

start server localhost:6379
PING

0x50 0x49 0x4E 0x47 0x0A

JMeterが送っている内容を見てみると、改行文字として<LF>しか送ってません。
Redisは>CRLF<なので、正しいコマンドとして認識しておらず応答してくれなかったようです。
そこで、BinaryTCPClientImplを使ってデータを送ってみます。
BinaryTCPClientImplでは、送信データにはHEX文字列を入力する必要があるそうです。
f:id:poad1010:20120728231911p:image

これでテストを流してみると、Redis側はちゃんと応答するようになります。
ですが、テストが通りません。応答は返ってきているのですが、タイムアウトとなります。
原因は、JMeter側での応答データ終了の検出設定にあります。

jmeter.propertiesにtcp.BinaryTCPClient.eomByte=10を追記します。
※ データ終了を表す値(1byte)を10進数表記で記述します。

すると、見事にテストが通りました。

まとめ

  • TCPClientImplは終了文字としてLFしか送らない
  • バイナリーデータを送るにはBinaryTCPClientImplを使う
  • BinaryTCPClientで送るデータはHEX文字列を設定する
  • BinaryTCPClientの応答データの終了検出はjmeter.propertiesにtcp.BinaryTCPClient.eomByteとして10進数表記で応答データの最終1byteの値を設定する