【笔记】redis-单机-57万QPS-实践

服务端

客户端

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);
    }

}

压测

执行参数

压测结果

猜你喜欢

转载自blog.csdn.net/shumeizwb/article/details/141277485