De vez en cuando recibimos preguntas sobre temas que no encajan directamente en la documentación, ya que profundizan en las interioridades de las librerías cliente. En esta serie cubriremos diferentes temas de interés - en este caso se trata de cómo el SDK arranca por sí mismo.
Tenga en cuenta que este post fue escrito con las versiones Java SDK 2.5.9 / 2.6.0 en mente. Las cosas podrían cambiar con el tiempo, pero el enfoque general debería seguir siendo el mismo.
Primeros pasos
Para saber a qué hosts remotos solicitar una configuración en general, nos basamos en la lista de nombres de host que el usuario proporciona a través de la opción CouchbaseCluster API. Así, por ejemplo, si se proporciona este código:
1 |
CouchbaseCluster grupo = CouchbaseCluster.crear("nodo1", "nodo2", "nodo3"); |
A continuación, vamos a utilizar nodo1 , nodo2 y nodo3 como parte del proceso inicial de arranque. Es importante entender en este punto que todavía no se ha establecido ninguna conexión. El código sólo almacena estos tres hosts en el directorio ConfigurationProvider como hosts semilla. Sin embargo, no se almacenan como cadenas, sino como un Dirección de red cada uno de los cuales proporciona una envoltura conveniente y más rica en características sobre el JDK simple Dirección Inet . Por ejemplo, puede modificarse a través de las propiedades del sistema para no permitir búsquedas DNS inversas o forzar IPv4 sobre IPv6 aunque el JDK esté configurado de otra forma.
Si no se proporciona ningún host, 127.0.0.1 se establecerá como anfitrión semilla.
Para los lectores que también utilicen un SDK que dependa de la aplicación cadena de conexión para la configuración: el SDK de Java analizará dicha cadena cuando se le proporcione, pero no utilizará ninguna de las opciones de configuración aparte de la lista de nodos de arranque. Toda configuración debe aplicarse a través de la función CouchbaseEnvironment y su constructor o a través de las propiedades del sistema cuando estén disponibles.
Una última cosa que tenemos que cubrir antes de abrir un cubo y meternos en faena. Si el CouchbaseEnvironment está configurado con dnsSrvEnabled Entonces, durante la fase de construcción del cluster, el código realizará una búsqueda DNS SRV para recuperar los hosts reales en lugar del host de referencia DNS SRV. El código utiliza clases JVM para hacer esto, el código se puede encontrar aquí.
Obtener una configuración
Ahora que tenemos una lista de nodos semilla con los que trabajar, en cuanto se abre un cubo comienza el verdadero trabajo. Consideremos el siguiente fragmento de código como punto de partida:
1 |
Cubo cubo = grupo.openBucket("viaje-muestra"); |
El acto de abrir un cubo tiene un montón de pequeños pasos que hay que completar, pero el primero y más importante es obtener una buena configuración inicial de uno de los nodos del clúster.
En ConfigurationProvider construye una cadena de cargadores de configuración, donde cada cargador tiene un mecanismo diferente de tratar de obtener una configuración. Actualmente hay dos cargadores diferentes en su lugar:
– CarrierLoaderintenta obtener la configuración a través del protocolo binario memcache del servicio KV (puerto
11210 por defecto).
– HttpLoaderintenta obtener la configuración del gestor de clústeres (puerto
8091 por defecto).
En CarrierLoader tiene prioridad, ya que es la opción más escalable (el servicio KV es capaz de gestionar peticiones por segundo mucho mayores que el gestor de clústeres) y, si no está disponible, el SDK recurrirá a la opción HttpLoader . Una de las razones por las que podría fallar es que ciertas versiones (antiguas) de Couchbase Server no soportan este mecanismo. Otros fallos en general incluyen el puerto 11210 bloqueados por cortafuegos (lo que, de todos modos, acarrea otros problemas más adelante...) o que los enchufes dejen de funcionar.
En HttpLoader también tiene dos HTTP endpoints para obtener (dependiendo de la versión de Couchbase Server de nuevo para compatibilidad con versiones anteriores) uno que proporciona una configuración más escueta y otro que proporciona una verbosa. La verbosa es la que ha estado ahí desde los días de Couchbase Server 1.8/2.0 y está garantizado que funcione, pero las nuevas características probablemente no estén expuestas ahí.
Aquí se entiende el tema: los enfoques más nuevos/más escalables se prueban primero con opciones de reserva para clientes más antiguos y configuraciones personalizadas. En general, el gestor de clústeres siempre es responsable de gestionar y proporcionar la configuración actualizada, pero también se envía al servicio KV para que los SDK puedan recuperarla con un comando especial.
Como caso especial para servidores antiguos, si la configuración se cargó a través del comando HttpLoader el SDK adjuntará una conexión de streaming a uno de los puntos finales HTTP para recibir actualizaciones de configuración. Si el CarrierLoader tuvo éxito, vamos a seguir sondeando con un intervalo de sondeo configurable para una configuración a través del protocolo binario en su lugar, ya que es más eficiente de los recursos en el lado del servidor (el servicio KV es mucho más adecuado para manejar potencialmente muchas peticiones que el gestor de clúster, que es la razón por la que la carga portadora se implementó en primer lugar).
Cubriremos los diferentes casos de error más adelante, por ahora supongamos que fuimos capaces de obtener una configuración válida de uno de los cargadores. En este punto se envía de nuevo al proveedor de configuración que luego se comprueba y se envía a la carpeta RequestHandler.
Es posible que el proveedor de configuración no esté reenviando la configuración, siendo la razón principal que esté obsoleta o no sea lo suficientemente nueva. Cada configuración tiene una revisión que viene con ella y el SDK rastrea internamente este número de revisión para propagar sólo las configuraciones que son más recientes que la ya procesada. Durante una operación de reequilibrio esto puede ocurrir con bastante frecuencia, y si tiene habilitado el registro de depuración puede detectarlo buscando una línea de registro con el mensaje: No aplicando nuevo configuración, mayor o mismo rev ID .
Tan pronto como el RequestHandler obtiene una nueva configuración empujada inicia un reconfiguración. La reconfiguración sólo tiene una tarea: tomar la configuración proporcionada por el servidor y cambiar el estado interno del SDK para reflejarla. Esto puede significar añadir o eliminar referencias a nodos, puntos finales de servicio o sockets. El SDK refleja el lenguaje común del servidor, por lo que gestiona Nodos que proporcionan Servicios que puede tener uno o más Puntos finales.
Si habilita el registro de depuración, podrá ver durante el arranque cómo se habilitan los diferentes nodos, servicios y puntos finales en función de la configuración proporcionada:
1 2 3 4 5 6 7 |
[cb-cálculos-8] 2018-06-20 17:43:55 DEBUG RequestHandler:435 - Inicio reconfiguración. [cb-cálculos-8] 2018-06-20 17:43:55 DEBUG RequestHandler:527 - Inicio reconfiguración para cubo viaje-muestra [cb-cálculos-8] 2018-06-20 17:43:55 DEBUG RequestHandler:292 - Nodo Dirección de red{localhost/127.0.0.1, fromHostname=falso, reverseDns=verdadero} ya registrado, saltarse. [cb-cálculos-8] 2018-06-20 17:43:55 DEBUG RequestHandler:352 - Tengo instruido a añada Servicio BINARIO, a Nodo Dirección de red{localhost/127.0.0.1, fromHostname=falso, reverseDns=verdadero} [cb-cálculos-8] 2018-06-20 17:43:55 DEBUG Nodo:273 - [127.0.0.1/localhost]: Añadir Servicio BINARIO [cb-cálculos-8] 2018-06-20 17:43:55 DEBUG Nodo:276 - [127.0.0.1/localhost]: Servicio BINARIO ya añadido, saltarse. ... |
También puedes ver que simplemente ignora ciertas operaciones si el resultado deseado ya se ha conseguido. Verás la mayoría de estos mensajes cuando lleguen nuevas configuraciones, es decir, durante el arranque, el reequilibrio o la conmutación por error.
La reconfiguración, al igual que el reequilibrio en el servidor, es una operación "en línea". Esto significa que las operaciones en vuelo no se ven afectadas la mayor parte del tiempo y, si lo están, se reprograman y se vuelven a colocar en el ringbuffer hasta que tienen éxito o se agota el tiempo.
En cuanto la primera reconfiguración ha tenido éxito, nuestra solicitud de apertura de cubo se completa y el Cubo se devuelve en la API síncrona. Aquí radica un compromiso no obvio: podríamos devolver la solicitud de cubo abierto de inmediato, pero no detectaríamos ningún error durante el arranque. Esperar a que se complete la primera reconfiguración nos permite detectar más errores de inmediato, pero también significa que cuantos más nodos se añadan al clúster, más tiempo tardará (asegúrese de medir el rendimiento de arranque de su clúster). El tiempo de espera por defecto de 5 segundos debería ser suficiente para la mayoría de los casos, pero en despliegues en la nube más lentos y grandes puede tener sentido aumentar este tiempo de espera.
Puede hacerlo modificando el entorno:
1 2 3 |
CouchbaseEnvironment env = DefaultCouchbaseEnvironment.constructor() .connectTimeout(Unidad de tiempo.SEGUNDOS.toMillis(10)) // ajustar a 10s desde 5 .construya(); |
o directamente en el openBucket llamar:
1 |
Cubo cubo = grupo.openBucket("viaje-muestra", 10, Unidad de tiempo.SEGUNDOS); |
Fallos de arranque y configuración
¿Qué ocurre si uno o todos los hosts semilla proporcionados no están disponibles? Para responder a esta pregunta tenemos que echar un vistazo rápido a cómo el cargador intenta obtener las configuraciones.
Para cada host de la lista de nodos semilla, se monta un cargador primario y otro de reserva, y todos ellos se disparan en paralelo intentando obtener una configuración adecuada. Una vez que se encuentra la primera, la secuencia observable se desuscribe y sólo se utiliza una configuración en adelante. Utilizar este enfoque en lugar de la secuencia proporciona tiempos de arranque más rápidos, pero también puede dar lugar a más errores en el registro si uno o más nodos no son accesibles. Dado que estamos optimizando para el caso bueno (en el caso de fallo algo está mal de todos modos) esto parece un compromiso aceptable.
En esencia, mientras haya un buen nodo desde el que arrancar en la lista de nodos semilla, el SDK será capaz de recoger una configuración. Consideremos un caso especial en el que en un MDS Configure el SDK para que sólo arranque desde un nodo que no contenga el servicio KV (digamos que sólo la consulta y, por supuesto, el gestor de clústeres).
Si tiene activado el registro de depuración, verá que el SDK intenta obtener una configuración del servicio KV, pero la rechaza con algún error:
1 |
[cb-cálculos-7] 2018-06-21 10:53:48 DEBUG Cargador:178 - Podría no añada servicio en Dirección de red{10.142.175.102/10.142.175.102, fromHostname=falso, reverseDns=verdadero} porque de com.couchbase.cliente.núcleo.punto final.kv.AuthenticationException: Cubo no encontrado en Seleccione Cubo comando, eliminar it de nuevo. |
El error específico no importa, pero más adelante en el registro se puede ver cómo el cargador cambia al HTTP fallback que está disponible:
1 2 3 4 5 |
[cb-cálculos-2] 2018-06-21 10:53:48 DEBUG Cargador:196 - Con éxito habilitado Servicio CONFIG en Nodo Dirección de red{10.142.175.102/10.142.175.102, fromHostname=falso, reverseDns=verdadero} [cb-cálculos-2] 2018-06-21 10:53:48 DEBUG HttpLoader:69 - Inicio a descubra config a través de HTTP Bootstrap ... [cb-cálculos-4] 2018-06-21 10:53:48 DEBUG HttpLoader:93 - Con éxito cargado config a través de HTTP. [cb-cálculos-4] 2018-06-21 10:53:48 DEBUG Cargador:203 - Tengo configuración de Servicio, intentando a analizar. |
Una implicación de esto es que incluso si en otro nodo el servicio KV puede estar disponible, en este caso el cargador HTTP seguirá indicando a la conexión de streaming que se abra para obtener configuraciones posteriores.
Es posible personalizar el comportamiento de arranque a través de la configuración del entorno:
- bootstrapHttpEnabled : Si el HttpLoader está activado (true por defecto).
- bootstrapCarrierEnabled : Si el CarrierLoader está activado (true por defecto).
- bootstrapHttpDirectPort : Con qué puerto debe contactar inicialmente el HttpLoader si SSL no está habilitado (por defecto 8091).
- bootstrapHttpSslPort : Con qué puerto debe contactar inicialmente el HttpLoader si SSL está habilitado (por defecto 18091).
- bootstrapCarrierDirectPort : Con qué puerto debe contactar inicialmente el CarrierLoader si SSL no está activado (por defecto 11210).
- bootstrapCarrierSslPort : El puerto con el que el CarrierLoader debe contactar inicialmente si SSL está activado (por defecto 11207).
Estos ajustes tienen valores por defecto y en general no necesitan ser modificados. Pueden ser útiles en escenarios de depuración o si se prueban compilaciones personalizadas dentro de Couchbase.
Reflexiones finales
Normalmente el bootstrap es suave y resistente a fallos de nodos individuales, pero si algo va mal y el cubo no se abre correctamente el primer orden de acción es siempre mirar los logs a nivel INFO/WARN y si es posible habilitar el logging DEBUG. Esto normalmente permite saber qué sockets no se conectaron correctamente o en qué punto de la fase de arranque el cliente tuvo que detenerse (por errores o timeouts).
Recuerde también que si ejecuta clusters grandes, especificar un tiempo de apertura del cubo más largo que los 5 segundos habituales puede tener sentido si la red es lenta o tiene una varianza de latencia más alta de lo habitual.
Buen artículo. Gracias.
Sólo curiosidad acerca de lo que registra en el lado del servidor tiene la información de arranque. ¿Es "memcached.log"? Apreciaría si pudiera arrojar más luz sobre esto. Thank you.
Sí, el memcached.log en el lado del servidor tiene parte de la información. Aunque no está registrando mucho (aparte del más reciente comando "hola") ya que podría ser muy spam.
¿Busca algo en concreto?