この章は、次の項目で構成されています。
この項の内容は次のとおりです。
接続プーリングとは、ロードを均衡化するために、再使用可能な物理接続のグループ(プール)を複数のセッションで使用することです。プールは、アプリケーションではなく、OCIにより管理されます。Webアプリケーション・サーバーおよび電子メール・サーバーの中間層アプリケーションでも接続プーリングを使用できます。
この機能は、バックエンドのOracle Databaseに接続しているWebアプリケーション・サーバーで使用する方法があります。Webアプリケーション・サーバーで、データベース・サーバーから同時に複数のデータ要求を受けたとします。アプリケーションでは、アプリケーションの初期化を行うとき、各環境にプール(またはプール・セット)を作成できます。
Oracle Databaseは、データベース・セッションと接続のファイングレイン管理など、いくつかのトランザクション・モニター機能を備えています。データベース・セッションのファイングレイン管理は、接続(サーバー・ハンドル)からデータベース・セッション(ユーザー・ハンドル)の概念を切り離すことで実行されます。セッション切替えやセッション移行でOCIコールを使用すると、アプリケーション・サーバーまたはトランザクション・モニターでは、少数の物理接続で複数のセッションを多重化でき、したがって、接続およびバックエンドのOracleサーバー・プロセスをプールすることで、高度な拡張性が実現します。
接続プール自体は通常、物理接続の共有プールで構成され、同じ数の専用サーバー・プロセスを含むバックエンド・サーバー・プールに変換されます。
物理接続の数は、アプリケーションで使用するデータベース・セッションの数より少なくなります。物理接続とバックエンド・サーバー・プロセスの数も、接続プーリングを使用することで少なくなります。これによって、多重化できるデータベース・セッションの数が多くなります。
中間層での接続プーリングは、共有サーバーがバックエンドで提供する機能に似ています。セッション多重化ロジックを中間層で管理することで、接続プーリングは、専用サーバー・インスタンスを共有サーバー・インスタンスと同じように動作させます。
中間層の接続プールによって、専用サーバー・プロセスへの着信接続を含む専用サーバー・プロセスのプーリングが制御されます。接続プーリングと共有サーバーの主な相違点は、共有サーバーの場合、クライアントからの接続は、通常データベース・インスタンスのディスパッチャへの接続である点です。ディスパッチャには、クライアント要求を適切な共有サーバーに送信します。ただし、接続プールからの物理接続は、中間層からバックエンドのサーバー・プールにある専用サーバー・プロセスに直接接続されます。
接続プーリングは、中間層自体がマルチスレッドである場合にのみ有効です。各スレッドでは、データベースに対するセッションをメンテナンスできます。データベースへの実際の接続は接続プールでメンテナンスされ、その接続(専用データベース・サーバー・プロセスのプールを含む)は中間層のすべてのスレッドの間で共有されます。
ステートレス・セッションは、中間層スレッド間で逐次再利用可能です。ある3層ユーザーのためにスレッドでデータベース要求を処理した後で、異なる3層ユーザーの異なる要求のために同じデータベース・セッションを再利用できます。
ただし、データベースに対するステートフル・セッションは、特定の3層ユーザーに関連する特定の状態になっている場合があるため、中間層スレッド間で逐次再利用はできません。このような状態の例としては、オープン・トランザクション、文からのフェッチ状態、PL/SQLパッケージ状態などがあります。その状態が存在するかぎり、そのセッションは異なる要求には再利用できせん。
注意: ステートレス・セッションも、オープン・トランザクション、オープン文のフェッチ状態などになる可能性があります。ただし、このような状態が継続するのは比較的短期間(中間層スレッドによって特定の3層要求を処理する期間のみ)であるため、(これらの状態がクリーン・アップされると)そのセッションを異なる三層ユーザーのために逐次再利用できます。ステートレス・セッションは通常、文キャッシュとともに使用されます。 |
接続プーリングによって、ステートレス接続とステートフル・セッションが提供されます。ステートレス・セッションを操作する必要がある場合は、「OCIでのセッション・プーリング」を参照してください。
複数の接続プールの拡張概念を、様々なデータベース接続に使用できます。複数の接続プールは、異なる優先度がユーザーに割り当てられている場合にも使用できます。接続プーリングを使用すると、異なるレベルのサービス保証を実装できます。
図9-1は、OCI接続プーリングを示しています。
接続プーリングでは、透過的アプリケーション・フェイルオーバー(TAF)が使用可能になっています。BACKUP
およびPRECONNECT
句が接続文字列では使用できず接続プーリングとTAFでは機能しない点を除いて、TAFの概念は、接続プール内の各接続に等しく適用されます。
接続プールの接続がフェイルオーバーする場合、主接続文字列自体を使用して接続します。セッションは、そのインスタンスに障害が発生した後に、プールを使用してデータベースとのラウンドトリップを行うときにフェイルオーバーします。リスナーは、サービスベースの接続文字列でよく行われるように、存在する正常なインスタンスに接続をルーティングするよう構成されます。
関連項目: 『Oracle Database Net Services管理者ガイド』の透過的アプリケーション・フェイルオーバーの構成に関する章 |
アプリケーションで接続プーリングを使用するには、次のことが必要です。
接続プーリングでは、OCIHandleAlloc()によってプール・ハンドルOCI_HTYPE_CPOOL
を割り当てる必要があります。指定された環境ハンドルに対して複数のプールを作成できます。
単一の接続プーリングの割当て例を次に示します。
OCICPool *poolhp; OCIHandleAlloc((void *) envhp, (void **) &poolhp, OCI_HTYPE_CPOOL, (size_t) 0, (void **) 0));
OCIConnectionPoolCreate()関数は、接続プール・ハンドルを初期化します。次のIN
パラメータがあります。
connMin
- プールの作成時にオープンする最小接続数です。
connIncr
- すべての接続がビジーのときにコールで接続が必要な場合に、オープンする接続の増分数です。この増分は、オープンしている接続の合計数がそのプールでオープン可能な最大接続数より少ない場合にのみ使用します。
connMax
- プールでオープンできる最大接続数です。プールで最大数の接続がオープンするとすべての接続がビジーになるため、コールに接続が必要な場合は、取得するまで待機することになります。ただし、プールにOCI_ATTR_CONN_NOWAIT
属性が設定されている場合は、エラーが戻されます。
poolUsername
およびpoolPassword
- ユーザー・セッションがプールの接続間で透過的に移行できます。
さらに、OCI_ATTR_CONN_TIMEOUT属性によって、プールの接続のタイムアウトを設定できます。この時間を超えてアイドル状態の接続は定期的に終了し、オープン接続が最適な数に維持されます。この属性が設定されていない場合は、接続がタイムアウトになることはありません。
注意: プールの縮小は、ネットワーク・ラウンドトリップがある場合にのみ発生します。操作がない場合、接続は継続されます。 |
前述の属性はすべて動的に構成できるため、アプリケーションでは現在のロード(オープン接続数とビジー接続数)を読み取り、これらの属性を適切に調整できます。
プール属性(connMax
、connMin
、connIncr
)を動的に変更する場合は、mode
パラメータにOCI_CPOOL_REINITIALIZE
を設定したOCIConnectionPoolCreate()をコールする必要があります。
OUTパラメータpoolName
とpoolNameLen
には、データベース名とその長さの引数のかわりに、後続のOCIServerAttach()とOCILogon2()コールで使用する値が格納されます。
アプリケーションで作成できるプールの数に制限はありません。中間層アプリケーションでは、複数のプールを作成して同一サーバーまたは異なるサーバーに接続し、アプリケーション固有のニーズに基づいてロードを均衡化できます。
このコールのコード例を次に示します。
OCIConnectionPoolCreate((OCIEnv *)envhp, (OCIError *)errhp, (OCICPool *)poolhp, &poolName, &poolNameLen, (text *)database,strlen(database), (ub4) connMin, (ub4) connMax, (ub4) connIncr, (text *)poolUsername,strlen(poolUserLen), (text *)poolPassword,strlen(poolPassLen), OCI_DEFAULT));
アプリケーションでは、次のいずれかのインタフェースを使用して、スレッドごとにデータベースにログインする必要があります。
最も単純なインタフェースです。このインタフェースは、単純な接続プール接続が必要で、セッション・ハンドルの属性を変更する必要がない場合に使用します。このインタフェースは、データベースへのプロキシ接続を行う場合にも使用できます。
次に、OCILogon2()
の使用例を示します。
for (i = 0; i < MAXTHREADS; ++i) { OCILogon2(envhp, errhp, &svchp[i], "hr", 2, "hr", 2, poolName, poolNameLen, OCI_LOGON2_CPOOL)); }
このインタフェースを使用してプロキシ接続を行うには、パスワード・パラメータをNULL
に設定してください。
このインタフェースの使用をお薦めします。このインタフェースを使用すると、ユーザーは、証明書、識別名などの外部認証方式も使用できます。OCISessionGet()
は、セッションを取得する場合の推奨する統一関数コールです。
次に、OCISessionGet()
の使用例を示します。
for (i = 0; i < MAXTHREADS; ++i) { OCISessionGet(envhp, errhp, &svchp, authp, (OraText *) poolName, strlen(poolName), NULL, 0, NULL, NULL, NULL, OCI_SESSGET_CPOOL) }
OCIServerAttach()およびOCISessionBegin()
アプリケーションで、ユーザー・セッション・ハンドルおよびサーバー・ハンドルに特別な属性を設定する必要がある場合は、別のインタフェースを使用できます。このような要件に対して、アプリケーションでは、すべてのハンドル(接続プール・ハンドル、サーバー・ハンドル、セッション・ハンドルおよびサービス・コンテキスト・ハンドル)を割り当てる必要があります。次の順序に従います。
接続プールを作成します。
接続プーリングでは、物理接続で仮想サーバー・ハンドルが透過的に多重化されるため、ユーザーがこの作業を行う必要はありません。ユーザーには、セッション用に専用(仮想)接続が確立されているように感じられます。多重化はユーザーに対して透過的に行われるため、仮想サーバー・ハンドル自体に対してセッションを多重化しないでください。ユーザー・レベルで明示的な多重化を必要とするセッション移行およびセッション切替えの概念は、接続プーリングに関しては廃止されたため、使用しないでください。
モードをOCI_CPOOL
に設定してOCIServerAttach()をコールします。
OCIプログラムでは、ユーザーは、接続プールを使用して作成されるセッションごとに一意の仮想サーバー・ハンドルを作成(モードをOCI_CPOOL
に設定してOCIServerAttach()
をコール)する必要があります。仮想サーバー・ハンドルとセッションは、1対1対応である必要があります。
モードをOCI_DEFAULT
に設定してOCISessionBegin()をコールします。
資格証明は、OCISessionBegin()
を使用してOCI_CRED_RDBMS
、OCI_CRED_EXT
またはOCI_CRED_PROXY
に設定できます。資格証明をOCI_CRED_EXT
に設定すると、セッション・ハンドルの設定ではユーザー名もパスワードも必要ありません。資格証明をOCI_CRED_PROXY
に設定すると、セッション・ハンドルの設定ではユーザー名のみ必要です (1次セッションを明示的に作成したり、OCI_ATTR_MIGSESSION
を設定する必要はありません)。
仮想サーバー・ハンドルに接続プールを指定する(mode
をOCI_CPOOL
に設定してOCIServerAttach()をコールする)場合は、OCISessionBegin()へのコールでOCI_MIGRATE
フラグを設定しないでください。互換性上の理由でのみ、このOCI_MIGRATE
フラグを渡すことをお薦めします。接続プールを使用するときに、ユーザーには、セッションに独自の専用(仮想)接続が確立されているように感じられますが、この接続は実際の接続に対して透過的に多重化されているだけのため、OCI_MIGRATE
フラグは使用しないでください。
OCI_CPOOL
モード(接続プーリング)では、バックエンド・データベースのセッション・メモリー(UGA)はSGAから取得されます。SGAで格納できるセッション・メモリー以上のメモリー量をアプリケーションで使用する場合は、バックエンド・データベースでSGAをチューニングしてSGAを拡張する必要性が生じる場合があります。バックエンド・データベースに対するメモリー・チューニングの要件は、インスタンスが専用モードである点を除けば、共有サーバーがバックエンドの場合にLARGE POOLを構成する動作に似ています。
関連項目: 『Oracle Databaseパフォーマンス・チューニング・ガイド』の共有サーバーの構成に関する項 |
さらにSGA制限が発生する場合は、次の点を考慮してください。
セッションごとにオープンする文を少なくして、セッション・メモリーの使用量を減らします。
中間層にセッションをプールすることで、バックエンドのセッション数を減らします。
それ以外の場合は、接続プーリングをオフにします。
アプリケーションでは、バックエンドで専用データベース・リンクを接続プーリングとともに使用しないでください。
バックエンドが専用サーバーの場合、専用データベース・リンクを使用するセッションは物理接続に連結され、同じ接続が他のセッションでは使用できなくなるため、効率的な接続プーリングは実現しません。アプリケーションで専用データベース・リンクを使用し、セッション間でバックエンド処理を効率的に共有できない場合は、共有データベース・リンクの使用を検討してください。
関連項目: 分散データベースの詳細は、『Oracle Database管理者ガイド』の共有データベース・リンクに関する項を参照してください。 |
次のコールから、ログイン・コールに対応するものを選択し、それを使用して接続プーリング・モードでデータベースからログオフします。
OCILogon2()を使用して接続を行った場合は、OCILogoff()
を使用してログオフする必要があります。
OCISessionGet()をコールして接続を行った場合は、OCISessionRelease()
をコールしてログオフする必要があります。
OCISessionEnd()およびOCIServerDetach()
OCIServerAttach()およびOCISessionBegin()をコールして接続を行いセッションを開始した場合は、OCISessionEnd()
をコールしてセッションを終了し、OCIServerDetach()
をコールして接続を解放する必要があります。
OCIConnectionPoolDestroy()を使用して、接続プールを破棄します。
プール・ハンドルは、OCIHandleFree()を使用して解放します。
次のコード・フラグメントは、前述した3つの処理を示しています。
for (i = 0; i < MAXTHREADS; ++i) { checkerr(errhp, OCILogoff((void *) svchp[i], errhp)); } checkerr(errhp, OCIConnectionPoolDestroy(poolhp, errhp, OCI_DEFAULT)); checkerr(errhp, OCIHandleFree((void *)poolhp, OCI_HTYPE_CPOOL));
この項の内容は次のとおりです。
セッション・プーリングとは、アプリケーションがデータベースに対するステートレス・セッションのグループを作成してメンテナンスすることを意味します。これらのセッションは、要求に応じてThinクライアントに提供されます。使用可能なセッションがない場合は、新しいセッションが作成されます。セッションでのクライアントの処理が完了すると、クライアントはプールに対してセッションを解放します。したがって、プール内のセッション数は動的に増加する場合があります。
プール内の一部のセッションに、特定のプロパティを使用してタグを付けることができます。たとえば、ユーザーは、デフォルト・セッションを要求して特定の属性を設定し、そのセッションにラベルまたはタグを付けて、プールに戻すことができます。そのユーザーまたは他のユーザーは、同じ属性を持つセッションを要求できるため、同じタグが付いたセッションを要求できます。プール内に、同じタグが付いた複数のセッションが存在する場合があります。セッションのタグは、変更またはリセットできます。
OCIでのセッション・プーリングを使用して、プロキシ・セッションを作成してメンテナンスすることもできます。
使用可能なセッションがなく、プールの最大サイズに達した場合のアプリケーションの動作は、指定した属性によって決まります。新しいセッションが作成されるか、エラーが戻されるか、またはスレッドがブロックしてセッションが使用可能になるまで待機する場合があります。
セッション・プーリングの主な利点は、パフォーマンスです。データベースへの接続(特にデータベースがリモートの場合)は、時間がかかるアクティビティです。したがって、クライアントでは、時間をかけてサーバーに接続し、資格証明を認証して有効なセッションを受け取るかわりに、プールからセッションを取り出すことができます。
セッション・プーリングにより次のタスクを実行できます。
ステートレス・セッションのプールを透過的に作成、メンテナンスおよび管理します。
アプリケーションでプールを作成し、プール内のセッションの最小数、増分数および最大数を指定するインタフェースを提供します。
ユーザーがデフォルト・セッションまたはタグ付けされたセッションをプールとの間で取得および解放するためのインタフェースを提供します。タグ付けされたセッションとは、クライアント定義の特定のプロパティが指定されたセッションです。
アプリケーションで、セッションの最小数と最大数を動的に変更できます。
長時間アイドル状態のセッションをクローズし、必要なときにセッションを作成することによって、常に最適な数のセッションがオープンしているようにメンテナンスするメカニズムを提供します。
セッション・プーリングで認証を行うことができます。
セッション・プールは、同種または異種のいずれかになります。同種セッション・プーリングとは、プール内のセッションの認証情報が同じ(同じユーザー名、パスワードおよび権限)であることを意味します。異種セッション・プーリングとは、セキュリティ属性と権限がセッションごとに異なる場合があるため、認証情報を指定する必要があることを意味します。
ユーザーは、タグを使用して、プール内のセッションをカスタマイズできます。クライアントは、デフォルト・セッションまたはタグが付いていないセッションをプールから取得して、特定の属性(NLS設定など)をセッションに設定し、OCISessionRelease()コールでセッションに適切なタグを付けて、プールに戻すことができます。
そのユーザーまたは他のユーザーは、同じ属性を持つセッションを使用するために、同じタグが付いたセッションを要求でき、これはOCISessionGet()コールで同じタグを指定することでできます。
セッション・プーリング用のハンドル ・タイプは次のとおりです。
セッション・プール・ハンドルです。これは、OCIHandleAlloc()を使用して割り当てます。OCISessionPoolCreate()およびOCISessionPoolDestroy()に渡す必要があります。属性の型はOCI_HTYPE_SPOOL
です。
OCIHandleAlloc()
コールの例を次に示します。
OCISPool *spoolhp; OCIHandleAlloc((void *) envhp, (void **) &spoolhp, OCI_HTYPE_SPOOL, (size_t) 0, (void **) 0));
環境ハンドルに対して、複数のセッション・プールを作成できます。
認証情報ハンドルです。これは、OCIHandleAlloc()を使用して割り当てます。これは、OCISessionGet()に渡されます。このハンドルでは、ユーザー・セッション・ハンドルでサポートされるすべての属性をサポートします。詳細は、「ユーザー・セッション・ハンドル属性」を参照してください。認証情報ハンドルの属性の型はOCI_HTYPE_AUTHINFO
です(表2-1を参照)。
OCIHandleAlloc()
コールの例を次に示します。
OCIAuthInfo *authp; OCIHandleAlloc((void *) envhp, (void **) &authp, OCI_HTYPE_AUTHINFO, (size_t) 0, (void **) 0));
関連項目:
|
ユーザー名とパスワードを使用する単純なセッション・プーリング・アプリケーションの作成手順は、次のとおりです。
OCISPool
ハンドルに対してOCIHandleAlloc()
を使用して、セッション・プール・ハンドルを割り当てます。環境ハンドルに対して複数のセッション・プールを作成できます。
mode
をOCI_DEFAULT
(新規セッション・プールの場合)に設定したOCISessionPoolCreate()を使用して、セッション・プールを作成します。その他のモードについては、関数の説明を参照してください。
スレッドごとにループを行います。次の処理を実行する関数を使用してスレッドを作成します。
OCIHandleAlloc()を使用して、OCIAuthInfo
型の認証情報ハンドルを割り当てます。
OCIAttrSet()を使用して、認証情報ハンドルにユーザー名とパスワードを設定します。
mode
をOCI_SESSGET_SPOOL
に設定したOCISessionGet()を使用して、プールされたセッションを取得します。
トランザクションを実行します。
ハンドルを割り当てます。
文を準備します。
注意: OCIセッション・プールから取得したサービス・コンテキストを使用する場合、OCISessionGet() (またはOCILogon2())によって戻されたサービス・コンテキストを使用する必要があり、これらのコールの外で他のサービス・コンテキストを作成する必要はありません。サービス・コンテキストを指定したOCIStmtPrepare2()を使用して取得した文ハンドルは、後で同じサービス・コンテキストとの組合せでのみ使用でき、異なるサービス・コンテキストとは使用できません。 |
文を実行します。
トランザクションをコミットまたはロールバックします。
OCISessionRelease()を使用して、セッションを解放(ログオフ)します。
OCIHandleFree()を使用して、認証情報ハンドルを解放します。
各スレッドのループを終了します。
OCISessionPoolDestroy()を使用して、セッション・プールを破棄します。
ここでは、セッション・プーリングのOCIコールの使用方法について説明します。OCIには、次のタスクを実行するためにセッション・プーリングのコールが用意されています。
セッション・プーリングでは、OCIHandleAlloc()をコールしてプール・ハンドルOCI_HTYPE_SPOOL
を割り当てる必要があります。
指定された環境ハンドルに対して複数のプールを作成できます。単一のセッション・プーリングの割当て例を次に示します。
OCISPool *poolhp; OCIHandleAlloc((void *) envhp, (void **) &poolhp, OCI_HTYPE_SPOOL, (size_t) 0, (void **) 0));
OCISessionPoolCreate()関数を使用して、セッション・プールを作成できます。このコールの使用例を次に示します。
OCISessionPoolCreate(envhp, errhp, poolhp, (OraText **)&poolName, (ub4 *)&poolNameLen, database, (ub4)strlen((const signed char *)database), sessMin, sessMax, sessIncr, (OraText *)appusername, (ub4)strlen((const signed char *)appusername), (OraText *)apppassword, (ub4)strlen((const signed char *)apppassword), OCI_DEFAULT);
次のコールを使用して、セッション・プーリング・モードでデータベースにログインできます。
最も単純なコールです。ただし、ユーザーは、セッションのタグ付けを行うことができません。セッション・プーリング・モードでOCILogon2()
を使用してデータベースにログインするコード例を、次に示します。
for (i = 0; i < MAXTHREADS; ++i) { OCILogon2(envhp, errhp, &svchp[i], "hr", 2, "hr", 2, poolName, poolNameLen, OCI_LOGON2_SPOOL)); }
これは、使用するのが推奨されるコールです。これにより、ユーザーは、プール内のセッションにラベル付け(タグ付け)ができるため、特定のセッションを容易に取得できます。次に、OCISessionGet()
の使用例を示します。これは、demo
ディレクトリのcdemosp.c
から取り出しました。
OCISessionGet(envhp, errhp, &svchp, authInfop, (OraText *)database,strlen(database), tag, strlen(tag), &retTag, &retTagLen, &found, OCI_SESSGET_SPOOL);
OCIセッション・プールから取得したサービス・コンテキストを使用する場合、OCISessionGet() (またはOCILogon2())によって戻されたサービス・コンテキストを使用する必要があり、これらのコールの外で他のサービス・コンテキストを作成する必要はありません。
サービス・コンテキストを指定したOCIStmtPrepare2()を使用して取得した文ハンドルは、後で同じサービス・コンテキストとの組合せでのみ使用でき、異なるサービス・コンテキストとは使用できません。
次のコールから、ログイン・コールに対応するものを選択し、それを使用してセッション・プーリング・モードでデータベースからログオフします。
OCILogon2()を使用して接続を行った場合は、OCILogoff()
をコールしてログオフする必要があります。
OCISessionGet()を使用して接続を行った場合は、OCISessionRelease()
をコールしてログオフする必要があります。保留状態のトランザクションは自動的にコミットされます。
セッション・プールを破棄するには、次の例に示すように、OCISessionPoolDestroy()をコールします。
OCISessionPoolDestroy(poolhp, errhp, OCI_DEFAULT);
セッション・プールを解放するには、次の例に示すように、OCIHandleFree()をコールします。
OCIHandleFree((void *)poolhp, OCI_HTYPE_SPOOL);
注意: 開発者: 接続をプールに解放する前に、オープン・トランザクションをコミットまたはロールバックしてください。そうしない場合、接続が解放されるときに、Oracle Databaseでは自動的にオープン・トランザクションがコミットされます。セッション・プールの使用中にインスタンス障害が検出されると、OCIではそのインスタンスへのセッションのクリーン・アップを試みます。 |
Oracle Real Application Clusters (Oracle RAC)は、単一データベースを複数ノードにある複数インスタンスで管理するデータベース・オプションです。Oracle RAC共有ディスクのデータベース・クラスタリングの方法により、拡張性が向上します。ノードは、現在の必要を満たすために、簡単に追加または解放でき、1つのノードに障害が発生した場合でも、他のノードでそのワークロードが引き継がれるため、可用性が向上します。Oracle RACにより、すべてのインスタンスがデータベース全体にアクセスできるため、データベースに高可用性機能とフェイルオーバー機能が加わります。
作業リクエストのバランシングは、接続時に1回と実行時に1回発生します。これらを、接続時ロード・バランシング(Oracle Net Servicesで提供)および実行時接続ロード・バランシングと呼びます。Oracle RAC環境の場合、セッション・プールでは、高速アプリケーション通知(FAN)イベントによりOracle RACロード・バランシング・アドバイザから受け取ったサービス・メトリックを使用して、アプリケーション・セッション要求を均衡化します。セッション・プールに入る作業リクエストは、現行のサービス・パフォーマンスを使用して、サービスを提供するOracle RACのインスタンス間で分散できます。
接続時ロード・バランシングは、アプリケーションによりセッションが最初に作成される時点で発生します。プールの一部であるセッションも、その最初の作成時にOracle RACインスタンス間で適切に分散される必要があります。これにより、各インスタンスでのセッションが作業を実行するチャンスを取得することになります。
ランタイム接続ロード・バランシングは、セッション・プール内で作業の処理に最も適切なセッションに作業リクエストをルーティングします。これは既存のセッション・プールからセッションを選択する際に発生するため、頻度の高いアクティビティです。単一インスタンスでのみサービスをサポートするセッション・プールの場合は、プール内で使用可能な最初のセッションで十分です。プールが複数インスタンスにまたがるサービスをサポートしている場合は、適切なサービスを提供できる、または容量の大きいインスタンスが多数のリクエストを取得するように、作業リクエストをインスタンス間で分散する必要があります。
実行時接続ロード・バランシングは、Oracle Databaseリリース10.2以上のサーバーと通信するOracle Databaseリリース11.1以上のクライアントでは、デフォルトで有効です。OCISessionPoolCreate()のコール時にmode
パラメータをOCI_SPC_NO_RLB
に設定すると、実行時接続ロード・バランシングが無効になります。
サービス時間に基づいてサービス・メトリックを受信するための要件は、次のとおりです。
Oracleクラスタウェアを伴うOracle RAC環境が設定され、有効化されていること。
アプリケーションがスレッド・ライブラリとリンクされていること。
OCI環境がOCI_EVENTS
およびOCI_THREADED
モードで作成されていること。
サービスが変更され、サービスの目的および接続ロード・バランシングの目的が次のように設定されていること。
EXEC DBMS_SERVICE.MODIFY_SERVICE("myService", DBMS_SERVICE.GOAL_SERVICE_TIME, clb_goal => dbms_service.clb_goal_short);
関連項目:
|
この項の内容は次のとおりです。
データベース常駐接続プーリング(DRCP)は、アプリケーションがデータベース接続を取得し、この接続で比較的短い時間動作してから接続を解放する一般的なWebアプリケーションの使用シナリオに対し、データベース・サーバーの接続プールを提供します。DRCPプール・サーバーのプロセスは、それぞれが専用サーバー・プロセスとデータベース・セッションを組み合せたものと同等であり、これをプール・サーバーと呼びます。プール・サーバーは、同じホストまたは複数のホストで実行されている複数のアプリケーション間で共有が可能です。接続ブローカ・プロセスは、プール・サーバーをデータベース・インスタンスのレベルで管理します。DRCPはプログラム実行時に選択される構成可能な機能であり、従来型とDRCPベースの接続アーキテクチャを同時に使用できるようになります。
DRCPは、中間層接続プーリングを実行できないマルチプロセス・シングル・スレッド・アプリケーション・サーバー(PHPやApacheなど)を含むアーキテクチャに特に関係しています。DRCPは、数百から数千単位のWebサーバーまたは中間層がデータベース・アクセスとクライアント側プールを必要とする大規模なWebデプロイメントでもきわめて効果的です(Javaのようなマルチスレッド・システムと言語でも)。データベースは、DRCPを使用して何万という同時接続に合せて拡張できます。多数の接続に伴ってデータベースWebアプリケーションのスケーリングが必要な場合、接続プーリング・ソリューションとしてDRCPが適しています。
DRCPは、中間層プロセスのスレッド間で接続を共有する中間層接続プールを補充します。また、DRCPを使用すると、同じ中間層ホスト上の中間層プロセス間でも、複数の中間層ホスト間でも、あるいは複数の中間層(Webサーバー、コンテナ)間でもデータベース接続を共有でき、異なる言語で書かれたアプリケーションにも対応できます。これにより、多数のクライアント接続をサポートするために必要となる主要なデータベース・リソースが大幅に削減されるため、データベース層のメモリー・フットプリントが削減され、中間層およびデータベース層の両方の拡張性が向上します。すぐに使用可能なサーバーのプールがあれば、クライアント接続の作成および分割にかかるコストを削減できるというメリットもあります。
クライアントはデータベース常駐接続プールから接続を取得し、接続ブローカと呼ばれるOracle Databaseバックグラウンド・プロセスに接続します。接続ブローカはプール機能を実装し、クライアントからの永続インバウンド接続間でプール・サーバーを多重化します
クライアントでデータベース・アクセスが必要な場合、接続プローカーではプールからサーバー・プロセスを取得して、クライアントに渡します。この結果、クライアントは要求が処理されるまで、そのサーバー・プロセスに直接接続されます。サーバーが終了すると、サーバー・プロセスは解放されてプールに戻され、クライアントからの接続は、接続ブローカに対してクライアント・プロセスからの永続インバウンド接続としてリストアされます。DRCPでは、リソースの解放によりセッションはそのままの状態で残りますが、接続(サーバー・プロセス)との関連付けはなくなります。このセッションは、システム・グローバル領域(SGA)ではなくプログラム・グローバル領域(PGA)にユーザー・グローバル領域(UGA)を格納するため、クライアントはアクティビティの検出時に透過的に接続を再確立できます。
DRCPは通常、接続数の多いアプリケーションで多用されます。共有サーバーは接続数が中程度の場合に効果的であり、専用セッションは接続数が少ない場合に適しています。しきい値サイズは、データベース・ホストで使用できるメモリー量と相対しています。
DRCPには、次の利点があります。
複数のクライアント・アプリケーション間と、中間層アプリケーション・サーバー間でリソース共有が可能になります。
データベース・ホストでリソース使用量が減少するため、データベースとアプリケーションのスケーラビリティが向上します。
共有サーバーと比較した場合、DRCPには次のような利点もあります。
DRCPは、クライアント側接続プーリングによって用意されるデータベース・サーバーに直接結び付けられます(つまり、クライアント側接続プーリングのような仲介を必要とせず、共有サーバーとも異なる)
DRCPは、データベース・サーバーをプールできます(クライアント側接続プーリングおよび共有サーバーと同様)
DRCPは、セッションをプールできます(クライアント側接続プーリングと同様で、共有サーバーとは異なる)
DRCPは、中間層の境界間で接続を共有できます(クライアント側接続プーリングとは異なる)
DRCPは、データベース・リソースの使用率を最小限に抑えたまま、多数の接続が必要となる環境でのスケーラビリティ要件に対応できる、独自の接続プーリング・ソリューションです。
関連項目:
|
プールは、DBMS_CONNECTION_POOL
パッケージを使用してDBAによって管理されます。プールはデフォルトでインストールされていますが、停止の状態です。DBAがプールを起動して、DRCP構成オプションを指定する必要があります。オプションには、プールで許可されるプール・サーバーの最小数と最大数、作成する接続ブローカの数、各接続ブローカが処理する接続の最大数などがあります。詳細は、リファレンスを参照してください。
OCIセッション・プールAPIは、データベース常駐接続プールと相互運用するために拡張されています。詳細は、リファレンスを参照してください。
関連項目:
|
この後の各項では、データベース常駐接続プールと相互運用するために拡張されたOCIセッション・プールAPIについて説明します。OCIアプリケーションでは通常、DRCP用のOCIセッション・プールの環境を、データベース接続文字列(connStr
)、ユーザー名(userid
)とパスワード(password
)が各セッションに関連付けられているかどうか、プール内のすべてのセッションが渡されたユーザー名およびパスワードで認証されるように、mode
パラメータがOCI_SPC_HOMOGENEOUS
として指定されている場合に、開始される最小セッション数(sessMin
)と次の増分セッション数(sessIncr
)、セッション・プールで許可される最大セッション数(sessMax
)などを指定することで、OCISessionPoolCreate()を使用して、DRCP用のOCIセッション・プールの環境を初期化します。
セッションはDRCPから取得され、mode
パラメータでOCI_SESSGET_SPOOL
属性を指定したOCISessionGet()によりOCIセッション・プールから取得され、OCISessionRelease()を使用してOCIセッション・プールに解放されたセッションは、DRCPに戻されます。OCIセッション・プールは、キャッシュされた接続ブローカに対する接続を透過的に維持することもでき、パフォーマンスが向上します。OCIアプリケーションでは、OCISessionGet() (authInfop
)を使用して、OCI_ATTR_CONNECTION_CLASS
属性を設定し、接続クラス名を指定するか、OCISessionGet()のコール前にOCIAuthInfo
ハンドルを使用することで、アプリケーションが同様の状態のセッションを残しているセッションを再利用できます。OCISessionGet() (mode
)を使用すれば、OCIアプリケーションでは、セッションの純正値、つまり、プールされたセッションを再利用する(OCI_SESSGET_PURITY_SELF
属性を設定)か、新規のセッションを使用する(OCI_SESSGET_PURITY_NEW
属性を設定)かも指定できます。
また、タグ付け、文キャッシュおよび透過的アプリケーション・フェイルオーバー(TAF)など、従来のクライアント側OCIセッション・プールにより提供される機能は、DRCPでもサポートされています。
Oracle Databaseリリース11.1では、OCIでOCISessionGet()を使用してセッションを取得する際に指定できる2つの設定が導入されました。
セッション純正値は、アプリケーション・ロジックをプール・セッションを再利用するように設定するか、または新規セッションを使用するように設定するかを指定します。OCISessionGet()は、OCI_SESSGET_PURITY_NEW
またはOCI_SESSGET_PURITY_SELF
の純正値設定を取り入れるよう拡張されています。または、OCISessionGet()
をコールする前に、OCIAuthInfo
ハンドルでOCI_ATTR_PURITY_NEW
またはOCI_ATTR_PURITY_SELF
を設定できます。どひらのメソッドも同じです。
注意: プールからセッションを再利用する場合、サーバーのNLS属性がクライアントのNLS属性よりも優先されます。たとえば、クライアントで 接続クラスを使用すれば、共有を制限し、この問題を回避できます。 |
例9-1では、接続プーリング・アプリケーションでのNEW
セッションの設定方法を示しています。
例9-1 セッション純正値の設定
/* OCIAttrSet method */ ub4 purity = OCI_ATTR_PURITY_NEW; OCIAttrSet (authInfop, OCI_HTYPE_AUTHINFO, &purity, sizeof (purity), OCI_ATTR_PURITY, errhp); OCISessionGet (envhp, errhp, &svchp, authInfop, poolName, poolNameLen, NULL, 0, NULL, NULL, NULL, OCI_SESSGET_SPOOL); /* poolName is the name returned by OCISessionPoolCreate() */ /* OCISessionGet mode method */ OCISessionGet (envhp, errhp, &svchp, authInfop, poolName, poolNameLen, NULL, 0, NULL, NULL, NULL, OCI_SESSGET_SPOOL | OCI_SESSGET_PURITY_NEW); /* poolName is the name returned by OCISessionPoolCreate() */
接続クラスには、アプリケーションで必要な接続のタイプの論理名を定義します。OCIセッション・プールのセッションは、複数のユーザーで共有することはできません(ユーザーHR
に対して最初に作成されたセッションは、ユーザーHR
による後続の要求にのみ割り当てられます)。接続クラスの設定により、所定のユーザーのセッションをさらに分離できます。接続クラスの設定では、同一のデータベース・ユーザーとして接続している複数のアプリケーションにおいて、アプリケーションに対応する論理名を使用してセッションを識別できます。その結果OCIでは、特定の接続クラスに属するセッションが接続クラス外で共有されなくなります。
OCIAuthInfo
ハンドルでOCI_ATTR_CONNECTION_CLASS
属性を使用すれば、接続クラスを設定できます。接続クラスは文字列属性です。OCIでサポートされる接続クラスの最大長は1024バイトです。アスタリスク(*)は特殊文字であり、接続クラス名には使用できません。
例9-2は、HRMSアプリケーションでは接続クラスHRMS
で識別されるセッションが必要であることを示しています。
例9-2 接続クラスをHRMSとして設定
OCISessionPoolCreate (envhp, errhp, spoolhp, &poolName, &poolNameLen, "HRDB", strlen("HRDB"), 0, 10, 1, "HR", strlen("HR"), "HR", strlen("HR"), OCI_SPC_HOMOGENEOUS); OCIAttrSet (authInfop, OCI_HTYPE_AUTHINFO, "HRMS", strlen ("HRMS"), OCI_ATTR_CONNECTION_CLASS, errhp); OCISessionGet (envhp, errhp, &svchp, authInfop, poolName, poolNameLen, NULL, 0, NULL, NULL, NULL, OCI_SESSGET_SPOOL);
例9-3は、採用アプリケーションでは接続クラスRECMS
で識別されるセッションが必要であることを示しています。
例9-3 接続クラスをRECMSとして設定
OCISessionPoolCreate (envhp, errhp, spoolhp, &poolName, &poolNameLen, "HRDB", strlen("HRDB"), 0, 10, 1, "HR", strlen("HR"), "HR", strlen("HR"), OCI_SPC_HOMOGENEOUS); OCIAttrSet (authInfop, OCI_HTYPE_AUTHINFO, "RECMS", strlen("RECMS"), OCI_ATTR_CONNECTION_CLASS, errhp); OCISessionGet (envhp, errhp, &svchp, authInfop, poolName, poolNameLen, NULL, 0, NULL, NULL, NULL, OCI_SESSGET_SPOOL);
表9-1では、様々なクライアント・シナリオで使用されるデフォルト値を示しています。
表9-1 各種クライアント・シナリオで使用されるデフォルト
属性または設定 | アプリケーションがセッション・プールからOCISessionGet()を使用 | OCIセッション・プールからのその他接続の取得なし |
---|---|---|
OCI_ATTR_PURITY |
OCI_ATTR_PURITY_SELF |
OCI_ATTR_PURITY_NEW |
OCI_ATTR_CONNECTION_CLASS |
OCIセッション・プールのすべての接続についてデフォルトの接続クラスとして使用されるクライアント側の各セッション・プールに対する、OCIで生成されたグローバル一意名。 |
SHARED |
セッションの共有 |
OCIセッション・プールにセッションを要求するスレッド間でのセッションの共有。 |
デフォルトのSHARED接続クラスを使用した特定のデータベースの全接続間における共有 |
データベース管理者(DBA)はSYSDBA
としてログインし、DBMS_CONNECTION_POOL.START_POOL
をデフォルト設定で使用して、デフォルト・プールSYS_DEFAULT_CONNECTION_POOL
を開始する必要があります。
プールの構成の詳細は、『Oracle Database管理者ガイド』を参照してください。
簡易接続文字列で:POOLED
を指定する(例9-4を参照)か、TNS接続文字列で(SERVER=POOLED)
を指定する(例9-5を参照)することで、どのアプリケーションでもデータベース常駐接続プールの恩恵を受けられます。
次の3種類のアプリケーション・シナリオについて検討し、それぞれでDRCPからどのような利点があるかについて説明します。
OCIセッション・プーリングを使用せず、接続クラスや純正値設定も指定しない(または純正値にNEW
を設定)アプリケーションは、DRCPから新しいセッションを取得します。同様に、アプリケーションが接続を解放してプールに戻した場合、デフォルトによりこのセッションは同じアプリケーションの他のインスタンスと共有されません。SQL*Plusは、OCIセッション・プーリングを使用しないクライアントの一例です。これは接続がアイドルのときでも、接続を保持し続けます。そのため、クライアント・セッションが存在する場合やクライアント・セッションがログオフしていない場合には、プール・サーバーが割り当てられたままになります。ただし、アプリケーションは既存のプール・サーバー・プロセスを再利用できるというメリットが得られます。
OCIセッション・プール外でOCISessionGet()コールを使用するアプリケーション、または接続クラスを指定し、purity=SELF
を設定するためのアプリケーションは、DRCPプール・サーバー・プロセスおよびセッションの両方を再利用できます。ただし、OCISessionRelease()コールの後、OCIは接続ブローカへの接続を終了します。次のOCISessionGet()
コールで、アプリケーションはブローカに再接続します。再接続すると、DRCPは指定された接続クラスに属するプール・サーバー(およびセッション)を割り当てます。ただし、再接続では接続の確立および再認証のコストが発生します。こうしたアプリケーションはDRCPリソース(プロセスおよびセッション)をより効率的に共有できますが、接続ブローカへの接続をキャッシュするというメリットは得られません。
OCIセッション・プールAPIを使用し、接続クラスを指定し、purity=SELF
を設定するアプリケーションは、プール・サーバー・プロセスと関連付けられたセッションの両方を再利用することにより、DRCPの機能をフルに活用します。これらは、接続ブローカにキャッシュされた接続の恩恵を受けます。キャッシュされた接続では、OCISessionGet()
コールで再認証のコストが発生しません。
DRCPがもたらすメリットを享受できるアプリケーションを設計する手順は、OCIセッション・プーリングの使用およびセッション・プーリングのためのOCIコールに関する項に示す、OCIセッション・プールを使用するアプリケーションに必要な手順とよく似ています。
唯一の追加ステップとして、DRCPとともに動作するようデプロイされた場合、最大限のパフォーマンスを得るために、アプリケーションで明示的な接続クラス設定を指定する必要があります。
最大限のパフォーマンスを得て、さらにDRCPリソースの共有機能を強化するため、同じアプリケーションの複数のインスタンスは、同じ接続クラス設定を指定する必要があります。アプリケーションの複数のインスタンスでデータベース・セッションを共有できることを確認してください。
例9-6では、データベース常駐接続プーリングDRCPアプリケーションを示しています。
例9-6 データベース常駐接続プーリング・アプリケーション
/* Assume that all necessary handles are allocated. */ /* This middletier uses a single database user. Create a homogeneous client-side session pool */ OCISessionPoolCreate (envhp, errhp, spoolhp, &poolName, &poolNameLen, "BOOKSDB", strlen("BOOKSDB"), 0, 10, 1, "SCOTT", strlen("SCOTT"), "password", strlen("password"), OCI_SPC_HOMOGENEOUS); while (1) { /* Process a client request */ WaitForClientRequest(); /* Application function */ /* Set the Connection Class on the OCIAuthInfo handle that is passed as argument to OCISessionGet*/ OCIAttrSet (authInfop, OCI_HTYPE_AUTHINFO, "BOOKSTORE", strlen("BOOKSTORE"), OCI_ATTR_CONNECTION_CLASS, errhp); /* Purity need not be set, as default is OCI_ATTR_PURITY_SELF for OCISessionPool connections */ /* You can get a SCOTT session released by Mid-tier 2 */ OCISessionGet(envhp, errhp, &svchp, authInfop, poolName, poolNameLen, NULL, 0, NULL, NULL, NULL, OCI_SESSGET_SPOOL); /* Database calls using the svchp obtained above */ OCIStmtExecute(...) /* This releases the pooled server on the database for reuse */ OCISessionRelease (svchp, errhp, NULL, 0, OCI_DEFAULT); } /* Mid-tier is done - exiting */ OCISessionPoolDestroy (spoolhp, errhp, OCI_DEFAULT);
例9-7および例9-8では、例9-6のコードに基づく2つのデプロイメントの例を示しており、コードがBOOKSTOREアプリケーションにサービスを提供する10の中間層ホストにデプロイされます。
最初のデプロイメント例では、使用されるデータベースが、専用サーバー・モードでDRCPが有効になっていないOracle Database 11g以下であると想定しています。クライアント側には11gライブラリがあります。例9-7では、このデプロイメントに使用する接続文字列を示しています。この場合、アプリケーションはデータベースから専用サーバー接続を取得します。
例9-7 DRCPが無効である専用サーバー・モードのデプロイメントで使用する接続文字列
BOOKSDB = (DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=oraclehost.company.com) (PORT=1521))(CONNECT_DATA = (SERVICE_NAME=books.company.com)))
2番目のデプロイメント例では、Oracle Database 11gデータベースでDRCPが有効になっているものと想定します。これで、すべての中間層プロセスがDRCPで提供されるプーリング機能の恩恵を受けられます。DRCPのデータベース・リソース要件は、専用サーバー・モードでの要件より大幅に少なくなります。例9-8では、このタイプのデプロイメントで接続文字列を変更する方法を示しています。
Oracle Database 11gクライアント・ライブラリにリンクされたOCIアプリケーションは、次のそれぞれに対して変更なしで動作します。
DRCPが無効のOracle Database 11gデータベース
Oracle Database 11gより前のリリースのデータベース・サーバー
DRCP接続文字列でのデプロイ時に、DRCPが有効であるOracle Database 11gデータベース・サーバー
前述のとおり、OCIセッション・プールAPIを接続クラスおよび純正値設定とともに使用するようクライアントが適切に変更されていれば、そのクライアントはDRCPにより提供される高度な拡張性を利用できます。
プール・サーバーでは、次のアクションを実行または使用できません。
データベースの停止
データベース常駐接続プールの停止
接続されたユーザーのパスワードの変更
共有データベース・リンクを使用した、異なるインスタンスにあるデータベース常駐接続プールへの接続
TCPSによるアドバンスト・セキュリティ・オプション(ASO)の使用
OCI_MIGRATE
オプションを使用して直接、またはOCISessionPoolCreate()コールを使用して間接的に、サーバー側の移行可能なセッションを使用
初期クライアント・ロールの使用
OCI_ATTR_APPCTX_NAME
およびOCI_ATTR_APPCTX_VALUE
などのアプリケーション・コンテキスト属性の使用
プール内のDDL前のセッションはまだDDL後のクライアントに渡される可能性があるため、プール内のデータベース・ユーザーに関係するDDL文の実行には注意が必要です。たとえば、ユーザーを削除する場合、そのユーザーのセッションがプール内に存在せず、そのユーザーとして認証されたブローカへの接続がないことを確認します。
明示的なロールが使用可能であり、プールに解放されるセッションは、後でデフォルトのログイン・ロールを必要とする(同じユーザーの)接続に渡される可能性があります。明示的なロールのあるセッションは解放せずに、かわりにセッションを終了してください。
注意: 暗号化やDRCPを使用した厳密認証などのOracle Advanced Security機能を使用できます。 |
DRCPは、「データベース常駐接続プーリング」で説明されているように、OCIセッション・プーリングとうまく統合されます。すでにDRCP、FANおよびRLBと統合されているので、OCIセッション・プールを使用することを強くお薦めします。
ただし、アプリケーションが独自のカスタム接続プールを使用して作成されている場合(またはアプリケーションでプーリングがまったく使用されていないが、セッションが使用されない期間があり、アプリケーションが正確さのために特定のセッションを取り戻すことに依存しない場合)でも、DRCPと統合できます。これは、「ステートフルまたはステートレスとしてのセッションの明示的作成」で説明されているように、OCI_ATTR_SESSION_STATE
属性を有効にすることでできます。
アプリケーションでセッションにOCI_SESSION_STATELESS
であるというフラグが立てられると、OCIではセッションを透過的にDRCPプールに戻す(DRCPが有効な場合)ときに、このセッションの注釈を利用します。同様に、アプリケーションでセッションをOCI_SESSION_STATEFUL
であると示すと、OCIでは該当するセッションを透過的にDRCPプールからチェックアウトするときに、この変更されたセッション状態の注釈を利用します。
アプリケーションでは、セッション状態をできるだけ速やかにマークして、基本のデータベース・リソースの効率的な利用を可能にする必要があります。
注意: 前に詳細に述べたように、その他のDRCP属性(接続クラスや純正など)をさらに指定する必要があります。 |
アプリケーションには通常、1単位の作業の継続期間中、特定のデータベース・セッションが必要です。この期間中、セッションはSTATEFULであるといわれます。この作業単位の最後に、後続の作業単位のためにその特定のセッションを維持することにアプリケーションが依存しない場合、そのセッションはSTATELESSであるといわれます。
アプリケーションでは、セッションのSTATEFULからSTATELESSへまたはその逆への遷移を検出すると、これらの遷移についてOCI_ATTR_SESSION_STATE
を使用してOCIに明示的に通知できます。
アプリケーションまたはコール元によるこの指示により、OCIとOracle Databaseでは、この情報を利用して、特定のスケーラビリティの最適化を透過的に実行できます。たとえば、アプリケーションがそのセッションで動作していない場合、そのセッションを必要とする他の誰かに与えることができます。または、アプリケーションで再び必要となった場合は、別のセッションによって置き換えることもできます。
セッションの状態をマークする例を、次のコード・フラグメントで示します。
wait_for_transaction_request(); do { ub1 state; /* mark database session as STATEFUL */ state = OCI_SESSION_STATEFUL; checkerr(errhp, OCIAttrSet(usrhp, OCI_HTYPE_SESSION, &state, 0, OCI_ATTR_SESSION_STATE, errhp)); /* do database work consisting of one or more related calls to the database */ ... /* done with database work, mark session as stateless */ state = OCI_SESSION_STATELESS; checkerr(errhp, OCIAttrSet(usrhp, OCI_HTYPE_SESSION, &state, 0,OCI_ATTR_SESSION_STATE, errhp)); wait_for_transaction_request(); } while(not _done);
セッションがOCIセッション・プールの外から取得された場合、セッションはOCI_SESSION_STATEFUL
として開始され、アプリケーションにより明示的にOCI_SESSION_STATELESS
へ変更されないかぎり、そのセッションの存続期間中OCI_SESSION_STATEFUL
のままです。
セッションがOCIセッション・プールから取得された場合、プールからの取得後にそのセッションで最初のコールが開始されたときに、セッションはデフォルトでOCI_SESSION_STATEFUL
としてマークされます。セッションは、やはりデフォルトで、プールに対して解放されるときにOCI_SESSION_STATELESS
としてマークされます。したがって、OCIセッション・プールでこの属性を明示的に設定する必要はありません。OCIセッション・プーリングでは、これを透過的に行います。この属性を使用するのは、OCIセッション・プーリングを使用していない場合にかぎります。
Real Application Clusters環境のデータベースでデータベース常駐接続プールが構成されているとき、プールの構成は各データベース・インスタンスに適用されます。1つのインスタンスでプールを起動または停止すると、すべてのインスタンスのプールが起動または停止されます。
データベース常駐接続プールがData Guard環境で作動するためには特定の条件があります。
プールの起動: プールが物理スタンバイ・データベース上で起動できるのは、プールがプライマリ・データベースですでに起動されている場合のみです。プールがプライマリで停止している場合は、スタンバイ・データベースで起動できません。
プールの停止: プールがプライマリ・データベース上で稼働中の場合、物理スタンバイ・データベース上で停止することはできません。プールを停止できるのは、プライマリ・データベースでプールが実行していない場合のみです。
物理スタンバイ・データベース上では、プール・パラメータの構成、デフォルトへの復元または変更を行うことはできません。
役割の交換が発生する、つまりプライマリが停止して、セカンダリ・データベースがプライマリ・データベースの役割を担当すると、物理スタンバイ・データベースについてこれまでに説明した制限事項はなくなります。スタンバイ・データベースがプライマリになったため、すべてのプール操作を実行できます。
ロジカル・スタンバイ・データベースでは、すべてのプール操作を行うことができます。
この項の内容は次のとおりです。
データベース・セッションを中間層スレッドで再利用できず(つまり、ステートフルのとき)、バックエンドのサーバー・プロセスの数によってはデータベースでスケール上の問題が発生する可能性がある場合は、OCI接続プーリングを使用します。
データベース・セッションを中間層スレッドで再利用でき(つまり、ステートレスのとき)、バックエンドのサーバー・プロセスの数によってはデータベースでスケール上の問題が発生する可能性がある場合は、OCIセッション・プーリングを使用します。
データベース・セッションを中間層スレッドで再利用できず(つまり、ステートフルのとき)、バックエンドのサーバー・プロセスの数がデータベースでスケール上の問題が発生するほど大きくない場合は、プーリングのメカニズムを使用する必要はありません。
注意: プールされていないセッションまたは接続があると、すべての中間層ユーザー要求に対してデータベース・セッション/接続を分割し、再作成することになります。これにより、データベース側でスケール上の重大な問題が発生し、要求を履行するまでの待機時間が非常に長くなる可能性があります。そのため、データベース・セッションがステートフルであるかステートレスであるかに基づいて、プーリング手法の1つを中間層アプリケーションに採用することを強くお薦めします。 |
接続プーリングではプール要素は接続であり、セッション・プーリングではプール要素はセッションとなります。
プールと同様に、スレッドがデータベースでの作業を終了し、リソースが解放されるまでは、プーリングされたリソースはある一定期間、アプリケーション・スレッドによってロックされます。使用期間中は、リソースは他のスレッドでは使用できません。したがって、アプリケーション開発者は、どんな種類のプーリングでも、比較的短期のタスクを効率的に処理されるということを認識する必要があります。ただし、アプリケーションが長時間実行されるトランザクションを実行中の場合、プーリングされたリソースを他のアプリケーションと共有することを長期間拒絶するため、リソースが枯渇する可能性があります。したがって、プーリングは短期のタスクで使用し、プールのサイズは必要なトランザクションの並列性を維持できる大きさに保つ必要があります。
接続プーリングおよびセッション・プーリングに関する次の追加情報に注意してください。
OCI接続プーリング
データベースへの接続はプーリングされます。ユーザーによって、セッションは作成および破棄されます。データベースに対する各コールにより、プールから使用できる適切な接続が取り出されます。
アプリケーションは、データベースに対する少数の物理的接続で複数のセッションを多重化します。ユーザーは、プール構成をチューニングして必要な並列性を達成できます。
アプリケーション・セッションの有効期間は、キャッシュされたプール接続の有効期間とは関係がありません。
OCIセッション・プーリング
セッションおよび接続はOCIによってプーリングされます。このアプリケーションはプールからセッションを取得し、セッションをプールに戻して解放します。
OCIでは、セッション作成用の次の関数を提供しています。
OCILogon()は、OCIセッションを取得する最も簡単な方法です。この方法の利点は、OCIサービス・コンテキストを簡単に取得できる点です。欠点は、セッション移行、プロキシ認証、接続プールまたはセッション・プールの使用などの、拡張OCI操作を実行できない点です。
OCILogon2()には、セッションを取得するOCILogon()
の機能が含まれます。このセッションは、基礎となる新規の接続があるセッション、既存の接続プールから仮想接続によって開始されたセッション、または既存のセッション・プールからのセッションである場合があります。この関数がコールされるときのmode
パラメータ値によって、動作が決まります。
ユーザーは、OCIによって戻されるサービス・コンテキストの属性(OCI_ATTR_
STMTCACHESIZE
を除く)を変更できません。
OCISessionBegin()は、プロキシ認証、接続プールまたはセッション・プールからのセッションの取得、外部資格照明および移行可能セッションなどの、OCIセッションの様々なオプションをすべてサポートします。これは、最低レベルのコールで、すべてのハンドルを明示的に割り当て、すべての属性を設定する必要があります。OCIServerAttach()
は、このコールの前にコールする必要があります。
OCISessionGet()は、セッションを取得する方法としてお薦めします。このセッションは、基礎となる新規の接続があるセッション、既存の接続プールから仮想接続によって開始されたセッション、または既存のセッション・プールからのセッションである場合があります。この関数がコールされるときのmode
パラメータ値によって、動作が決まります。この動作はOCILogon2()
に似ていますが、これを使用して、プールから特定のセッションを取得するタグを指定することもできます。
OCIには、次の種類のセッションがあります。
基本的なOCIセッション
基本的なOCIセッションは、専用OCIサーバー・ハンドル上でユーザー名およびパスワードを使用することで機能します。これは、プール・メカニズムではありません。使用するタイミングの詳細は、「接続プーリングまたはセッション・プーリングの使用/不使用の判断」を参照してください。
外部資格証明を使用して認証された場合、ユーザー名またはパスワードは必要ありません。
セッション・プール・セッション
セッション・プール・セッションは、セッション・プール・キャッシュから取得します。一部のセッションはタグ付けされる場合があります。これは、ステートレス・セッションです。OCISessionGet()コールおよびOCISessionRelease()コールはそれぞれ、セッション・キャッシュからセッションを取得および解放します。これにより、セッションの作成および破棄からサーバーが保護されます。
接続プール・セッション、セッション・プーリング・セッション、非プーリング・セッションの比較については、「接続プーリングまたはセッション・プーリングの使用/不使用の判断」を参照してください。
接続プール・セッション
接続プール・セッションは、OCI接続プールからのOCISessionGet()コールおよびOCISessionBegin()コールを使用して作成されます。これはステートフル・セッションであるため、セッション・キャッシュはありません。各コールによって新規セッションが作成され、ユーザーの責任でこれらのセッションを終了します。
セッションは、接続プールのサーバー・ハンドル間で自動的に移行できます。各セッションにユーザー名およびパスワードを含めたり、各セッションをプロキシ・セッションにすることができます。接続プール・セッション、セッション・プーリング・セッション、非プーリング・セッションの比較については、「接続プーリングまたはセッション・プーリングの使用/不使用の判断」を参照してください。
サーバー・ハンドルを共有するセッション
少数の物理接続で複数のOCIセッションを多重化できます。これらの複数のセッションに同じサーバー・ハンドルを持たせると、アプリケーションにより、手動で多重化できます。OCI接続プールのAPIを使用して、セッション多重化の詳細はOCIで行うことをお薦めします。
プロキシ・セッション
プロキシ・セッションは、クライアントのパスワードを中間層から保護する必要がある場合に役立ちます。プロキシ・セッションはまた、OCI接続プールまたはOCIセッション・プールの一部に含めることができます。
移行可能なセッション
移行可能なトランザクション・ハンドルを使用している場合、アプリケーションで移行可能なセッションを使用する必要はなく、かわりにOCI接続プーリングを使用します。
この項の内容は次のとおりです。
文キャッシュは、セッションごとの文のキャッシュを提供および管理する機能です。文キャッシュによって、サーバーではカーソルが使用できる状態になり、文を再解析する必要はありません。文キャッシュは、接続プーリングおよびセッション・プーリングとともに使用でき、パフォーマンスおよび拡張性が向上します。また、セッション・プーリングを使用しなくても使用できます。文キャッシュを実装するOCIコールは次のとおりです。
セッション・プーリングを使用せずに文のキャッシングを実行する場合、ユーザーは通常のOCI手順を実行してログオンします。セッションを取得するコールには、セッションにおいて文のキャッシングが有効であるかどうかを指定するモードがあります。最初、文キャッシュは空です。開発者は、文テキストを使用してキャッシュ内で文を見つけようとします。文が存在する場合、APIは以前に準備した文ハンドルを戻しますが、存在しない場合は、新しく準備した文ハンドルを戻します。
アプリケーション開発者は、文がキャッシュに戻される前に、文のバインドと定義を実行し、その後簡単に文の実行とフェッチを実行できます。キャッシュで文ハンドルが見つからない場合、開発者は、他のステップに加えて、ハンドルに異なる属性を設定する必要があります。
OCIStmtPrepare2()には、キャッシュ内に文が見つからない場合に、開発者がプリペアド文を使用するのか、またはNULLの文ハンドルを使用するのかを指定するモードがあります。
擬似コードは次のようになります。
OCISessionBegin( userhp, ... OCI_STMT_CACHE) ; OCIAttrset(svchp, userhp, ...); /* Set the user handle in the service context */ OCIStmtPrepare2(svchp, &stmthp, stmttext, key, ...); OCIBindByPos(stmthp, ...); OCIDefineByPos(stmthp, ...); OCIStmtExecute(svchp, stmthp, ...); OCIStmtFetch2(svchp, ...); OCIStmtRelease(stmthp, ...); ...
セッション・プーリングでの文キャッシュの場合、概念は同じですが、文キャッシュはセッション・レイヤーではなくセッション・プール・レイヤーで使用可能になります。
属性OCI_ATTR_SPOOL_STMTCACHESIZE
により、セッション・プール内のセッションごとにデフォルトの文キャッシュ・サイズが設定されます。これは、OCI_HTYPE_SPOOL
ハンドルで設定されます。プールの特定のセッションの文キャッシュ・サイズは、そのセッションでOCI_ATTR_STMTCACHESIZE
を使用することでいつでも上書きできます。OCI_ATTR_SPOOL_STMTCACHESIZE
の値は、いつでも変更できます。この属性を使用して、プール・レベルで文のキャッシングを有効または無効にできます。作成後、それと同時に、サービス・コンテキストで属性OCI_ATTR_STMTCACHESIZE
を使用して、セッション・レベルで文のキャッシングを有効または無効にできます。この変更内容は、プール内の個別のセッションがユーザーに提供された際に反映されます。タグ付きセッションでは、この動作は当てはまりません。これについては、項の後半で説明します。
注意: 属性は、セッションの取得後に変更できます。ただし、いったん属性を変更すると、基本の物理セッションで設定されたままになります。この値は、セッションが解放されてセッション・プールに戻される間、暗黙的に元の値にリセットされることはありません。したがって、OCIStmtRelease() を使用してセッションを解放するまで、セッションの状態を維持するのは開発者の責任です。 |
文キャッシュの有効化または無効化は、プーリングされていないセッションと同様に、各プーリング・セッションで許可されます。
OCI_SESSGET_STMTCACHE
またはOCI_LOGON2_STMTCACHE
をそれぞれモード引数に指定すると、OCISessionGet()コールまたはOCILogon2()コールで文キャッシュ以外のプールから取り出されたセッションでも、ユーザーは文キャッシュを使用可能にできます。
セッション・プールのセッションを要求する場合は、そのセッションの文キャッシュ・サイズのデフォルトはプールの文キャッシュ・サイズとなります。つまり、そのセッション内で文キャッシュを使用可能または使用不可にすることができます。たとえば、プーリング・セッション(セッションA)に使用可能な文キャッシュがあり、文キャッシュがプールでオフにされ、ユーザーがセッションを要求してセッションAが戻された場合、文キャッシュはセッションAではオフになります。別の例としては、プールのセッションAに使用可能な文キャッシュがなく、プール・レベルの文キャッシュがオンになっている場合、セッションAがユーザーに戻される前に、プールのセッションとサイズが等しいセッションAの文キャッシュがオンになります。
タグ付けされたセッションが要求され、取り出された場合は、異なる結果となります。この場合は、文キャッシュのサイズは変更されません。したがって、文キャッシュはオンまたはオフになりません。さらに、OCISessionGet()コールでユーザーがモードOCI_SESSGET_STMTCACHE
を指定する場合、このセッションがタグ付けされていると、これは無視されます。前述の例では、セッションAがタグ付けされていた場合、ユーザーに対するセッションとして戻されます。
OCIでの文キャッシュには、守るべきいくつかの規則があります。
OCIStmtPrepare()
ではなく、OCIStmtPrepare2()関数を使用します。OCIStmtPrepare()
を使用する場合は、複数のサービス・コンテキスト間で1つの文ハンドルを使用しないことをお薦めします。使用した場合、OCIStmtPrepare2()
を使用して文を取得するとエラーが発生します。文ハンドルを新規のサービス・コンテキストに移行するとき、実際には古いセッションに関連するカーソルがクローズされるため、文ハンドルの共有は取得されません。OCIでは、文ハンドルが移行されると、古いセッションに関連するすべてのバッファが解放されるため、クライアント側での共有も取得されません。
セッションごとに1つのサービス・コンテキストを保持する必要があります。サービス・コンテキストを指定したOCIStmtPrepare2()
を使用して取得した文ハンドルは、後で同じサービス・コンテキストとの組合せでのみ使用でき、異なるサービス・コンテキストとは使用できません。
OCIStmtPrepare2()のコールは、セッションに文キャッシュがなくても、文ハンドルを割り当てます。そのため、OCIStmtPrepare2()のみを使用するアプリケーションでは、文ハンドルのためにOCIHandleAlloc()をコールできません。
OCIStmtPrepare2()のコール後、ユーザーが文ハンドルでの処理を完了した後にOCIStmtRelease()をコールする必要があります。文キャッシュを使用する場合、これによって文はキャッシュに対して解放されます。文キャッシュを使用しない場合、文は割当て解除されます。OCIHandleFree()をコールしてメモリーを解放しないでください。
OCI_PREP2_CACHE_SEARCHONLY
モードを指定してOCIStmtPrepare2()をコールし、NULL
の文が戻された(文が見つからなかった)場合、後続のOCIStmtRelease()コールは必要ないため、実行しないでください。
OCIStmtPrepare()
を使用してプリコンパイルされた文に対して、OCIStmtRelease()をコールしないでください。
文キャッシュには最大サイズ(文の数)があり、サービス・コンテキストのOCI_ATTR_STMTCACHESIZE
属性で変更できます。デフォルト値は20です。また、この属性を使用して、プールされている、またはプールされていないセッションの文キャッシュを使用可能または使用不可にできます。モードを OCI_STMT_CACHE
に設定しないでOCISessionBegin()をコールする場合、サービス・コンテキストのOCI_ATTR_STMTCACHESIZE
を0 (ゼロ)以外の属性に設定し、文キャッシュをオンにできます。セッション・プール・レベルで文キャッシュをオンにしない場合、OCISessionGet()により、文キャッシュに対応していないセッションが戻されます。OCI_ATTR_STMTCACHESIZE
を使用すれば、キャッシュをオンにできます。同様に、同じ属性を使用してキャッシュ・サイズを0 (ゼロ)に設定することにより、文キャッシュをオフにできます。
文の解放時に文にタグを付けることができ、これによって、次回に同じタグの文を要求できます。タグは、キャッシュを検索するために使用します。タグなしの文(タグがNULL
)は、タグ付きの文の特殊なケースです。2つの文がタグのみ異なる場合、または、一方がタグ付きでもう一方がタグなしの場合、これらの文は異なる文とみなされます。
アプリケーションがキャッシュ内の文でバインドおよび定義操作を繰り返すのを回避するために、アプリケーションは、文キャッシュから取得した文で不透明なコンテキストを登録し、サービス・コンテキストでコールバック関数を登録できます。バインドおよび定義バッファなどのアプリケーション・データは、不透明なコンテキストに包含することができます。このコンテキストは、最初にキャッシュから取得された文で登録されます。文が2回目以降キャッシュから取得された際には、アプリケーションはバインドおよび定義バッファを再利用でき、コンテキストはその文で登録されます。アプリケーションにはまだ、バインドおよび定義を管理する責任があります。さらに、バインドと定義両方のデータとバッファを再利用するか、データのみを変更してバッファを再利用するか、または現在のサイズが十分でない場合は、バッファを解放して再割当てできます。最後のケースでは、再バインドと再定義を行う必要があります。これらのバインドおよび定義バッファに対してアプリケーションが割り当てたメモリーをクリーンアップするには、セッションを閉じる一環として文をエージ・アウトするかまたは全キャッシュを消去する際に、コールバック関数をコールします。消去するすべての文に対してコールバック関数をコールします。コールバック関数の内部で、アプリケーションはメモリーを解放して、必要なその他のクリーンアップを実行します。例9-9に擬似コードを示します。
例9-9 キャッシュ内の文に対するバインドおよび定義操作の最適化
Get the statement using OCIStmtPrepare2(...) Get the opaque context from the statement if it exists If opaque context does not exist { Allocate fetch buffers, do the OCIBindByPos, OCIDefineByPos, and so forth Enclose the buffer addresses inside a context and set the context and callback function on the statement } Execute/Fetch using the statement, and process the data in the fetch buffers. OCIStmtRelease() that statement Next OCIStmtPrepare2() OCIAttrGet() opaque application context from statement handle Execute/Fetch using the statement and process the data in the fetch buffers. OCIStmtRelease() . . . void callback_fn (context, statement, mode) { /* mode= OCI_CBK_STMTCACHE_STMTPURGE means this was called when statement was aging out of the statement cache or if the session is ended */ <free the buffers in the context.> }
この項の内容は次のとおりです。
Oracle Call Interfaceでは、OCIコールの他に、ユーザー固有のコードを実行できます。この機能は次のことに使用できます。
ユーザーがアプリケーションをチューニングできるように、トレースやパフォーマンス測定のためのコードを追加します。
特定のOCIコールに対するコードの事前処理または事後処理を実行します。
Oracle Database固有のOCIインタフェースを使用して、OCI付きの他のデータ・ソースにアクセスし、OCIコールがOracle以外のデータ・ソースへのユーザー・コールバックを使用するようにします。
OCIコールバック機能は、OCIコールの実行前後におけるユーザー・コードのコールをサポートしています。また、OCIコードを実行するかわりに、ユーザー定義コードを実行できる機能も用意されています。
アプリケーションのソース・コードを修正することなく、ユーザー・コールバック・コードを動的に登録できます。動的な登録は、OCIEnvCreate()コール時に環境ハンドルを初期化した後、ユーザーが作成した5つ以下の動的リンク・ライブラリをロードすることによって実装されます。ユーザーが作成したライブラリ(Windowsの動的リンク・ライブラリ(DLL)またはSolaris上の共有ライブラリなど)では、選択したOCIコールに対するユーザー・コールバックをアプリケーションに透過的に登録します。
サンプル・アプリケーション
OCIユーザー・コールバック機能を説明するすべてのデモ・プログラムのリストについては、付録Bを参照してください。
アプリケーションから、OCIUserCallbackRegister()関数を使用してユーザー・コールバック・ライブラリを登録できます。コールバックは、環境ハンドルのコンテキスト内に登録されます。アプリケーションでは、OCIUserCallbackGet()関数を使用してハンドルに登録されたコールバックについての情報を取り出すことができます。
ユーザー定義コールバックは、OCIコールおよび環境ハンドルに対して登録されたサブルーチンです。ユーザー定義コールバックには、最初のコールバック、置換コールバックまたは最後のコールバックを指定できます。
最初のコールバックの場合は、プログラムからOCI関数をコールするときにコールされます。
置換コールバックは、最初のコールバック実行の後に実行されます。置換コールバックからOCI_CONTINUE
の値が戻されると、後続の置換コールバックまたは通常のOCIコードが実行されます。置換コールバックからOCI_CONTINUE
以外の値が戻される場合は、後続の置換コールバックおよびOCIコードは実行されません。
置換コールバックからOCI_CONTINUE
以外の値が戻されるか、またはOCI関数が正常に実行されると、プログラムの制御は最後のコールバックに移ります(最後のコールバックが登録されている場合)。
置換コールバックまたは最後のコールバックからOCI_CONTINUE
以外が戻された場合は、コールバックからのリターン・コードは、対応付けられたOCIコールから戻されます。
ユーザー・コールバックでは、無効なハンドルまたは無効なコンテキストが渡された場合に、OCI_INVALID_HANDLE
を戻すことがあります。
注意: 任意のコールバックからOCI_CONTINUE 以外の値が戻された場合は、リターン・コードが後続のコールバックに渡されます。置換コールバックまたは最後のコールバックからOCI_CONTINUE 以外のリターン・コードが戻された場合は、最後の(OCI_CONTINUE ではない)リターン・コードがOCIコールから戻されます。 |
ユーザー・コールバックの登録には、OCIUserCallbackRegister()コールを使用します。
現段階では、OCIUserCallbackRegister()は環境ハンドルでしか登録できません。typedef OCIUserCallback
のユーザー・コールバック関数は、OCI関数コードfcodeによって識別されるOCIコールのコンテキストとともに登録します。コールバックの型は、最初のコールバック、置換または最後のコールバックのいずれの場合もwhenパラメータによって指定します。
たとえば、最初のコールバック関数stmtprep_entry_dyncbk_fn
とそのコンテキストdynamic_context
は、次のパラメータを使用してOCIUserCallbackRegister()関数をコールすることにより、OCIStmtPrepare()コールの環境ハンドルhndlp
に対して登録されます。
OCIUserCallbackRegister( hndlp, OCI_HTYPE_ENV, errh, stmtprep_entry_dyncbk_fn, dynamic_context, OCI_FNCODE_STMTPREPARE, OCI_UCBTYPE_ENTRY (OCIUcb*) NULL);
ユーザー・コールバック関数は、次の構文を使用する必要があります。
typedef sword (*OCIUserCallback) (void *ctxp, /* context for the user callback*/ void *hndlp, /* handle for the callback, env handle for now */ ub4 type, /* type of handlp, OCI_HTYPE_ENV for this release */ ub4 fcode, /* function code of the OCI call */ ub1 when, /* type of the callback, entry or exit */ sword returnCode, /* OCI return code */ ub4 *errnop, /* Oracle error number */ va_list arglist); /* parameters of the oci call */
コールバックは、OCIUserCallbackRegister()コールで説明したパラメータの他に、リターン・コードerrnop、およびプロトタイプ定義で宣言された元のOCIのすべてのパラメータを使用してコールします。
最初のコールバックでは、リターン・コードは常にOCI_SUCCESS
として渡され、*errnop
は常に0として渡されます。errnop
はIN/OUTパラメータであるため、*errnop
はerrnop
の内容を指します。
コールバックがOCIリターン・コードを変更しない場合は、OCI_CONTINUE
を戻す必要があり、*errnop
に戻される値は無視されます。ただし、コールバックからOCI_CONTINUE
以外のリターン・コードが戻された場合は、最後に戻されたリターン・コードがそのコールのリターン・コードになります。この時点で、*errnop
に戻された値は、エラー・ハンドルに設定されるか、または環境ハンドルにエラー情報が戻された場合は環境ハンドルに設定されます。これは、OCIHandleAlloc()などの特定のOCIコールではエラー・ハンドルが存在しないためです。
置換コールバックの場合は、returnCode
は前のコールバックまたはOCIコールから戻されるOCI_CONTINUE
以外のリターン・コードであり、*errnop
はエラー・ハンドル内に戻されるエラー番号の値です。これによって、後続のコールバックでは、必要に応じてリターン・コードやエラー情報を変更できます。
置換コールバックからOCI_CONTINUE
以外のリターン・コードが戻されると、後続の置換コールバックおよびOCIコードは無視され、最後のコールバックの処理に移るという点で、置換コールバックの処理は異なります。
置換コールバックからOCI_CONTINUE
が戻されてOCIコードの処理が可能になった場合は、最初のコールバックからのリターン・コードが無視されることに注意してください。
OCIコールの元のパラメータはすべて、変数パラメータとしてコールバックに渡され、コールバックでは、それらをva_argを使用して取り出す必要があります。コールバックのデモ・プログラムには例が提供されています。
コールバックの登録を解除するために、NULL値を登録することができます。つまり、OCIUserCallbackRegister()コールのコールバック(OCIUserCallback()
)の値がNULL
の場合は、ユーザー・コールバックの登録は解除されます。
スレッドセーフ・モードを使用している場合、OCIプログラムでは、ユーザー・コールバックをコールする前にすべてのmutexを取得します。
例9-10では、典型的なOCIコールの全体的な処理を説明する擬似コードを示しています。
例9-10 典型的なOCIコールの全体的な処理を説明する擬似コード
OCIXyzCall() { Acquire mutexes on handles; retCode = OCI_SUCCESS; errno = 0; for all ENTRY callbacks do { EntryretCode = (*entryCallback)(..., retcode, &errno, ...); if (retCode != OCI_CONTINUE) { set errno in error handle or environment handle; retCode = EntryretCode; } } for all REPLACEMENT callbacks do { retCode = (*replacementCallback) (..., retcode, &errno, ...); if (retCode != OCI_CONTINUE) { set errno in error handle or environment handle goto executeEXITCallback; } } retCode = return code for XyzCall; /* normal processing of OCI call */ errno = error number from error handle or env handle; executeExitCallback: for all EXIT callbacks do { exitRetCode = (*exitCallback)(..., retCode, &errno,...); if (exitRetCode != OCI_CONTINUE) { set errno in error handle or environment handle; retCode = exitRetCode; } } release mutexes; return retCode }
OCIコード全体がコールバックに置き換わる場合、コールバックでは、通常、コール・コンテキストに独自のエラー情報を維持し、これを使用して、OCIErrorGet()コールの置換コールバックのbufp
パラメータおよびerrcodep
パラメータにエラー情報を戻します。
ただし、コールバックがOCIコードの一部をオーバーライドするか、他のなんらかの後処理のみを行う場合は、最後のコールバックを使用して、エラー・テキストや OCIErrorGet()コールのerrcodep
パラメータを、独自のエラー・メッセージやエラー番号で修正できます。最後のコールバックに渡される*errnop
は、エラー・ハンドルまたは環境ハンドル内のエラー番号です。
最初のコールバックからOCIコールのコール元にエラーを戻す場合は、置換コールバックまたは最後のコールバックを登録する必要があります。これは、OCIコードが実行される場合、最初のコールバックのエラー・コードが無視されるためです。したがって、最初のコールバックから独自のコンテキストによって、置換コールバックまたは最後のコールバックにエラーが渡される必要があります。
ユーザー・コールバックは、OCI動作の監視または他のデータ・ソースへアクセスするために使用されることが多いので、コールバックの登録は、割込みが発生しないよう透過的に行われることが望まれます。これは、OCIの初期化時に、ユーザーが作成した動的リンク・ライブラリをロードすることで可能になります。動的にリンクされているこれらのライブラリはパッケージと呼ばれます。ユーザーが作成したパッケージは、選択したOCIコール用のユーザー・コールバックを登録します。これらのコールバックでは、実行時に制御を受け取ったときに、必要に応じてユーザー・コールバックの追加登録または登録解除を行うことができます。
OCIデモ・プログラムとともに、パッケージを作成するためのMakefile (Solarisのociucb.mk
)が用意されています。このパッケージの正確な名前と位置は、オペレーティング・システムによって異なります。パッケージのソース・コードには、OCIの初期化時および環境作成時にコールされる特別なコールバックのコードを含める必要があります。
オペレーティング・システムの環境変数ORA_OCI_UCBPKG
を設定することで、パッケージのロードを制御します。この変数によって、汎用的な方法でパッケージに名前が付けられます。パッケージは、$ORACLE_HOME/lib
ディレクトリに格納してください。
ORA_OCI_UCBPKG
変数には、パッケージ名をセミコロンで区切ったリストを含めることができます。パッケージは、リストに指定されている順序でロードされます。
たとえば、以前パッケージは次のように指定されました。
setenv ORA_OCI_UCBPKG mypkg
現在でも前述の指定はできますが、さらに複数のパッケージを次のように指定できます。
setenv ORA_OCI_UCBPKG "mypkg;yourpkg;oraclepkg;sunpkg;msoftpkg"
これらのパッケージはすべて順番にロードされます。つまり、最初にmypkg
がロードされ、最後にmsoftpkg
がロードされます。
最大5パッケージまで指定できます。
注意: サンプルのMakefileociucb.mk によって、Solarisではociucb.so.1.0 、Windowsシステムではociucb.dll が作成されます。ociucb パッケージをロードするには、環境変数ORA_OCI_UCBPKG にociucb を設定する必要があります。パッケージ名が.so で終わっている場合、SolarisではOCIEnvCreate()または OCIEnvNlsCreate()がエラーになります。パッケージ名は、.so.1.0 で終わる必要があります。
動的リンク・ライブラリの作成の詳細は、オペレーティング・システムのデモ・ディレクトリにあるMakefileをお読みください。ユーザー定義のコールバックの詳細は、使用されているオペレーティング・システムのドキュメントでアプリケーションのコンパイルとリンクの項を参照してください。 |
以前は、パッケージによりOCIEnvCallback()
関数のソース・コードを指定する必要がありました。ただし、OCIEnvCallback()
関数は廃止されています。かわりに、パッケージ・ソースにより2つの関数を指定する必要があります。最初の関数の名前はpackagenameにして、Initという接尾辞を付ける必要があります。たとえば、パッケージの名前がfoo
の場合、ソース・ファイル(たとえば、必ずしもfoo.cというわけではない)には、次のように正確に指定されたOCISharedLibInit()
関数のコールが記述されたfooInit()
関数が含まれている必要があります。
sword fooInit(metaCtx, libCtx, argfmt, argc, argv) void * metaCtx; /* The metacontext */ void * libCtx; /* The context for this package. */ ub4 argfmt; /* package argument format */ sword argc; /* package arg count*/ void * argv[]; /* package arguments */ { return (OCISharedLibInit(metaCtx, libCtx, argfmt, argc, argv, fooEnvCallback)); }
この場合、OCISharedLibInit()
関数の最後のパラメータであるfooEnvCallback()
は、第2の関数の名前になります。任意の名前にできますが、通常はpackagenameにして、EnvCallbackという接尾辞を付けます。
この関数は、OCIEnvCallback()
にかわるものです。現在のところ、すべての動的なユーザー・コールバックは、この関数に登録する必要があります。関数は、次のように指定されるOCIEnvCallbackType
型にしてください。
typedef sword (*OCIEnvCallbackType)(OCIEnv *env, ub4 mode, size_t xtramem_sz, void *usrmemp, OCIUcb *ucbDesc);
環境ハンドルが作成されると、このコールバック関数が最後にコールされます。env
パラメータは、新しく作成された環境ハンドルです。
mode
、xtramem_sz
およびusrmempp
は、OCIEnvCreate()コールに渡されるパラメータです。最後のパラメータucbDesc
は、パッケージに渡される記述子です。後述するように、パッケージではこの記述子を使用して、ユーザー・コールバックを登録します。
サンプルのociucb.c
ファイルがdemo
ディレクトリにあります。Solarisでは、パッケージ作成用のMakefile ociucb.mk
も、demo
ディレクトリに用意されています。これはオペレーティング・システムによって異なりますので、注意してください。さらにdemo
ディレクトリには、これを説明するユーザー・コールバックのデモ・プログラムがすべて(cdemoucb.c、cdemoucbl.c
)揃っています。
ユーザー・コールバックは、アプリケーション自体に静的に登録することも、実行時にDLL内に動的に登録することもできます。動的な登録の目的の動作を維持するには、以前に登録されていたコールバックを上書きした後に、上書きしたコールバックを新しく登録したコールバック内でコールするという、アプリケーションのメカニズムが必要です。この結果、ユーザー・コールバックが連鎖することがあります。
OCIUserCallbackGet()関数により、どの関数およびコンテキストをOCIコール用に登録するかが決まります。
Oracle Databaseはアクセス対象となる最も重要なデータベース・ソフトウェアであるため、アプリケーションはOCIインタフェースを有効に利用し、ユーザー・コールバックを使用してOracle以外のデータにアクセスできます。これにより、OCIで記述されたアプリケーションは、パフォーマンスを低下させずにOracleデータにアクセスできます。ユーザー・コールバックでは、Oracle以外のデータにアクセスするためのドライバを記述できます。OCIには非常に強力なインタフェースが備えられているため、通常、大部分のデータ・ソースに対してOCIコールを直接マッピングできます。このソリューションは、すべてのデータ・ソースに対してパフォーマンスの低下を引き起こす、ODBCのような他の中間層用のアプリケーションを記述することより優れています。OCIを使用するとOracleデータ・ソースにアクセスするのに特に問題は発生しませんが、ODBCがOracle以外のデータ・ソースにアクセスする場合と同じ問題が発生します。
OCIEnvCallback()
などのコールバック関数の使用方法には、いくつかの制約があります。
コールバックからは、OCIUserCallbackRegister()、OCIUserCallbackGet()、OCIHandleAlloc()およびOCIHandleFree()以外のOCI関数をコールできません。これらの関数であっても、ユーザー・コールバックでコールされている場合、コールバックは再帰を避けるためにコールされません。たとえば、OCIHandleFree()
がOCILogoff()のコールバックでコールされている場合は、OCILogoff()
のコールバックの実行中にOCIHandleFree()
のコールバックを行うことはできません。
コールバックでは、環境ハンドルまたはエラー・ハンドルなどのOCIデータ構造を変更できません。
OCIUserCallbackRegister()コール自体または次のいずれのコールに対しても、コールバックは登録できません。
OCIStmtPrepare()コールに対してそれぞれ最初のコールバック、置換コールバックおよび最後のコールバックを登録している5つのパッケージがあるとします。つまり、ORA_OCI_UCBPKG
変数が、例9-11のように設定されています。
各パッケージpkgN
(Nは1から5)では、pkgNInit()
関数およびPkgNEnvCallback()
関数を、例9-12のように指定します。
例9-12 pkgNInit()関数とPkgNEnvCallback()関数の指定
pkgNInit(void *metaCtx, void *libCtx, ub4 argfmt, sword argc, void **argv) { return OCISharedLibInit(metaCtx, libCtx, argfmt, argc, argv, pkgNEnvCallback); }
例9-13では、pkgNEnvCallback()
関数での最初、置換および最後のコールバックの登録方法を示しています。
例9-13 pkgNEnvCallback()を使用した最初、置換および最後のコールバックの登録
pkgNEnvCallback(OCIEnv *env, ub4 mode, size_t xtramemsz, void *usrmemp, OCIUcb *ucbDesc) { OCIHandleAlloc((void *)env, (void **)&errh, OCI_HTYPE_ERROR, (size_t) 0, (void **)NULL); OCIUserCallbackRegister(env, OCI_HTYPE_ENV, errh, pkgN_entry_callback_fn, pkgNctx, OCI_FNCODE_STMTPREPARE, OCI_UCBTYPE_ENTRY, ucbDesc); OCIUserCallbackRegister(env, OCI_HTYPE_ENV, errh, pkgN_replace_callback_fn, pkgNctx, OCI_FNCODE_STMTPREPARE, OCI_UCBTYPE_REPLACE, ucbDesc); OCIUserCallbackRegister(env, OCI_HTYPE_ENV, errh, pkgN_exit_callback_fn, pkgNctx, OCI_FNCODE_STMTPREPARE, OCI_UCBTYPE_EXIT, ucbDesc); return OCI_CONTINUE; }
最後に、例9-14では、アプリケーションのソース・コードで、ユーザー・コールバックをNULL
ucbDescに登録する方法を示しています。
例9-14 ユーザー・コールバックのNULL ucbDescへの登録
OCIUserCallbackRegister(env, OCI_HTYPE_ENV, errh, static_entry_callback_fn, pkgNctx, OCI_FNCODE_STMTPREPARE, OCI_UCBTYPE_ENTRY, (OCIUcb *)NULL); OCIUserCallbackRegister(env, OCI_HTYPE_ENV, errh, static_replace_callback_fn, pkgNctx, OCI_FNCODE_STMTPREPARE, OCI_UCBTYPE_REPLACE, (OCIUcb *)NULL); OCIUserCallbackRegister(env, OCI_HTYPE_ENV, errh, static_exit_callback_fn, pkgNctx, OCI_FNCODE_STMTPREPARE, OCI_UCBTYPE_EXIT, (OCIUcb *)NULL);
例9-15では、OCIStmtPrepare()コールが実行されると、コールバックが次の順にコールされることを示しています。
例9-15 コールバックを順にコールするためのOCIStmtPrepare()の使用
static_entry_callback_fn() pkg1_entry_callback_fn() pkg2_entry_callback_fn() pkg3_entry_callback_fn() pkg4_entry_callback_fn() pkg5_entry_callback_fn() static_replace_callback_fn() pkg1_replace_callback_fn() pkg2_replace_callback_fn() pkg3_replace_callback_fn() pkg4_replace_callback_fn() pkg5_replace_callback_fn() OCI code for OCIStmtPrepare call pkg5_exit_callback_fn() pkg4_exit_callback_fn() pkg3_exit_callback_fn() pkg2_exit_callback_fn() pkg1_exit_callback_fn() static_exit_callback_fn()
注意: 最後のコールバックは、最初のコールバックおよび置換コールバックとは逆の順にコールされます。 |
最初のコールバックと最後のコールバックからは任意のリターン・コードが戻され、続いて次のコールバックが処理されます。ただし、置換コールバックからOCI_CONTINUE
以外のリターン・コードが戻された場合は、連鎖の次のコールバック(または、最後の置換コールバックの場合はOCIコード)が無視され、最後のコールバックの処理に移ります。たとえば、pkg3_replace_callback_fn()
からOCI_SUCCESS
が戻された場合、pkg4_replace_callback_fn()
、pkg5_replace_callback_fn()
およびOCIStmtPrepare()コールのOCI処理は無視されます。かわりに、pkg5_exit_callback_fn()
が次に実行されます。
外部プロシージャからコールバックとして使用できるいくつかのOCI関数があります。これらの関数については、第20章のリストを参照してください。PL/SQLコードからコールできるCサブルーチンの作成方法の詳細は、使用可能なOCIコールのリストおよびコード例も含め、『Oracle Databaseアドバンスト・アプリケーション開発者ガイド』を参照してください。
この項の内容は次のとおりです。
透過的アプリケーション・フェイルオーバー(TAF)はクライアント側の機能であり、インスタンスまたはネットワークの障害によりデータベース接続に失敗すると発生するエンドユーザー・アプリケーションに対する障害を最小限にするために設計されています。TAFは、Oracle Real Application Clusters (Oracle RAC)およびOracle Data Guardフィジカル・スタンバイ・データベースなどの各種システム構成に実装できます。TAFは、単一インスタンス・システムの再起動後(修復時など)にも使用できます。
TAFは、データベース・セッションをリストアし、オプションでオープン問合せを再実行するよう構成できます。Oracle Database 10gリリース2 (10.2)より前のリリースでは、SELECT
フェイルオーバー・オプションを持つTAFは、障害時に使用されていた文でのみ実行されていました。たとえば、アプリケーションで10個の文ハンドルが使用中で、7番目の文が障害時の文(障害発生時に使用中の文)だった場合は、7番目の文がTAFを使用してフェイルオーバーされた後に、1番目から6番目までの文と8番目から10番目までの文を再実行する必要がありました。
Oracle Database 10gリリース2 (10.2)以降、これが改善されています。このリリースでは、アプリケーションが障害後に使用するすべての文で、フェイルオーバーが試行されます。つまり、他の文に対する実行またはフェッチの試行により、障害時文の場合と同様にTAFリカバリが実行されます。後続の文が今回成功する(前回は失敗)か、または試行されたTAFリカバリに対応するエラー(ORA-25401
など)をアプリケーションで受信する可能性があります。
注意: リモート・データベース・リンクまたはDML文については、TAFはサポートされていません。 |
TAFは、クライアント側とサーバー側のどちらでも構成できます。両方とも構成した場合、サーバー側の設定が優先されます。
クライアント側でTAFを構成するには、接続記述子のCONNECT_DATA
の部分にFAILOVER_MODE
パラメータを含めます。
関連項目: TAF (接続データ・セクション1)のクライアント側における構成の詳細は、『Oracle Database Net Servicesリファレンス』を参照してください。 |
サーバー側でTAFを構成するには、DBMS_SERVICE.MODIFY_SERVICE
パッケージ・プロシージャによりターゲット・サービスを変更します。
最初のフェイルオーバーは成功しないことがあります。OCIでは、失敗した後にフェイルオーバーを再試行するメカニズムを提供しています。
関連項目: TAF (DBMS_SERVICE)のサーバー側における構成の詳細は、『Oracle Database PL/SQLパッケージおよびタイプ・リファレンス』を参照してください。 |
フェイルオーバー中に遅延が発生する可能性があるため、アプリケーション開発者は、フェイルオーバーが進行中であることをユーザーに知らせ、フェイルオーバーの完了通知をユーザーが待機するよう要求した方がよい場合があります。さらに、最初のインスタンスのセッションで、いくつかのALTER
SESSION
コマンドを受け取る可能性もあります。これらのALTER
SESSION
コマンドが、2番目のインスタンスで自動的に再実行されることはありません。したがって、開発者は、これらを2番目のインスタンスで再実行する可能性があります。セッションに影響を与えるOCIAttrSet()コールも再実行する必要があります。
これらの要件に対応するため、アプリケーションの開発者は、フェイルオーバー・コールバック関数を登録できます。フェイルオーバーが発生すると、コールバック関数が、ユーザーのセッションを再確立する間に数回呼び出されます。
コールバック関数が最初にコールされるのは、データベースがインスタンスの接続中断を最初に検出したときです。このコールバックは、アプリケーションがユーザーに対して遅延の発生を通知することを目的にしています。フェイルオーバーが正常終了すると、コールバック関数の2番目のコールは、接続が再確立して使用可能になったときに発生します。
接続が再確立されると、クライアントではALTER
SESSION
コマンドを再実行し、ユーザーにフェイルオーバーが発生したことを通知できます。フェイルオーバーが失敗すると、フェイルオーバーが発生しなかったことをアプリケーションに通知するために、コールバックがコールされます。さらに、1次ハンドル以外のユーザー・ハンドルが新しい接続で再認証されるたびに、コールバックがコールされます。各ユーザー・ハンドルはサーバー側セッションを表すため、クライアントでは、そのセッションのためにALTER
SESSION
コマンドを再実行できます。
ユーザー定義のアプリケーション・フェイルオーバー・コールバック関数の基本的な構造は次のとおりです。
sb4 appfocallback_fn ( void * svchp, void * envhp, void * fo_ctx, ub4 fo_type, ub4 fo_event );
次のパラメータの例は、「フェイルオーバー・コールバックの例」で提供されています。
最初のパラメータのsvchp
は、サービス・コンテキスト・ハンドルです。これはvoid *
型です。
2番目のパラメータのenvhp
は、OCI環境ハンドルです。これはvoid *
型です。
3番目のパラメータのfo_ctx
は、クライアント・コンテキストです。これは、クライアントが指定するメモリーへのポインタです。この領域には、クライアントが必要な状態またはコンテキストを保管できます。このパラメータはvoid *
型です。
4番目のパラメータのfo_type
は、フェイルオーバーの種類です。このパラメータにより、クライアントが要求したフェイルオーバーの種類をコールバックが判断できます。通常の値は次のとおりです。
OCI_FO_SESSION
- ユーザーがセッションのフェイルオーバーのみを要求したことを示します。
OCI_FO_SELECT
- ユーザーが選択フェイルオーバーも要求したことを示します。
最後のパラメータは、フェイルオーバーのイベントです。このパラメータは、コールバックがコールされた理由を示します。次の値をとることができます。
OCI_FO_BEGIN
- フェイルオーバーが中断した接続を検出し、フェイルオーバーが開始することを示します。
OCI_FO_END
- フェイルオーバーが正常終了したことを示します。
OCI_FO_ABORT
- フェイルオーバーが失敗し、再試行できないことを示します。
OCI_FO_ERROR
- 同様にフェイルオーバーが失敗したことを示しますが、アプリケーションでエラーを処理してフェイルオーバーを再試行する機会が与えられます。
OCI_FO_REAUTH
- 複数の認証ハンドルがあり、最初の認証の後にフェイルオーバーが発生したことを示します。ユーザー・ハンドルが再認証されたことを示します。どのユーザー・ハンドルかを調べるために、アプリケーションで、最初のパラメータであるサービス・コンテキスト・ハンドルのOCI_ATTR_SESSION
属性をチェックします。
フェイルオーバー・コールバックを使用するには、それをサーバー・コンテキスト・ハンドルに登録する必要があります。この登録は、コールバック定義構造体を作成し、サーバー・ハンドルのOCI_ATTR_FOCBK
属性にこの構造体を設定することにより行います。
コールバック定義構造の型はOCIFocbkStruct
にする必要があります。ここには、2つのフィールドがあり、フィールドcallback_function
にはコールする関数のアドレスが含まれており、フィールドfo_ctx
にはクライアント・コンテキストのアドレスが含まれています。
コールバック登録の例は、例9-17の一部として含まれています。
この項では、簡単なユーザー定義コールバック関数の定義の例(例9-16を参照)、フェイルオーバー・コールバックの登録の例(例9-17を参照)およびフェイルオーバー・コールバックの登録解除の例(例9-18を参照)を示しています。
例9-16 ユーザー定義フェイルオーバー・コールバック関数の定義
sb4 callback_fn(svchp, envhp, fo_ctx, fo_type, fo_event) void * svchp; void * envhp; void *fo_ctx; ub4 fo_type; ub4 fo_event; { switch (fo_event) { case OCI_FO_BEGIN: { printf(" Failing Over ... Please stand by \n"); printf(" Failover type was found to be %s \n", ((fo_type==OCI_FO_SESSION) ? "SESSION" :(fo_type==OCI_FO_SELECT) ? "SELECT" : "UNKNOWN!")); printf(" Failover Context is :%s\n", (fo_ctx?(char *)fo_ctx:"NULL POINTER!")); break; } case OCI_FO_ABORT: { printf(" Failover stopped. Failover will not occur.\n"); break; } case OCI_FO_END: { printf(" Failover ended ...resuming services\n"); break; } case OCI_FO_REAUTH: { printf(" Failed over user. Resuming services\n"); break; } default: { printf("Bad Failover Event: %d.\n", fo_event); break; } } return 0; }
例9-17 フェイルオーバー・コールバックの登録
int register_callback(srvh, errh) void *srvh; /* the server handle */ OCIError *errh; /* the error handle */ { OCIFocbkStruct failover; /* failover callback structure */ /* allocate memory for context */ if (!(failover.fo_ctx = (void *)malloc(strlen("my context.")+1))) return(1); /* initialize the context. */ strcpy((char *)failover.fo_ctx, "my context."); failover.callback_function = &callback_fn; /* do the registration */ if (OCIAttrSet(srvh, (ub4) OCI_HTYPE_SERVER, (void *) &failover, (ub4) 0, (ub4) OCI_ATTR_FOCBK, errh) != OCI_SUCCESS) return(2); /* successful conclusion */ return (0); }
例9-18 フェイルオーバー・コールバックの登録解除
OCIFocbkStruct failover; /* failover callback structure */ sword status; /* set the failover context to null */ failover.fo_ctx = NULL; /* set the failover callback to null */ failover.callback_function = NULL; /* unregister the callback */ status = OCIAttrSet(srvhp, (ub4) OCI_HTYPE_SERVER, (void *) &failover, (ub4) 0, (ub4) OCI_ATTR_FOCBK, errhp);
フェイルオーバーは、成功しないことがあります。失敗した場合は、コールバック関数には、fo_event
パラメータにOCI_FO_ABORT
またはOCI_FO_ERROR
の値が戻されます。OCI_FO_ABORT
の場合は、フェイルオーバーが失敗し、フェイルオーバーを続行できないことを示します。ただし、OCI_FO_ERROR
は、エラーを処理する機会のあるコールバック関数を提供します。たとえば、一定時間待機してから、コールバックからOCIライブラリにフェイルオーバーの再試行を指示する必要があります。
表9-2で提示されているイベントの時系列について考えます。
表9-2 時間およびイベント
時間 | イベント |
---|---|
T0 |
データベースに障害が発生します(障害はT5まで続きます)。 |
T1 |
ユーザーの操作によってフェイルオーバーが動作します。 |
T2 |
ユーザーが再接続を試行します。試行が失敗します。 |
T3 |
|
T4 |
フェイルオーバー・コールバックは事前に定義されたスリープ状態になります。 |
T5 |
データベースがリストアされます。 |
T6 |
フェイルオーバー・コールバックによって新しいフェイルオーバーが動作し、成功します。 |
T7 |
ユーザーは再接続に成功します。 |
コールバック関数からOCI_FO_RETRY
の値が戻されると、新しいフェイルオーバーが動作します。
例9-19では、前述のシナリオと類似したフェイルオーバー手法の実装に使用できるコールバック関数を示しています。この場合、フェイルオーバー・コールバックはループに入り、スリープ状態になってから、成功するまでフェイルオーバーを再試行します。
例9-19 フェイルオーバー手法を実装するコールバック関数
/*--------------------------------------------------------------------*/ /* the user-defined failover callback */ /*--------------------------------------------------------------------*/ sb4 callback_fn(svchp, envhp, fo_ctx, fo_type, fo_event ) void * svchp; void * envhp; void *fo_ctx; ub4 fo_type; ub4 fo_event; { OCIError *errhp; OCIHandleAlloc(envhp, (void **)&errhp, (ub4) OCI_HTYPE_ERROR, (size_t) 0, (void **) 0); switch (fo_event) { case OCI_FO_BEGIN: { printf(" Failing Over ... Please stand by \n"); printf(" Failover type was found to be %s \n", ((fo_type==OCI_FO_NONE) ? "NONE" :(fo_type==OCI_FO_SESSION) ? "SESSION" :(fo_type==OCI_FO_SELECT) ? "SELECT" :(fo_type==OCI_FO_TXNAL) ? "TRANSACTION" : "UNKNOWN!")); printf(" Failover Context is :%s\n", (fo_ctx?(char *)fo_ctx:"NULL POINTER!")); break; } case OCI_FO_ABORT: { printf(" Failover aborted. Failover will not occur.\n"); break; } case OCI_FO_END: { printf("\n Failover ended ...resuming services\n"); break; } case OCI_FO_REAUTH: { printf(" Failed over user. Resuming services\n"); break; } case OCI_FO_ERROR: { /* all invocations of this can only generate one line. The newline * will be put at fo_end time. */ printf(" Failover error gotten. Sleeping..."); sleep(3); printf("Retrying. "); return (OCI_FO_RETRY); break; } default: { printf("Bad Failover Event: %d.\n", fo_event); break; } } return 0; }
この項の内容は次のとおりです。
バックエンド・データベース・サーバーにアクセスするアプリケーション・サーバーにログインするのにWebブラウザをユーザーが使用するとします。データベース・インスタンスに障害が発生した場合、障害がユーザーに通知されるまでに最大で数分かかる場合があります。HAイベント通知には、サーバー・インスタンスの障害を迅速に検出する機能、これをクライアントに通知する機能、および接続プールのアイドル状態の接続をクリーン・アップする機能が用意されています。
Oracle RACデータベースに接続された項可用性のクライアントの場合、データベース障害が発生したときに、HAイベント通知を使用して、ベストエフォート型プログラム・シグナルをクライアントに提供できます。クライアント・アプリケーションは、環境ハンドルにコールバックを登録し、この情報に興味があることを伝えます。このクライアントによって確立された接続に重大な障害イベントが発生すると、そのイベントに関する情報(イベント・ペイロード)や、障害によって切断された接続(サーバー・ハンドル)のリストとともにこのコールバックが呼び出されます。
たとえば、インスタンスAに対する2つの接続と、同じデータベースのインスタンスBに対する2つの接続を持つクライアント・アプリケーションを考えてみます。インスタンスAに障害が発生すると、イベント通知がクライアントに送信され、次に、クライアントにより、インスタンスBに対する2つの接続が切断され、かつ登録されているコールバックが呼び出されます。この場合、同じデータベースの別のインスタンスCに障害が発生しても、クライアントには通知されません(この障害がこのクライアントの接続に影響しないためです)。
HAイベント通知メカニズムにより、障害が発生した際のアプリケーションの応答時間が短縮されます。Oracle Database 10gリリース2 (10.2)でメカニズムが導入される以前は、障害が発生した場合、TCPタイムアウトの期限が切れるまで接続は切断されず、これには数分かかることがありました。HAイベント通知を使用すると、スタンドアロン、接続プールおよびセッション・プール接続はOCIによって自動的に切断されてクリーン・アップされ、障害イベントが発生してから数秒以内にアプリケーション・コールバックが呼び出されます。これらのサーバー・ハンドルのいずれかがTAFに対応している場合は、フェイルオーバーもOCIによって自動的に実行されます。
HAイベント通知を有効化するには、アプリケーションはOracle RACインスタンスに接続する必要があります。また、これらのアプリケーションで次を行う必要があります。
OCI_EVENTS
モードでのOCI環境の初期化
通知が有効化されたサービスへの接続(DBMS_SERVICE.MODIFY_SERVICE
プロシージャを使用してAQ_HA_NOTIFICATIONS
をTRUE
に設定する)
スレッド・ライブラリとのリンク
これにより、これらのアプリケーションはコールバックを登録でき、このコールバックが、HAイベントの発生のたびに呼び出されるようになります。
OCIEvent
ハンドルは、イベント・ペイロードの属性をカプセル化します。OCIでは、イベント・コールバックを呼び出す前にこのハンドルを暗黙的に割り当て、その結果イベント・コールバックでは、OCIAttrGet()
を呼び出すことにより、イベントの読取り専用属性を取得できます。これらの属性に関連するメモリーが有効なのは、イベント・コールバック期間中のみです。
Oracle RACのインスタンス内の接続プールは、Oracle RACの別のインスタンスに接続されている接続のプールから構成されます。ノード障害の通知を受け取った場合は、特定のインスタンスに接続されているすべての接続をクリーンアップする必要があります。接続が使用されている場合、OCIで接続を閉じる必要があります。これにより、即座に透過的アプリケーション・フェイルオーバー(TAF)が発生し、これらの接続が再確立されます。アイドル状態でプールの空きリストに入っている接続は消去する必要があるため、不適切な接続はプールからユーザーには戻されません。
カスタム接続プールに対応するために、OCIには、環境ハンドルに登録可能なコールバック関数が用意されています。登録すると、このコールバックはHAイベントが発生したときに呼び出されます。セッション・プールは、接続プールと同じように処理されます。OCI接続プールまたはセッション・プールのサーバー・ハンドルは、コールバックには渡されません。このため、場合によっては、空の接続リストを使用してコールバックが呼び出されることがあります。
独立した接続に対しては特別な処理は必要なく、障害の発生したインスタンスに接続されている接続はすべて即座に切断されます。アイドル状態の接続に対しては、TAFにより、この接続が後続のOCIコールで使用された際に再確立されます。接続が失敗イベントで使用されている場合は即座に切断されるため、TAFを開始できます。これは、接続の「使用中」の接続およびセッション・プールにも当てはまります。
OCIEventCallback
型のイベント・コールバックには、次のシグネチャがあります。
void evtcallback_fn (void *evtctx, OCIEvent *eventhp );
このシグネチャでは、evtctx
はクライアントのコンテキストで、OCIEvent
は、OCIライブラリに対しては不透明なイベント・ハンドルです。もう1つの入力引数は、イベント・ハンドルであるeventhp
です(イベントに関連付けられている属性)。
この関数は、登録されると、イベントのたびに1回ずつ呼び出されます。Oracle RAC HAイベントの場合、このコールバックは、影響を受けた接続が切断されてから呼び出されます。次の環境ハンドル属性を使用して、イベントのコールバックとコンテキストをそれぞれ登録します。
text *myctx = "dummy context"; /* dummy context passed to callback fn */ ... /* OCI_ATTR_EVTCBK and OCI_ATTR_EVTCTX are read-only. */ OCIAttrSet(envhp, (ub4) OCI_HTYPE_ENV, (void *) evtcallback_fn, (ub4) 0, (ub4) OCI_ATTR_EVTCBK, errhp); OCIAttrSet(envhp, (ub4) OCI_HTYPE_ENV, (void *) myctx, (ub4) 0, (ub4) OCI_ATTR_EVTCTX, errhp); ...
OCIイベント・コールバック内では、影響を受けるサーバー・ハンドルのリストは、OCIEvent
ハンドルにカプセル化されます。Oracle RAC HA DOWNイベントの場合、クライアント・アプリケーションは、属性タイプOCI_ATTR_HA_SRVFIRST
およびOCI_ATTR_HA_SRVNEXT
とともにOCIAttrGet()を使用することにより、イベントの影響を受けるサーバー・ハンドルのリストを反復できます。
OCIAttrGet(eventhp, OCI_HTYPE_EVENT, (void *)&srvhp, (ub4 *)0, OCI_ATTR_HA_SRVFIRST, errhp); /* or, */ OCIAttrGet(eventhp, OCI_HTYPE_EVENT, (void *)&srvhp, (ub4 *)0, OCI_ATTR_HA_SRVNEXT, errhp);
この関数は、属性OCI_ATTR_HA_SRVFIRST
を使用して呼び出されると、影響を受けるサーバー・ハンドルのリスト内の最初のサーバー・ハンドルを取得します。この関数は、属性OCI_ATTR_HA_SRVNEXT
を使用して呼び出されると、リスト内の次のサーバー・ハンドルを取得します。戻すサーバー・ハンドルがなくなると、この関数はOCI_NO_DATA
を戻し、srvhp
はNULL
ポインタになります。
srvhp
は、HAイベントにより接続が閉じられたサーバー・ハンドルへの出力ポインタです。errhp
は、移入されるエラー・ハンドルです。アプリケーションは、取得する影響を受けたサーバー・ハンドルがなくなると、OCI_NO_DATA
エラーを戻します。
HAイベントの影響を受けたサーバー・ハンドルのリストを取得する場合、接続はクローズ済でありかつ多くのサーバー・ハンドル属性が無効になっていることに注意してください。かわりに、イベント通知コールバックに必要な接続ごとの属性を、サーバー・ハンドルのユーザー・メモリー・セグメントに格納します。このメモリーは、サーバー・ハンドルが解放されるまで有効です。
次の機能は、カスタム・プールに適用されます。
サーバー・ハンドルをカスタム・プールのかわりに作成する場合、サーバー・ハンドルに親接続オブジェクトのタグを付けることができます。OCIHandleAlloc()のユーザー・メモリー・パラメータを使用して、ユーザー・メモリー・セグメントをサーバー・ハンドルに割り当てるよう要求します。ユーザー・メモリー・セグメントのポインタは、OCIHandleAlloc()
によって戻されます。
HAイベントが発生し、影響を受けるサーバー・ハンドルが取得された場合、適切なクリーン・アップを実行できるようサーバー・ハンドルのタグ情報を取得するための方法があります。OCI_ATTR_USER_MEMORY
属性を使用して、ハンドルのユーザー・メモリー・セグメントのポインタを取得します。OCI_ATTR_USER_MEMORY
は、ユーザーが割り当てたすべてのハンドルに対して有効です。ハンドルに追加メモリーが割り当てられている場合、ユーザー・メモリーへのポインタが戻されます。追加メモリーが割り当てられていないハンドルに関しては、NULL
ポインタが戻されます。この属性は読取り専用で、データ型はvoid*
です。
注意: サーバー・ハンドルのユーザー・メモリー・セグメントの正確な内容は自由に定義できます。OCIは、このメモリーからは書込みまたは読取りを行わないため、これにより、HAイベント・コールバックからのクリーン・アップ・アクティビティ(必要な場合は、その他の目的)が簡単になります。ユーザー・メモリー・セグメントは、サーバー・ハンドルに対するOCIHandleFree()のコールによって解放されます。 |
例9-20では、イベント通知の例を示しています。
例9-20 イベント通知
sword retval; OCIServer *srvhp; struct myctx { void *parentConn_myctx; uword numval_myctx; }; typedef struct myctx myctx; myctx *myctxp; /* Allocate a server handle with user memory - pre 10.2 functionality */ if (retval = OCIHandleAlloc(envhp, (void **)&srvhp, OCI_HTYPE_SERVER, (size_t)sizeof(myctx), (void **)&myctxp) /* handle error */ myctxp->parentConn_myctx = <parent connection reference>; /* In an event callback function, retrieve the pointer to the user memory */ evtcallback_fn(void *evtctx, OCIEvent *eventhp) { myctx *ctxp = (myctx *)evtctx; OCIServer *srvhp; OCIError *errhp; sb4 retcode; retcode = OCIAttrGet(eventhp, OCI_HTYPE_SERVER, &srvhp, (ub4 *)0, OCI_ATTR_HA_SRVFIRST, errhp); while (!retcode) /* OCIAttrGet will return OCI_NO_DATA if no more srvhp */ { OCIAttrGet((void *)srvhp, OCI_HTYPE_SERVER, (void *)&ctxp, (ub4)0, (ub4)OCI_ATTR_USER_MEMORY, errhp); /* Remove the server handle from the parent connection object */ retcode = OCIAttrGet(eventhp, OCI_HTYPE_SERVER, &srvhp, (ub4 *)0, OCI_ATTR_HA_SRVNEXT, errhp); ... } ... }
接続がTAFに対応している場合もいない場合も、アプリケーションにその動作を調整させることができます。次のようにOCIAttrGet()
を使用して、サーバー・ハンドルがTAFに対応しているかどうかを確認します。
boolean taf_capable; ... OCIAttrGet(srvhp, (ub4) OCI_HTYPE_SERVER, (void *) &taf_capable, (ub4) sizeof(taf_capable), (ub4)OCI_ATTR_TAF_ENABLED, errhp); ...
この例では、taf_capable
はブール変数で、サーバー・ハンドルがTAFに対応している場合はTRUE
、対応していない場合はFALSE
に設定されます。srvhp
は、入力ターゲットのサーバー・ハンドルです。OCI_ATTR_TAF_ENABLED
は、ブール変数のポインタで読取り専用です。errhp
は、入力エラー・ハンドルです。
この項の内容は次のとおりです。
OCIでは、Streams Advanced Queuing (Streams AQ)機能へのインタフェースを提供します。Streams AQは、Oracle Databaseと統合されたメッセージ・キューイングです。Streams AQでは、キューイング・システムをデータベースに統合することによりこの機能を実現しており、その結果メッセージ対応データベースが作成されます。Streams AQでは統合された解決策が提供されるため、アプリケーション開発者がメッセージ・インフラストラクチャを構築する必要がなくなり、特定のビジネス・ロジックに専念できるようになりました。
注意: Streamsアドバンスト・キューイングを使用するには、Oracle DatabaseのEnterprise Editionを使用している必要があります。 |
関連項目:
|
OCIライブラリには、Streamsアドバンスト・キューイングに関して次の関数が含まれています。
シングル・キューにメッセージの配列をエンキューできます。メッセージはすべて、同じエンキュー・オプションを共有しますが、配列内の各メッセージには異なるメッセージ・プロパティを含めることができます。シングル・キューからメッセージの配列をデキューすることもできます。トランザクション・グループ・キューの場合は、1回のコールでシングル・トランザクション・グループにすべてのメッセージをデキューできます。
次の記述子がOCI Streams AQ操作に使用されます。
OCIAQEnqOptions
OCIAQDeqOptions
OCIAQMsgProperties
OCIAQAgent
標準OCIDescriptorAlloc()コールを使用して、サービス・ハンドルに対してこれらの記述子を割り当てられます。次のコードでこの例を示します。
OCIDescriptorAlloc(svch, &enqueue_options, OCI_DTYPE_AQENQ_OPTIONS, 0, 0 ); OCIDescriptorAlloc(svch, &dequeue_options, OCI_DTYPE_AQDEQ_OPTIONS, 0, 0 ); OCIDescriptorAlloc(svch, &message_properties, OCI_DTYPE_AQMSG_PROPERTIES, 0, 0); OCIDescriptorAlloc(svch, &agent, OCI_DTYPE_AQAGENT, 0, 0 );
各記述子には、設定または読取りができる様々な属性が含まれます。
次の表は、OCI Streams AQ関数および記述子の関数、パラメータ、オプションと、DBMS_AQパッケージのPL/SQL AQ関数との比較を表します。表9-3では、AQ関数を比較しています。
表9-3 AQ関数
PL/SQLファンクション | OCI関数 |
---|---|
DBMS_AQ.ENQUEUE |
|
DBMS_AQ.DEQUEUE |
|
DBMS_AQ.LISTEN |
|
DBMS_AQ.ENQUEUE_ARRAY |
|
DBMS_AQ.DEQUEUE_ARRAY |
|
表9-4では、エンキュー関数のパラメータを比較しています。
表9-4 エンキュー・パラメータ
DBMS_AQ.ENQUEUEパラメータ | OCIAQEnq()パラメータ |
---|---|
queue_name |
queue_name |
enqueue_options |
enqueue_options |
message_properties |
message_properties |
payload |
payload |
msgid |
msgid |
- |
注意: |
表9-5では、デキュー関数のパラメータを比較しています。
表9-5 デキュー・パラメータ
DBMS_AQ.DEQUEUEパラメータ | OCIAQDeq()パラメータ |
---|---|
queue_name |
queue_name |
dequeue_options |
dequeue_options |
message_properties |
message_properties |
payload |
payload |
msgid |
msgid |
- |
注意: |
表9-6では、リスニング関数のパラメータを比較しています。
表9-6 リスニング・パラメータ
DBMS_AQ.LISTENパラメータ | OCIAQListen2()パラメータ |
---|---|
agent_list |
agent_list |
wait |
wait |
agent |
agent |
listen_delivery_mode |
|
- |
|
表9-7では、配列エンキュー関数のパラメータを比較しています。
表9-7 配列エンキュー・パラメータ
DBMS_AQ.ENQUEUE_ARRAYパラメータ | OCIAQEnqArray()パラメータ |
---|---|
queue_name |
queue_name |
enqueue_options |
enqopt |
array_size |
iters |
message_properties_array |
msgprop |
payload_array |
payload |
msgid_array |
msgid |
- |
注意: |
表9-8では、配列デキュー関数のパラメータを比較しています。
表9-8 配列デキュー・パラメータ
DBMS_AQ.DEQUEUE_ARRAYパラメータ | OCIAQDeqArray()パラメータ |
---|---|
queue_name |
queue_name |
dequeue_options |
deqopt |
array_size |
iters |
message_properties_array |
msgprop |
payload_array |
payload |
msgid_array |
msgid |
- |
注意: |
表9-9では、エージェント属性のパラメータを比較しています。
表9-9 エージェント・パラメータ
PL/SQL Agentパラメータ | OCIAQAgent属性 |
---|---|
name |
|
address |
|
protocol |
|
表9-10では、メッセージ・プロパティのパラメータを比較しています。
表9-10 メッセージ・プロパティ
PL/SQLメッセージ・プロパティ | OCIAQMsgProperties属性 |
---|---|
priority |
|
delay |
|
expiration |
|
correlation |
|
attempts |
|
recipient_list |
|
exception_queue |
|
enqueue_time |
|
state |
|
sender_id |
|
transaction_group |
|
original_msgid |
|
delivery_mode |
|
表9-11では、エンキュー・オプション属性を比較しています。
表9-11 エンキュー・オプション属性
PL/SQLエンキュー・オプション | OCIAQEnqOptions属性 |
---|---|
visibility |
|
relative_msgid |
|
sequence_deviation |
(非推奨) |
|
|
delivery_mode |
|
表9-12では、デキュー・オプション属性を比較しています。
表9-12 デキュー・オプション属性
PL/SQLデキュー・オプション | OCIAQDeqOptions属性 |
---|---|
consumer_name |
|
dequeue_mode |
|
navigation |
|
visibility |
|
wait |
|
msgid |
|
correlation |
|
|
|
|
|
delivery_mode |
|
注意: OCIAQEnq()は、エンキュー・オプションOCI_ATTR_SEQUENCE をOCI_ATTR_RELATIVE_MSGID とともに指定している間は、エラーORA-25219 を戻します。これは、2つのメッセージをエンキューした場合に発生します。2番目のメッセージに対しては、最初のメッセージの前にこのメッセージをデキューするように、エンキュー・オプションOCI_ATTR_SEQUENCE およびOCI_ATTR_RELATIVE_MSGID が設定されます。順序を指定していない場合エラーは戻されませんが、関連メッセージの前にこのメッセージはデキューされません。
|
バッファ・メッセージは、Streams AQ内の非永続型メッセージ機能で、Oracle Database 10gリリース2から使用可能になりました。バッファ・メッセージは、共有メモリーに格納され、インスタンスに障害が発生すると失われることがあります。永続型メッセージとは異なり、ディスクにはREDOが書き込まれません。永続型メッセージ操作より、バッファ・メッセージのエンキューとデキューの方がはるかに高速です。共有メモリーには制限があるため、バッファ・メッセージをディスクにオーバーフローさせることが必要になる場合があります。メッセージ・コンシューマが低速である場合や、なんらかの理由により停止した場合、フロー制御を有効にすることにより、アプリケーションによって共有メモリーが許容オーバーになるのを阻止できます。バッファ・メッセージには、次の関数が使用されます。
例9-21では、エンキュー・バッファ・メッセージの例を示しています。
例9-21 エンキュー・バッファ・メッセージ
... OCIAQMsgProperties *msgprop; OCIAQEnqueueOptions *enqopt; message msg; /* message is an object type */ null_message nmsg; /* message indicator */ ... /* Allocate descriptors */ OCIDescriptorAlloc(envhp, (void **)&enqopt, OCI_DTYPE_AQENQ_OPTIONS, 0, (void **)0)); OCIDescriptorAlloc(envhp, (void **)&msgprop,OCI_DTYPE_AQMSG_PROPERTIES, 0, (void **)0)); /* Set delivery mode to buffered */ dlvm = OCI_MSG_BUFFERED; OCIAttrSet(enqopt, OCI_DTYPE_AQENQ_OPTIONS, (void *)&dlvm, sizeof(ub2), OCI_ATTR_MSG_DELIVERY_MODE, errhp); /* Set visibility to Immediate (visibility must always be immediate for buffered messages) */ vis = OCI_ENQ_ON_COMMIT; OCIAttrSet(enqopt, OCI_DTYPE_AQENQ_OPTIONS,(void *)&vis, sizeof(ub4), OCI_ATTR_VISIBILITY, errhp) /* Message was an object type created earlier, msg_tdo is its type descriptor object */ OCIAQEnq(svchp, errhp, "Test_Queue", enqopt, msgprop, msg_tdo, (void **)&mesg, (void **)&nmesg, (OCIRaw **)0, 0)); ...
例9-22では、デキュー・バッファ・メッセージの例を示しています。
例9-22 デキュー・バッファ・メッセージ
... OCIAQMsgProperties *msgprop; OCIAQDequeueOptions *deqopt; ... OCIDescriptorAlloc(envhp, (void **)&mprop, OCI_DTYPE_AQMSG_PROPERTIES, 0, (void **)0)); OCIDescriptorAlloc(envhp, (void **)&deqopt, OCI_DTYPE_AQDEQ_OPTIONS, 0, (void **)0); /* Set visibility to Immediate (visibility must always be immediate for buffered message operations) */ vis = OCI_ENQ_ON_COMMIT; OCIAttrSet(deqopt, OCI_DTYPE_AQDEQ_OPTIONS,(void *)&vis, sizeof(ub4), OCI_ATTR_VISIBILITY, errhp) /* delivery mode is buffered */ dlvm = OCI_MSG_BUFFERED; OCIAttrSet(deqopt, OCI_DTYPE_AQDEQ_OPTIONS, (void *)&dlvm, sizeof(ub2), OCI_ATTR_MSG_DELIVERY_MODE, errhp); /* Set the consumer for which to dequeue the message (this must be specified regardless of the type of message being dequeued). */ consumer = "FIRST_SUBSCRIBER"; OCIAttrSet(deqopt, OCI_DTYPE_AQDEQ_OPTIONS, (void *)consumer, (ub4)strlen((char*)consumer), OCI_ATTR_CONSUMER_NAME, errhp); /* Dequeue the message but do not return the payload (to simplify the code fragment) */ OCIAQDeq(svchp, errhp, "test_queue", deqopt, msgprop, msg_tdo, (void **)0, (void **)0, (OCIRaw**)0, 0); ...
注意: 配列操作は、バッファ・メッセージに対してはサポートされません。アプリケーションでは、配列サイズを1に設定してOCIAQEnqArray() およびOCIAQDeqArray() 関数を使用できます。 |
この項の内容は次のとおりです。
パブリッシュ・サブスクライブの通知機能により、OCIアプリケーションでは、クライアント通知の直接受信、通知を送信できる電子メール・アドレスの登録、通知を送信できるHTTP URLの登録、あるいは通知に対してコールされるPL/SQLプロシージャの登録を行うことができます。図9-2は、そのプロセスを示しています。
OCIアプリケーションでは次の処理を行います。
AQネームスペースに通知の実行を登録し、エンキューが発生したときに通知を受け取ります。
データベース・イベントのサブスクリプションの実行を登録し、そのイベントがトリガーされたときに通知を受け取ります。
一時的な登録の解除またはすべての登録の破棄など、登録を管理します。
登録されたクライアントに通知を送信します。
前述のすべての場合において、OCIアプリケーションでの通知の直接受信、事前に指定された電子メール・アドレスへの通知の送信、事前に定義されたHTTP URLへの通知の送信、または通知の結果として、事前に指定したデータベースPL/SQLプロシージャのコールが可能です。
登録されたクライアントは、イベントが発生した時、または明示的なAQエンキューにあるとき、非同期に通知を受け取ります。クライアントは、データベースに接続する必要はありません。
関連項目:
|
登録する方法には、次の2通りがあります。
直接登録。直接データベースに登録します。この方法は単純で、登録は即時に有効になります。「データベースへのパブリッシュ・サブスクライブの直接登録」を参照してください。
オープン登録。Lightweight Directory Access Protocol (LDAP)を使用して登録し、データベースはここから登録要求を受け取ります。これは、クライアントがデータベースに接続できない場合(データベースの停止中にクライアントがデータベースのオープン・イベントに登録する場合)、またはクライアントが複数のデータベース内の同じイベントまたは複数のイベントに同時に登録する場合に役立ちます。「パブリッシュ・サブスクライブのオープン登録」を参照してください。
イベント通知の直接登録および受信を行うには、OCIアプリケーションで次の手順を実行する必要があります。適切なイベント・トリガーまたはAQエンキューがセットアップされていることを前提としています。初期化パラメータCOMPATIBLE
はリリース8.1以上に設定されている必要があります。
注意: パブリッシュ・サブスクライブ機能は、マルチスレッド・オペレーティング・システム上でのみ使用できます。 |
OCI_EVENTS
モードでOCIEnvCreate()またはOCIEnvNlsCreate()をコールし、アプリケーションで通知の登録および受信を行うことを指定します。クライアント上で、通知専用のリスニング・スレッドが起動します。
ハンドル・タイプをOCI_HTYPE_SUBSCRIPTION
に指定してOCIHandleAlloc()をコールし、サブスクリプション・ハンドルを割り当てます。
OCIAttrSet()をコールし、次のサブスクリプション・ハンドル属性を設定します。
OCI_ATTR_SUBSCR_HOSTADDR
- 通信の送信先であるクライアントIP (IPv4またはIPv6書式)を設定する環境ハンドル属性。
Oracle Databaseコンポーネントおよびユーティリティでは、インターネット・プロトコル・バージョン6 (IPv6)アドレスをサポートします。
関連項目: IPアドレスのIPv6書式の詳細は、「OCI_ATTR_SUBSCR_HOSTADDR」、「OCI_ATTR_SUBSCR_IPADDR」および『Oracle Database Net Services管理者ガイド』を参照してください。 |
OCI_ATTR_SUBSCR_TIMEOUT
- 登録のタイムアウト間隔 (秒単位)。タイムアウトが設定されていない場合、デフォルトは0 (ゼロ)です。
OCI_ATTR_SUBSCR_NTFN_GROUPING_CLASS
- 通知グループ化クラス。
次の定数でNTFNグループ化オプションを使用することで、通知の間隔を置くことができます。通知グループ化クラスでサポートされる値は、次のとおりです。
#define OCI_SUBSCR_NTFN_GROUPING_CLASS_TIME 1 /* time */
OCI_ATTR_SUBSCR_NTFN_GROUPING_TYPE
- 通知グループ化タイプ。
通知グループ化タイプでサポートされる値は、次のとおりです。
#define OCI_SUBSCR_NTFN_GROUPING_TYPE_SUMMARY 1 /* summary */ #define OCI_SUBSCR_NTFN_GROUPING_TYPE_LAST 2 /* last */
サブスクリプションを登録する前に、OCI_ATTR_SUBSCR_NAME
、OCI_ATTR_SUBSCR_NAMESPACE
およびOCI_ATTR_SUBSCR_RECPTPROTO
を設定しておく必要があります。
OCI_ATTR_SUBSCR_RECPTPROTO
をOCI_SUBSCR_PROTO_OCI
に設定した場合は、OCI_ATTR_SUBSCR_CALLBACK
およびOCI_ATTR_SUBSCR_CTX
も設定する必要があります。
OCI_ATTR_SUBSCR_RECPTPROTO
をOCI_SUBSCR_PROTO_MAIL
、OCI_SUBSCR_PROTO_SERVER
またはOCI_SUBSCR_PROTO_HTTP
に設定した場合は、OCI_ATTR_SUBSCR_RECPT
も設定する必要があります。
OCI_ATTR_SUBSCR_CALLBACK
とOCI_ATTR_SUBSCR_RECPT
を同時に設定すると、アプリケーション・エラーが発生します。
アプリケーションでサブスクリプションに対する通知の送信を実行するには、OCI_ATTR_SUBSCR_PAYLOAD
が必要です。
関連項目: mode = OCI_EVENTS | OCI_OBJECT による環境の設定については、「サブスクリプション・ハンドル属性」および「OCI環境の作成」を参照してください。OCI_OBJECT は通知のグループ化に必須です。 |
QOS、タイムアウト間隔、ネームスペースおよびポートの値を設定します(例9-23を参照)。
OCI_ATTR_SUBSCR_RECPTPROTO
をOCI_SUBSCR_PROTO_OCI
に設定し、サブスクリプション・ハンドルで使用するコールバック・ルーチンを定義します。
OCI_ATTR_SUBSCR_RECPTPROTO
をOCI_SUBSCR_PROTO_SERVER
に設定し、通知に対してコールするPL/SQLプロシージャをデータベースに定義します。
OCISubscriptionRegister()をコールし、サブスクリプションを登録します。このコールにより、複数のサブスクリプションを同時に登録できます。
例9-23では、QOSレベルの設定の例を示しています。
例9-23 QOSレベル、通知グループ化のクラス、値およびタイプ、ネームスペース固有のコンテキストの設定
/* Set QOS levels */ ub4 qosflags = OCI_SUBSCR_QOS_PAYLOAD; /* Set QOS flags in subscription handle */ (void) OCIAttrSet((dvoid *) subscrhp, (ub4) OCI_HTYPE_SUBSCRIPTION, (dvoid *) &qosflags, (ub4) 0, (ub4) OCI_ATTR_SUBSCR_QOSFLAGS, errhp); /* Set notification grouping class */ ub4 ntfn_grouping_class = OCI_SUBSCR_NTFN_GROUPING_CLASS_TIME; (void) OCIAttrSet((dvoid *) subscrhp, (ub4) OCI_HTYPE_SUBSCRIPTION, (dvoid *) &ntfn_grouping_class, (ub4) 0, (ub4) OCI_ATTR_SUBSCR_NTFN_GROUPING_CLASS, errhp); /* Set notification grouping value of 10 minutes */ ub4 ntfn_grouping_value = 600; (void) OCIAttrSet((dvoid *) subscrhp, (ub4) OCI_HTYPE_SUBSCRIPTION, (dvoid *) &ntfn_grouping_value, (ub4) 0, (ub4) OCI_ATTR_SUBSCR_NTFN_GROUPING_VALUE, errhp); /* Set notification grouping type */ ub4 ntfn_grouping_type = OCI_SUBSCR_NTFN_GROUPING_TYPE_SUMMARY; /* Set notification grouping type in subscription handle */ (void) OCIAttrSet((dvoid *) subscrhp, (ub4) OCI_HTYPE_SUBSCRIPTION, (dvoid *) &ntfn_grouping_type, (ub4) 0, (ub4) OCI_ATTR_SUBSCR_NTFN_GROUPING_TYPE, errhp); /* Set namespace specific context */ (void) OCIAttrSet((dvoid *) subscrhp, (ub4) OCI_HTYPE_SUBSCRIPTION, (dvoid *) NULL, (ub4) 0, (ub4) OCI_ATTR_SUBSCR_NAMESPACE_CTX, errhp);
パブリッシュ・サブスクライブのオープン登録の前提条件は、次のとおりです。
LDAP (オープン登録)を使用する登録では、クライアントがエンタープライズ・ユーザーであることが必要です。
関連項目: 『Oracle Database Advanced Security管理者ガイド』のエンタープライズ・ユーザー・セキュリティの管理に関する項 |
データベースの互換性は、9.0以上であることが必要です。
LDAP_REGISTRATION_ENABLED
をTRUE
に設定する必要があります。次のように設定します。
ALTER SYSTEM SET LDAP_REGISTRATION_ENABLED=TRUE
デフォルトはFALSE
です。
LDAP_REG_SYNC_INTERVAL
をLDAPからの登録をリフレッシュする時間隔(秒単位)に設定します。
ALTER SYSTEM SET LDAP_REG_SYNC_INTERVAL = time_interval
デフォルトは0 (ゼロ)で、この場合リフレッシュは行われません。
データベースでLDAPの登録情報を即時にリフレッシュするには、次のように設定します。
ALTER SYSTEM REFRESH LDAP_REGISTRATION
Oracle Enterprise Security Manager (OESM)を使用したオープン登録の手順は、次のとおりです。
各エンタープライズ・ドメインで、エンタープライズ・ロールのENTERPRISE_AQ_USER_ROLE
を作成します。
エンタープライズ・ドメイン内の各データベースについて、グローバル・ロールのGLOBAL_AQ_USER_ROLE
をエンタープライズ・ロールのENTERPRISE_AQ_USER_ROLE
に追加します。
各エンタープライズ・ドメインについて、エンタープライズ・ロールのENTERPRISE_AQ_USER_ROLE
を、管理コンテキストの下のcn=oraclecontext
の下にある権限グループのcn=OracleDBAQUsers
に追加します。
データベース内のイベントへの登録が承認される各エンタープライズ・ユーザーに対して、エンタープライズ・ロールのENTERPRISE_AQ_USER_ROLE
を付与します。
mode
をOCI_EVENTS
| OCI_USE_LDAP
に設定したOCIEnvCreate()または OCIEnvNlsCreate()をコールします。
OCIAttrSet()をコールし、次の環境ハンドル属性を設定してLDAPにアクセスします。
ハンドル・タイプをOCI_HTYPE_SUBSCRIPTION
に指定してOCIHandleAlloc()をコールし、サブスクリプション・ハンドルを割り当てます。
記述子タイプをOCI_DTYPE_SRVDN
に指定してOCIArrayDescriptorAlloc()をコールし、サーバーのDN記述子を割り当てます。
OCIAttrSet()をコールし、サーバーのDN記述子属性を、クライアントが通知を受信するデータベースの識別名であるOCI_ATTR_SERVER_DN
に設定します。OCIAttrSet()
は、この属性について複数回コールできるため、登録には複数のデータベース・サーバーが含まれます。
OCIAttrSet()をコールし、次のサブスクリプション・ハンドル属性を設定します。
QOS、タイムアウト間隔、ネームスペースおよびポートの値を設定します。「QOS、タイムアウト間隔、ネームスペース、クライアント・アドレスおよびポート番号の設定」を参照してください。
OCISubscriptionRegister()をコールし、サブスクリプションを登録します。データベースがLDAPにアクセスして新規登録を取り出すと、登録が有効になります。取り出す頻度は、LDAP_REG_SYNC_INTERVAL
の値によって決まります。
OCIAttrSet()を使用して、QOSFLAGSを次のQOSレベルに設定できます。
OCI_SUBSCR_QOS_RELIABLE
- 信頼性のある通知は、インスタンスやデータベースが再起動しても持続します。信頼性とは、サーバーの信頼性のことであり、それも永続的なキューやバッファ・メッセージについての信頼性です。このオプションは、通知の永続性を記述します。デフォルトでは、登録は永続的です。
OCI_SUBSCR_QOS_PURGE_ON_NTFN
- 通知を受信すると、最初の通知で登録を削除します。(サブスクリプションの登録は解除されます。)
/* Set QOS levels */ ub4 qosflags = OCI_SUBSCR_QOS_RELIABLE | OCI_SUBSCR_QOS_PURGE_ON_NTFN; /* Set flags in subscription handle */ (void)OCIAttrSet((void *)subscrhp, (ub4)OCI_HTYPE_SUBSCRIPTION, (void *)&qosflags, (ub4)0, (ub4)OCI_ATTR_SUBSCR_QOSFLAGS, errhp); /* Set auto-expiration after 30 seconds */ ub4 timeout = 30; (void)OCIAttrSet((void *)subscrhp, (ub4)OCI_HTYPE_SUBSCRIPTION, (void *)&timeout, (ub4)0, (ub4)OCI_ATTR_SUBSCR_TIMEOUT, errhp);
タイムアウトを過ぎると、登録が削除され、通知がクライアントに送信されるため、クライアントはコールバックを呼び出し、必要なアクションを実行できるようになります。タイムアウト前にクライアントに障害が発生した場合、登録は消去されます。
環境ハンドルにはポート番号を設定でき、クライアントがファイアウォールの背後のシステム上にあり、特定のポートの通知のみを受信できる場合に、この番号が重要になります。クライアントは、環境ハンドルの属性を使用して、最初の登録の前にリスナー・スレッドのポートを指定できます。このスレッドは、OCISubscriptionRegister()の初回のコール時に開始されます。指定されたこのポート番号が使用可能な場合、この番号が使用されます。クライアントが異なる環境ハンドルを使用して異なるポートで別のスレッドを開始しようとすると、エラーが戻されます。
ub4 port = 1581; (void)OCIAttrSet((void *)envhp, (ub4)OCI_HTYPE_ENV, (void *)&port, (ub4)0, (ub4)OCI_ATTR_SUBSCR_PORTNO, errhp);
これとは異なりポートが自動的に決定される場合は、環境ハンドルから属性を取得することにより、クライアント・スレッドが通知をリスニングしているポート番号を取得できます。
(void)OCIAttrGet((void *)subhp, (ub4)OCI_HTYPE_ENV, (void *)&port, (ub4)0, (ub4)OCI_ATTR_SUBSCR_PORTNO, errhp);
クライアント・アドレス設定の例
text ipaddr[16] = "10.177.246.40"; (void)(OCIAttrSet((dvoid *) envhp, (ub4) OCI_HTYPE_ENV, (dvoid *) ipaddr, (ub4) strlen((const char *)ipaddr), (ub4) OCI_ATTR_SUBSCR_IPADDR, errhp));
表9-13は、パブリッシュ・サブスクライブ通知に使用される関数のリストです。
クライアントは、登録したサブスクリプションに対してアクティビティが発生したときにコールされる、通知コールバックを登録する必要があります。たとえば、AQネームスペースでは、目的のメッセージがエンキューされたときに通知コールバックが発生します。
このコールバックは通常、サブスクリプション・ハンドルのOCI_ATTR_SUBSCR_CALLBACK
属性によって設定されます。
通知コールバックからは、OCI_CONTINUE
値が戻される必要があります。また、次の仕様に準拠している必要があります。
typedef ub4 (*OCISubscriptionNotify) ( void *pCtx, OCISubscription *pSubscrHp, void *pPayload, ub4 iPayloadLen, void *pDescriptor, ub4 iMode);
パラメータは次のように記述します。
コールバックが登録されたときに指定されたユーザー定義コンテキスト。
コールバックが登録されたときに指定されたサブスクリプション・ハンドル。
この通知のペイロード。現在のところ、ペイロードのub1 * (バイト・シーケンス)のみがサポートされています。
この通知のペイロードの長さ。
ネームスペース固有の記述子。この記述子からネームスペース指定のパラメータを抽出できます。この記述子の構造はユーザーには不透明で、型はネームスペースによって異なります。
記述子の属性は、ネームスペース固有です。アドバンスト・キューイング(AQ)の場合、記述子はOCI_DTYPE_AQNFY
です。AQネームスペースの場合、グループで受信する通知の数は通知記述子に提供されます。pDescriptor
の属性は次のとおりです。
コール固有モードです。有効な値はOCI_DEFAULT
のみです。この値は、デフォルトのコールを実行します。
例9-24では、通知コールバックでのAQグループ化通信属性の使用方法を示しています。
例9-24 OCI通知コールバックでのAQグループ化通信属性の使用
ub4 notifyCB1(void *ctx, OCISubscription *subscrhp, void *pay, ub4 payl, void *desc, ub4 mode) { oratext *subname; ub4 size; OCIColl *msgid_array = (OCIColl *)0; ub4 msgid_cnt = 0; OCIRaw *msgid; void **msgid_ptr; sb4 num_msgid = 0; void *elemind = (void *)0; boolean exist; ub2 flags; oratext *hexit = (oratext *)"0123456789ABCDEF"; ub4 i, j; /* get subscription name */ OCIAttrGet(subscrhp, OCI_HTYPE_SUBSCRIPTION, (void *)&subname, &size, OCI_ATTR_SUBSCR_NAME,ctxptr->errhp); /* print subscripton name */ printf("Got notification for %.*s\n", size, subname); fflush((FILE *)stdout); /* get the #ntfns received in this group */ OCIAttrGet(desc, OCI_DTYPE_AQNFY, (void *)&msgid_cnt, &size, OCI_ATTR_AQ_NTFN_GROUPING_COUNT, ctxptr->errhp); /* get the group - collection of msgids */ OCIAttrGet(desc, OCI_DTYPE_AQNFY, (void *)&msgid_array, &size, OCI_ATTR_AQ_NTFN_GROUPING_MSGID_ARRAY, ctxptr->errhp); /* get notification flag - regular, timeout, or grouping notification? */ OCIAttrGet(desc, OCI_DTYPE_AQNFY, (void *)&flags, &size, OCI_ATTR_NFY_FLAGS, ctxptr->errhp); /* print notification flag */ printf("Flag: %d\n", (int)flags); /* get group (collection) size */ if (msgid_array) checkerr(ctxptr->errhp, OCICollSize(ctxptr->envhp, ctxptr->errhp, CONST OCIColl *) msgid_array, &num_msgid), "Inside notifyCB1-OCICollSize"); else num_msgid =0; /* print group size */ printf("Collection size: %d\n", num_msgid); /* print all msgids in the group */ for(i = 0; i < num_msgid; i++) { ub4 rawSize; /* raw size */ ub1 *rawPtr; /* raw pointer */ /* get msgid from group */ checkerr(ctxptr->errhp, OCICollGetElem(ctxptr->envhp, ctxptr->errhp, (OCIColl *) msgid_array, i, &exist, (void **)(&msgid_ptr), &elemind), "Inside notifyCB1-OCICollGetElem"); msgid = *msgid_ptr; rawSize = OCIRawSize(ctxptr->envhp, msgid); rawPtr = OCIRawPtr(ctxptr->envhp, msgid); /* print msgid size */ printf("Msgid size: %d\n", rawSize); /* print msgid in hexadecimal format */ for (j = 0; j < rawSize; j++) { /* for each byte in the raw */ printf("%c", hexit[(rawPtr[j] & 0xf0) >> 4]); printf("%c", hexit[(rawPtr[j] & 0x0f)]); } printf("\n"); } /* print #ntfns received in group */ printf("Notification Count: %d\n", msgid_cnt); printf("\n"); printf("***********************************************************\n"); fflush((FILE *)stdout); return 1; }
実行が登録されているサブスクリプションでなんらかのアクティビティが発生したときにコールするPL/SQLプロシージャは、データベース内に作成する必要があります。
このプロシージャは通常、サブスクリプション・ハンドルのOCI_ATTR_SUBSCR_RECPT
属性によって設定されます。
関連項目:
|
例9-25では、システム・イベント、クライアント通知およびアドバンスト・キューイングが連動して、パブリッシュ・サブスクライブ通知を実装する方法を示しています。
例9-25のPL/SQLコードでは、ユーザー・スキーマpubsub
環境で公開サブスクライブ・メカニズムをサポートするために必要なすべてのオブジェクトを作成します。このコードでは、エージェントsnoop
が、ログイン・イベント時にパブリッシュされるメッセージをサブスクライブします。ユーザーpubsub
には、アドバンスト・キューイング機能を使用するためにAQ_ADMINISTRATOR_ROLE
およびAQ_USER_ROLE
の権限が必要です。システム・イベントのトリガーを有効にするには、初期化パラメータ_SYSTEM_TRIG_ENABLED
をTRUE
(デフォルト)に設定する必要があります。 例9-25を実行する前に、pubsub
として接続します。
例9-25 パブリッシュ・サブスクライブ通知の実装
---------------------------------------------------------- ----create queue table for persistent multiple consumers ---------------------------------------------------------- ---- Create or replace a queue table begin DBMS_AQADM.CREATE_QUEUE_TABLE( QUEUE_TABLE=>'pubsub.raw_msg_table', MULTIPLE_CONSUMERS => TRUE, QUEUE_PAYLOAD_TYPE =>'RAW', COMPATIBLE => '8.1.5'); end; / ---------------------------------------------------------- ---- Create a persistent queue for publishing messages ---------------------------------------------------------- ---- Create a queue for logon events begin DBMS_AQADM.CREATE_QUEUE(QUEUE_NAME=>'pubsub.logon', QUEUE_TABLE=>'pubsub.raw_msg_table', COMMENT=>'Q for error triggers'); end; / ---------------------------------------------------------- ---- Start the queue ---------------------------------------------------------- begin DBMS_AQADM.START_QUEUE('pubsub.logon'); end; / ---------------------------------------------------------- ---- define new_enqueue for convenience ---------------------------------------------------------- create or replace procedure new_enqueue(queue_name in varchar2, payload in raw , correlation in varchar2 := NULL, exception_queue in varchar2 := NULL) as enq_ct dbms_aq.enqueue_options_t; msg_prop dbms_aq.message_properties_t; enq_msgid raw(16); userdata raw(1000); begin msg_prop.exception_queue := exception_queue; msg_prop.correlation := correlation; userdata := payload; DBMS_AQ.ENQUEUE(queue_name,enq_ct, msg_prop,userdata,enq_msgid); end; / ---------------------------------------------------------- ---- add subscriber with rule based on current user name, ---- using correlation_id ---------------------------------------------------------- declare subscriber sys.aq$_agent; begin subscriber := sys.aq$_agent('SNOOP', null, null); dbms_aqadm.add_subscriber(queue_name => 'pubsub.logon', subscriber => subscriber, rule => 'CORRID = ''ix'' '); end; / ---------------------------------------------------------- ---- create a trigger on logon on database ---------------------------------------------------------- ---- create trigger on after logon create or replace trigger systrig2 AFTER LOGON ON DATABASE begin new_enqueue('pubsub.logon', hextoraw('9999'), dbms_standard.login_user); end; / ---------------------------------------------------------- ---- create a PL/SQL callback for notification of logon ---- of user 'ix' on database ---------------------------------------------------------- ---- create or replace procedure plsqlnotifySnoop( context raw, reginfo sys.aq$_reg_info, descr sys.aq$_descriptor, payload raw, payloadl number) as begin dbms_output.put_line('Notification : User ix Logged on\n'); end; /
サブスクリプションが作成された後、クライアントはコールバック関数を使用して通知を登録する必要があります。例9-26では、登録に必要なステップを実行するサンプル・コードを示しています。ここでは、わかりやすくするために、セッション・ハンドルの割当ておよび初期化のステップを省略しています。
例9-26 コールバック関数を使用する通知の登録
... static ub4 namespace = OCI_SUBSCR_NAMESPACE_AQ; static OCISubscription *subscrhpSnoop = (OCISubscription *)0; static OCISubscription *subscrhpSnoopMail = (OCISubscription *)0; static OCISubscription *subscrhpSnoopServer = (OCISubscription *)0; /* callback function for notification of logon of user 'ix' on database */ static ub4 notifySnoop(ctx, subscrhp, pay, payl, desc, mode) void *ctx; OCISubscription *subscrhp; void *pay; ub4 payl; void *desc; ub4 mode; { printf("Notification : User ix Logged on\n"); (void)OCIHandleFree((void *)subscrhpSnoop, (ub4) OCI_HTYPE_SUBSCRIPTION); return 1; } static void checkerr(errhp, status) OCIError *errhp; sword status; { text errbuf[512]; ub4 buflen; sb4 errcode; if (status == OCI_SUCCESS) return; switch (status) { case OCI_SUCCESS_WITH_INFO: printf("Error - OCI_SUCCESS_WITH_INFO\n"); break; case OCI_NEED_DATA: printf("Error - OCI_NEED_DATA\n"); break; case OCI_NO_DATA: printf("Error - OCI_NO_DATA\n"); break; case OCI_ERROR: OCIErrorGet ((void *) errhp, (ub4) 1, (text *) NULL, &errcode, errbuf, (ub4) sizeof(errbuf), (ub4) OCI_HTYPE_ERROR); printf("Error - %s\n", errbuf); break; case OCI_INVALID_HANDLE: printf("Error - OCI_INVALID_HANDLE\n"); break; case OCI_STILL_EXECUTING: printf("Error - OCI_STILL_EXECUTING\n"); break; case OCI_CONTINUE: printf("Error - OCI_CONTINUE\n"); break; default: printf("Error - %d\n", status); break; } } static void initSubscriptionHn (subscrhp, subscriptionName, func, recpproto, recpaddr, recppres) OCISubscription **subscrhp; char * subscriptionName; void * func; ub4 recpproto; char * recpaddr; ub4 recppres; { /* allocate subscription handle */ (void) OCIHandleAlloc((void *) envhp, (void **)subscrhp, (ub4) OCI_HTYPE_SUBSCRIPTION, (size_t) 0, (void **) 0); /* set subscription name in handle */ (void) OCIAttrSet((void *) *subscrhp, (ub4) OCI_HTYPE_SUBSCRIPTION, (void *) subscriptionName, (ub4) strlen((char *)subscriptionName), (ub4) OCI_ATTR_SUBSCR_NAME, errhp); /* set callback function in handle */ if (func) (void) OCIAttrSet((void *) *subscrhp, (ub4) OCI_HTYPE_SUBSCRIPTION, (void *) func, (ub4) 0, (ub4) OCI_ATTR_SUBSCR_CALLBACK, errhp); /* set context in handle */ (void) OCIAttrSet((void *) *subscrhp, (ub4) OCI_HTYPE_SUBSCRIPTION, (void *) 0, (ub4) 0, (ub4) OCI_ATTR_SUBSCR_CTX, errhp); /* set namespace in handle */ (void) OCIAttrSet((void *) *subscrhp, (ub4) OCI_HTYPE_SUBSCRIPTION, (void *) &namespace, (ub4) 0, (ub4) OCI_ATTR_SUBSCR_NAMESPACE, errhp); /* set receive with protocol in handle */ (void) OCIAttrSet((void *) *subscrhp, (ub4) OCI_HTYPE_SUBSCRIPTION, (void *) &recpproto, (ub4) 0, (ub4) OCI_ATTR_SUBSCR_RECPTPROTO, errhp); /* set recipient address in handle */ if (recpaddr) (void) OCIAttrSet((void *) *subscrhp, (ub4) OCI_HTYPE_SUBSCRIPTION, (void *) recpaddr, (ub4) strlen(recpaddr), (ub4) OCI_ATTR_SUBSCR_RECPT, errhp); /* set receive with presentation in handle */ (void) OCIAttrSet((void *) *subscrhp, (ub4) OCI_HTYPE_SUBSCRIPTION, (void *) &recppres, (ub4) 0, (ub4) OCI_ATTR_SUBSCR_RECPTPRES, errhp); printf("Begining Registration for subscription %s\n", subscriptionName); checkerr(errhp, OCISubscriptionRegister(svchp, subscrhp, 1, errhp, OCI_DEFAULT)); printf("done\n"); } int main( argc, argv) int argc; char * argv[]; { OCISession *authp = (OCISession *) 0; /***************************************************** Initialize OCI Process/Environment Initialize Server Contexts Connect to Server Set Service Context ******************************************************/ /* Registration Code Begins */ /* Each call to initSubscriptionHn allocates and initializes a Registration Handle */ /* Register for OCI notification */ initSubscriptionHn( &subscrhpSnoop, /* subscription handle*/ (char*) "PUBSUB.LOGON:SNOOP", /* subscription name */ /*<queue_name>:<agent_name> */ (void*)notifySnoop, /* callback function */ OCI_SUBSCR_PROTO_OCI, /* receive with protocol */ (char *)0, /* recipient address */ OCI_SUBSCR_PRES_DEFAULT); /* receive with presentation */ /* Register for email notification */ initSubscriptionHn( &subscrhpSnoopMail, /* subscription handle */ (char*) "PUBSUB.LOGON:SNOOP", /* subscription name */ /* <queue_name>:<agent_name> */ (void*)0, /* callback function */ OCI_SUBSCR_PROTO_MAIL, /* receive with protocol */ (char*) "xyz@company.com", /* recipient address */ OCI_SUBSCR_PRES_DEFAULT); /* receive with presentation */ /* Register for server to server notification */ initSubscriptionHn( &subscrhpSnoopServer, /* subscription handle */ (char*) "PUBSUB.LOGON:SNOOP", /* subscription name */ /* <queue_name>:<agent_name> */ (void*)0, /* callback function */ OCI_SUBSCR_PROTO_SERVER, /* receive with protocol */ (char*) "pubsub.plsqlnotifySnoop", /* recipient address */ OCI_SUBSCR_PRES_DEFAULT); /* receive with presentation */ checkerr(errhp, OCITransCommit(svchp, errhp, (ub4) OCI_DEFAULT)); /***************************************************** The Client Process does not need a live Session for Callbacks. End Session and Detach from Server. ******************************************************/ OCISessionEnd ( svchp, errhp, authp, (ub4) OCI_DEFAULT); /* detach from server */ OCIServerDetach( srvhp, errhp, OCI_DEFAULT); while (1) /* wait for callback */ sleep(1); }
ユーザーIX
がデータベースにログインした場合、クライアントは電子メールで通知を受け取り、コールバック関数notifySnoop
がコールされます。電子メール通知は、アドレスxyz@company.comに送信され、PL/SQLプロシージャplsqlnotifySnoop
もデータベースでコールされます。
例9-27では、LDAP登録の方法を説明するコード・フラグメントを示しています。プログラム・コメントはすべて目を通してください。
例9-27 LDAP登録
... /* To use the LDAP registration feature, OCI_EVENTS | OCI_EVENTS |OCI_USE_LDAP*/ /* must be set in OCIEnvCreate or OCIEnvNlsCreate */ /* (Note: OCIInitialize is deprecated): */ (void) OCIInitialize((ub4) OCI_EVENTS|OCI_OBJECT|OCI_USE_LDAP, (void *)0, (void * (*)(void *, size_t)) 0, (void * (*)(void *, void *, size_t))0, (void (*)(void *, void *)) 0 ); ... /* set LDAP attributes in the environment handle */ /* LDAP host name */ (void) OCIAttrSet((void *)envhp, OCI_HTYPE_ENV, (void *)"yow", 3, OCI_ATTR_LDAP_HOST, (OCIError *)errhp); /* LDAP server port */ ldap_port = 389; (void) OCIAttrSet((void *)envhp, OCI_HTYPE_ENV, (void *)&ldap_port, (ub4)0, OCI_ATTR_LDAP_PORT, (OCIError *)errhp); /* bind DN of the client, normally the enterprise user name */ (void) OCIAttrSet((void *)envhp, OCI_HTYPE_ENV, (void *)"cn=orcladmin", 12, OCI_ATTR_BIND_DN, (OCIError *)errhp); /* password of the client */ (void) OCIAttrSet((void *)envhp, OCI_HTYPE_ENV, (void *)"welcome", 7, OCI_ATTR_LDAP_CRED, (OCIError *)errhp); /* authentication method is "simple", username/password authentication */ ldap_auth = 0x01; (void) OCIAttrSet((void *)envhp, OCI_HTYPE_ENV, (void *)&ldap_auth, (ub4)0, OCI_ATTR_LDAP_AUTH, (OCIError *)errhp); /* administrative context: this is the DN above cn=oraclecontext */ (void) OCIAttrSet((void *)envhp, OCI_HTYPE_ENV, (void *)"cn=acme,cn=com", 14, OCI_ATTR_LDAP_CTX, (OCIError *)errhp); ... /* retrieve the LDAP attributes from the environment handle */ /* LDAP host */ (void) OCIAttrGet((void *)envhp, OCI_HTYPE_ENV, (void *)&buf, &szp, OCI_ATTR_LDAP_HOST, (OCIError *)errhp); /* LDAP server port */ (void) OCIAttrGet((void *)envhp, OCI_HTYPE_ENV, (void *)&intval, 0, OCI_ATTR_LDAP_PORT, (OCIError *)errhp); /* client binding DN */ (void) OCIAttrGet((void *)envhp, OCI_HTYPE_ENV, (void *)&buf, &szp, OCI_ATTR_BIND_DN, (OCIError *)errhp); /* client password */ (void) OCIAttrGet((void *)envhp, OCI_HTYPE_ENV, (void *)&buf, &szp, OCI_ATTR_LDAP_CRED, (OCIError *)errhp); /* administrative context */ (void) OCIAttrGet((void *)envhp, OCI_HTYPE_ENV, (void *)&buf, &szp, OCI_ATTR_LDAP_CTX, (OCIError *)errhp); /* client authentication method */ (void) OCIAttrGet((void *)envhp, OCI_HTYPE_ENV, (void *)&intval, 0, OCI_ATTR_LDAP_AUTH, (OCIError *)errhp); ... /* to set up the server DN descriptor in the subscription handle */ /* allocate a server DN descriptor, dn is of type "OCIServerDNs **", subhp is of type "OCISubscription **" */ (void) OCIDescriptorAlloc((void *)envhp, (void **)dn, (ub4) OCI_DTYPE_SRVDN, (size_t)0, (void **)0); /* now *dn is the server DN descriptor, add the DN of the first database that you want to register */ (void) OCIAttrSet((void *)*dn, (ub4) OCI_DTYPE_SRVDN, (void *)"cn=server1,cn=oraclecontext,cn=acme,cn=com", 42, (ub4)OCI_ATTR_SERVER_DN, errhp); /* add the DN of another database in the descriptor */ (void) OCIAttrSet((void *)*dn, (ub4) OCI_DTYPE_SRVDN, (void *)"cn=server2,cn=oraclecontext,cn=acme,cn=com", 42, (ub4)OCI_ATTR_SERVER_DN, errhp); /* set the server DN descriptor into the subscription handle */ (void) OCIAttrSet((void *) *subhp, (ub4) OCI_HTYPE_SUBSCRIPTION, (void *) *dn, (ub4)0, (ub4) OCI_ATTR_SERVER_DNS, errhp); ... /* now you will try to get the server DN information from the subscription handle */ /* first, get the server DN descriptor out */ (void) OCIAttrGet((void *) *subhp, (ub4) OCI_HTYPE_SUBSCRIPTION, (void *)dn, &szp, OCI_ATTR_SERVER_DNS, errhp); /* then, get the number of server DNs in the descriptor */ (void) OCIAttrGet((void *) *dn, (ub4)OCI_DTYPE_SRVDN, (void *)&intval, &szp, (ub4)OCI_ATTR_DN_COUNT, errhp); /* allocate an array of char * to hold server DN pointers returned by an Oracle database*/ if (intval) { arr = (char **)malloc(intval*sizeof(char *)); (void) OCIAttrGet((void *)*dn, (ub4)OCI_DTYPE_SRVDN, (void *)arr, &intval, (ub4)OCI_ATTR_SERVER_DN, errhp); } /* OCISubscriptionRegister() calls have two modes: OCI_DEFAULT and OCI_REG_LDAPONLY. If OCI_DEFAULT is used, there should be only one server DN in the server DN descriptor. The registration request will be sent to the database. If a database connection is not available, the registration request will be detoured to the LDAP server. However, if mode OCI_REG_LDAPONLY is used, the registration request will be directly sent to LDAP. This mode should be used when there is more than one server DN in the server DN descriptor or you are sure that a database connection is not available. In this example, two DNs are entered, so you should use mode OCI_REG_LDAPONLY in LDAP registration. */ OCISubscriptionRegister(svchp, subhp, 1, errhp, OCI_REG_LDAPONLY); ... /* as OCISubscriptionRegister(), OCISubscriptionUnregister() also has mode OCI_DEFAULT and OCI_REG_LDAPONLY. The usage is the same. */ OCISubscriptionUnRegister(svchp, *subhp, errhp, OCI_REG_LDAPONLY); } ...