1
0
mirror of https://github.com/php/doc-es.git synced 2026-03-29 10:52:21 +02:00
Files
archived-doc-es/reference/mysqlnd_ms/quickstart.xml
2015-09-16 20:32:42 +00:00

2387 lines
95 KiB
XML

<?xml version="1.0" encoding="utf-8"?>
<!-- $Revision$ -->
<!-- EN-Revision: b2640f8900175a34675e303ac3f8878aff8d4a54 Maintainer: seros Status: ready -->
<!-- Reviewed: no -->
<chapter xml:id="mysqlnd-ms.quickstart" xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Guía rápida y ejemplos</title>
<para>
El complemento de equilibrado de carga y replicación de mysqlnd es fácil de usar.
Esta guía rápida mostrará los casos de uso típicos, y proporcionará consejos sobre cómo
empezar a utilizarlo.
</para>
<para>
Se recomienda leer las secciones de referencia además de esta guía
rápida. Aquí se intenta evitar tratar con conceptos teóricos
y limitaciones. En su lugar, se enlazará con las secciones de referencia. Es seguro
comenzar con esta guía rápida. Sin embargo, antes de usar el complemento en entornos de objetivos
críticos, le animamos a que lea también la información de fondo de las
secciones de referencia.
</para>
<para>
El enfoque es usar mysqlnd_ms de PECL para trabajar con clústeres MySQL asíncronos,
a saber, la replicación MySQL. Generalmente hablando, un clúster asíncrono es más
difícil de usar que uno sincrónico. Por lo tanto, los usuarios de, por ejemplo, el Clúster MySQL,
encontrarán más información de la necesaria.
</para>
<section xml:id="mysqlnd-ms.quickstart.configuration">
<title>Puesta en marcha</title>
<para>
El complemento está implementado como una extensión de PHP. Vea también las
<link linkend="mysqlnd-ms.installation">instrucciones de instalación</link> para
instalar la extenxión
<link xlink:href="&url.pecl.package;mysqlnd_ms">PECL/mysqlnd_ms</link>.
</para>
<para>
Compile o configure la extensión (API) de MySQL para PHP (<link linkend="ref.mysqli">mysqli</link>,
<link linkend="ref.pdo-mysql">PDO_MYSQL</link>,
<link linkend="ref.mysql">mysql</link>) que planee usar con soporte
para la biblioteca <link linkend="book.mysqlnd">mysqlnd</link>. PECL/mysqlnd_ms
es un complemento para la biblioteca mysqlnd. Para utilizar el complemento con cualqier extensión
de MySQL para PHP, la extensión ha de utilizar la biblioteca mysqlnd.
</para>
<para>
Después carque la extensión en PHP y active el complemento en el fichero de
confuguración de PHP con la directiva de configuración llamada
<link linkend="ini.mysqlnd-ms.enable">mysqlnd_ms.enable</link>.
</para>
<para>
<example>
<title>Habilitar el complemento (php.ini)</title>
<programlisting role="ini">
<![CDATA[
mysqlnd_ms.enable=1
mysqlnd_ms.config_file=/ruta/al/complemento_mysqlnd_ms.ini
]]>
</programlisting>
</example>
</para>
<para>
El complemento utiliza su propio fichero de configuración. Use la directiva
de configuración de PHP
<link linkend="ini.mysqlnd-ms.config-file">mysqlnd_ms.config_file</link>
para establecer la ruta completa al fichero de configuración específico del complemento.
Este fichero debe ser legible por PHP (p.ej., el usuario del servidor web).
Por favor, observe que la directiva de configuración <link linkend="ini.mysqlnd-ms.config-file">mysqlnd_ms.config_file</link>
sustituye a <link linkend="ini.mysqlnd-ms.ini-file">mysqlnd_ms.ini_file</link>
desde la versión 1.4.0. Es un error común usar la directiva de configuración antigua,
la cual ya no está disponible.
</para>
<para>
Cree un fichero de configuración específico del complemento. Guarde el fichero en la ruta
establecida por la directiva de configuración de PHP
<link linkend="ini.mysqlnd-ms.config-file">mysqlnd_ms.config_file</link>.
</para>
<para>
El <link linkend="mysqlnd-ms.plugin-ini-json">fichero de configuración</link> del complemento
está basado en <acronym>JSON</acronym>. Está dividido en una o más secciones.
Cada sección tiene un nombre, por ejemplo, <literal>myapp</literal>. Cada sección
contiene su propio conjunto de ajustes de configuración.
</para>
<para>
Una sección debe incluir, como mínimo, el servidor maestro de replicación MySQL y establecer
una lista de esclavos. El complemento admite únicamente un servidor maestro por sección.
La configuración de replicación MySQL de múltiples maestros aún no está totalemnte soportada.
Use el ajuste de configuración
<link linkend="ini.mysqlnd-ms-plugin-config-v2.master">master</link>
para establecer el nombre del anfitrión y el puerto o socket del servidor maestro de MySQL.
Los servidores esclavos de MySQL se configuran usando la
palabra clave
<link linkend="ini.mysqlnd-ms-plugin-config-v2.slave">slave</link>.
</para>
<para>
<example>
<title>Fichero de configuración mínima específico del complemento (mysqlnd_ms_plugin.ini)</title>
<programlisting role="ini">
<![CDATA[
{
"myapp": {
"master": {
"master_0": {
"host": "localhost"
}
},
"slave": [
]
}
}
]]>
</programlisting>
</example>
</para>
<para>
Es necesario configurar una lista de servidores esclavos de MySQL, aunque puede
contener una lista vacía. Se recomienda que siempre se configure al
menos un servidor esclavo.
</para>
<para>
Las listas de servidores pueden usar la <link linkend="mysqlnd-ms.plugin-ini-json.server-list-syntax">
sintaxis anónima o no anónima</link>. Las listas no
anónimas incluyen sobrenombres para los servidores, tales como <literal>master_0</literal>
para el maestro del ejemplo de arriba. Esta guía rápida utiliza la
sintaxis no anónima más prolija.
</para>
<para>
<example>
<title>Configuración mínima recomendada específica del complemento (mysqlnd_ms_plugin.ini)</title>
<programlisting role="ini">
<![CDATA[
{
"myapp": {
"master": {
"master_0": {
"host": "localhost",
"socket": "\/tmp\/mysql.sock"
}
},
"slave": {
"slave_0": {
"host": "192.168.2.27",
"port": "3306"
}
}
}
}
]]>
</programlisting>
</example>
</para>
<para>
Si al menos existen
dos servidores en total, el complemento puede empezar a equilibrar la carga e intercambiar
conexiones. El intercambio de conexiones no siempre es transparente y puede ocasionar
problemas en ciertos casos. Las secciones de referencia sobre
<link linkend="mysqlnd-ms.pooling">intercambio y agrupación de conexiones</link>,
<link linkend="mysqlnd-ms.transaction">manejo de transacciones</link>,
<link linkend="mysqlnd-ms.failover">tolerancia a fallos</link>
<link linkend="mysqlnd-ms.loadbalancing">equilibrado de carga</link> y
<link linkend="mysqlnd-ms.rwsplit">división de lectura-escritura</link> proporcionan
más detalles. Los problemas potenciales están descritos más tarde en esta guía.
</para>
<para>
Es responsabilidad de la aplicación manejar los problemas potenciales causados
por el intercambio de conexiones, configurando un servidor maestro con al menos un servidor
esclavo, lo cual permite el intercambio para trabajar con los problemas relacionados que se puedan encontrar.
</para>
<para>
Los servidores maestros y esclavos de MySQL que se configuren no necesitan
ser parte de la configuración de replicación MySQL. Para realizar pruebas, se puede usar un único
servidor MySQL y hacerle saber al complemento que se trata de un servidor maestro y esclavo,
como se muestra abajo. Esto podría ser de ayuda para detectar muchos problemas potenciales con
los intercambios de conexión. Sin embargo, tal configuración no será propensa a los problemas
causados por la demora de replicación.
</para>
<para>
<example>
<title>Usar un servidor como maestro y esclavo (¡solamente para pruebas!)</title>
<programlisting role="ini">
<![CDATA[
{
"myapp": {
"master": {
"master_0": {
"host": "localhost",
"socket": "\/tmp\/mysql.sock"
}
},
"slave": {
"slave_0": {
"host": "127.0.0.1",
"port": "3306"
}
}
}
}
]]>
</programlisting>
</example>
</para>
<para>
El complemento intenta notificarle de configuraciones inválidas. Desde 1.5.0,
lanzará una advertencia durante el arranque de PHP si no se puede leer el fichero de configuración,
está vacío, o falló el análisis de JSON. Según la configuración de PHP, estos
errores podrían aparecer únicamente en algunos ficheros log. Se hace una validación extra cuando se
establece una conexión y se buscan secciones válidas en el fichero de configuración.
Establecer <link linkend="ini.mysqlnd-ms.force-config-usage">mysqlnd_ms.force_config_usage</link>
podría ayudar a depurar una configuración incorrecta. Véanse también las
<link linkend="mysqlnd-ms.plugin-ini-json.debug_config">notas de depuración del fichero de configuración</link>.
</para>
</section>
<section xml:id="mysqlnd-ms.quickstart.usage">
<title>Ejecutar sentencias</title>
<para>
El complemento se puede usar con cualquier extensión de MySQL para PHP
(<link linkend="ref.mysqli">mysqli</link>,
<link linkend="ref.mysql">mysql</link>, y
<link linkend="ref.pdo-mysql">PDO_MYSQL</link>) que esté
compilada para utilizar la biblioteca <link linkend="book.mysqlnd">mysqlnd</link>.
<literal>PECL/mysqlnd_ms</literal> se acopla a la biblioteca <link linkend="book.mysqlnd">mysqlnd</link>.
No cambia la API o el comportamiento de estas extensiones.
</para>
<para>
Siempre que se esté abriendo una conexión a MySQL, el complemento compara el valor del
parámetro 'host' de la llamada de conexión con los nombres de las secciones
del fichero de configuración específico del complemento. Si, por ejemplo, el
fichero de configuración específico del complemento tiene una sección <literal>myapp</literal> , se
ha de hacer referencia a ésta para abrir una conexión a MySQL para el
anfitrión <literal>myapp</literal>
</para>
<para>
<example>
<title>Fichero de configuración específico del complemento (mysqlnd_ms_plugin.ini)</title>
<programlisting role="ini">
<![CDATA[
{
"myapp": {
"master": {
"master_0": {
"host": "localhost",
"socket": "\/tmp\/mysql.sock"
}
},
"slave": {
"slave_0": {
"host": "192.168.2.27",
"port": "3306"
}
}
}
}
]]>
</programlisting>
</example>
</para>
<para>
<example>
<title>Abrir una conexión con equilibrado de carga</title>
<programlisting role="php">
<![CDATA[
<?php
/* El equilibrado de carga sigue las reglas de la sección "myapp" del fichero de configuración del complemento */
$mysqli = new mysqli("myapp", "nombre_usuario", "contraseña", "base_datos");
$pdo = new PDO('mysql:host=myapp;dbname=base_datos', 'nombre_usuario', 'contraseña');
$mysql = mysql_connect("myapp", "nombre_usuario", "contraseña");
?>
]]>
</programlisting>
</example>
</para>
<para>
Los ejemplos de conexión de arriba tendrán equilibrio de carga.
El complemento enviará sentencias de sólo lectura al servidor esclavo de MySQL con la
IP <literal>192.168.2.27</literal> y escuchará el puerto <literal>3306</literal>
para la conexión cliente de MySQL. Todas las demás sentencias serán dirigidas al
servidor maestro de MySQL que se ejecuta en el anfitrión <literal>localhost</literal>. Si se ejecuta en sistemas
operativos de tipo Unix, el maestro sobre <literal>localhost</literal> aceptará
conexiones cliente de MySQL en el socket de dominio Unix <literal>/tmp/mysql.sock</literal>,
mientras que TCP/IP es el puerto predeterminado en Windows.
El complemento usará el nombre de usuario <literal>nombre_usuario</literal> y la contraseña
<literal>contraseña</literal> para conectarse a cualquier servidor de MySQL incluido en
la sección <literal>myapp</literal> del fichero de configuración del complemento. Una vez
realizada la conexión, el complemento seleccionará <literal>base_datos</literal> como el esquema
actual.
</para>
<para>
El nombre de usuario, la contraseña y el nombre del esquema son tomados de las llamadas a la
API de conexión y usados por todos los servidores. En otras palabras: se deben usar los mismos
nombre de usuario y contraseña para cada servidor MySQL incluido en una sección del fichero de configuración
del comlemento. No es una limitación generalizada. A partir de <literal>PECL/mysqlnd_ms</literal> 1.1.0,
es posible establecer
<link linkend="mysqlnd-ms.plugin-ini-json.server-config-keywords">username</link> y
<link linkend="mysqlnd-ms.plugin-ini-json.server-config-keywords">password</link> para cualquier servidor del
fichero de configuración del complemento, para utilizarlos en lugar de las credenciales pasadas
a la llamada a la API.
</para>
<para>
El complemento no cambia la API para ejecutar sentencias.
La <link linkend="mysqlnd-ms.rwsplit">división de lectura-escritura</link>
se desarrolla aparte. El siguiente ejemplo asume que no existe
una demora de replicación significante entre el maestro y el esclavo.
</para>
<para>
<example>
<title>Ejecutar sentencias</title>
<programlisting role="php">
<![CDATA[
<?php
/* El equilibrado de carga sigue las reglas de la sección "myapp" del fichero de configuración del complemento */
$mysqli = new mysqli("myapp", "nombre_usuario", "contraseña", "base_datos");
if (mysqli_connect_errno()) {
/* Por supuesto, su manejo de errores es mejor... */
die(sprintf("[%d] %s\n", mysqli_connect_errno(), mysqli_connect_error()));
}
/* Las sentencias se ejcutarán en el maestro */
if (!$mysqli->query("DROP TABLE IF EXISTS test")) {
printf("[%d] %s\n", $mysqli->errno, $mysqli->error);
}
if (!$mysqli->query("CREATE TABLE test(id INT)")) {
printf("[%d] %s\n", $mysqli->errno, $mysqli->error);
}
if (!$mysqli->query("INSERT INTO test(id) VALUES (1)")) {
printf("[%d] %s\n", $mysqli->errno, $mysqli->error);
}
/* solo lectura: la sentencia se ejecutará en un esclavo */
if (!($res = $mysqli->query("SELECT id FROM test"))) {
printf("[%d] %s\n", $mysqli->errno, $mysqli->error);
} else {
$fila = $res->fetch_assoc();
$res->close();
printf("El esclavó devuelve el ID = '%s'\n", $fila['id'];
}
$mysqli->close();
?>
]]>
</programlisting>
&example.outputs.similar;
<screen>
<![CDATA[
El esclavó devuelve el ID = '1'
]]>
</screen>
</example>
</para>
</section>
<section xml:id="mysqlnd-ms.quickstart.connectionpooling">
<title>Estado de la conexión</title>
<para>
El complemento cambia la semántica de un gestor de conexión de MySQL para PHP.
Un gestor de conexión representa una agrupación de conexiones, en lugar de una
única conexión de red cliente-servidor de MySQL. La agrupación de conexiones consiste
en una conexión maestra, y opcionalmente cualquier número de conexiones esclavas.
</para>
<para>
Cada conexión de la agrupación de conexiones tiene su propio estado. Por ejemplo,
las variables SQL de usuario, las tablas temporales y las transacciones, son parte del estado.
Se puede encontrar un listado completo con los elementos que pertenecen al estado de una conexión en
los conceptos de la documentación de
<link linkend="mysqlnd-ms.pooling">agrupación e intercambio de conexiones</link>.
Si el complemento decide intercambiar conexiones para equilibrar la carga, a la
aplicación se le podría proporcionar una conexión que tuviera un estado diferente.
Las aplicaciones deben considerar esto.
</para>
<para>
<example>
<title>Configuración del complemento con un esclavo y un maestro</title>
<programlisting role="ini">
<![CDATA[
{
"myapp": {
"master": {
"master_0": {
"host": "localhost",
"socket": "\/tmp\/mysql.sock"
}
},
"slave": {
"slave_0": {
"host": "192.168.2.27",
"port": "3306"
}
}
}
}
]]>
</programlisting>
</example>
</para>
<para>
<example>
<title>Problema: estado de conexión y variables SQL de usuario</title>
<programlisting role="php">
<![CDATA[
<?php
$mysqli = new mysqli("myapp", "nombre_usuario", "contraseña", "base_datos");
if (!$mysqli) {
/* Por supuesto, su manejo de errores es mejor... */
die(sprintf("[%d] %s\n", mysqli_connect_errno(), mysqli_connect_error()));
}
/* Conexión 1, la conexión vincula una variable SQL de usuario, no se ejecuta ningún SELECT en el maestro */
if (!$mysqli->query("SET @myrole='master'")) {
printf("[%d] %s\n", $mysqli->errno, $mysqli->error);
}
/* Conexión 2, se ejecuta en el esclavo a causa de SELECT */
if (!($res = $mysqli->query("SELECT @myrole AS _role"))) {
printf("[%d] %s\n", $mysqli->errno, $mysqli->error);
} else {
$fila = $res->fetch_assoc();
$res->close();
printf("@myrole = '%s'\n", $fila['_role']);
}
$mysqli->close();
?>
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
@myrole = ''
]]>
</screen>
</example>
</para>
<para>
El ejemplo abre una conexión con equilibrio de carga y ejecuta dos sentencias.
La primera sentencia <literal>SET @myrole='master'</literal> no comienza
con la cadena <literal>SELECT</literal>. Por lo tanto, el complemento no
la reconoce como una consulta de solo lectura que se ejecutaría en un esclavo. El
complemento ejecuta la sentencia en una conexión al maestro. La sentencia
establece una variable SQL de usuario que está vinculada a la conexión con el maestro. El
estado de la conexión maestra ha sido cambiado.
</para>
<para>
La siguiente sentencia es <literal>SELECT @myrole AS _role</literal>.
El complemento la reconoce como una consulta de solo lectura y la envía al
esclavo. La sentencia se ejecuta en una conexión al esclavo. Esta
segunda conexión no tiene ninguna variable SQL de usuario vinculada a ella.
Tiene un estado diferente a la primera conexión al maestro.
La variable SQL de usuario no está establecida. El script del ejemplo imprime
<literal>@myrole = ''</literal>.
</para>
<para>
Es responsabilidad del desarrollador de la apliación ocuparse
del estado de la conexión. El complemento no monitoriza todas
las actividades de cambio de estado. La monitorización de todos los casos posibles
sería una tarea intensiva de la CPU, si es que esta tarea pudiera ser realizada.
</para>
<para>
Los problemas se pueden evitar fácilmente usando sugerencias SQL.
</para>
</section>
<section xml:id="mysqlnd-ms.quickstart.sqlhints">
<title>Sugerencias SQL</title>
<para>
Las sugerencias SQL pueden forzar a una consulta a elegir un servidor específico de la agrupación de conexiones.
Proporcionan al complemento una recomendación para utilizar un servidor indicado, lo que puede resolver
problemas causados por intercambios de conexión y estados de conexión.
</para>
<para>
Las sugerencias SQL son comentarios que siguen el estándar. Ya que
los comentarios SQL son ignorados por sistemas de procesamiento SQL,
no interfieren con otros programas como el Servidor MySQL, el Proxy MySQL,
o un cortafuegos.
</para>
<para>
El complemento admite tres sugerencias SQL: La
sugerencia <constant>MYSQLND_MS_MASTER_SWITCH</constant> hace que el complemento ejecute una
sentencia en el maestro, <constant>MYSQLND_MS_SLAVE_SWITCH</constant>
fuerza el uso del esclavo, y
<constant>MYSQLND_MS_LAST_USED_SWITCH</constant> ejecutará una sentencia en
el mismo servidor que se utilizó con la sentencia anterior.
</para>
<para>
El complemento examina el comienzo de una sentencia para la existencia de una sugerencia
SQL. Las sugerencias SQL solo son reconocidas si aparecen al comienzo de
la sentencia.
</para>
<para>
<example>
<title>Configuración del complemento con un esclavo y un maestro</title>
<programlisting role="ini">
<![CDATA[
{
"myapp": {
"master": {
"master_0": {
"host": "localhost",
"socket": "\/tmp\/mysql.sock"
}
},
"slave": {
"slave_0": {
"host": "192.168.2.27",
"port": "3306"
}
}
}
}
]]>
</programlisting>
</example>
</para>
<para>
<example>
<title>Sugerencias SQL para evitar el intercambio de conexión</title>
<programlisting role="php">
<![CDATA[
<?php
$mysqli = new mysqli("myapp", "nombre_usuario", "contraseña", "base_datos");
if (mysqli_connect_errno()) {
/* Por supuesto, su manejo de errores es mejor... */
die(sprintf("[%d] %s\n", mysqli_connect_errno(), mysqli_connect_error()));
}
/* Conexión 1, la conexión vincula una variable SQL de usuario, no se ejecuta ningún SELECT en el maestro */
if (!$mysqli->query("SET @myrole='master'")) {
printf("[%d] %s\n", $mysqli->errno, $mysqli->error);
}
/* Conexión 1, se ejecuta en el maestro debido a la sugerencia SQL */
if (!($res = $mysqli->query(sprintf("/*%s*/SELECT @myrole AS _role", MYSQLND_MS_LAST_USED_SWITCH)))) {
printf("[%d] %s\n", $mysqli->errno, $mysqli->error);
} else {
$fila = $res->fetch_assoc();
$res->close();
printf("@myrole = '%s'\n", $fila['_role']);
}
$mysqli->close();
?>
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
@myrole = 'master'
]]>
</screen>
</example>
</para>
<para>
En el ejemplo de arriba, el uso de <constant>MYSQLND_MS_LAST_USED_SWITCH</constant> evita
el intercambio de sesión desde el maestro al esclavo al ejecutar la sentencia
<literal>SELECT</literal>.
</para>
<para>
Las sugerencias SQL también se pueden usar para ejecutar sentencias <literal>SELECT</literal>
en el servidor maestro de MySQL. Esto podría ser necesario si los servidores esclavos de MySQL
están normalmente detrás del maestro, pero no se necesitan datos actuales del clúster.
</para>
<para>
En la versión 1.2.0, el concepto de nivel de servicio ha sido introducido para dirigir
casos en los que no son necesarios datos actuales. El uso de un nivel de servicio requiere menos atención
y elimina la necesidad de usar sugerencias SQL para este caso. Se puede encontrar más
información más abajo en la sección de nivel de servicio y consistencia.
</para>
<para>
<example>
<title>Enfrentarse a la demora de replicación</title>
<programlisting role="php">
<![CDATA[
<?php
$mysqli = new mysqli("myapp", "nombre_usuario", "contraseña", "base_datos");
if (!$mysqli) {
/* Por supuesto, su manejo de errores es mejor... */
die(sprintf("[%d] %s\n", mysqli_connect_errno(), mysqli_connect_error()));
}
/* Forzar el uso del maestro, el maestro siempre tiene datos recientes y actuales */
if (!$mysqli->query(sprintf("/*%s*/SELECT critical_data FROM important_table", MYSQLND_MS_MASTER_SWITCH))) {
printf("[%d] %s\n", $mysqli->errno, $mysqli->error);
}
?>
]]>
</programlisting>
</example>
</para>
<para>
Un caso de uso puede incluir la creación de tablas en un esclavo.
Si no se proporciona una sugenrencia SQL, el complemento enviará las sentencias
<literal>CREATE</literal> e <literal>INSERT</literal> al maestro. Utilice la
sugerencia SQL <constant>MYSQLND_MS_SLAVE_SWITCH</constant> si quiere
ejecutar tales sentencias en un esclavo, por ejemplo, para construir tablas de
informes temporales.
</para>
<para>
<example>
<title>Creación de una tabla en un esclavo</title>
<programlisting role="php">
<![CDATA[
<?php
$mysqli = new mysqli("myapp", "nombre_usuario", "contraseña", "base_datos");
if (!$mysqli) {
/* Por supuesto, su manejo de errores es mejor... */
die(sprintf("[%d] %s\n", mysqli_connect_errno(), mysqli_connect_error()));
}
/* Forzar el uso del esclavo */
if (!$mysqli->query(sprintf("/*%s*/CREATE TABLE informes_esclavo(id INT)", MYSQLND_MS_SLAVE_SWITCH))) {
printf("[%d] %s\n", $mysqli->errno, $mysqli->error);
}
/* Continuar usando esta conexión esclava en particular */
if (!$mysqli->query(sprintf("/*%s*/INSERT INTO informes_esclavo(id) VALUES (1), (2), (3)", MYSQLND_MS_LAST_USED_SWITCH))) {
printf("[%d] %s\n", $mysqli->errno, $mysqli->error);
}
/* ¡No usar MYSQLND_MS_SLAVE_SWITCH, ya que permitiría el intercambio con otro esclavo! */
if ($res = $mysqli->query(sprintf("/*%s*/SELECT COUNT(*) AS _num FROM informes_esclavo", MYSQLND_MS_LAST_USED_SWITCH))) {
$fila = $res->fetch_assoc();
$res->close();
printf("Hay %d filas en la tabla 'informes_esclavo'", $fila['_num']);
} else {
printf("[%d] %s\n", $mysqli->errno, $mysqli->error);
}
$mysqli->close();
?>
]]>
</programlisting>
</example>
</para>
<para>
La sugerencia SQL <constant>MYSQLND_MS_LAST_USED</constant> prohíbe el intercambio de una
conexión, por lo que fuerza el uso de la conexión utilizada anteriormente.
</para>
</section>
<section xml:id="mysqlnd-ms.quickstart.transactions">
<title>Transacciones locales</title>
<para>
La versión actual del complemento no es segura con transacciones de forma predeterminada,
ya que no considera su ejecución en ningún caso. Las transacciones SQL son
unidades de trabajo para ejecutarlas en un único servidor. El complemento no siempre sabe
cuándo comienza y cuándo finaliza una unidad de trabajo. Por lo tanto, el complemento podría
decidir intercambiar conexiones en mitad de una transacción.
</para>
<para>
Ningún tipo de equilibrador de carga de MySQL puede detectar los límites de una transacción sin ningún
tipo de sugerencia por parte de la aplicación.
</para>
<para>
Se pueden usar sugerencias SQL para superar esta limitación. De forma alternativa,
se puede activar la monitorización de llamadas a la API de transacciones. En el último caso,
de deben usar llamadas a la API solamente para controlar transacciones. Véase más abajo.
</para>
<para>
<example>
<title>Configuración del complemento con un esclavo y un maestro</title>
<programlisting role="ini">
<![CDATA[
[myapp]
{
"myapp": {
"master": {
"master_0": {
"host": "localhost",
"socket": "\/tmp\/mysql.sock"
}
},
"slave": {
"slave_0": {
"host": "192.168.2.27",
"port": "3306"
}
}
}
}
]]>
</programlisting>
</example>
</para>
<para>
<example>
<title>Usar sugerencias SQL para transacciones</title>
<programlisting role="php">
<![CDATA[
<?php
$mysqli = new mysqli("myapp", "nombre_usuario", "contraseña", "base_datos");
if (!$mysqli) {
/* Por supuesto, su manejo de errores es mejor... */
die(sprintf("[%d] %s\n", mysqli_connect_errno(), mysqli_connect_error()));
}
/* No es un SELECT, se usará el maestro */
if (!$mysqli->query("START TRANSACTION")) {
/* Por favor, use un manejo de errores mejor en su código */
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
}
/* ¡Evitar el intercambio de conexión! */
if (!$mysqli->query(sprintf("/*%s*/INSERT INTO test(id) VALUES (1)", MYSQLND_MS_LAST_USED_SWITCH))) {
/* Por favor, realice el ROLLBACK apropiado en su código, y no use simplemente 'die' */
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
}
if ($res = $mysqli->query(sprintf("/*%s*/SELECT COUNT(*) AS _num FROM test", MYSQLND_MS_LAST_USED_SWITCH))) {
$fila = $res->fetch_assoc();
$res->close();
if ($fila['_num'] > 1000) {
if (!$mysqli->query(sprintf("/*%s*/INSERT INTO events(task) VALUES ('cleanup')", MYSQLND_MS_LAST_USED_SWITCH))) {
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
}
}
} else {
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
}
if (!$mysqli->query(sprintf("/*%s*/UPDATE log SET last_update = NOW()", MYSQLND_MS_LAST_USED_SWITCH))) {
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
}
if (!$mysqli->query(sprintf("/*%s*/COMMIT", MYSQLND_MS_LAST_USED_SWITCH))) {
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
}
$mysqli->close();
?>
]]>
</programlisting>
</example>
</para>
<para>
Desde PHP 5.4.0, la biblioteca <literal>mysqlnd</literal> permite al
complemento monitorizar el estado del modo <literal>autocommit</literal>, si
el modo está establecido por llamadas a la API en lugar de usar sentencias SQL como
<literal>SET AUTOCOMMIT=0</literal>. Esto permite que el complemento
considere las transacciones. En este caso, es necesario usar sugerencias SQL.
</para>
<para>
Si se está utlizando PHP 5.4.0 o superior, con las llamadas a la API que habilitan el modo <literal>autocommit</literal>,
y cuando se establece la opción de configuración del complemento
<link linkend="ini.mysqlnd-ms-plugin-config-v2.trx-stickiness">trx_stickiness=master</link>,
éste puede deshabilitar automáticamente el equilibrado de carga y el intercambio de conexiones
para transacciones SQL. Con esta configuración, el complemento detiene el equilibrado de carga
si <literal>autocommit</literal> está deshabilitado, y dirige todas las sentencias al
maestro. Esto evita el intercambio de conexiones en mitad de
una transacción. Una vez que <literal>autocommit</literal> es rehabilitado, el complemento
inicia de nuevo las sentencias con equilibrado de carga.
</para>
<para>
La detección de los límites de transacciones basada en API ha sido mejorada con PHP 5.5.0 y
<literal>PECL/mysqlnd_ms</literal> 1.5.0 para cubirir no solo llamadas a <function>mysqli_autocommit</function>,
sino también a <function>mysqli_begin</function>,
<function>mysqli_commit</function> y <function>mysqli_rollback</function>.
</para>
<para>
<example>
<title>Equilibrado de carga considerando transacciones: el ajuste trx_stickiness</title>
<programlisting role="ini">
<![CDATA[
{
"myapp": {
"master": {
"master_0": {
"host": "localhost",
"socket": "\/tmp\/mysql.sock"
}
},
"slave": {
"slave_0": {
"host": "127.0.0.1",
"port": "3306"
}
},
"trx_stickiness": "master"
}
}
]]>
</programlisting>
</example>
</para>
<para>
<example>
<title>Consideración de transacciones</title>
<programlisting role="php">
<![CDATA[
<?php
$mysqli = new mysqli("myapp", "nombre_usuario", "contraseña", "base_datos");
if (!$mysqli) {
/* Por supuesto, su manejo de errores es mejor... */
die(sprintf("[%d] %s\n", mysqli_connect_errno(), mysqli_connect_error()));
}
/* Deshabilitar autocommit, el complemento ejecutará todas las sentencias en el maestro */
$mysqli->autocommit(FALSE);
if (!$mysqli->query("INSERT INTO test(id) VALUES (1)")) {
/* Por favor, realice el ROLLBACK apropiado en su código, y no use simplemente 'die' */
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
}
if ($res = $mysqli->query("SELECT COUNT(*) AS _num FROM test")) {
$fila = $res->fetch_assoc();
$res->close();
if ($fila['_num'] > 1000) {
if (!$mysqli->query("INSERT INTO events(task) VALUES ('cleanup')")) {
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
}
}
} else {
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
}
if (!$mysqli->query("UPDATE log SET last_update = NOW()")) {
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
}
if (!$mysqli->commit()) {
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
}
/* El complemento asume que la transacción ha finalizado e inicia el equilibrado de carga de nuevo */
$mysqli->autocommit(true);
$mysqli->close();
?>
]]>
</programlisting>
</example>
</para>
<note>
<title>Requisitos de versión</title>
<para>
La opción de configuración del complemento
<link linkend="ini.mysqlnd-ms-plugin-config-v2.trx-stickiness">trx_stickiness=master</link>
requiere PHP 5.4.0 o superior.
</para>
</note>
<para>
Por favor, observe las restricciones descritas en la
sección de conceptos sobre
<link linkend="mysqlnd-ms.transaction">manejo de transacciones</link>.
</para>
</section>
<section xml:id="mysqlnd-ms.quickstart.xa_transactions">
<title>Transacciones XA/Distribuidas</title>
<note>
<title>Requisitos de versión</title>
<para>
Las funciones relacionadas con XA han sido introducidas en la verisón 1.6.0-alpha de PECL mysqlnd_ms.
</para>
</note>
<note>
<title>Se buscan los primeros adaptadores</title>
<para>
Esta característica está actualmente en desarrollo. Podrían existir problemas y/o
limitaciones. No la use en entornos de producción, aunque
las primeras pruebas indican una calidad razonable.
</para>
<para>
Por favor, contacte con el equipo de desarrollo si está interesado en esta característica.
Estamos buscando comentarios de la vida real para completar esta característica.
</para>
</note>
<para>
Las transacciones XA son un método estandarizado para ejecutar transacciones a través
de varios recursos. Estos recursos pueden ser bases de datos u otros sistemas
transaccionales. El servidor de MySQL admite sentencias SQL de XA, las cuales permiten a los usuarios
llevar a cabo una transacción SQL distribuida que genera varios servidores de bases de datos,
o cualquier otro tipo siempre y cuando admita también las sentencias SQL. En este
tipo de escenario, es responsabilidad del usuario coordinar los servidores
participantes.
</para>
<para>
<literal>PECL/mysqlnd_ms</literal> puede actuar como un coordinador de transacciones para una transacción
global (distribuida, XA) se lleve a cabo solamente en servidores de MySQL. Como coordinador transacciones, el complemento
rastrea todos los servidores involucrados en una transacción global y envía de forma transparente
las sentencias SQL apropiadas a los participantes. Las transacción globales son controladas con
<function>mysqlnd_ms_xa_begin</function>, <function>mysqlnd_ms_xa_commit</function>
y <function>mysqlnd_ms_xa_rollback</function>. Los detalles SQL mayormente están ocultos a
la aplicación, ya que es necesario rastrear y coordinar a los participantes.
</para>
<para>
<example>
<title>Patrón general para transacciones XA</title>
<programlisting role="php">
<![CDATA[
<?php
$mysqli = new mysqli("myapp", "nombre_usuario", "contraseña", "base_datos");
if (!$mysqli) {
/* Por supuesto, su manejo de errores es mejor... */
die(sprintf("[%d] %s\n", mysqli_connect_errno(), mysqli_connect_error()));
}
/* iniciar una transacción global */
$gtrid_id = "12345";
if (!mysqlnd_ms_xa_begin($mysqli, $gtrid_id)) {
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
}
/* ejecutar las consultas como siempre: XA BEGIN será inyectada en cuanto se ejecute una consulta */
if (!$mysqli->query("INSERT INTO orders(order_id, item) VALUES (1, 'christmas tree, 1.8m')")) {
/* Tanto si falla INSERT o falla la inyección de XA BEGIN */
if ('XA' == substr($mysqli->sqlstate, 0, 2)) {
printf("Fallo relacionado con una transacción/XA global, [%d] %s\n", $mysqli->errno, $mysqli->error);
} else {
printf("Fallo de INSERT, [%d] %s\n", $mysqli->errno, $mysqli->error);
}
/* revertir la transacción global */
mysqlnd_ms_xa_rollback($mysqli, $xid);
die("Deteniendo.");
}
/* continuar con la realización de consultas en otros servidores, p.ej. otros fragmentos */
/* consignar la transacción global */
if (!mysqlnd_ms_xa_commit($mysqli, $xa_id)) {
printf("[%d] %s\n", $mysqli->errno, $mysqli->error);
}
?>
]]>
</programlisting>
</example>
</para>
<para>
A diferencia de las transacciones locales, la cuales se llevan a cabo en un único servidor,
las transacciones XA tienen un identificador (xid) asociado a ellas.
El identificador de transacción de XA está compuesto por un identificador de transacción
global (gtrid), un identificador de ramificación (bqual), y
un identificador de formato (formatID). Solamente el identificador de transacción global puede
y debe proporcionarse al invocar a cualquier función de XA del complemento.
</para>
<para>
Una vez que ha sido iniciada una transacción global, el complemento comienza a rastrear servidores
hsata que la transacción global finaliza. Cuando un servidor se escoge para la ejecución de una consulta,
el complemento inyecta la sentencia SQL <literal>XA BEGIN</literal> antes de
ejecutar la sentencia SQL real en el servidor. <literal>XA BEGIN</literal>
hace que el servidor participe en la transacción global. Si falla la inyección
de la sentencia SQL, el complemento informará del problema en réplica a la función
de ejecución de consultas que se empleó. En el ejemplo de arriba,
<literal>$mysqli->query("INSERT INTO orders(order_id, item) VALUES (1, 'christmas tree, 1.8m')")</literal>
indicaría tal error. Se podría comprobar el código de estado SQL de los errores para
determinar la consulta real (aquí: <literal>INSERT</literal>) que ha fallado
o el error relacionado con la transacción global. Depende de usted ignorar el
fallo de iniciar la transacción global en un servidor y continuar la ejecución
sin tener al servidor participando en la transacción global.
</para>
<para>
<example>
<title>Las transacciones locales y globales son mutuamente exclusivas</title>
<programlisting role="php">
<![CDATA[
<?php
$mysqli = new mysqli("myapp", "nombre_usuario", "contraseña", "base_datos");
if (!$mysqli) {
/* Por supuesto, su manejo de errores es mejor... */
die(sprintf("[%d] %s\n", mysqli_connect_errno(), mysqli_connect_error()));
}
/* iniciar una transacción local */
if (!$mysqli->begin_transaction()) {
die(sprintf("[%d/%s] %s\n", $mysqli->errno, $mysqli->sqlstate, $mysqli->error));
}
/* anora no se puede iniciar la transición global - primero debe finalizar la local */
$gtrid_id = "12345";
if (!mysqlnd_ms_xa_begin($mysqli, $gtrid_id)) {
die(sprintf("[%d/%s] %s\n", $mysqli->errno, $mysqli->sqlstate, $mysqli->error));
}
?>
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
Warning: mysqlnd_ms_xa_begin(): (mysqlnd_ms) Some work is done outside global transaction. You must end the active local transaction first in ... on line ...
[1400/XAE09] (mysqlnd_ms) Some work is done outside global transaction. You must end the active local transaction first
]]>
</screen>
</example>
</para>
<para>
Una transacción global no se puede iniciar cuando una transacción local está activa.
El complelento intenta detectar esta situación tan pronto como sea posible, que es cuando
se llama a <function>mysqlnd_ms_xa_begin</function>. Si solamente se emplean llamadas a la API para
controlar transacciones, el complemento sabrá qué transacción local está abierta,
devolviendo un error para <function>mysqlnd_ms_xa_begin</function>. Sin embargo, observe que
las <link linkend="mysqlnd-ms.transaction">limitaciones para detectar límites
de transacciones</link> del complemento. En el peor de los casos, si se emplea SQL directo
para transacciones locales (<literal>BEGIN</literal>,
<literal>COMMIT</literal>, ...), podría suceder que un error de demorara
hasta que se ejecute alguna sentencia SQL en el servidor.
</para>
<para>
Para finalizar una transacción global se ha de invocar a <function>mysqlnd_ms_xa_commit</function> o
a <function>mysqlnd_ms_xa_rollback</function>. Cuando finaliza una transacción global,
todos los participantes deben ser informados de dicha finalización. Por lo tando, PECL/mysqlnd_ms
enviará de forma transparente las sentencias SQL relacionadas apropiadas
a alguno o todos ellos. Cualquier fallo durante esta fase ocasionará una reversión
implícita. La API relacionada con XA se mantiene aquí intencionadamente simple. Una API
más compleja que proporcionara más control dejaría al descubierto pocas ventajas, si las hubiera,
sobre una implementación de usuario que enviara todas las sentencias SQL de XA de bajo nivel por sí misma.
</para>
<para>
Las transacciones XA emplean el protocolo de consignación de dos fases. El protocolo de consignación
de dos fases es un protocolo de bloqueo. Hay casos en los que no se puede hacer ningún
progreso, incluso al emplear tiempos de espera. Los coordinadores de transaccionesbe made, not even when using timeouts. Transaction coordinators
deberían sobrevivir a sus propios fallos, ser capaces de detectar bloqueos y deshacer empates.
<literal>PECL/mysqlnd_ms</literal> toma el papel de un coordinador de transacciones y puede ser
configurado para sobrevivir a su propia caída para evitar problemas con servidores de MySQL bloqueados.
Por tanto, el complemento puede y debería ser configurado para utilizar un estado persistente y seguro ante caídas
para permitir la recolección de basura de transacciones globales abortadas y no finalizadas.
Una transacción global puede ser abortada en un estado abierto si el complemento falla (se cae)
o falla una conexión desde el complemento a un participante de una transacción global.
</para>
<para>
<example>
<title>Almacenamiento del estado del coordinador de transacciones</title>
<programlisting role="ini">
<![CDATA[
{
"myapp": {
"xa": {
"state_store": {
"participant_localhost_ip": "192.168.2.12",
"mysql": {
"host": "192.168.2.13",
"user": "root",
"password": "",
"db": "test",
"port": "3312",
"socket": null
}
}
},
"master": {
"master_0": {
"host": "localhost",
"socket": "\/tmp\/mysql.sock"
}
},
"slave": {
"slave_0": {
"host": "192.168.2.14",
"port": "3306"
}
}
}
}
]]>
</programlisting>
</example>
</para>
<para>
Actualmente, <literal>PECL/mysqlnd_ms</literal> admite solamente el uso de tablas de bases de datos de MySQL
como almacenamiento de estados. Las definiciones de SQL de las tablas se proporcionan en la
<link linkend="ini.mysqlnd-ms-plugin-config-v2.xa">sección de configuración del complemento.</link>
Asegúrese de utilizar un motor de almacenamiento transaccional y seguro ante caídas
para las tablas, como InnoDB. InnoDB es el motor de tablas
predeterminado en versiones recientes del servidor de MySQL. Asegúrese también
de que el servidor de bases de datos sea altamente disponible.
</para>
<para>
Si se ha configurado un almacén de estado, el complemento puede realizar una recolección de basura.
Durante dicha recolección, podría ser necesario conectarse a un participante
de una transacción global fallida. Así, el almacén de estado mantiene una lista de participantes
y, entre otras cosas, sus nombres de host. Si la recolección de basura se ejecuta
en otro host que no sea el que haya escrito una entrada de participación con el
el nombre de host <literal>localhost</literal>, entonces <literal>localhost</literal>
se resolverá a máquinas diferentes. Hay dos soluciones al problema.
O no se configura ningún servidor con el nombre de host <literal>localhost</literal> y
se configura una dirección IP (y puerto), o se indica la recolección de basura.
En el ejemplo anterior, <literal>localhost</literal> se usa para
<literal>master_0</literal>, de ahí que podría no resolverse al host
correcto durante la recolección de basura. Sin embargo, <literal>participant_localhost_ip</literal>
también está establecido para indicar la recolección de basura que <literal>localhost</literal>
representa para la IP <literal>192.168.2.12</literal>.
</para>
</section>
<section xml:id="mysqlnd-ms.quickstart.qos-consistency">
<title>Nivel de servicio y consistencia</title>
<note>
<title>Requisitos de versión</title>
<para>
Los niveles de servicios han sido introducidos en la versión 1.2.0-alpha de PECL mysqlnd_ms.
<function>mysqlnd_ms_set_qos</function>
está disponible con PHP 5.4.0 o superior.
</para>
</note>
<para>
Las diferentes soluciones de clúster MySQL ofrecen diferentes niveles de
servicios y de consistencia de datos para sus usuarios. Un clúster de replicación MySQL asíncrono
ofrece la consistencia final de forma predeterminada. Una lectura ejecutada en un esclavo asíncrono
puede devolver datos actuales, antiguos, o ningún dato, dependiendo de si el esclavo
ha replicado todos los conjuntos de cambios del maestro.
</para>
<para>
Las aplicaciones que utilizan un clúster de replicación MySQL necesitan ser diseñadas para que funcionen
correctamente con datos de consistencia final. En algunos casos, sin embargo, los datos antiguos
no son aceptables. En estos casos solamente son permitidos ciertos accesos a esclavos o incluso a un maestro
para realizar la calidad de servicio requerida del clúster.
</para>
<para>
A partir de PECL mysqlnd_ms 1.2.0, el complemento puede seleccionar automáticamente
nodos de replicación de MySQL que proporcionen consistencia de sesión o
consistencia fuerte. La consistencia de sesión significa que un cliente puede leer sus escrituras.
Otros clientes pueden ver o no la escritura del cliente. La consistencia fuerte significa
que todos los clientes verán todas las escrituras del cliente.
</para>
<para>
<example>
<title>Consistencia de sesión: lectura de sus escrituras</title>
<programlisting role="ini">
<![CDATA[
{
"myapp": {
"master": {
"master_0": {
"host": "localhost",
"socket": "\/tmp\/mysql.sock"
}
},
"slave": {
"slave_0": {
"host": "127.0.0.1",
"port": "3306"
}
}
}
}
]]>
</programlisting>
</example>
</para>
<para>
<example>
<title>Solicitud de consistencia de sesión</title>
<programlisting role="php">
<![CDATA[
<?php
$mysqli = new mysqli("myapp", "nombre_usuario", "contraseña", "base_datos");
if (!$mysqli) {
/* Por supuesto, su manejo de errores es mejor... */
die(sprintf("[%d] %s\n", mysqli_connect_errno(), mysqli_connect_error()));
}
/* división de lecutura-escritura: se usa el maestro */
if (!$mysqli->query("INSERT INTO orders(order_id, item) VALUES (1, 'christmas tree, 1.8m')")) {
/* Por favor, use un manejo de errores mejor en su código */
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
}
/* Solicitud de consistencia de sesión: lectura de sus escrituras */
if (!mysqlnd_ms_set_qos($mysqli, MYSQLND_MS_QOS_CONSISTENCY_SESSION)) {
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
}
/* El complemento selecciona un nodo que posee los cambios, aquí: el maestro */
if (!$res = $mysqli->query("SELECT item FROM orders WHERE order_id = 1")) {
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
}
var_dump($res->fetch_assoc());
/* Volver a la consistencia final: se permiten datos antiguos */
if (!mysqlnd_ms_set_qos($mysqli, MYSQLND_MS_QOS_CONSISTENCY_EVENTUAL)) {
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
}
/* El complemento seleccina cualquier esclavo, se permiten datos antiguos */
if (!$res = $mysqli->query("SELECT item, price FROM specials")) {
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
}
?>
]]>
</programlisting>
</example>
</para>
<para>
Los niveles de servicios se pueden establecer en el fichero de configuración del complemento y
en tiempo de ejecución usando <function>mysqlnd_ms_set_qos</function>.
En el ejemplo, la función se usa para forzar la
consistencia de sesión (lectura de sus escrituras) para todas las sentencias futuras hasta próximo aviso.
La sentencia <literal>SELECT</literal> de la tabla <literal>orders</literal>
se ejecuta en el maestro para asegurarse de que las escrituras anteriores puedan ser vistas por el cliente.
La lógica de la división de lectura-escritura ha sido adaptada para satisfacer el nivel de servicio.
</para>
<para>
Después de que una aplicación haya leído sus cambios desde la tabla <literal>orders</literal>,
vuelve al nivel de servicio predeterminado, que es la consistencia final. Ésta
no pone restricciones al elegir un nodo para la ejecución de sentencias.
Por lo tanto, la sentencia <literal>SELECT</literal> de la tabla <literal>specials</literal>
se ejecuta en un esclavo.
</para>
<para>
La nueva funcionalidad sustituye el uso de sugerencias SQL y la opción
de configuración <literal>master_on_write</literal>. En muchos casos,
<function>mysqlnd_ms_set_qos</function> es más fácil de usar, más potente y
mejora la portabilidad.
</para>
<para>
<example>
<title>Edad máxima/demora del esclavo</title>
<programlisting role="ini">
<![CDATA[
{
"myapp": {
"master": {
"master_0": {
"host": "localhost",
"socket": "\/tmp\/mysql.sock"
}
},
"slave": {
"slave_0": {
"host": "127.0.0.1",
"port": "3306"
}
},
"failover" : "master"
}
}
]]>
</programlisting>
</example>
</para>
<para>
<example>
<title>Limitar la demora del esclavo</title>
<programlisting role="php">
<![CDATA[
<?php
$mysqli = new mysqli("myapp", "nombre_usuario", "contraseña", "base_datos");
if (!$mysqli) {
/* Por supuesto, su manejo de errores es mejor... */
die(sprintf("[%d] %s\n", mysqli_connect_errno(), mysqli_connect_error()));
}
/* Demora de la lectura desde los esclavos de no más de cuatro segundos */
$ret = mysqlnd_ms_set_qos(
$mysqli,
MYSQLND_MS_QOS_CONSISTENCY_EVENTUAL,
MYSQLND_MS_QOS_OPTION_AGE,
4
);
if (!$ret) {
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
}
/* El comlemento elige cualquier esclavo, el cual puede o no poseer los cambios */
if (!$res = $mysqli->query("SELECT item, price FROM daytrade")) {
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
}
/* Volver a lo predeterminado: usar todos los esclavos y maestros permitidos */
if (!mysqlnd_ms_set_qos($mysqli, MYSQLND_MS_QOS_CONSISTENCY_EVENTUAL)) {
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
}
?>
]]>
</programlisting>
</example>
</para>
<para>
El nivel de servicio de consistencia final se puede usar con un parámetro
opcional para establecer la demora máxima de los esclavos elegidos. Si se establece,
el complemento comprueba <literal>SHOW SLAVE STATUS</literal> para todos
los esclavos configurados. En el caso del ejemplo, solamente los esclavos
para los que <literal>Slave_IO_Running=Yes</literal>,
<literal>Slave_SQL_Running=Yes</literal> y
<literal>Seconds_Behind_Master &lt;= 4</literal>
sea verdadero, son tenidos en cuenta para la ejecución de la sentencia
<literal>SELECT item, price FROM daytrade</literal>.
</para>
<para>
La comprobacion de <literal>SHOW SLAVE STATUS</literal> se realiza de manera transparente desde
la perspectiva de las aplicaciones. Los errores, si los hubiera, son notificados como
advertencias. No se establecerá ningún error en el gestor de conexión. Incluso si todas
las sentencias SQL <literal>SHOW SLAVE STATUS</literal> ejecutadas por el
complemento fallan, la ejecución de las sentencias del usuario no se detienen, dado
que la toleracia a fallos del maestro está habilitada. Por lo tanto, no se requieren cambios en la aplicación.
</para>
<note>
<title>Operaciones caras y lentas</title>
<para>
La comprobación de <literal>SHOW SLAVE STATUS</literal> para todos los esclavos añade carga adicional
a la aplicación. Es una operación en segundo plano cara y lenta.
Intente minimizar su uso. Desafortunadamente, un clúster de replicación MySQL
no proporciona a los clientes la posibilidad de solicitar una lista de candidatos
desde una instancia central.
En consecuencia, no existe una manera más eficiente de comprobar la demora de los esclavos.
</para>
<para>
Por favor, observe las limitaciones y propiedades de <literal>SHOW SLAVE STATUS</literal>
tal y como están explicadas en el manual de referencia de MySQL.
</para>
</note>
<para>
Para evitar que mysqlnd_ms emita una advertencia si no se pudieron encontrar esclavos
que se demoren no más que el número de segundos definido con respecto al maestro,
es necesario habilitar la tolerancia a fallos del maestro en el fichero de configuración del complemento.
Si no se pudieron encontrar esclavos y la tolerancia a fallos está habilitada, el complemento
eligirá un maestro para ejecutar la setencia.
</para>
<para>
Si no se pudieron encontrar esclavos y la tolerancia a fallos está deshabilitada, el complemento emitirá
una advertencia, no ejecutará la sentencia y establecerá un error
sobre la conexión.
</para>
<para>
<example>
<title>La tolerancia a fallos no está establecida</title>
<programlisting role="ini">
<![CDATA[
{
"myapp": {
"master": {
"master_0": {
"host": "localhost",
"socket": "\/tmp\/mysql.sock"
}
},
"slave": {
"slave_0": {
"host": "127.0.0.1",
"port": "3306"
}
}
}
}
]]>
</programlisting>
</example>
</para>
<para>
<example>
<title>No hay esclavos dentro del límite de tiempo</title>
<programlisting role="php">
<![CDATA[
<?php
$mysqli = new mysqli("myapp", "nombre_usuario", "contraseña", "base_datos");
if (!$mysqli) {
/* Por supuesto, su manejo de errores es mejor... */
die(sprintf("[%d] %s\n", mysqli_connect_errno(), mysqli_connect_error()));
}
/* Demora de la lectura desde los esclavos de no más de cuatro segundos */
$ret = mysqlnd_ms_set_qos(
$mysqli,
MYSQLND_MS_QOS_CONSISTENCY_EVENTUAL,
MYSQLND_MS_QOS_OPTION_AGE,
4
);
if (!$ret) {
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
}
/* El comlemento elige cualquier esclavo, el cual puede o no poseer los cambios */
if (!$res = $mysqli->query("SELECT item, price FROM daytrade")) {
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
}
/* Volver a lo predeterminado: usar todos los esclavos y maestros permitidos */
if (!mysqlnd_ms_set_qos($mysqli, MYSQLND_MS_QOS_CONSISTENCY_EVENTUAL)) {
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
}
?>
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
PHP Warning: mysqli::query(): (mysqlnd_ms) Couldn't find the appropriate slave connection. 0 slaves to choose from. Something is wrong in %s on line %d
PHP Warning: mysqli::query(): (mysqlnd_ms) No connection selected by the last filter in %s on line %d
[2000] (mysqlnd_ms) No connection selected by the last filter
]]>
</screen>
</example>
</para>
</section>
<section xml:id="mysqlnd-ms.quickstart.gtid">
<title>ID de transacción globales</title>
<note>
<title>Requisitos de versión</title>
<para>
Se ha introducido una inyección de ID de transacciones global en el lado del cliente en la versión 1.2.0-alpha de mysqlnd_ms.
Esta característica no es necesaria para clústeres sincrónicos, tales como el Clúster MySQL.
Utilícela con clústeres asíncronos tales como la replicación MySQL clásica.
</para>
<para>
A partir de la versión candidata MySQL 5.6.5-m8, el servidor MySQL introduce los identificadores de transacciones globales internos.
La característica del ID de transacciones global interno está soportada por <literal>PECL/mysqlnd_ms</literal> 1.3.0-alpha o
posterior. Sin embargo, el conjunto final de características encontrado en las versiones de producción de MySQL 5.6 hasta la fecha,
no es suficiente para soportar las ideas tratadas abajo en todos los casos. Véase también la
<link linkend="mysqlnd-ms.gtid">sección de conceptos</link>.
</para>
</note>
<para>
<literal>PECL/mysqlnd_ms</literal> puede usar tanto su propia emulación del ID de transacciones global como la
característica del ID de transacciones global interna de MySQL 5.6.5-m8 o posterior. Desde la perspectiva del
desarrolador, los enfoques del lado del cliente y del lado del servidor ofrecen las mismas características en cuanto
a niveles de servicios proporcionados por PECL/mysqlnd_ms. Las diferencias
son tratadas en la <link linkend="mysqlnd-ms.gtid">sección de conceptos</link>.
</para>
<para>
Esta guía rápida primero demuestra el uso de la emulación interna del ID de transacciones global en el
lado del cliente en <literal>PECL/mysqlnd_ms</literal> antes de mostrar cómo usar su homólogo en el lado del servidor.
Este orden asegura que la idea subyacente se trata primero.
</para>
<para>
<emphasis role="bold">La idea y la emulación en el lado del cliente</emphasis>
</para>
<para>
En su forma más básica, un ID de Transacciones Global (GTID de sus siglas en inglés) es un contador de una tabla del
maestro. El contador se incrementa siempre que se consigne una transacción en el maestro.
Los esclavos replican la tabla. El contador sirve para dos propósitos. En caso de un
fallo del maestro, ayuda al administrador de la base de datos a identificar al esclavo que fue
promovido más recientemente como nuevo maestro. Este esclavo es aquel con el
valor de contador más alto. Las aplicaciones pueden usar el ID de transacciones global para buscar
los esclavos que ya han replicado una escritura en particular (identificada por un ID de transacciones
global).
</para>
<para>
<literal>PECL/mysqlnd_ms</literal> puede inyectar SQL para cada transacción consignada para incrementar un contador GTID.
El ID creado es accesible por la aplicación para poder identificar una operación de escritura
de una aplicación. Esto habilita al complemento para proporcionar el nivel de servicio de consistencia de sesión
(lectura de sus escrituras) mediante no solamente la consulta a los maestros, sino también a los esclavos que ya han
replicado el cambio. La carga de lectura se le quita al maestro.
</para>
<para>
La emulación del ID de transacciones global en el lado del cliente tiene algunas limitaciones. Por favor,
lea la <link linkend="mysqlnd-ms.gtid">sección de conceptos</link>
detenidamente para comprender completamente los principios y las ideas
subyacentes antes de usarla en entornos de producción. No es necesario un conocimiento
profundo para continuar con esta guía rápida.
</para>
<para>
Primero, cree una tabla contador en su servidor maestro e inserte un registro en ella.
El complemento no asiste en la creación de la tabla.
Los administradores de la base de datos deben asegurarse de que existe. Dependiendo del modo de notificación
de errores, el complemento ignorará de forma silenciosa la ausencia de la tabla o abandonará.
</para>
<para>
<example>
<title>Crear una tabla contador en el maestro</title>
<programlisting role="sql">
<![CDATA[
CREATE TABLE `trx` (
`trx_id` int(11) DEFAULT NULL,
`last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=latin1
INSERT INTO `trx`(`trx_id`) VALUES (1);
]]>
</programlisting>
</example>
</para>
<para>
En el fichero de configuración del complemento establezca el SQL para actualizar la
tabla del ID de transacciones global usando <literal>on_commit</literal>
en la sección <literal>global_transaction_id_injection</literal>.
Asegúrese de que el nombre de la tabla usado para la sentencia <literal>UPDATE</literal>
está completamente cualificado. En el ejemplo,
<literal>test.trx</literal> se usa para referirse a la tabla <literal>trx</literal>
del esquema (base de datos) <literal>test</literal>. Use la tabla que se creó en
el paso anterior. Es importante establecer el nombre de la tabla completamente cualificado,
ya que la conexión donde se realiza la inyección puede usar una base de datos predeterminada
diferente. Asegúrese de que al usuario que abra la conexión
se le permita ejecutar la sentencia <literal>UPDATE</literal>.
</para>
<para>
Habilite la notificación de los errores que pudieran ocurrir cuando mysqlnd_ms realice inyecciones
de ID de transacciones global.
</para>
<para>
<example>
<title>Configuración del complemento: SQL para la inyección del GTID en el lado del cliente</title>
<programlisting role="ini">
<![CDATA[
{
"myapp": {
"master": {
"master_0": {
"host": "localhost",
"socket": "\/tmp\/mysql.sock"
}
},
"slave": {
"slave_0": {
"host": "127.0.0.1",
"port": "3306"
}
},
"global_transaction_id_injection":{
"on_commit":"UPDATE test.trx SET trx_id = trx_id + 1",
"report_error":true
}
}
}
]]>
</programlisting>
</example>
</para>
<para>
<example>
<title>Inyección transparente del ID de transacciones global</title>
<programlisting role="php">
<![CDATA[
<?php
$mysqli = new mysqli("myapp", "nombre_usuario", "contraseña", "base_datos");
if (!$mysqli) {
/* Por supuesto, su manejo de errores es mejor... */
die(sprintf("[%d] %s\n", mysqli_connect_errno(), mysqli_connect_error()));
}
/* modo autoconsigna, transacción en el maestro, el GTID debe ser incrementado */
if (!$mysqli->query("DROP TABLE IF EXISTS test")) {
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
}
/* modo autoconsigna, transacción en el maestro, el GTID debe ser incrementado */
if (!$mysqli->query("CREATE TABLE test(id INT)")) {
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
}
/* modo autoconsigna, transacción en el maestro, el GTID debe ser incrementado */
if (!$mysqli->query("INSERT INTO test(id) VALUES (1)")) {
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
}
/* modo autoconsigna, lectura en el esclavo, sin incremento */
if (!($res = $mysqli->query("SELECT id FROM test"))) {
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
}
var_dump($res->fetch_assoc());
?>
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
array(1) {
["id"]=>
string(1) "1"
}
]]>
</screen>
</example>
</para>
<para>
El ejemplo ejecuta tres sentencias en el modo autoconsigna en el maestro, causando
tres transacciones en el maestro. Para cada sentencia, el complemento
inyectará el <literal>UPDATE</literal> configurado de forma transparente antes de ejecutar
las sentencias SQL del usuario. Cuando finaliza el script, el contador de IDs
de transacciones globales en el maestro ha sido incrementado en tres.
</para>
<para>
La cuarta sentencia SQL ejecutada en el ejemplo, un <literal>SELECT</literal>,
no desencadena un incremento. Solamente las transacciones (escrituras) ejecutadas en un maestro
incrementarán el contador GTID.
</para>
<note>
<title>SQL para el ID de transacciones global: ¡se busca una solución eficiente!</title>
<para>
El SQL usado para la emulación del ID de transacciones global es ineficiente.
Está optimizado para la trasnparencia, no para el rendimiento. No lo use para entornos de
producción. Por favor, ayude a encontrar una solución eficiente para su inclusión en el manual.
Apreciamos su aportación.
</para>
</note>
<para>
<example>
<title>Configuración del complemento: SQL para obtener el GTID</title>
<programlisting role="ini">
<![CDATA[
{
"myapp": {
"master": {
"master_0": {
"host": "localhost",
"socket": "\/tmp\/mysql.sock"
}
},
"slave": {
"slave_0": {
"host": "127.0.0.1",
"port": "3306"
}
},
"global_transaction_id_injection":{
"on_commit":"UPDATE test.trx SET trx_id = trx_id + 1",
"fetch_last_gtid" : "SELECT MAX(trx_id) FROM test.trx",
"report_error":true
}
}
}
]]>
</programlisting>
</example>
</para>
<para>
<example>
<title>Obtener el GTID después de la ejecución</title>
<programlisting role="php">
<![CDATA[
<?php
$mysqli = new mysqli("myapp", "nombre_usuario", "contraseña", "base_datos");
if (!$mysqli) {
/* Por supuesto, su manejo de errores es mejor... */
die(sprintf("[%d] %s\n", mysqli_connect_errno(), mysqli_connect_error()));
}
/* modo autoconsigna, transacción en el maestro, el GTID debe ser incrementado */
if (!$mysqli->query("DROP TABLE IF EXISTS test")) {
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
}
printf("GTID después de la transacción %s\n", mysqlnd_ms_get_last_gtid($mysqli));
/* modo autoconsigna, transacción en el maestro, el GTID debe ser incrementado */
if (!$mysqli->query("CREATE TABLE test(id INT)")) {
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
}
printf("GTID después de la transacción %s\n", mysqlnd_ms_get_last_gtid($mysqli));
?>
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
GTID después de la transacción 7
GTID después de la transacción 8
]]>
</screen>
</example>
</para>
<para>
Las aplicaciones pueden preguntar a PECL mysqlnd_ms por un ID de transacciones global que
pertenezca a la última operación de escritura realizada por la aplicación.
La función <function>mysqlnd_ms_get_last_gtid</function> devuelve el
GTID obtenido cuando se ejecuta la sentencia SQL desde la
entrada <literal>fetch_last_gtid</literal> de la sección
<literal>global_transaction_id_injection</literal> del
fichero de configuración del complemento. La función puede ser invocada
después de que el GTID haya sido incrementado.
</para>
<para>
Se aconseja que las aplicaciones no ejecuten la sentencia
SQL por sí mismas ya que aumenta el riesgo de que accidentalmente se ocasione un incremento
implícito del GTID. También, si se usa la función, es más fácil migrar
una aplicación desde una sentencia SQL para obtener un ID de transacciones a otra,
por ejemplo, si ningún servidor MySQL incluye el soporte interno para IDs de transacciones globales.
</para>
<para>
Esta guía rápida muestra una sentencia SQL que devolverá un GTID igual o mayor
que el creado por la sentencia anterior. Éste es exactamente el GTID creado
por la sentencia anterior si ningún cliente ha incrementado el GTID en el
tiempo transcurrido entre la ejecución de la sentencia y el <literal>SELECT</literal>
para obtener el GTID. De otro modo, será mayor.
</para>
<para>
<example>
<title>Configuración del complemento: comprobar un GTID en particular</title>
<programlisting role="ini">
<![CDATA[
{
"myapp": {
"master": {
"master_0": {
"host": "localhost",
"socket": "\/tmp\/mysql.sock"
}
},
"slave": {
"slave_0": {
"host": "127.0.0.1",
"port": "3306"
}
},
"global_transaction_id_injection":{
"on_commit":"UPDATE test.trx SET trx_id = trx_id + 1",
"fetch_last_gtid" : "SELECT MAX(trx_id) FROM test.trx",
"check_for_gtid" : "SELECT trx_id FROM test.trx WHERE trx_id >= #GTID",
"report_error":true
}
}
}
]]>
</programlisting>
</example>
</para>
<para>
<example>
<title>Nivel de servicio de consistencia de sesión y GTID combinados</title>
<programlisting role="php">
<![CDATA[
<?php
$mysqli = new mysqli("myapp", "nombre_usuario", "contraseña", "base_datos");
if (!$mysqli) {
/* Por supuesto, su manejo de errores es mejor... */
die(sprintf("[%d] %s\n", mysqli_connect_errno(), mysqli_connect_error()));
}
/* modo autoconsigna, transacción en el maestro, el GTID debe ser incrementado */
if ( !$mysqli->query("DROP TABLE IF EXISTS test")
|| !$mysqli->query("CREATE TABLE test(id INT)")
|| !$mysqli->query("INSERT INTO test(id) VALUES (1)")
) {
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
}
/* GTID es un identificador para la última escritura */
$gtid = mysqlnd_ms_get_last_gtid($mysqli);
/* Consistencia de sesión (lectura de sus escrituras): intentar leer de los esclavos, no sólo del maestro */
if (false == mysqlnd_ms_set_qos($mysqli, MYSQLND_MS_QOS_CONSISTENCY_SESSION, MYSQLND_MS_QOS_OPTION_GTID, $gtid)) {
die(sprintf("[006] [%d] %s\n", $mysqli->errno, $mysqli->error));
}
/* Ejecutar en el maestro o en el esclavo que ha replicado el INSERT */
if (!($res = $mysqli->query("SELECT id FROM test"))) {
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
}
var_dump($res->fetch_assoc());
?>
]]>
</programlisting>
</example>
</para>
<para>
Un GTID devuelto por <function>mysqlnd_ms_get_last_gtid</function>
se puede usar como una opción para el nivel de servicio de consistencia de sesión.
La consistencia de sesión proporciona la lectura de sus escrituras. La consistencia de sesión
puede ser solicitada llamando a
<function>mysqlnd_ms_set_qos</function>.
En el ejemplo, el complemento ejecutará la sentencia <literal>SELECT</literal>
en el maestro o en un esclavo que ya ha repliacdo
el <literal>INSERT</literal> anterior.
</para>
<para>
PECL mysqlnd_ms comprobará de forma transparente cada esclavo configurado, si
éste ha replicado el <literal>INSERT</literal>, mediante la comprobación de la tabla
GTID de esclavos. La comprobación se realiza ejecutando la sentencia SQL establecida con la
opción <literal>check_for_gtid</literal> de la
sección <literal>global_transaction_id_injection</literal> del
fichero de configuración del complemento. Por favor, observe que este es un procedimiento
lento y caro. Las aplicaciones deberían intentar usarlo poco y únicamente
si la carga de lectura en el maestro es alta.
</para>
<para>
<emphasis role="bold">Uso de la característica del ID de transacciones global en el lado del servidor</emphasis>
</para>
<note>
<title>Soporte del servidor insuficiente en MySQL 5.6</title>
<para>
El complemento ha sido derarrollado usando una versión de preproducción de MySQL 5.6.
Resulta de que todas las versiones de producción de MySQL 5.6 no proporcionan
clientes con suficiente información para forzar la consistencia de sesión basándose en GTID.
Por favor, lea la <link linkend="mysqlnd-ms.gtid">sección de conceptos</link>
para más detalles.
</para>
</note>
<para>
Desde MySQL 5.6.5-m8, el sistema de Replicación MySQL introduce los IDs de transacciones
globales. Los identificadores de transacciones son automáticamente generados y
mantenidos por el servidor. Los usuarios no necesitan ocuparse de ellos.
No hay necesidad de configurar ninguna tabla de antemano, ni de configurar
<literal>on_commit</literal>. Ya no es necesaria la emulación en el lado del cliente.
</para>
<para>
Los clientes puede seguir usando los identificadores de transacciones globales para conseguir
la consistencia de sesión al leer desde esclavos de la Replicación MySQL en algunos casos, ¡pero no en todos!.
El algoritmo funciona como está descrito arriba. Se deben configurar diferentes sentencias SQL para
<literal>fetch_last_gtid</literal> y <literal>check_for_gtid</literal>.
Estas sentencias se proporcionan más abajo. Por favor, observe que MySQL 5.6.5-m8 es una versión en
desarrollo. Los detalles de la implementación del servidor pueden cambiar en el futuro y requerir
la adopción de las sentencias SQL mostradas.
</para>
<para>
Al utilizar la siguiente configuración, cualquier funcionalidad descrita arriba puede
usarse junto con la característica del ID de transacciones global en el lado del servidor.
<function>mysqlnd_ms_get_last_gtid</function> y <function>mysqlnd_ms_set_qos</function>
siguen funcionando como se describió arriba. La única diferencia es que el servidor
no utiliza un número de secuencia simple, sino un string que contiene un identificador del servidor
y un número de secuencia. Por lo tanto, los usuarios no podrán obtener fáciltmente un orden desde los GTIDs
devueltos por <function>mysqlnd_ms_get_last_gtid</function>.
</para>
<para>
<example>
<title>Configuración de complemento: usar la característica del GTID interna de MySQL 5.6.5-m8</title>
<programlisting role="ini">
<![CDATA[
{
"myapp": {
"master": {
"master_0": {
"host": "localhost",
"socket": "\/tmp\/mysql.sock"
}
},
"slave": {
"slave_0": {
"host": "127.0.0.1",
"port": "3306"
}
},
"global_transaction_id_injection":{
"fetch_last_gtid" : "SELECT @@GLOBAL.GTID_DONE AS trx_id FROM DUAL",
"check_for_gtid" : "SELECT GTID_SUBSET('#GTID', @@GLOBAL.GTID_DONE) AS trx_id FROM DUAL",
"report_error":true
}
}
}
]]>
</programlisting>
</example>
</para>
</section>
<section xml:id="mysqlnd-ms.quickstart.cache">
<title>Integración de una caché</title>
<note>
<title>Requisitos de versión, dependencias y estado</title>
<para>
Se puede encontrar más sobre los requisitos de la versión, las dependencias del orden de carga de las extensiones y
el estado actual en la <link linkend="mysqlnd-ms.concept_cache">sección de conceptos</link>.
</para>
</note>
<para>
Los clústeres de bases de datos pueden proporcionar diferentes niveles de consistencia. A partir de
<literal>PECL/mysqlnd_ms</literal> 1.2.0 es posible acosejar al complemento para que cosidere solamente los
nodos del clúster que puedan proporcionar el nivel de consistencia requerido. Por ejemplo,
si se usa la Replicación MySQL con su consistencia final a lo ancho del
clúster, es posible solicitar la consistencia de sesión (lectura de sus escrituras)
en cualquier momento usando <function>mysqlnd_ms_set_quos</function>. Por favor, vea también la
introducción de
<link linkend="mysqlnd-ms.quickstart.qos-consistency">nivel de servicio y consistencia</link>.
</para>
<para>
<example>
<title>Resumen: calidad del servicio para solicitar la lectura de sus escrituras</title>
<programlisting role="php">
/* Solicitud de consistencia de sesión: lectura de sus escrituras */
if (!mysqlnd_ms_set_qos($mysqli, MYSQLND_MS_QOS_CONSISTENCY_SESSION))
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
</programlisting>
</example>
</para>
<para>
Asumiendo que a PECL/mysqlnd se le ha indicado explícitamente que proporcione un nivel de consistencia
no mayor que la consistencia final, es posible reemplazar un acceso de lectura de un nodo de
la base de datos por una caché en el lado del cliente usando el tiempo de vida (TTL) como
estrategia de invalidación. El nodo de la base de datos y la caché pueden o no servir
datos actuales, ya que esto es lo que la consistencia final define.
</para>
<para>
El reemplazo de un acceso de lectura de un nodo de la base de datos por un acceso a la caché local
puede mejorar el redimiento general y disminuir la carga de la base de datos. Si la entrada de la caché
es reutilizada por otro cliente que no sea el que creó dicha entrada,
se guaradará un acceso a la base de datos, por lo que se disminuye la carga de la base de datos. Además,
el rendimiento del sistema se puede mejorar si el cálculo y la entrega
de una consulta a la base de datos es más baja que un acceso a la caché local.
</para>
<para>
<example>
<title>Configuración del complemento: sin entradas especiales para la caché</title>
<programlisting role="ini">
<![CDATA[
{
"myapp": {
"master": {
"master_0": {
"host": "localhost",
"socket": "\/tmp\/mysql.sock"
}
},
"slave": {
"slave_0": {
"host": "127.0.0.1",
"port": "3306"
}
},
}
}
]]>
</programlisting>
</example>
</para>
<para>
<example>
<title>Almacenar en caché una petición a un servidor</title>
<programlisting role="php">
<![CDATA[
<?php
$mysqli = new mysqli("myapp", "nombre_usuario", "contraseña", "base_datos");
if (!$mysqli) {
/* Por supuesto, su manejo de errores es mejor... */
die(sprintf("[%d] %s\n", mysqli_connect_errno(), mysqli_connect_error()));
}
if ( !$mysqli->query("DROP TABLE IF EXISTS test")
|| !$mysqli->query("CREATE TABLE test(id INT)")
|| !$mysqli->query("INSERT INTO test(id) VALUES (1)")
) {
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
}
/* Explícitamente permite la consistencia final y el almacenamiento en caché (TTL <= 60 segundos) */
if (false == mysqlnd_ms_set_qos($mysqli, MYSQLND_MS_QOS_CONSISTENCY_EVENTUAL, MYSQLND_MS_QOS_OPTION_CACHE, 60)) {
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
}
/* Para que este ejemplo funcione, debemos esperar a que el esclavo se ponga al día. Estilo de fuerza bruta. */
$intentos = 0;
do {
/* comprobar si el esclavo posee la tabla */
if ($res = $mysqli->query("SELECT id FROM test")) {
break;
} else if ($mysqli->errno) {
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
}
/* esperar a que el esclavo se ponga al día */
usleep(200000);
} while ($intentos++ < 10);
/* La consulta ha sido ejecutada en un esclavo, el resultado está en la caché */
assert($res);
var_dump($res->fetch_assoc());
/* Servido desde la caché */
$res = $mysqli->query("SELECT id FROM test");
?>
]]>
</programlisting>
</example>
</para>
<para>
El ejemplo muestra cómo usar la caché. Primero, se ha de establecer
la calidad del servicio al de consistencia final y tener en cuenta explícitamente el almacenamiento en la caché.
Esto se lleva a cabo llamando a <function>mysqlnd_ms_set_qos</function>.
Luego, el conjunto de resultados de cada sentencia de solo lectura se almacena en la caché hasta un
máximo de segundos según lo permitido por <function>mysqlnd_ms_set_qos</function>.
</para>
<para>
El TTL real es menor o igual al valor establecido
con <function>mysqlnd_ms_set_qos</function>. El valor pasado a la función
establece la edad máxima (en segundos) de los datos proporcionados. Para calcular
el valor real del TTL, se comprueba la demora de replicación de un servidor y se resta
del valor dado. Si, por ejemplo, la edad máxima es de 60 segundos y
el esclavo notifica una demora de 10 segundos, el TTL resultante es de 50 segundos.
El TTL se calcula de forma individual para cada consulta de la caché.
</para>
<para>
<example>
<title>Lectura de sus escrituras y almacenamiento en la caché combinados</title>
<programlisting role="php">
<![CDATA[
<?php
$mysqli = new mysqli("myapp", "nombre_usuario", "contraseña", "base_datos");
if (!$mysqli) {
/* Por supuesto, su manejo de errores es mejor... */
die(sprintf("[%d] %s\n", mysqli_connect_errno(), mysqli_connect_error()));
}
if ( !$mysqli->query("DROP TABLE IF EXISTS test")
|| !$mysqli->query("CREATE TABLE test(id INT)")
|| !$mysqli->query("INSERT INTO test(id) VALUES (1)")
) {
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
}
/* Explícitamente permite la consistencia final y el almacenamiento en caché (TTL <= 60 segundos) */
if (false == mysqlnd_ms_set_qos($mysqli, MYSQLND_MS_QOS_CONSISTENCY_EVENTUAL, MYSQLND_MS_QOS_OPTION_CACHE, 60)) {
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
}
/* Para que este ejemplo funcione, debemos esperar a que el esclavo se ponga al día. Estilo de fuerza bruta. */
$intentos = 0;
do {
/* comprobar si el esclavo posee la tabla */
if ($res = $mysqli->query("SELECT id FROM test")) {
break;
} else if ($mysqli->errno) {
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
}
/* esperar a que el esclavo se ponga al día */
usleep(200000);
} while ($intentos++ < 10);
assert($res);
/* La consulta ha sido ejecutada en un esclavo, el resultado está en la caché */
var_dump($res->fetch_assoc());
/* Servido desde la caché */
if (!($res = $mysqli->query("SELECT id FROM test"))) {
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
}
var_dump($res->fetch_assoc());
/* Actualización en el maestro */
if (!$mysqli->query("UPDATE test SET id = 2")) {
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
}
/* Lectura de sus escrituras */
if (false == mysqlnd_ms_set_qos($mysqli, MYSQLND_MS_QOS_CONSISTENCY_SESSION)) {
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
}
/* Obtener los últimos datos */
if (!($res = $mysqli->query("SELECT id FROM test"))) {
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
}
var_dump($res->fetch_assoc());
?>
]]>
</programlisting>
</example>
</para>
<para>
La calidad del servicio se puede cambiar en cualquier momento para evitar el uso de la caché.
Si fuera necesario, se podría cambiar para leer sus escrituras (consistencia de sesión). En este caso,
la caché no se usará y se leerán datos actuales.
</para>
</section>
<section xml:id="mysqlnd-ms.quickstart.failover">
<title>Tolerancia a fallos</title>
<para>
Por omisión, el complemento no intenta la tolerancia a fallos si falla al conectarse a un equipo
anfitrión. Esto evita los problemas relacionados con el
<link linkend="mysqlnd-ms.quickstart.connectionpooling">estado de la conexión</link>.
Se recomienda tratar de forma manual los errores de conexión de una manera similar a una transacción
fallida. Se debería capturar el error, reconstruir el estado de la conexión y volver a ejecutar la
consulta tal como se muestra abajo.
</para>
<para>
Si el estado de la conexión no es la cuestión, de forma alternativa se puede habilitar la tolerancia
a fallos automática y silenciosa. Dependiendo de la configuración, la tolerancia a fallos automática
y silenciosa se intentará en el maestro antes de emitir un error, o se intentará
conectar a otros esclavos, dada la consulta permitida para ello, antes de intentar conectarse
a un maestro. Ya que la <link linkend="mysqlnd-ms.failover">tolerancia a fallos automática</link> no
es infalible, no se trata en esta guía rápida. En su lugar, los detalles se proporcionan
en la sección de conceptos posterior.
</para>
<para>
<example>
<title>Tolerancia a fallos manual, opcional automática</title>
<programlisting role="ini">
<![CDATA[
{
"myapp": {
"master": {
"master_0": {
"host": "localhost",
"socket": "\/tmp\/mysql.sock"
}
},
"slave": {
"slave_0": {
"host": "simulate_slave_failure",
"port": "0"
},
"slave_1": {
"host": "127.0.0.1",
"port": 3311
}
},
"filters": { "roundrobin": [] }
}
}
]]>
</programlisting>
</example>
</para>
<para>
<example>
<title>Tolerancia a fallos manual</title>
<programlisting role="php">
<![CDATA[
<?php
$mysqli = new mysqli("myapp", "nombre_usuario", "contraseña", "base_datos");
if (!$mysqli) {
/* Por supuesto, su manejo de errores es mejor... */
die(sprintf("[%d] %s\n", mysqli_connect_errno(), mysqli_connect_error()));
}
$sql = "SELECT 1 FROM DUAL";
/* el manejo de errores debería realizarse sin tener en cuenta el complemento */
if (!($res = $enlace->query($sql))) {
/* específico del complemento: comprobar errores de conexión */
switch ($enlace->errno) {
case 2002:
case 2003:
case 2005:
printf("Error de conexión - ¡intentando con el siguiente esclavo!\n");
/* el equilibrador de carga elegirá el siguiente esclavo */
$res = $enlace->query($sql);
break;
default:
/* no hay errores de conexión, la tolerancia a fallos es poco probable que ayude */
die(sprintf("SQL error: [%d] %s", $enlace->errno, $enlace->error));
break;
}
}
if ($res) {
var_dump($res->fetch_assoc());
}
?>
]]>
</programlisting>
</example>
</para>
</section>
<section xml:id="mysqlnd-ms.quickstart.partitioning">
<title>Particionamiento y fragmentación</title>
<para>
La agrupación (clustering) de bases de datos se realiza por varias razones. Los clúster pueden mejorar la disponibilidad,
la tolerancia a fallos, e incrementar el rendimiento aplicando un enfoque de "divide y vencerás",
ya que el trabajo se distribuye en varias máquinas. La agrupación (clustering) a veces se combina con
particionamiento y fragmentación para dividir aún más una tarea compleja grande en unidades
más pequeñas y manejables.
</para>
<para>
El complemento mysqlnd_ms pretende dar soporte a una gran variedad de clústeres de bases de datos de MySQL. Algunos sabores de
clúster de bases de datos de MySQL poseen métodos internos para el particionamiento y la fragmentacion,
los cuales podrían ser transparentes de usar. El complemetno admite los dos enfoques más
comunes: filtración de tabla de Replicación de MySQL, y la fragmentación
(particionamiento basada en aplicación).
</para>
<para>
La Replicación de MySQL admite el particionamiento como filtros que permiten
crear esclavos que replican todas las bases de datos o específicas del maestro, o tablas.
Es entonces responsabilidad de la aplicación
elegir un esclavo según las reglas de los filtros. Se puede usar el filtro
<literal><link linkend="ini.mysqlnd-ms-plugin-config-v2.filter-node-groups">node_groups</link></literal>
de mysqlnd_ms para dar soporte manual a esto, o usar el filtro de tablas experimental.
</para>
<para>
El particionamiento manual o fragmentación está soportado a través del
filtro de agrupación de nodos, y de las sugerencias SQL a partir de 1.5.0. El filtro node_groups
permite asignar un nombre simbólico a un grupo de servidores maestros y esclavos.
En el ejemplo, el maestro <literal>master_0</literal> y <literal>slave_0</literal>
forman un grupo con el nombre de <literal>Partition_A</literal>. Es su responsabilidad
decidir lo que haga un grupo. Por ejemplo, se podrían usar grupos de
nodos para la fragmentación, y usar los nombres de grupos para direccionar fragmentos
como <literal>Shard_A_Range_0_100</literal>.
</para>
<para>
<example>
<title>Clúster de grupos de nodos</title>
<programlisting role="ini">
<![CDATA[
{
"myapp": {
"master": {
"master_0": {
"host": "localhost",
"socket": "\/tmp\/mysql.sock"
}
},
"slave": {
"slave_0": {
"host": "simulate_slave_failure",
"port": "0"
},
"slave_1": {
"host": "127.0.0.1",
"port": 3311
}
},
"filters": {
"node_groups": {
"Partition_A" : {
"master": ["master_0"],
"slave": ["slave_0"]
}
},
"roundrobin": []
}
}
}
]]>
</programlisting>
</example>
</para>
<para>
<example>
<title>Particionamiento manual usando sugerencias SQL</title>
<programlisting role="php">
<![CDATA[
<?php
function select($mysqli, $msj, $sugerencia = '')
{
/* Nota: prueba débil, dos conexiones a dos servidores podrían tener el mismo id de hilo */
$sql = sprintf("SELECT CONNECTION_ID() AS _thread, '%s' AS _hint FROM DUAL", $msj);
if ($sugerencia) {
$sql = $sugerencia . $sql;
}
if (!($res = $mysqli->query($sql))) {
printf("[%d] %s", $mysqli->errno, $mysqli->error);
return false;
}
$fila = $res->fetch_assoc();
printf("%d - %s - %s\n", $fila['_thread'], $fila['_hint'], $sql);
return true;
}
$mysqli = new mysqli("myapp", "usuario", "contraseña", "base_datos");
if (!$mysqli) {
/* Por supuesto, su manejo de errores es mejor... */
die(sprintf("[%d] %s\n", mysqli_connect_errno(), mysqli_connect_error()));
}
/* Permitir siempre esclavos */
select($mysqli, "slave_0");
select($mysqli, "slave_1");
/* solamente permtir los servidores del grupo de nodos "Partition_A" */
select($mysqli, "slave_1", "/*Partition_A*/");
select($mysqli, "slave_1", "/*Partition_A*/");
?>
]]>
</programlisting>
<screen>
<![CDATA[
6804 - slave_0 - SELECT CONNECTION_ID() AS _thread, 'slave1' AS _hint FROM DUAL
2442 - slave_1 - SELECT CONNECTION_ID() AS _thread, 'slave2' AS _hint FROM DUAL
6804 - slave_0 - /*Partition_A*/SELECT CONNECTION_ID() AS _thread, 'slave1' AS _hint FROM DUAL
6804 - slave_0 - /*Partition_A*/SELECT CONNECTION_ID() AS _thread, 'slave1' AS _hint FROM DUAL
]]>
</screen>
</example>
</para>
<para>
Por omisión, el complemento usará todos los servidores maestros y esclavos configurados para
la ejecución de consultas. Pero si una consulta comienza con una sugerencia SQL como
<literal>/*node_group*/</literal>, el complemento considerará únicamente los servidores
enumerados en <literal>node_group</literal> para la ejecución de consultas. Así,
las consultas <literal>SELECT</literal> prefijadas con <literal>/*Partition_A*/</literal>
úncamente serán ejecutadas en <literal>slave_0</literal>.
</para>
</section>
<section xml:id="mysqlnd-ms.quickstart.mysql_fabric">
<title>MySQL Fabric</title>
<note>
<title>Requisitos de versión y estado</title>
<para>
Se comenzó a trabajar en el soporte para MySQL Fabricen la versión 1.6.
Considere el soporte con una calidad prealfa. El manual podría no enumerar
todas las limitaciones de la/s característica/s. Aún se está trabajand en ello.
</para>
<para>
La fragmentación es el único caso de uso soportado por el complemento hasta la fecha.
</para>
</note>
<note>
<title>Conceptos de MySQL Fabric</title>
<para>
Por favor, verfique el manual de referencia de MySQL para más información sobre MySQL Fabric
y cómo configurarlo. El manual de PHP asumen que se es familiar
con los conceptos e ideas básicos de MySQL Fabric.
</para>
</note>
<para>
MySQL Fabric es un sistema para gestionar granjas de servidores MySQL para lograr una
Alta Disponibilidad y opcionalmente soporte para fragmentación. Técnicamente, es un
middleware para gestionar y monitorizar servidores MySQL.
</para>
<para>
Los clientes consultan a MySQL Fabric para obtener listas de servidores MySQL,
sus estados y sus roles. Por ejemplo, los clientes pueden solicitar una lista de
esclavos para un grupo de Replicacion MySQL y para ver si están listos para
manejar peticiones SQL. Otro ejemplo es un clúster de servidores de MySQL fragmentados
donde el cliente busca conocer a qué fragmento consultar para una tabla y clave de
de fragmentación dados. Si se configura para utilizar Fabric, el complemento usa XML RCP sobre HTTP
para obtener la lista en tiempo de ejecución desde un host deto MySQL Fabric. La llamada XML a un
procedimiento remoto se realiza en segundo plano y de forma transparente desde el punto
de vista de los desarrolladores.
</para>
<para>
En lugar de enumerar los servidores MySQL directamente en el fichero de configuración del
complemento, este contiene una lista de uno o más host de MySQL Fabric.
</para>
<para>
<example>
<title>Configuración del complemento: hosts de Fabric en lugar de servidores MySQL</title>
<programlisting role="ini">
<![CDATA[
{
"myapp": {
"fabric": {
"hosts": [
{
"host" : "127.0.0.1",
"port" : 8080
}
]
}
}
}
]]>
</programlisting>
</example>
</para>
<para>
Los usuarios utilizan las nuevas funciones
<function>mysqlnd_ms_fabric_select_shard</function> y
<function>mysqlnd_ms_fabric_select_global</function> para cambiar al
conjunto de servidores responsables de una clave de fragmento dada. Luego, el
complemento elige un servidor apropiado para ejecutar las consultas.
Al hacer esto, el complemento se encarga del conjunto de reglas adicionales
de equilibrado de carga.
</para>
<para>
El ejemplo de abajo asume que MySQL Fabric ha sido configurado
al fragmento de tabla <literal>test.fabrictest</literal> usando
la columna <literal>id</literal> de la tabla como clave de fragmento.
</para>
<para>
<example>
<title>Particionameinto manual usando sugerencias SQL</title>
<programlisting role="php">
<![CDATA[
<?php
$mysqli = new mysqli("myapp", "usuario", "contraseña", "base_datos");
if (!$mysqli) {
/* Por supuesto, su manejo de errores es mejor... */
die(sprintf("[%d] %s\n", mysqli_connect_errno(), mysqli_connect_error()));
}
/* Crear una tabla global - una tabla disponible en todos los fragmentos */
mysqlnd_ms_fabric_select_global($mysqli, "test.fabrictest");
if (!$mysqli->query("CREATE TABLE test.fabrictest(id INT NOT NULL PRIMARY KEY)")) {
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
}
/* Cambiar la conexión al fragmento apropiado e insertar un registro */
mysqlnd_ms_fabric_select_shard($mysqli, "test.fabrictest", 10);
if (!($res = $mysqli->query("INSERT INTO fabrictest(id) VALUES (10)"))) {
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
}
/* Intentar leer el recién insertado registro */
mysqlnd_ms_fabric_select_shard($mysqli, "test.fabrictest", 10);
if (!($res = $mysqli->query("SELECT id FROM test WHERE id = 10"))) {
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
}
?>
]]>
</programlisting>
</example>
</para>
<para>
El ejemplo crea una tabla fragmentada, inserta un registro y lo lee
después. Todas las operaciones de lenguaje de definición de datos (DDL)
sobre una tabla fragmentada deben aplicarse a lo que se llama el grupo de servidores global.
Antes de crear o alterar una tabla fragmentada, se llama a
<function>mysqlnd_ms_fabric_select_global</function>
para cambiar la conexión dada a los servidores correspondientes del grupo
global. Las senetencias SQL de manipulación de datos (DML) deben ser enviadas a los fragmentos
directamente. La función <link linkend="function.mysqlnd-ms-fabric-select-shard">
<function>mysqlnd_ms_fabric_select_shard</function></link> cambia una
conexión para manejar el fragmento de una cierta clave de fragmento.
</para>
</section>
</chapter>
<!-- Keep this comment at the end of the file
Local variables:
mode: sgml
sgml-omittag:t
sgml-shorttag:t
sgml-minimize-attributes:nil
sgml-always-quote-attributes:t
sgml-indent-step:1
sgml-indent-data:t
indent-tabs-mode:nil
sgml-parent-document:nil
sgml-default-dtd-file:"~/.phpdoc/manual.ced"
sgml-exposed-tags:nil
sgml-local-catalogs:nil
sgml-local-ecat-files:nil
End:
vim600: syn=xml fen fdm=syntax fdl=2 si
vim: et tw=78 syn=sgml
vi: ts=1 sw=1
-->