jetstream-api 0.4.2__tar.gz → 0.4.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {jetstream_api-0.4.2/jetstream_api.egg-info → jetstream_api-0.4.4}/PKG-INFO +25 -8
- {jetstream_api-0.4.2 → jetstream_api-0.4.4}/README.md +24 -7
- {jetstream_api-0.4.2 → jetstream_api-0.4.4}/ilink/__init__.py +1 -1
- {jetstream_api-0.4.2 → jetstream_api-0.4.4}/ilink/admin.py +174 -23
- {jetstream_api-0.4.2 → jetstream_api-0.4.4}/ilink/common.py +13 -0
- {jetstream_api-0.4.2 → jetstream_api-0.4.4}/ilink/msg.py +7 -0
- {jetstream_api-0.4.2 → jetstream_api-0.4.4}/ilink/properties.py +156 -28
- {jetstream_api-0.4.2 → jetstream_api-0.4.4}/ilink/qmgr.py +252 -21
- {jetstream_api-0.4.2 → jetstream_api-0.4.4/jetstream_api.egg-info}/PKG-INFO +25 -8
- {jetstream_api-0.4.2 → jetstream_api-0.4.4}/pyproject.toml +1 -1
- {jetstream_api-0.4.2 → jetstream_api-0.4.4}/LICENSE.txt +0 -0
- {jetstream_api-0.4.2 → jetstream_api-0.4.4}/ilink/admintopic.py +0 -0
- {jetstream_api-0.4.2 → jetstream_api-0.4.4}/ilink/connectivity.py +0 -0
- {jetstream_api-0.4.2 → jetstream_api-0.4.4}/ilink/exception.py +0 -0
- {jetstream_api-0.4.2 → jetstream_api-0.4.4}/ilink/pattern.py +0 -0
- {jetstream_api-0.4.2 → jetstream_api-0.4.4}/ilink/producer.py +0 -0
- {jetstream_api-0.4.2 → jetstream_api-0.4.4}/ilink/py.typed +0 -0
- {jetstream_api-0.4.2 → jetstream_api-0.4.4}/ilink/topic.py +0 -0
- {jetstream_api-0.4.2 → jetstream_api-0.4.4}/ilink/util.py +0 -0
- {jetstream_api-0.4.2 → jetstream_api-0.4.4}/jetstream_api.egg-info/SOURCES.txt +0 -0
- {jetstream_api-0.4.2 → jetstream_api-0.4.4}/jetstream_api.egg-info/dependency_links.txt +0 -0
- {jetstream_api-0.4.2 → jetstream_api-0.4.4}/jetstream_api.egg-info/requires.txt +0 -0
- {jetstream_api-0.4.2 → jetstream_api-0.4.4}/jetstream_api.egg-info/top_level.txt +0 -0
- {jetstream_api-0.4.2 → jetstream_api-0.4.4}/setup.cfg +0 -0
- {jetstream_api-0.4.2 → jetstream_api-0.4.4}/tests/test_example.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: jetstream-api
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.4
|
|
4
4
|
Summary: Python client API for iLink M.O.M. (Message Oriented Middleware)
|
|
5
5
|
Author: snowjeans
|
|
6
6
|
License: Boost Software License - Version 1.0 - August 17th, 2003
|
|
@@ -137,15 +137,32 @@ print(adm.getTopicList())
|
|
|
137
137
|
클러스터 큐 관리자에 접속하면 후보 주소를 캐싱해 두었다가, 리더가 바뀌어도 따라갑니다.
|
|
138
138
|
|
|
139
139
|
```python
|
|
140
|
-
qmgr.
|
|
141
|
-
print(qmgr.get_cluster_addresses())
|
|
142
|
-
qmgr.reconnect()
|
|
140
|
+
qmgr.connect("10.0.0.1", 5000, "APP", True) # 주소 하나면 됩니다
|
|
141
|
+
print(qmgr.get_cluster_addresses()) # ['10.0.0.1:5000', '10.0.0.2:5000']
|
|
142
|
+
qmgr.reconnect() # 새 리더를 찾아 재접속
|
|
143
|
+
|
|
144
|
+
# 첫 접속 시점의 장애까지 대비하려면 목록으로 (포트 인자 없음)
|
|
145
|
+
qmgr.connect("10.0.0.1:5000,10.0.0.2:5000", "APP", True)
|
|
143
146
|
```
|
|
144
147
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
148
|
+
접속에 성공하면 핸드셰이크 직후 나머지 노드 주소를 서버에서 받아 캐싱하므로
|
|
149
|
+
**주소는 하나만 주면 됩니다.** 목록은 첫 접속 시점의 장애까지 대비할 때 씁니다.
|
|
150
|
+
|
|
151
|
+
### 0.4.4 변경 - 자동 재접속은 옵션이 아니라 기본 동작
|
|
152
|
+
|
|
153
|
+
캐싱된 endpoint 목록이 있으면(= 클러스터) 통신 장애로 실패한 put/get 을 페일오버
|
|
154
|
+
재접속 후 **한 번 자동 재시도합니다.** `set_auto_reconnect()` 는 더 이상 필요 없으며,
|
|
155
|
+
불러도 무시됩니다(기존 코드 호환용으로만 남겨 두었습니다).
|
|
156
|
+
|
|
157
|
+
| 상황 | 동작 |
|
|
158
|
+
|---|---|
|
|
159
|
+
| 미커밋 트랜잭션 **있음** | 예외를 올립니다. 앱이 `reconnect()` 후 트랜잭션을 처음부터 다시 수행 |
|
|
160
|
+
| 미커밋 트랜잭션 **없음** (auto-commit 포함) | 내부에서 재접속 후 1회 재시도 |
|
|
161
|
+
| endpoint 목록 없음 (비클러스터/구엔진) | 예외를 올립니다 - 갈 곳이 없습니다 |
|
|
162
|
+
|
|
163
|
+
> 재시도는 **at-least-once** 입니다. 서버가 처리한 뒤 응답이 유실된 시점에 재시도하면
|
|
164
|
+
> **중복 put 이 생길 수 있습니다.** 멱등하지 않은 처리라면 메시지 ID 로 중복을 걸러야
|
|
165
|
+
> 합니다. 미커밋 트랜잭션이 없을 때만 재시도하므로 트랜잭션 유실은 없습니다.
|
|
149
166
|
|
|
150
167
|
## 0.4.0 신규 - 관리 연결 하나로 pub/sub
|
|
151
168
|
|
|
@@ -91,15 +91,32 @@ print(adm.getTopicList())
|
|
|
91
91
|
클러스터 큐 관리자에 접속하면 후보 주소를 캐싱해 두었다가, 리더가 바뀌어도 따라갑니다.
|
|
92
92
|
|
|
93
93
|
```python
|
|
94
|
-
qmgr.
|
|
95
|
-
print(qmgr.get_cluster_addresses())
|
|
96
|
-
qmgr.reconnect()
|
|
94
|
+
qmgr.connect("10.0.0.1", 5000, "APP", True) # 주소 하나면 됩니다
|
|
95
|
+
print(qmgr.get_cluster_addresses()) # ['10.0.0.1:5000', '10.0.0.2:5000']
|
|
96
|
+
qmgr.reconnect() # 새 리더를 찾아 재접속
|
|
97
|
+
|
|
98
|
+
# 첫 접속 시점의 장애까지 대비하려면 목록으로 (포트 인자 없음)
|
|
99
|
+
qmgr.connect("10.0.0.1:5000,10.0.0.2:5000", "APP", True)
|
|
97
100
|
```
|
|
98
101
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
102
|
+
접속에 성공하면 핸드셰이크 직후 나머지 노드 주소를 서버에서 받아 캐싱하므로
|
|
103
|
+
**주소는 하나만 주면 됩니다.** 목록은 첫 접속 시점의 장애까지 대비할 때 씁니다.
|
|
104
|
+
|
|
105
|
+
### 0.4.4 변경 - 자동 재접속은 옵션이 아니라 기본 동작
|
|
106
|
+
|
|
107
|
+
캐싱된 endpoint 목록이 있으면(= 클러스터) 통신 장애로 실패한 put/get 을 페일오버
|
|
108
|
+
재접속 후 **한 번 자동 재시도합니다.** `set_auto_reconnect()` 는 더 이상 필요 없으며,
|
|
109
|
+
불러도 무시됩니다(기존 코드 호환용으로만 남겨 두었습니다).
|
|
110
|
+
|
|
111
|
+
| 상황 | 동작 |
|
|
112
|
+
|---|---|
|
|
113
|
+
| 미커밋 트랜잭션 **있음** | 예외를 올립니다. 앱이 `reconnect()` 후 트랜잭션을 처음부터 다시 수행 |
|
|
114
|
+
| 미커밋 트랜잭션 **없음** (auto-commit 포함) | 내부에서 재접속 후 1회 재시도 |
|
|
115
|
+
| endpoint 목록 없음 (비클러스터/구엔진) | 예외를 올립니다 - 갈 곳이 없습니다 |
|
|
116
|
+
|
|
117
|
+
> 재시도는 **at-least-once** 입니다. 서버가 처리한 뒤 응답이 유실된 시점에 재시도하면
|
|
118
|
+
> **중복 put 이 생길 수 있습니다.** 멱등하지 않은 처리라면 메시지 ID 로 중복을 걸러야
|
|
119
|
+
> 합니다. 미커밋 트랜잭션이 없을 때만 재시도하므로 트랜잭션 유실은 없습니다.
|
|
103
120
|
|
|
104
121
|
## 0.4.0 신규 - 관리 연결 하나로 pub/sub
|
|
105
122
|
|
|
@@ -705,9 +705,11 @@ class ILAdminService:
|
|
|
705
705
|
|
|
706
706
|
Raises:
|
|
707
707
|
ILOperationException: 세 서비스 주소나 세 ilcc 포트 중 하나라도 비었을 때
|
|
708
|
-
(``MIMQE_INVALID_ARGUMENT``),
|
|
709
|
-
|
|
710
|
-
|
|
708
|
+
(``MIMQE_INVALID_ARGUMENT``), **세 서비스 주소 중 둘 이상이 같은
|
|
709
|
+
서비스를 가리킬 때**(``MIMQE_INVALID_ARGUMENT``, 엔진 rev 3322+),
|
|
710
|
+
같은 이름이 이미 있을 때, 이름에 금지 문자가 있을 때
|
|
711
|
+
(``MIMQE_UNAVAILABLE_CLUSTER_NAME``), 포트가 1025~65535 밖일 때
|
|
712
|
+
(``MIMQE_UNAVAILABLE_PORT_NUMBER``).
|
|
711
713
|
ILException: 통신 실패.
|
|
712
714
|
|
|
713
715
|
Note:
|
|
@@ -722,6 +724,26 @@ class ILAdminService:
|
|
|
722
724
|
역할(코디네이터/심판)과 각 ilcc 포트는 **서비스가 배정한다.** 이 호출을
|
|
723
725
|
받은 서비스가 세 곳에 정의를 퍼뜨리면서 대상별로 채워 넣는다.
|
|
724
726
|
|
|
727
|
+
.. warning:: 접속 정보가 겹치면 생성이 실패한다
|
|
728
|
+
|
|
729
|
+
``priSvc`` / ``secSvc`` / ``witSvc`` 는 **서로 다른 관리 서비스**여야 한다.
|
|
730
|
+
둘 이상이 같은 서비스를 가리키면 서버가 ``MIMQE_INVALID_ARGUMENT`` 로
|
|
731
|
+
**생성을 거부한다**(엔진 rev 3322+).
|
|
732
|
+
|
|
733
|
+
**같은 IP 라도 관리 서비스 포트가 다르면 된다**(한 호스트에 여러 서비스를
|
|
734
|
+
띄우는 시험 구성이 그렇다). 비교는 ``"호스트:포트"`` 로 하며 포트를 생략하면
|
|
735
|
+
9998 로 본다 - 즉 ``"10.0.0.1"`` 과 ``"10.0.0.1:9998"`` 은 같은 주소이고,
|
|
736
|
+
``"10.0.0.1:9988"`` 은 다른 주소다.
|
|
737
|
+
|
|
738
|
+
왜 막느냐면, 겹친 채로 만들어지면 그 서비스가 같은 이름의 오브젝트를 두
|
|
739
|
+
역할로 갖게 되고 전파가 "있으면 갱신"으로 처리해 **나중 것이 앞 것을
|
|
740
|
+
덮어쓰기** 때문이다. 조정자가 하나뿐인 구성 - 3-서비스를 강제해서 막으려던
|
|
741
|
+
바로 그 상태가 된다.
|
|
742
|
+
|
|
743
|
+
**rev 3322 미만 엔진은 검사하지 않는다.** 거부 없이 그 구성이 그대로
|
|
744
|
+
만들어지므로 부르는 쪽이 지켜야 한다. 이 라이브러리는 검사하지 않는다 -
|
|
745
|
+
판정을 두 곳에 두면 규칙이 어긋나기 때문이다.
|
|
746
|
+
|
|
725
747
|
Warning:
|
|
726
748
|
**닿지 않는 서비스가 있어도 이 호출은 성공한다.** 구성만 미리 해두는
|
|
727
749
|
운영을 허용하기 때문이다. 대신 클러스터가 ``STARTING`` 에 머물고
|
|
@@ -868,6 +890,34 @@ class ILAdminService:
|
|
|
868
890
|
노드 편집은 **읽고-고치고-쓴다**: getClusterProperty 로 받아
|
|
869
891
|
add_node()/remove_node() 한 뒤 이 메서드로 되돌려준다.
|
|
870
892
|
|
|
893
|
+
Raises:
|
|
894
|
+
ILOperationException: 노드가 규칙을 어겼을 때(전부
|
|
895
|
+
``MIMQE_INVALID_ARGUMENT``) - 큐 관리자 이름이 빈 노드가 있을 때
|
|
896
|
+
(**rev 3319+**), 같은 ``host:port`` 노드가 둘 이상일 때, 같은
|
|
897
|
+
``nodeId`` 노드가 둘 이상일 때(**rev 3323+**).
|
|
898
|
+
ILException: 통신 실패.
|
|
899
|
+
|
|
900
|
+
.. warning:: 토폴로지는 여기서 바꾸지 마라
|
|
901
|
+
|
|
902
|
+
``priSvc`` / ``secSvc`` / ``witSvc`` 와 세 포트는 **생성 시점에 정하는
|
|
903
|
+
값**이다. ilsc 의 ALTER 는 이 항목을 아예 받지 않는다. 그런데 이 메서드는
|
|
904
|
+
속성 전체를 통째로 보내므로 **바뀐 채로 저장된다**(실측).
|
|
905
|
+
|
|
906
|
+
문제는 **빠진 서비스에 오브젝트가 남는다**는 것이다. 전파는 새 토폴로지로만
|
|
907
|
+
가므로 옛 서비스는 통지를 못 받고, 그 자리에 **옛 토폴로지를 든 오브젝트가
|
|
908
|
+
그대로 남는다**(실측: SEC 을 옮긴 뒤 옛 SEC 서비스에 잔존). 조정자를
|
|
909
|
+
옮기려면 **클러스터를 지우고 다시 만들어라.**
|
|
910
|
+
|
|
911
|
+
Note:
|
|
912
|
+
**클러스터 구성 검사는 전부 이 경로에서 이뤄진다.** 노드 추가/삭제도
|
|
913
|
+
결국 여기로 오므로, 서버는 쓰기 입구인 여기 한 곳에서 막는다.
|
|
914
|
+
:meth:`addClusterNode` / :meth:`removeClusterNode` 도 같은 검사를 받는다.
|
|
915
|
+
|
|
916
|
+
중복 노드가 왜 막히는지는
|
|
917
|
+
:meth:`ILClusterProperty.add_node` 를 보라 - 요약하면 같은 큐 관리자를
|
|
918
|
+
두 노드로 등록하면 이중 제어가 되고, ``nodeId`` 가 겹치면 **노드가 서로
|
|
919
|
+
다른 id 를 받아 갈라진다.**
|
|
920
|
+
|
|
871
921
|
Example::
|
|
872
922
|
|
|
873
923
|
p = svc.getClusterProperty("CC1")
|
|
@@ -896,7 +946,11 @@ class ILAdminService:
|
|
|
896
946
|
port (int): **큐 관리자 리스너 포트**(ilcc 포트가 아니다).
|
|
897
947
|
|
|
898
948
|
Raises:
|
|
899
|
-
ILOperationException: 조회나 반영이 거절됐을 때.
|
|
949
|
+
ILOperationException: 조회나 반영이 거절됐을 때. **같은 ``host:port``
|
|
950
|
+
노드가 이미 있으면 거절된다** - 한 큐 관리자를 두 노드로 등록하는
|
|
951
|
+
셈이기 때문이다(``MIMQE_ALREADY_EXIST`` 로 이 라이브러리가 먼저 막고,
|
|
952
|
+
블롭을 직접 만들어 보내도 서버가 ``MIMQE_INVALID_ARGUMENT`` 로
|
|
953
|
+
거절한다 - **엔진 rev 3323+**).
|
|
900
954
|
ILException: 통신 실패.
|
|
901
955
|
|
|
902
956
|
Warning:
|
|
@@ -2935,12 +2989,20 @@ class ILAdminQmgr:
|
|
|
2935
2989
|
qmgr.startChannel("TO.QM2")
|
|
2936
2990
|
|
|
2937
2991
|
qmgr2.createQueue(ILQueueProperty("APP.ORDERS")) # 받는 쪽
|
|
2992
|
+
|
|
2993
|
+
.. note:: 구엔진(6.3.3 미만) 호환
|
|
2994
|
+
|
|
2995
|
+
큐 속성은 엔진 6.3.3 에서 전송 레이아웃이 한 번 바뀐 **유일한**
|
|
2996
|
+
속성이다(4096 -> 2200). 붙어 있는 엔진의 리비전을 보고 알아서 옛
|
|
2997
|
+
레이아웃으로 보내므로 호출부는 달라질 게 없다. 다만 구엔진에는
|
|
2998
|
+
만료(``expiry``)/영속성(``deliveryMode``)/파티션 설정이 아예 없어
|
|
2999
|
+
**그 값들은 무시된다** - 설정해도 반영되지 않는다.
|
|
2938
3000
|
"""
|
|
2939
3001
|
try:
|
|
2940
3002
|
self.service.apply(
|
|
2941
3003
|
self.qmgrName,
|
|
2942
3004
|
ILC.MIMQ_CREATE_QUEUE_REQUEST,
|
|
2943
|
-
|
|
3005
|
+
self._queueBlob(prop),
|
|
2944
3006
|
ILC.MIMQ_SUCCESSFUL_QUEUE_CREATION,
|
|
2945
3007
|
)
|
|
2946
3008
|
except _PASSTHROUGH_EXCEPTIONS:
|
|
@@ -3043,6 +3105,41 @@ class ILAdminQmgr:
|
|
|
3043
3105
|
except Exception as exc:
|
|
3044
3106
|
raise ILException(exc) from exc
|
|
3045
3107
|
|
|
3108
|
+
def _queueBlob(self, prop: ILQueueProperty) -> bytes:
|
|
3109
|
+
"""큐 속성을 **붙어 있는 엔진이 이해하는 레이아웃**으로 직렬화한다.
|
|
3110
|
+
|
|
3111
|
+
Args:
|
|
3112
|
+
prop (ILQueueProperty): 보낼 큐 속성.
|
|
3113
|
+
|
|
3114
|
+
Returns:
|
|
3115
|
+
bytes: rev 3155 미만이면 구버전(4096), 그 외에는 현행(2200) 블롭.
|
|
3116
|
+
|
|
3117
|
+
Note:
|
|
3118
|
+
판정을 ``0 < revision`` 으로 한정한 이유는 다른 버전 게이트와 같다.
|
|
3119
|
+
버전 문자열을 파싱하지 못하면 revision 이 0 으로 남는데, 그때 구버전
|
|
3120
|
+
취급을 하면 최신 엔진에 4096 을 보내 조용히 깨진다.
|
|
3121
|
+
"""
|
|
3122
|
+
return prop.get_serialized(
|
|
3123
|
+
legacy=0 < self.revision < ILC.QUEUE_LAYOUT_MIN_REVISION
|
|
3124
|
+
)
|
|
3125
|
+
|
|
3126
|
+
def _requireQueueFlowSupport(self) -> None:
|
|
3127
|
+
"""유량 조회를 지원하지 않는 엔진이면 그 자리에서 실패시킨다.
|
|
3128
|
+
|
|
3129
|
+
Raises:
|
|
3130
|
+
ILOperationException: 엔진이 6.3.4(rev 3299) 미만일 때.
|
|
3131
|
+
|
|
3132
|
+
Note:
|
|
3133
|
+
유량 통계는 6.3.4 에서 들어왔다. 구엔진은 요청 코드 자체를 모르므로
|
|
3134
|
+
응답이 없거나 엉뚱한 리포트가 온다. 무엇이 잘못됐는지 알 수 없는
|
|
3135
|
+
타임아웃 대신 **왜 안 되는지 적힌 예외**로 끊는다.
|
|
3136
|
+
"""
|
|
3137
|
+
if 0 < self.revision < ILC.QUEUE_FLOW_MIN_REVISION:
|
|
3138
|
+
raise ILOperationException(
|
|
3139
|
+
"MIMQE_NOT_SUPPORTED (queue flow statistics require engine 6.3.4, "
|
|
3140
|
+
f"rev {ILC.QUEUE_FLOW_MIN_REVISION}+, current: {self.revision})"
|
|
3141
|
+
)
|
|
3142
|
+
|
|
3046
3143
|
def getQueueProperty(self, queueName: str) -> ILQueueProperty:
|
|
3047
3144
|
"""큐 하나의 전체 속성을 조회한다(종류 무관).
|
|
3048
3145
|
|
|
@@ -3204,12 +3301,20 @@ class ILAdminQmgr:
|
|
|
3204
3301
|
prop = qmgr.getQueueProperty("APP.ORDERS")
|
|
3205
3302
|
prop.setMaxDepth(50000)
|
|
3206
3303
|
qmgr.setQueueProperty(prop)
|
|
3304
|
+
|
|
3305
|
+
.. note:: 구엔진(6.3.3 미만) 호환
|
|
3306
|
+
|
|
3307
|
+
큐 속성은 엔진 6.3.3 에서 전송 레이아웃이 한 번 바뀐 **유일한**
|
|
3308
|
+
속성이다(4096 -> 2200). 붙어 있는 엔진의 리비전을 보고 알아서 옛
|
|
3309
|
+
레이아웃으로 보내므로 호출부는 달라질 게 없다. 다만 구엔진에는
|
|
3310
|
+
만료(``expiry``)/영속성(``deliveryMode``)/파티션 설정이 아예 없어
|
|
3311
|
+
**그 값들은 무시된다** - 설정해도 반영되지 않는다.
|
|
3207
3312
|
"""
|
|
3208
3313
|
try:
|
|
3209
3314
|
self.service.apply(
|
|
3210
3315
|
self.qmgrName,
|
|
3211
3316
|
ILC.MIMQ_SET_QUEUE_PROPERTY,
|
|
3212
|
-
|
|
3317
|
+
self._queueBlob(prop),
|
|
3213
3318
|
ILC.MIMQ_SUCCESSFUL_SET_QUEUE_PROPERTY,
|
|
3214
3319
|
)
|
|
3215
3320
|
except _PASSTHROUGH_EXCEPTIONS:
|
|
@@ -3388,23 +3493,39 @@ class ILAdminQmgr:
|
|
|
3388
3493
|
``getIncomingDataSize()`` / ``getOutgoingDataSize()`` / ``getDepth()``).
|
|
3389
3494
|
|
|
3390
3495
|
Raises:
|
|
3391
|
-
ILOperationException: 그런 큐가 없을 때.
|
|
3496
|
+
ILOperationException: 그런 큐가 없을 때. 또는 엔진이 6.3.4(rev 3299)
|
|
3497
|
+
미만이라 유량 통계 자체를 지원하지 않을 때.
|
|
3392
3498
|
ILException: 통신 실패.
|
|
3393
3499
|
|
|
3394
3500
|
Note:
|
|
3395
3501
|
**한 시점의 스냅샷**이다. 시간에 따른 추이를 보려면
|
|
3396
3502
|
:meth:`getQueueFlowInfoArray` 를 쓴다.
|
|
3397
3503
|
|
|
3398
|
-
..
|
|
3504
|
+
.. note:: 값은 **1초 주기**로 채워진다
|
|
3505
|
+
|
|
3506
|
+
유량 스냅샷은 서버가 1초마다 적재한다. 그래서 갓 만든 큐도 **조회
|
|
3507
|
+
자체는 즉시 되지만**(엔진 r3336+) 유입/유출 값은 첫 적재가 돌 때까지
|
|
3508
|
+
0 이다 - 실측: put 직후 0초 시점 0, 1.2초 시점 5건/2695B (r3336).
|
|
3509
|
+
즉시 깊이가 필요하면 :meth:`getQueueRealTimeInfo` 를 쓰라. 그쪽은
|
|
3510
|
+
적재 주기와 무관하게 현재 값을 준다.
|
|
3511
|
+
|
|
3512
|
+
**엔진 r3336 미만에서는 갓 만든 큐가 최대 1초간 조회되지 않는다** -
|
|
3513
|
+
첫 적재 전까지 ``MIMQE_OBJECT_NOT_FOUND`` 가 난다(r3335 실측
|
|
3514
|
+
0.1~1.1초, 단건·배열 50회). put/get 을 해도 앞당겨지지 않는다.
|
|
3515
|
+
|
|
3516
|
+
.. versionchanged:: 0.1.6
|
|
3517
|
+
이전 문서는 "유량 테이블은 주기적(약 1분) 갱신"이라고 적었으나
|
|
3518
|
+
**사실이 아니다**. 실서버에서 관측된 35초 지연은 적재 주기가
|
|
3519
|
+
아니라 **적재 스레드가 분 단위 통계 리포트에 붙들려 있던 것**으로,
|
|
3520
|
+
엔진 r3331 통계 컴팩션으로 해소됐다.
|
|
3399
3521
|
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
우회할 수 없다** - 방금 만든 큐의 현황은 :meth:`getQueueRealTimeInfo`
|
|
3405
|
-
로 보라(이쪽은 즉시 조회된다).
|
|
3522
|
+
.. versionchanged:: 0.4.4
|
|
3523
|
+
엔진 r3336 이 큐 생성 시점에 유량 항목을 미리 등재하도록 바뀌어
|
|
3524
|
+
**생성 직후 조회 실패가 사라졌다**(24/24 첫 시도 성공, 값은 0).
|
|
3525
|
+
재시도 코드는 구엔진 대비로만 남기면 된다.
|
|
3406
3526
|
|
|
3407
3527
|
"""
|
|
3528
|
+
self._requireQueueFlowSupport()
|
|
3408
3529
|
try:
|
|
3409
3530
|
report_msg = self.service.request(
|
|
3410
3531
|
self.qmgrName, ILC.MIMQ_GET_QUEUE_ACCESS_INFO, queueName
|
|
@@ -3441,23 +3562,39 @@ class ILAdminQmgr:
|
|
|
3441
3562
|
실제 기록이 그보다 적으면 0 으로 채운 항목이 들어간다.
|
|
3442
3563
|
|
|
3443
3564
|
Raises:
|
|
3444
|
-
ILOperationException: 그런 큐가 없을 때.
|
|
3565
|
+
ILOperationException: 그런 큐가 없을 때. 또는 엔진이 6.3.4(rev 3299)
|
|
3566
|
+
미만이라 유량 통계 자체를 지원하지 않을 때.
|
|
3445
3567
|
ILException: 통신 실패.
|
|
3446
3568
|
|
|
3447
3569
|
Note:
|
|
3448
3570
|
0 으로 채워진 항목과 실제로 유량이 0 이었던 구간은 값만으로 구분되지
|
|
3449
3571
|
않는다. 적체 추세만 보는 용도로 쓰라.
|
|
3450
3572
|
|
|
3451
|
-
..
|
|
3573
|
+
.. note:: 값은 **1초 주기**로 채워진다
|
|
3574
|
+
|
|
3575
|
+
유량 스냅샷은 서버가 1초마다 적재한다. 그래서 갓 만든 큐도 **조회
|
|
3576
|
+
자체는 즉시 되지만**(엔진 r3336+) 유입/유출 값은 첫 적재가 돌 때까지
|
|
3577
|
+
0 이다 - 실측: put 직후 0초 시점 0, 1.2초 시점 5건/2695B (r3336).
|
|
3578
|
+
즉시 깊이가 필요하면 :meth:`getQueueRealTimeInfo` 를 쓰라. 그쪽은
|
|
3579
|
+
적재 주기와 무관하게 현재 값을 준다.
|
|
3580
|
+
|
|
3581
|
+
**엔진 r3336 미만에서는 갓 만든 큐가 최대 1초간 조회되지 않는다** -
|
|
3582
|
+
첫 적재 전까지 ``MIMQE_OBJECT_NOT_FOUND`` 가 난다(r3335 실측
|
|
3583
|
+
0.1~1.1초, 단건·배열 50회). put/get 을 해도 앞당겨지지 않는다.
|
|
3452
3584
|
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3585
|
+
.. versionchanged:: 0.1.6
|
|
3586
|
+
이전 문서는 "유량 테이블은 주기적(약 1분) 갱신"이라고 적었으나
|
|
3587
|
+
**사실이 아니다**. 실서버에서 관측된 35초 지연은 적재 주기가
|
|
3588
|
+
아니라 **적재 스레드가 분 단위 통계 리포트에 붙들려 있던 것**으로,
|
|
3589
|
+
엔진 r3331 통계 컴팩션으로 해소됐다.
|
|
3590
|
+
|
|
3591
|
+
.. versionchanged:: 0.4.4
|
|
3592
|
+
엔진 r3336 이 큐 생성 시점에 유량 항목을 미리 등재하도록 바뀌어
|
|
3593
|
+
**생성 직후 조회 실패가 사라졌다**(24/24 첫 시도 성공, 값은 0).
|
|
3594
|
+
재시도 코드는 구엔진 대비로만 남기면 된다.
|
|
3459
3595
|
|
|
3460
3596
|
"""
|
|
3597
|
+
self._requireQueueFlowSupport()
|
|
3461
3598
|
try:
|
|
3462
3599
|
res_msg = self.service.inquire(
|
|
3463
3600
|
self.qmgrName, ILC.MIMQ_GET_QUEUE_ACCESS_INFO_ARRAY, queueName
|
|
@@ -4831,6 +4968,15 @@ class ILAdminQmgr:
|
|
|
4831
4968
|
Note:
|
|
4832
4969
|
**누가 언제 마지막으로 이 큐를 건드렸는지** 알 수 있어 소비자가
|
|
4833
4970
|
멈춘 상황을 진단할 때 유용하다.
|
|
4971
|
+
|
|
4972
|
+
.. note:: 구엔진(6.3.3 미만)에 붙었을 때
|
|
4973
|
+
|
|
4974
|
+
그 시절 엔진은 이 요청에 **큐 속성 블롭을 통째로** 답한다. 사용 현황
|
|
4975
|
+
통계가 속성 안에 들어 있었기 때문이다. 클라이언트가 알아서 판별해
|
|
4976
|
+
같은 필드를 채우므로 호출부는 달라질 게 없다. 다만
|
|
4977
|
+
:attr:`~ilink.properties.ILQueueRealTimeInfo.partitions` 는 **빈
|
|
4978
|
+
목록**이다 - 파티셔닝은 6.3.3 에서 들어온 기능이라 구엔진 큐에는
|
|
4979
|
+
파티션이라는 개념 자체가 없다.
|
|
4834
4980
|
"""
|
|
4835
4981
|
try:
|
|
4836
4982
|
res_msg = self.service.inquire(
|
|
@@ -4838,7 +4984,12 @@ class ILAdminQmgr:
|
|
|
4838
4984
|
)
|
|
4839
4985
|
if res_msg.getType() == ILC.MIMQ_REPORT_MESSAGE:
|
|
4840
4986
|
raise ILOperationException(res_msg.getReportMsg().getArg2())
|
|
4841
|
-
|
|
4987
|
+
data = res_msg.getDataMsg().getData() or b""
|
|
4988
|
+
# 구엔진(rev 3155 미만)은 이 요청에 **큐 속성 블롭 4096** 을 그대로
|
|
4989
|
+
# 답한다. 그 시절에는 사용 현황 통계가 속성 안에 들어 있었기 때문이다.
|
|
4990
|
+
if len(data) >= ILC.MIMQ_VO_QUEUE_LEN_LEGACY:
|
|
4991
|
+
return ILQueueRealTimeInfo.from_legacy_queue_blob(data)
|
|
4992
|
+
return ILQueueRealTimeInfo(data)
|
|
4842
4993
|
except _PASSTHROUGH_EXCEPTIONS:
|
|
4843
4994
|
raise
|
|
4844
4995
|
except Exception as exc:
|
|
@@ -719,6 +719,19 @@ class ILC:
|
|
|
719
719
|
MIMQ_VO_SERVER_LEN = 428
|
|
720
720
|
MIMQ_VO_QMGR_LEN = 666
|
|
721
721
|
MIMQ_VO_QUEUE_LEN = 2200
|
|
722
|
+
#: 큐 속성 블롭의 **구버전(4096) 길이**. 엔진 6.3.3 미만이 쓰던 레이아웃이다.
|
|
723
|
+
#: 0~1772 와 2142~2162 는 현행과 같고, 그 사이 370 바이트만 다르다 -
|
|
724
|
+
#: 구버전은 사용 현황 통계, 현행은 만료/영속성/파티셔닝 설정이 들어간다.
|
|
725
|
+
MIMQ_VO_QUEUE_LEN_LEGACY = 4096
|
|
726
|
+
#: 큐 속성이 2200 레이아웃으로 바뀐 **첫 엔진 리비전**(6.3.3.3155).
|
|
727
|
+
#: 이 값보다 낮으면 4096 레이아웃으로 주고받아야 한다.
|
|
728
|
+
QUEUE_LAYOUT_MIN_REVISION = 3155
|
|
729
|
+
#: 구버전(4096) 블롭에서 공통 접두가 끝나고 레이아웃이 갈리는 지점.
|
|
730
|
+
#: 큐 **유량 통계** 조회가 들어온 첫 엔진 리비전(6.3.4.3299).
|
|
731
|
+
QUEUE_FLOW_MIN_REVISION = 3299
|
|
732
|
+
QUEUE_LEGACY_DIVERGE_OFFSET = 1772
|
|
733
|
+
#: 구버전(4096) 블롭에서 incomingDelayDepth 가 있는 위치. 현행과 같다.
|
|
734
|
+
QUEUE_LEGACY_TAIL_OFFSET = 2142
|
|
722
735
|
MIMQ_VO_CHANNEL_LEN = 2048
|
|
723
736
|
MIMQ_VO_DEST_ITEM_LEN = 1024
|
|
724
737
|
MIMQ_VO_QUEUE_INFO_LEN = 512
|
|
@@ -2524,7 +2524,14 @@ class ILMsgUtil:
|
|
|
2524
2524
|
elif cls is ILQmgrProperty:
|
|
2525
2525
|
serialized_length = ILC.MIMQ_VO_QMGR_LEN
|
|
2526
2526
|
elif cls is ILQueueProperty:
|
|
2527
|
+
# 큐 속성만 컨테이너 길이가 바뀐 적이 있다(엔진 6.3.3 에서 4096 -> 2200).
|
|
2528
|
+
# 버전을 따지는 대신 **응답 길이에서 보폭을 역산**한다. 구엔진에 붙어도
|
|
2529
|
+
# 목록이 그대로 읽히고, 새 엔진에서는 계산 결과가 2200 이라 무해하다.
|
|
2527
2530
|
serialized_length = ILC.MIMQ_VO_QUEUE_LEN
|
|
2531
|
+
if count > 0:
|
|
2532
|
+
stride = (len(ba) - count_length) // count
|
|
2533
|
+
if stride == ILC.MIMQ_VO_QUEUE_LEN_LEGACY:
|
|
2534
|
+
serialized_length = ILC.MIMQ_VO_QUEUE_LEN_LEGACY
|
|
2528
2535
|
elif cls is ILChannelProperty:
|
|
2529
2536
|
serialized_length = ILC.MIMQ_VO_CHANNEL_LEN
|
|
2530
2537
|
elif cls is ILSpokeProperty:
|
|
@@ -446,6 +446,7 @@ class ILXmitQueueMinorProperty(ILQueueMinorProperty):
|
|
|
446
446
|
super().__init__(name, ILC.MIMQ_CHANNEL_TRANSMISSION_QUEUE)
|
|
447
447
|
|
|
448
448
|
|
|
449
|
+
|
|
449
450
|
class ILQueueProperty(_AutoAccessor):
|
|
450
451
|
"""큐의 **전체 속성**. 큐 생성/조회/변경에 모두 쓰이는 값 객체.
|
|
451
452
|
|
|
@@ -515,6 +516,11 @@ class ILQueueProperty(_AutoAccessor):
|
|
|
515
516
|
maxMemorySize (int): 메모리 상주 한도(바이트). 기본 10485760(10MB).
|
|
516
517
|
sharedMemorySize (int): 공유 메모리 크기. 기본 0.
|
|
517
518
|
numberOfPartition (int): 파티션 수. 기본 10.
|
|
519
|
+
legacyBlob (bool): 이 객체가 **구버전(4096) 블롭**에서 만들어졌는지.
|
|
520
|
+
엔진 rev 3155 미만이 보낸 응답이면 True 다. 그 경우 ``expiry`` /
|
|
521
|
+
``expiredQueue`` / ``deliveryMode`` / ``maxMemorySize`` /
|
|
522
|
+
``sharedMemorySize`` / ``numberOfPartition`` 은 **서버가 준 값이 아니라
|
|
523
|
+
기본값**이다 - 구엔진에는 그 설정이 없다.
|
|
518
524
|
"""
|
|
519
525
|
|
|
520
526
|
def __init__(
|
|
@@ -552,6 +558,8 @@ class ILQueueProperty(_AutoAccessor):
|
|
|
552
558
|
self.maxMemorySize = 10485760
|
|
553
559
|
self.sharedMemorySize = 0
|
|
554
560
|
self.numberOfPartition = 10
|
|
561
|
+
#: 구버전(4096) 블롭에서 만들어졌는지. 내부 판별용.
|
|
562
|
+
self.legacyBlob = False
|
|
555
563
|
|
|
556
564
|
if isinstance(name, (bytes, bytearray)):
|
|
557
565
|
self._from_bytes(bytes(name))
|
|
@@ -575,6 +583,9 @@ class ILQueueProperty(_AutoAccessor):
|
|
|
575
583
|
+ str(len(ba))
|
|
576
584
|
+ "]"
|
|
577
585
|
)
|
|
586
|
+
# 엔진 6.3.3 미만은 4096 레이아웃을 보낸다. 길이로 가른다 -
|
|
587
|
+
# 엔진의 QueueProperty::IsLegacyBlob(nDataSize) 와 같은 판정이다.
|
|
588
|
+
self.legacyBlob = len(ba) >= ILC.MIMQ_VO_QUEUE_LEN_LEGACY
|
|
578
589
|
offset = 0
|
|
579
590
|
self.name = ba_pick_string(ba, offset, 70)
|
|
580
591
|
offset += 70
|
|
@@ -603,19 +614,27 @@ class ILQueueProperty(_AutoAccessor):
|
|
|
603
614
|
offset += 10
|
|
604
615
|
self.traceEnabled = ba_pick_string(ba, offset, 1).lower() == "t"
|
|
605
616
|
offset += 2
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
617
|
+
# ---- 여기(1772)부터 레이아웃이 갈린다 ----
|
|
618
|
+
if self.legacyBlob:
|
|
619
|
+
# 구버전 4096: 이 자리는 사용 현황 통계다. 통계는
|
|
620
|
+
# ILQueueRealTimeInfo 가 같은 블롭에서 따로 꺼내가므로 여기서는
|
|
621
|
+
# 건너뛴다. 신규 설정 항목은 애초에 없으니 기본값을 유지한다.
|
|
622
|
+
offset = ILC.QUEUE_LEGACY_TAIL_OFFSET
|
|
623
|
+
else:
|
|
624
|
+
self.expiry = ba_pick_long(ba, offset, 20)
|
|
625
|
+
offset += 20
|
|
626
|
+
self.expiredQueue = ba_pick_string(ba, offset, 70)
|
|
627
|
+
offset += 70
|
|
628
|
+
self.deliveryMode = ba_pick_int(ba, offset, 2)
|
|
629
|
+
offset += 2
|
|
630
|
+
self.maxMemorySize = ba_pick_long(ba, offset, 20)
|
|
631
|
+
offset += 20
|
|
632
|
+
self.sharedMemorySize = ba_pick_long(ba, offset, 20)
|
|
633
|
+
offset += 20
|
|
634
|
+
self.numberOfPartition = ba_pick_int(ba, offset, 10)
|
|
635
|
+
offset += 10
|
|
636
|
+
offset += 228
|
|
637
|
+
# ---- 공통 접미 (2142) ----
|
|
619
638
|
self.incomingDelayDepth = ba_pick_int(ba, offset, 10)
|
|
620
639
|
offset += 10
|
|
621
640
|
self.incomingDelay = ba_pick_int(ba, offset, 10)
|
|
@@ -623,16 +642,27 @@ class ILQueueProperty(_AutoAccessor):
|
|
|
623
642
|
except Exception as exc:
|
|
624
643
|
raise ILException(exc) from exc
|
|
625
644
|
|
|
626
|
-
def get_serialized(self) -> bytes:
|
|
645
|
+
def get_serialized(self, legacy: bool = False) -> bytes:
|
|
627
646
|
"""이 객체를 서버 규격의 **고정폭 바이트**로 직렬화한다.
|
|
628
647
|
|
|
648
|
+
Args:
|
|
649
|
+
legacy (bool): True 면 **구버전(4096) 레이아웃**으로 만든다.
|
|
650
|
+
엔진 rev 3155 미만에 보낼 때 쓴다. 기본 False(현행 2200).
|
|
651
|
+
|
|
629
652
|
Returns:
|
|
630
653
|
bytes: 서버로 그대로 보낼 수 있는 바이트열.
|
|
631
654
|
|
|
632
655
|
Note:
|
|
633
656
|
라이브러리가 요청을 보낼 때 내부적으로 부른다. 응용이 직접 부를
|
|
634
|
-
일은 거의
|
|
657
|
+
일은 거의 없다 - 어느 레이아웃으로 보낼지는 접속한 엔진 리비전을
|
|
658
|
+
보고 :class:`ILAdminQmgr` 가 정한다.
|
|
659
|
+
|
|
660
|
+
구버전 레이아웃에는 ``expiry`` / ``expiredQueue`` / ``deliveryMode`` /
|
|
661
|
+
``maxMemorySize`` / ``sharedMemorySize`` / ``numberOfPartition`` 자리가
|
|
662
|
+
**없다.** 그 값들은 실리지 않고 버려진다 - 구엔진이 모르는 설정이다.
|
|
635
663
|
"""
|
|
664
|
+
if legacy:
|
|
665
|
+
return self._get_serialized_legacy()
|
|
636
666
|
ba = bytearray(ILC.MIMQ_VO_QUEUE_LEN)
|
|
637
667
|
offset = 0
|
|
638
668
|
ba_merge(self.name, ba, offset)
|
|
@@ -681,6 +711,18 @@ class ILQueueProperty(_AutoAccessor):
|
|
|
681
711
|
offset += 10
|
|
682
712
|
return bytes(ba)
|
|
683
713
|
|
|
714
|
+
def _get_serialized_legacy(self) -> bytes:
|
|
715
|
+
"""구버전(4096) 레이아웃으로 직렬화한다. 엔진 rev 3155 미만 전송용."""
|
|
716
|
+
cur = self.get_serialized() # 현행 2200
|
|
717
|
+
ba = bytearray(ILC.MIMQ_VO_QUEUE_LEN_LEGACY)
|
|
718
|
+
d = ILC.QUEUE_LEGACY_DIVERGE_OFFSET
|
|
719
|
+
t = ILC.QUEUE_LEGACY_TAIL_OFFSET
|
|
720
|
+
# 공통 접두(0~1772)와 공통 접미(2142~2162)만 옮긴다. 그 사이는 구버전에서
|
|
721
|
+
# 사용 현황 통계 자리라 설정값을 넣으면 통계로 오독된다 - 0 으로 남긴다.
|
|
722
|
+
ba[0:d] = cur[0:d]
|
|
723
|
+
ba[t:t + 20] = cur[t:t + 20]
|
|
724
|
+
return bytes(ba)
|
|
725
|
+
|
|
684
726
|
def get_type_string(self) -> str:
|
|
685
727
|
"""종류 코드를 사람이 읽는 문자열로 바꾼다.
|
|
686
728
|
|
|
@@ -1739,16 +1781,30 @@ class ILSecureFileProperty(_AutoAccessor):
|
|
|
1739
1781
|
class ILQueueRealTimeInfo(_AutoAccessor):
|
|
1740
1782
|
"""큐의 **실시간 상태.** :meth:`ILAdminQmgr.getQueueRealTimeInfo` 가 돌려준다.
|
|
1741
1783
|
|
|
1742
|
-
"소비자가 멈춘 것 같다" 를 진단할 때
|
|
1743
|
-
|
|
1784
|
+
"소비자가 멈춘 것 같다" 를 진단할 때 깊이와 미커밋 건수를 함께 보면 된다.
|
|
1785
|
+
|
|
1786
|
+
Note:
|
|
1787
|
+
**마지막 put/get 4항목은 6.3.3 미만 엔진 전용이다** - ``lastPutTime`` /
|
|
1788
|
+
``lastGetTime`` / ``lastPutClient`` / ``lastGetClient`` 넷을 말한다.
|
|
1789
|
+
6.3.3 에서 실시간 속성이 별도 조회로 분리되면서 엔진의 실시간 정보
|
|
1790
|
+
구조체에서 이 4개가 빠졌다 - 전송 규격에 자리는 남아 있지만 서버가
|
|
1791
|
+
채우지 않는다(직렬화에서 그 180바이트를 건너뛴다). 그래서 구엔진에
|
|
1792
|
+
붙었을 때만 값이 온다.
|
|
1793
|
+
|
|
1794
|
+
6.3.3+ 에서 "누가 마지막으로 이 큐를 건드렸는지" 를 보려면 세션 쪽을
|
|
1795
|
+
보라 - :meth:`ILAdminQmgr.getSessionList` 가 주는
|
|
1796
|
+
:class:`ILSessionProperty` 의 ``lastAccessObject`` 가 그 정보를 세션
|
|
1797
|
+
기준으로 담고 있다.
|
|
1744
1798
|
|
|
1745
1799
|
Attributes:
|
|
1746
1800
|
curDepth (int): 현재 깊이(메시지 수). 기본 0.
|
|
1747
1801
|
openTime (int): 큐가 열린 시각(epoch 밀리초). 기본 0.
|
|
1748
|
-
lastPutTime (int): 마지막 put 시각(epoch 밀리초).
|
|
1749
|
-
lastGetTime (int): 마지막 get 시각(epoch 밀리초).
|
|
1750
|
-
lastPutClient (str): 마지막으로 put 한 클라이언트 이름.
|
|
1751
|
-
|
|
1802
|
+
lastPutTime (int): 마지막 put 시각(epoch 밀리초). **엔진 6.3.3+ 는 항상 0**.
|
|
1803
|
+
lastGetTime (int): 마지막 get 시각(epoch 밀리초). **엔진 6.3.3+ 는 항상 0**.
|
|
1804
|
+
lastPutClient (str): 마지막으로 put 한 클라이언트 이름.
|
|
1805
|
+
**엔진 6.3.3+ 는 항상** ``""``.
|
|
1806
|
+
lastGetClient (str): 마지막으로 get 한 클라이언트 이름.
|
|
1807
|
+
**엔진 6.3.3+ 는 항상** ``""``.
|
|
1752
1808
|
numberOfClient (int): 이 큐를 열고 있는 클라이언트 수. 기본 0.
|
|
1753
1809
|
numberOfChannel (int): 이 큐를 열고 있는 채널 수. 기본 0.
|
|
1754
1810
|
uncommittedPut (int): 아직 커밋되지 않은 put 건수. 기본 0.
|
|
@@ -1889,6 +1945,43 @@ class ILQueueRealTimeInfo(_AutoAccessor):
|
|
|
1889
1945
|
ret += " " + str(partition)
|
|
1890
1946
|
return ret
|
|
1891
1947
|
|
|
1948
|
+
@classmethod
|
|
1949
|
+
def from_legacy_queue_blob(cls, ba: bytes) -> "ILQueueRealTimeInfo":
|
|
1950
|
+
"""**구버전(4096) 큐 속성 블롭**에서 실시간 정보를 뽑아 만든다.
|
|
1951
|
+
|
|
1952
|
+
Args:
|
|
1953
|
+
ba (bytes): 엔진 rev 3155 미만이 보낸 큐 속성 블롭(4096).
|
|
1954
|
+
|
|
1955
|
+
Returns:
|
|
1956
|
+
ILQueueRealTimeInfo: 채워진 실시간 정보 객체.
|
|
1957
|
+
|
|
1958
|
+
Note:
|
|
1959
|
+
구엔진은 실시간 정보 요청(``MIMQ_GET_QUEUE_LIVE_PROPERTY``)에 **큐 속성
|
|
1960
|
+
블롭 그대로** 답한다. 사용 현황 통계가 그 블롭 안에 들어 있기 때문이다
|
|
1961
|
+
(6.3.3 에서 별도 조회로 분리됐다). 그래서 같은 응답으로 속성 객체와
|
|
1962
|
+
이 객체를 모두 채울 수 있다.
|
|
1963
|
+
|
|
1964
|
+
:attr:`partitions` 는 **빈 목록**이 된다. 파티셔닝은 6.3.3 에서 들어온
|
|
1965
|
+
기능이라 구엔진 큐에는 파티션이라는 개념 자체가 없다 - 누락이 아니라
|
|
1966
|
+
사실이다.
|
|
1967
|
+
"""
|
|
1968
|
+
o = ILC.QUEUE_LEGACY_DIVERGE_OFFSET
|
|
1969
|
+
info = cls()
|
|
1970
|
+
info.curDepth = ba_pick_int(ba, 1740, 10)
|
|
1971
|
+
info.openTime = ba_pick_long(ba, o, 20)
|
|
1972
|
+
info.lastPutTime = ba_pick_long(ba, o + 20, 20)
|
|
1973
|
+
info.lastGetTime = ba_pick_long(ba, o + 40, 20)
|
|
1974
|
+
info.lastPutClient = ba_pick_string(ba, o + 60, 70)
|
|
1975
|
+
info.lastGetClient = ba_pick_string(ba, o + 130, 70)
|
|
1976
|
+
info.numberOfClient = ba_pick_int(ba, o + 200, 10)
|
|
1977
|
+
info.numberOfChannel = ba_pick_int(ba, o + 210, 10)
|
|
1978
|
+
# o+220 ~ o+300 : accumulated put/get/putSize/getSize (누적 총량).
|
|
1979
|
+
# 현행 유량 조회(ILQueueFlow)는 **구간별** 값이라 의미가 달라 쓰지 않는다.
|
|
1980
|
+
info.uncommittedPut = ba_pick_int(ba, o + 300, 10)
|
|
1981
|
+
info.uncommittedGet = ba_pick_int(ba, o + 310, 10)
|
|
1982
|
+
info.partitions = []
|
|
1983
|
+
return info
|
|
1984
|
+
|
|
1892
1985
|
|
|
1893
1986
|
class ILQueueFlow(_AutoAccessor):
|
|
1894
1987
|
"""큐의 유입/유출량 스냅샷 하나.
|
|
@@ -3603,17 +3696,24 @@ class ILClusterNode(_AutoAccessor):
|
|
|
3603
3696
|
최저값 = 초기 리더, 스위치백 우선순위). 사람이 타이핑하지는 않지만 **서버가
|
|
3604
3697
|
채번해 주지도 않는다** - ilsc 가 그렇듯 **클라이언트가 max+1 로 매겨 보낸다**
|
|
3605
3698
|
(0 은 비클러스터 예약값). ILClusterProperty.add_node() 가 대신 해 준다.
|
|
3606
|
-
|
|
3607
|
-
|
|
3699
|
+
**중복은 서버가 거부한다**(엔진 rev 3323+). 같은 nodeId 가 둘이면
|
|
3700
|
+
``MIMQE_INVALID_ARGUMENT (duplicate NodeId ...)``, 같은 host:port 가 둘이면
|
|
3701
|
+
``MIMQE_INVALID_ARGUMENT (duplicate node ...)`` 다. 0 을 여럿 보내는 것도
|
|
3702
|
+
같은 이유로 막힌다 - 그 전 엔진은 그대로 저장했고, 그러면 노드가 서로 다른
|
|
3703
|
+
id 를 받아 갈라졌다.
|
|
3608
3704
|
|
|
3609
3705
|
운영자가 다루는 식별자는 qmgrName + host:port 다.
|
|
3610
3706
|
|
|
3611
3707
|
Attributes:
|
|
3612
|
-
nodeId (int): 노드 번호. 기본 0. **1
|
|
3613
|
-
예약값). :meth:`ILClusterProperty.add_node` 가
|
|
3708
|
+
nodeId (int): 노드 번호. 기본 0. **1 이상이고 클러스터 안에서 유일**해야
|
|
3709
|
+
한다(0 은 비클러스터 예약값). :meth:`ILClusterProperty.add_node` 가
|
|
3710
|
+
max+1 로 채번하므로 직접 매길 일은 없다. 중복이면 서버가 거부한다
|
|
3711
|
+
(**엔진 rev 3323+**).
|
|
3614
3712
|
host (str): 노드의 IP 또는 호스트명. 기본 ``""``.
|
|
3615
3713
|
port (int): 노드 큐 관리자의 리스너 포트. 기본 0. 심판(witness) 노드일
|
|
3616
|
-
때는 **상대 ilcc 포트**를 넣는다.
|
|
3714
|
+
때는 **상대 ilcc 포트**를 넣는다. ``host:port`` 조합은 클러스터 안에서
|
|
3715
|
+
**유일**해야 한다 - 같은 큐 관리자를 두 번 넣으면 서버가 거부한다
|
|
3716
|
+
(**엔진 rev 3323+**).
|
|
3617
3717
|
status (int): **읽기 전용** 노드 상태. 서버가 채운다. 기본 0.
|
|
3618
3718
|
qmgrName (str): 노드 큐 관리자 이름. 기본 ``""``. **필수 값이다** -
|
|
3619
3719
|
클러스터 기동/정지가 이 이름으로 노드를 찾으므로 비어 있으면 그 노드가
|
|
@@ -3712,8 +3812,10 @@ class ILClusterControlResult(_AutoAccessor):
|
|
|
3712
3812
|
아무것도 싣지 않으므로 셋 다 0 / 빈 목록으로 온다 - **"빠진 노드가 없다"는
|
|
3713
3813
|
뜻이 아니라 "알 수 없다"는 뜻**이다. :attr:`known` 으로 구분한다.
|
|
3714
3814
|
|
|
3715
|
-
``withNodes=False`` 로 불렀다면
|
|
3716
|
-
|
|
3815
|
+
``withNodes=False`` 로 불렀다면 노드를 제어하지 않으므로 **셋 다 0 / 빈 목록**
|
|
3816
|
+
으로 온다(엔진 rev 3322+). "빠져서 0" 이 아니라 "안 하기로 해서 0" 인데,
|
|
3817
|
+
부른 쪽은 자기가 준 옵션을 아니까 구분된다. **rev 3317~3321 은 제어하지
|
|
3818
|
+
않았는데도 전체 노드 수**를 실었다.
|
|
3717
3819
|
|
|
3718
3820
|
Example::
|
|
3719
3821
|
|
|
@@ -3839,6 +3941,13 @@ class ILClusterProperty(_AutoAccessor):
|
|
|
3839
3941
|
있어야 한다. 하나라도 비면 서버가 ``MIMQE_INVALID_ARGUMENT`` 로 거절한다.
|
|
3840
3942
|
이미 만들어진 클러스터를 읽어오는 데는 제한이 없다.
|
|
3841
3943
|
|
|
3944
|
+
**접속 정보가 겹치면 생성이 실패한다.** ``pri_svc`` / ``sec_svc`` /
|
|
3945
|
+
``wit_svc`` 중 둘 이상이 같은 관리 서비스를 가리키면 서버가
|
|
3946
|
+
``MIMQE_INVALID_ARGUMENT`` 로 거부한다(**엔진 rev 3322+**). 같은 IP 라도
|
|
3947
|
+
관리 서비스 포트가 다르면 된다 - 비교는 ``"호스트:포트"`` 로 하며 포트
|
|
3948
|
+
생략은 9998 로 본다. rev 3322 미만 엔진은 검사하지 않으므로, 겹친 구성이
|
|
3949
|
+
거부 없이 만들어지고 조정자가 하나뿐인 상태가 된다.
|
|
3950
|
+
|
|
3842
3951
|
Example::
|
|
3843
3952
|
|
|
3844
3953
|
p = ILClusterProperty(
|
|
@@ -3959,6 +4068,17 @@ class ILClusterProperty(_AutoAccessor):
|
|
|
3959
4068
|
식별은 **주소(host:port)로 한다**. qmgrName 중복은 막지 않는다 -
|
|
3960
4069
|
HA 는 양 노드 이름이 같은 구성이 오히려 정상이다.
|
|
3961
4070
|
|
|
4071
|
+
**중복 등록은 서버가 거부한다**(엔진 rev 3323+). 막히는 것은 둘이다.
|
|
4072
|
+
|
|
4073
|
+
- 같은 ``host:port`` 를 두 번 - 한 큐 관리자를 두 노드로 등록하는 셈이다.
|
|
4074
|
+
ilcc 가 같은 대상에 두 번 하트비트하고, 클러스터 기동/정지가 같은 것을
|
|
4075
|
+
두 번 제어하며, 제어 수 집계도 부풀려진다.
|
|
4076
|
+
- 같은 ``nodeId`` 를 두 번 - **이쪽이 더 나쁘다.** 멤버십 통지가 "너의 id"
|
|
4077
|
+
를 내릴 때 어느 쪽인지 정할 수 없어 **노드가 서로 다른 id 를 받아
|
|
4078
|
+
갈라진다.**
|
|
4079
|
+
|
|
4080
|
+
rev 3323 미만 엔진은 둘 다 그대로 저장했다.
|
|
4081
|
+
|
|
3962
4082
|
Args:
|
|
3963
4083
|
qmgr_name (str): 노드 큐 관리자 이름. **필수다**(엔진 rev 3319+).
|
|
3964
4084
|
비우면 서버가 저장을 거부한다.
|
|
@@ -3974,11 +4094,19 @@ class ILClusterProperty(_AutoAccessor):
|
|
|
3974
4094
|
(이 API 의 오류 관례를 따른다 - 내장 예외를 쓰면 호출자가
|
|
3975
4095
|
ILException 계열만 잡다가 놓친다)
|
|
3976
4096
|
|
|
4097
|
+
|
|
3977
4098
|
Note:
|
|
3978
4099
|
**큐 관리자 이름은 필수다.** 원래는 표시용 필드였는데 클러스터
|
|
3979
4100
|
기동/정지가 이 이름으로 노드를 찾게 되면서 **제어 키로 승격**됐다.
|
|
3980
4101
|
비어 있으면 그 노드는 제어 대상에서 빠지므로, 엔진 rev 3319 부터
|
|
3981
4102
|
아예 저장을 거부한다. 여기서도 먼저 막는다.
|
|
4103
|
+
|
|
4104
|
+
Example::
|
|
4105
|
+
|
|
4106
|
+
p = svc.getClusterProperty("CC1")
|
|
4107
|
+
p.add_node("QM1", "10.0.0.1", 21001)
|
|
4108
|
+
p.add_node("QM2", "10.0.0.2", 21002) # nodeId 는 1, 2 로 자동 채번
|
|
4109
|
+
svc.setClusterProperty(p)
|
|
3982
4110
|
"""
|
|
3983
4111
|
if not qmgr_name:
|
|
3984
4112
|
raise ILOperationException(
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import inspect
|
|
4
|
+
import os
|
|
4
5
|
import time
|
|
5
6
|
import uuid
|
|
6
7
|
from typing import List
|
|
@@ -66,7 +67,12 @@ class ILQmgr:
|
|
|
66
67
|
# HA-3 클라이언트 페일오버: 접속 성공 시 캐싱한 클러스터 후보("host:port")
|
|
67
68
|
self.clusterAddresses: list[str] | None = None
|
|
68
69
|
self.clusterLeaderNodeId = -1
|
|
69
|
-
|
|
70
|
+
# endpoint 목록을 남겨 둘 파일. **지정했을 때만** 읽고 쓴다(기본 없음).
|
|
71
|
+
self.endpointCacheFile: str | None = None
|
|
72
|
+
# 트랜잭션 세션에서 **커밋되지 않은 작업이 걸려 있는지**.
|
|
73
|
+
# 성공한 put/get 마다 켜고 commit/rollback/접속 시 끈다.
|
|
74
|
+
# 자동 재접속을 허용할지 판단하는 데 쓴다(걸린 게 없으면 잃을 것도 없다).
|
|
75
|
+
self.txnPending = False
|
|
70
76
|
|
|
71
77
|
# --- context manager ---------------------------------------------------
|
|
72
78
|
def __enter__(self) -> "ILQmgr":
|
|
@@ -226,12 +232,13 @@ class ILQmgr:
|
|
|
226
232
|
def connect(
|
|
227
233
|
self,
|
|
228
234
|
host: str,
|
|
229
|
-
port: int,
|
|
230
|
-
client_name: str = "",
|
|
235
|
+
port: int | str | bool | None = None,
|
|
236
|
+
client_name: str | bool = "",
|
|
231
237
|
auto_commit: bool = False,
|
|
232
238
|
*,
|
|
233
239
|
clientName: str | None = None,
|
|
234
240
|
isAutoCommit: bool | None = None,
|
|
241
|
+
_allow_redirect: bool = True,
|
|
235
242
|
) -> None:
|
|
236
243
|
"""큐 관리자에 접속해 세션을 연다.
|
|
237
244
|
|
|
@@ -240,9 +247,13 @@ class ILQmgr:
|
|
|
240
247
|
찾아갈 수 있게 한다.
|
|
241
248
|
|
|
242
249
|
Args:
|
|
243
|
-
host (str): 큐 관리자의 IP 또는 호스트명.
|
|
250
|
+
host (str): 큐 관리자의 IP 또는 호스트명. **``"host:port"`` 또는 콤마로
|
|
251
|
+
이은 목록** (``"h1:p1,h2:p2"``)을 줄 수도 있다 - 그 경우 ``port`` 는
|
|
252
|
+
생략하고 뒤 인자가 한 칸씩 당겨진다
|
|
253
|
+
(``connect("h1:p1,h2:p2", "APP", True)``).
|
|
244
254
|
port (int): 큐 관리자의 리스너 포트. **관리 서비스(ilsvc) 포트가 아니다** -
|
|
245
255
|
관리 포트(보통 9998)는 :class:`~ilink.admin.ILAdminService` 쪽이다.
|
|
256
|
+
주소 목록 형태로 줬으면 지정하지 않는다.
|
|
246
257
|
client_name (str): 이 세션의 클라이언트 이름. 기본 ``""`` 이면 **호출한
|
|
247
258
|
모듈 이름을 자동으로** 쓴다. 큐 관리자 세션 목록에 그대로 보이므로
|
|
248
259
|
운영상 알아볼 수 있는 이름을 주는 편이 좋다.
|
|
@@ -260,13 +271,63 @@ class ILQmgr:
|
|
|
260
271
|
세 번째 인자에 ``bool`` 을 주면 ``auto_commit``
|
|
261
272
|
으로 해석한다 - ``connect(host, port, True)`` 가 동작한다.
|
|
262
273
|
|
|
274
|
+
.. note:: 클러스터에는 **주소 하나면 된다**
|
|
275
|
+
|
|
276
|
+
접속에 성공하면 핸드셰이크 직후 나머지 노드 주소를 서버에서 받아
|
|
277
|
+
캐싱한다(:meth:`get_cluster_addresses` 로 확인). 이후 리더가 바뀌어도
|
|
278
|
+
:meth:`reconnect` 가 그 후보로 새 리더를 찾아간다.
|
|
279
|
+
|
|
280
|
+
**주소 목록은 최초 접속 부트스트랩용이다.** 캐싱은 접속에 성공한
|
|
281
|
+
뒤에 일어나므로, 하나만 준 그 주소가 첫 접속 시점에 죽어 있으면 갈
|
|
282
|
+
곳이 없다. 목록을 주면 순회하며 붙고, 팔로워가
|
|
283
|
+
``MIMQE_FOLLOWER_NODE`` 로 거절하면 응답에 실린 리더 힌트를 맨 앞으로
|
|
284
|
+
올려 재시도한다(최대 2회전).
|
|
285
|
+
|
|
286
|
+
**단일 주소 형태는 리더 힌트를 따라가지 않는다** - 팔로워를 지목하면
|
|
287
|
+
그대로 예외가 난다. 그때는 :meth:`reconnect` 를 부르면 리더로 넘어간다.
|
|
288
|
+
|
|
263
289
|
Example::
|
|
264
290
|
|
|
265
291
|
qmgr = ILQmgr()
|
|
266
292
|
qmgr.connect("192.168.0.10", 5000) # 트랜잭션 세션
|
|
267
293
|
qmgr.connect("192.168.0.10", 5000, auto_commit=True) # 자동 커밋
|
|
268
294
|
qmgr.connect("192.168.0.10", 5000, client_name="billing-svc")
|
|
295
|
+
|
|
296
|
+
# 클러스터 - 주소 하나면 충분하다(나머지는 접속 후 캐싱된다)
|
|
297
|
+
qmgr.connect("10.0.0.1", 5000, "APP", True)
|
|
298
|
+
qmgr.getClusterAddresses() # ['10.0.0.1:5000', '10.0.0.2:5000']
|
|
299
|
+
|
|
300
|
+
# 첫 접속 시점의 장애까지 대비하려면 목록으로 준다(포트 인자 없음)
|
|
301
|
+
qmgr.connect("10.0.0.1:5000,10.0.0.2:5000", "APP", True)
|
|
269
302
|
"""
|
|
303
|
+
# 주소 목록 형태 - 자바 connect(addressList, clientName, isAutoCommit) 대응.
|
|
304
|
+
# "host:port" 하나든 콤마로 이은 여럿이든 같은 경로로 처리한다.
|
|
305
|
+
# 포트를 따로 받지 않으므로 뒤 인자가 한 칸씩 당겨진 형태다.
|
|
306
|
+
# 포트를 int 로 함께 준 호출은 기존 경로 그대로 둔다(오탐 방지).
|
|
307
|
+
if isinstance(host, str) and ("," in host or (":" in host and not isinstance(port, int))):
|
|
308
|
+
if isinstance(port, bool):
|
|
309
|
+
auto_commit = port
|
|
310
|
+
elif isinstance(port, str):
|
|
311
|
+
if isinstance(client_name, bool):
|
|
312
|
+
auto_commit = client_name
|
|
313
|
+
client_name = port
|
|
314
|
+
elif port is not None:
|
|
315
|
+
raise ILSessionException(
|
|
316
|
+
ILRC.MIMQE_INVALID_ADDRESS,
|
|
317
|
+
"address list form takes no port argument : [%s:%s]" % (host, port),
|
|
318
|
+
)
|
|
319
|
+
if clientName is not None:
|
|
320
|
+
client_name = clientName
|
|
321
|
+
if isAutoCommit is not None:
|
|
322
|
+
auto_commit = isAutoCommit
|
|
323
|
+
candidates = [a.strip() for a in host.split(",") if a.strip()]
|
|
324
|
+
if not candidates:
|
|
325
|
+
raise ILSessionException(
|
|
326
|
+
ILRC.MIMQE_INVALID_ADDRESS, "empty address list"
|
|
327
|
+
)
|
|
328
|
+
self._connect_any(candidates, client_name or "", bool(auto_commit))
|
|
329
|
+
return
|
|
330
|
+
|
|
270
331
|
# Support legacy positional bool for client_name (Java-style overload)
|
|
271
332
|
if isinstance(client_name, bool):
|
|
272
333
|
auto_commit = client_name
|
|
@@ -302,6 +363,7 @@ class ILQmgr:
|
|
|
302
363
|
|
|
303
364
|
self.sessionId = conn_res_msg.getArg1()
|
|
304
365
|
self.isAutoCommitFlag = auto_commit
|
|
366
|
+
self.txnPending = False
|
|
305
367
|
if self.sessionId == "":
|
|
306
368
|
self.disconnect()
|
|
307
369
|
raise ILSessionException(
|
|
@@ -352,9 +414,36 @@ class ILQmgr:
|
|
|
352
414
|
# 막으면 정상 클러스터에서 페일오버 후보 캐싱이 조용히 사라진다.
|
|
353
415
|
if not (0 < self.revision < 3265):
|
|
354
416
|
self._fetch_cluster_info()
|
|
355
|
-
|
|
356
|
-
raise
|
|
417
|
+
self._save_endpoint_cache()
|
|
357
418
|
except Exception as exc:
|
|
419
|
+
# 팔로워를 지목했으면 응답에 실린 리더 힌트로 **한 번** 옮겨 붙는다.
|
|
420
|
+
# 앱이 옛 리더 주소만 들고 재기동해도 승격된 리더를 찾아간다 -
|
|
421
|
+
# 주소를 하나만 줘도 되게 한다는 설계 의도가 여기까지다.
|
|
422
|
+
if _allow_redirect:
|
|
423
|
+
hint = self._extract_leader_hint(exc)
|
|
424
|
+
if hint:
|
|
425
|
+
colon = hint.rfind(":")
|
|
426
|
+
try:
|
|
427
|
+
self.service.shutdown()
|
|
428
|
+
except Exception:
|
|
429
|
+
pass
|
|
430
|
+
self.connect(
|
|
431
|
+
hint[:colon],
|
|
432
|
+
int(hint[colon + 1:]),
|
|
433
|
+
client_name,
|
|
434
|
+
auto_commit,
|
|
435
|
+
_allow_redirect=False,
|
|
436
|
+
)
|
|
437
|
+
return
|
|
438
|
+
# 힌트를 줄 상대조차 없으면(노드가 죽어 있음) 파일에 남겨 둔
|
|
439
|
+
# 후보로 한 번 더 돈다. 경로를 지정했을 때만 동작한다.
|
|
440
|
+
cached = [a for a in self._load_endpoint_cache()
|
|
441
|
+
if a != "%s:%s" % (host, port)]
|
|
442
|
+
if cached:
|
|
443
|
+
self._connect_any(cached, client_name, auto_commit)
|
|
444
|
+
return
|
|
445
|
+
if isinstance(exc, _PASSTHROUGH_EXCEPTIONS):
|
|
446
|
+
raise
|
|
358
447
|
raise ILException(exc) from exc
|
|
359
448
|
|
|
360
449
|
def disconnect(self) -> None:
|
|
@@ -428,6 +517,7 @@ class ILQmgr:
|
|
|
428
517
|
committed = qmgr.commit() # 두 건이 원자적으로 확정된다
|
|
429
518
|
"""
|
|
430
519
|
try:
|
|
520
|
+
self.txnPending = False
|
|
431
521
|
report_msg = self.service.request(ILC.MIMQ_COMMIT_TRANSACTION_REQUEST)
|
|
432
522
|
if report_msg is None:
|
|
433
523
|
raise ILOperationException("MIMQE_INTERNAL_ERROR")
|
|
@@ -460,6 +550,7 @@ class ILQmgr:
|
|
|
460
550
|
"""
|
|
461
551
|
try:
|
|
462
552
|
self.set_last_access_time()
|
|
553
|
+
self.txnPending = False
|
|
463
554
|
report_msg = self.service.request(ILC.MIMQ_ROLLBACK_REQUEST)
|
|
464
555
|
if (
|
|
465
556
|
report_msg is not None
|
|
@@ -651,27 +742,110 @@ class ILQmgr:
|
|
|
651
742
|
return list(self.clusterAddresses) if self.clusterAddresses else None
|
|
652
743
|
|
|
653
744
|
def set_auto_reconnect(self, value: bool) -> None:
|
|
654
|
-
"""
|
|
745
|
+
"""**더 이상 쓰이지 않는다 - 불러도 아무 일도 일어나지 않는다.**
|
|
655
746
|
|
|
656
747
|
Args:
|
|
657
|
-
value (bool):
|
|
748
|
+
value (bool): 무시된다.
|
|
658
749
|
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
750
|
+
Note:
|
|
751
|
+
0.4.4 부터 자동 재접속은 **옵션이 아니라 기본 동작**이다. 접속 때
|
|
752
|
+
받아 둔 endpoint 목록이 있으면(= 클러스터) 통신 장애로 실패한
|
|
753
|
+
put/get 을 페일오버 재접속 후 1회 자동 재시도한다. 다만 **미커밋
|
|
754
|
+
트랜잭션이 걸려 있으면** 재접속으로 그것이 유실되므로 예외를 그대로
|
|
755
|
+
올린다 - 앱이 트랜잭션을 처음부터 다시 수행해야 한다.
|
|
756
|
+
|
|
757
|
+
메서드 자체는 기존 코드가 깨지지 않도록 남겨 둔 것이다.
|
|
758
|
+
|
|
759
|
+
.. deprecated:: 0.4.4
|
|
760
|
+
자동 재접속은 항상 켜져 있다.
|
|
665
761
|
"""
|
|
666
|
-
|
|
762
|
+
# no-op : 자동 재접속은 이제 조건부 기본 동작이다(endpoint 목록 + 미커밋 없음).
|
|
667
763
|
|
|
668
764
|
def is_auto_reconnect(self) -> bool:
|
|
669
|
-
"""
|
|
765
|
+
"""**항상 True 다.** 0.4.4 부터 자동 재접속은 옵션이 아니다.
|
|
670
766
|
|
|
671
767
|
Returns:
|
|
672
|
-
bool:
|
|
768
|
+
bool: 항상 True.
|
|
769
|
+
|
|
770
|
+
Note:
|
|
771
|
+
.. deprecated:: 0.4.4
|
|
772
|
+
자동 재접속은 항상 켜져 있다.
|
|
673
773
|
"""
|
|
674
|
-
return
|
|
774
|
+
return True
|
|
775
|
+
|
|
776
|
+
def set_endpoint_cache_file(self, path: str | None) -> None:
|
|
777
|
+
"""클러스터 endpoint 목록을 남겨 둘 **파일 경로**를 지정한다.
|
|
778
|
+
|
|
779
|
+
Args:
|
|
780
|
+
path (str | None): 파일 경로. None 이면 파일을 쓰지도 읽지도 않는다(기본).
|
|
781
|
+
|
|
782
|
+
Note:
|
|
783
|
+
**접속하기 전에 지정해야 한다.** 지정하면
|
|
784
|
+
|
|
785
|
+
* :meth:`connect` 가 설정 주소로 붙지 못했을 때 이 파일의 주소들을
|
|
786
|
+
**보조 후보**로 써서 한 번 더 시도한다.
|
|
787
|
+
* 접속에 성공해 서버에서 받은 목록이 달라졌으면 파일을 새로 쓴다.
|
|
788
|
+
|
|
789
|
+
설정 주소를 **항상 먼저** 쓴다 - 파일이 낡아 이미 빠진 노드를 담고
|
|
790
|
+
있어도 정상 경로가 그것 때문에 느려지지 않는다.
|
|
791
|
+
|
|
792
|
+
입출력 실패는 **전부 조용히 무시한다.** 캐시 때문에 접속이 깨지면
|
|
793
|
+
안 되기 때문이다(경로가 없거나 권한이 없어도 그냥 동작한다).
|
|
794
|
+
|
|
795
|
+
한 줄에 ``host:port`` 하나씩 적힌 평범한 텍스트라 사람이 열어
|
|
796
|
+
고쳐도 된다. ``#`` 으로 시작하는 줄은 주석이다.
|
|
797
|
+
|
|
798
|
+
Example::
|
|
799
|
+
|
|
800
|
+
q = ILQmgr()
|
|
801
|
+
q.set_endpoint_cache_file("/var/run/myapp/ilink-cluster.txt")
|
|
802
|
+
q.connect("10.0.0.1", 5000, "APP", True)
|
|
803
|
+
"""
|
|
804
|
+
self.endpointCacheFile = path
|
|
805
|
+
|
|
806
|
+
def setEndpointCacheFile(self, path: str | None) -> None:
|
|
807
|
+
""":meth:`set_endpoint_cache_file` 의 camelCase 별칭.
|
|
808
|
+
|
|
809
|
+
Args:
|
|
810
|
+
path (str | None): 파일 경로.
|
|
811
|
+
"""
|
|
812
|
+
self.set_endpoint_cache_file(path)
|
|
813
|
+
|
|
814
|
+
def _load_endpoint_cache(self) -> list[str]:
|
|
815
|
+
# 파일에서 보조 후보를 읽는다. 없거나 못 읽으면 빈 목록.
|
|
816
|
+
if not self.endpointCacheFile:
|
|
817
|
+
return []
|
|
818
|
+
try:
|
|
819
|
+
out = []
|
|
820
|
+
with open(self.endpointCacheFile, "r", encoding="utf-8") as fp:
|
|
821
|
+
for line in fp:
|
|
822
|
+
line = line.strip()
|
|
823
|
+
if not line or line.startswith("#"):
|
|
824
|
+
continue
|
|
825
|
+
if line.rfind(":") > 0 and line not in out:
|
|
826
|
+
out.append(line)
|
|
827
|
+
return out
|
|
828
|
+
except Exception:
|
|
829
|
+
return []
|
|
830
|
+
|
|
831
|
+
def _save_endpoint_cache(self) -> None:
|
|
832
|
+
# 받은 목록이 파일과 다르면 원자적으로 바꿔 쓴다(임시 파일 + rename).
|
|
833
|
+
if not self.endpointCacheFile or not self.clusterAddresses:
|
|
834
|
+
return
|
|
835
|
+
try:
|
|
836
|
+
if self._load_endpoint_cache() == list(self.clusterAddresses):
|
|
837
|
+
return
|
|
838
|
+
tmp = "%s.%d.tmp" % (self.endpointCacheFile, os.getpid())
|
|
839
|
+
with open(tmp, "w", encoding="utf-8", newline="\n") as fp:
|
|
840
|
+
fp.write("# ilink cluster endpoints - 라이브러리가 자동으로 갱신한다\n")
|
|
841
|
+
for addr in self.clusterAddresses:
|
|
842
|
+
fp.write(addr + "\n")
|
|
843
|
+
os.replace(tmp, self.endpointCacheFile)
|
|
844
|
+
except Exception:
|
|
845
|
+
try:
|
|
846
|
+
os.remove(tmp)
|
|
847
|
+
except Exception:
|
|
848
|
+
pass
|
|
675
849
|
|
|
676
850
|
@staticmethod
|
|
677
851
|
def _extract_leader_hint(exc: Exception) -> str | None:
|
|
@@ -714,7 +888,7 @@ class ILQmgr:
|
|
|
714
888
|
except Exception:
|
|
715
889
|
i += 1; continue
|
|
716
890
|
try:
|
|
717
|
-
self.connect(host, port, client_name, auto_commit)
|
|
891
|
+
self.connect(host, port, client_name, auto_commit, _allow_redirect=False)
|
|
718
892
|
return
|
|
719
893
|
except Exception as exc:
|
|
720
894
|
last_error = exc
|
|
@@ -757,9 +931,26 @@ class ILQmgr:
|
|
|
757
931
|
candidates.insert(0, last)
|
|
758
932
|
self._connect_any(candidates, self.clientName, self.isAutoCommitFlag)
|
|
759
933
|
|
|
934
|
+
@staticmethod
|
|
935
|
+
def _is_connection_error(exc: BaseException) -> bool:
|
|
936
|
+
# 자바 ILQueue.isConnectionError 대응 - 원인 사슬에 통신 계열이 있으면 참.
|
|
937
|
+
t: BaseException | None = exc
|
|
938
|
+
seen = 0
|
|
939
|
+
while t is not None and seen < 10:
|
|
940
|
+
if isinstance(t, (OSError, ILSessionException)):
|
|
941
|
+
return True
|
|
942
|
+
t = getattr(t, "__cause__", None)
|
|
943
|
+
seen += 1
|
|
944
|
+
return False
|
|
945
|
+
|
|
760
946
|
def _try_auto_reconnect(self) -> bool:
|
|
761
|
-
# 통신 장애 시 내부 호출.
|
|
762
|
-
|
|
947
|
+
# 통신 장애 시 내부 호출. 옵션이 아니라 기본 동작이다.
|
|
948
|
+
# 접속 때 받아 둔 endpoint 목록이 없으면 갈 곳이 없다(비클러스터/구엔진).
|
|
949
|
+
# 미커밋 트랜잭션이 걸려 있으면 재접속으로 그것이 유실되므로 예외를 그대로
|
|
950
|
+
# 올린다 - 앱이 트랜잭션을 처음부터 다시 수행해야 한다.
|
|
951
|
+
if not self.clusterAddresses:
|
|
952
|
+
return False
|
|
953
|
+
if not self.isAutoCommitFlag and self.txnPending:
|
|
763
954
|
return False
|
|
764
955
|
try:
|
|
765
956
|
self.reconnect()
|
|
@@ -1243,6 +1434,17 @@ class ILQueue:
|
|
|
1243
1434
|
raise TypeError("unsupported message type")
|
|
1244
1435
|
|
|
1245
1436
|
def _put_msg(self, msg: ILDataMsg) -> str:
|
|
1437
|
+
try:
|
|
1438
|
+
return self._put_msg_once(msg)
|
|
1439
|
+
except Exception as exc:
|
|
1440
|
+
# HA-3: 통신 장애 + 오토커밋 세션이면 페일오버 재접속 후 1회 재시도.
|
|
1441
|
+
# endpoint 목록이 있고 미커밋 트랜잭션이 없을 때만 재접속한다.
|
|
1442
|
+
if ILQmgr._is_connection_error(exc) and self.qmgr._try_auto_reconnect():
|
|
1443
|
+
self.conn = self.qmgr.get_conn()
|
|
1444
|
+
return self._put_msg_once(msg)
|
|
1445
|
+
raise
|
|
1446
|
+
|
|
1447
|
+
def _put_msg_once(self, msg: ILDataMsg) -> str:
|
|
1246
1448
|
try:
|
|
1247
1449
|
msg_id = msg.getHeader().getMsgId()
|
|
1248
1450
|
if msg_id == "":
|
|
@@ -1260,6 +1462,11 @@ class ILQueue:
|
|
|
1260
1462
|
if report_msg.getReportMsg().getCode() != ILC.MIMQ_SUCCESSFUL_PUT:
|
|
1261
1463
|
raise ILOperationException(report_msg.getReportMsg().getArg2())
|
|
1262
1464
|
self.qmgr.set_last_access_time()
|
|
1465
|
+
# 트랜잭션 세션이면 이제 **커밋 안 된 작업이 걸려 있다.**
|
|
1466
|
+
# 실패한 연산은 표시하지 않는다 - 세션이 끊기면 서버가 버리므로
|
|
1467
|
+
# 재접속 후 다시 보내도 중복이 생기지 않는다.
|
|
1468
|
+
if not self.qmgr.is_auto_commit():
|
|
1469
|
+
self.qmgr.txnPending = True
|
|
1263
1470
|
return msg_id
|
|
1264
1471
|
except _PASSTHROUGH_EXCEPTIONS:
|
|
1265
1472
|
raise
|
|
@@ -1333,6 +1540,11 @@ class ILQueue:
|
|
|
1333
1540
|
if report_msg.getReportMsg().getCode() != ILC.MIMQ_SUCCESSFUL_PUT:
|
|
1334
1541
|
raise ILOperationException(report_msg.getReportMsg().getArg2())
|
|
1335
1542
|
self.qmgr.set_last_access_time()
|
|
1543
|
+
# 트랜잭션 세션이면 이제 **커밋 안 된 작업이 걸려 있다.**
|
|
1544
|
+
# 실패한 연산은 표시하지 않는다 - 세션이 끊기면 서버가 버리므로
|
|
1545
|
+
# 재접속 후 다시 보내도 중복이 생기지 않는다.
|
|
1546
|
+
if not self.qmgr.is_auto_commit():
|
|
1547
|
+
self.qmgr.txnPending = True
|
|
1336
1548
|
return msg_ids
|
|
1337
1549
|
except _PASSTHROUGH_EXCEPTIONS:
|
|
1338
1550
|
raise
|
|
@@ -1403,6 +1615,21 @@ class ILQueue:
|
|
|
1403
1615
|
msg = q.get("abc-123", MatchOption.MATCH_MSG_ID) # 메시지 ID 로
|
|
1404
1616
|
msg = q.get("corr-456", MatchOption.MATCH_CORREL_ID) # 상관 ID 로
|
|
1405
1617
|
"""
|
|
1618
|
+
try:
|
|
1619
|
+
return self._get_once(matchId, option)
|
|
1620
|
+
except ILNoMsgException:
|
|
1621
|
+
raise
|
|
1622
|
+
except Exception as exc:
|
|
1623
|
+
# HA-3: 통신 장애 + 오토커밋 세션이면 페일오버 재접속 후 1회 재시도.
|
|
1624
|
+
# endpoint 목록이 있고 미커밋 트랜잭션이 없을 때만 재접속한다.
|
|
1625
|
+
if ILQmgr._is_connection_error(exc) and self.qmgr._try_auto_reconnect():
|
|
1626
|
+
self.conn = self.qmgr.get_conn()
|
|
1627
|
+
return self._get_once(matchId, option)
|
|
1628
|
+
raise
|
|
1629
|
+
|
|
1630
|
+
def _get_once(
|
|
1631
|
+
self, matchId: str | None = None, option: MatchOption = MatchOption.NONE
|
|
1632
|
+
) -> ILDataMsg:
|
|
1406
1633
|
try:
|
|
1407
1634
|
ext = None
|
|
1408
1635
|
if matchId:
|
|
@@ -1426,6 +1653,8 @@ class ILQueue:
|
|
|
1426
1653
|
self.qmgr.set_last_access_time()
|
|
1427
1654
|
msg = self.conn.recv(timeout)
|
|
1428
1655
|
if msg.getType() == ILC.MIMQ_DATA_MESSAGE:
|
|
1656
|
+
if not self.qmgr.is_auto_commit():
|
|
1657
|
+
self.qmgr.txnPending = True
|
|
1429
1658
|
return msg.getDataMsg()
|
|
1430
1659
|
|
|
1431
1660
|
if msg.getType() != ILC.MIMQ_REPORT_MESSAGE:
|
|
@@ -1500,6 +1729,8 @@ class ILQueue:
|
|
|
1500
1729
|
offset += length
|
|
1501
1730
|
temp_msg = ILMsg(ba)
|
|
1502
1731
|
data_msgs.append(temp_msg.getDataMsg())
|
|
1732
|
+
if data_msgs and not self.qmgr.is_auto_commit():
|
|
1733
|
+
self.qmgr.txnPending = True
|
|
1503
1734
|
return data_msgs
|
|
1504
1735
|
|
|
1505
1736
|
if msg.getType() != ILC.MIMQ_REPORT_MESSAGE:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: jetstream-api
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.4
|
|
4
4
|
Summary: Python client API for iLink M.O.M. (Message Oriented Middleware)
|
|
5
5
|
Author: snowjeans
|
|
6
6
|
License: Boost Software License - Version 1.0 - August 17th, 2003
|
|
@@ -137,15 +137,32 @@ print(adm.getTopicList())
|
|
|
137
137
|
클러스터 큐 관리자에 접속하면 후보 주소를 캐싱해 두었다가, 리더가 바뀌어도 따라갑니다.
|
|
138
138
|
|
|
139
139
|
```python
|
|
140
|
-
qmgr.
|
|
141
|
-
print(qmgr.get_cluster_addresses())
|
|
142
|
-
qmgr.reconnect()
|
|
140
|
+
qmgr.connect("10.0.0.1", 5000, "APP", True) # 주소 하나면 됩니다
|
|
141
|
+
print(qmgr.get_cluster_addresses()) # ['10.0.0.1:5000', '10.0.0.2:5000']
|
|
142
|
+
qmgr.reconnect() # 새 리더를 찾아 재접속
|
|
143
|
+
|
|
144
|
+
# 첫 접속 시점의 장애까지 대비하려면 목록으로 (포트 인자 없음)
|
|
145
|
+
qmgr.connect("10.0.0.1:5000,10.0.0.2:5000", "APP", True)
|
|
143
146
|
```
|
|
144
147
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
148
|
+
접속에 성공하면 핸드셰이크 직후 나머지 노드 주소를 서버에서 받아 캐싱하므로
|
|
149
|
+
**주소는 하나만 주면 됩니다.** 목록은 첫 접속 시점의 장애까지 대비할 때 씁니다.
|
|
150
|
+
|
|
151
|
+
### 0.4.4 변경 - 자동 재접속은 옵션이 아니라 기본 동작
|
|
152
|
+
|
|
153
|
+
캐싱된 endpoint 목록이 있으면(= 클러스터) 통신 장애로 실패한 put/get 을 페일오버
|
|
154
|
+
재접속 후 **한 번 자동 재시도합니다.** `set_auto_reconnect()` 는 더 이상 필요 없으며,
|
|
155
|
+
불러도 무시됩니다(기존 코드 호환용으로만 남겨 두었습니다).
|
|
156
|
+
|
|
157
|
+
| 상황 | 동작 |
|
|
158
|
+
|---|---|
|
|
159
|
+
| 미커밋 트랜잭션 **있음** | 예외를 올립니다. 앱이 `reconnect()` 후 트랜잭션을 처음부터 다시 수행 |
|
|
160
|
+
| 미커밋 트랜잭션 **없음** (auto-commit 포함) | 내부에서 재접속 후 1회 재시도 |
|
|
161
|
+
| endpoint 목록 없음 (비클러스터/구엔진) | 예외를 올립니다 - 갈 곳이 없습니다 |
|
|
162
|
+
|
|
163
|
+
> 재시도는 **at-least-once** 입니다. 서버가 처리한 뒤 응답이 유실된 시점에 재시도하면
|
|
164
|
+
> **중복 put 이 생길 수 있습니다.** 멱등하지 않은 처리라면 메시지 ID 로 중복을 걸러야
|
|
165
|
+
> 합니다. 미커밋 트랜잭션이 없을 때만 재시도하므로 트랜잭션 유실은 없습니다.
|
|
149
166
|
|
|
150
167
|
## 0.4.0 신규 - 관리 연결 하나로 pub/sub
|
|
151
168
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|