服务端
客户端
pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>NettyTest</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>22</maven.compiler.source>
<maven.compiler.target>22</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- https://mvnrepository.com/artifact/io.netty/netty-all -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.112.Final</version>
</dependency>
</dependencies>
<build>
<plugins>
<!-- 主动声明插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.1.2</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<!-- 绑定生命周期 -->
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<!-- 设置依赖的存放路径 -->
<configuration>
<outputDirectory>${project.build.directory}/lib</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
org.example.redis.RedisClient
package org.example.redis;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.redis.RedisDecoder;
import io.netty.handler.codec.redis.RedisEncoder;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
public class RedisClient {
public static void main(String[] args) throws InterruptedException, ExecutionException {
int requests = args.length > 0 ? Integer.parseInt(args[0]) : 1;
int connections = args.length > 1 ? Integer.parseInt(args[1]) : 1;
int threads = args.length > 2 ? Integer.parseInt(args[2]) : 1;
EventLoopGroup group = new NioEventLoopGroup(threads);
try {
Bootstrap b = new Bootstrap();
b.group(group)
.channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, false)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
//解码
p.addLast(new RedisDecoder());
// p.addLast(new RedisBulkStringAggregator());
// p.addLast(new RedisArrayAggregator());
//编码
p.addLast(new RedisEncoder());
p.addLast(new RedisClientHandler());
}
});
// Start the connection attempt.
Channel ch1 = b.connect("192.168.253.201", 6379).sync().channel();
// Channel ch2 = b.connect("192.168.253.201", 6379).sync().channel();
Channel[] channels = {ch1};
// Read commands from the stdin.
System.out.println("Enter Redis commands (quit to end)");
for (int i = 0; i < connections; i++) {
channels[i].writeAndFlush("set count 0").sync();
}
Thread.sleep(1000);
CountDownLatch latch = new CountDownLatch(requests);
long start = System.currentTimeMillis();
for ( int i = 0; i < requests; i++) {
// Sends the received line to the server.
RedisIncrCommand cmd = RedisIncrCommandPool.get();
channels[0].writeAndFlush(cmd);
cmd.getResponse().whenComplete((result, ex) ->
{
latch.countDown();
RedisIncrCommandPool.release(cmd);
if (ex != null) {
System.err.print("response failed: ");
ex.printStackTrace(System.err);
}
else {
if( latch.getCount() % 1000000 == 0) {
System.out.println("response: " + result);
}
}
});
}
latch.await();
long end = System.currentTimeMillis();
long delta = end -start;
System.out.println("请求 " + requests + " 个");
System.out.println("耗时 " + delta + " ms");
System.out.println("QPS " + requests * 1000L / delta + " 次/秒");
// Wait until all messages are flushed before closing the channel.
} finally {
group.shutdownGracefully();
System.exit(0);
}
}
}
org.example.redis.RedisClientHandler
/*
* Copyright 2016 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.example.redis;
import io.netty.buffer.ByteBufUtil;
import io.netty.channel.Channel;
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
import io.netty.handler.codec.CodecException;
import io.netty.handler.codec.redis.*;
import io.netty.util.Attribute;
import io.netty.util.AttributeKey;
import io.netty.util.CharsetUtil;
import io.netty.util.ReferenceCountUtil;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.ConcurrentHashMap;
/**
* An example Redis client handler. This handler read input from STDIN and write output to STDOUT.
*/
public class RedisClientHandler extends ChannelDuplexHandler {
private static AttributeKey<Queue> key = AttributeKey.valueOf("queue");
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) {
String command = null;
if( msg instanceof RedisIncrCommand )
{
command = ((RedisIncrCommand) msg).getFullCommand();
Attribute<Queue> attribute = ctx.channel().attr(key);
if( attribute.get() == null )
{
attribute.set(new LinkedList());
}
attribute.get().offer(msg);
}
else
{
Attribute<Queue> attribute = ctx.channel().attr(key);
if( attribute.get() == null )
{
attribute.set(new LinkedList());
}
attribute.get().offer(msg);
command = (String)msg;
}
RedisMessage request = new InlineCommandRedisMessage(command);
ctx.write(request, promise);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
RedisMessage redisMessage = (RedisMessage) msg;
Attribute<Queue> attribute = ctx.channel().attr(key);
Object polled = attribute.get().poll();
if( polled instanceof RedisIncrCommand )
{
((RedisIncrCommand) polled).getResponse().complete(redisMessage.toString());
}
else
{
printAggregatedRedisResponse(redisMessage);
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
System.err.print("exceptionCaught: ");
cause.printStackTrace(System.err);
ctx.close();
}
private static void printAggregatedRedisResponse(RedisMessage msg) {
if (msg instanceof SimpleStringRedisMessage) {
System.out.println(((SimpleStringRedisMessage) msg).content());
} else if (msg instanceof ErrorRedisMessage) {
System.out.println(((ErrorRedisMessage) msg).content());
} else if (msg instanceof IntegerRedisMessage) {
System.out.println(((IntegerRedisMessage) msg).value());
} else if (msg instanceof FullBulkStringRedisMessage) {
System.out.println(getString((FullBulkStringRedisMessage) msg));
} else if (msg instanceof ArrayRedisMessage) {
for (RedisMessage child : ((ArrayRedisMessage) msg).children()) {
printAggregatedRedisResponse(child);
}
} else {
throw new CodecException("unknown message type: " + msg);
}
}
private static String getString(FullBulkStringRedisMessage msg) {
if (msg.isNull()) {
return "(null)";
}
return msg.content().toString(CharsetUtil.UTF_8);
}
}
org.example.redis.RedisIncrCommand
package org.example.redis;
import java.util.concurrent.CompletableFuture;
public class RedisIncrCommand {
private String command = "INCR";
private String key;
private CompletableFuture<String> response = new CompletableFuture();
public RedisIncrCommand(String key) {
this.key = key;
}
public void reset() {
response = new CompletableFuture<>();
}
public String getFullCommand() {
return command + " " + key;
}
public void setResponse(String result) {
response.complete(result);
}
public CompletableFuture<String> getResponse(
) {
return response;
}
}
org.example.redis.RedisIncrCommandPool
package org.example.redis;
import java.util.concurrent.ConcurrentLinkedDeque;
public class RedisIncrCommandPool {
private final static ConcurrentLinkedDeque<RedisIncrCommand> pool = new ConcurrentLinkedDeque<>();
public static RedisIncrCommand get()
{
RedisIncrCommand command = pool.poll();
if (command == null)
{
command = new RedisIncrCommand("count");
}
return command;
}
public static void release(RedisIncrCommand command)
{
command.reset();
pool.add(command);
}
}
压测
执行参数
压测结果