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.oraals 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?
- Sicherheit: Es handelt sich um einen bekannten Port. Ja, schon. Wenn das der einzige Grund ist, wäre das dann aber „Security by Obscurity“. Es gibt schließlich auch Port Scanner.
- Sicherheit: Trennung der Datenbanken/Anwendungen. Das kann ein valider Grund sein: bei mehreren Datenbanken auf dem selben Server den Anwendungs-Zugriff per Firewall regeln, sodass jede Anwendung nur auf ihre eigene Datenbank zugreifen kann.
- Annehmlichkeit: Den Port an die Telefon-Durchwahl des DBAs anpassen. OK… Ein Beispiel aus dem wirklichen Leben – aber: Kein Kommentar :)
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.orahinterlegt – oder dessen vollständigen „Connection Descriptor“ alslocal_listenerin der Datenbank selbst einträgt. Die dritte Möglichkeit wäre, einen statischen Eintrag für die Datenbank in derlistener.orazu 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 …
Weiterführende Links
- Registering the Oracle database with the listener (Blog)
- Configuring and Administering Oracle Net Listener (Oracle official documentation)
- Oracle’s Automatic Listener Registration (Blog)
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:
- die Verwendung eines speziellen Services für bestimmte Anwendungen oder Nutzergruppen zur Kontrolle, wann sie sich zur Datenbank verbinden können und wann nicht
- die Nutzung von Oracle's Resource Manager zur Kontrolle der Ressourcenverteilung z. B. großer, ressourcenhungriger Batch-Prozesse, indem man ihnen einen speziellen „Batch-Service“ zuweist
- das Auditing mit speziellen Anpassungen für eine Anwendung bzw. Benutzergruppe, die sich über diesen Service anmeldet, konfigurieren
- in einer Hochverfügbarkeits-Umgebung mit Data Guard sicherstellen, dass sich (störrische) Anwendungen immer nur zur primären Datenbank verbinden (und nicht zu einer im Lesemodus geöffneten Standby, oder bei einer Standby im MOUNT-Modus penetrant kleben bleiben)
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.
- wenn die Standby-DB Read-Only geöffnet ist, soll nur das Reporting auf selbige zugreifen
- zur gleichen Zeit sollen alle anderen Anwendungen weiterhin ausschließlich die Primär-DB verwenden
- das Reporting soll ausschließlich auf die Standby zugreifen (um die Primär-DB nicht extra zu belasten)
- und schließlich, well die Standby lediglich im Status MOUNTED ist, soll sich nichts dorthin verbinden, indem der Listener sich „dumm stellt"
Es ist wahrscheinlich schon erkennbar, worauf ich abziele:
- Definieren eines Services für die produktive Arbeit, z. B.
SERV_PROD - Definieren eines weiteren Services für das Reporting, z. B.
SERV_REPORT - Konfigurieren eines „dynamischen Listener-Eintrags“ auf beiden Maschinen, an dem sich beide Services registrieren können
- und schließlich die Würze: Erstellen eines Triggers der dafür sorgt, dass immer nur die jeweils richtigen Services auf der jeweiligen Datenbank gestartet sind (auch nach einem Switchover)
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.
-
das ist natürlich stark vereinfacht – es gibt weitere Rollen mit dieser Möglichkeit, z. B. SYSDG ↩︎
