Site Logo

IzzySoft


{itemlist}
 

Listener & Services konfigurieren und nutzen

Listener und Services ergeben interessante Kombinationen. Werfen wir einmal einen Blick auf die Kombinationen!

Inhalt

Listener

Das dürfte auch für den angehenden DBA keine Neuigkeit sein: Anwendungen benötigen einen Listener, um auf die Datenbank zugreifen zu können. Die allgemeine Beschreibung und Arbeitsweise eines Listeners möchte ich daher hier überspringen, und gleich zum (hoffentlich) interessanten Teil übergehen.

Einfacher Listener

Ein einfacher Listener benötigt nicht einmal eine Konfiguration. Er muss lediglich gestartet werden (lsnrctl start), und schon ist er verfügbar. In ihrer Standard-Konfiguration wird er von einer auf dem gleichen Server laufenden Datenbank auch innerhalb einer Minute gefunden, und sie wird sich bei ihm anmelden (wer nicht so lange warten möchte: ein als SYSDBA abgesetztes alter system register; führt diese Anmeldung sofort durch) – und schon kann die Anwendung lokal oder über das Netzwerk sich mit der Datenbank verbinden.

Anmerkung: Wonach die Datenbank dabei sucht ist ein auf der gleichen Maschine (Hostname, IP-Adresse) laufender Listener, der auf Port 1521 lauscht – was sich prinzipiell so beschreiben ließe:

(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=$HOSTNAME)(PORT=1521)))

Ein einzelner Listener kann mehrere Datenbanken versorgen. In einem einfachen Setup ist das auch alles, was man braucht: ein einziger Listener genügt. Man benötigt nicht einmal eine listener.ora als Konfigurationsdatei.

Statischer Listener

Mit obigem „einfachen Setup“ würde sich eine Datenbank zwar am Listener anmelden – aber nur, wenn sie geöffnet ist. Für eine normale Anwendung genügt dies zwar vollauf (sie könnte sich zu einer nicht gestarteten Datenbank ohnehin nicht verbinden). Aber ein DBA müsste sich somit direkt am Server anmelden, um die Datenbank zu starten. Und der Data Guard Broker könnte keinen Switchover durchführen: sobald die Datenbank heruntergefahren wäre (oder sich lediglich im MOUNT Status befände), wäre ihm eine Anmeldung unmöglich. Daher könnte er selbige auch nicht überwachen. Dafür braucht es in der listener.ora einen Eintrag, der die Datenbank explizit beschreibt. Eine einfache listener.ora dafür könnte etwa wie folgt aussehen:

LISTENER = (
  DESCRIPTION_LIST = (
    DESCRIPTION =
      (ADDRESS = (PROTOCOL = TCP)(HOST = myhost.example.com)(PORT = 1521))
      (ADDRESS = (PROTOCOL = IPC)(KEY = EXTPROC1521))
    )
)

SID_LIST_LISTENER= (
  SID_LIST= (
   (SID_DESC=
    (GLOBAL_DBNAME=MYDB)
    (SID_NAME=MYDB)
   )
)

Vorausgesetzt in der tnsnames.ora findet sich ein korrespondierender Eintrag, kann man sich nun als SYSDBA auch über das Netzwerk anmelden, selbst wenn die Datenbank heruntergefahren ist:

$ sqlplus sys@mydb as sysdba

Der wichtige Teil hier ist as sysdba. Ohne SYSDBA-Rechte kann sich niemand an einer nicht geöffneten Datenbank anmelden.1

Und was ist mit den anderen Datenbanken auf dem selben Server, die sich zuvor automatisch beim Listener registriert haben? Die machen das weiterhin. Zumindest solange der Listener weiterhin LISTENER heißt und auf Port 1521 läuft..

Ein statischer Eintrag für eine Datenbank in der SID_LIST_* des Listeners ermöglicht es einem SYSDBA sich remote zu einer Datenbank verbinden, selbst wenn diese nicht geöffnet ist – sogar, wenn sie komplett gestoppt wurde (shutdown). Alle anderen Datenbanken und Services funktionieren weiter wie gehabt, registrieren sich also beispielsweise weiterhin automatisch beim Listener.

Listener auf spezifischen Ports

Warum sollte man das tun? Ist der Default-Port nicht in Ordnung? Kurze Antwort: Ist er – und sofern man keinen vernünftigen Anlass hat gibt es auch keinen Grund, daran etwas zu ändern. Das vereinfacht Administration und Wartung.

Was könnten jetzt gute Gründe sein?

Wenn dies gewünscht ist – schauen wir uns einmal an, wie es umgesetzt wird. Beginnen wir mit der listener.ora und konfigurieren unseren speziellen Listener auf Port 1621:

APPLLIST = (
  DESCRIPTION_LIST =
    (DESCRIPTION =
      (ADDRESS = (PROTOCOL = TCP)(HOST = server.example.com)(PORT = 1621))
    )
)

Datei speichern, dann den neuen Listener starten:

$ lsnrctl start appllist

Jetzt müssen wir die Datenbank dazu bringen, sich bei dem neuen Listener zu registrieren. Dies geschieht mit dem Befehl alter system set local_listener. Anders als man jetzt vielleicht denken würde reicht es aber nicht aus, dabei nur den Namen des Listeners zu übergeben; Oracle würde das zwar akzeptieren, es wäre jedoch wirkungslos. Obwohl es ein Leichtes sein sollte, den auf dem gleichen Rechner laufenden Listener (local_listener) ausfindig zu machen besteht die Datenbank darauf, dass der Name über die tnsnames.ora (oder einen entsprechenden Namensdienst) auflösbar ist. Alternativ kann man jedoch einfach den kompletten „Connection Descriptor“ übergeben:

ALTER SYSTEM SET LOCAL_LISTENER=
'(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=server.example.com)(PORT=1621)))';

Soll die Datenbank sich bei beiden Listenern registrieren (also unserem obigen einfachen Listener auf Port 1521 (der von der Firewall blockiert wird) und dem speziellen auf Port 1621), ist auch das möglich:

ALTER SYSTEM SET LOCAL_LISTENER=
'(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=server.example.com)(PORT=1521)))',
'(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=server.example.com)(PORT=1621)))';

Hat man jetzt ein Wartungsfenster, bei dem man innerhalb des Datenbank-Segments über den Listener auf Port 1521 auf die Datenbanken zugreifen, aber einen Zugriff der Anwendung unterbinden will – kann man einfach den speziellen, auf Port 1621 lauschenden Listener herunterfahren.

Ein Listener lässt sich für beliebige (freie) Ports konfigurieren, und eine Datenbank kann sich gleichzeitig bei mehreren Listenern registrieren. Das erfordert jedoch, dass man entweder den speziellen Listener auch in der tnsnames.ora hinterlegt – oder dessen vollständigen „Connection Descriptor“ als local_listener in der Datenbank selbst einträgt. Die dritte Möglichkeit wäre, einen statischen Eintrag für die Datenbank in der listener.ora zu erstellen.

Was wäre jetzt so schlimm daran, mit einem statischen Eintrag zu arbeiten? Der scheint doch am einfachsten einzurichten zu sein. Schon. Aber er würde dann auch bei heruntergefahrener oder im MOUNT Status befindlichen Datenbank einer anfragenden Anwendung mitteilen, dass es die Datenbank gibt – sich die Anwendung dorthin jedoch nicht verbinden darf („Shutdown or initialization in progress“). Nicht jede Anwendung kommt damit klar – speziell bei Verwendung von Load-Balancing oder Hochverfügbarkeit. Websphere spielt dann beispielsweise die „beleidigte Leberwurst“ und bleibt stur vor der geschlossenen Tür stehen („dann warte ich halt, bis jemand aufmacht!“) – anstatt den nächsten Server seiner Liste zu fragen.

Eine andere Möglichkeit, mit derartigen Sturköpfen umzugehen, wären …

Services

Einen Service kann man sich in diesem Kontext vereinfacht als einen alternativen Namen vorstellen, auf den die Datenbank zusätzlich hört – und den man separat verwalten kann. Über diesen können auch Verbindungen zur Datenbank aufgebaut werden, da sich ein Service ebenfalls beim Listener registriert. Man muss jedoch nicht gleich die ganze Datenbank (oder den Listener) herunterfahren, um diese Verbindungen wieder loszuwerden: Man kann sich einfach des Services (und der „daran hängenden“ Verbindungen) entledigen. Und mehr. Einige möglich Anwendungsfälle wären:

Es gibt sicherlich weitere, doch dies soll für einen groben Überblick reichen.

Unterschiede

Es gibt verschiedene Arten von Services. Ich werde mich hier auf zwei davon konzentrieren – darauf basierend wie sie definiert sind und konfiguriert werden:

ALTER SYSTEM SET services_names='MYDB_SERV1,MYDB_SERV2';

Damit definiert man zwei Services: MYDB_SERV1 und MYDB_SERV2. Der Listener wird beide anzeigen – unabhängig davon, in welchem open_mode sich die Datenbank befindet. Um sie wieder „unsichtbar“ zu machen, müssen sie komplett entfernt werden. Will man dies dynamisch tun, benötigt man dafür immer ALTER SYSTEM Rechte (sofern man sich dafür keine „Wrapper-Prozedur“ schreibt). Gleiches gilt, will man diese Aufgabe delegieren – damit ein anderer Anwender (oder Anwendungs-Prozess) den Service aktivieren und entfernen kann. Aber es ist natürlich sehr einfach einzurichten.

begin
  dbms_service.create_service(
    'MYDB_SERV1',
    'MYDB_SERV1'
  );
  dbms_service.start_service('MYDB_SERV1');
  dbms_service.disconnect_session('MYDB_SERV1',1); -- 0: POST_TRANSACTION, 1: IMMEDIATE, 2: NO_REPLAY
  dbms_service.stop_service('MYDB_SERV1');
end;
/

Dieser Service wäre nur verfügbar, wenn die Datenbank geöffnet wurde (bis hin zu MOUNT lässt sich dbms_service nicht aufrufen, da es nicht gefunden würde). Des Weiteren, will man die Verwaltung des Services delegieren, bedarf es dafür lediglich eines GRANT EXECUTE ON dbms_services zum entsprechenden User. Ein weiteres Plus: Alle über diesen Service angemeldeten Sessions lassen sich einfach auf einen Schlag beenden, ohne dass man sie erst mühsam heraussuchen (oder die Datenbank durchstarten) müsste – und um dies für Sessions des Services MYDB_SERV1 zu tun, braucht man nicht um die von MYDB_SERV2 besorgt zu sein. Besonders praktisch, wenn man etwa ein Schema-Upgrade für Anwendung-1 durchführen möchte (wobei keine der zugehörigen Anwendungs-Sessions mit selbigem verbunden sein darf), während Anwendung-2 normal weiterlaufen soll.

exec dbms_service.disconnect_session('MYDB_SERV1',1)

Anwendungsfall: Standby DB

Jetzt fassen wir obiges einmal zu einem schönen Anwendungsfall zusammen:

Sagen wir, wir haben eine Primär- und eine Standby-Datenbank. Normalerweise ist die Standby-DB genau das: eine Standby-DB, im Modus MOUNTED. Hin und wieder will man aber lastintensives Reporting (mit reinem Lesezugriff) auf die Standby-DB auslagern, um die Primärdatenbank (mit der Real-Time Anwendung) zu entlasten – wofür man sie im Lesemodus öffnet.

Es ist wahrscheinlich schon erkennbar, worauf ich abziele:

Der Teil mit dem Listener ist einfach und wurde bereits oben beschrieben. Da die Services sich automatisch mit selbigem Verbinden, sobald sie laufen, gibt es auch diesbezüglich nichts weiter zu tun. Definieren wir also unsere Services:

begin
  dbms_service.create_service('SERV_PROD','SERV_PROD');
  dbms_service.create_service('SERV_REPORT','SERV_REPORT');
end;
/

Das war einfach. Aber jetzt laufen beide Services auf beiden Datenbanken (sofern die Standby gerade READ ONLY WITH APPLY war) – das war so nicht geplant. Initial müssen wir also den jeweils unerwünschten Service einmal manuell beenden. Um zukünftige (Nicht-) Starts soll sich ein Trigger kümmern:

create or replace trigger service_trigger
after startup on database
begin
  if sys_context('userenv','database_role') = 'PRIMARY'
  then
    dbms_service.start_service('SERV_PROD');
    dbms_service.stop_service('SERV_REPORT');
  else
    dbms_service.stop_service('SERV_PROD');
    dbms_service.start_service('SERV_REPORT');
  end if;
end;
/

Das war's: Sobald man die Primärdatenbank hochfährt, ist dort der Service SERV_PROD verfügbar – nicht jedoch SERV_REPORT; startet man die Standby als READ ONLY [WITH APPLY], wird SERV_REPORT gestartet, nicht jedoch SERV_PROD. Und wenn die Standby mit startup mount hochgefahren wird? Habe ich da nicht etwas vergessen? Nope. Im Status MOUNT lässt sich das Package dbms_service gar nicht ansprechen, wie oben bereits erwähnt – es wird also keiner der beiden Services gestartet. Genau wie gewünscht.


  1. das ist natürlich stark vereinfacht – es gibt weitere Rollen mit dieser Möglichkeit, z. B. SYSDG ↩︎

2020-11-18