Fichier de configuration du plugin (>=1.1.x)

Note: Historique : Cette fonctionnalité a été ajoutée en PECL/mysqlnd_ms 1.1.0-beta

La description ci-dessous s'applique pour PECL/mysqlnd_ms >= 1.1.0-beta. Elle n'est pas valide pour les versions antérieures.

Le plugin utilise un fichier de configuration propre, qui contient les informations sur le maitre de réplication MySQL, les escalves, la politique de bascule et la stratégie de failover et l'utilisation de connexions paresseuses (lazy).

Le plugin charge son fichier de configuration au début de la requête web. Il est ensuite mis en cache mémoire et utilisé pour toute la durée de la requête web. De cette façon, il n'est pas nécessaire de redémarrer PHP après avoir déployé le fichier de configuration. Les modifications du fichier de configuration devient ainsi actives en seulement quelques minutes.

La directive de configuration PHP mysqlnd_ms.config_file est utilisée pour indiquer le fichier de configuration des plugins. Notez que la directive de configuration PHP ne sera pas évaluée pour chaque requête web. Toutefois, la modification du nom du fichier de configuration ou son lieu de stockage nécessite un redémarrage de PHP. Cependant, aucun redémarrage n'est nécessaire pour lire les modifications d'un fichier de configuration déjà en place.

L'utilisation et l'analyse JSON est rapide, et l'utilisation de JSON rend simple les structures hiérarchiques par rapport au format standard php.ini.

Exemple #1 Conversion d'un hash PHP en frmat JSON

<?php
$config = array(
  "myapp" => array(
	"master" => array(
	  "master_0" => array(
		"host"   => "localhost",
		"socket" => "/tmp/mysql.sock",
	  ),
	),
	"slave" => array(),
  ),
);

file_put_contents("mysqlnd_ms.ini", json_encode($config, JSON_PRETTY_PRINT));
printf("Fichier mysqlnd_ms.ini créé...\n");
printf("Affichage du contenu...\n");
printf("%s\n", str_repeat("-", 80));
echo file_get_contents("mysqlnd_ms.ini");
printf("\n%s\n", str_repeat("-", 80));
?>
<?php
/* Toutes les connexions suivantes seront en balance de charge */
$mysqli = new mysqli("myapp", "username", "password", "database");
$pdo = new PDO('mysql:host=myapp;dbname=database', 'username', 'password');
$mysql = mysql_connect("myapp", "username", "password");

$mysqli = new mysqli("localhost", "username", "password", "database");
?>

Les noms de section sont des chaînes de caractères. Il est valide d'utiliser un nom de section comme 192.168.2.1, 127.0.0.1 ou localhost. Si, par exemple, une application se connecte à localhost et que la section de configuration localhost existe pour ce plugin, la sémantique de l'opération de connexion change. L'application n'utilise plus que le serveur MySQL sur l'hôte localhost mais le plugin commence à effectuer un balance de charge en suivant les règles issues de la section de configuration localhost. De cette façon, vous pouvez effectuer de la balance de charge des requêtes depuis l'application sans aucune modification du code source de l'application. Garder à l'esprit qu'une telle configuration ne contribue pas à une meilleure lisibilité de votre code source. L'utilisation de noms de section qui peuvent être utilisés comme noms d'hôte doit être de dernier recours.

Chaque section de configuration contient au moins une liste de serveurs maîtres, et une liste de serveurs esclaves. La liste des maîtres est configurée avec le mot clé master, alors que la liste des esclaves est configurée avec le mot clé slave. Le fait de ne pas fournir de liste d'esclaves produit une erreur de type E_ERROR (erreur fatale). Malgré le fait que vous ne pouvez pas omettre de liste d'esclaves, elle peut être vide. Il est possible de n'autoriser aucun esclave. Cependant, ce n'est recommandé que pour les clusters synchrones (lire la documentation sur les clusters supportés). La majeure partie de la documentation parle que des clusters de réplication MySQL asynchrones.

Les listes de maîtres et d'esclaves peuvent être optionnellement indexées par le nom symbolique du serveur qu'elles décrivent.

Exemple #3 Liste des esclaves anonymes

<?php
$mysqli = new mysqli("myapp", "username", "password", "database");
?>
<?php
$mysqli = new mysqli("myapp", "username", "password", "database");
?>
<?php
$mysqli = new mysqli("invalid_section", "username", "password", "database");
?>
<?php
$link = new mysqli("myapp", "root", "", "test");
printf("[%d] %s\n", mysqli_connect_errno(), mysqli_connect_error());
$link->query("SELECT 1 FROM DUAL");
?>
<?php
function pick_server($connected, $query, $masters, $slaves, $last_used_connection, $in_transaction)
{
 static $slave_idx = 0;
 static $num_slaves = NULL;
 if (is_null($num_slaves))
  $num_slaves = count($slaves);

 /* défaut : retour à la logique interne du plugin */
 $ret = NULL;

 printf("L'utilisation s'est connecté sur '%s'...\n", $connected);
 printf("... décision du serveur pour exécuter '%s'\n", $query);

 $where = mysqlnd_ms_query_is_select($query);
 switch ($where)
 {
  case MYSQLND_MS_QUERY_USE_MASTER:
   printf("... utilisation du maître\n");
   $ret = $masters[0];
   break;
  case MYSQLND_MS_QUERY_USE_SLAVE:
   /* SELECT ou astuce SQL pour l'utilisation d'un esclave */
   if (stristr($query, "FROM table_on_slave_a_only"))
   {
	/* une table qui est uniquement sur le premier esclave configuré  */
	printf("... accès à la table disponible uniquement sur l'esclave A détectée\n");
	$ret = $slaves[0];
   }
   else
   {
	/* round robin */
	printf("... quelques requêtes en lecture seul pour un esclave\n");
	$ret = $slaves[$slave_idx++ % $num_slaves];
   }
   break;
  case MYSQLND_MS_QUERY_LAST_USED:
   printf("... utilisation du dernier serveur utilisé\n");
   $ret = $last_used_connection;
   break;
 }

 printf("... ret = '%s'\n", $ret);
 return $ret;
}

$mysqli = new mysqli("myapp", "root", "", "test");

if (!($res = $mysqli->query("SELECT 1 FROM DUAL")))
 printf("[%d] %s\n", $mysqli->errno, $mysqli->error);
else
 $res->close();

if (!($res = $mysqli->query("SELECT 2 FROM DUAL")))
 printf("[%d] %s\n", $mysqli->errno, $mysqli->error);
else
 $res->close();


if (!($res = $mysqli->query("SELECT * FROM table_on_slave_a_only")))
 printf("[%d] %s\n", $mysqli->errno, $mysqli->error);
else
 $res->close();

$mysqli->close();
?>
<?php
function pick_server($connected, $query, $masters, $slaves, $last_used_connection, $in_transaction)
{
  $picked_masters = array()
  foreach ($masters as $key => $value) {
	if (mt_rand(0, 2) > 1)
	  $picked_masters[] = $key;
  }
  $picked_slaves = array()
  foreach ($slaves as $key => $value) {
	if (mt_rand(0, 2) > 1)
	  $picked_slaves[] = $key;
  }
  return array($picked_masters, $picked_slaves);
}
?>

Le plugin émettra une erreur de type E_RECOVERABLE si la fonction de rappel échoue à retourner une liste de serveurs. L'erreur contiendra ceci : (mysqlnd_ms) User multi filter callback has not returned a list of servers to use. The callback must return an array in %s on line %d. Dans le cas où la liste de serveurs n'est pas vide mais contient des identifiants/clés de serveurs invalides, une erreur de type E_RECOVERABLE sera émise avec un message d'erreur du type : (mysqlnd_ms) User multi filter callback has returned an invalid list of servers to use. Server id is negative in %s on line %d, ou similaire.

Une erreur est émise concernant une liste d'esclave ou de maître vide suivant la configuration. Si une liste de maître vide est retournée pour une opération en écriture, le plugin émettra une alerte dont le message sera : (mysqlnd_ms) Couldn't find the appropriate master connection. 0 masters to choose from. Something is wrong in %s on line %d. Typiquement, cette alerte sera suivit d'une erreur de type E_ERROR. Dans le cas d'une opération en lecture et une liste d'esclave vide, le comportement dépendra de la configuration du fail over. Si le fail over du maître est désactivé, le plugin émettra une alerte dont le message sera (mysqlnd_ms) Couldn't find the appropriate slave connection. 0 slaves to choose from. Something is wrong in %s on line %d.

Filtre : node_groups object

Le filtre node_groups vous permet de grouper les noeuds de cluster et requêter les groupes sélectionnés, par exemple, pour supporter le partitionnement de données. Le partitionnement de données peut être requis pour la fragmentation manuelle, la copie primaire basée sur les clusters fonctionnant avec plusieurs maîtres, ou pour éviter les points chauds lors des mises à jour sur les clusters dépourvus en interne de partitionnement. Le filtre est un filtre multiple qui retourne zéro, un ou plusieurs serveurs. Ainsi, il doit être suivi d'autres filtres pour réduire le nombre de candidats à un seul, pour l'exécution de la requête.

Mot clé Description Version
user defined node group name

Un ou plusieurs groupes de noeuds doivent être définis. Un groupe de noeuds peut avoir un nom arbitraire, défini par l'utilisateur. Le nom est utilisé en combinaison d'une astuce SQL pour restreindre l'exécution de la requête aux noeuds listés par le groupe de noeuds. Pour exécuter une requête sur un des serveurs d'un groupe de noeuds, la requête doit commencer avec l'astuce SQL /*user defined node group name*/. Veuillez noter qu'aucun espace n'est autorisé autour de user defined node group name. En raison du fait que user defined node group name est utilisé tel que comme partie d'une astuce SQL, vous devez choisir le nom valide avec le langage SQL.

Chaque entré du groupe de noeuds doit contenir une liste de serveurs master. Les serveurs slave sont également autorisés. Le fait de ne pas fournir de liste de master pour un groupe de noeuds name_of_group va émettre une erreur de type E_RECOVERABLE_ERROR comme celle-ci (mysqlnd_ms) No masters configured in node group 'name_of_group' for 'node_groups' filter.

La liste des serveurs maîtres et esclaves doit correspondre, respectivement, aux entrées de la liste des serveurs global master et de slave. Le fait de référencer un serveur inconnu d'une de ces listes va émettre une erreur de type E_RECOVERABLE_ERROR comme celle-ci (mysqlnd_ms) Unknown master 'server_alias_name' (section 'name_of_group') in 'node_groups' filter configuration.

Exemple #20 Partitionnement manuel

<?php
$mysqli = new mysqli("myapp", "username", "password", "database");
$mysqli->real_escape("this will be escaped using the server_charset setting - utf8");
$mysqli->set_charset("latin1");
$mysqli->real_escape("this will be escaped using latin1");
/* server_charset définit implicitement une connexion utf8 */
$mysqli->query("SELECT 'This connection will be set to server_charset upon establishing' AS _msg FROM DUAL");
/* latin1 sera maintenant utilisé */
$mysqli->set_charset("latin1");
?>

master_on_write bool

Si définit, le plugin utilisera le serveur maître uniquement après que la première requête ait été exécutée sur le maître. Les applications peuvent toujours envoyer les requêtes aux esclaves en utilisant les astuces SQL pour écraser la décision automatique.

Cette configuration permet de corriger le lag dans la réplication. Si une application exécute une requête INSERT, le plugin utilisera, par défaut, le maître pour exécuter les requêtes suivantes, y compris les requêtes SELECT. Ceci permet d'éviter les problèmes de lecture depuis les esclaves qui n'ont pas encore répliqués les requêtes INSERT.

Exemple #26 Utilisation du maître lors d'une écriture pour consolider les lectures

<?php
/* On suppose que le failover automatique est configuré */
$mysqli = new mysqli("myapp", "username", "password", "database");

/* Défini le statut interne du plugin à in_trx = 1 */
$mysqli->autocommit(false);

/* On suppose que le serveur échoue */
if (!($res = $mysqli->query("SELECT 'Assume this query fails' AS _msg FROM DUAL"))) {
 /* Gestion de l'échec de la transaction, le statut interne du plugin est toujours in_trx = 1 */
 printf("[%d] %s", $mysqli->errno, $mysqli->error);
 /*
  Si vous utilisez l'autocommit() pour les transactions, il est préférable
  de l'appeler comme ceci : autocommit(true). Sinon, le plugin pensera
  que la transaction courante continue, et ainsi, toute modification
  de connexion sera interdite.
 */
 $mysqli->autocommit(true);
 /* De plus, vous pouvez vouloir démarrer une nouvelle transaction */
 $mysqli->autocommit(false);
}
/* Maintenant, utilisation du latin1 */
$mysqli->set_charset("latin1");
?>

Si un serveur échoue au milieu d'une transaction, le plugin continue à refuser le changement de connexions tant que la transaction courante n'est pas terminée. Souvenez-vous que le plugin surveille les appels API pour détecter les limites de la transaction. Aussi, vous devez, par exemple, activer le mode autocommit pour finir la transaction courante avant que le plugin ne continue à faire du balance de charge et ainsi, ne change de serveur. De plus, vous voudriez vouloir démarrer une nouvelle transaction immédiatement après, et désactiver l'autocommit.

Le fait de ne pas gérer les requêtes qui échouent, et de ne pas terminer une transaction en échec utilisant des appels API, peuvent causer des erreurs de ce type : Commands out of sync; you can't run this command now. Aussi, il est important de gérer toutes les erreurs.

transient_error object

Cette option a été introduite en version 1.6.0.

Un noeud de cluster de bases de données peut émettre une erreur passagère au client. Le client peut alors répéter l'opération sur le même noeud, peut changer de noeud, ou peut annuler l'opération. Par définition, il est résonnable pour un client de re-tenter la même opération sur le même noeud avant de continuer.

PECL/mysqlnd_ms peut entrer dans une boucle de tentatives à la place de l'application. En configuration l'option transient_error, le plugin peut répéter l'opération ayant échouée avec un code erreur spécifique pendant un certain nombre de fois, avec une pause entre chaque tentative. Si l'erreur passagère disparaît pendant l'exécution de la boucle, elle ne sera pas vue de l'application. Sinon, l'erreur sera transmise à l'application à la fin de la boucle.

Exemple #29 Boucle de tentative pour les erreurs passagères

{
    "myapp": {
        "master": {
            "master_0": {
                "host": "localhost"
            }
        },
        "slave": {
            "slave_0": {
                "host": "192.168.78.136",
                "port": "3306"
            }
       },
       "transient_error": {
          "mysql_error_codes": [
            1297
          ],
          "max_retries": 2,
          "usleep_retry": 100
       }
    }
}

Clé Description Version
mysql_error_codes

Liste des codes erreurs passagères. Vous pouvez y inclure n'importe quel code erreur MySQL. Il est possible de considérer n'importe quelle erreur comme passagère, et pas seulement l'erreur 1297 (HY000 (ER_GET_TEMPORARY_ERRMSG), Message: Got temporary error %d '%s' from %s). Avant d'ajouter d'autres codes erreurs, autre que 1297, à cette liste, assurez-vous que votre cluster supporte une nouvelle tentative sans impacter le statut de votre application.

Depuis 1.6.0.
max_retries

Le nombre de tentatives d'une opération échouant avec une erreur passagère avant d'envoyer l'erreur à l'utilisateur.

Par défaut : 1

Depuis 1.6.0.
usleep_retry

Durée en millisecondes d'attente entre deux tentatives. La valeur est passée à la fonction C usleep().

Par défaut : 100

Depuis 1.6.0.

LoadingChargement en cours