Comment accéder à SQL Server dans le cadre d'une transaction XA avec le driver ODBC Easysoft SQL Server et Oracle Tuxedo.
Présentation
Pourquoi les transactions distribuées sont nécessaires
Une transaction est une série d'actions exécutées en une seule opération dans laquelle toutes les actions sont exécutées ou aucune d'entre elles ne l'est. Une transaction se termine par une action de validation qui rend les modifications permanentes. Si l'une des modifications ne peut pas être validée, la transaction sera annulée, annulant toutes les modifications.
Une transaction distribuée est une transaction qui peut s'étendre sur plusieurs ressources. Par exemple, une ou plusieurs bases de données ou une base de données et une file d'attente de messages. Pour que la transaction soit validée avec succès, toutes les ressources individuelles doivent être validées avec succès ; si l'une d'entre elles échoue, la transaction doit être annulée dans toutes les ressources. Par exemple, une transaction distribuée peut consister en un transfert d'argent entre deux comptes bancaires, hébergés par des banques différentes, et donc également sur des bases de données différentes. Vous ne voudriez pas qu'une transaction soit validée sans la garantie que les deux se termineront avec succès. Sinon, les données peuvent être dupliquées (si l'insertion se termine et la suppression échoue) ou perdues (si la suppression se termine et l'insertion échoue).
Chaque fois qu'une application a besoin d'accéder ou de mettre à jour les données dans plusieurs ressources transactionnelles, elle doit donc utiliser une transaction distribuée. Il est possible d'utiliser une transaction distincte sur chacune des ressources, mais cette approche est sujette aux erreurs. Si la transaction d'une ressource est validée avec succès mais qu'une autre échoue et doit être annulée, la première transaction ne peut plus être annulée, de sorte que l'état de l'application devient incohérent. Si une ressource est validée avec succès mais que le système plante avant que l'autre ressource ne puisse être validée avec succès, l'application est à nouveau incohérente.
XA
Le modèle X/Open Distributed Transaction Processing (DTP) définit une architecture pour le traitement distribué des transactions. Dans l'architecture DTP, un gestionnaire de transaction coordonnateur indique à chaque ressource comment traiter une transaction, en fonction de sa connaissance de toutes les ressources participant à la transaction. Les ressources qui gèrent normalement leur propre validation et récupération de transaction délèguent cette tâche au gestionnaire de transactions.
La spécification XA de l'architecture fournit une norme ouverte qui garantit l'interopérabilité entre les intergiciels transactionnels et les produits de base de données conformes. Ces différentes ressources sont donc capables de participer ensemble à une transaction distribuée.
Le modèle DTP comprend trois composants interdépendants :
- Un programme d'application qui définit les limites des transactions et spécifie les actions qui constituent une transaction.
- Des gestionnaires de ressources tels que des bases de données ou des systèmes de fichiers qui permettent d'accéder à des ressources partagées.
- Un gestionnaire de transactions qui attribue des identifiants aux transactions, surveille leur progression et assume la responsabilité de l'achèvement des transactions et de la reprise après échec.
La norme XA définit le protocole de validation en deux phases et l'interface utilisée pour la communication entre un gestionnaire de transactions et un gestionnaire de ressources. Le protocole de validation en deux phases fournit une garantie tout ou rien que tous les participants impliqués dans la transaction valident ou annulent ensemble. Par conséquent, la transaction entière est validée ou la transaction entière est annulée.
La validation en deux phases consiste en une phase de préparation et une phase de validation. Au cours de la phase de préparation, tous les participants à la transaction doivent accepter d'effectuer les modifications requises par la transaction. Si l'un des participants signale un problème, la phase de préparation échouera et la transaction sera annulée. Si la phase de préparation réussit, la phase deux, la phase de validation commence. Au cours de la phase de validation, le gestionnaire de transactions demande à tous les participants de valider la transaction.
SQL Server et XA
Pour activer la prise en charge de XA dans SQL Server 2019, suivez les instructions de la section "Exécution du service MS DTC" contenue dans ce document :
Comprendre les transactions XA
Pour activer la prise en charge de XA dans les versions antérieures de SQL Server, suivez les instructions de ce document :
Configuration des transactions XA dans Microsoft SQL Server pour IBM Business Process Manager (BPM)
Le pilote ODBC SQL Server a été testé avec des instances SQL Server 2016 et 2019 compatibles XA.
Le pilote ODBC Easysoft SQL Server
La prise en charge de XA a été ajoutée au pilote ODBC SQL Server dans la version 1.11.3. La prise en charge XA du pilote a été testée avec Oracle Tuxedo et SQL Server 2016 et 2019.
Pour inscrire le pilote ODBC SQL Server dans une transaction XA, vous devez utiliser une structure nommée es_xa_context
dans votre candidature. es_xa_context
se connecte à la source de données ODBC que vous avez spécifiée dans la configuration de votre gestionnaire de ressources XA et renvoie un descripteur de connexion. Par exemple :
int ret; SQLHANDLE hEnv, hConn; ret = es_xa_context( NULL, &hEnv, &hConn );
Dans Tuxedo, la source de données ODBC qui es_xa_context
se connecte à est spécifié dans le gestionnaire de ressources OPENINFO
chaîne dans le fichier de configuration de Tuxedo. Dans cet exemple, c'est "SQLSERVER_SAMPLE":
OPENINFO="EASYSOFT_SQLSERVER_ODBC:DSN=SQLSERVER_SAMPLE"
Le nom du gestionnaire de ressources XA et le commutateur XA définis par le pilote sont EASYSOFT_SQLSERVER_ODBC
et essql_xaosw
.
Dans Tuxedo, vous les spécifiez dans le fichier de définition de Tuxedo Resource Manager, ${TUXDIR}/udataobj/RM
. Par exemple :
EASYSOFT_SQLSERVER_ODBC:essql_xaosw:-L/usr/local/easysoft/sqlserver/lib -lessqlsrv -lodbcinst
Exemple d'application Easysoft / Tuxedo / SQL Server XA
Tout d'abord, configurez une source de données de pilote ODBC SQL Server qui se connecte à une instance SQL Server compatible XA :
- Sur votre ordinateur Tuxedo, installez le pilote ODBC SQL Server.
- Créez une source de données de pilote ODBC SQL Server dans odbc.ini. Par exemple :
[SQLSERVER_SAMPLE] Driver=Easysoft ODBC-SQL Server Description=Easysoft SQL Server ODBC driver Server=mymachine\myxaenabledinstance User=mydomain\myuser Password=mypassword Database=XA1
- Créez un exemple de table pour l'application Tuxedo :
$ /usr/local/easysoft/unixODBC/bin/isql.sh -v SQLSERVER_SAMPLE SQL> CREATE TABLE [dbo].[tx_test1]([i] [int] NULL,[c] [varchar](100) NULL)
Créez et exécutez l'exemple d'application Tuxedo XA.
-
$ cd ~ $ mkdir simpdir $ cd simpdir $ touch simpcl.c simpserv.c ubbsimple
- Ajoutez ces lignes à simpcl.c :
#include <stdio.h> #include "atmi.h" /* TUXEDO Header File */ #if defined(__STDC__) || defined(__cplusplus) main(int argc, char *argv[]) #else main(argc, argv) int argc; char *argv[]; #endif { char *sendbuf, *rcvbuf; long sendlen, rcvlen; int ret; if(argc != 2) { (void) fprintf(stderr, "Usage: simpcl <SQL>\n"); exit(1); } /* Attach to System/T as a Client Process */ if (tpinit((TPINIT *) NULL) == -1) { (void) fprintf(stderr, "Tpinit failed\n"); exit(1); } sendlen = strlen(argv[1]); /* Allocate STRING buffers for the request and the reply */ if((sendbuf = (char *) tpalloc("STRING", NULL, sendlen+1)) == NULL) { (void) fprintf(stderr,"Error allocating send buffer\n"); tpterm(); exit(1); } if((rcvbuf = (char *) tpalloc("STRING", NULL, sendlen+1)) == NULL) { (void) fprintf(stderr,"Error allocating receive buffer\n"); tpfree(sendbuf); tpterm(); exit(1); } (void) strcpy(sendbuf, argv[1]); /* Request the service EXECUTE, waiting for a reply */ ret = tpcall("EXECUTE", (char *)sendbuf, 0, (char **)&rcvbuf, &rcvlen, (long)0); if(ret == -1) { (void) fprintf(stderr, "Can't send request to service EXECUTE\n"); (void) fprintf(stderr, "Tperrno = %d\n", tperrno); tpfree(sendbuf); tpfree(rcvbuf); tpterm(); exit(1); } (void) fprintf(stdout, "Returned string is: %s\n", rcvbuf); /* Free Buffers & Detach from System/T */ tpfree(sendbuf); tpfree(rcvbuf); tpterm(); return(0); }
- Ajoutez ces lignes à simpserv.c :
#include <stdio.h> #include <ctype.h> #include <atmi.h> /* TUXEDO Header File */ #include <userlog.h> /* TUXEDO Header File */ #include <xa.h> #include <sql.h> #include <sqlext.h> #include <string.h> /* tpsvrinit is executed when a server is booted, before it begins processing requests. It is not necessary to have this function. Also available is tpsvrdone (not used in this example), which is called at server shutdown time. */ int tpsvrinit(int argc, char *argv[]) { int ret; /* Some compilers warn if argc and argv aren't used. */ argc = argc; argv = argv; /* simpapp is non-transactional, so there is no need for tpsvrinit() to call tx_open() or tpopen(). However, if this code is modified to run in a Tuxedo group associated with a Resource Manager then either a call to tx_open() or a call to tpopen() must be inserted here. */ /* userlog writes to the central TUXEDO message log */ userlog("Welcome to the simple server"); ret = tpopen(); userlog("tpopen returned %d, error=%x", ret, tperrno ); return(0); } void tpsvrdone( void ) { int ret; ret = tpclose(); userlog("tpclose returned %d", ret); } /* This function performs the actual service requested by the client. Its argument is a structure containing among other things a pointer to the data buffer, and the length of the data buffer. */ xa_open_entry() call. int es_xa_context( int* rmid, SQLHANDLE* henv, SQLHANDLE* hdbc ); void EXECUTE(TPSVCINFO *rqst) { int ret; char *result; SQLHANDLE hStmt; char str[ 256 ]; SQLHANDLE hEnv, hConn; SQLSMALLINT slen; ret = es_xa_context( NULL, &hEnv, &hConn ); userlog("es_xa_context returns %d, hEnv = %p, hConn = %p", ret, hEnv, hConn ); if ( ret != 0 ) { result = tpalloc( "STRING", "*", 128 ); sprintf( result, "es_xa_context returned %d", ret ); /* Return the transformed buffer to the requestor. */ tpreturn(TPSUCCESS, 0, result, strlen( result ), 0); } else { ret = tpbegin( 0, 0 ); ret = SQLAllocHandle( SQL_HANDLE_STMT, hConn, &hStmt ); ret = SQLExecDirect( hStmt, rqst -> data, rqst -> len ); ret = SQLFreeHandle( SQL_HANDLE_STMT, hStmt ); ret = tpcommit( 0 ); result = tpalloc( "STRING", "*", 128 ); sprintf( result, "tpcommit returns %d", ret ); /* Return the transformed buffer to the requestor. */ tpreturn(TPSUCCESS, 0, result, strlen( result ), 0); } }
- Ajoutez ces lignes à ubbsimple :
*RESOURCES IPCKEY 123456 DOMAINID simpapp MASTER simple MAXACCESSERS 20 MAXSERVERS 10 MAXSERVICES 10 MODEL SHM LDBAL N *MACHINES DEFAULT: APPDIR="/home/myuser/simpdir" TUXCONFIG="/home/myuser/simpdir/tuxconfig" TUXDIR="/home/myuser/OraHome/tuxedo12.2.2.0.0" mymachine LMID=simple TLOGNAME=TLOG TLOGDEVICE="/home/myuser/simpdir/tuxlog" *GROUPS GROUP1 LMID=simple GRPNO=1 OPENINFO=NONE TMSNAME=mySQLSERVER_TMS OPENINFO="EASYSOFT_SQLSERVER_ODBC:DSN=SQLSERVER_SAMPLE" *SERVERS DEFAULT: CLOPT="-A" simpserv SRVGRP=GROUP1 SRVID=1 *SERVICES EXECUTE
- Définissez votre environnement :
export TUXDIR=/home/myuser/OraHome/tuxedo12.2.2.0.0 export TUXCONFIG=/home/myuser/simpdir/tuxconfig export PATH=$PATH:$TUXDIR/bin export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$TUXDIR/lib:/usr/local/easysoft/unixODBC/lib: \ /usr/local/easysoft/sqlserver/lib:/usr/local/easysoft/lib
- Construisez l'exemple de client :
buildclient -o simpcl -f simpcl.c
Si vous obtenez l'erreur "référence non définie à dlopen" lors de la construction du client, essayez plutôt cette commande :
buildclient -o simpcl -f "-Xlinker --no-as-needed simpcl.c"
- Construisez l'exemple de serveur :
buildserver -r EASYSOFT_SQLSERVER_ODBC -s EXECUTE -o simpserv -f "simpserv.c \ -L/usr/local/easysoft/sqlserver/lib -lessqlsrv -lodbc"
- Créez le fichier TUXCONFIG pour l'exemple d'application :
tmloadcf ubbsimple
- Créez un périphérique de journalisation Tuxedo pour l'exemple d'application :
$ tmadmin -c > crdl -z /home/myuser/simpdir/tuxlog -b 512
- Créez un gestionnaire de transactions Tuxedo qui s'interface avec le pilote ODBC de SQL Server :
$ buildtms -o mySQLSERVER_TMS -r EASYSOFT_SQLSERVER_ODBC
- Démarrez le serveur d'exemple :
$ tmboot
- Testez l'exemple d'application :
./simpcl "insert into tx_test1 values( 1, 'hello world' )" /usr/local/easysoft/unixODBC/bin/isql.sh -v SQLSERVER_SAMPLE SQL> select * from tx_test1 +------------+--------------+ | i | c | +------------+--------------+ | 1 | hello world | +------------+--------------+
- Si vous voyez les données dans la table SQL Server, arrêtez l'exemple de serveur :
tmshutdown
Sinon, consultez ULOG.nnn dans le répertoire de l'application exemple.