Dist Sys Notes Imagen Broadcast1
Dist Sys Notes Imagen Broadcast1
Dist Sys Notes Imagen Broadcast1
Sistemas distribuidos
Universidad de Cambridge
Tripos de Ciencias de la Computación, Parte IB
Período de San Miguel 2020/21
1. Introducción
Este curso de 8 lecciones sobre sistemas distribuidos constituye la segunda mitad de Sistemas concurrentes y distribuidos. Mientras que la
primera mitad se centró en la concurrencia entre múltiples procesos o subprocesos que se ejecutan en la misma computadora, esta segunda
mitad va más allá al examinar los sistemas que consisten en múltiples computadoras que se comunican.
La simultaneidad en una sola computadora también se conoce como simultaneidad de memoria compartida, ya que varios subprocesos que
se ejecutan en el mismo proceso tienen acceso al mismo espacio de direcciones. Por lo tanto, los datos se pueden pasar fácilmente de un hilo a
otro: una variable o puntero que es válido para un hilo también es válido para otro.
Esta situación cambia cuando pasamos a sistemas distribuidos. Todavía tenemos concurrencia en un sistema distribuido, ya que diferentes
computadoras pueden ejecutar programas en paralelo. Sin embargo, normalmente no tenemos memoria compartida, ya que cada computadora
en un sistema distribuido ejecuta su propio sistema operativo con su propio espacio de direcciones, utilizando la memoria integrada en esa
computadora. Las diferentes computadoras solo pueden comunicarse enviándose mensajes a través de una red.
(Existen formas limitadas de memoria compartida distribuida en algunas supercomputadoras y sistemas de investigación, y existen
tecnologías como el acceso directo a memoria remota (RDMA) que permiten que las computadoras accedan a la memoria de otras a través de
una red. Además, las bases de datos pueden, en cierto sentido, considerarse como memoria compartida, pero con un modelo de datos diferente
en comparación con la memoria direccionable por bytes. Sin embargo, en términos generales, la mayoría de los sistemas distribuidos prácticos
se basan en el paso de mensajes).
Cada una de las computadoras en un sistema distribuido se llama nodo. Aquí, "computadora" se interpreta de manera bastante amplia: los
nodos pueden ser computadoras de escritorio, servidores en centros de datos, dispositivos móviles, automóviles conectados a Internet, sistemas
de control industrial, sensores o muchos otros tipos de dispositivos. En este curso no los distinguimos: un nodo puede ser cualquier tipo de
dispositivo informático comunicante.
Diapositiva 1
• Maarten van Steen y Andrew S. Tanenbaum. Sistemas distribuidos. ISBN 978-1543057386. Descarga gratuita desde https://
www.distributed-systems.net/index.php/books/ds3/ (tercera edición, 2017).
Este libro ofrece una amplia visión general de una gran variedad de temas de sistemas distribuidos, con muchos ejemplos de
sistemas prácticos.
Este libro es más avanzado, profundiza en varios algoritmos distribuidos importantes y demuestra su corrección. Recomendado si
desea explorar la teoría con mayor profundidad que la que cubre este curso.
• Martín Kleppmann. Diseño de aplicaciones intensivas en datos, O'Reilly, 2017. ISBN 978-1449373320.
Este libro va más en la dirección de las bases de datos, pero también cubre una serie de temas de sistemas distribuidos. Está
diseñado para ingenieros de software en la industria que trabajan con bases de datos distribuidas.
• Jean Bacon y Tim Harris. Sistemas Operativos: Diseño de Software Concurrente y Distribuido.
Addison-Wesley, 2003. ISBN 978-0321117892.
Este libro proporciona un enlace a la mitad del curso sobre sistemas concurrentes ya los temas de sistemas operativos.
En su caso, estas notas de clase también contienen referencias a trabajos de investigación y otras lecturas de referencia útiles (estos
se dan entre corchetes, y los detalles aparecen al final de este documento). Sin embargo, solo se puede examinar el material cubierto en
las notas de clase y los videos.
Lectura recomendada
Diapositiva 2
El programa de estudios, las diapositivas y las notas de clase de este curso se han revisado sustancialmente para 2020/21.
Esto significa que es posible que se hayan introducido nuevos errores. Si nota algo incorrecto o si algo no está claro, ¡hágamelo saber por
correo electrónico ([email protected])!
En cuanto a otros cursos, las preguntas de exámenes anteriores están disponibles en https://www.cl.cam.ac.uk/teaching/exams/
pastpapers/t-ConcurrentandDistributedSystems.html . Debido a cambios en el plan de estudios, las siguientes preguntas de exámenes
anteriores ya no son aplicables: 2018 P5 Q8; 2015 P5 Q8; 2014 P5 Q9 (a); 2013 P5 Q9; 2011 P5 Q8 (b).
Estas notas también contienen ejercicios, que son material sugerido para la discusión en las supervisiones. Solu
Las notas informativas para supervisores están disponibles en la página web del curso.
Este curso está relacionado con varios otros cursos en los tripos, como se muestra en la diapositiva 3.
2
Machine Translated by Google
Hay una serie de razones para crear sistemas distribuidos. Algunas aplicaciones están intrínsecamente distribuidas: si desea
enviar un mensaje desde su teléfono al teléfono de un amigo, esa operación inevitablemente requiere que esos teléfonos se
comuniquen a través de algún tipo de red.
Algunos sistemas distribuidos hacen cosas que en principio podría hacer una sola computadora, pero lo hacen de manera más
confiable. Una sola computadora puede fallar y es posible que deba reiniciarse de vez en cuando, pero si está utilizando varios
nodos, entonces un nodo puede continuar sirviendo a los usuarios mientras otro nodo se reinicia.
Por lo tanto, un sistema distribuido tiene el potencial de ser más confiable que una sola computadora, al menos si está bien diseñado
(¡contradiciendo un poco la cita de Lamport en la diapositiva 1)!
Otra razón para la distribución es un mejor rendimiento: si un servicio tiene usuarios en todo el mundo y todos tienen que
acceder a un solo nodo, los usuarios del Reino Unido o los usuarios de Nueva Zelanda lo encontrarán lento (o ambas cosas). Al
colocar nodos en múltiples ubicaciones alrededor del mundo, podemos sortear la lentitud de la velocidad de la luz al enrutar a cada
usuario a un nodo cercano.
Finalmente, algunas tareas informáticas o de procesamiento de datos a gran escala son simplemente demasiado grandes para
realizarlas en una sola computadora, o serían intolerablemente lentas. Por ejemplo, el Gran Colisionador de Hadrones del CERN
está respaldado por una infraestructura informática mundial con 1 millón de núcleos de CPU para el análisis de datos y 1 exabyte
(1018 bytes) de almacenamiento. Consulte https://wlcg-public.web.cern.ch/.
Diapositiva 4
Sin embargo, también hay desventajas en los sistemas distribuidos, porque las cosas pueden salir mal y el sistema necesita
lidiar con tales fallas. La red puede fallar, dejando a los nodos incapaces de comunicarse.
3
Machine Translated by Google
Diapositiva 5
Otra cosa que puede salir mal es que un nodo se bloquee, funcione mucho más lento de lo normal o se comporte mal
de alguna otra manera (quizás debido a un error de software o una falla de hardware). Si queremos que un nodo se haga
cargo cuando otro nodo falla, debemos detectar que se ha producido un bloqueo; como veremos, incluso eso no es
sencillo. Las fallas de la red y las fallas de los nodos pueden ocurrir en cualquier momento, sin previo aviso.
En una sola computadora, si un componente falla (p. ej., uno de los módulos de RAM presenta una falla), normalmente
no esperamos que la computadora continúe funcionando de todos modos: probablemente se bloquee. El software no
necesita estar escrito de una manera que trate explícitamente con RAM defectuosa. Sin embargo, en un sistema distribuido
a menudo queremos tolerar que algunas partes del sistema se rompan y que el resto siga funcionando. Por ejemplo, si un
nodo se bloqueó (una falla parcial), los nodos restantes aún pueden continuar brindando el servicio.
Si un componente de un sistema deja de funcionar, lo llamamos falla, y muchos sistemas distribuidos se esfuerzan por
proporcionar tolerancia a fallas: es decir, el sistema como un todo continúa funcionando a pesar de la falla. Tratar con
fallas es lo que hace que la computación distribuida sea fundamentalmente diferente, y a menudo más difícil, en
comparación con la programación de una sola computadora. Algunos ingenieros de sistemas distribuidos creen que si
puede resolver un problema en una sola computadora, ¡es básicamente fácil! Aunque, para ser justos con nuestros colegas
en otras áreas de la informática, esto probablemente no sea cierto.
Esto es duro.
Diapositiva 6
4
Machine Translated by Google
mensaje m
nodo i nodo j
Diapositiva 7
En este curso, simplemente asumimos que hay alguna forma de que un nodo envíe un mensaje a otro nodo.
No nos importa especialmente cómo se representa o codifica físicamente ese mensaje (los protocolos de red, conocidos
informalmente como los bytes en el cable), porque el principio básico de enviar y recibir mensajes sigue siendo el mismo,
incluso cuando las tecnologías de red particulares van y vienen. El "cable" en realidad puede ser ondas de radio, láseres,
una memoria USB en el bolsillo de alguien o incluso discos duros en una camioneta.
https://docs.aws.amazon.com/snowball/latest/ug/using-device.html
Diapositiva 8
De hecho, si desea enviar un mensaje muy grande (piense en decenas de terabytes), sería lento enviar esos datos a
través de Internet y, de hecho, es más rápido escribir esos datos en un montón de discos duros, cargarlos en una furgoneta
y llevarlos a su destino. Pero desde el punto de vista de los sistemas distribuidos, el método de entrega del mensaje no es
importante: solo vemos un canal de comunicación abstracto con cierta latencia (retraso desde que se envía un mensaje
hasta que se recibe) y ancho de banda (el volumen de datos que se pueden transferir por unidad de tiempo).
El curso de redes informáticas en el período de Cuaresma se centra en los protocolos de red que permiten que los
mensajes lleguen a su destino. El estudio de los sistemas distribuidos se basa en esa instalación y, en cambio, se centra
en cómo varios nodos deben coordinarse para lograr alguna tarea compartida. El diseño de algoritmos distribuidos consiste
en decidir qué mensajes enviar y cómo procesar los mensajes cuando se reciben.
5
Machine Translated by Google
Diapositiva 9
Diapositiva 10
OBTENER
/docencia/2021/ConcDisSys
<!DOCTYPEhtml><html>...
Diapositiva 11
En la web hay dos tipos principales de nodos: los servidores alojan sitios web y los clientes (navegadores web) los
muestran. Cuando carga una página web, su navegador web envía un mensaje de solicitud HTTP al servidor apropiado.
Al recibir esa solicitud, el servidor web envía un mensaje de respuesta que contiene la
6
Machine Translated by Google
contenido de la página al cliente que lo solicitó. Estos mensajes normalmente son invisibles, pero podemos capturar y
visualizar el tráfico de la red con una herramienta como Charles (https://www.charlesproxy.com/), se muestra en la
diapositiva 12. El video de la conferencia incluye una demostración de este software en acción.
En una URL, la parte entre // y la siguiente / es el nombre de host del servidor al que el cliente va a enviar la solicitud
(por ejemplo, www.cst.cam.ac.uk), y el resto (por ejemplo, /enseñanza /2021/ConcDisSys) es la ruta que el cliente solicita
en su mensaje de solicitud. Además de la ruta, la solicitud también contiene información adicional, como el método HTTP
(por ejemplo, GET para cargar una página o POST para enviar un formulario), la versión del software del cliente (el agente
de usuario) y una lista de formatos de archivo que el cliente entiende (el encabezado de aceptación). El mensaje de
respuesta contiene el archivo que se solicitó y un indicador de su formato de archivo (el tipo de contenido); en el caso de
una página web, puede ser un documento HTML, una imagen, un video, un documento PDF o cualquier otro tipo de archivo.
Dado que las solicitudes y las respuestas pueden ser más grandes de lo que cabe en un solo paquete de red, el
protocolo HTTP se ejecuta sobre TCP, lo que descompone una gran parte de los datos en un flujo de pequeños paquetes
de red (consulte la diapositiva 13) y coloca volver a juntarlos en el destinatario. HTTP también permite enviar múltiples
solicitudes y múltiples respuestas a través de una única conexión TCP. Sin embargo, al observar este protocolo desde el
punto de vista de los sistemas distribuidos, este detalle no es importante: tratamos la solicitud como un mensaje y la
respuesta como otro mensaje, independientemente de la cantidad de paquetes de red físicos involucrados en su
transmisión. Esto mantiene las cosas independientes de la tecnología de red subyacente.
Diapositiva 13
7
Machine Translated by Google
Diapositiva 14
El servicio de pagos a su vez se comunica con una red de tarjetas como Visa o MasterCard, que
se comunica con el banco que emitió su tarjeta para tomar el pago.
Para los programadores que están implementando la tienda online, el código para procesar el pago
puede parecerse al código de la diapositiva 15.
si (resultado.esÉxito()) {
cumplirPedido();
}
Llamar a la función procesarPago parece llamar a cualquier otra función, pero de hecho, lo que sucede detrás de
escena es que la tienda envía una solicitud al servicio de pagos, espera una respuesta y luego devuelve la respuesta que
recibió. La implementación real de processPayment, la lógica que se comunica con la red de tarjetas y los bancos, no
existe en el código de la tienda: es parte del servicio de pagos, que es otro programa que se ejecuta en otro nodo que
pertenece a una empresa diferente.
Este tipo de interacción, donde el código en un nodo parece llamar a una función en otro nodo, se denomina Llamada
a procedimiento remoto (RPC). En Java, se llama Invocación de método remoto (RMI). El software que implementa RPC
se denomina framework o middleware de RPC. (No todo el middleware se basa en RPC; también hay middleware que
utiliza diferentes modelos de comunicación).
Cuando una aplicación desea llamar a una función en otro nodo, el marco RPC proporciona un código auxiliar en su
lugar. El stub tiene el mismo tipo de firma que la función real, pero en lugar de ejecutar la función real, codifica los
argumentos de la función en un mensaje y envía ese mensaje al control remoto.
8
Machine Translated by Google
nodo, solicitando que se llame a esa función. El proceso de codificación de los argumentos de la función se conoce como
clasificación. En el ejemplo de la diapositiva 16, se usa una codificación JSON para ordenar, pero en la práctica también se
usan otros formatos.
talón de procesoPago()
m1
argumentos del mariscal
desarmar argumentos
implementación de
esperando
procesoPago()
m2
resultado del mariscal
resultado desorganizado
función devuelve
{
"solicitud": "procesarPago", "tarjeta":
{ "número": "1234567887654321",
"fecha de caducidad": "10/2024", {
"resultado": "éxito",
"CVC": "123" "id": "XP61hHw2Rvo"
m1 = m2 = }
},
"cantidad": 3,99,
"moneda": "GBP"
}
Diapositiva 16
El envío del mensaje desde el cliente RPC al servidor RPC puede realizarse a través de HTTP (en cuyo caso, esto
también se denomina servicio web), o se puede utilizar uno de los diferentes protocolos de red. En el lado del servidor, el
marco RPC desarma (descodifica) el mensaje y llama a la función deseada con los argumentos proporcionados. Cuando la
función regresa, sucede lo mismo a la inversa: el valor de retorno de la función se clasifica, se envía como un mensaje al
cliente, el cliente lo desclasifica y el stub lo devuelve. Por lo tanto, para la persona que llama al stub, parece como si la función
se hubiera ejecutado localmente.
Idealmente, RPC hace que una llamada a una función remota tenga el mismo aspecto
que una llamada a una función local.
“Transparencia de ubicación”: el
sistema oculta dónde se encuentra un recurso.
En la práctica. . .
Diapositiva 17
Ejercicio 1. Las redes y los nodos pueden fallar. ¿Cuáles son las implicaciones para el código que llama a otro nodo a través
de RPC? ¿En qué se diferencia RPC de una llamada de función local? ¿Se puede lograr la transparencia de la ubicación?
A lo largo de las décadas, se han desarrollado muchas variantes de RPC, con el objetivo de facilitar la programación de
sistemas distribuidos. Esto incluye middleware orientado a objetos como CORBA en la década de 1990.
Sin embargo, los desafíos subyacentes de los sistemas distribuidos siguen siendo los mismos [Waldo et al., 1994].
9
Machine Translated by Google
historia de RPC
Diapositiva 18
Hoy en día, la forma más común de RPC se implementa utilizando datos JSON enviados a través de HTTP. Un conjunto
popular de principios de diseño para dichas API basadas en HTTP se conoce como transferencia de estado representacional
o REST [Fielding, 2000], y las API que se adhieren a estos principios se denominan RESTful. Estos principios incluyen:
• los recursos (objetos que pueden ser inspeccionados y manipulados) están representados por URL, y
• el estado de un recurso se actualiza al realizar una solicitud HTTP con un tipo de método estándar, como
como POST o PUT, a la URL apropiada.
La popularidad de REST se debe al hecho de que el código JavaScript que se ejecuta en un navegador web puede realizar
fácilmente este tipo de solicitud HTTP, como se muestra en la diapositiva 19. En los sitios web modernos, es muy común
usar JavaScript para realizar solicitudes HTTP a un servidor sin recargar toda la página. Esta técnica a veces se conoce
como Ajax.
RPC/RESTO en JavaScript
Diapositiva 19
El código de la diapositiva 19 toma los argumentos args, los clasifica en JSON mediante JSON.stringify() y luego los
envía a la URL https://example.com/pagos mediante una solicitud HTTP POST. Hay tres resultados posibles: o el servidor
devuelve un código de estado que indica el éxito (en cuyo caso desarmamos la respuesta usando response.json()), o el
servidor devuelve un código de estado que indica un error, o la solicitud falla porque no se recibió ninguna respuesta.
recibida del servidor (probablemente debido a una interrupción de la red). El código llama a la función Success() o Failure()
en cada uno de estos casos.
Aunque las API RESTful y el RPC basado en HTTP se originaron en la web (donde el cliente es JavaScript ejecutándose
en un navegador web), ahora también se usan comúnmente con otros tipos de clientes (por ejemplo, aplicaciones móviles)
o para servidores de servidor a servidor. comunicación.
10
Machine Translated by Google
Diapositiva 20
Tal RPC de servidor a servidor es especialmente común en grandes empresas, cuyos sistemas de software son
demasiado grandes y complejos para ejecutarse en un solo proceso en una sola máquina. Para gestionar esta
complejidad, el sistema se descompone en múltiples servicios, que son desarrollados y administrados por diferentes
equipos y que incluso pueden implementarse en diferentes lenguajes de programación. Los marcos RPC facilitan la
comunicación entre estos servicios.
Cuando se utilizan diferentes lenguajes de programación, el marco RPC necesita convertir los tipos de datos de
modo que el código que se llama entienda los argumentos de la persona que llama y, de la misma manera, el valor
de retorno de la función. Una solución típica es utilizar un lenguaje de definición de interfaz (IDL) para proporcionar
firmas de tipos independientes del idioma de las funciones que están disponibles en RPC. A partir del IDL, los
desarrolladores de software pueden generar automáticamente código de clasificación/desclasificación y stubs RPC
para los respectivos lenguajes de programación de cada servicio y sus clientes. La diapositiva 21 muestra un ejemplo
del IDL utilizado por gRPC, denominados búferes de protocolo. Los detalles del idioma no son importantes para este curso.
mensaje PaymentRequest
{ mensaje Tarjeta { cadena
requerida cardNumber = 1; opcional int32
expiraciónMes = 2; opcional int32
expirationYear = 3; opcional int32 CVC = 4; }
enum Moneda { GBP = 1; USD = 2; }
tarjeta requerida
Tarjeta requerida = 1; cantidad
de int64 = 2; Moneda requerida
3; } Moneda =
Diapositiva 21
11
Machine Translated by Google
En el problema de los dos generales [Gray, 1978], imaginamos dos generales, cada uno al frente de un ejército, que quieren
capturar una ciudad. (Disculpas por la analogía militarista, ¡no se me ocurrió!) Las defensas de la ciudad
son fuertes, y si solo uno de los dos ejércitos ataca, el ejército será derrotado. Sin embargo, si ambos ejércitos
ataque al mismo tiempo, capturarán con éxito la ciudad.
ciudad
¿ataque? ¿ataque?
ejercito 1 ejercito 2
mensajeros
Diapositiva 22
Por lo tanto, los dos generales deben coordinar su plan de ataque. Esto se dificulta por el hecho de que
los dos ejércitos están acampados a cierta distancia uno del otro y solo pueden comunicarse por medio de mensajeros. los
los mensajeros deben atravesar el territorio controlado por la ciudad, por lo que a veces son capturados.
Así, un mensaje enviado por un general puede o no ser recibido por el otro general, y el remitente
no sabe si su mensaje llegó, excepto al recibir una respuesta explícita del otro
fiesta. Si un general no recibe ningún mensaje, es imposible saber si es porque el otro
general no envió ningún mensaje, o porque todos los mensajeros fueron capturados.
generales 1 generales 2
10 de noviembre de acuerdo!
generales 1 generales 2
Diapositiva 23
¿Qué protocolo deben usar los dos generales para acordar un plan? Para cada general hay dos opciones:
o el general promete seguir adelante con el ataque en cualquier caso (incluso si no se recibe respuesta), o
el general espera un reconocimiento antes de comprometerse a atacar. En el primer caso, el general
quien promete seguir adelante corre el riesgo de quedarse solo en el ataque. En el segundo caso, el general que espera
reconocimiento traslada el problema al otro general, que ahora debe decidir si se compromete a
atacar (y correr el riesgo de quedarse solo) o esperar un reconocimiento del reconocimiento.
12
Machine Translated by Google
El problema es que no importa cuántos mensajes se intercambien, ninguno de los generales puede estar
seguro de que el otro ejército también aparecerá al mismo tiempo. Una secuencia repetida de acuses de recibo de
ida y vuelta puede aumentar gradualmente la confianza de que los generales están de acuerdo, pero se puede
probar que no pueden alcanzar la certeza intercambiando un número finito de mensajes.
Este experimento mental demuestra que en un sistema distribuido, no hay forma de que un nodo tenga certeza
sobre el estado de otro nodo. La única forma en que un nodo puede saber algo es comunicando ese conocimiento
en un mensaje. En una nota filosófica, esto es quizás similar a la comunicación entre humanos: no tenemos
telepatía, por lo que la única forma de que otra persona sepa lo que estás pensando es comunicándolo (a través
del habla, la escritura, el lenguaje corporal, etc.).
Como ejemplo práctico del problema de los dos generales, la diapositiva 25 adapta el modelo de la diapositiva
22 a la aplicación del pago de bienes en una tienda online. La tienda y el servicio de procesamiento de pagos con
tarjeta de crédito se comunican por RPC y algunos de estos mensajes pueden perderse. Sin embargo, la tienda
quiere asegurarse de que envía los productos solo si se pagan, y solo carga la tarjeta del cliente si se envían los
productos.
cliente
En la práctica, el ejemplo de la compra online no coincide exactamente con el problema de los dos generales:
en este escenario, es seguro que el servicio de pagos siga adelante con un pago, porque si la tienda no puede
despachar la mercancía, puede reembolsar el pago. El hecho de que un pago sea algo que se puede deshacer (a
diferencia de un ejército derrotado) hace que el problema sea solucionable. Si se interrumpe la comunicación entre
la tienda y el servicio de pago, la tienda puede esperar hasta que se restablezca la conexión y luego consultar el
servicio de pago para averiguar el estado de cualquier transacción cuyo resultado se desconociera.
13
Machine Translated by Google
los generales bizantinos [Lamport et al., 1982] tiene un escenario similar al problema de los dos generales.
Nuevamente tenemos ejércitos queriendo capturar una ciudad, aunque en este caso pueden ser tres o más. Nuevamente
los generales se comunican por medio de mensajeros, aunque esta vez asumimos que si se envía un mensaje, siempre se
entrega correctamente.
ejercito 3
¿ataque?
mensajeros mensajeros
ciudad
¿ataque? ¿ataque?
ejercito 1 ejercito 2
mensajeros
Diapositiva 26
El desafío en el entorno bizantino es que algunos generales pueden ser "traidores": es decir, pueden tratar de engañar
y confundir deliberada y maliciosamente a los otros generales. Un ejemplo de tal comportamiento malicioso se muestra en
la diapositiva 27: aquí, el general 3 recibe dos mensajes contradictorios de los generales 1 y 2. El general 1 le dice al
general 3 que ataque, mientras que el general 2 afirma que el general 1 ordenó la retirada. Es imposible que el general 3
determine si el general 2 miente (el primer caso) o si el general 2 es honesto mientras que el general 1 emite órdenes
contradictorias (el segundo caso).
¡ataque!
general
¡Dije retirada!
¡retirada!
general
¡Dije retirada!
Diapositiva 27
Los generales honestos no saben quiénes son los generales maliciosos, pero los generales maliciosos pueden coludirse
y coordinar sus acciones en secreto. Incluso podríamos suponer que todos los generales maliciosos están controlados por
un adversario malvado. El problema de los generales bizantinos es entonces asegurarse de que todos los generales
honestos estén de acuerdo en el mismo plan (por ejemplo, si atacar o retirarse). Es imposible especificar qué van a hacer
los generales maliciosos, así que lo mejor que podemos hacer es lograr que los generales honestos estén de acuerdo.
Esto es difícil: de hecho, se puede probar que algunas variantes del problema pueden resolverse solo si estrictamente
menos de un tercio de los generales son maliciosos. Es decir, en un sistema con 3f + 1 generales, no más de f pueden ser
maliciosos. Por ejemplo, un sistema con 4 generales puede tolerar f = 1 general malicioso, y un sistema con 7 generales
puede tolerar f = 2.
14
Machine Translated by Google
Sin embargo , los generales honestos deben ponerse de acuerdo sobre el plan
permanece duro
Diapositiva 28
El problema se simplifica un poco si los generales usan criptografía (firmas digitales) para probar quién dijo qué: por
ejemplo, esto permitiría al general 2 probar al general 3 cuál fue la orden del general 1 y así demostrar la honestidad del
general 2. No entraremos en los detalles de las firmas digitales en este curso, ya que se tratan en el curso de seguridad
(término de Pascua de la Parte IB). Sin embargo, incluso con las firmas, el problema de los generales bizantinos sigue
siendo un desafío.
¿Es el problema de los generales bizantinos de relevancia práctica? Los sistemas distribuidos reales a menudo
implican relaciones de confianza complejas. Por ejemplo, un cliente debe confiar en una tienda en línea para que
realmente le entregue los productos que solicitó, aunque puede disputar el pago a través de su banco si los productos
nunca llegan o si se les cobra demasiado. Pero si una tienda en línea de alguna manera permitiera a los clientes pedir
productos sin pagar por ellos, los estafadores sin duda explotarían esta debilidad, por lo que la tienda debe asumir que
los clientes son potencialmente maliciosos. Por otro lado, para RPC entre servicios pertenecientes a la tienda, que se
ejecutan en el mismo centro de datos, un servicio probablemente puede confiar en los otros servicios administrados por
la misma empresa. El servicio de pagos no confía plenamente en la tienda, ya que alguien podría configurar una tienda
fraudulenta o utilizar números de tarjetas de crédito robados, pero es probable que la tienda confíe en el servicio de pagos. Y así.
Y al final, queremos que el cliente, la tienda online y el servicio de pagos estén de acuerdo con cualquier pedido que se
realice. El problema de los generales bizantinos es una simplificación de relaciones de confianza tan complejas, pero es
un buen punto de partida para estudiar sistemas en los que algunos participantes pueden comportarse maliciosamente.
cliente
¿estar de acuerdo?
RPC RPC
pedido
Diapositiva 29
Antes de continuar, una breve digresión sobre el origen de la palabra “bizantino”. El término proviene del imperio
bizantino, llamado así por su ciudad capital, Bizancio o Constantinopla, que ahora es Estambul en Turquía. No hay
evidencia histórica de que los generales del imperio bizantino fueran más propensos a la intriga y la conspiración que
los de otros lugares. Más bien, la palabra bizantino se había usado en el sentido de "excesivamente complicado,
burocrático, tortuoso" mucho antes de que Leslie Lamport adoptara la palabra para describir el problema de los generales
bizantinos; la etimología exacta no está clara.
15
Machine Translated by Google
Fuente: https://commons.wikimedia.org/wiki/File:Byzantiumby650AD.svg
Diapositiva 30
¡En sistemas reales, tanto los nodos como las redes pueden estar defectuosos!
Diapositiva 31
slate.com/technology/2014/08/ shark-attacks-threaten-google-
s-undersea-internet-cables-video.html
https://twitter.com/uhoelzle/status/1263333283107991558
Diapositiva 32
dieciséis
Machine Translated by Google
Comencemos con la red. Ninguna red es perfectamente confiable: incluso en sistemas cuidadosamente diseñados con enlaces de
red redundantes, las cosas pueden salir mal [Bailis y Kingsbury, 2014]. Alguien podría desconectar accidentalmente el cable de red
equivocado. Se ha demostrado que tanto los tiburones como las vacas causan daños e interrupciones en las redes de larga distancia
(consulte los enlaces en la diapositiva 32). O una red puede sobrecargarse temporalmente, quizás por accidente o quizás debido a un
ataque de denegación de servicio. Cualquiera de estos puede hacer que los mensajes se pierdan.
En un modelo de sistema, adoptamos una visión más abstracta, lo que nos evita los detalles de preocuparnos por los tiburones y
las vacas. La mayoría de los algoritmos distribuidos asumen que la red proporciona un mensaje bidireccional que pasa entre un par de
nodos, también conocido como comunicación punto a punto o unidifusión. Las redes reales a veces permiten la comunicación de
difusión o multidifusión (enviar un paquete a muchos destinatarios al mismo tiempo, lo que se usa, por ejemplo, para descubrir una
impresora en una red local), pero en términos generales, asumir que solo unidifusión es un buen modelo de Internet. hoy dia. Más
adelante, en la lección 4, mostraremos cómo implementar la transmisión sobre la comunicación de unidifusión.
Luego podemos elegir qué tan confiables queremos asumir que son estos enlaces. La mayoría de los algoritmos asumen una de
las tres opciones enumeradas en la diapositiva 33.
Suponga una comunicación bidireccional punto a punto entre dos nodos, con uno de
los siguientes:
deduplicación
Enlaces de pérdida justa :
Diapositiva 33
Curiosamente, es posible convertir unos tipos de enlace en otros. Por ejemplo, si tenemos un enlace de pérdida justa, podemos
convertirlo en un enlace confiable retransmitiendo continuamente los mensajes perdidos hasta que finalmente se reciben y filtrando los
mensajes duplicados en el lado del destinatario. La suposición de pérdida justa significa que cualquier partición de la red (interrupción
de la red) durará solo un período de tiempo finito, pero no para siempre, por lo que podemos garantizar que finalmente se recibirán
todos los mensajes.
Por supuesto, los mensajes enviados durante una partición de la red solo se recibirán después de que se repare la interrupción, lo
que puede llevar mucho tiempo, pero las definiciones de la diapositiva 33 no dicen nada sobre el retraso o la latencia de la red.
Llegaremos a ese tema en la diapositiva 35.
El protocolo TCP, que analizamos brevemente en la Sección 1.2, realiza este tipo de reintento y desduplicación a nivel de paquete
de red. Sin embargo, TCP generalmente se configura con un tiempo de espera, por lo que se dará por vencido y dejará de intentarlo
después de cierto tiempo, generalmente del orden de un minuto. Para superar las particiones de red que duran más de esta duración,
se debe implementar un mecanismo de deduplicación y reintento independiente además del proporcionado por TCP.
Un enlace arbitrario es un modelo preciso para la comunicación a través de Internet: cada vez que su comunicación se enruta a
través de una red (ya sea el wifi de una cafetería o una red troncal de Internet), el operador de esa red puede interferir y manipular sus
paquetes de red. de manera arbitraria. Alguien que manipula el tráfico de la red también se conoce como adversario activo.
Afortunadamente, es casi posible convertir un enlace arbitrario en un enlace de pérdida justa utilizando técnicas criptográficas. El
protocolo Transport Layer Security (TLS), que proporciona la "s" de "seguro" en https://, evita que un adversario activo espíe, modifique,
falsifique o reproduzca el tráfico (más sobre esto en el curso de Seguridad, Parte IB término de Semana Santa).
Lo único que TLS no puede evitar es que el adversario abandone (bloquee) la comunicación. Por lo tanto, un enlace arbitrario
puede convertirse en un enlace de pérdida justa solo si asumimos que el adversario no bloquea la comunicación para siempre. En
algunas redes, es posible enrutar el enlace de red interrumpido, pero no siempre es así.
17
Machine Translated by Google
Por lo tanto, la suposición de un enlace de red confiable quizás no sea tan poco realista como puede parecer a primera vista:
en general, es posible recibir todos los mensajes enviados, siempre que estemos dispuestos a esperar un período de tiempo
potencialmente arbitrario para recibirlos. reintentos durante una partición de red. Sin embargo, también debemos considerar la
posibilidad de que el remitente de un mensaje se bloquee al intentar retransmitir un mensaje, lo que puede causar que ese mensaje
se pierda de forma permanente. Esto nos lleva al tema de los bloqueos de nodos.
Diapositiva 34
En el modelo de bloqueo y detención, asumimos que después de que un nodo falla, nunca se recupera. Este es un modelo
razonable para una falla de hardware irrecuperable, o para la situación en la que una persona deja caer su teléfono en el inodoro,
después de lo cual queda permanentemente fuera de servicio. Con una caída del software, el modelo de parada por caída puede
parecer poco realista, porque simplemente podemos reiniciar el nodo, después de lo cual se recuperará. Sin embargo, algunos
algoritmos asumen un modelo de parada de choque, ya que eso hace que el algoritmo sea más simple. En este caso, un nodo que
falla y se recupera tendría que volver a unirse al sistema como un nuevo nodo. Alternativamente, el modelo de recuperación tras un
bloqueo permite explícitamente que los nodos se reinicien y reanuden el procesamiento después de un bloqueo.
Finalmente, el modelo bizantino es el modelo más general de comportamiento de los nodos: como en el problema de los
generales bizantinos, un nodo defectuoso no solo puede bloquearse, sino también desviarse del algoritmo especificado de manera
arbitraria, incluida la exhibición de un comportamiento malicioso. Un error en la implementación de un nodo también podría
clasificarse como una falla bizantina. Sin embargo, si todos los nodos ejecutan el mismo software, todos tendrán el mismo error, por
lo que cualquier algoritmo que se base en menos de un tercio de los nodos con fallas bizantinas no podrá tolerar dicho error. En
principio, podríamos intentar usar varias implementaciones diferentes del mismo algoritmo, pero rara vez es una opción práctica.
En el caso de la red, era posible convertir un modelo a otro utilizando protocolos genéricos.
Este no es el caso con los diferentes modelos de comportamiento de los nodos. Por ejemplo, un algoritmo diseñado para un modelo
de sistema de recuperación de fallas puede verse muy diferente de un algoritmo bizantino.
Yo Asíncrono:
Los mensajes se pueden retrasar arbitrariamente.
Los nodos pueden pausar la ejecución arbitrariamente.
No hay garantías de tiempo en absoluto.
Diapositiva 35
18
Machine Translated by Google
La tercera parte de un modelo de sistema es la suposición de sincronía, que tiene que ver con el tiempo. Las tres elecciones
que podemos hacer aquí son sincrónicas, asincrónicas o parcialmente sincrónicas [Dwork et al., 1988].
(Tenga en cuenta que las definiciones de estos términos difieren un poco en diferentes partes de la informática; nuestras definiciones
aquí son estándar en el campo de la computación distribuida).
Un sistema síncrono es lo que nos encantaría tener: un mensaje enviado a través de la red nunca toma más tiempo que una
latencia máxima conocida, y los nodos siempre ejecutan su algoritmo a una velocidad predecible. Muchos problemas en la
computación distribuida son mucho más fáciles si asume un sistema síncrono.
Y es tentador suponer sincronía, porque las redes y los nodos se comportan bien la mayor parte del tiempo, por lo que esta
suposición suele ser cierta.
Desafortunadamente, la mayor parte del tiempo no es lo mismo de siempre, y los algoritmos diseñados para un modelo síncrono
a menudo fallan catastróficamente si se violan los supuestos de latencia limitada y velocidad de ejecución limitada, aunque sea por
un corto tiempo, e incluso si esto sucede raramente. Y en los sistemas prácticos, hay muchas razones por las que la latencia de la
red o la velocidad de ejecución a veces pueden variar enormemente, consulte la diapositiva 36.
El otro extremo es un modelo asíncrono, en el que no hacemos ninguna suposición de tiempo: permitimos que los mensajes se
retrasen arbitrariamente en la red y permitimos diferencias arbitrarias en las velocidades de procesamiento de los nodos (por ejemplo,
permitimos que un nodo pause la ejecución mientras otros nodos continúan funcionando normalmente). Los algoritmos que están
diseñados para un modelo asincrónico suelen ser muy sólidos, ya que no se ven afectados por interrupciones temporales de la red o
picos de latencia.
Desafortunadamente, algunos problemas en la computación distribuida son imposibles de resolver en un modelo asíncrono y,
por lo tanto, tenemos el modelo parcialmente síncrono como compromiso. En este modelo, asumimos que nuestro sistema es
sincrónico y se comporta bien la mayor parte del tiempo, pero ocasionalmente puede pasar al modo asíncrono en el que todas las
garantías de tiempo están desactivadas, y esto puede suceder de manera impredecible. El modelo parcialmente síncrono es bueno
para muchos sistemas prácticos, pero usarlo correctamente requiere cuidado.
Las redes suelen tener una latencia bastante predecible, que en ocasiones
puede aumentar:
I Reconfiguración de red/ruta
Diapositiva 36
Hay muchas razones por las que un sistema puede violar los supuestos de sincronía. Ya hemos hablado sobre el aumento
ilimitado de la latencia si los mensajes se pierden y se retransmiten, especialmente si tenemos que esperar a que se repare una
partición de la red antes de que los mensajes puedan pasar. Otra razón por la que aumenta la latencia en una red es la congestión
que provoca la puesta en cola de los paquetes en los búferes del conmutador. La reconfiguración de la red también puede causar
grandes retrasos: incluso dentro de un solo centro de datos, se han documentado casos de paquetes que se retrasan por más de un
minuto [Imbriaco, 2012].
Podríamos esperar que la velocidad a la que los nodos ejecutan sus algoritmos sea constante: después de todo, una instrucción
generalmente toma un número fijo de ciclos de reloj de la CPU y la velocidad del reloj no varía mucho.
Sin embargo, incluso en un solo nodo, hay muchas razones por las que un programa en ejecución puede pausarse inesperadamente
durante períodos de tiempo significativos. La programación en el sistema operativo puede adelantarse a un subproceso en ejecución
y dejarlo en pausa mientras se ejecutan otros programas, especialmente en una máquina con mucha carga. Un problema real en los
lenguajes administrados por memoria como Java es que cuando se ejecuta el recolector de elementos no utilizados, debe pausar
todos los subprocesos en ejecución de vez en cuando (esto se conoce como una pausa de recolección de elementos no utilizados
para detener el mundo). ¡ En pilas grandes, estas pausas pueden durar varios minutos [Thompson, 2013]! Los errores de página son
otra razón por la que un subproceso puede suspenderse, especialmente cuando no queda mucha memoria libre.
Como sabe por la mitad de los sistemas concurrentes de este curso, los subprocesos pueden y serán reemplazados incluso en
los momentos más inconvenientes, en cualquier parte de un programa. En un sistema distribuido, esto es particularmente
19
Machine Translated by Google
problemático, porque para un nodo, el tiempo parece "detenerse" mientras está en pausa, y durante este tiempo todos los
demás nodos continúan ejecutando sus algoritmos normalmente. Otros nodos pueden incluso notar que el nodo en pausa
no responde y suponer que se ha bloqueado. Después de un tiempo, el nodo en pausa reanuda el procesamiento, sin
siquiera darse cuenta de que estuvo en pausa durante un período de tiempo significativo.
Combinado con las muchas razones para la latencia de red variable, esto significa que en los sistemas prácticos, rara
vez es seguro asumir un modelo de sistema síncrono. La mayoría de los algoritmos distribuidos deben diseñarse para el
modelo asíncrono o parcialmente síncrono.
Red I : confiable,
de pérdida justa o arbitraria
Nodos I :
parada de choque, recuperación de choque o bizantino
Temporización:
síncrona , parcialmente síncrona o asíncrona
Diapositiva 37
20
Machine Translated by Google
Por lo general, las expectativas de disponibilidad de un servicio se formalizan como un objetivo de nivel de servicio (SLO),
que generalmente especifica el porcentaje de solicitudes que deben devolver una respuesta correcta dentro de un tiempo de
espera especificado, medido por un determinado cliente durante un cierto período de tiempo. hora. Un acuerdo de nivel de
servicio (SLA) es un contrato que especifica algún SLO, así como las consecuencias si no se cumple el SLO (por ejemplo, es
posible que el proveedor de servicios deba ofrecer un reembolso a sus clientes).
Las fallas (como bloqueos de nodos o interrupciones de la red) son una causa común de falta de disponibilidad. Para
aumentar la disponibilidad, podemos reducir la frecuencia de fallas, o podemos diseñar sistemas para que sigan funcionando
a pesar de que algunos de sus componentes estén defectuosos; el último enfoque se llama tolerancia a fallas. Es posible
reducir la frecuencia de las fallas comprando hardware de mayor calidad e introduciendo redundancia, pero este enfoque
nunca puede reducir la probabilidad de fallas a cero. En cambio, la tolerancia a fallas es el enfoque adoptado por muchos
sistemas distribuidos.
Tolerancia a fallos:
el sistema como un todo continúa funcionando, a pesar de las
fallas (se asume un número máximo de fallas)
Diapositiva 39
Si todos los nodos fallan y no se recuperan, ningún algoritmo podrá realizar ningún trabajo, por lo que no tiene sentido
tolerar un número arbitrario de fallas. Más bien, un algoritmo está diseñado para tolerar un número específico de fallas: por
ejemplo, algunos algoritmos distribuidos pueden progresar siempre que menos de la mitad de los nodos se hayan bloqueado.
En los sistemas tolerantes a fallas, queremos evitar los puntos únicos de falla (SPOF), es decir, los componentes que
causarían una interrupción si fallaran. Por ejemplo, Internet está diseñado para no tener SPOF: no hay un servidor o enrutador
cuya destrucción provocaría la caída de Internet por completo (aunque la pérdida de algunos componentes, como los enlaces
de fibra intercontinentales clave, causa una interrupción notable).
El primer paso para tolerar fallas es detectar fallas, lo que a menudo se hace con un detector de fallas.
("Detector de fallas" sería más lógico, pero "detector de fallas" es el término convencional.) Un detector de fallas generalmente
detecta fallas por colisión. Las fallas bizantinas no siempre son detectables, aunque en algunos casos el comportamiento
bizantino deja evidencia que puede usarse para identificar y excluir nodos maliciosos.
Detectores de fallos
Detector de fallos:
algoritmo que detecta si otro nodo es defectuoso
Problema:
no se puede diferenciar entre un nodo bloqueado, un nodo que no responde
temporalmente, un mensaje perdido y un mensaje retrasado
Diapositiva 40
21
Machine Translated by Google
En la mayoría de los casos, un detector de fallas funciona enviando mensajes periódicamente a otros nodos y etiquetando
un nodo como fallado si no se recibe una respuesta dentro del tiempo esperado. Idealmente, nos gustaría que se agotara el
tiempo de espera si y solo si el nodo realmente se bloqueó (esto se denomina detector de falla perfecto). Sin embargo, el
problema de los dos generales nos dice que esta no es una forma totalmente precisa de detectar un bloqueo, porque la
ausencia de una respuesta también podría deberse a la pérdida o retraso del mensaje.
Un detector de fallas perfecto basado en el tiempo de espera solo existe en un sistema sincrónico de parada por choque
con enlaces confiables; en un sistema parcialmente síncrono, no existe un detector de fallas perfecto. Además, en un sistema
asíncrono, no existe una falla basada en el tiempo de espera, ya que los tiempos de espera no tienen sentido en el modelo
asíncrono. Sin embargo, existe un útil detector de fallas en los sistemas parcialmente síncronos: el detector de fallas
eventualmente perfecto [Chandra y Toueg, 1996].
Diapositiva 41
Más adelante veremos cómo usar un detector de fallas de este tipo para diseñar mecanismos de tolerancia a fallas y para
recuperarse automáticamente de fallas de nodos. Usando tales algoritmos, es posible construir sistemas que sean altamente
disponibles. Tolerar fallas también facilita las operaciones diarias: por ejemplo, si un servicio puede tolerar que uno de cada
tres nodos no esté disponible, entonces se puede implementar una actualización de software instalándolo y reiniciando un
nodo a la vez, mientras que el resto dos nodos continúan ejecutando el servicio. Poder implementar actualizaciones de
software de esta manera, sin que los clientes noten ninguna interrupción, es importante para muchas organizaciones que
trabajan continuamente en su software.
Para aplicaciones críticas para la seguridad, como los sistemas de control de tráfico aéreo, sin duda es importante invertir
en buenos mecanismos de tolerancia a fallas. Sin embargo, no es el caso que una mayor disponibilidad sea siempre mejor.
Alcanzar una disponibilidad extremadamente alta requiere un esfuerzo de ingeniería muy centrado y, a menudo, elecciones
de diseño conservadoras. Por ejemplo, la antigua red telefónica de línea fija está diseñada para una disponibilidad de "cinco
nueves", pero la desventaja de este enfoque en la disponibilidad es que ha evolucionado muy lentamente. La mayoría de los
servicios de Internet ni siquiera llegan a cuatro nueves debido a los rendimientos decrecientes: más allá de algún punto, el
costo adicional de lograr una mayor disponibilidad supera el costo del tiempo de inactividad ocasional, por lo que es
económicamente racional aceptar una cierta cantidad de tiempo de inactividad.
Ejercicio 2. Los enlaces de red confiables permiten reordenar los mensajes. Proporcione un pseudocódigo para un algoritmo
que refuerce las propiedades de un enlace punto a punto confiable, de modo que los mensajes se reciban en el orden en que
fueron enviados (esto se denomina enlace FIFO), asumiendo un modelo de sistema asíncrono de parada por choque.
Ejercicio 3. ¿Cómo necesitamos cambiar el algoritmo del ejercicio 2 si asumimos un modelo de recuperación de fallas en lugar
de un modelo de parada de fallas?
22
Machine Translated by Google
En la noche del 30 de junio al 1 de julio de 2012 (hora del Reino Unido), muchos
servicios y sistemas en línea de todo el mundo colapsaron simultáneamente.
¿Qué sucedió?
Diapositiva 42
En esta lección veremos el concepto de tiempo en sistemas distribuidos. Ya hemos visto que nuestras suposiciones
sobre el tiempo forman una parte clave del modelo del sistema en el que se basan los algoritmos distribuidos.
Por ejemplo, los detectores de fallas basados en el tiempo de espera deben medir el tiempo para determinar cuándo ha
transcurrido un tiempo de espera. Los sistemas operativos se basan en gran medida en temporizadores y mediciones de
tiempo para programar tareas, realizar un seguimiento del uso de la CPU y muchos otros fines. Las aplicaciones a menudo
desean registrar la hora y la fecha en que ocurrieron los eventos: por ejemplo, al depurar un error en un sistema distribuido,
las marcas de tiempo son útiles para la depuración, ya que nos permiten reconstruir qué cosas sucedieron aproximadamente
al mismo tiempo en diferentes nodos. Todos estos requieren mediciones de tiempo más o menos precisas.
Diapositiva 43
23
Machine Translated by Google
relojes de cuarzo
Yo cristal de cuarzo
recortado con láser
Diapositiva 44
Los relojes de cuarzo son baratos, pero no son totalmente precisos. Debido a imperfecciones de fabricación, algunos relojes
funcionan un poco más rápido que otros. Además, la frecuencia de oscilación varía con la temperatura.
Los relojes de cuarzo típicos están ajustados para ser bastante estables a temperatura ambiente, pero las temperaturas
significativamente más altas o más bajas ralentizan el reloj. La velocidad a la que un reloj se adelanta o atrasa se denomina deriva.
La temperatura
afecta
significativamente la deriva
Diapositiva 45
relojes atómicos
I El cesio-133 tiene un
resonancia (“transición
hiperfina”) a ÿ 9 GHz
Sintonizo un oscilador
electrónico a esa frecuencia
resonante I 1 segundo =
en 3 millones de años)
https: //www.microsemi.com/product-
directory/cesium-frequency-references/
I Precio ÿ £ 20,000 (?) (puede 4115-5071a-cesium-primary-frequency-standard
obtener relojes de rubidio más
baratos por ÿ £ 1,000)
Diapositiva 46
Cuando se requiere mayor precisión, se utilizan relojes atómicos. Estos relojes se basan en la cuántica.
24
Machine Translated by Google
propiedades mecánicas de ciertos átomos, como el cesio o el rubidio. De hecho, la unidad de tiempo de un segundo en el
Sistema Internacional de Unidades (SI) se define como exactamente 9.192.631.770 períodos de una frecuencia resonante
particular del átomo de cesio-133.
Diapositiva 47
Otro método de alta precisión para obtener la hora es confiar en el sistema de posicionamiento por satélite GPS, o
sistemas similares como Galileo o GLONASS. Estos sistemas funcionan al tener varios satélites orbitando la Tierra y
transmitiendo la hora actual a muy alta resolución. Los receptores miden el tiempo que tardó la señal de cada satélite en
llegar a ellos y lo utilizan para calcular su distancia de cada satélite y, por lo tanto, su ubicación. Al conectar un receptor
GPS a una computadora, es posible obtener un reloj con una precisión de una fracción de microsegundo, siempre que el
receptor pueda obtener una señal clara de los satélites. En un centro de datos, generalmente hay demasiada interferencia
electromagnética para obtener una buena señal, por lo que un receptor GPS requiere una antena en el techo del edificio
del centro de datos.
Ahora tenemos un problema: tenemos dos definiciones diferentes de tiempo, una basada en la mecánica cuántica y la
otra basada en la astronomía, y esas dos definiciones no coinciden con precisión. Una rotación del planeta Tierra alrededor
de su propio eje no toma exactamente 24 × 60 × 60 × 9,192,631,770 períodos de frecuencia de resonancia de cesio-133.
De hecho, la velocidad de rotación del planeta ni siquiera es constante: fluctúa debido a los efectos de las mareas, los
terremotos, el derretimiento de los glaciares y algunos factores inexplicables.
Diapositiva 48
La solución es el Tiempo Universal Coordinado (UTC), que se basa en el tiempo atómico, pero incluye correcciones
para tener en cuenta las variaciones en la rotación de la Tierra. En la vida cotidiana, usamos nuestra zona horaria local,
que se especifica como un desplazamiento de UTC.
La zona horaria local del Reino Unido se llama Greenwich Mean Time (GMT) en invierno y British Summer Time (BST)
en verano, donde GMT se define como UTC y BST se define como UTC + 1 hora.
De manera confusa, el término hora media de Greenwich se usó originalmente para referirse a la hora solar media en el
25
Machine Translated by Google
Meridiano de Greenwich, es decir, antes se definía en términos de astronomía, mientras que ahora se define en términos de relojes
atómicos. Hoy en día, el término UT1 se usa para referirse al tiempo solar medio a 0° de longitud.
La diferencia entre UTC y TAI es que UTC incluye segundos bisiestos, que se agregan según sea necesario
para mantener el UTC más o menos sincronizado con la rotación de la Tierra.
Segundos bisiestos
http://leapsecond.com/notes/leap-watch.htm
Diapositiva 49
Debido a los segundos bisiestos, no es cierto que una hora siempre tenga 3600 segundos y un día siempre tenga 86 400
segundos. En la escala de tiempo UTC, un día puede tener 86 399 segundos, 86 400 segundos o 86 401 segundos debido a un
segundo bisiesto. Esto complica el software que necesita trabajar con fechas y horas.
En informática, una marca de tiempo es una representación de un punto particular en el tiempo. Normalmente se utilizan dos
representaciones de marcas de tiempo: tiempo Unix e ISO 8601. Para el tiempo Unix, cero corresponde a la fecha elegida
arbitrariamente del 1 de enero de 1970, conocida como la época. Hay variaciones menores: por ejemplo, System.currentTimeMillis()
de Java es como el tiempo de Unix, pero usa milisegundos en lugar de segundos.
Diapositiva 50
Para ser correcto, el software que funciona con marcas de tiempo necesita conocer los segundos intercalares. Por ejemplo, si
desea calcular cuántos segundos transcurrieron entre dos marcas de tiempo, necesita saber cuántos segundos intercalares se
insertaron entre esas dos fechas. Para fechas que están a más de seis meses en el futuro, esto es imposible de saber, ¡porque la
rotación de la Tierra aún no ha ocurrido!
El enfoque más común en el software es simplemente ignorar los segundos intercalares, fingir que no existen y esperar que el
problema desaparezca de alguna manera. Este enfoque lo adoptan las marcas de tiempo de Unix y el estándar POSIX. Para el
software que solo necesita tiempos de grano grueso (por ejemplo, redondeado al día más cercano), esto está bien, ya que la
diferencia de unos pocos segundos no es significativa.
Sin embargo, los sistemas operativos y los sistemas distribuidos a menudo se basan en marcas de tiempo de alta resolución
para mediciones precisas del tiempo, donde una diferencia de un segundo es muy notable. En tales entornos, ignorar los segundos
bisiestos puede ser peligroso. Por ejemplo, supongamos que tiene un programa Java que llama dos veces
26
Machine Translated by Google
System.currentTimeMillis(), con 500 ms de diferencia, dentro de un segundo bisiesto positivo (es decir, mientras el reloj marca las 23:59:60).
¿Cuál será la diferencia entre esas dos marcas de tiempo? No puede ser 500, ya que el reloj currentTimeMillis() no tiene en cuenta los
segundos bisiestos. ¿Se detiene el reloj, por lo que la diferencia entre las dos marcas de tiempo es cero? ¿O podría la diferencia incluso
ser negativa, por lo que el reloj retrocede por un breve momento? La documentación guarda silencio sobre esta pregunta. (Probablemente,
la mejor solución sea usar un reloj monótono, que presentamos en la diapositiva 58).
El mal manejo del segundo intercalar el 30 de junio de 2012 es lo que causó las fallas simultáneas de muchos servicios ese día
(Diapositiva 42). Debido a un error en el kernel de Linux, el segundo intercalar tenía una alta probabilidad de desencadenar una condición
de bloqueo activo cuando se ejecutaba un proceso de subprocesos múltiples [Allen, 2013, Minar, 2012]. Incluso un reinicio no solucionó el
problema, pero configurar el reloj del sistema restableció el mal estado en el kernel.
¡Ignorándolos!
Diapositiva 51
Hoy en día, algunos programas manejan los segundos bisiestos explícitamente, mientras que otros programas continúan ignorándolos.
Una solución pragmática que se usa ampliamente hoy en día es que cuando ocurre un segundo bisiesto positivo, en lugar de insertarlo
entre las 23:59:59 y las 00:00:00, el segundo adicional se distribuye durante varias horas antes y después de esa hora deliberadamente
ralentizando los relojes durante ese tiempo (o acelerándolos en el caso de un segundo bisiesto negativo). Este enfoque se llama manchar
el segundo bisiesto y no está exento de problemas.
Sin embargo, es una alternativa pragmática a hacer que todo el software sea consciente y resistente a los segundos intercalares, lo que
bien puede ser inviable.
Ejercicio 4. Describa algunos problemas que pueden surgir de la mancha de segundo bisiesto.
Diapositiva 52
27
Machine Translated by Google
Los relojes atómicos son demasiado costosos y voluminosos para integrarlos en todas las computadoras y teléfonos, por lo que
se utilizan relojes de cuarzo. Como se discutió en la diapositiva 45, estos relojes se desvían y necesitan ajustes de vez en cuando.
El enfoque más común es utilizar el Protocolo de tiempo de red (NTP). Todos los sistemas operativos principales tienen clientes NTP
incorporados; por ejemplo, la diapositiva 53 muestra el cuadro de diálogo de configuración de NTP en macOS.
Diapositiva 53
Puede ponerse en contacto con múltiples servidores, descartar valores atípicos, promedio de descanso
Diapositiva 54
La sincronización horaria en una red se ve dificultada por una latencia impredecible. Como se discutió en la diapositiva 36, tanto
la latencia de la red como la velocidad de procesamiento de los nodos pueden variar considerablemente. Para reducir los efectos de
las variaciones aleatorias, NTP toma varias muestras de medidas de tiempo y aplica filtros estadísticos para eliminar los valores
atípicos.
La diapositiva 55 muestra cómo NTP estima el sesgo de reloj entre el cliente y el servidor. Cuando el cliente envía un mensaje
de solicitud, incluye la marca de tiempo actual t1 según el reloj del cliente. Cuando el servidor recibe la solicitud, y antes de procesarla,
el servidor registra la marca de tiempo actual t2 de acuerdo con el reloj del servidor. Cuando el servidor envía su respuesta, repite el
valor t1 de la solicitud y también incluye la marca de tiempo de recepción del servidor t2 y la marca de tiempo de respuesta del
servidor t3 en la respuesta. Finalmente, cuando el cliente recibe la respuesta, registra la marca de tiempo actual t4 de acuerdo con el
reloj del cliente.
Podemos determinar el tiempo que los mensajes tardaron en viajar a través de la red calculando el tiempo de ida y vuelta desde
el punto de vista del cliente (t4 ÿ t1) y restando el tiempo de procesamiento en el servidor (t3 ÿ t2). Luego estimamos que la latencia
de la red unidireccional es la mitad del retraso total de la red. Por lo tanto, cuando la respuesta llega al cliente, podemos estimar que
el reloj del servidor se habrá movido a t3 más la latencia de la red unidireccional. Luego restamos la hora actual del cliente t4 de la
hora estimada del servidor para obtener el desfase estimado entre los dos relojes.
Esta estimación depende de la suposición de que la latencia de la red es aproximadamente la misma en ambas direcciones.
Esta suposición es probablemente cierta si la latencia está dominada por la distancia geográfica entre
28
Machine Translated by Google
cliente y servidor. Sin embargo, si el tiempo de espera en la red es un factor significativo en la latencia (por ejemplo, si el
enlace de red de un nodo está muy cargado mientras que el enlace del otro nodo tiene suficiente capacidad), entonces
podría haber una gran diferencia entre la latencia de solicitud y respuesta. Desafortunadamente, la mayoría de las redes no
dan a los nodos ninguna indicación de la latencia real que ha experimentado un paquete en particular.
t1 solicitud: t1
t2
respuesta: (t1, t2, t3) t3
t4
D
Tiempo estimado del servidor cuando el cliente recibe la respuesta: t3 + 2
ÿ t2 - t1 + t3 - t4
Sesgo de reloj estimado: ÿ = t3 + ÿ t4 = 2
2
Diapositiva 55
Ejercicio 5. ¿Cuál es el error máximo posible en la estimación de sesgo del cliente NTP con respecto a un servidor en
particular, suponiendo que ambos nodos siguen correctamente el protocolo?
Una vez que NTP ha estimado el sesgo de reloj entre el cliente y el servidor, el siguiente paso es ajustar el reloj del
cliente para alinearlo con el servidor. El método utilizado para esto depende de la cantidad de sesgo. El cliente corrige las
pequeñas diferencias suavemente ajustando la velocidad del reloj para que funcione un poco más rápido o más lento según
sea necesario, lo que reduce gradualmente el sesgo en el transcurso de unos minutos. Este proceso se llama girar el reloj.
La diapositiva 57 muestra un ejemplo de rotación, en el que la frecuencia del reloj del cliente converge a la misma
velocidad que la del servidor, manteniendo a los dos sincronizados en unos pocos milisegundos. Por supuesto, la precisión
exacta lograda en un sistema en particular depende de las propiedades de tiempo de la red entre el cliente y el servidor.
Sin embargo, si el sesgo es mayor, la rotación tomaría demasiado tiempo, por lo que el cliente NTP establece su reloj
a la fuerza en la hora correcta estimada en función de la marca de tiempo del servidor. Esto se llama pisar el reloj. Cualquier
aplicación que esté mirando el reloj en el cliente verá que el tiempo salta repentinamente hacia adelante o hacia atrás.
Y finalmente, si el sesgo es muy grande (por defecto, más de unos 15 minutos), el cliente NTP puede decidir que algo
debe estar mal y negarse a ajustar el reloj, dejando el problema para que lo corrija un usuario u operador. Por esta razón,
cualquier sistema que dependa de la sincronización del reloj debe ser monitoreado cuidadosamente para detectar sesgos
de reloj: solo porque un nodo esté ejecutando NTP, eso no garantiza que su reloj sea correcto, ya que podría quedarse
atascado en un estado de pánico en el que se niega a ajustar el reloj.
Una vez que el cliente ha estimado el sesgo de reloj ÿ, necesita aplicar esa
corrección a su reloj.
¡Los sistemas que dependen de la sincronización del reloj necesitan monitorear el sesgo del reloj!
Diapositiva 56
29
Machine Translated by Google
http://www.ntp.org/ntpfaq/NTP-s-algo.htm
Diapositiva 57
El hecho de que los relojes puedan ser acelerados por NTP, es decir, movidos repentinamente hacia adelante o hacia atrás, tiene una
implicación importante para cualquier software que necesite medir el tiempo transcurrido. La diapositiva 58 muestra un ejemplo en Java, en el
que queremos medir el tiempo de ejecución de una función hacerAlgo(). Java tiene dos funciones principales para obtener la marca de tiempo
actual del reloj local del sistema operativo: currentTimeMillis() y nanoTime(). Además de la diferente resolución (milisegundos versus
nanosegundos), la diferencia clave entre los dos es cómo se comportan frente a los ajustes de reloj de NTP u otras fuentes.
currentTimeMillis() es un reloj de hora del día (también conocido como reloj de tiempo real) que devuelve el tiempo transcurrido desde un
punto de referencia fijo (en este caso, la época de Unix del 1 de enero de 1970). Cuando el cliente NTP avanza el reloj local, un reloj de hora
del día puede saltar. Por lo tanto, si usa un reloj de este tipo para medir el tiempo transcurrido, la diferencia resultante entre la marca de tiempo
final y la marca de tiempo de inicio puede ser mucho mayor que el tiempo transcurrido real (si el reloj se adelantó), o incluso puede ser negativa
(si el reloj se dio un paso atrás). Por lo tanto, este tipo de reloj no es adecuado para medir el tiempo transcurrido.
Por otro lado, nanoTime() es un reloj monótono, que no se ve afectado por los pasos de NTP: sigue contando los segundos transcurridos,
pero siempre avanza. Solo la velocidad a la que se mueve hacia adelante puede ajustarse mediante la rotación de NTP. Esto hace que un reloj
monótono sea mucho más robusto para medir el tiempo transcurrido.
La desventaja es que una marca de tiempo de un reloj monótono no tiene sentido por sí misma: mide el tiempo desde algún punto de referencia
arbitrario, como el tiempo desde que se inició esta computadora. Cuando se usa un reloj monótono, solo tiene sentido la diferencia entre dos
marcas de tiempo del mismo nodo. No tiene sentido comparar marcas de tiempo de reloj monótonas en diferentes nodos.
// MALO:
larga startTime = System.currentTimeMillis(); hacer algo();
long endTime = System.currentTimeMillis(); largo
transcurridoMillones = horafinalización - horainicial ; //
¡Millones transcurridos pueden ser negativos!
Diapositiva 58
La mayoría de los sistemas operativos y lenguajes de programación proporcionan tanto un reloj de la hora del día como un reloj monótono.
reloj, ya que ambos son útiles para diferentes propósitos.
30
Machine Translated by Google
Tiempo desde una fecha fija (por ejemplo, época del 1 de enero de 1970)
Yo Java: System.currentTimeMillis()
Yo Linux: clock_gettime(CLOCK_REALTIME)
Reloj monotónico:
System.nanoTime()
Yo Linux: clock_gettime(CLOCK_MONOTONIC)
Diapositiva 59
m1
m1
metro2 m2
C ve m2 primero, m1 segundo,
aunque lógicamente m1 sucedió antes que m2.
Diapositiva 60
¿Cómo puede C determinar el orden correcto en el que debe poner los mensajes? Un reloj monótono no funcionará
ya que sus marcas de tiempo no son comparables entre nodos. Un primer intento podría ser obtener una marca de tiempo
de un reloj de hora del día cada vez que un usuario desee enviar un mensaje y adjuntar esa marca de tiempo al mensaje.
En este escenario, podríamos esperar razonablemente que m2 tenga una marca de tiempo posterior a m1, ya que m2 es
una respuesta a m1 y, por lo tanto , m2 debe haber ocurrido después de m1.
Desafortunadamente, en un modelo de sistema parcialmente síncrono, esto no funciona de manera confiable. La
sincronización del reloj realizada por NTP y protocolos similares siempre deja cierta incertidumbre residual sobre
31
Machine Translated by Google
el sesgo exacto entre dos relojes, especialmente si la latencia de la red en las dos direcciones es asimétrica.
Por lo tanto, no podemos descartar el siguiente escenario: A envía m1 con la marca de tiempo t1 de acuerdo con el reloj de A.
Cuando B recibe m1, la marca de tiempo según el reloj de B es t2, donde t2 < t1, porque el reloj de A está ligeramente adelantado
al reloj de B. Por lo tanto, si ordenamos los mensajes en función de las marcas de tiempo de los relojes que marcan la hora del
día, es posible que nuevamente terminemos con un orden incorrecto.
t1 m1
m1
t2
metro2 m2
Diapositiva 61
Para formalizar lo que queremos decir con el orden "correcto" en este tipo de escenario, usamos la relación sucede antes
como se define en la diapositiva 62. Esta definición asume que cada nodo tiene solo un único hilo de ejecución, por lo que para
cualquier dos pasos de ejecución de un nodo, está claro cuál ocurrió primero. Más formalmente, asumimos que existe un orden
total estricto en los eventos que ocurren en el mismo nodo. Un proceso de subprocesos múltiples se puede modelar utilizando un
nodo separado para representar cada subproceso.
Luego, extendemos este orden a través de los nodos definiendo que un mensaje se envía antes de que se reciba ese mismo
mensaje (en otras palabras, descartamos el viaje en el tiempo: no es posible recibir un mensaje que aún no se ha enviado). Para
mayor comodidad, asumimos que cada mensaje enviado es único, por lo que cuando se recibe un mensaje, siempre sabemos
sin ambigüedades dónde y cuándo se envió ese mensaje. En la práctica, pueden existir mensajes duplicados, pero podemos
hacerlos únicos, por ejemplo, al incluir la ID del nodo emisor y un número de secuencia en cada mensaje.
Finalmente, tomamos la clausura transitiva, y el resultado es la relación sucede antes. Este es un orden parcial, lo que
significa que es posible que para algunos eventos a y b, ni a suceda antes de b, ni b suceda antes de a. En ese caso, llamamos
a y b concurrentes. Tenga en cuenta que aquí, "concurrente" no significa literalmente "al mismo tiempo", sino que a y b son
independientes en el sentido de que no hay una secuencia de mensajes que conduzcan de uno a otro.
Diapositiva 62
32
Machine Translated by Google
A B C
a
Y
B m1
C
D m2
F
Diapositiva 63
Ejercicio 6. Una relación R es de orden parcial estricto si es irreflexiva (@a. (a, a) ÿ R) y transitiva (ÿa, b, c. (a, b) ÿ R ÿ (b, c) ÿ R
=ÿ (a, c) ÿ R). (Estas dos condiciones también implican que R es asimétrica, es decir, que ÿa, b. (a, b) ÿ R =ÿ (b, a) ÿ/ R.)
Demuestre que la relación que ocurre antes es un orden parcial estricto.
Puede suponer que dos nodos cualesquiera están separados por una distancia distinta de cero, así como el principio físico de que
la información no puede viajar más rápido que la velocidad de la luz.
Ejercicio 7. Muestre que para dos eventos cualesquiera a y b, exactamente una de las tres afirmaciones siguientes debe ser
verdadera: ya sea a ÿ b, o b ÿ a, o ak b.
La relación sucede antes es una forma de razonar sobre la causalidad en sistemas distribuidos. La causalidad considera si la
información pudo haber fluido de un evento a otro y, por lo tanto, si un evento pudo haber influido en otro. En el ejemplo de la
diapositiva 60, m2 (“¡Oh, no, no lo es!”) es una respuesta a m1 (“¡La luna está hecha de queso!”), por lo que m1 influyó en m2. Si
un evento realmente "causó" otro es una pregunta filosófica que no necesitamos responder ahora; lo que importa para nuestros
propósitos es que el remitente de m2 ya había recibido m1 en el momento de enviar m2.
Causalidad
Tomado de la física (relatividad).
I Cuando a ÿ b, entonces a podría haber causado b.
I Cuando akb, sabemos que a no puede haber causado b.
La relación sucede antes de codificar la causalidad potencial.
distancia en el espacio
a B hora
luz de un luz de b
C
La noción de causalidad se toma prestada de la física, donde generalmente se cree que no es posible que la información viaje
más rápido que la velocidad de la luz. Por lo tanto, si tiene dos eventos a y b que ocurren lo suficientemente separados en el
espacio, pero juntos en el tiempo, entonces es imposible que una señal enviada desde a llegue a la ubicación de b antes del
evento b, y viceversa. Por lo tanto, a y b deben estar causalmente no relacionados.
Un evento c que esté lo suficientemente cerca en el espacio de a, y lo suficientemente largo después de a en el tiempo, estará
dentro del cono de luz de a: es decir, es posible que una señal de a llegue a c, y por lo tanto a podría influir en c. En los sistemas
distribuidos, normalmente trabajamos con mensajes en una red en lugar de haces de luz, pero el principio es muy similar.
33
Machine Translated by Google
Marcas de tiempo físicas: útiles para muchas cosas, pero pueden ser
inconsistentes con la causalidad.
Diapositiva 65
El primer tipo de reloj lógico que examinaremos es el reloj de Lamport, introducido por Lamport [1978] en
uno de los artículos seminales de la computación distribuida.
Diapositiva 66
34
Machine Translated by Google
Diapositiva 67
Una marca de tiempo de Lamport es esencialmente un número entero que cuenta el número de eventos que han ocurrido.
Como tal, no tiene relación directa con el tiempo físico. En cada nodo, el tiempo aumenta porque el número entero se incrementa
en cada evento. El algoritmo asume un modelo de parada de bloqueo (o un modelo de recuperación de bloqueo si la marca de
tiempo se mantiene en un almacenamiento estable, es decir, en el disco).
Cuando se envía un mensaje a través de la red, el remitente adjunta su marca de tiempo actual de Lamport a ese mensaje.
En el ejemplo de la diapositiva 68, t = 2 está unido a m1 y t = 4 está unido a m2. Cuando el destinatario recibe un mensaje,
adelanta su reloj Lamport local a la marca de tiempo del mensaje más uno; si el reloj del destinatario ya está adelantado con
respecto a la marca de tiempo del mensaje, solo se incrementa.
Las marcas de tiempo de Lamport tienen la propiedad de que si a sucedió antes de b, entonces b siempre tiene una marca
de tiempo mayor que a; en otras palabras, las marcas de tiempo son consistentes con la causalidad. Sin embargo, lo contrario
no es cierto: en general, si b tiene una marca de tiempo mayor que a, sabemos que b 6ÿ a, pero no sabemos si es el caso de
que a ÿ b o de que ak b.
También es posible que dos eventos diferentes tengan la misma marca de tiempo. En el ejemplo de la diapositiva 68, el
tercer evento en el nodo A y el primer evento en el nodo B tienen una marca de tiempo de 3. Si necesitamos una marca de
tiempo única para cada evento, cada marca de tiempo puede extenderse con el nombre o identificador del nodo. en que se
produjo dicho hecho. Dentro del alcance de un solo nodo, a cada evento se le asigna una marca de tiempo única; por lo tanto,
asumiendo que cada nodo tiene un nombre único, la combinación de marca de tiempo y nombre de nodo es globalmente única
(en todos los nodos).
A B C
(1, A)
(1, C)
(2, A) (2, m1)
(3, B)
(3, A) (4, B)
(4, m2) (5, C)
Recuerde que la relación sucede antes es un orden parcial (Diapositiva 62). Usando las marcas de tiempo de Lamport
podemos extender este pedido parcial a un pedido total. Usamos el orden lexicográfico sobre pares (marca de tiempo, nombre
de nodo): es decir, primero comparamos las marcas de tiempo y, si son iguales, rompemos los vínculos comparando los nombres
de los nodos.
Esta relación ÿ pone todos los eventos en un orden lineal: para dos eventos cualesquiera a 6= b tenemos a ÿ b o
35
Machine Translated by Google
b ÿ a. Es un orden causal: es decir, siempre que a ÿ b tenemos a ÿ b. En otras palabras, ÿ es una extensión lineal del orden
parcial ÿ. Sin embargo, si akb podríamos tener a ÿ b o b ÿ a, por lo que el algoritmo determina arbitrariamente el orden de los
dos eventos.
Ejercicio 8. Dada la secuencia de mensajes en la siguiente ejecución, muestre las marcas de tiempo de Lamport en cada evento
de envío o recepción.
A B C D
m1
m2
m3
m4
m5
m6
m7
m8
m9
Ejercicio 9. Demostrar que el orden total ÿ utilizando las marcas de tiempo de Lamport es un orden causal.
Dadas las marcas de tiempo de Lamport de dos eventos, en general no es posible saber si esos eventos son concurrentes
o si uno sucedió antes que el otro. Si queremos detectar cuándo los eventos son concurrentes, necesitamos un tipo diferente
de tiempo lógico: un reloj vectorial.
Mientras que las marcas de tiempo de Lamport son solo un número entero (posiblemente con un nombre de nodo adjunto),
las marcas de tiempo de vector son una lista de números enteros, uno para cada nodo en el sistema. Por convención, si
ponemos los n nodos en un vector hN1, N2, . . . , Nni, entonces un vector timestamp es un vector similar ht1, t2, . . . , tni donde
ti es la entrada correspondiente al nodo Ni . Concretamente, ti es el número de eventos que se sabe que han ocurrido en el
nodo Ni .
En un vector T = ht1, t2, . . . , tni nos referimos al elemento ti como T[i], como un índice en una matriz.
Relojes vectoriales
Dadas las marcas de tiempo de Lamport L(a) y L(b) con L(a) < L(b) no
podemos decir si a ÿ b o ak b.
Diapositiva 69
Aparte de la diferencia entre un escalar y un vector, el algoritmo del reloj vectorial es muy similar a un reloj Lamport
(compare la diapositiva 66 y la diapositiva 70). Un nodo inicializa su reloj vectorial para que contenga un cero, incrementa la i-
el sistema. Cada vez que ocurre un evento en la propia entrada del nodo Ni ) en su reloj , ésima
vectorial.
entrada
(En la(es
práctica,
para cada
estenodo
vector
ena
menudo se implementa como un mapa de ID de nodo a números enteros en lugar de una matriz de números enteros). Cuando
se envía un mensaje a través de la red, la marca de tiempo del vector actual del remitente se adjunta al mensaje. Finalmente,
cuando se recibe un mensaje, el destinatario fusiona la marca de tiempo del vector en el mensaje con su marca de tiempo local
tomando el máximo de elementos de los dos vectores, y luego el destinatario incrementa su propia entrada.
36
Machine Translated by Google
Diapositiva 70
La diapositiva 71 muestra un ejemplo de este algoritmo en acción. Tenga en cuenta que cuando C recibe el mensaje m2 de B, la
entrada del vector para A también se actualiza a 2 porque este evento tiene una dependencia causal indirecta de los dos eventos que
ocurrieron en A. De esta manera, las marcas de tiempo del vector reflejan la transitividad de los eventos. -antes de la relación.
A B C
h1, 0, 0i
h0, 0, 1i
h2, 0, 0i (h2, 0, 0i, m1)
h2, 1, 0i
h3, 0, 0i h2, 2, 0i
(h2, 2, 0i, m2) h2, 2, 2i
Por ejemplo, h2, 2, 0i representa los dos primeros eventos de A, los dos
primeros eventos de B y ningún evento de C.
Diapositiva 71
si y si T 6ÿ T y T
0 0 0
yo t k t 6ÿT
Diapositiva 72
Luego definimos un orden parcial sobre las marcas de tiempo del vector como se muestra en la diapositiva 72. Decimos que un vector
37
Machine Translated by Google
es menor o igual a otro vector si cada elemento del primer vector es menor o igual al elemento correspondiente del segundo
vector. Un vector es estrictamente menor que otro vector si son menores o iguales y si difieren en al menos un elemento.
Sin embargo, dos vectores son incomparables si un vector tiene un valor mayor en un elemento y el otro tiene un valor mayor
en un elemento diferente.
0
Por ejemplo, T = h2, 2, 0i y T = h0, 0, 1i son incomparables porque T[1] > T0 [1] pero T[3] < T0 [3].
El orden parcial sobre marcas de tiempo vectoriales corresponde exactamente al orden parcial definido por la relación
antes de que suceda. Por lo tanto, el algoritmo de reloj vectorial nos proporciona un mecanismo para calcular la relación
antes de que ocurra en la práctica.
Ejercicio 10. Dada la misma secuencia de mensajes que en el ejercicio 8, muestre los relojes vectoriales en cada evento de
envío o recepción.
Ejercicio 11. Utilizando el Lamport y las marcas de tiempo vectoriales calculadas en los Ejercicios 8 y 10, indique si se puede
determinar que los siguientes eventos tienen o no una relación de ocurrencia anterior.
Ejercicio 12. Hemos visto varios tipos de relojes físicos (relojes horarios con NTP, relojes monotónicos) y relojes lógicos.
Para cada uno de los siguientes usos del tiempo, explique qué tipo de reloj es el más adecuado: programación de procesos;
E/S; consistencia del sistema de archivos distribuido; validez del certificado criptográfico; actualizaciones simultáneas de la
base de datos.
Esto completa nuestra discusión sobre el tiempo lógico. Hemos visto dos algoritmos clave: los relojes Lamport y los
relojes vectoriales, uno que proporciona un orden total y el otro que captura el orden parcial de lo que sucede antes.
Se han propuesto varias otras construcciones: por ejemplo, hay relojes híbridos que combinan algunas de las propiedades
de los relojes lógicos y físicos [Kulkarni et al., 2014].
Diapositiva 73
38
Machine Translated by Google
Antes de entrar en detalles, debemos aclarar algunos términos. Cuando una aplicación quiere enviar un mensaje a todos
los nodos del grupo, utiliza un algoritmo para transmitirlo. Para que esto suceda, el algoritmo de transmisión envía algunos
mensajes a otros nodos a través de enlaces punto a punto, y otro nodo recibe dicho mensaje cuando llega a través del enlace
punto a punto. Finalmente, el algoritmo de difusión puede entregar el mensaje a la aplicación. Como veremos en breve, a veces
hay un retraso entre el momento en que se recibe un mensaje y cuando se entrega.
Solicitud Solicitud
transmisión entregar
La red
Diapositiva 74
Examinaremos tres formas diferentes de transmisión. Todos estos son confiables: cada mensaje finalmente se entrega a
cada nodo sin fallas, sin garantías de tiempo. Sin embargo, difieren en cuanto al orden en que se pueden entregar los mensajes
en cada nodo. Resulta que esta diferencia en el orden tiene consecuencias muy fundamentales para los algoritmos que
implementan la transmisión.
Transmisión FIFO:
Si m1 y m2 son transmitidos por el mismo nodo, y broadcast(m1) ÿ
broadcast(m2), entonces m1 debe entregarse antes que m2
Transmisión causal:
Diapositiva 75
El tipo de transmisión más débil se llama transmisión FIFO, que está estrechamente relacionada con los enlaces FIFO
(consulte el Ejercicio 2). En este modelo, los mensajes enviados por el mismo nodo se entregan en el orden en que fueron enviados.
Por ejemplo, en la diapositiva 76, m1 debe entregarse antes que m3, ya que ambos fueron enviados por A. Sin embargo, m2
puede entregarse en cualquier momento antes, entre o después de m1 y m3.
Otro detalle sobre estos protocolos de transmisión: asumimos que cada vez que un nodo transmite un mensaje, también
se entrega ese mensaje a sí mismo (representado como una flecha de bucle invertido en la diapositiva 76). Esto puede parecer
innecesario al principio; después de todo, ¡un nodo sabe qué mensajes ha transmitido él mismo! – pero necesitaremos esto
para la transmisión de orden total.
39
Machine Translated by Google
transmisión FIFO
A B C
m1 m1
m1
m2 m2 m2
m3 m3
m3
La ejecución de ejemplo en la diapositiva 76 es una transmisión FIFO válida, pero viola la causalidad: el nodo C entrega m2
antes que m1, aunque B transmite m2 después de entregar m1. La transmisión causal proporciona una propiedad de ordenación
más estricta que la transmisión FIFO. Como sugiere el nombre, garantiza que los mensajes se entreguen en orden causal: es
decir, si la transmisión de un mensaje ocurrió antes de la transmisión de otro mensaje, todos los nodos deben entregar esos
dos mensajes en ese orden. Si se transmiten dos mensajes al mismo tiempo, un nodo puede entregarlos en cualquier orden.
En el ejemplo de la diapositiva 76, si el nodo C recibe m2 antes que m1, el algoritmo de transmisión en C tendrá que retener
(retrasar o amortiguar) m2 hasta que se haya entregado m1 , para garantizar que los mensajes se entreguen en orden causal.
En el ejemplo de la diapositiva 77, los mensajes m2 y m3 se transmiten simultáneamente. Los nodos A y C entregan mensajes
en el orden m1, m3, m2, mientras que el nodo B los entrega en el orden m1, m2, m3.
Cualquiera de estas órdenes de entrega es aceptable, ya que ambas son consistentes con la causalidad.
Transmisión causal
A B C
m1 m1
m1
m3 m2 m2
metro3
m3
El tercer tipo de transmisión es la transmisión de orden total, a veces también conocida como transmisión atómica.
Si bien FIFO y la transmisión causal permiten que diferentes nodos entreguen mensajes en diferentes órdenes, la transmisión
de orden total impone la coherencia entre los nodos, lo que garantiza que todos los nodos entreguen mensajes en el mismo
orden. El orden de entrega preciso no está definido, siempre que sea el mismo en todos los nodos.
Las diapositivas 78 y 79 muestran dos ejecuciones de ejemplo de transmisión de orden total. En la diapositiva 78, los tres
nodos entregan los mensajes en el orden m1, m2, m3, mientras que en la diapositiva 79, los tres nodos entregan los mensajes
en el orden m1, m3, m2. Cualquiera de estas ejecuciones es válida, siempre que los nodos estén de acuerdo.
Al igual que con la transmisión causal, es posible que los nodos deban retener mensajes, esperando otros mensajes que
deben entregarse primero. Por ejemplo, el nodo C podría recibir los mensajes m2 y m3 en cualquier orden. Si el algoritmo ha
determinado que m3 debe entregarse antes que m2, pero si el nodo C recibe m2 primero, entonces C deberá retener m2 hasta
que se haya recibido m3 .
Otro detalle importante se puede ver en estos diagramas: en el caso de FIFO y transmisión causal,
40
Machine Translated by Google
cuando un nodo transmite un mensaje, puede entregar ese mensaje a sí mismo de inmediato, sin tener que
esperar la comunicación con cualquier otro nodo. Esto ya no es cierto en la transmisión de orden total: por ejemplo,
en la diapositiva 78, es necesario entregar m2 antes que m3, por lo que la entrega de m3 del nodo A a sí mismo debe esperar hasta después
A ha recibido m2 de B. Del mismo modo, en la diapositiva 79, la entrega de m2 del nodo B a sí mismo debe esperar por m3.
A B C
m1 m1
m1
m3 m2 m2
m3
m3
A B C
m1 m1
m1
m2 m2
m3
m3
m3
m2
Orden total
Transmisión causal
transmisión
transmisión FIFO
Transmisión confiable
Mejor esfuerzo
transmisión
= más fuerte que
Diapositiva 80
41
Machine Translated by Google
Finalmente, la transmisión de orden total FIFO es como la transmisión de orden total, pero con el requisito FIFO
adicional de que todos los mensajes transmitidos por el mismo nodo se entregan en el orden en que fueron enviados. Los
ejemplos de las diapositivas 78 y 79 son, de hecho, ejecuciones de transmisión de orden total FIFO válidas, ya que m1 se
entrega antes que m3 en ambos.
Ejercicio 13. Demostrar que la transmisión causal también satisface los requisitos de la transmisión FIFO, y que la transmisión
de orden total FIFO también satisface los requisitos de la transmisión causal.
I Problema: el nodo puede bloquearse antes de que se entreguen todos los mensajes
A B C
m1
m1
Diapositiva 81
Para mejorar la confiabilidad, podemos contar con la ayuda de los otros nodos. Por ejemplo, podríamos decir que la
primera vez que un nodo recibe un mensaje en particular, lo reenvía a todos los demás nodos (esto se denomina transmisión
confiable ansiosa). Este algoritmo garantiza que incluso si algunos nodos fallan, todos los nodos restantes (no defectuosos)
recibirán todos los mensajes. Sin embargo, este algoritmo es bastante ineficiente: en ausencia de fallas, cada mensaje se
envía O (n veces). Esto significa2 )que
veces
utiliza
en un
unagrupo
gran de
cantidad
n nodos,
de tráfico
ya quedecada
rednodo
redundante.
recibirá todos los mensajes n ÿ 1
A B C
m1
m1
m1 m1
m1
m1
Diapositiva 82
42
Machine Translated by Google
Se han desarrollado muchas variantes de este algoritmo, optimizando en varias dimensiones, como la tolerancia a fallas, el
tiempo hasta que todos los nodos reciben un mensaje y el ancho de banda de red utilizado. Una familia particularmente común de
algoritmos de transmisión son los protocolos de chismes (también conocidos como protocolos epidémicos). En estos protocolos, un
nodo que desea emitir un mensaje lo envía a un pequeño número fijo de nodos que se eligen aleatoriamente. Al recibir un mensaje
por primera vez, un nodo lo reenvía a un número fijo de nodos elegidos al azar. Esto se asemeja a la forma en que los chismes, los
rumores o una enfermedad infecciosa se pueden propagar a través de una población.
Los protocolos de chismes no garantizan estrictamente que todos los nodos recibirán un mensaje: es posible que en la
selección aleatoria de nodos, siempre se omita algún nodo. Sin embargo, si los parámetros del algoritmo se eligen adecuadamente,
la probabilidad de que un mensaje no se entregue puede ser muy pequeña. Los protocolos de chismes son atractivos porque, con
los parámetros correctos, son muy resistentes a la pérdida de mensajes y los bloqueos de nodos, a la vez que siguen siendo
eficientes.
Protocolos de chismes
Diapositiva 83
Ahora que tenemos una transmisión confiable (usando una transmisión confiable ansiosa o un protocolo de chismes), podemos
construya transmisiones FIFO, causales o de orden total sobre él. Comencemos con la transmisión FIFO.
en la inicialización
hacer sendSeq := 0; entregado := h0, 0, . . . , 0i; búfer: = {} final en
Diapositiva 84
Cada mensaje de difusión FIFO enviado por el nodo Ni se etiqueta con el número de nodo emisor i y un número de secuencia
que es 0 para el primer mensaje enviado por Ni 1 para el segundo mensaje,,y consiste
así sucesivamente.
en el número
El de
estado
secuencia
local en
sendSeq
cada nodo
(contando la cantidad de mensajes transmitidos por este nodo), entregado (un vector con una entrada por nodo, contando la
cantidad de mensajes de cada remitente que este nodo ha entregado), y búfer (un búfer para retener mensajes hasta que estén
listos para ser entregados). El algoritmo busca mensajes de cualquier remitente que coincidan con el siguiente número de secuencia
esperado y luego incrementa ese número, asegurándose de que los mensajes de cada remitente en particular se entreguen en
orden creciente de número de secuencia.
43
Machine Translated by Google
Diapositiva 85
El algoritmo de transmisión causal es algo similar a la transmisión FIFO; en lugar de adjuntar un número de secuencia
a cada mensaje que se transmite, adjuntamos un vector de números enteros. Este algoritmo a veces se denomina algoritmo
de reloj vectorial, aunque es bastante diferente del algoritmo de la diapositiva 70. En el algoritmo de reloj vectorial de la
diapositiva 70 , los elementos vectoriales cuentan el número de eventos que han ocurrido en cada nodo, mientras que en el
algoritmo causal algoritmo de difusión, los elementos del vector cuentan el número de mensajes de cada remitente que se
han entregado.
El estado local en cada nodo consta de sendSeq, entregado y búfer, que tienen el mismo significado que en el algoritmo
de transmisión FIFO. Cuando un nodo quiere transmitir un mensaje, adjuntamos el número de nodo emisor i y deps, un
vector que indica las dependencias causales de ese mensaje. Construimos deps tomando una copia de delivered, el vector
que cuenta cuántos mensajes de cada remitente se han entregado en este nodo. Esto indica que todos los mensajes que
se han entregado localmente antes de esta transmisión deben aparecer antes del mensaje de transmisión en el orden
causal. Luego, actualizamos el elemento propio del nodo de envío de este vector para que sea igual a sendSeq, lo que
garantiza que cada mensaje transmitido por este nodo tenga una dependencia causal con el mensaje anterior transmitido
por el mismo nodo.
Al recibir un mensaje, el algoritmo primero lo agrega al búfer como en la transmisión FIFO y luego busca en el búfer
cualquier mensaje que esté listo para ser entregado. La comparación deps ÿ entregado utiliza el operador ÿ en los vectores
definidos en la diapositiva 72. Esta comparación es verdadera si este nodo ya entregó todos los mensajes que deben
preceder a este mensaje en el orden causal. Cualquier mensaje que esté causalmente listo se entrega a la aplicación y se
elimina del búfer, y se incrementa el elemento apropiado del vector entregado.
Diapositiva 86
Finalmente, la transmisión de orden total (y la transmisión de orden total FIFO) son más complicadas. En la diapositiva
86 se describen dos enfoques simples , uno basado en un nodo líder designado y un algoritmo sin líder que utiliza marcas
de tiempo de Lamport. Sin embargo, ninguno de estos enfoques es tolerante a fallas: en ambos casos, el bloqueo de un solo
44
Machine Translated by Google
node puede impedir que todos los demás nodos puedan entregar mensajes. En el enfoque de líder único, el líder es un único
punto de falla. Volveremos al problema de la transmisión de orden total tolerante a fallas en la lección 6.
Ejercicio 14. Proporcione un pseudocódigo para un algoritmo que implemente la transmisión de orden total FIFO utilizando
relojes de puerto Lam. Puede suponer que cada nodo tiene una ID única y que se conoce el conjunto de todas las ID de nodo.
Suponga además que la red subyacente proporciona una transmisión FIFO confiable. [2020 Documento 5 Pregunta 8]
5 replicación
Ahora abordaremos el problema de la replicación, lo que significa mantener una copia de los mismos datos en varios nodos,
cada uno de los cuales se denomina réplica. La replicación es una característica estándar de muchas bases de datos
distribuidas, sistemas de archivos y otros sistemas de almacenamiento. Es uno de los principales mecanismos que tenemos
para lograr la tolerancia a fallas: si una réplica falla, podemos continuar accediendo a las copias de los datos en otras réplicas.
I Conservar una copia de los mismos datos en varios nodos I Bases de datos,
I Un nodo que tiene una copia de los datos se denomina réplica . I Si algunas réplicas
son defectuosas, otras siguen estando accesibles . I Distribuya la carga entre muchas
I RAID tiene un solo controlador; En un sistema distribuido, cada nodo actúa de forma
independiente.
Diapositiva 87
cliente
45
Machine Translated by Google
Consideremos como ejemplo el acto de "gustar" una actualización de estado en una red social. Cuando hace clic en el botón
"me gusta", el hecho de que le haya gustado y la cantidad de personas a las que les ha gustado deben almacenarse en algún lugar
para que pueda mostrárselo a usted y a otros usuarios. Esto suele ocurrir en una base de datos en los servidores de la red social.
Podemos considerar los datos almacenados en una base de datos como su estado.
Una solicitud para actualizar la base de datos puede perderse en la red, o puede perderse un reconocimiento de que se ha
realizado una actualización. Como de costumbre, podemos mejorar la confiabilidad volviendo a intentar la solicitud. Sin embargo,
si no tenemos cuidado, el reintento podría provocar que la solicitud se procese varias veces, lo que provocaría un estado incorrecto
en la base de datos.
Diapositiva 89
Para que no piense que se trata de un problema puramente hipotético, considere la diapositiva 89, una captura de pantalla
genuina (¡lo prometo!) de Twitter que hice en 2014, que muestra el perfil de un usuario que aparentemente sigue a un número
negativo de personas. No tengo una idea de las funciones internas de Twitter para saber exactamente qué sucedió aquí, pero
supongo que esta persona solía seguir a varias personas, luego las dejó de seguir y, debido a un problema de red durante el
proceso de dejar de seguir, la disminución del contador de seguimiento se volvió a intentar, lo que resultó en más decrementos de
los que el usuario estaba siguiendo originalmente.
Si seguir a un número negativo de personas parece un problema trivial, entonces, en lugar de disminuir un contador de
seguimiento, considere el acto de deducir £ 1,000 del saldo de su cuenta bancaria. La operación de la base de datos es
esencialmente la misma, pero realizar esta operación demasiadas veces tiene el potencial de hacerte sentir bastante infeliz.
Una forma de evitar que una actualización surta efecto varias veces es desduplicar las solicitudes. Sin embargo, en un modelo
de sistema de recuperación de fallas, esto requiere almacenar solicitudes (o algunos metadatos sobre solicitudes, como un reloj
vectorial) en un almacenamiento estable, para que los duplicados puedan detectarse con precisión incluso después de una falla.
Una alternativa al registro de solicitudes de deduplicación es hacer que las solicitudes sean idempotentes.
idempotencia
likeSet ÿ {userID}
Diapositiva 90
46
Machine Translated by Google
Incrementar un contador no es idempotente, pero agregar un elemento a un conjunto sí lo es. Por lo tanto, si se requiere un
contador (como en el número de Me gusta), podría ser mejor mantener el conjunto de elementos en la base de datos y derivar el
valor del contador del conjunto calculando su cardinalidad.
Se puede volver a intentar una actualización idempotente de forma segura, porque realizarla varias veces tiene el mismo efecto
que realizarla una vez. La idempotencia permite que una actualización tenga una semántica exactamente una vez: es decir, la
actualización se puede aplicar varias veces, pero el efecto es el mismo que si se hubiera aplicado exactamente una vez.
La idempotencia es una propiedad muy útil en los sistemas prácticos y, a menudo, se encuentra en el contexto de RPC (Sección
1.3), donde los reintentos suelen ser inevitables.
Sin embargo, la idempotencia tiene una limitación que se hace evidente cuando hay varias actualizaciones en curso. En la
diapositiva 91, el cliente 1 agrega una identificación de usuario al conjunto de Me gusta de una publicación, pero se pierde el
reconocimiento. El cliente 2 lee el conjunto de Me gusta de la base de datos (incluida la ID de usuario agregada por el cliente 1) y
luego realiza una solicitud para eliminar la ID de usuario nuevamente. Mientras tanto, el cliente 1 vuelve a intentar su solicitud, sin
darse cuenta de la actualización realizada por el cliente 2. Por lo tanto, el reintento tiene el efecto de agregar nuevamente la ID de usuario al conjunto.
Esto es inesperado ya que el cliente 2 observó el cambio del cliente 1, por lo que la eliminación ocurrió causalmente después de
agregar el elemento del conjunto y, por lo tanto, podríamos esperar que en el estado final, la ID de usuario no esté presente en el
conjunto. En este caso, el hecho de que agregar un elemento a un conjunto sea idempotente no es suficiente para que el reintento
sea seguro.
cliente 1 cliente 2
f: agregar como
Pobre de mí
conjunto de me gusta
g: a diferencia
Pobre de mí
Diapositiva 91
Un problema similar ocurre en la diapositiva 92, en la que tenemos dos réplicas. En el primer escenario, un cliente primero
agrega x a ambas réplicas de la base de datos, luego intenta eliminar x nuevamente de ambas. Sin embargo, la solicitud de
eliminación a la réplica B se pierde y el cliente se bloquea antes de que pueda volver a intentarlo. En el segundo escenario, un
cliente intenta agregar x a ambas réplicas, pero la solicitud a la réplica A se pierde y nuevamente el cliente falla.
agregar (x)
cliente
agregar
A B
(x)
sumar (x)
Diapositiva 92
En ambos escenarios, el resultado es el mismo: x está presente en la réplica B y está ausente en la réplica A.
47
Machine Translated by Google
Sin embargo, el efecto deseado es diferente: en el primer escenario, el cliente quería que x se eliminara de ambas réplicas, mientras
que en el segundo escenario, el cliente quería que x estuviera presente en ambas réplicas. Cuando las dos réplicas reconcilien sus
estados inconsistentes, queremos que ambas terminen en el estado que el cliente pretendía. Sin embargo, esto no es posible si las
réplicas no pueden distinguir entre estos dos escenarios.
Para solucionar este problema, podemos hacer dos cosas. Primero, adjuntamos una marca de tiempo lógica a cada operación
de actualización y almacenamos esa marca de tiempo en la base de datos como parte de los datos escritos por la actualización. En
segundo lugar, cuando se nos pide que eliminemos un registro de la base de datos, en realidad no lo eliminamos, sino que escribimos
un tipo especial de actualización (llamada lápida) que lo marca como eliminado. En la diapositiva 93, los registros que contienen
falsos son lápidas.
cliente
A B
(t1,
t1
sumar(x)) {x 7ÿ (t1, verdadero)}
{x 7ÿ (t1, verdadero)}
(t1, sumar(x)) (t2,
t2
quitar(x)) (t2, quitar(x))
{x 7ÿ (t2, falso)}
Diapositiva 93
En muchos sistemas replicados, las réplicas ejecutan un protocolo para detectar y reconciliar cualquier diferencia (esto se
denomina anti-entropía), de modo que las réplicas eventualmente contengan copias consistentes de los mismos datos. Gracias a las
lápidas, el proceso de antientropía puede diferenciar entre un registro que se ha eliminado y un registro que aún no se ha creado. Y
gracias a las marcas de tiempo, podemos saber qué versión de un registro es más antigua y cuál es más nueva. El proceso de anti-
entropía luego mantiene el registro más nuevo y descarta el más antiguo.
Este enfoque también ayuda a abordar el problema de la diapositiva 91: una solicitud reintentada tiene la misma marca de
tiempo que la solicitud original, por lo que un reintento no sobrescribirá un valor escrito por una solicitud causalmente posterior con
una marca de tiempo mayor.
Reconciliación de réplicas
A B
reconciliar estado
{x 7ÿ (t2, falso)} {x 7ÿ (t1, verdadero)}
(anti-entropía)
Diapositiva 94
La técnica de adjuntar una marca de tiempo a cada actualización también es útil para manejar actualizaciones simultáneas.
En la diapositiva 95, el cliente 1 quiere establecer la clave x en el valor v1 (con marca de tiempo t1), mientras que el cliente 2 quiere
establecer la misma clave x en el valor v2 (con marca de tiempo t2). La réplica A recibe v2 primero y v1 segundo, mientras que la
réplica B recibe las actualizaciones en el orden opuesto. Para garantizar que ambas réplicas terminen en el mismo estado, no nos
basamos en el orden en que reciben las solicitudes, sino en el orden de sus marcas de tiempo.
48
Machine Translated by Google
cliente 1 cliente 2
A B
Diapositiva 95
Los detalles de este enfoque dependen del tipo de marcas de tiempo utilizadas. Si usamos relojes Lamport (con el orden
total definido en la diapositiva 68), se ordenarán arbitrariamente dos actualizaciones simultáneas, dependiendo de cómo se
asignen las marcas de tiempo. En este caso, obtenemos lo que se conoce como la semántica del último escritor gana (LWW): la
actualización con la marca de tiempo más grande entra en vigor y se descartan las actualizaciones simultáneas con marcas de
tiempo más bajas para la misma clave. Es fácil trabajar con este enfoque, pero implica la pérdida de datos cuando se realizan
varias actualizaciones al mismo tiempo. Si esto es un problema o no, depende de la aplicación: en algunos sistemas, descartar
actualizaciones concurrentes está bien.
Cuando descartar actualizaciones concurrentes no es aceptable, necesitamos usar un tipo de marca de tiempo que nos
permita detectar cuándo ocurren actualizaciones concurrentes, como relojes vectoriales. Con tales marcas de tiempo parcialmente
ordenadas, podemos saber cuándo un nuevo valor debe sobrescribir un valor anterior (cuando la actualización anterior ocurrió
antes de la nueva actualización), y cuando varias actualizaciones son simultáneas, podemos mantener todos los valores escritos
simultáneamente. Estos valores escritos simultáneamente se denominan conflictos o, a veces, hermanos. La aplicación puede
luego fusionar los conflictos en un solo valor, como se discutió en la Lección 8.
Una desventaja de los relojes vectoriales es que pueden volverse costosos: cada cliente necesita una entrada en el vector,
y en sistemas con una gran cantidad de clientes (o donde los clientes asumen una nueva identidad cada vez que se reinician),
estos vectores pueden llegar a ser grandes. potencialmente ocupando más espacio que los propios datos.
Otros tipos de relojes lógicos, como los vectores de versión punteada [Pregui¸ca et al., 2010], se han desarrollado para optimizar
este tipo de sistema.
Ejercicio 15. Apache Cassandra, una base de datos distribuida ampliamente utilizada, utiliza un enfoque de replicación similar al
que se describe aquí. Sin embargo, utiliza marcas de tiempo físicas en lugar de marcas de tiempo lógicas, como se explica aquí:
https://www.datastax.com/ blog/ 2013/ 09/why-cassandra-doesnt-need-vector-clocks. Escriba una crítica de esta publicación de
blog. ¿Qué opinas de sus argumentos y por qué? ¿Qué hechos faltan en él? ¿Qué recomendación le harías a alguien que esté
considerando usar Cassandra?
5.2 Quórumes
Como se discutió al comienzo de esta lección, la replicación es útil ya que nos permite mejorar la confiabilidad de un sistema:
cuando una réplica no está disponible, las réplicas restantes pueden continuar procesando solicitudes.
La falta de disponibilidad puede deberse a un nodo defectuoso (p. ej., un bloqueo o una falla de hardware), a una partición de la
red (incapacidad para acceder a un nodo a través de la red) o a un mantenimiento planificado (p. ej., reiniciar un nodo para
instalar actualizaciones de software).
Sin embargo, los detalles de cómo se realiza exactamente la replicación tienen un gran impacto en la confiabilidad del
sistema. Sin tolerancia a fallas, tener varias réplicas empeoraría la confiabilidad: cuantas más réplicas tenga, mayor será la
probabilidad de que alguna de las réplicas sea defectuosa en un momento dado (suponiendo que las fallas no estén perfectamente
correlacionadas). Sin embargo, si el sistema continúa funcionando a pesar de algunas réplicas defectuosas, la confiabilidad
mejora: la probabilidad de que todas las réplicas sean defectuosas al mismo tiempo es mucho menor que la probabilidad de que
una réplica sea defectuosa.
49
Machine Translated by Google
norte
Ahora exploraremos cómo lograr la tolerancia a fallas en la replicación. Para comenzar, considere el ejemplo de la
diapositiva 97. Suponga que tenemos dos réplicas, A y B, que inicialmente asocian la clave x con un valor v0 (y la marca de
tiempo t0). Un cliente intenta actualizar el valor de x a v1 (con marca de tiempo t1). Logra actualizar B, pero la actualización
de A falla porque A no está disponible temporalmente. Posteriormente, el cliente intenta volver a leer el valor que ha escrito;
la lectura tiene éxito en A pero falla en B. Como resultado, la lectura no devuelve el valor v1 previamente escrito por el
mismo cliente, sino el valor inicial v0.
cliente
A B
obtener (x)
(t0, v0)
Este escenario es problemático, ya que desde el punto de vista del cliente parece que se ha perdido el valor que ha
escrito. Imagine que publica una actualización en una red social, luego actualiza la página y no ve la actualización que acaba
de publicar. Como este comportamiento es confuso para los usuarios, muchos sistemas requieren coherencia de lectura
después de escritura (también conocida como coherencia de lectura de sus escrituras), en la que nos aseguramos de que
después de que un cliente escriba un valor, el mismo cliente podrá volver a leer el mismo valor de la base de datos.
Estrictamente hablando, con consistencia de lectura después de escritura, después de escribir, es posible que un cliente
no lea el valor que escribió porque, al mismo tiempo, otro cliente puede haber sobrescrito el valor. Por lo tanto, decimos que
la consistencia de lectura después de escritura requiere leer el último valor escrito o un valor posterior.
En la diapositiva 97 , podríamos garantizar la coherencia de lectura tras escritura asegurándonos de que siempre
escribimos en ambas réplicas y/o leemos de ambas réplicas. Sin embargo, esto significaría que las lecturas y/o escrituras
ya no son tolerantes a fallas: si una réplica no está disponible, una escritura o lectura que requiera respuestas de ambas
réplicas no podrá completarse.
Podemos resolver este enigma usando tres réplicas, como se muestra en la diapositiva 98. Enviamos cada solicitud de
lectura y escritura a las tres réplicas, pero consideramos que la solicitud fue exitosa siempre que recibamos ÿ 2 respuestas.
En el ejemplo, la escritura se realiza correctamente en las réplicas B y C, mientras que la lectura se realiza correctamente
en las réplicas A y B. Con una política de "2 de 3" tanto para lecturas como para escrituras, se garantiza que al menos una
de las respuestas a una read es de una réplica que vio la escritura más reciente (en el ejemplo, esta es la réplica B).
50
Machine Translated by Google
Quórum (2 de 3)
cliente
A B C
OK OK
obtener (x)
Diferentes réplicas pueden devolver diferentes respuestas a la misma solicitud de lectura: en la diapositiva 98, la lectura en A
devuelve el valor inicial (t0, v0), mientras que la lectura en B devuelve el valor (t1, v1) escrito previamente por este cliente. Usando
las marcas de tiempo, el cliente puede saber qué respuesta es la más reciente y devolver v1 a la aplicación.
En este ejemplo, el conjunto de réplicas {B, C} que respondió a la solicitud de escritura es un quórum de escritura y el conjunto
{A, B} que respondió a la lectura es un quórum de lectura. En general, un quórum es un conjunto mínimo de nodos que deben
responder a alguna solicitud para que tenga éxito. (El término proviene de la política, donde el quórum se refiere al número mínimo
de votos necesarios para tomar una decisión válida, por ejemplo, en un parlamento o comité). el quórum para la lectura debe tener
una intersección no vacía: en otras palabras, el quórum de lectura debe contener al menos un nodo que haya reconocido la escritura.
Una opción común de quórum en los sistemas distribuidos es un quórum mayoritario, que es cualquier subconjunto de nodos
que comprende estrictamente más de la mitad de los nodos. En un sistema con tres nodos {A, B, C}, los quórumes mayoritarios son
{A, B}, {A, C} y {B, C}. En general, en un sistema con un número impar de nodos n, cualquier subconjunto de tamaño n+1 es un
quórum mayoritario (2 de 3, o2 3 de
un5,quórum
. . . ). Con un número par de nodos n, esto debe redondearse a 2 . Por ejemplo, 3 de 4 forman
mayoritario.
n+1 = n+2
2
Los quórumes mayoritarios tienen la propiedad de que dos quórumes cualesquiera siempre tienen al menos un elemento en común.
Sin embargo, también son posibles otras construcciones de quórum además de las mayorías.
A B C D Y
Un sistema que requiere reconocimientos w para escrituras (es decir, un quórum de escritura de tamaño w) puede continuar
procesando actualizaciones siempre que no haya más de n ÿ w réplicas disponibles, y un sistema que requiere respuestas r para
lecturas puede continuar leyendo siempre que no haya más de n ÿ w réplicas disponibles. más de n ÿ r réplicas no están disponibles.
Con quórumes mayoritarios, esto significa que un sistema de tres réplicas puede tolerar que una réplica no esté disponible, un
sistema de cinco réplicas puede tolerar que dos no estén disponibles, y así sucesivamente.
51
Machine Translated by Google
En este enfoque de quórum para la replicación, es posible que falten algunas actualizaciones de algunas réplicas en un
momento dado: por ejemplo, en la diapositiva 98, falta la actualización (t1, v1) de la réplica A, ya que se eliminó esa solicitud
de escritura. Para volver a sincronizar las réplicas entre sí, un enfoque es confiar en un proceso anti-entropía, como se explica
en la diapositiva 94.
Leer reparación
cliente
A B C
obtener (x)
La actualización (t1, v1) es más reciente que (t0, v0) ya que t0 < t1.
El cliente ayuda a propagar (t1, v1) a otras réplicas.
Diapositiva 100
Otra opción es conseguir que los clientes ayuden con el proceso de difusión de actualizaciones. Por ejemplo, en la
diapositiva 100, el cliente lee (t1, v1) de B, pero recibe un valor anterior (t0, v0) de A y no responde de C. Dado que el cliente
ahora sabe que la actualización (t1, v1 ) debe propagarse a A, puede enviar esa actualización a A (utilizando la marca de
tiempo original t1, ya que no se trata de una nueva actualización, solo un reintento de una actualización anterior). El cliente
también puede enviar la actualización a C, aunque no sepa si C la necesita (si resulta que C ya tiene esta actualización, solo
se desperdicia una pequeña cantidad de ancho de banda de red). Este proceso se denomina reparación de lectura. El cliente
puede realizar una reparación de lectura en cualquier solicitud de lectura que realice, independientemente de si fue el cliente
el que realizó originalmente la actualización en cuestión.
Las bases de datos que utilizan este modelo de replicación a menudo se denominan estilo Dynamo, en honor a la base
de datos Dynamo de Amazon [DeCandia et al., 2007], que la popularizó. Sin embargo, el enfoque en realidad es anterior a
Dynamo [Attiya et al., 1995].
Diapositiva 101
52
Machine Translated by Google
Al usar la transmisión de orden total FIFO, es fácil construir un sistema replicado: transmitimos cada solicitud de
actualización a las réplicas, que actualizan su estado en función de cada mensaje a medida que se entrega. Esto se
denomina replicación de máquina de estado (SMR), porque una réplica actúa como una máquina de estado cuyas
entradas son entregas de mensajes. Solo requerimos que la lógica de actualización sea determinista: dos réplicas
cualesquiera que estén en el mismo estado y reciban la misma entrada, deben terminar en el mismo estado siguiente.
Incluso los errores deben ser deterministas: si una actualización tiene éxito en una réplica pero falla en otra, se volverían inconsistentes.
Una característica excelente de SMR es que la lógica para pasar de un estado al siguiente puede ser arbitrariamente
compleja, siempre que sea determinista. Por ejemplo, se puede ejecutar una transacción de base de datos completa con
lógica empresarial arbitraria, y esta lógica puede depender tanto del mensaje de difusión como del estado actual de la
base de datos. Algunas bases de datos distribuidas realizan la replicación de esta manera, y cada réplica ejecuta de forma
independiente el mismo código de transacción determinista (esto se conoce como replicación activa). Este principio
también es la base de las cadenas de bloques, las criptomonedas y los registros distribuidos: la "cadena de bloques" en
una cadena de bloques no es más que la secuencia de mensajes entregados por un protocolo de transmisión de orden
total (más sobre esto en la Lección 6), y cada réplica se ejecuta de forma determinista. las transacciones descritas en
esos bloques para determinar el estado del libro mayor (por ejemplo, quién posee qué dinero). Un "contrato inteligente" es
solo un programa determinista que una réplica ejecuta cuando se entrega un mensaje en particular.
Las desventajas de la replicación de máquinas de estado son las limitaciones de la transmisión de orden total. Como
se discutió en la Sección 4.2, cuando un nodo quiere transmitir un mensaje a través de una transmisión de orden total, no
puede enviarse ese mensaje inmediatamente. Por esta razón, cuando se utiliza la replicación de máquinas de estado, una
réplica que desea actualizar su estado no puede hacerlo de inmediato, sino que debe pasar por el proceso de transmisión,
coordinarse con otros nodos y esperar a que se le devuelva la actualización. La tolerancia a fallas de la replicación de la
máquina de estado depende de la tolerancia a fallas de la transmisión de orden total subyacente, que discutiremos en la
lección 6. Sin embargo, la replicación basada en la transmisión de orden total es ampliamente utilizada.
cliente 1 cliente 2
I F
T1
T1
T2
T2
OK cometer
OK
cometer
Diapositiva 103
53
Machine Translated by Google
Recuerde de la diapositiva 86 que una forma de implementar la transmisión de orden total es designar un nodo como líder y enrutar
todos los mensajes de transmisión a través de él para imponer una orden de entrega. Este principio también se usa ampliamente para la
replicación de bases de datos: muchos sistemas de bases de datos designan una réplica como líder, primaria o maestra. Cualquier
transacción que desee modificar la base de datos debe ejecutarse en la réplica líder. Como se muestra en la diapositiva 103, el líder
puede ejecutar varias transacciones al mismo tiempo; sin embargo, compromete esas transacciones en una orden total. Cuando se
confirma una transacción, la réplica líder transmite los cambios de datos de esa transacción a todas las réplicas de seguidores, y los
seguidores aplican esos cambios en el orden de confirmación. Este enfoque se conoce como replicación pasiva y podemos ver que es
equivalente a la transmisión de orden total de los registros de confirmación de transacciones.
Tanto sobre el uso de la transmisión de orden total para la replicación. ¿Qué pasa con los otros modelos de transmisión de la Clase
4 ? ¿Podemos usarlos también para la replicación? La respuesta es sí, como se muestra en la diapositiva 104; sin embargo, se requiere
más cuidado para garantizar que las réplicas permanezcan consistentes. No basta con asegurarse de que la actualización de estado sea
determinista.
Por ejemplo, podemos usar la transmisión causal, que garantiza el mismo orden de entrega en las réplicas cuando una actualización
se produjo antes que otra, pero que puede entregar actualizaciones simultáneas en cualquier orden. Si queremos asegurarnos de que
las réplicas terminen en el mismo estado, sin importar en qué orden se entreguen las actualizaciones simultáneas, debemos hacer que
esas actualizaciones sean conmutativas: es decir, debemos asegurarnos de que el resultado final sea el mismo, sin importar en qué en
qué orden se aplican esas actualizaciones. Esto se puede hacer, y veremos algunas técnicas para la conmutatividad en la lección 8.
Diapositiva 104
6 Consenso
En esta lección volvemos al problema de la transmisión de orden total. Vimos en la Sección 5.3 que la transmisión de orden total es muy
útil para permitir la replicación de máquinas de estado. Como se discutió en la diapositiva 86, una forma de implementar la transmisión
de orden total es designar un nodo como líder y enrutar todos los mensajes a través de él. Luego, el líder solo necesita distribuir los
mensajes a través de la transmisión FIFO, y esto es suficiente para garantizar que todos los nodos entreguen la misma secuencia de
mensajes en el mismo orden.
Sin embargo, el gran problema con este enfoque es que el líder es un único punto de falla: si deja de estar disponible, todo el
sistema se detiene. Una forma de superar esto es a través de la intervención manual: se puede notificar a un operador humano si el líder
no está disponible, y esta persona luego reconfigura todos los nodos para usar un nodo diferente como su líder. Este proceso se
denomina conmutación por error y, de hecho, se utiliza en muchos sistemas de bases de datos.
La conmutación por error funciona bien en situaciones en las que la indisponibilidad del líder se planifica con anticipación, por
ejemplo, cuando es necesario reiniciar el líder para instalar actualizaciones de software. Sin embargo, para interrupciones repentinas e
inesperadas del líder (por ejemplo, un bloqueo, una falla de hardware o un problema de red), la conmutación por error se ve afectada por
el hecho de que los humanos están limitados en cuanto a la rapidez con la que pueden realizar este procedimiento. Incluso en el mejor
de los casos, un operador tardará varios minutos en responder, durante los cuales el sistema no podrá procesar ninguna actualización.
Esto plantea la pregunta: ¿podemos transferir automáticamente el liderazgo de un nodo a otro en caso de que el antiguo líder no
esté disponible? La respuesta es sí, y esto es exactamente lo que hacen los algoritmos de consenso.
54
Machine Translated by Google
Diapositiva 105
• Para convertir la emisión de orden total en consenso, un nodo que quiere proponer un valor lo emite, y el primer
mensaje entregado por la emisión de orden total se toma como valor decidido.
• Para convertir el consenso en una transmisión de orden total, usamos una instancia separada del protocolo de
consenso para decidir sobre el primero, segundo, tercero, . . . mensaje a entregar. Un nodo que quiere emitir un
mensaje lo propone para una de estas rondas de consenso. El algoritmo de consenso luego asegura que todos los
nodos estén de acuerdo en la secuencia de mensajes que se entregarán.
Diapositiva 106
Los dos algoritmos de consenso más conocidos son Paxos [Lamport, 1998] y Raft [Ongaro and Ouster hout, 2014].
En su formulación original, Paxos proporciona solo consenso sobre un único valor, y el algoritmo Multi Paxos es una
generalización de Paxos que proporciona transmisión de orden FIFO total. Por otro lado, Raft está diseñado para
proporcionar una transmisión de orden FIFO total "lista para usar".
55
Machine Translated by Google
I Paxos, Raft, etc. usan relojes que solo se usan para tiempos de espera/
detector de fallas para garantizar el progreso. La seguridad (corrección)
no depende del tiempo.
Diapositiva 107
El diseño de un algoritmo de consenso depende de manera crucial del modelo del sistema, como se analiza en la Sección 2.3.
Paxos y Raft asumen un modelo de sistema con enlaces de pérdida justa (Diapositiva 33), comportamiento de recuperación de fallas de los
nodos (Diapositiva 34) y sincronía parcial (Diapositiva 35).
Los supuestos sobre el comportamiento de la red y los nodos pueden debilitarse a bizantinos, y dichos algoritmos se utilizan en
cadenas de bloques. Sin embargo, los algoritmos de consenso tolerantes a fallas bizantinos son significativamente más complicados y
menos eficientes que los no bizantinos. Por ahora, nos centraremos en los algoritmos de recuperación de fallas y pérdida justa, que son
útiles en muchos entornos prácticos (como centros de datos con redes privadas confiables). La unidad L47 en la Parte III entra en detalles
del consenso bizantino.
Por otro lado, la suposición de sincronía parcial no puede debilitarse a asincronía. La razón es que el consenso requiere un detector
de fallas (Diapositiva 40), que a su vez requiere un reloj local para activar tiempos de espera [Chandra y Toueg, 1996]. Si no tuviéramos
ningún reloj, entonces un algoritmo de consenso determinista nunca podría terminar. De hecho, se ha demostrado que ningún algoritmo
asíncrono determinista puede resolver el problema del consenso con terminación garantizada. Este hecho se conoce como el resultado
FLP, uno de los teoremas más importantes de la computación distribuida, llamado así por sus tres autores Fischer, Lynch y Paterson
[Fischer et al., 1985].
Elección de líder
Multi-Paxos, Raft, etc. utilizan un líder para secuenciar los mensajes.
Usar un detector de fallas ( tiempo de espera) para determinar sospechas
accidente o indisponibilidad del líder.
I En caso de choque del líder sospechoso, elija uno nuevo.
¡ Prevengo a dos líderes al mismo tiempo ("split-brain")!
A B C D Y
En el centro de la mayoría de los algoritmos de consenso se encuentra un proceso para elegir un nuevo líder cuando el líder existente
56
Machine Translated by Google
deja de estar disponible por cualquier motivo. Los detalles difieren entre los algoritmos; en esta lección nos
concentraremos en el enfoque adoptado por Raft, pero muchas de las lecciones de Raft son igualmente relevantes
para otros algoritmos de consenso. Howard y Mortier [2020] brindan una comparación detallada de Raft y Multi-
Paxos (sin embargo, Paxos/Multi-Paxos no son examinables).
La elección de un líder se inicia cuando los otros nodos sospechan que el líder actual ha fallado, generalmente
porque no han recibido ningún mensaje del líder durante algún tiempo. Uno de los otros nodos se convierte en
candidato y les pide a los otros nodos que voten si aceptan al candidato como su nuevo líder. Si un quórum
(Sección 5.2) de nodos vota a favor del candidato, se convierte en el nuevo líder. Si se utiliza un quórum mayoritario,
esta votación puede tener éxito siempre que la mayoría de los nodos (2 de 3 o 3 de 5, etc.) estén funcionando y
puedan comunicarse.
Si hubiera varios líderes, podrían tomar decisiones inconsistentes que llevarían a violaciones de las propiedades
de seguridad de la transmisión de orden total (una situación conocida como cerebro dividido). Por lo tanto, la clave
que queremos de la elección de un líder es que solo debe haber un líder a la vez. En Raft, el concepto de "en
cualquier momento" se captura al tener un número de término, que es solo un número entero que se incrementa
cada vez que se inicia una elección de líder. Si se elige un líder, el algoritmo de votación garantiza que sea el único
líder dentro de ese período en particular. Diferentes términos pueden tener diferentes líderes.
¡Es posible que el Nodo 1 ni siquiera sepa que se ha elegido un nuevo líder!
Diapositiva 109
Sin embargo, recuerde de la diapositiva 41 que en un sistema parcialmente síncrono, un detector de fallas
basado en el tiempo de espera puede ser inexacto: puede sospechar que un nodo se ha bloqueado cuando en
realidad funciona bien, por ejemplo, debido a un pico en la latencia de la red. Por ejemplo, en la diapositiva 109, el
nodo 1 es el líder en el término t, pero la red entre este y los nodos 2 y 3 se interrumpe temporalmente. Los nodos
2 y 3 pueden detectar que el nodo 1 ha fallado y elegir un nuevo líder en el término t + 1, aunque el nodo 1 todavía
funciona correctamente. Además, es posible que el nodo 1 ni siquiera haya notado el problema de la red, y
tampoco sabe aún sobre el nuevo líder. Por lo tanto, terminamos con dos nodos que creen ser el líder.
¿Seré tu líder en
el término t?
sí sí
Podemos
entregar mensaje m siguiente
en el término t?
bueno bueno
Bien, ahora
entrega m por favor
Diapositiva 110
57
Machine Translated by Google
Por esta razón, incluso después de que un nodo ha sido elegido líder, debe actuar con cuidado, ya que en cualquier
momento el sistema puede contener a otro líder con un mandato posterior del que aún no ha tenido conocimiento. No es
seguro que un líder actúe unilateralmente. En cambio, cada vez que un líder quiere decidir sobre el próximo mensaje a
entregar, debe solicitar nuevamente la confirmación de un quórum de nodos. Esto se ilustra en la diapositiva 110:
1. En el primer ida y vuelta, el nodo izquierdo es elegido líder gracias a los votos de los otros dos nodos.
2. En el segundo ida y vuelta, el líder propone el siguiente mensaje a entregar, y los seguidores toman conocimiento
de que no conocen ningún líder con término posterior a t.
3. Finalmente, el líder realmente entrega m y transmite este hecho a los seguidores, para que puedan hacer
lo mismo.
Si se ha elegido otro líder, el antiguo líder se enterará por al menos uno de los reconocimientos en la segunda vuelta,
porque al menos uno de los nodos del quórum de la segunda vuelta también debe haber votado por el nuevo líder. Por lo
tanto, a pesar de que pueden existir varios líderes al mismo tiempo, los líderes anteriores ya no podrán decidir qué
mensajes enviar, lo que hace que el algoritmo sea seguro.
Para comprender el algoritmo, vale la pena tener en cuenta la máquina de estados de la diapositiva 111. Un nodo
puede estar en uno de tres estados: líder, candidato o seguidor. Cuando un nodo comienza a ejecutarse por primera vez,
o cuando falla y se recupera, se inicia en el estado de seguidor y espera mensajes de otros nodos.
Si no recibe mensajes de un líder o candidato durante un período de tiempo, el seguidor sospecha que el líder no está
disponible y puede intentar convertirse en líder. El tiempo de espera para detectar la falla del líder es aleatorio, para reducir
la probabilidad de que varios nodos se conviertan en candidatos al mismo tiempo y compitan para convertirse en líder.
Cuando un nodo sospecha que el líder ha fallado, pasa al estado candidato, incrementa el número del término e inicia
una elección de líder en ese término. Durante esta elección, si el nodo recibe noticias de otro candidato o líder con un
mandato más alto, vuelve al estado de seguidor. Pero si la elección tiene éxito y recibe votos de un quórum de nodos, el
candidato pasa al estado líder. Si no se reciben suficientes votos dentro de un período de tiempo, la elección finaliza y el
candidato reinicia la elección con un término más alto.
Una vez que un nodo está en el estado líder, sigue siendo líder hasta que se cierra o falla, o hasta que recibe un
mensaje de un líder o candidato con un término superior al suyo. Un término más alto podría ocurrir si una partición de la
red hiciera que el líder y otro nodo no pudieran comunicarse durante el tiempo suficiente para que el otro nodo comenzara
una elección para un nuevo líder. Al enterarse de un término más alto, el ex líder se retira para convertirse en seguidor.
pone en marcha
elección
o recupera
se acabó el tiempo
del accidente
sospecha
del fracaso del líder
quórum
Seguidor Candidato Líder
descubre
nuevo término
Diapositiva 111
58
Machine Translated by Google
La diapositiva 112 muestra el pseudocódigo para iniciar y para iniciar una elección. Las variables definidas en el bloque
de inicialización constituyen el estado de un nodo. Cuatro de las variables (currentTerm, voteFor log y commitLength) deben,
mantenerse en un almacenamiento estable (por ejemplo, en el disco), ya que sus valores no deben perderse en caso de
falla. Las otras variables pueden estar en la memoria volátil, y la función de recuperación de fallas restablece sus valores.
Cada nodo tiene una ID única y suponemos que hay una constante global, nodos, que contiene el conjunto de ID de todos
los nodos del sistema. Esta versión del algoritmo no se ocupa de la reconfiguración (agregar o eliminar nodos en el sistema).
La variable log contiene una serie de entradas, cada una de las cuales tiene las propiedades msg y term. La propiedad
msg de cada entrada de matriz contiene un mensaje que queremos entregar a través de la transmisión de orden total, y la
propiedad term contiene el número de término en el que se transmitió. El registro utiliza una indexación basada en cero, por
lo que log[0] es la primera entrada de registro y log[log.lengthÿ1] es la última. El registro crece agregando nuevas entradas
al final y Raft replica este registro en todos los nodos. Cuando una entrada de registro (y todos sus predecesores) se han
replicado en un quórum de nodos, se confirma. En el momento en que confirmamos una entrada de registro, también
enviamos su mensaje a la aplicación. Antes de que se confirme una entrada de registro, aún puede cambiar, pero Raft
garantiza que una vez que se confirma una entrada de registro, es definitiva y todos los nodos confirmarán la misma
secuencia de entradas de registro. Por lo tanto, la entrega de mensajes de entradas de registro confirmadas en su orden de
registro nos brinda una transmisión de orden FIFO total.
Cuando un nodo sospecha que un líder falla, inicia una elección de líder de la siguiente manera: incrementa currentTerm,
establece su propio rol como candidato y vota por sí mismo configurando voteFor y votesReceived en su propia ID de nodo.
Luego envía un mensaje VoteRequest a cada uno de los nodos, pidiéndoles que voten si este candidato debe ser el nuevo
líder. El mensaje contiene el nodeId del candidato, su término actual (después de incrementarse), el número de entradas en
su registro y la propiedad de término de su última entrada de registro.
m1 m2 m3
Balsa (1/9): inicialización registro =
mensaje
término
1 1 1
en la inicialización
do currentTerm := 0; votado por := registro[0] registro[1] registro[2]
registro nulo := hola; commitLength := 0
rolActual := seguidor; líder actual: = votos nulos
recibidos: = {}; enviadoLength := hola; ackedLength := hola fin en
al recuperarse de un bloqueo ,
haga currentRole := seguidor; líder actual: = votos
nulos recibidos: = {}; enviadoLength := hola; ackedLength := hola
fin en
Diapositiva 112
c para candidato
Balsa (2/9): votando por un nuevo líder
Diapositiva 113
59
Machine Translated by Google
La diapositiva 113 muestra lo que sucede cuando un nodo recibe un mensaje de solicitud de voto de un candidato. Un
nodo solo votará por un candidato si el registro del candidato está al menos tan actualizado como el registro del votante; esto
evita que un candidato con un registro desactualizado se convierta en líder, lo que podría provocar la pérdida de entradas de
registro confirmadas. El registro del candidato es aceptable si el término de su última entrada de registro es mayor que el
término de la última entrada de registro en el nodo que recibió el mensaje VoteRequest. Además, el registro también es
aceptable si los términos son los mismos y el registro del candidato contiene al menos tantas entradas como el registro del destinatario.
Esta lógica se refleja en la variable logOk.
Además, un nodo no votará por un candidato si ya ha votado por otro candidato en el mismo período o en un período
posterior. La variable termOk es verdadera si está bien votar por el candidato de acuerdo con esta regla. La variable voteFor
realiza un seguimiento de cualquier voto anterior del nodo actual en currentTerm.
Si logOk y termOk, entonces el nodo actualiza su término actual al término del candidato, lo registra y envía un mensaje
, de VoteResponse
el nodo envía un mensaje VoteResponse que
que
contiene
contiene
verdadero
falso (que
(que
indica
indica
unaéxito)
negativa
al candidato.
a votar por
vote
el candidato).
in voteFor De
Además
lo contrario,
del
indicador de éxito o fracaso, el mensaje de respuesta contiene el ID de nodo del nodo que envía el voto y el término del voto.
terminar
si termina en
Diapositiva 114
Volviendo al candidato, la diapositiva 114 muestra el código para procesar los mensajes VoteResponse. Ignoramos
cualquier respuesta relacionada con términos anteriores (que podrían llegar tarde debido a demoras en la red). Si el término
en la respuesta es más alto que el término del candidato, el candidato cancela la elección y vuelve al estado de seguidor.
Pero si el término es correcto y el indicador de éxito otorgado se establece en verdadero, el candidato agrega la ID de nodo
del votante al conjunto de votos recibidos.
Si el conjunto de votos constituye un quórum, el candidato pasa al estado líder. Como su primera acción como líder,
actualiza las variables sentLength y ackedLength (explicadas a continuación) y luego llama a la función ReplicateLog (definida
en la diapositiva 116) para cada seguidor. Esto tiene el efecto de enviar un mensaje a cada seguidor, informándoles sobre el
nuevo líder.
En el líder, sentLength y ackedLength son variables que asignan cada ID de nodo a un número entero (los nodos que
no son líderes no usan estas variables). Para cada seguidor F, sentLength[F] rastrea cuántas entradas de registro, contando
desde el comienzo del registro, se han enviado a F, y ackedLength[F] rastrea cuántas entradas de registro han sido
reconocidas como recibidas por F. Al convertirse en un líder, un nodo inicializa sentLength[F] a log.length (es decir, asume
que el seguidor ya ha recibido el registro completo), e inicializa ackedLength[F] a 0 (es decir, todavía no se ha reconocido
nada). Estas suposiciones pueden ser incorrectas: por ejemplo, es posible que al seguidor le falten algunas de las entradas
de registro que están presentes en el líder.
En este caso, sentLength[F] se corregirá a través de un proceso que analizamos en la diapositiva 119.
La diapositiva 115 muestra cómo se agrega una nueva entrada al registro cuando la aplicación desea transmitir un
mensaje a través de la transmisión de orden total. Un líder puede simplemente seguir adelante y agregar una nueva entrada
a su registro, mientras que cualquier otro nodo debe pedirle al líder que haga esto en su nombre, a través de un enlace FIFO
(para garantizar la transmisión total del pedido FIFO). Luego, el líder actualiza su propia entrada en ackedLength a log.length,
lo que indica que ha reconocido su propia adición al registro y llama a ReplicateLog para cada nodo.
Además, un líder también llama periódicamente a ReplicateLog para cada nodo, incluso si no hay un mensaje nuevo
para transmitir. Esto sirve para múltiples propósitos: permite que los seguidores sepan que el líder todavía está vivo; sirve
como retransmisión de cualquier mensaje del líder al seguidor que se haya perdido; y
60
Machine Translated by Google
actualiza al seguidor sobre qué mensajes se pueden enviar, como se explica a continuación.
Diapositiva 115
Llamado al líder cada vez que hay un nuevo mensaje en el registro, y también
periódicamente. Si no hay mensajes nuevos, las entradas son la lista vacía.
Los mensajes de LogRequest con las entradas = hola sirven como latidos del corazón, lo que les
permite a los seguidores saber que el líder todavía está vivo.
Diapositiva 116
La función ReplicateLog se muestra en la diapositiva 116. Su propósito es enviar cualquier nueva entrada de registro
del líder al nodo seguidor con ID followerId. Primero establece las entradas variables en el sufijo del registro que
comienza con el índice sentLength[followerId], si existe. Es decir, si sentLength[followerId] es el número de entradas de
registro ya enviadas a followerId, entonces las entradas contienen las entradas restantes que aún no se han enviado. Si
sentLength[followerId] = log.length, las entradas de la variable se establecen en la matriz vacía.
Luego, ReplicateLog envía un mensaje LogRequest a followerId que contiene entradas y varios otros valores: la ID
del líder; su término actual; la longitud del prefijo de registro que precede a las entradas; el término de la última entrada
de registro que precede a las entradas; y commitLength, que es el número de entradas de registro que se han
confirmado, contadas desde el inicio del registro. Más información sobre la confirmación de entradas de registro en breve.
Cuando un seguidor recibe uno de estos mensajes LogRequest del líder, procesa el mensaje como se muestra en
la diapositiva 117. Primero, si el mensaje es para un término posterior al que el seguidor ha visto anteriormente,
actualiza su término actual y acepta el remitente de el mensaje como líder.
Luego, el seguidor verifica si su registro es consistente con el del líder. logLength es el número de entradas de
registro que preceden a las nuevas entradas contenidas en el mensaje LogRequest. El seguidor requiere que su registro
sea al menos tan largo como logLength (es decir, que no falte ninguna entrada), y que el término de la última entrada
de registro en el prefijo logLength del registro del seguidor sea el mismo que el término del mismo registro entrada en el
líder. Si ambas comprobaciones pasan, la variable logOk se establece en verdadero.
Si el mensaje LogRequest es para el término esperado y si logOk, entonces el seguidor acepta el mensaje y llama
a la función AppendEntries (definida en la diapositiva 118) para agregar entradas a su propio registro. Luego responde
al líder con un mensaje LogResponse que contiene la ID del seguidor, el término actual, un reconocimiento del número
de entradas de registro recibidas y el valor verdadero que indica que LogRequest
61
Machine Translated by Google
fue exitoso. Si el mensaje es de un término desactualizado o logOk es falso, el seguidor responde con un LogResponse
que contiene falso para indicar un error.
Diapositiva 117
Diapositiva 118
La diapositiva 118 muestra la función AppendEntries, a la que llama un seguidor para ampliar su registro con las
entradas recibidas del líder. logLength es el número de entradas de registro (contadas desde el principio del registro) que
preceden a las nuevas entradas. Si el registro del seguidor ya contiene entradas en log[logLength] y más allá, compara el
término de esa entrada existente con el término de la primera entrada nueva recibida del líder.
Si son inconsistentes, el seguidor descarta esas entradas existentes truncando el registro. Esto sucede si las entradas de
registro existentes provienen de un líder anterior, que ahora ha sido reemplazado por un nuevo líder.
A continuación, cualquier entrada nueva que aún no esté presente en el registro del seguidor se agrega al registro. En
el caso de la duplicación de mensajes, esta operación es idempotente, ya que se entiende que las nuevas entradas
comienzan en el índice logLength en el registro.
Finalmente, el seguidor comprueba si el número entero leaderCommit en el mensaje LogRequest es mayor que su
variable local commitLength. Si es así, esto significa que los nuevos registros están listos para confirmarse y enviarse a la
aplicación. El líder mueve su commitLength hacia adelante y realiza la entrega de difusión de orden total de los mensajes
en las entradas de registro correspondientes.
Esto completa el algoritmo desde el punto de vista de los seguidores. Lo que queda es volver a
el líder y para mostrar cómo procesa los mensajes LogResponse de los seguidores (consulte la diapositiva 119).
62
Machine Translated by Google
Diapositiva 119
Un líder que recibe un mensaje LogResponse primero verifica el término en el mensaje: si el término del remitente es
posterior al término del destinatario, eso significa que se ha iniciado una nueva elección de líder y, por lo tanto, este nodo pasa
de líder a seguidor. Los mensajes con un término obsoleto se ignoran. Para los mensajes con el término correcto, verificamos el
campo booleano de éxito para ver si el seguidor aceptó las entradas del registro.
Si el éxito = verdadero, el líder actualiza sentLength y ackedLength para reflejar la cantidad de entradas de registro
reconocidas por el seguidor y luego llama a la función CommitLogEntries (Diapositiva 120). Si éxito = falso, sabemos que el
seguidor no aceptó las entradas del registro porque su variable logOk era falsa. En este caso, el líder disminuye el valor de
sentLength para este seguidor y llama a ReplicateLog para volver a intentar enviar el mensaje LogRequest comenzando con
una entrada de registro anterior. Esto puede suceder varias veces, pero eventualmente el líder enviará al seguidor una serie de
entradas que amplían limpiamente el registro existente del seguidor, momento en el cual el seguidor aceptará LogRequest. (Este
algoritmo podría optimizarse para requerir menos reintentos, pero en este curso evitaremos que sea más complejo de lo
necesario).
función CommitLogEntries
minAcks := d(|nodos| + 1)/2e
ready := {len ÿ {1, . . . , registro.longitud} | acks(len) ÿ minAcks} si
está listo 6= {} ÿ max(ready) > commitLength ÿ
log[max(ready) ÿ 1].term = currentTerm entonces
for i := commitLength to max(ready) ÿ 1 entregue
log[i].msg al final de la aplicación for
commitLength := max(ready) end if end function
Diapositiva 120
Finalmente, la diapositiva 120 muestra cómo el líder determina qué entradas de registro se deben confirmar. Definimos la
función acks(longitud) para tomar un número entero, una cantidad de entradas de registro contadas desde el inicio del registro.
Esta función devuelve el número de nodos que han confirmado la recepción de entradas de registro de longitud o más.
CommitLogEntries usa esta función para determinar cuántas entradas de registro han sido reconocidas por un quórum
mayoritario de nodos o más. La variable ready contiene el conjunto de longitudes de prefijo de registro que están listas para
confirmar, y si ready no está vacío, max(ready) es la longitud máxima de prefijo de registro que podemos confirmar. Si esto
excede el valor actual de commitLength, significa que hay nuevas entradas de registro que ahora están listas para confirmar
porque han sido reconocidas por suficientes nodos. El mensaje en cada una de estas entradas de registro ahora se entrega a la
aplicación en el líder y se actualiza la variable commitLength. En el siguiente mensaje LogRequest que el líder envíe a los
seguidores, se incluirá el nuevo valor de commitLength, lo que hará que los seguidores confirmen y entreguen las mismas
entradas de registro.
63
Machine Translated by Google
Ejercicio 16. Tres nodos están ejecutando el algoritmo Raft. En un momento dado, cada nodo tiene el registro que se muestra a
continuación:
m1 m2 m1 m4 m5 m6 m1 m4 m7 mensaje
registro en el nodo A: registro en el nodo B: registro en el nodo C:
1 1 1 2 2 2 1 2 3 término
(a) Explique qué eventos pueden haber ocurrido que causaron que los nodos estuvieran en este estado.
(b) ¿Cuáles son los valores posibles de la variable commitLength en cada nodo? (c) El nodo A inicia una
elección de líder en el término 4, mientras que los nodos están en el estado anterior. ¿Es posible que obtenga quórum de votos?
¿Qué pasa si la elección fue iniciada por uno de los otros nodos? (d) Suponga que el nodo B es elegido líder en el término 4,
mientras que los nodos están en el estado anterior. Dé la secuencia de mensajes intercambiados entre B y C después de esta
elección.
7 Consistencia de la réplica
Hemos visto cómo realizar la replicación usando quórums de lectura/escritura y la replicación de máquinas de estado usando
transmisión de orden total. En este contexto hemos dicho que queremos que las réplicas tengan “copias consistentes de los mismos
datos”, sin definir exactamente a qué nos referimos con consistente.
Desafortunadamente, la palabra "coherencia" significa diferentes cosas en diferentes contextos. En el contexto de las
transacciones, la C en ACID significa consistencia que es una propiedad de un estado: es decir, podemos decir que una base de
datos está en un estado consistente o inconsistente, lo que significa que el estado satisface o viola ciertas invariantes definidas por
el solicitud. Por otro lado, en el contexto de la replicación, hemos usado informalmente “consistencia” para referirnos a una relación
entre réplicas: queremos que una réplica sea consistente con otra réplica.
Dado que no existe una definición verdadera de consistencia, hablamos en cambio de una variedad de modelos de consistencia.
Hemos visto un ejemplo particular de un modelo de consistencia, a saber, consistencia de lectura después de escritura (Diapositiva
97), que restringe los valores que una operación de lectura puede devolver cuando el mismo nodo escribe previamente en el mismo
elemento de datos. Veremos más modelos en esta conferencia.
64
Machine Translated by Google
Transacciones distribuidas
Diapositiva 122
Diapositiva 123
El algoritmo más común para asegurar el compromiso atómico a través de múltiples nodos es el protocolo de compromiso de dos fases (2PC) [Gray, 1978]. (No debe confundirse
con el bloqueo de dos fases (2PL), discutido en la primera mitad de este curso: 2PL garantiza el aislamiento serializable, mientras que 2PC garantiza el compromiso atómico. También
hay un protocolo de compromiso de tres fases, pero asume el protocolo sincrónico poco realista modelo de sistema, por lo que no lo discutiremos aquí.) El flujo de comunicación de
cliente coordinador
A B
iniciar T1
T1
. . . ejecución habitual de transacciones. . . T1
cometer T1
preparar
OK OK
decisión de
cometer o abortar cometer
Diapositiva 124
sesenta y cinco
Machine Translated by Google
Cuando se utiliza la confirmación en dos fases, un cliente primero inicia una transacción regular de un solo nodo en cada
réplica que participa en la transacción y realiza las lecturas y escrituras habituales dentro de esas transacciones. Cuando el
cliente está listo para confirmar la transacción, envía una solicitud de confirmación al coordinador de transacciones, un nodo
designado que administra el protocolo 2PC. (En algunos sistemas, el coordinador es parte del cliente).
El coordinador primero envía un mensaje de preparación a cada réplica que participa en la transacción y cada réplica
responde con un mensaje que indica si puede confirmar la transacción (esta es la primera fase del protocolo). Las réplicas
aún no confirman la transacción, pero deben asegurarse de que definitivamente podrán confirmar la transacción en la
segunda fase si así lo indica el coordinador. Esto significa, en particular, que la réplica debe escribir todas las actualizaciones
de la transacción en el disco y verificar cualquier restricción de integridad antes de responder ok al mensaje de preparación,
mientras continúa manteniendo los bloqueos para la transacción.
I Cuando se recupere, lea la decisión del disco y envíela a las réplicas (o cancele
Diapositiva 125
El problema con la confirmación de dos fases es que el coordinador es un único punto de falla. Los bloqueos del
coordinador se pueden tolerar haciendo que el coordinador escriba sus decisiones de compromiso/cancelación en un
almacenamiento estable, pero aun así, puede haber transacciones que se hayan preparado pero que aún no se hayan
comprometido/cancelado en el momento del bloqueo del coordinador (llamadas transacciones dudosas). actas). Cualquier
transacción dudosa debe esperar hasta que el coordinador se recupere para conocer su destino; no pueden decidir
unilateralmente comprometerse o abortar, porque esa decisión podría terminar siendo inconsistente con el coordinador y
otros nodos, lo que podría violar la atomicidad.
Afortunadamente, es posible evitar el punto único de falla del coordinador mediante el uso de un algoritmo de consenso
o un protocolo de transmisión de orden total. La diapositiva 126 muestra un algoritmo de compromiso de dos fases tolerante
a fallas basado en Paxos Commit [Gray y Lamport, 2006]. La idea es que cada nodo que participe en la transacción use la
transmisión de orden total para difundir su voto sobre si comprometerse o abortar.
Además, si el nodo A sospecha que el nodo B ha fallado (porque no se recibió ningún voto de B dentro de un tiempo de
espera), entonces A puede intentar votar para abortar en nombre de B. Esto introduce una condición de carrera: si el nodo B
es lento, podría ser que el nodo B transmita su propio voto para comprometerse casi al mismo tiempo que el nodo A
sospecha que B ha fallado y vota en nombre de B.
Estos votos se entregan a cada nodo por emisión de orden total, y cada destinatario cuenta los votos de forma
independiente. Al hacerlo, contamos solo el primer voto de cualquier réplica dada e ignoramos cualquier voto posterior de la
misma réplica. Dado que la transmisión de orden total garantiza la misma orden de entrega en cada nodo, todos los nodos
acordarán si el primer voto entregado de una réplica dada fue un voto de confirmación o un voto de cancelación, incluso en
el caso de una condición de carrera entre múltiples nodos que transmiten votos contradictorios. por la misma réplica.
Si un nodo observa que el primer voto entregado desde alguna réplica es un voto para abortar, entonces la transacción
66
Machine Translated by Google
puede ser abortado inmediatamente. De lo contrario, un nodo debe esperar hasta que haya entregado al menos un voto de cada réplica.
Una vez que se han entregado estos votos, y ninguna de las réplicas vota para abortar en su primer mensaje entregado, entonces se puede
confirmar la transacción. Gracias a la transmisión de orden total, se garantiza que todos los nodos tomarán la misma decisión sobre si
abortar o confirmar, lo que preserva la atomicidad.
Diapositiva 126
if ok = true ,
entonces commitVotes[T] := commitVotes[T] ÿ {replicaId}
if commitVotes[T] = replicas[T] then decide[T] := true
confirma la transacción T en este nodo
terminar
si se decidió [T] :=
true abortar transacción T en este
nodo terminar si
terminar
si termina en
Diapositiva 127
7.2 Linealizabilidad
Un protocolo de compromiso atómico es una forma de preservar la coherencia entre múltiples réplicas frente a fallas, al garantizar que todos
los participantes de una transacción se comprometan o cancelen. Sin embargo, cuando hay varios nodos que leen y modifican
simultáneamente algunos datos compartidos, no es suficiente garantizar el mismo resultado de confirmación o cancelación para todos los
nodos. También tenemos que razonar sobre la interacción que surge de la actividad concurrente.
En esta sección, presentaremos un modelo de consistencia particular para el sistema concurrente que se llama linearizabilidad.
Discutiremos la linealizabilidad de manera informal; si está interesado en los detalles, Herlihy y Wing [1990] dan una definición formal. La
gente a veces dice consistencia fuerte cuando se refiere a la linealizabilidad, pero el concepto de "consistencia fuerte" es bastante vago e
impreciso. Nos apegaremos al término linearizabilidad, que tiene un significado definido con precisión.
En la diapositiva 128 aparece una definición informal de linealizabilidad . En las siguientes diapositivas aclararemos
lo que esto significa a través de ejemplos.
La linealizabilidad es un concepto útil no solo en sistemas distribuidos, sino también en el contexto de la concurrencia de memoria
compartida en una sola máquina. Curiosamente, en una computadora con múltiples núcleos de CPU (prácticamente todos los servidores,
computadoras portátiles y teléfonos inteligentes en la actualidad), el acceso a la memoria no se puede linealizar de manera predeterminada. Esta
67
Machine Translated by Google
se debe a que cada núcleo de CPU tiene sus propios cachés y una actualización realizada por un núcleo no se refleja inmediatamente
en el caché de otro núcleo. Por lo tanto, incluso una sola computadora comienza a comportarse un poco como un sistema replicado. La
unidad L304 en la Parte III entra en detalles del comportamiento de la memoria multinúcleo.
No confunda la linealizabilidad con la serializabilidad, aunque ambas palabras parecen significar algo así como "puede organizarse
en un orden secuencial". La serialización significa que las transacciones tienen el mismo efecto que si se hubieran ejecutado en algún
orden en serie, pero no define cuál debería ser ese orden.
La linealizabilidad define los valores que deben devolver las operaciones, según la concurrencia y el orden relativo de esas operaciones.
Es posible que un sistema proporcione serializabilidad y linealizabilidad: la combinación de las dos se denomina serializabilidad estricta
o serializabilidad de una copia.
Diapositiva 128
El objetivo principal de la linealización es garantizar que los nodos observen el sistema en un estado "actualizado"; es decir, no
leen valores obsoletos (obsoletos). Anteriormente hemos visto este concepto de leer un valor "actualizado" en el contexto de la
consistencia de lectura después de escritura (Diapositiva 97). Sin embargo, mientras que la coherencia de lectura después de la
escritura define solo un modelo de coherencia para las lecturas y escrituras realizadas por el mismo nodo, la linealización generaliza
esta idea a las operaciones realizadas simultáneamente por diferentes nodos.
Desde el punto de vista de un cliente, cada operación lleva una cierta cantidad de tiempo. Decimos que una operación comienza
en el momento en que es solicitada por la aplicación y finaliza cuando el resultado de la operación es devuelto a la aplicación. Entre el
inicio y el final, pueden ocurrir varios pasos de comunicación de red; por ejemplo, si se utilizan quórums, una operación puede finalizar
cuando el cliente haya recibido respuestas de un quórum de réplicas.
En la diapositiva 129 y las diapositivas siguientes, representamos la vista del cliente de una operación de obtener/establecer como
un rectángulo que cubre el período de tiempo desde el principio hasta el final de una operación. Dentro del rectángulo escribimos el
efecto de la operación: set(x, v) significa actualizar el elemento de datos x para que tenga el valor v, y get(x) ÿ v significa una lectura de
x que devuelve el valor v.
cliente
A B C
OK OK
obtener (x)
Diapositiva 129
68
Machine Translated by Google
cliente 1 cliente 2
Me enfoco en observable por el cliente
comportamiento: cuándo y qué
?
vuelve la operación
Diapositiva 130
cliente 1 cliente 2
en este caso
Diapositiva 131
69
Machine Translated by Google
La linealizabilidad no se trata solo de la relación de una operación de obtención con una operación de establecimiento anterior,
sino que también puede relacionar una operación de obtención con otra. La diapositiva 132 muestra un ejemplo de un sistema que
utiliza lecturas y escrituras de quórum, pero que, sin embargo, no es linealizable. Aquí, el cliente 1 establece x en v1 y, debido a una
peculiaridad de la red, la actualización de la réplica A se realiza rápidamente, mientras que las actualizaciones de las réplicas B y C se retrasan.
El cliente 2 lee de un quórum de {A, B}, recibe las respuestas {v0, v1} y determina que v1 es el valor más nuevo en función de la marca
de tiempo adjunta. Una vez finalizada la lectura del cliente 2, el cliente 3 inicia una lectura desde un quórum de {B, C}, recibe v0 de
ambas réplicas y devuelve v0 (ya que no conoce v1).
Por lo tanto, el cliente 3 observa un valor más antiguo que el cliente 2, aunque el orden de las operaciones en tiempo real requeriría
que la lectura del cliente 3 devuelva un valor que no es más antiguo que el resultado del cliente 2. Este comportamiento no está
permitido en un sistema linealizable.
(t1, v1)
(t0, v0)
obtener (x)
(t0, v0)
OK
OK (t0, v0)
Diapositiva 132
Diapositiva 133
Afortunadamente, es posible hacer que las operaciones get y set sean linealizables usando lecturas y escrituras de quórum. Las
operaciones de configuración no cambian: como antes, envían la actualización a todas las réplicas y esperan el reconocimiento de un
quórum de réplicas.
Para las operaciones de obtención, se requiere otro paso, como se muestra en la diapositiva 134. Un cliente primero debe enviar
la solicitud de obtención a las réplicas y esperar las respuestas de un quórum. Si algunas respuestas incluyen un valor más reciente
que otras respuestas, como lo indican sus marcas de tiempo, entonces el cliente debe volver a escribir el valor más reciente en todas
las réplicas que aún no respondieron con el valor más reciente, como en la reparación de lectura (Diapositiva 100) . La operación de
obtención finaliza solo después de que el cliente está seguro de que el valor más reciente está almacenado en un quórum de réplicas:
es decir, después de que un quórum de réplicas respondió bien a la reparación de lectura o respondió con el valor más reciente en
primer lugar. .
Este enfoque se conoce como el algoritmo ABD, en honor a sus autores Attiya, Bar-Noy y Dolev [Attiya et al., 1995]. Garantiza
lecturas y escrituras linealizables, porque cada vez que finaliza una operación de obtención y configuración,
70
Machine Translated by Google
sabemos que el valor leído o escrito está presente en un quórum de réplicas y, por lo tanto, se garantiza que cualquier lectura
de quórum posterior observará ese valor (o un valor posterior).
(t1, v1)
(t0, v0)
(t1, conjunto (x, v1))
...
OK
OK
...
OK
OK
Diapositiva 134
Diapositiva 135
La operación de conjunto para la que el algoritmo ABD garantiza la linealización es la denominada escritura ciega (escritura
incondicional): simplemente sobrescribe el valor de un elemento de datos, independientemente de su valor anterior. Si varios
clientes escriben al mismo tiempo en el mismo elemento, y si se utiliza una política de resolución de conflictos donde el último
escritor gana (Diapositiva 95), entonces una de esas escrituras terminará como el "ganador" y los otros valores se descartarán
silenciosamente.
En algunas aplicaciones, queremos ser más cuidadosos y sobrescribir un valor solo si otro nodo no lo ha modificado al
mismo tiempo. Esto se puede lograr con una operación atómica de comparación e intercambio (CAS). En la primera mitad de
este curso se analizó una operación CAS para la concurrencia entre subprocesos en un solo nodo. Esto plantea la pregunta:
¿cómo podemos implementar una operación CAS linealizable en un sistema replicado y distribuido?
Recuerde que el propósito de la linealizabilidad es hacer que un sistema se comporte como si hubiera una sola copia de los
datos, y todas las operaciones en él sucedan de forma atómica, incluso si el sistema está de hecho replicado. Esto hace que
CAS sea una operación natural que se desee admitir en un contexto linealizable.
El algoritmo ABD no puede implementar CAS, porque diferentes réplicas pueden ver las operaciones en un orden diferente
y, por lo tanto, llegar a conclusiones inconsistentes sobre si una operación CAS en particular tuvo éxito o no. Sin embargo, es
posible implementar una operación CAS linealizable y replicada utilizando la transmisión de orden total, como se muestra en la
diapositiva 136. Simplemente transmitimos cada operación que queremos realizar y, de hecho, ejecutamos la operación cuando
se entrega. Al igual que en la replicación de máquinas de estado (Diapositiva 101), este algoritmo asegura que una operación
tenga el mismo efecto y resultado en cada réplica.
71
Machine Translated by Google
al entregar (CAS, x, antiguo, nuevo) por emisión de orden total hacer éxito := false if
localState[x] = antiguo then localState[x] := nuevo; exito := final verdadero si devuelve
finalizará el
Diapositiva 136
Ejercicio 17. ¿Es linealizable la siguiente ejecución? Si no, ¿dónde ocurre la violación?
obtener(x)
ÿ1 conjunto(x,
0)
conjunto(x,
1)
obtener(x)
ÿ1
ÿverdad
cas(x,
2)
1,
obtener(x)
ÿ2
case(x,
ÿfalso
3)
0,
obtener(
ÿ4
ÿverdadero
cas(x,
4)
2,
obtener(x)
ÿ2
72
Machine Translated by Google
no puede realizar ninguna operación. Aunque el nodo se esté ejecutando, una falla de comunicación de este tipo hace que no esté
disponible.
Ventajas de linealizabilidad:
Desventajas:
Diapositiva 137
Como ejemplo, considere la aplicación de calendario que puede encontrar en la mayoría de los teléfonos, tabletas y computadoras.
Nos gustaría que las citas y las entradas en esta aplicación se sincronicen en todos nuestros dispositivos; en otras palabras, queremos
que se replique de manera que cada dispositivo sea una réplica. Además, nos gustaría poder ver, modificar y agregar eventos de
calendario incluso cuando un dispositivo está fuera de línea (por ejemplo, debido a una cobertura de red móvil deficiente). Si el protocolo
de replicación de la aplicación de calendario fuera linealizable, esto no sería posible, ya que un dispositivo fuera de línea no puede
comunicarse con un quórum de réplicas.
En cambio, las aplicaciones de calendario permiten al usuario leer y escribir eventos en su calendario incluso cuando un dispositivo
está desconectado, y sincronizan las actualizaciones entre dispositivos en algún momento posterior, en segundo plano, cuando hay una
conexión a Internet disponible. El video de esta conferencia incluye una demostración de actualizaciones sin conexión a un calendario.
Diapositiva 138
Esta compensación se conoce como el teorema CAP (llamado así por la consistencia, la disponibilidad y la tolerancia de partición),
que establece que si hay una partición de red en un sistema, debemos elegir entre una de las siguientes opciones [Gilbert y Lynch,
2002 ]:
1. Podemos tener consistencia linealizable, pero en este caso, algunas réplicas no podrán responder a las solicitudes porque no
pueden comunicarse con un quórum. No poder responder a las solicitudes hace que esos nodos no estén disponibles.
2. Podemos permitir que las réplicas respondan a las solicitudes incluso si no pueden comunicarse con otras réplicas.
En este caso, siguen estando disponibles, pero no podemos garantizar la linearización.
A veces, el teorema CAP se formula como una elección de "elegir 2 de 3", pero ese encuadre es engañoso.
73
Machine Translated by Google
Un sistema puede ser linealizable y disponible siempre que no haya una partición de red, y la elección se fuerza solo en
presencia de una partición [Kleppmann, 2015].
Esta compensación se ilustra en la diapositiva 139, donde el nodo C no puede comunicarse con los nodos A y B. En el lado
de la partición de A y B, las operaciones linealizables pueden continuar con normalidad, porque A y B constituyen un quórum.
Sin embargo, si C desea leer el valor de x, debe esperar (posiblemente indefinidamente) hasta que se repare la partición de la
red, o debe devolver su valor local de x, que no refleja el valor previamente escrito por A en el otro. lado de la partición.
El teorema de la PAC
Un sistema puede ser fuertemente consistente (linealizable) o
Disponible en presencia de una partición de red
redconjunto
v1)
(x,
partició
de
ÿv0 obtener(x)
obtener(x)
ÿv1 obtener
ÿv1
C debe esperar indefinidamente a que la red se recupere o devolver un valor
potencialmente obsoleto
Diapositiva 139
La aplicación de calendario elige la opción 2: renuncia a la linealización a favor de permitir que el usuario continúe realizando
operaciones mientras un dispositivo está fuera de línea. Muchos otros sistemas también hacen esta elección para varios
razones.
El enfoque de permitir que cada réplica procese lecturas y escrituras basándose únicamente en su estado local y sin esperar
la comunicación con otras réplicas se denomina replicación optimista. Se han propuesto una variedad de modelos de consistencia
para sistemas replicados de manera optimista, siendo el más conocido el de consistencia eventual.
La coherencia eventual se define como: "si no se realizan nuevas actualizaciones en un objeto, eventualmente todas las
lecturas devolverán el último valor actualizado" [Vogels, 2009]. Esta es una definición muy débil: ¿qué sucede si las
actualizaciones de un objeto nunca se detienen, por lo que la premisa de la declaración nunca es cierta? Un modelo de
consistencia ligeramente más fuerte llamado consistencia eventual fuerte, definido en la diapositiva 140, suele ser más apropiado [Shapiro et al., 201
Se basa en la idea de que cuando dos réplicas se comunican, convergen hacia el mismo estado.
Consistencia eventual
Las operaciones de procesamiento de réplicas se basan únicamente en su estado local.
Propiedades:
Diapositiva 140
Tanto en la consistencia final como en la consistencia final fuerte, existe la posibilidad de que diferentes nodos actualicen
simultáneamente el mismo objeto, lo que genera conflictos (como se discutió anteriormente en la Diapositiva 95). Se han
desarrollado varios algoritmos para resolver esos conflictos automáticamente [Shapiro et al., 2011].
74
Machine Translated by Google
consenso, cuyo
transmisión de orden parcialmente síncrono
total, CAS linealizable
Diapositiva 141
El software de colaboración es una amplia categoría de software que facilita que varias personas trabajen juntas en alguna
tarea. Esto incluye aplicaciones como Google Docs/Office 365 (documentos de texto multiusuario, hojas de cálculo,
presentaciones, etc.), Overleaf ( documentos LATEX colaborativos), software de gráficos multiusuario (p. ej., Figma),
herramientas de planificación de proyectos (p. ej., Trello), aplicaciones para tomar notas (por ejemplo, OneNote, Evernote,
Notion) y calendarios compartidos entre colegas o familiares (como la sincronización de calendario que vimos en la diapositiva 138).
75
Machine Translated by Google
El software de colaboración moderno permite que varias personas actualicen un documento al mismo tiempo, sin tener que
enviar y recibir archivos por correo electrónico. Esto hace que la colaboración sea otro ejemplo de replicación: cada dispositivo
en el que un usuario ha abierto un documento es una réplica, y cualquier actualización realizada en una réplica debe enviarse a
través de la red a las réplicas en otros dispositivos.
En principio, sería posible utilizar un esquema de replicación linealizable para software de colaboración.
Sin embargo, dicho software sería lento de usar, ya que cada operación de lectura o escritura tendría que ponerse en contacto
con un quórum de réplicas; además, no funcionaría en un dispositivo que no esté conectado. En su lugar, en aras de un mejor
rendimiento y una mayor solidez frente a las interrupciones de la red, la mayoría del software de colaboración utiliza una
replicación optimista que proporciona una fuerte consistencia eventual (Diapositiva 140).
actualizaciones simultáneas?
Familias de algoritmos:
Diapositiva 142
En esta sección veremos algunos algoritmos que se utilizan para este tipo de colaboración. Como ejemplo, considere la
demostración de sincronización del calendario en la grabación de la conferencia de la Sección 7.3. Inicialmente, dos nodos
comienzan con la misma entrada de calendario. En el nodo A, el título cambia de "Conferencia" a "Conferencia 1" y, al mismo
tiempo, en el nodo B, la hora cambia de 12:00 a 10:00. Estas dos actualizaciones ocurren mientras los dos nodos no pueden
comunicarse temporalmente, pero finalmente se restaura la conectividad y los dos nodos sincronizan sus cambios. En el
resultado que se muestra en la diapositiva 143, la entrada final del calendario refleja tanto el cambio en el título como el cambio
en la hora.
nodo A nodo B
{ {
"título": "Conferencia", "título": "Conferencia",
"fecha": "2020-11-05", "fecha": "2020-11-05",
"hora": "12:00" "hora": "12:00"
} }
{ {
"título": "Conferencia 1", "título": "Conferencia 1",
"fecha": "2020-11-05", "fecha": "2020-11-05",
"hora": "10:00" "hora": "10:00"
} }
Diapositiva 143
Este escenario es un ejemplo de resolución de conflictos, que ocurre cada vez que varias escrituras simultáneas en el
mismo objeto deben integrarse en un solo estado final (consulte también la diapositiva 95). Los tipos de datos replicados sin
conflictos, o CRDT para abreviar, son una familia de algoritmos que realizan dicha resolución de conflictos [Shapiro et al., 2011].
Un CRDT es un objeto replicado al que accede una aplicación a través de la interfaz orientada a objetos de un tipo de datos
abstracto, como un conjunto, una lista, un mapa, un árbol, un gráfico, un contador, etc.
La diapositiva 144 muestra un ejemplo de un CRDT que proporciona un mapa de claves a valores. La aplicación
76
Machine Translated by Google
puede invocar dos tipos de operaciones: leer el valor de una clave dada y establecer el valor de una clave dada (que
agrega la clave si aún no está presente).
El estado local en cada nodo consta de los valores establecidos que contienen (marca de tiempo, clave, valor) triples.
La lectura del valor de una clave dada es una operación puramente local que solo inspecciona los valores en el nodo
actual y no realiza ninguna comunicación de red. El algoritmo conserva la invariante de que los valores contienen como
máximo un elemento para cualquier clave dada. Por lo tanto, al leer el valor de una clave, el valor es único si existe.
Diapositiva 144
Para actualizar el valor de una clave dada, creamos una marca de tiempo global única para la operación (una marca
de tiempo de Lamport (Diapositiva 66) es una buena opción) y luego transmitimos un mensaje que contiene la marca de
tiempo, la clave y el valor. Cuando se entrega ese mensaje, verificamos si la copia local de valores ya contiene una
entrada con una marca de tiempo más alta para la misma clave; si es así, ignoramos el mensaje, porque el valor con la
marca de tiempo más alta tiene prioridad. De lo contrario, eliminamos el valor anterior (si lo hay) y agregamos el triple
nuevo (marca de tiempo, clave, valor) a los valores. Esto significa que resolvemos las actualizaciones simultáneas de la
misma clave utilizando el enfoque de último escritor gana (LWW) que vimos en la diapositiva 95.
Este algoritmo es un ejemplo de un enfoque que insinuamos en la diapositiva 104, a saber, un método para realizar
la replicación utilizando una transmisión confiable, sin requerir una entrega totalmente ordenada. Es un CRDT basado en
operaciones porque cada mensaje de difusión contiene una descripción de una operación de actualización (a diferencia
de los CRDT basados en estado que veremos en breve). Permite que las operaciones se completen sin conectividad de
red, porque el remitente de una transmisión confiable puede enviarse un mensaje a sí mismo de inmediato y enviarlo a
otros nodos en algún momento posterior. Además, aunque los mensajes pueden entregarse en diferentes órdenes en
diferentes réplicas, el algoritmo garantiza una fuerte consistencia eventual porque la función que actualiza el estado de
una réplica es conmutativa.
77
Machine Translated by Google
Ejercicio 18. Demuestre que el algoritmo CRDT del mapa basado en operaciones proporciona una fuerte consistencia eventual.
Ejercicio 19. Proporcione un pseudocódigo para una variante del algoritmo CRDT de mapa basado en operaciones que tiene una
semántica de registro de valores múltiples en lugar de una semántica de que el último escritor gana; es decir, cuando hay varias
actualizaciones simultáneas para la misma clave, el algoritmo debe conservar todas esas actualizaciones en lugar de conservar
solo la que tiene la marca de tiempo más grande.
En la diapositiva 146 se muestra un algoritmo CRDT alternativo para el mismo tipo de datos de mapa. La definición de valores
y la función para leer el valor de una clave es la misma que en la diapositiva 144. Sin embargo, las actualizaciones se manejan de
manera diferente: en lugar de transmitir cada operación, actualizamos directamente los valores y luego transmitimos la totalidad
de los valores. Al entregar este mensaje en otra réplica, fusionamos los estados de las dos réplicas usando una función de fusión
t. Esta función de combinación compara las marcas de tiempo de las entradas con la misma clave y conserva las que tienen la
marca de tiempo mayor.
en la inicialización
hacer valores: =
{} final en
Este enfoque de transmitir todo el estado de la réplica y fusionarlo con el estado de otra réplica se denomina CRDT basado
en el estado. La desventaja del enfoque basado en el estado es que es probable que los mensajes de difusión sean más grandes
que en el enfoque basado en operaciones. La ventaja del enfoque basado en el estado es que puede tolerar mensajes perdidos o
duplicados: siempre que dos réplicas finalmente logren intercambiar sus últimos estados, convergerán al mismo estado, incluso si
se perdieron algunos mensajes anteriores. Los mensajes duplicados también están bien porque el operador de combinación es
idempotente (cf. Diapositiva 90). Esta es la razón por la cual un CRDT basado en el estado puede usar una transmisión de mejor
esfuerzo no confiable, mientras que un CRDT basado en operaciones requiere una transmisión confiable (y algunos incluso
requieren una transmisión causal).
Diapositiva 147
78
Machine Translated by Google
Además, los CRDT basados en el estado no se limitan a los sistemas de replicación que utilizan la transmisión. Otros métodos
de replicación, como los algoritmos de escritura de quórum y los protocolos anti-entropía que vimos en la lección 5, también pueden
usar los CRDT para la resolución de conflictos (consulte la diapositiva 94).
Como otro ejemplo de actualizaciones simultáneas y la necesidad de resolución de conflictos, consideraremos el software de
colaboración como Google Docs. Cuando escribe en un documento de Google, las pulsaciones de teclas se aplican inmediatamente
a la copia local del documento en su navegador web, sin esperar a que se sincronicen con un servidor o cualquier otro usuario. Esto
significa que cuando dos usuarios escriben al mismo tiempo, sus documentos pueden diferir temporalmente; a medida que se lleva a
cabo la comunicación de red, el sistema debe garantizar que todos los usuarios converjan en la misma vista del documento. El video
de esta conferencia incluye una demostración de Google Docs que muestra este proceso de resolución de conflictos en acción.
Diapositiva 148
Podemos pensar en un documento de texto editable en colaboración como una lista de caracteres, donde cada usuario puede
insertar o eliminar caracteres en cualquier índice de la lista. Las fuentes, el formato, las imágenes incrustadas, las tablas, etc. añaden
más complejidad, por lo que por ahora nos concentraremos en el texto sin formato. Cuando varios usuarios pueden actualizar
simultáneamente un documento de texto, surge un problema particular, que se demuestra en el ejemplo de la diapositiva 149.
En este ejemplo, dos usuarios A y B comienzan con el mismo documento, "BC". El usuario A agrega el carácter "A" al principio
del documento, para que se lea "ABC". Al mismo tiempo, el usuario B agrega el carácter "D" al final del documento, para que se lea
"BCD". A medida que A y B fusionan sus ediciones, esperaríamos que el documento final diga "ABCD".
En la diapositiva 149, las réplicas de los usuarios se comunican enviándose las operaciones que han realizado. El usuario A
envía (insertar, 0, "A") a B, y B aplica esta operación, lo que lleva al resultado deseado "ABCD". Sin embargo, cuando B envía
(insertar, 2, "D") a A y A inserta el carácter "D" en el índice 2, el resultado es "ABDC", no el "ABCD" esperado.
usuario A usuario B
01 01
ABC BCD
012 012
ABDC ABCD
0123 0123
Diapositiva 149
79
Machine Translated by Google
El problema es que en el momento en que B realizó la operación de inserción (2, "D"), el índice 2 se refería a la
posición posterior al carácter "C". Sin embargo, la inserción simultánea de A en el índice 0 tuvo el efecto de aumentar los
índices de todos los caracteres posteriores en 1, por lo que la posición después de "C" ahora es el índice 3, no el índice 2.
La transformación operativa es un enfoque que se utiliza para resolver este problema. Existe una familia de diferentes
algoritmos que utilizan este enfoque y que varían en los detalles de cómo resuelven los conflictos. Pero el principio
general que tienen en común se ilustra en la diapositiva 150.
Transformación operativa
usuario A usuario B
ABC BCD
012 012
ABCD ABCD
0123 0123
Diapositiva 150
Un nodo realiza un seguimiento del historial de operaciones que ha realizado. Cuando un nodo recibe la operación
de otro nodo que es concurrente con una o más de sus propias operaciones, transforma la operación entrante en relación
con sus propias operaciones concurrentes.
La función T(op1 , op2 ) toma dos operaciones: op1 es una operación entrante y op2 es una operación local
concurrente. T devuelve una operación transformada op0 tal que 1
aplicar op0 al por
originalmente estado
op1local
. Por
1
tiene
2, el yefecto
ejemplo,
“D”) op2 =previsto
si op1(insertar,
= (insertar,
0,
“A”) entonces la operación transformada es T(op1 , op2 ) = (insertar, 3, “D”) porque el la inserción original op1 en el índice
2 ahora debe realizarse en el índice 3 debido a la inserción concurrente en el índice 0. Por otro lado, T(op2 , op1 ) = op2
devuelve el op2 no modificado porque la inserción en el índice 0 no es afectado por una inserción concurrente posterior
en el documento.
La función de transformación se vuelve más complicada cuando se tienen en cuenta las eliminaciones, el formateo,
etc., y omitiremos los detalles. Sin embargo, este enfoque se utiliza en la práctica: por ejemplo, el algoritmo de resolución
de conflictos en Google Docs utiliza un enfoque de transformación operativa basado en el sistema de investigación Júpiter
de Xerox PARC [Nichols et al., 1995]. Una limitación de este enfoque es que requiere la comunicación entre los usuarios
para usar la transmisión de orden total, lo que requiere el uso de un nodo líder designado para secuenciar las
actualizaciones, o un algoritmo de consenso como en la lección 6.
Una alternativa a la transformación operativa, que evita la necesidad de una transmisión de orden total, es usar un
CRDT para la edición de texto. En lugar de identificar posiciones en el texto mediante índices y, por lo tanto, necesitar
una transformación operativa, los CRDT de edición de texto funcionan adjuntando un identificador único a cada carácter.
Estos identificadores permanecen sin cambios, incluso si se insertan o eliminan los caracteres circundantes.
Se han propuesto varias construcciones para estos identificadores únicos, una de las cuales se ilustra en la diapositiva
151. Aquí, a cada carácter se le asigna un número racional i ÿ Q con 0 < i < 1, donde 0 representa el comienzo del
documento, 1 es el final y los números intermedios identifican los caracteres del documento en orden ascendente.
También usamos el símbolo ` para representar el comienzo del documento y a para representar el final; estos símbolos
son parte del estado interno del algoritmo, no visibles para el usuario.
Cuando queremos insertar un nuevo carácter entre dos caracteres adyacentes existentes con números de posición i
y j, podemos asignar a ese nuevo carácter un número de posición de i+j que siempre se 2,
encuentra
posición siempre
entre i y existe,
j. Esta siempre
nueva
que usemos aritmética de precisión arbitraria (los números de coma flotante tienen una precisión limitada, por lo que ya
no funcionarán una vez que los intervalos se vuelvan demasiado pequeños). Es posible que dos nodos diferentes generen
caracteres con el mismo número de posición si se insertan simultáneamente en la misma posición, por lo que podemos
usar el ID del nodo que generó un carácter para desempatar cualquier carácter que tenga el mismo número de posición.
Usando este enfoque, la resolución de conflictos se vuelve fácil: una inserción con un número de posición particular
80
Machine Translated by Google
simplemente se puede transmitir a otras réplicas, que luego agregan ese carácter a su conjunto de caracteres y ordenan por número de
posición para obtener el documento actual.
usuario A usuario B
` BC un ` BC un
0,0 0,5 0,75 1,0 0,0 0,5 0,75 1,0
`ABC `BCD a
0,0 0,25 0,5 0,75 1,0 0,0 0,5 0,75 0,875 1,0
` ABCD un ` ABCD un
0,0 0,25 0,5 0,75 0,875 1,0 0,0 0,25 0,5 0,75 0,875 1,0
Diapositiva 151
Este algoritmo se muestra en las siguientes dos diapositivas. El estado de una réplica son los caracteres establecidos, que contienen
(posición, ID de nodo, carácter) se triplica.
en la inicialización do
chars := {(0, null, `), (1, null, a)} end on
a pedido para insertar el carácter v en el índice índice en el nodo nodeId do let (p1,
n1, v1) := ElementAt(chars, index ) let (p2, n2, v2) := ElementAt(chars, index +
1) broadcast (insertar ,(p1 + p2)/2, nodeId, v) por final de transmisión causal
el
Diapositiva 152
Diapositiva 153
81
Machine Translated by Google
La función ElementAt itera sobre los elementos de chars en orden ascendente del número de posición.
Lo hace encontrando primero el elemento mínimo, es decir, el elemento para el cual no existe otro elemento con un número de
posición más bajo. Si hay varios elementos con el mismo número de posición, se elige el elemento con el ID de nodo más bajo.
Si index = 0, devolvemos este elemento mínimo; de lo contrario, eliminamos el elemento mínimo, disminuimos el índice y
repetimos. (Este es un algoritmo bastante lento; una implementación real haría un esfuerzo por ser más eficiente).
Los caracteres de una réplica se inicializan con elementos para ` y a. Para obtener el carácter en un índice particular,
usamos el ElementAt que acabamos de definir, agregando 1 al índice para omitir el primer elemento en caracteres, que siempre
es (0, nulo, `).
Para insertar un carácter en una posición particular, obtenemos los números de posición p1 y p2 del predecesor y sucesor
inmediatos, y luego calculamos el nuevo número de posición como (p1+p2)/2. Difundimos entonces esta operación por
transmisión causal. En la entrega de un mensaje de inserción, simplemente agregamos el triple a chars.
Para eliminar un carácter en una posición particular, usamos ElementAt, agregando 1 para omitir ` como antes, para
encontrar el número de posición y el ID de nodo de ese carácter. Luego, transmitimos esta información, que identifica de manera
única a un carácter en particular, mediante una transmisión causal como un mensaje de eliminación. En la entrega de un
mensaje de eliminación, una réplica elimina el elemento en caracteres que coincida tanto con el número de posición como con
el ID de nodo en el mensaje, si existe.
La razón para usar la transmisión causal (en lugar de solo la transmisión confiable) en este algoritmo es garantizar que si
se elimina un carácter, todas las réplicas procesan la inserción del carácter antes de procesar la eliminación. Esta restricción es
necesaria porque las operaciones para insertar y eliminar el mismo carácter no conmutan. Sin embargo, las inserciones y
eliminaciones de diferentes caracteres conmutan, lo que permite que este algoritmo asegure la convergencia y una fuerte
consistencia eventual.
Propiedades de consistencia:
Aislamiento de transacciones serializable
Muchas de las técnicas utilizadas por Spanner son muy convencionales y las hemos visto anteriormente en este curso:
utiliza el algoritmo de consenso de Paxos para la replicación de máquinas de estado, bloqueo de dos fases para garantizar el
aislamiento serializable entre transacciones y compromiso de dos fases para garantizar compromiso atómico. Se requiere
mucho esfuerzo de ingeniería para hacer que estos algoritmos funcionen bien en la práctica, pero a nivel arquitectónico,
82
Machine Translated by Google
Spanner evita los bloqueos en las transacciones de solo lectura al permitir que una transacción lea desde una
instantánea consistente de la base de datos: es decir, la transacción observa toda la base de datos tal como era en un
solo momento, incluso si algunas partes de la base de datos son subsecuentemente actualizado por otras transacciones
mientras se ejecuta la transacción de solo lectura. La palabra "consistente" en el contexto de una instantánea significa
que es consistente con la causalidad: si la transacción T1 ocurrió antes de la transacción T2, y si la instantánea contiene
los efectos de T2, entonces también debe contener los efectos de T1.
Instantáneas consistentes
Una transacción de solo lectura observa una instantánea consistente: si T1 ÿ T2
I Tr ignora los valores con tw > tr; observa el valor más reciente con tw ÿ tr
Diapositiva 155
La implementación de instantáneas de Spanner utiliza el control de concurrencia de múltiples versiones (MVCC), una forma
particular de control de concurrencia optimista similar a lo que se discutió en la primera mitad de este curso.
MVCC se basa en asignar una marca de tiempo de compromiso a cada transacción; cada objeto de datos está etiquetado con
la marca de tiempo de la transacción que lo escribió. Cuando se actualiza un objeto, no solo lo sobrescribimos, sino que
almacenamos varias versiones antiguas (cada una etiquetada con una marca de tiempo) además de la última versión. La
instantánea de una transacción de solo lectura también se define mediante una marca de tiempo: es decir, la transacción lee la
versión más reciente de cada objeto que precede a la marca de tiempo de la instantánea e ignora cualquier versión del objeto
cuya marca de tiempo sea mayor que la de la instantánea. Muchas otras bases de datos también usan MVCC, pero lo que hace
que Spanner sea especial es la forma en que asigna marcas de tiempo a las transacciones.
Para garantizar que las instantáneas sean consistentes con la causalidad, el algoritmo MVCC requiere que si la transacción
T1 ocurrió antes de la transacción T2, entonces la marca de tiempo de confirmación de T1 debe ser menor que la de T2. Sin
embargo, recuerde de la diapositiva 61 que las marcas de tiempo de los relojes físicos no necesariamente satisfacen esta
propiedad. Por lo tanto, nuestra respuesta natural debería ser usar marcas de tiempo lógicas, como las marcas de tiempo de
Lamport, en su lugar (Sección 4.1).
Desafortunadamente, las marcas de tiempo lógicas también tienen problemas. Considere el ejemplo de la diapositiva 156,
donde un usuario observa los resultados de la transacción T1 y luego realiza alguna acción, que se ejecuta en una transacción
T2. Esto significa que tenemos una dependencia en tiempo real (Sección 7.2) entre las transacciones: T1 debe haber terminado
antes de que comience T2 y, por lo tanto, esperamos que T2 tenga una marca de tiempo mayor que T1.
Sin embargo, las marcas de tiempo de Lamport no pueden garantizar necesariamente esta propiedad de ordenación: recuerde
que funcionan adjuntando una marca de tiempo a cada mensaje que se comunica a través de la red y tomando el máximo cada
vez que se recibe dicho mensaje. Sin embargo, en el ejemplo de la diapositiva 156, es posible que no se envíe ningún mensaje
desde la réplica A, donde se ejecuta T1 , a la réplica B, donde se ejecuta T2 . En cambio, la comunicación se realiza a través
de un usuario, y no podemos esperar que un ser humano incluya una marca de tiempo formada correctamente.
83
Machine Translated by Google
en cada acción que realizan. Sin un mecanismo confiable para propagar la marca de tiempo en cada paso de la comunicación,
las marcas de tiempo lógicas no pueden proporcionar la garantía de pedido que necesitamos.
A B
T1
resultados
acción
T2
Diapositiva 156
Otra opción para generar marcas de tiempo lógicas sería tener un único servidor designado que firme las marcas de
tiempo de las transacciones. Sin embargo, este enfoque falla en una base de datos distribuida globalmente, ya que ese servidor
se convertiría en un único punto de falla y un cuello de botella en el rendimiento. Además, si las transacciones que se ejecutan
en un continente diferente al del servidor de marca de tiempo deben esperar una respuesta, el inevitable tiempo de ida y vuelta
debido a los retrasos a la velocidad de la luz haría que las transacciones fueran lentas de ejecutar. Se requiere un enfoque
menos centralizado para las marcas de tiempo.
Aquí es donde entra en juego el mecanismo TrueTime de Spanner. TrueTime es un sistema de relojes físicos que no
devuelve una sola marca de tiempo, sino un intervalo de incertidumbre. Aunque no podemos garantizar relojes perfectamente
sincronizados en sistemas prácticos (sección 3.2), podemos realizar un seguimiento de los errores que pueden introducirse en
varios puntos del sistema. Para los relojes atómicos, los límites de error son informados por el fabricante. Para los receptores
GPS, el error depende de la calidad de las señales de los satélites actualmente dentro del alcance. El error que se presenta al
sincronizar relojes en una red depende del tiempo de ida y vuelta (ejercicio 5). El error de un reloj de cuarzo depende de su
tasa de desviación y del tiempo transcurrido desde su última sincronización con un reloj más preciso.
Cuando le pide a TrueTime la marca de tiempo actual, devuelve un intervalo [más temprano, más reciente]. El sistema no
conoce la verdadera marca de tiempo física actual treal, pero puede garantizar que tearliest ÿ treal ÿ tlatest con una probabilidad
muy alta al tener en cuenta todas las fuentes de error anteriores.
TrueTime: incertidumbre explícita del reloj físico El reloj TrueTime de Spanner devuelve
[tearliest, tlatest].
La verdadera marca de tiempo física debe estar dentro de ese rango.
Al confirmar, espere la incertidumbre ÿi = ti,latest ÿ ti,earliest.
tiempo
A fisico B
compromiso requerido
ÿ1
ÿ1
t1, último
compromiso hecho tiempo real
Diapositiva 157
Cuando la transacción Ti desea confirmar en Spanner, obtiene un intervalo de marca de tiempo [ti,earliest, ti,latest] de
TrueTime y asigna ti,latest para que sea la marca de tiempo de confirmación de Ti . Sin embargo, antes de que la transacción
realmente se comprometa y libere sus bloqueos, primero se detiene y espera una duración igual al período de incertidumbre del reloj.
84
Machine Translated by Google
ÿi = ti,último ÿ ti,primero. Solo después de que haya transcurrido este tiempo de espera, la transacción se confirma y sus escrituras
se vuelven visibles para otras transacciones.
Aunque no tenemos relojes perfectamente sincronizados y, por lo tanto, un nodo no puede saber la hora física exacta de un evento,
este algoritmo asegura que la marca de tiempo de una transacción sea menor que la hora física real en el momento en que se confirma la
transacción. Por lo tanto, si T2 comienza más tarde en tiempo real que T1, la marca de tiempo más temprana posible que podría asignarse
a T2 debe ser mayor que la marca de tiempo de T1.
Dicho de otra manera, la espera garantiza que los intervalos de marca de tiempo de T1 y T2 no se superpongan, incluso si las transacciones
se ejecutan en nodos diferentes sin comunicación entre las dos transacciones.
Dado que cada transacción tiene que esperar a que transcurra el intervalo de incertidumbre, el desafío ahora es mantener ese intervalo
de incertidumbre lo más pequeño posible para que las transacciones sigan siendo rápidas. Google logra esto instalando relojes atómicos y
receptores GPS en cada centro de datos y sincronizando el reloj de cuarzo de cada nodo con un servidor horario en el centro de datos local
cada 30 segundos. En el centro de datos local, los viajes de ida y vuelta suelen ser inferiores a 1 ms, por lo que el error de reloj introducido
por la latencia de la red es bastante pequeño. Si aumenta la latencia de la red, por ejemplo, debido a la congestión, el intervalo de
incertidumbre de TrueTime crece en consecuencia para dar cuenta del aumento del error.
2
tiempo
incertidumbre del servidor + tiempo de ida y vuelta al servidor del reloj
0
0 10 20 30 40 50 60 70 80 90
Entre las sincronizaciones periódicas del reloj cada 30 segundos, el reloj de un nodo está determinado únicamente por su oscilador de
cuarzo local. El error introducido aquí depende de la tasa de deriva del cuarzo. Para estar seguro, Google supone una tasa de deriva
máxima de 200 ppm, que es significativamente más alta que la deriva observada en condiciones normales de funcionamiento (Diapositiva
45). Además, Google monitorea la deriva de cada nodo y alerta a los administradores sobre cualquier valor atípico.
¿Es la tasa de deriva de 200 ppm una suposición segura? Según el artículo de Spanner: “Las estadísticas de nuestras máquinas
muestran que las CPU defectuosas son 6 veces más probables que los relojes defectuosos. Es decir, los problemas de reloj son
extremadamente poco frecuentes, en comparación con problemas de hardware mucho más graves. Como resultado, creemos que la
implementación de TrueTime es tan confiable como cualquier otra pieza de software de la que depende Spanner”. [Corbett et al., 2012].
Si asumimos una deriva de cuarzo de 200 ppm y han pasado 30 segundos desde la última sincronización del reloj, esto implica una
incertidumbre de reloj de 6 ms debido a la deriva de cuarzo (además de cualquier incertidumbre de la latencia de la red, el receptor GPS y
los relojes atómicos) . El resultado es un intervalo de incertidumbre que crece gradualmente con el tiempo transcurrido desde la última
sincronización del reloj, hasta aproximadamente 7 ms, y que se restablece a aproximadamente 1 ms (tiempo de ida y vuelta + incertidumbre
del servidor del reloj) en cada sincronización del reloj, como se muestra en la figura. Diapositiva 158.
El intervalo de incertidumbre medio es, por tanto, de aproximadamente 4 ms en condiciones normales de funcionamiento, y estos 4 ms
son, por tanto, el tiempo medio que debe esperar una transacción antes de poder comprometerse. Esto es mucho más rápido de lo que
podrían ser las transacciones si tuvieran que esperar un viaje de ida y vuelta de la red intercontinental (que tomaría del orden de 100 ms o
más).
Para resumir: a través de una contabilidad cuidadosa de la incertidumbre, TrueTime proporciona límites superiores e inferiores en el
tiempo físico actual; a través de relojes de alta precisión mantiene pequeño el intervalo de incertidumbre; al esperar a que pase la
incertidumbre, Intervalo Spanner asegura que las marcas de tiempo sean consistentes con la causalidad; y al usar esas marcas de tiempo
para MVCC, Spanner proporciona transacciones serializables sin necesidad de bloqueos para transacciones de solo lectura. Este enfoque
mantiene las transacciones rápidas, sin imponer ningún requisito a los clientes para propagar marcas de tiempo lógicas.
85
Machine Translated by Google
Resumen:
I Los sistemas distribuidos están en todas
partes I Los usa todos los días: por ejemplo,
aplicaciones web I Objetivos clave: disponibilidad,
escalabilidad, rendimiento I Problemas clave: simultaneidad, fallas,
latencia ilimitada I Abstracciones clave: replicación, transmisión,
consenso I Nadie de manera correcta , solo compensaciones
Diapositiva 159
Esto nos lleva al final del curso sobre Sistemas Concurrentes y Distribuidos. Partimos de una premisa simple: cuando envías un mensaje
por la red y no obtienes respuesta, no sabes lo que pasó. Tal vez el mensaje se perdió, o la respuesta se perdió, o el mensaje se retrasó, o
el nodo remoto falló, y no podemos distinguir entre estos tipos de fallas.
Los sistemas distribuidos son fascinantes porque tenemos que trabajar con conocimientos parciales y verdades inciertas. Nunca tenemos
certeza sobre el estado del sistema, porque para cuando nos enteramos de algo, ese estado ya puede estar desactualizado. ¡De esta manera
se parece más a la vida real que a la mayoría de las computadoras! En la vida real, a menudo necesitas tomar decisiones con información
incompleta.
Pero los sistemas distribuidos también son inmensamente prácticos: todos los sitios web y la mayoría de las aplicaciones son sistemas
distribuidos, y los servidores y las bases de datos que subyacen a la mayoría de los sitios web son, a su vez, sistemas distribuidos adicionales.
Después de graduarse, muchos de ustedes terminarán trabajando en dichos sistemas. Con suerte, las ideas de este curso le han brindado
una base sólida para que pueda ir y hacer que esos sistemas sean confiables y comprensibles.
Referencias
Steven L Allen. ¡Los aviones se estrellarán! Cosas que los segundos intercalares no causaron, y causaron, 2013. URL http://
www.hanksville.org/futurofutc/preprints/files/2 AAS%2013-502 Allen.pdf.
Hagit Attiya, Amotz Bar-Noy y Danny Dolev. Compartiendo memoria de manera robusta en sistemas de paso de mensajes. Journal of the
ACM, 42(1):124–142, enero de 1995. doi:10.1145/200836.200869. URL http://www.cse.huji.ac.il/course/2004/dist/p124-attiya.pdf .
Peter Bailis y Kyle Kingsbury. La red es confiable. Cola ACM, 12(7), 2014. doi:10.1145/2639988.2639988. URL
https://queue.acm.org/detail.cfm?id=2655736.
Tushar Deepak Chandra y Sam Toueg. Detectores de fallas poco confiables para sistemas distribuidos confiables. Journal of the ACM,
43(2):225–267, marzo de 1996. doi:10.1145/226643.226647. URL http://courses.csail.mit.edu/6.852/08/papers/CT96-JACM.pdf .
James C. Corbett, Jeffrey Dean, Michael Epstein, Andrew Fikes, Christopher Frost, JJ Furman, Sanjay Ghemawat, Andrey Gubarev,
Christopher Heiser, Peter Hochschild, Wilson C. Hsieh, Sebastian Kanthak, Eugene Kogan, Hongyi Li, Alexander Lloyd, Sergey Melnik,
David Mwaura, David Nagle, Sean Quinlan, Rajesh Rao, Lindsay Rolig, Yasushi Saito, Michal Szymaniak, Christopher Taylor, Ruth
Wang y Dale Woodford. Spanner: la base de datos distribuida globalmente de Google. En el 10º Simposio USENIX sobre Diseño e
Implementación de Sistemas Operativos, OSDI 2012, octubre de 2012.
URL https://www.usenix.org/conference/osdi12/technical-sessions/presentation/corbett.
Giuseppe DeCandia, Deniz Hastorun, Madan Jampani, Gunavardhan Kakulapati, Avinash Lakshman, Alex Pilchin, Swaminathan
Sivasubramanian, Peter Vosshall y Werner Vogels. Dynamo: la tienda clave-valor de alta disponibilidad de Amazon. ACM SIGOPS
Operating Systems Review, 41 (6): 205–220, diciembre de 2007. doi: 10.1145 / 1323293.1294281. URL http://www.allthingsdistributed.com/
files/amazon-dynamo-sosp2007.pdf.
Cynthia Dwork, Nancy A. Lynch y Larry Stockmeyer. Consenso en presencia de sincronía parcial. Journal of the ACM, 35(2):288–323,
abril de 1988. doi:10.1145/42282.42283. URL http://www.net.t-labs.tu-berlin.de/ÿpetr/ADC 07/documentos/DLS88.pdf.
Roy Thomas Fielding. Estilos arquitectónicos y diseño de arquitecturas de software basadas en red. tesis doctoral, Universidad
de California, Irvine, 2000. URL https://www.ics.uci.edu/ÿfielding/pubs/dissertation/top.htm.
86
Machine Translated by Google
Michael J. Fischer, Nancy A. Lynch y Michael S. Paterson. Imposibilidad de consenso distribuido con un proceso defectuoso.
Journal of the ACM, 32(2):374–382, abril de 1985. doi:10.1145/3149.214121. URL https://groups.csail.mit.edu/tds/papers/ Lynch/jacm85.pdf.
Seth Gilbert y Nancy Lynch. La conjetura de Brewer y la viabilidad de servicios web coherentes, disponibles y tolerantes a particiones. ACM
SIGACT News, 33(2):51–59, junio de 2002. doi:10.1145/564585.564601. URL https://www.comp.nus.edu.sg/ ÿgilbert/pubs/BrewersConjecture-
SigAct.pdf.
Jim Grey y Leslie Lamport. Consenso sobre el compromiso de la transacción. ACM Transactions on Database Systems, 31(1):133–160, marzo
de 2006. doi:10.1145/1132863.1132867. URL http://db.cs.berkeley.edu/cs286/papers/paxoscommit-tods2006.pdf.
Jim N. Gray. Notas sobre sistemas operativos de bases de datos. En R. Bayer, RM Graham y G. Seegmüller, editores, Op erating Systems,
volumen 60 de LNCS, páginas 393–481. Springer, 1978. doi:10.1007/3-540-08755-9 9. URL http: //jimgray.azurewebsites.net/papers/
dbos.pdf.
Mauricio Herlihy. Sincronización sin esperas. Transacciones de ACM en lenguajes y sistemas de programación, 13(1):124–149,
Enero de 1991. doi:10.1145/114005.102808. URL http://cs.brown.edu/ÿmph/Herlihy91/p124-herlihy.pdf.
Maurice P. Herlihy y Jeannette M. Wing. Linealizabilidad: una condición de corrección para objetos concurrentes. ACM Transactions on
Programming Languages and Systems, 12(3):463–492, julio de 1990. doi:10.1145/78969.78972. URL http://cs.brown.edu/ÿmph/HerlihyW90/
p463-herlihy.pdf.
Heidi Howard y Richard Mortier. Paxos vs Raft: ¿hemos llegado a un consenso sobre el consenso distribuido? En 7th Workshop on Principles
and Practice of Consistency for Distributed Data, PaPoC, abril de 2020. doi:10.1145/3380787.3393681. URL https://arxiv.org/abs/2004.05074.
Marcos Imbriaco. Tiempo de inactividad el sábado pasado, diciembre de 2012. URL https://github.com/blog/1364-downtime-last-saturday.
Martín Kleppmann. Una crítica del teorema CAP. arXiv, septiembre de 2015. URL http://arxiv.org/abs/1509.05393.
Sandeep S. Kulkarni, Murat Demirbas, Deepak Madappa, Bharadwaj Avva y Marcelo Leone. Relojes físicos lógicos.
En 18th International Conference on Principles of Distributed Systems (OPODIS), volumen 8878 de LNCS, páginas 17–32.
Springer, diciembre de 2014. doi:10.1007/978-3-319-14472-6 2. URL https://cse.buffalo.edu/ÿdemirbas/publications/hlc.pdf.
Leslie Lamport. Tiempo, relojes y el orden de los eventos en un sistema distribuido. Comunicaciones de la ACM, 21(7): 558–565, 1978.
doi:10.1145/359545.359563. URL http://research.microsoft.com/en-US/um/people/Lamport/pubs/time clocks.pdf.
Leslie Lamport. El parlamento a tiempo parcial. ACM Transactions on Computer Systems, 16(2):133–169, mayo de 1998.
doi:10.1145/279227.279229. URL http://research.microsoft.com/en-us/um/people/lamport/pubs/lamport-paxos.pdf.
Leslie Lamport, Robert Shostak y Marshall Pease. El problema de los generales bizantinos. ACM Transactions on Programming Languages
and Systems, 4(3):382–401, 1982. doi:10.1145/357172.357176. URL http://research.microsoft.com/en-us/um/ people/lamport/pubs/byz.pdf.
Nelson Minar. El segundo intercalar colapsa la mitad de Internet, julio de 2012. URL http://www.somebits.com/weblog/tech/bad/leap
segundo-2012.html.
David A. Nichols, Pavel Curtis, Michael Dixon y John Lamping. Ventanas de alta latencia y bajo ancho de banda en el sistema de colaboración
de Júpiter. En el 8º Simposio Anual de ACM sobre Interfaz de Usuario y Tecnología de Software, UIST 1995, páginas 111–120, noviembre
de 1995. doi:10.1145/215585.215706. URL http://www.lively-kernel.org/repository/webwerkstatt/projects/Collaboration/paper/Jupiter.pdf .
Diego Ongaro y John Ousterhout. En busca de un algoritmo de consenso comprensible. En Conferencia Técnica Anual USENIX, ATC. USENIX,
junio de 2014. URL https://www.usenix.org/conference/atc14/technical-sessions/presentation/
óngaro
Nuno Pregui¸ca, Carlos Baquero, Paulo Sérgio Almeida, Victor Fonte, and Ricardo Gon¸calves. Vectores de versión punteada:
Relojes lógicos para replicación optimista, noviembre de 2010. URL http://arxiv.org/pdf/1011.5808v1.pdf.
Marc Shapiro, Nuno Pregui¸ca, Carlos Baquero, and Marek Zawirski. Tipos de datos replicados sin conflictos. En 13.° Simposio internacional
sobre estabilización, seguridad y protección de sistemas distribuidos, SSS, páginas 386–400, octubre de 2011. doi:10.1007/978-3-642-24550-3
29. URL https://pages.lip6.fr/Marek.Zawirski/papers/RR-7687.pdf.
Martín Thompson. Recolección de basura de Java destilada, junio de 2013. URL https://www.infoq.com/articles/Java Garbage
Colección Destilados/.
Jim Waldo, Geoff Wyant, Ann Wollrath y Sam Kendall. Una nota sobre computación distribuida. Informe Técnico TR-94-29,
Laboratorios Sun Microsystems, 1994. URL http://m.mirror.facebook.net/kde/devel/smli tr-94-29.pdf.
87