Cómo utilizar Netty para recibir 350.000 objetos por segundo en una sola máquina

Hay muchas demostraciones en Internet que usan netty y protostuff para transmitir objetos rpc. La mayoría de ellos están tallados en un molde. También copié uno al principio. La prueba local no tuvo obstáculos y no se produjo ninguna anomalía.

Después de implementar el entorno de prelanzamiento, después de la prueba de esfuerzo, hubo muchos problemas y varios informes de errores aparecieron sin cesar. Por supuesto, utilicé una gran cantidad de datos durante la prueba de esfuerzo y envié solicitudes muy intensivas. Una sola máquina envía 20.000 objetos en los primeros 100 ms por segundo, y se detiene durante 900 ms y envía en un bucle sin fin. Se utilizan un total de 40 máquinas como clientes y se envían 2 máquinas netty al mismo tiempo. Servidor El servidor envía objetos, por lo que, en promedio, cada servidor recibe alrededor de 400 000 objetos por segundo Debido a la lógica empresarial subyacente, la lógica solo puede procesar 350 000 mediciones reales por segundo.

El código en Internet se ha modificado muchas veces y se ha probado repetidamente. Al final, no ha logrado errores ni excepciones. Una sola máquina puede recibir más de 350.000 objetos por segundo. Por lo tanto, escriba un artículo para registrar. El código en el texto será coherente con la lógica en línea.

Serialización y deserialización de protostuff

Esto no es nada especial, solo busque una herramienta en línea.

Introducir pom

<protostuff.version>1.7.2</protostuff.version>
<dependency>
    <groupId>io.protostuff</groupId>
    <artifactId>protostuff-core</artifactId>
    <version>${protostuff.version}</version>
</dependency>

<dependency>
    <groupId>io.protostuff</groupId>
    <artifactId>protostuff-runtime</artifactId>
    <version>${protostuff.version}</version>
</dependency>
public class ProtostuffUtils {
    /**
     * 避免每次序列化都重新申请Buffer空间
     * 这句话在实际生产上没有意义,耗时减少的极小,但高并发下,如果还用这个buffer,会报异常说buffer还没清空,就又被使用了
     */
//    private static LinkedBuffer buffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE);
    /**
     * 缓存Schema
     */
    private static Map<Class<?>, Schema<?>> schemaCache = new ConcurrentHashMap<>();
 
    /**
     * 序列化方法,把指定对象序列化成字节数组
     *
     * @param obj
     * @param <T>
     * @return
     */
    @SuppressWarnings("unchecked")
    public static <T> byte[] serialize(T obj) {
        Class<T> clazz = (Class<T>) obj.getClass();
        Schema<T> schema = getSchema(clazz);
        LinkedBuffer buffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE);
        byte[] data;
        try {
            data = ProtobufIOUtil.toByteArray(obj, schema, buffer);
//            data = ProtostuffIOUtil.toByteArray(obj, schema, buffer);
        } finally {
            buffer.clear();
        }
 
        return data;
    }
 
    /**
     * 反序列化方法,将字节数组反序列化成指定Class类型
     *
     * @param data
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> T deserialize(byte[] data, Class<T> clazz) {
        Schema<T> schema = getSchema(clazz);
        T obj = schema.newMessage();
        ProtobufIOUtil.mergeFrom(data, obj, schema);
//        ProtostuffIOUtil.mergeFrom(data, obj, schema);
        return obj;
    }
 
    @SuppressWarnings("unchecked")
    private static <T> Schema<T> getSchema(Class<T> clazz) {
        Schema<T> schema = (Schema<T>) schemaCache.get(clazz);
        if (Objects.isNull(schema)) {
            //这个schema通过RuntimeSchema进行懒创建并缓存
            //所以可以一直调用RuntimeSchema.getSchema(),这个方法是线程安全的
            schema = RuntimeSchema.getSchema(clazz);
            if (Objects.nonNull(schema)) {
                schemaCache.put(clazz, schema);
            }
        }
 
        return schema;
    }
}

Aquí hay un hoyo, es decir, la mayoría de los códigos en línea en la parte superior usan búferes estáticos. No hay ningún problema en el caso de un solo subproceso. En el caso de subprocesos múltiples, es muy fácil hacer que un búfer sea utilizado por otro subproceso nuevamente sin que se borre después de un uso, y se lanzará una excepción. La supuesta evitación de solicitar espacio de reserva cada vez, el impacto medido en el rendimiento es extremadamente pequeño.

Además, los dos ProtostuffIOUtil cambiaron a ProtobufIOUtil, porque también había una anomalía y no había ninguna anomalía después de la modificación.

Método de serialización personalizado

Decodificador decodificador:

import com.jd.platform.hotkey.common.model.HotKeyMsg;
import com.jd.platform.hotkey.common.tool.ProtostuffUtils;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
 
import java.util.List;
 
/**
 * @author wuweifeng
 * @version 1.0
 * @date 2020-07-29
 */
public class MsgDecoder extends ByteToMessageDecoder {
    @Override
    protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf in, List<Object> list) {
        try {
 
            byte[] body = new byte[in.readableBytes()];  //传输正常
            in.readBytes(body);
 
            list.add(ProtostuffUtils.deserialize(body, HotKeyMsg.class));
 
//            if (in.readableBytes() < 4) {
//                return;
//            }
//            in.markReaderIndex();
//            int dataLength = in.readInt();
//            if (dataLength < 0) {
//                channelHandlerContext.close();
//            }
//            if (in.readableBytes() < dataLength) {
//                in.resetReaderIndex();
//                return;
//            }
//
//            byte[] data = new byte[dataLength];
//            in.readBytes(data);
//
//            Object obj = ProtostuffUtils.deserialize(data, HotKeyMsg.class);
//            list.add(obj);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Codificador

import com.jd.platform.hotkey.common.model.HotKeyMsg;
import com.jd.platform.hotkey.common.tool.Constant;
import com.jd.platform.hotkey.common.tool.ProtostuffUtils;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
 
/**
 * @author wuweifeng
 * @version 1.0
 * @date 2020-07-30
 */
public class MsgEncoder extends MessageToByteEncoder {
 
    @Override
    public void encode(ChannelHandlerContext ctx, Object in, ByteBuf out) {
        if (in instanceof HotKeyMsg) {
            byte[] bytes = ProtostuffUtils.serialize(in);
            byte[] delimiter = Constant.DELIMITER.getBytes();
 
            byte[] total = new byte[bytes.length + delimiter.length];
            System.arraycopy(bytes, 0, total, 0, bytes.length);
            System.arraycopy(delimiter, 0, total, bytes.length, delimiter.length);
 
            out.writeBytes(total);
        }
    }
}

Primero mire el decodificador Decoder, este se usa para decodificar el mensaje después de que netty recibe el mensaje, y convierte el byte en un objeto (HotKeyMsg personalizado). Hay un montón de publicaciones que he comentado, comentado y las publicaciones que deberían encontrarse en Internet están escritas así. Este método en sí no es un problema en escenas normales y la decodificación sigue siendo normal, pero es muy propenso a atascarse cuando hay cientos de miles. Así que agregué un decodificador delimitador DelimiterBasedFrameDecoder antes de este decodificador.

Cuando se recibe un mensaje, primero se pasa el decodificador delimitador y luego, cuando se trata de MsgDecoder, es un flujo de bytes de objeto que se ha separado y se puede deserializar directamente con la clase de herramienta proto. Constant.DELIMITER es una cadena especial que personalizo y se usa como separador.

Mire codificador, codificador, primero serialice el objeto a transmitir en byte [] con ProtostuffUtils, y luego cuelgue mi separador personalizado en la cola. De esta forma, cuando el objeto se envíe al exterior, se utilizará el codificador y se agregará el separador.

El código del lado del servidor correspondiente es más o menos así:

Después de eso, el objeto transferido se puede utilizar directamente en el Handler.

Mira el lado del cliente de nuevo

Es lo mismo que el lado del servidor, pero también estos códecs, no hay diferencia. Debido a la comunicación entre netty y el servidor, utilizo la misma definición de objeto.

Lo mismo ocurre con los manipuladores.

Una sola máquina y grupo

Después de escribir todo lo anterior, podemos probarlo. Podemos iniciar un cliente, un servidor y luego hacer un bucle sin fin para enviar este objeto al servidor, y luego puede enviar directamente el objeto al servidor después de recibir el objeto. Escríbalo y envíelo al cliente como está. Verá que funciona sin problemas, enviar N millones por segundo no es un problema, el códec es normal y los lados del cliente y del servidor son relativamente normales. La premisa actual es que la clase de herramienta de ProtoBuf es la misma que la mía. No comparta el búfer. Los artículos que se encuentran en Internet básicamente han terminado en este punto, y está bien enviar algunos mensajes al azar. Sin embargo, de hecho, después de que este tipo de código esté en línea, será eliminado.

De hecho, las pruebas locales también son muy sencillas: inicie algunos clientes, todos junto con un servidor, y luego envíeles objetos en un bucle sin fin, y luego vea si hay excepciones en ambos extremos. En este caso, la diferencia con el primero es la misma en el lado del cliente, pero en el lado del servidor. Anteriormente, solo se enviaba un mensaje a un cliente al mismo tiempo. Ahora se envía a dos clientes al mismo tiempo. Este paso sucederá si no se tiene cuidado Hay un problema, se recomienda que lo pruebe usted mismo.

Después de eso, agreguemos algo más. Comencé dos servidores con dos puertos respectivamente. En realidad, hay dos servidores de servidores diferentes en línea. El cliente enviará objetos a los dos servidores en un bucle sin fin al mismo tiempo, como se muestra en el código a continuación.

Para enviar mensajes, usualmente usamos channel.writeAndFlush (). Puedes eliminar la sincronización y ejecutar el código para ver. Encontrará bultos arrojados de forma anormal. Obviamente, enviamos mensajes a dos canales diferentes, pero la hora era a la misma hora y, como resultado, se produjeron paquetes pegajosos graves. Muchos mensajes recibidos por el servidor son irregulares y se informará de una gran cantidad de errores. Si el intervalo entre los dos canales es de 100 ms, la situación está resuelta. Por supuesto, al final podemos usar la sincronización para enviar de forma sincrónica, de modo que no se produzca ninguna excepción.

El código anterior ha sido probado, con 40 clientes y 2 servidores, cada servidor recibe una media de 400.000 objetos por segundo, que pueden funcionar de forma continua y estable.

Supongo que te gusta

Origin blog.csdn.net/qq_46388795/article/details/108664404
Recomendado
Clasificación