¡Uso detallado del Disruptor de cola concurrente de alto rendimiento! Análisis detallado de la aplicación y principios básicos del framework Disruptor

¡Acostúmbrate a escribir juntos! Este es el tercer día de mi participación en el "Nuggets Daily New Plan · April Update Challenge", haz clic para ver los detalles del evento

concepto basico

  • Disruptor es un marco de procesamiento asíncrono de alto rendimiento, un JMS de servicio de mensajes de Java ligero, que puede realizar operaciones simultáneas de colas sin bloqueos .
  • Disruptor implementa una función similar a una cola mediante una matriz en anillo y es una cola limitada. Generalmente se usa en escenarios de productor-consumidor.
  • Disruptor es una implementación del patrón Observer
  • Disruptor resuelve los problemas de rendimiento con las siguientes tres opciones de diseño:
    • Estructura de la matriz de anillos:
      • Para evitar la recolección de basura, use una matriz en lugar de una lista enlazada
      • Los arreglos son más amigables con el mecanismo de caché del procesador
    • Posicionamiento de la posición del elemento:
      • La longitud de la matriz es 2 ^ n ^, lo que puede mejorar la velocidad de posicionamiento a través de la operación de bits.
      • Los subíndices de los elementos en la matriz están en forma de incremento
      • El índice es de tipo long , por lo que no tiene que preocuparse por el desbordamiento del índice .
    • Diseño sin cerradura:
      • Cada subproceso productor o consumidor primero solicitará la posición del elemento operable en la matriz. Si la aplicación tiene éxito, escribirá o leerá datos directamente en la posición aplicada.
  • Comparación de Disruptor y BlockingQueue :
    • BlockingQueue: cola FIFO. Cuando el productor Producer publica un evento en la cola, el consumidor Consumer puede recibir la notificación. Si no hay ningún evento de consumo en la cola, el consumidor será bloqueado hasta que el productor publique un nuevo evento.
    • Disruptor puede hacer más que BlockingQueue:
      • El mismo evento en la cola del Disruptor puede tener múltiples consumidores, y los consumidores pueden procesarse en paralelo, o pueden formar un gráfico de dependencia para depender entre sí y procesarlos en secuencia
      • Disruptor puede preasignar espacio de memoria para almacenar contenido de eventos
      • Disruptor utiliza un diseño extremadamente optimizado y sin bloqueos para lograr objetivos de rendimiento extremadamente altos
  • Por lo general, si hay dos subprocesos de procesamiento independientes, se puede usar un Disruptor de cola concurrente de alto rendimiento para implementar
  • Ventajas del Disruptor:
    • Utilice colas sin bloqueos para lograr operaciones simultáneas con un rendimiento muy alto
    • Todos los visitantes registran la implementación de sus propios números de serie, lo que permite que múltiples productores y múltiples consumidores compartan la misma estructura de datos
    • Los números de secuencia se rastrean en cada objeto, incluidos RingBuffer, WaitStrategy, Producer y Consumer , y se usa el relleno de línea de caché, por lo que no hay intercambio falso ni contención no intencionada.

aplicación disruptiva

definir evento

  • Evento: el tipo de datos intercambiados en el evento. Cola disruptiva

Definir una fábrica de eventos

  • Event Factory: 定义事件Event实例化方法,用来实例化一个事件Event. 需要实现接口com.lmax.disruptor.EventFactory< T >
  • Disruptor通过事件工厂EventFactoryRingBuffer中预创建事件Event的实例
    • 一个事件实例Event类似于一个数据槽
    • 生产者Producer发布Publish之前,先从Ringbuffer中获取一个事件Event实例
    • 然后生产者Producer向事件Event实例中填充数据,然后再发布到RingBuffer
    • 最后由消费者Consumer获取事件Event实例并读取实例中的数据

定义事件处理实现

  • 通过实现接口com.lmax.disruptor.EventHandler< T > 定义事件处理的具体实现

定义事件处理的线程池

  • Disruptor通过java.util.concurrent.ExecutorService提供的线程来触发消费者Consumer的事件处理

指定等待策略

  • Disruptor中使用策略模式定义消费者Consumer处理事件的等待策略,通过com.lmax.disruptor.WaitStrategy接口实现
  • WaitStrategy等待策略有三种常用的实现: 每种策略具有不同的性能和优缺点.根据实际运行环境的CPU的硬件特点选择恰当的策略,并且使用特定的JVM配置启动参数,能够实现不同的性能提升
    • BlockingWaitStrategy:
      • 性能最低
      • 对CPU的消耗最小
      • 能够在不同的部署环境中提供更加一致的性能
    • SleepingWaitStrategy:
      • 性能以及对CPU的消耗和BlockingWaitStrategy差不多
      • 对生产者线程影响最小
      • 适合应用于异步日志等场景
    • YieldingWaitStrategy:
      • 性能最高
      • 适合应用于低延迟的系统
      • 在要求极高的性能并且事件处理线程个数小于CPU逻辑核心个数的场景中,推荐使用这个等待策略
      • 比如CPU开启超线程的特性

启动Disruptor

EventFactory<Event> eventFactory = new EventFactory();
int ringBufferSize = 1024*1024;

Disruptor<Event> disruptor = new Disruptor<Event>(eventFactory, ringBufferSize, executor, ProcedureType.SINGLE, blockingWaitStrategy);
EventHandler<Event> eventHandler = new EventHandle();
disruptor.handleEventsWith(eventHandler);
disruptor.start();
复制代码

发布事件

  • Disruptor的事件Event的发布Publish过程是一个两阶段提交过程:
    • 第一步: 先从RingBuffer获取下一个可以写入事件的序号
    • 第二步: 获取对应的事件Event对象,将数据写入事件对象
    • 第三步: 将事件提交到RingBuffer
  • Disruptor中要求RingBuffer.publish()方法必须要被调用.也就是说,即使发生异常,也要执行publish()方法,这就要求调用者Producer在事件处理的实现上要判断携带的数据的正确性和完整性

关闭Disruptor

  • disruptor.shutdown() : 关闭Disruptor. 方法会阻塞,直至所有的事件都得到处理
  • executor.shutdown() : 关闭Disruptor使用的线程池. 如果线程池需要关闭,必须进行手动关闭 ,Disruptorshutdown时不会自动关闭使用的线程池

Disruptor原理

核心概念

RingBuffer

  • RingBuffer: 环形缓冲区
  • RingBuffer3.0开始,仅仅负责对通过Disruptor进行交换的事件数据进行存储和更新
  • Disruptor的高级应用场景中 ,RingBuffer可以使用用户自定义的实现来替代

Sequence Disruptor

  • 使用顺序递增的序号来编号管理通过Sequence Disruptor进行交换的事件数据,对事件的处理总是按着序号逐个递增进行处理
  • 使用Sequence用于跟踪标识某个特定的事件处理者,包括RingBufferConsumer的处理进度
  • 使用Sequence来标识进度可以防止不同的Sequence之间的CPU的缓存间的伪共享Flase Sharing问题

Sequencer

  • SequencerDisruptor的核心
  • Sequencer接口有两个实现类:
    • SingleProducerSequencer
    • MultiProducerSequencer
    • 这是定义在生产者和消费者之间快速,正确传递数据的并发算法

Sequence Barrier

  • 用于保持对RingBufferpublished SequenceConsumer依赖的其余的ConsumerSequence引用
  • Sequence Barrier中定义了Consumer是否还有可处理的事件的逻辑

WaitStrategy

  • WaitStrategy定义Consumer等待事件的策略

Event

  • Disruptor中生产者Producer和消费者Consumer之间进行交换的数据叫做事件Event
  • Event类型不是Disruptor定义的,而是由Disruptor的使用者来自定义指定

EventProcessor

  • EventProcessor持有指定的消费者ConsumerSequence, 并且提供用于调用事件处理实现的事件循环EventLoop

EventHandler

  • Disruptor中定义的事件处理接口,由使用者实现,用于事件的具体处理,是消费者Consumer的真正实现

Producer

  • Producer: 生产者. 泛指Disruptor发布事件的调用方.没有在Disruptor中定义特定的接口或者类型

inserte la descripción de la imagen aquí

内存预分配

  • RingBuffer使用数组Object[] entries来存储元素:
    • 初始化RingBuffer时,会将所有数组元素entries的指定为特定的事件Event参数,此时Event中的detail属性为null
    • 生产者向RingBuffer写入消息时 ,RingBuffer不是直接将数组元素entries指向Event对象,而是先获取Event对象,更改Event对象中的detail属性
    • 消费者在消费时,也是从RingBuffer中读取Event, 读取Event对象中的detail属性
    • 由此可见,在生产和消费过程中 ,RingBuffer中的数组元素entries没有发生任何变化,没有产生临时对象,数组中的元素一直存活,直到RingBuffer消亡
  • 通过以上方式,可以最小化JVM中的垃圾回收GC的频率,提升性能
private void fill(EventFactory<E> eventFactory) {
	for (int i = 0; i < bufferSize; i++) {
		// 使用工厂方法初始化数组中的entries元素
		entries[BUFFER_PAD + i] = eventFactory.newInstance(); 
	}
}
复制代码

消除伪共享

  • Disruptor中的伪共享: 如果两个相互独立的并发变量位于同一个缓存行时,在并发的情况下,会相互影响彼此的缓存有效性,进而影响并发操作的性能
  • Disruptor中消除伪共享:
    • Sequence.java中使用多个long变量填充,确保一个序号独占一个缓存行
private static class Padding {
	public long nextValue = Sequence.INITIAL_VALUE, cachedValue = Sequence.INITIAL_VALUE, p2, p3, p4, p5, p6, p7; 
}
复制代码

消除锁和CAS操作

  • Disruptor中,通过联合使用SequenceBarrierSequence, 协调和管理消费者和生产者之间的处理关系,避免了锁和CAS操作
  • Disruptor中的各个消费者和生产者持有自己的序号Sequence, 序号Sequence需要满足以下条件:
    • 条件一: 消费者的序号Sequence的数值必须小于生产者的序号Sequence的数值
    • 条件二: 消费者的序号Sequence的数值必须小于依赖关系中前置的消费者的序号Sequence的数值
    • Condición 3: El valor de la Secuencia del número de serie del productor no puede ser mayor que el valor de la Secuencia del número de serie que el consumidor está consumiendo , para evitar que el productor sea demasiado rápido y sobrescriba los mensajes de eventos que aún no han sido consumado.
  • La condición uno y la condición dos se implementan en el método waitFor() en SequenceBarrier :
/**
 * 等待给定的序号值可以供消费者使用
 *  
 * @param sequence 消费者期望获取的下一个序号值
 * @return long 可供消费者使用的序号的值
 */
public long waitFor(final long sequence) throws AlertException, InterruptedException, TimeoutException {
	checkALert();

	/**
	 * 根据指定的waitStrategy策略,等待期望的下一序号值可供使用
	 * 这里不一定能保证返回值availableSequence一定和给定的参数sequence的值相等,两者的大小关系取决于使用的等待策略waitStrategy
	 * - YieldingWaitStrategy : 自旋100次后,会直接返回dependentSequence中最小的序号sequence,这是不能保证返回的值大于等于给定的序号值
	 * - BlockingWaitStrategy : 阻塞等待给定的序号sequence值可用为止,可用不是返回的值就等于给定的序号值,而是返回的值大于等于给定的序号值
	 */
	long availableSequence = waitStrategy.waitFor(sequence, cursorSequence, dependentSequence, this);

	// 如果当前可用的序号值小于给定的序号值,就返回当前可用的序号值,此时调用者EventProcessor会继续等待wait
	if (availableSequence < sequence) {
		return sequence;
	}

	// 批处理
	return sequencer.getHighestPublishedSequence(sequence, availableSequence);
}
复制代码
  • La condición 3 es para el SequenceBarrier establecido por el productor. La decisión lógica ocurre cuando el productor obtiene la siguiente entrada disponible de RingBuffer, y RingBuffer delegará la adquisición de la siguiente entrada disponible al secuenciador para su procesamiento:
@Override
public long next() {
	if (n < 1) {
		throw new IllegalArgumentException("n must be > 0");
	}
	long nextValue = this.nextValue;
	// 下一个序号值等于当前序号值加上期望获取的序号数量
	long nextSequence = nextValue + n;
	
	// 使用下一个序号值减掉RingBuffer中的总量值bufferSize,来判断是否会发生覆盖
	long wrapPoint = nextSequence - bufferSize;
	
	/*
	 * cachedValue就是缓存的消费者中的最小序号值
	 * cachedValue不是当前最新的消费者中最小序号值,而是上一次方法调用时进入到下面if条件判断时,被赋值的消费者中最小序号值
	 * 
	 * 这样做可以在判定是否出现覆盖的时候,不需要每次都调用getMinimumSequence计算消费者中的最小序号值,从而节省开销。只要确保
	 * 当生产者的值大于了缓存cachedGatingSequence一个bufferSize时,重新获取一下getMinimumSequence()即可
	 */
	long cachedGatingSequence = this.cachedValue;

	/*
	 * wrapPoint > cachedGatingSequence : 当生产者已经超过上一次缓存的消费者中的最小序号值cachedGatingSequence一个bufferSize大小时,需要重新获取cachedGatingSequence,防止生产者一直生产,消费者没有来得及消费时,发生覆盖的情况
	 * cachedGatingSequence > nextValue : 生产者和消费者的序号值都是顺序递增的,并且生产者的序号Sequence是先于消费者Sequence,这里是先于而不是大于。对于nextValue的值大于了LONG.MAXVALUE时,此时nextValue + 1就会变为负数,wrapPoint值也会变为负数,此时必然cachedGatingSequence > nextValue。 getMinimumSequence()获取的是消费者中最小序号值,但不代表是走在最后的一个消费者
	 */
	if (wrapPoint > cachedGatingSequence || cachedGatingSequence > nextValue) {
		cursor.setVolatile(nextValue);
		long minSequence;
		while (wrapPoint > (minSequence = Util.getMinimumSequence(gatingSequences, nextValue))) {
			// 生产者阻塞,等待消费者消费,直到不会发生覆盖的情况继续向下执行
			LockSupport.parkNanos(1L);
		}
		this.cacheValue = minSequence;
	}
	this.nextValue = nextSequence;
	return nextSequence;
}
复制代码

efecto por lotes

  • Cuando el productor es demasiado rápido que el consumidor, el consumidor puede alcanzar al productor a través del efecto de lote.
    • Los consumidores obtienen varios elementos de eventos de matriz preparados de RingBuffer al mismo tiempo para el procesamiento de consumo, lo que mejora la eficiencia del consumo.
/**
 * 等待给定的序号值可以供消费者使用
 *  
 * @param sequence 消费者期望获取的下一个序号值
 * @return long 可供消费者使用的序号的值
 */
public long waitFor(final long sequence) throws AlertException, InterruptedException, TimeoutException {
	checkALert();
	long availableSequence = waitStrategy.waitFor(sequence, cursorSequence, dependentSequence, this);
	if (availableSequence < sequence) {
		return sequence;
	}

	/*
	 * 获取消费者可以消费的最大序列号,通过批处理来提升效率:
	 * - 当availableSequence > sequence时,需要遍历序号sequence到序号availableSequence,获取到最前面一个准备就绪,可以进行消费的事件Event对应的序号sequence
	 * - 最小值为sequence - 1 
	 */
	return sequencer.getHighestPublishedSequence(sequence, availableSequence);
}
复制代码

Supongo que te gusta

Origin juejin.im/post/7085677820323561502
Recomendado
Clasificación