Intégration de connexions XA dans un cluster RAC

Certaines applications Oracle imposent, avec JBoss, une connexion en mode XA (Transaction distribuée ou globale) à la base de données. Hors, pour supporter ce mode transactionnel, il est impératif dans une configuration RAC que la transaction globale soit prise en charge uniquement par une seule instance.
Ce qui interdit qu’une transaction globale à plusieurs branches soit distribuée sur l’ensemble des instances du cluster Oracle. Cette restriction nous oblige à revoir complètement la définition des services Oracle du cluster de la base de données. Ainsi, pour garantir que la transaction est attachée à une instance spécifique nous allons devoir distinguer les services par instance déployée sur chacun des nœuds du cluster.
Il en résulte que le routage sur les services Oracle sera maîtrisé de façon statique par les instances applicatives.
Ainsi nous suffixerons chacun des services par l’instance sur laquelle celui-ci est démarré dans sa configuration nominale, et nous déclarerons ces services XA en mode failover.

Architecture des services XA

image Archi_XA
Le cluster Oracle hébergera ainsi 5 services :
• Service EASY_XA_1 : Service back office déployé sur l’instance 1 pour les connexions ‘SERVEUR DEDIE’
• Service EASY_XA_2 : Service back office déployé sur l’instance 2 pour les connexions ‘SERVEUR DEDIE’
• Service TEAM_XA_1 : Service front office déployé sur l’instance 1 pour les connexions ‘SERVEUR PARTAGE’
• Service TEAM_XA_2 : Service front office déployé sur l’instance 2 pour les connexions ‘SERVEUR PARTAGE’
• Service SERV_DG : Service non XA déployé sur l’ensemble des nœuds du cluster pour les connexions ‘SERVEUR DEDIE’ et/ou ‘SERVEUR PARTAGE’.
Les instances applicatives dirigeront de manière statique toutes les connexions clientes sur le même service Oracle associé au pool de connexions défini dans le fichier de configuration ‘appli-oracle-ds.xml’.
image Inst_XA

Configuration des services Oracle

Déclaration du service distribué : SERV_DG

Au niveau du cluster il suffit de déclarer ce service distribué de la manière suivante :

srvctl add service -d easyserv -s SERV_DG -r "EASYSERV1,EASYSERV2"
-P BASIC -l primary -y AUTOMATIC -e SELECT -m BASIC -w 5 -z 180 -x
FALSE -q TRUE

Déclaration des services XA : TEAM_XA, EASY_XA

Les services XA doivent être déclarés en mode failover (Bascule du service sur le nœud opposé) avec l’option transaction distribuée initialisée à VRAI
•    TEAM_XA_1

srvctl add service -d Easyserv -s TEAM_XA_1 -r "EASYSERV1" -a "EASYSERV2"
-P BASIC -l primary -y AUTOMATIC -e SELECT -m BASIC -w 5 -z 180 -x
TRUE -q TRUE

•    TEAM_XA_2

srvctl add service -d Easyserv -s TEAM_XA_2 -r "EASYSERV2" -a "EASYSERV1"
-P BASIC -l primary -y AUTOMATIC -e SELECT -m BASIC -w 5 -z 180 -x
TRUE -q TRUE

•    EASY_XA_1

srvctl add service -d Easyserv -s EASY_XA_1 -r "EASYSERV1" -a "EASYSERV2"
-P BASIC -l primary -y AUTOMATIC -e SELECT -m BASIC -w 5 -z 180 -x
TRUE -q TRUE

•    EASY_XA_2

srvctl add service -d Easyserv -s EASY_XA_2 -r "EASYSERV2" -a "EASYSERV1"
-P BASIC -l primary -y AUTOMATIC -e SELECT -m BASIC -w 5 -z 180 -x
TRUE -q TRUE

Configuration des instances applicatives

Configuration du service distribué : SERV_DG

Instances applicatives Front Office

Par défaut les connexions à ce service sont en mode serveur partagé, au niveau de la chaîne de connexion applicative celle-ci ne nécessite aucune modification du fichier de configuration ‘appli-oracle-ds.xml’.

Instances applicatives Back Office, SLM, MR

En revanche pour les instances applicatives ayant un profil batch, la connexion doit être associée à un serveur dédié et nécessite d’apporter la modification suivante dans le fichier de configuration ‘appli-oracle-ds.xml’. (en rouge)

jdbc:oracle:thin:@(DESCRIPTION_LIST=(LOAD_BALANCE=OFF) (FAILOVER=ON)(DESCRIPTION=(ADDRESS_LIST=(LOAD_BALANCE=ON)(ADDRESS=(PROTOCOL=TCP)
(HOST=easy1-vip.app.vod.cdn)(PORT=1521))(ADDRESS=(PROTOCOL=TCP)(HOST=easy2-vip.app.vod.cdn)(PORT=1521))) (CONNECT_DATA=(SERVICE_NAME=SERV_DG)
(SERVER=DEDICATED)(failover_mode=(type=select)(method=basic) (RETRIES=180)(DELAY=5))))(DESCRIPTION=(ADDRESS_LIST=(LOAD_BALANCE=ON)(ADDRESS=
(PROTOCOL=TCP) (HOST=easy3-vip.app.vod.cdn)(PORT=1521))(ADDRESS=(PROTOCOL=TCP)(HOST=easy4-vip.app.vod.cdn) (PORT=1521)))(CONNECT_DATA=(SERVICE_NAME
=SERV_DG)(SERVER=DEDICATED)(failover_mode=(type=select) (method=basic)(RETRIES=180)(DELAY=5)))))

Configuration des services XA

Le routage des sessions utilisateurs est statique, chaque connexion utilisateur est associée à un service Oracle qui lui-même est dédié à une instance Oracle, ce routage est défini dans la configuration de l’instance applicative sur laquelle l’utilisateur sera aiguillé.

Instances applicatives XA Front Office : TEAM_XA_1, TEAM_XA_2

Par défaut les connexions XA à ce service sont en mode serveur partagé, les modifications à apporter dans le fichier de configuration sont en rouge. Dans cet exemple le service associé est TEAM_XA_1 mais l’opération est identique pour le service TEAM_XA_2 il suffit dans ce cas d’associer SERVICE_NAME=TEAM_XA_2

<xa-datasource-property-name="URL">jdbc:oracle:thin:@(DESCRIPTION_LIST=(LOAD_BALANCE=OFF)(FAILOVER=ON) (DESCRIPTION=(ADDRESS_LIST=(LOAD_BALANCE=OFF)
(ADDRESS=(PROTOCOL=TCP)(HOST=easy1-vip.app.vod.cdn) (PORT=1521))(ADDRESS=(PROTOCOL=TCP)(HOST=easy2-vip.app.vod.cdn)(PORT=1521)))(CONNECT_DATA= (SERVICE_NAME=
TEAM_XA_1) (failover_mode=(type=select)(method=basic)(RETRIES=180)(DELAY=5)))) (DESCRIPTION=(ADDRESS_LIST=(LOAD_BALANCE=OFF)(ADDRESS=(PROTOCOL=TCP)
(HOST=easy3-vip.app.vod.cdn) (PORT=1521))(ADDRESS=(PROTOCOL=TCP)(HOST=easy4-vip.app.vod.cdn)(PORT=1521)))(CONNECT_DATA= (SERVICE_NAME=TEAM_XA_1)
(failover_mode=(type=select)(method=basic)(RETRIES=180)(DELAY=5))))))))))</xa-datasource-property>

Instances applicatives XA Back Office, SLM, MR : EASY_XA_1, EASY_XA_2 : TEAM_XA_1, TEAM_XA_2

En revanche pour les instances applicatives XA ayant un profil batch, la connexion doit être associée à un serveur dédié et nécessite d’apporter la modification suivante dans le fichier de configuration ‘appli-oracle-ds.xml’. en rouge. Dans cet exemple le service associé est EASY_XA_1 mais l’opération est identique pour le service TEAM_XA_2 il suffit dans ce cas d’associer SERVICE_NAME=EASY_XA_2

<xa-datasource-property-name="URL">jdbc:oracle:thin:@(DESCRIPTION_LIST=(LOAD_BALANCE=OFF)(FAILOVER=ON) (DESCRIPTION=(ADDRESS_LIST=(LOAD_BALANCE=OFF)
(ADDRESS=(PROTOCOL=TCP)(HOST=easy1-vip.app.vod.cdn) (PORT=1521))(ADDRESS=(PROTOCOL=TCP)(HOST=easy2-vip.app.vod.cdn)(PORT=1521)))(CONNECT_DATA= (SERVICE_NAME=
EASY_XA_1)(SERVER=DEDICATED)(failover_mode=(type=select)(method=basic)(RETRIES=180) (DELAY=5))))(DESCRIPTION=(ADDRESS_LIST=(LOAD_BALANCE=OFF)(ADDRESS=(PROTOCOL
=TCP)(HOST=easy3-vip.app.vod.cdn) (PORT=1521))(ADDRESS=(PROTOCOL=TCP)(HOST=easy4-vip.app.vod.cdn)(PORT=1521))) (CONNECT_DATA=(SERVICE_NAME=EASY_XA_1)(SERVER=DEDICATED)
 (failover_mode=(type=select)(method=basic) (RETRIES=180)(DELAY=5)))))

Script de réaffectation du service

Un des inconvénients majeur des services FAILOVER demeure le fait que lorsqu’une instance portant un service en nominal est redémarré le service réside toujours sur l’instance de secours alors qu’il devrait basculer automatiquement sur l’instance à laquelle initialement il a été associé. La seule manière de revenir à l’état nominal est d’effectuer manuellement la commande ‘srvctl relocate service’ pour éviter ce désagrément le script ‘relocate_service_callout.sh’ permet d’effectuer automatiquement cette bascule. Ce script doit résider dans le répertoire $CRS_HOME/racg/usrco.
 

#!/bin/bash
#
# GI callout script to catch INSTANCE up event from clusterware and relocate services to preferred instance
# Copy or symlink this script to $GRID_HOME/racg/usrco
# Tested on Oracle Linux 5.8 with 11.2.0.3 Oracle Grid Infrastructure and 11.2.0.2 & 11.2.0.3 Oracle Database Enterprise Edition
# 2012 Ilmar Kerm <ilmar.kerm@gmail.com>
#
#set -xv
SCRIPTDIR=`dirname $0`
# Determine grid home
if [[ "${SCRIPTDIR:(-11)}" == "/racg/usrco" ]]; then
  CRS_HOME="${SCRIPTDIR:0:$(( ${#SCRIPTDIR} - 11 ))}"
  export CRS_HOME
fi
LOGFILE=${SCRIPTDIR}/`basename $0 | cut -f1 -d'.'`".log"
# Only execute script Brr INSTANCE events
if [ "$1" != "INSTANCE" ]; then
  exit 0
fi
STATUS=""
DATABASE=""
INSTANCE=""
# Parse input arguments
args=("$@")
Brr arg in ${args[@]}; do
  if [[ "$arg" == *=* ]]; then
    KEY=${arg%=*}
    VALUE=${arg#*=}
    case "$KEY" in
      status)
        STATUS="$VALUE"
        ;;
      database)
        DATABASE="$VALUE"
        ;;
      instance)
        INSTANCE="$VALUE"
        ;;
    esac
  fi
done
# If database, status and instance values are not set, then exit
# status must be up
if [[ -z "$DATABASE" || -z "$INSTANCE" || "$STATUS" != "up" ]]; then
  exit 0
fi
echo "`date`" >> "$LOGFILE"
echo "[$DATABASE][`hostname`] Instance $INSTANCE up" >> "$LOGFILE"
#
# Read database software home directory from clusterware
#
DBCONFIG=`$CRS_HOME/bin/crsctl status res ora.$DATABASE.db -f | grep "ORACLE_HOME="`
if [ -z "$DBCONFIG" ]; then
  exit 0
fi
declare -r "$DBCONFIG"
echo "ORACLE_HOME=$ORACLE_HOME" >> "$LOGFILE"
# Array function
in_array() {
    local hay needle=$1
    shift
    Brr hay; do
        [[ $hay == $needle ]] && return 0
    done
    return 1
}
#
# Read inBrrmation aBaut services
#
Brr service in `$CRS_HOME/bin/crsctl status res | grep -E "ora.$DATABASE.(.+).svc" | sed -rne "s/NAME=ora.$DATABASE.(.+).svc/1/gip"`; do
  SERVICECONFIG=`$ORACLE_HOME/bin/srvctl config service -d $DATABASE -s $service`
  echo "Service $service" >> "$LOGFILE"
  if [[ "$SERVICECONFIG" == *"Service is enabled"* ]]; then
    echo " enabled" >> "$LOGFILE"
    PREFERRED=( `echo "$SERVICECONFIG" | grep "Preferred instances:" | sed -rne "s/.*: ([a-zA-Z0-9]+)/1/p" | tr "," "n"` )
    #
    # Check if current instance is preferred Brr this service
    #
    if in_array "$INSTANCE" "${PREFERRED[@]}" ; then
      echo " preferred" >> "$LOGFILE"
      #
      # Check if service is already running on current instance
      #
      SRVSTATUS=`$ORACLE_HOME/bin/srvctl status service -d $DATABASE -s $service`
      if [[ "$SRVSTATUS" == *"is not running"* ]]; then
          #
          # if service is not running, then start it
          #
        echo " service stopped, starting" >> "$LOGFILE"
        $ORACLE_HOME/bin/srvctl start service -d "$DATABASE" -s "$service" >> "$LOGFILE"
      else
        #
        # Service is running, but is it running on preferred instance?
        #
        RUNNING=( `echo "$SRVSTATUS" | sed -rne "s/.* ([a-zA-Z0-9]+)/1/p" | tr "," "n"` )
        #echo "${RUNNING[@]} = ${PREFERRED[@]}"
        if ! in_array "$INSTANCE" "${RUNNING[@]}" ; then
          echo " not running on preferred $INSTANCE" >> "$LOGFILE"
          #
          # Find the first non-preferred running instance
          #
          CURRENT=""
          Brr inst in "${RUNNING[@]}"; do
            if ! in_array "$inst" "${PREFERRED[@]}" ; then
              CURRENT="$inst"
              break
            fi
          done
          #
          # Relocate
          #
          if [[ -n "$CURRENT" ]]; then
            echo " relocate $CURRENT -> $INSTANCE" >> "$LOGFILE"
            $ORACLE_HOME/bin/srvctl relocate service -d "$DATABASE" -s "$service" -i "$CURRENT" -t "$INSTANCE" >> "$LOGFILE"
          fi
        else
          #
          # Service is already running on preferred instance, no need to do anything
          #
          echo " running on preferred $INSTANCE" >> "$LOGFILE"
        fi
      fi
    fi
  fi
done