Mirajv1.0
FR

22. miraj-proxy : répartition de charge entre les nœuds

miraj-proxy se place entre les applications et les nœuds d'un cluster MIRAJ (chapitre 12). Il dirige chaque connexion vers le bon nœud, sans rien changer côté application :

  • sur le port d'écriture (rw), une connexion part toujours vers le primaire courant, y compris après une bascule : l'application n'a plus à connaître le primaire ;
  • sur le port de lecture (ro), une connexion part vers le nœud sain le moins chargé, en écartant les secondaires trop en retard.

Le proxy relaie les octets sans lire le protocole. Le chiffrement TLS et l'authentification passent de bout en bout : le proxy n'a ni certificat ni mot de passe des comptes applicatifs, et les clients se connectent avec leurs outils et pilotes habituels, comme à un serveur MIRAJ.

Le nœud est choisi à l'ouverture de chaque connexion. Une connexion ouverte reste sur son nœud jusqu'à sa fermeture ; le proxy ne répartit pas les instructions d'une même connexion.

miraj-proxy est livré avec l'édition Cluster. Il fonctionne aussi devant un serveur seul, hors cluster : il sert alors de point d'entrée unique, ce qui prépare un passage ultérieur au cluster.

22.1 Mise en place#

  1. Nœuds. Démarrez chaque nœud avec l'adresse du proxy dans --proxy-protocol-from (ou la clé proxy_protocol_from de miraj_config.xml) :

     miraj-server.exe --root D:\donnees --log --cluster-config cluster.toml --proxy-protocol-from 10.0.0.9

    Les connexions venues de cette adresse doivent commencer par l'en-tête PROXY qui donne l'adresse du vrai client (voir 22.5). Les autres connexions ne changent pas.

  1. Compte de supervision. Sur le primaire (les comptes sont relayés aux autres nœuds) :

     CREATE USER 'proxy_monitor'@'localhost' IDENTIFIED BY '…';
     GRANT PROCESS, REPLICATION CLIENT ON *.* TO 'proxy_monitor'@'localhost';

    Les connexions de supervision arrivent sans adresse de client : le nœud y voit l'adresse du proxy. Nommez donc l'hôte du compte d'après cette adresse (localhost si le proxy tourne sur un nœud, 10.0.0.9 sinon). PROCESS sert à compter les sessions actives de tous les comptes ; REPLICATION CLIENT à lire le retard de réplication.

  1. proxy.toml, à côté de miraj-proxy.exe (ou --config <fichier>) : voir 22.2.
  1. Démarrage :

     miraj-proxy.exe --log

    Le proxy sonde chaque nœud, annonce le primaire, puis accepte les connexions. Les applications se connectent au port d'écriture pour écrire, et au port de lecture pour les lectures qui tolèrent un léger retard de réplication.

22.2 Fichier proxy.toml#

[proxy]
rw = "0.0.0.0:7007"          # écritures et lectures : toujours le primaire courant
ro = "0.0.0.0:7017"          # lectures : le nœud sain le moins chargé (facultatif)
admin = "127.0.0.1:7117"     # état du proxy, lu par miraj-proxy status (facultatif)
language = "fr"              # langue des erreurs envoyées aux clients (en par défaut)
log_file = "proxy.log"       # journal écrit avec --log, relatif au dossier de ce fichier
source = "10.0.0.9"          # adresse locale des connexions vers les nœuds (facultatif)

[nodes]                      # identifiant du nœud (cluster.toml, [node] id) = adresse client
n1 = "10.0.0.1:7007"
n2 = "10.0.0.2:7007"
n3 = "10.0.0.3:7007"

[monitor]                    # compte de supervision
user = "proxy_monitor"
password = "…"               # ou la variable d'environnement MIRAJ_PROXY_PASSWORD
tls = "preferred"            # preferred | required | disabled
tls_ca = "cluster-ca.pem"    # autorité des certificats des nœuds (implique required)
period_ms = 1000             # une sonde par nœud et par période
timeout_ms = 2000            # connexion à un nœud et réponse d'une sonde
unhealthy_after = 3          # sondes ratées d'affilée avant d'écarter un nœud

[routing]
primary_in_ro = false        # le primaire reçoit aussi des connexions de lecture
max_lag_bytes = 16_777_216   # secondaire écarté des lectures au-delà (0 : sans limite)
max_lag_seconds = 5          # idem, en secondes (0 : sans limite)
active_weight = 4            # poids d'une session active face à une connexion ouverte
connection_reserve = 5       # places laissées libres sous max_connections d'un nœud
close_rw_on_primary_change = true  # coupe les connexions d'écriture vers l'ancien primaire
proxy_protocol = true        # en-tête PROXY : les nœuds voient l'adresse du vrai client

Seules rw, la table [nodes] et user sont obligatoires. Les chemins relatifs se lisent à partir du dossier de proxy.toml. Une erreur de syntaxe, une clé inconnue ou une valeur hors bornes arrête le proxy au démarrage, avec le numéro de la ligne en cause.

Les identifiants de [nodes] sont ceux des nœuds (@@cluster_node_id) : le proxy vérifie à chaque sonde que le nœud joint annonce bien cet identifiant et écarte celui qui en annonce un autre (adresse mal recopiée). Les adresses sont celles du port client (7007 par défaut), pas du port entre nœuds.

Protégez proxy.toml : il contient le mot de passe du compte de supervision en clair, sauf à le donner par MIRAJ_PROXY_PASSWORD.

22.3 Choix du nœud#

22.3.1 Port d'écriture#

Le primaire est le nœud sain qui annonce le rôle PRIMARY, accepte les écritures et porte l'époque la plus haute (@@cluster_epoch). Un ancien primaire isolé qui se croit encore primaire est donc ignoré dès que le nouveau est élu. Deux primaires à la même époque (configuration manuelle erronée) : le proxy n'en choisit aucun, refuse les connexions d'écriture et le signale dans son journal. Un serveur seul hors cluster (rôle NONE) est son propre primaire.

22.3.2 Port de lecture#

Un nœud reçoit des connexions de lecture s'il est sain et :

  • secondaire en flux vu du primaire (STATE = CONNECTED), dans les limites max_lag_bytes et max_lag_seconds ;
  • ou primaire, si primary_in_ro = true ;
  • ou serveur hors cluster ;
  • et s'il lui reste plus de connection_reserve places sous son max_connections.

Parmi ces nœuds, le proxy choisit celui dont la charge est la plus faible :

charge = connexions ouvertes par le proxy vers ce nœud + active_weight × sessions actives

Les connexions ouvertes sont comptées à l'instant, dès le choix : une rafale de connexions se répartit aussitôt, sans attendre la sonde suivante. Les sessions actives (qui exécutent une commande, hors Sleep) viennent de la dernière sonde et comptent aussi le travail des clients connectés directement au nœud. À charge égale, les nœuds sont servis à tour de rôle.

Si aucun nœud n'est éligible (secondaires tous en retard ou injoignables), la connexion de lecture part vers le primaire plutôt que d'être refusée.

22.3.3 Nœud injoignable#

Un nœud dont unhealthy_after sondes d'affilée échouent est écarté ; la sonde suivante réussie le rétablit. Si l'ouverture de la connexion vers le nœud choisi échoue, le proxy essaie le suivant avant d'avoir transmis le moindre octet : le client ne voit rien. Sans aucun nœud, le client reçoit l'erreur 9043 (« Aucun nœud disponible pour les connexions RW » ou « RO ») à la place de la poignée de main.

22.4 Bascule du primaire#

Quand le primaire change (élection en mode raft, promotion en mode manuel) :

  • les nouvelles connexions d'écriture vont au nouveau primaire dès qu'une sonde l'a vu, en général en moins d'une période de sonde ;
  • avec close_rw_on_primary_change = true (défaut), le proxy ferme les connexions d'écriture encore ouvertes vers l'ancien primaire. L'application reçoit une erreur de connexion perdue et se reconnecte par le même port, vers le nouveau primaire. Sans cette fermeture, ses écritures recevraient l'erreur 1290 d'un secondaire ;
  • les connexions de lecture ouvertes continuent : un secondaire reste lisible.

Pendant une élection, aucun nœud n'est primaire : les connexions d'écriture reçoivent l'erreur 9043 le temps de l'élection. Une application doit donc savoir se reconnecter, comme devant un serveur redémarré.

22.5 Adresse des clients : l'en-tête PROXY#

Sans précaution, chaque nœud verrait toutes les connexions venir de l'adresse du proxy. Les comptes définis par hôte ('compta'@'10.0.1.%') ne correspondraient plus, et un seul client qui se trompe de mot de passe assez de fois ferait bloquer l'adresse du proxy, donc tous les clients (max_connect_errors, voir 11.5.2).

Avec proxy_protocol = true (défaut), le proxy écrit en tête de chaque connexion un en-tête PROXY (version 2) qui donne l'adresse du vrai client. Le nœud l'utilise pour choisir le compte, pour le blocage après échecs, et l'affiche dans SHOW PROCESSLIST. Son journal note connexion de <client> par le relais <proxy>.

Le nœud ne lit cet en-tête que sur les connexions venues des adresses de --proxy-protocol-from, et l'exige d'elles : un autre poste ne peut pas se faire passer pour un client quelconque, et une connexion du proxy sans en-tête est fermée. Deux conséquences :

  • donnez l'adresse de la machine du proxy telle que les nœuds la voient (celle choisie par source si vous la fixez) ;
  • depuis la machine du proxy, un administrateur ne peut pas se connecter directement à un nœud (sa connexion viendrait d'une adresse de confiance, sans en-tête) : il passe par un port du proxy. Sur une machine à plusieurs adresses, source réserve une adresse aux connexions du proxy et laisse les autres ordinaires.

Si les nœuds ne sont pas configurés, ils ferment la connexion des sondes dès la poignée de main et le journal du proxy le rappelle (le nœud doit accepter l'en-tête PROXY de ce proxy). Un nœud que le proxy ne joint pas du tout est écarté avec la cause de l'échec (connexion TCP à … impossible : …, adresse source inutilisable), une authentification refusée ou un échec TLS avec l'erreur rendue par le nœud, sans ce rappel. proxy_protocol = false supprime l'en-tête ; le proxy l'accepte, avec un avertissement au démarrage.

22.6 Supervision#

miraj-proxy status lit l'état sur le port admin du proxy en marche :

MIRAJ 1.0.0 - proxy, démarré depuis 3605 s
Primaire : n1
NŒUD  ADRESSE        RÔLE       ÉPOQUE  SAIN  CONNEXIONS  ACTIVES  RETARD  RW  RO  SCORE  LECTURES
n1    10.0.0.1:7007  PRIMARY    7       oui   42/151      3        0       38  0   50     primaire (primary_in_ro = false)
n2    10.0.0.2:7007  SECONDARY  7       oui   23/151      1        0       0   21  25     oui
n3    10.0.0.3:7007  SECONDARY  7       oui   22/151      0        0       0   22  22     oui
Connexions relayées depuis le démarrage : RW 1250 (refusées 3), RO 8410 (refusées 0)

CONNEXIONS : Threads_connected du nœud sur son max_connections ; ACTIVES : sessions actives ; RW, RO : connexions ouvertes par le proxy ; LECTURES : oui, ou la raison pour laquelle le nœud ne reçoit pas de lectures, suivie de la dernière erreur de sonde.

Le port admin n'exige aucune authentification : gardez-le sur la boucle locale (valeur de l'exemple).

Avec --log, le journal log_file garde, horodatés : le démarrage, chaque nœud écarté ou rétabli, chaque changement de rôle ou d'époque, chaque changement de primaire et les connexions d'écriture fermées, les nœuds injoignables. Une connexion ordinaire n'y laisse aucune ligne.

22.7 Démarrage automatique#

miraj-proxy s'arrête avec la fenêtre qui l'a lancé. Pour le démarrer avec Windows, créez une tâche planifiée, comme pour le serveur (voir 2.2) :

schtasks /Create /TN "MIRAJ - proxy" /SC ONSTART /RU SYSTEM /RL HIGHEST ^
  /TR "\"C:\Program Files\MIRAJ\miraj-proxy.exe\" --log"

Pour la disponibilité, installez deux proxys sur deux machines, derrière une adresse virtuelle ou une résolution DNS à deux entrées : ils ne partagent aucun état, chacun sonde les nœuds.

22.8 Limites#

  • Choix à l'ouverture seulement. Une connexion reste sur son nœud. Une application qui mêle lectures et écritures sur une même connexion doit utiliser le port d'écriture.
  • Pas de découverte des nœuds. La liste [nodes] est fixe : ajouter un nœud demande de modifier proxy.toml et de redémarrer le proxy.
  • Lecture après écriture. Une lecture par le port de lecture peut ne pas voir une écriture toute récente faite par le port d'écriture (réplication asynchrone). Pour la garantir, écrivez avec @@cluster_sync_commit = APPLIED (§12.8), ou lisez par le port d'écriture.
  • Un fil par sens et par connexion. Le proxy est dimensionné pour quelques milliers de connexions simultanées, pas pour des dizaines de milliers.
  • KILL et identifiants de connexion. CONNECTION_ID() et SHOW PROCESSLIST donnent les identifiants du nœud joint : un KILL doit être envoyé sur le même nœud.