jetstream-api 0.19.3__tar.gz → 0.20.0__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.19.3 → jetstream_api-0.20.0}/PKG-INFO +55 -2
- {jetstream_api-0.19.3 → jetstream_api-0.20.0}/README.md +54 -1
- {jetstream_api-0.19.3 → jetstream_api-0.20.0}/ilink/__init__.py +1 -1
- jetstream_api-0.20.0/ilink/_qleader.py +386 -0
- {jetstream_api-0.19.3 → jetstream_api-0.20.0}/ilink/common.py +10 -5
- {jetstream_api-0.19.3 → jetstream_api-0.20.0}/ilink/producer.py +64 -24
- {jetstream_api-0.19.3 → jetstream_api-0.20.0}/ilink/properties.py +6264 -6208
- {jetstream_api-0.19.3 → jetstream_api-0.20.0}/ilink/qmgr.py +203 -6
- {jetstream_api-0.19.3 → jetstream_api-0.20.0}/jetstream_api.egg-info/PKG-INFO +55 -2
- {jetstream_api-0.19.3 → jetstream_api-0.20.0}/jetstream_api.egg-info/SOURCES.txt +1 -0
- {jetstream_api-0.19.3 → jetstream_api-0.20.0}/pyproject.toml +1 -1
- {jetstream_api-0.19.3 → jetstream_api-0.20.0}/LICENSE.txt +0 -0
- {jetstream_api-0.19.3 → jetstream_api-0.20.0}/ilink/_codec.py +0 -0
- {jetstream_api-0.19.3 → jetstream_api-0.20.0}/ilink/_consumer.py +0 -0
- {jetstream_api-0.19.3 → jetstream_api-0.20.0}/ilink/_groupwire.py +0 -0
- {jetstream_api-0.19.3 → jetstream_api-0.20.0}/ilink/admin.py +0 -0
- {jetstream_api-0.19.3 → jetstream_api-0.20.0}/ilink/admintopic.py +0 -0
- {jetstream_api-0.19.3 → jetstream_api-0.20.0}/ilink/connectivity.py +0 -0
- {jetstream_api-0.19.3 → jetstream_api-0.20.0}/ilink/exception.py +0 -0
- {jetstream_api-0.19.3 → jetstream_api-0.20.0}/ilink/msg.py +0 -0
- {jetstream_api-0.19.3 → jetstream_api-0.20.0}/ilink/partitioner.py +0 -0
- {jetstream_api-0.19.3 → jetstream_api-0.20.0}/ilink/pattern.py +0 -0
- {jetstream_api-0.19.3 → jetstream_api-0.20.0}/ilink/py.typed +0 -0
- {jetstream_api-0.19.3 → jetstream_api-0.20.0}/ilink/topic.py +0 -0
- {jetstream_api-0.19.3 → jetstream_api-0.20.0}/ilink/util.py +0 -0
- {jetstream_api-0.19.3 → jetstream_api-0.20.0}/jetstream_api.egg-info/dependency_links.txt +0 -0
- {jetstream_api-0.19.3 → jetstream_api-0.20.0}/jetstream_api.egg-info/requires.txt +0 -0
- {jetstream_api-0.19.3 → jetstream_api-0.20.0}/jetstream_api.egg-info/top_level.txt +0 -0
- {jetstream_api-0.19.3 → jetstream_api-0.20.0}/setup.cfg +0 -0
- {jetstream_api-0.19.3 → jetstream_api-0.20.0}/tests/test_example.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: jetstream-api
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.20.0
|
|
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
|
|
@@ -60,6 +60,59 @@ lz4 배치 압축을 쓰려면 `pip install "jetstream-api[lz4]"`, zstd 배치
|
|
|
60
60
|
## 설치 방법
|
|
61
61
|
pip install jetstream-api
|
|
62
62
|
|
|
63
|
+
## 0.20.0 변경 - 큐 묶음 리더 라우팅, 큐 속성 leaderGroup (자바 v2.16.0 과 같음, 짝 엔진 7.0.1.3512 이상)
|
|
64
|
+
|
|
65
|
+
> 큐 묶음(LEADERGROUP) 라우팅은 엔진 **7.0.1.3540** 이상에서 묶음 모드가 켜졌을 때만 동작합니다(묶음 모드는 엔진 3-b3 에서 켜집니다).
|
|
66
|
+
> 그 밖의 엔진(옛 판 · 묶음 모드 꺼짐)에서는 0.19.5 와 같이 동작하고, 7.0.1.3540 이상이면 세션마다 처음 큐를 쓸 때 조회 왕복이 한 번
|
|
67
|
+
> 늡니다. FLUSH TOPIC 과 겹친 발행의 유실 0 은 지금처럼 엔진 7.0.1.3527 이상입니다(0.19.5 절).
|
|
68
|
+
|
|
69
|
+
- **큐 묶음 리더 라우팅**(엔진 QLOOKUP-1): 클러스터의 큐마다 리더가 다를 수 있다(같은 묶음의 큐는 같은 리더). `ILQmgr` 는 처음
|
|
70
|
+
큐를 쓸 때 그 큐의 리더를 묻고(3397 "Q"), 리더 노드마다 연결 하나(세션 이름 `<클라이언트 이름>-Q<노드 id>`)로 그 큐의 요청을
|
|
71
|
+
보냅니다. 공개 API 는 바뀌지 않습니다. 앱 세션의 접속 능력 선언에 `qlead=1` 이 실립니다.
|
|
72
|
+
- `access_queue(name)`(존재 확인)은 묶음 모드면 그 조회 하나로 존재와 리더를 함께 봅니다.
|
|
73
|
+
- 자동 확인 세션: 리더가 옮겨 가 엔진이 `MIMQE_FOLLOWER_NODE (leader=…)` 로 거절하면 다시 물어 **한 번** 새 리더로 다시 보냅니다
|
|
74
|
+
(그 요청은 적용되지 않은 것이 확정입니다). 큐 연결이 끊겨도 한 번 다시 보냅니다. 리더가 정해지지 않은 동안은 세션 타임아웃까지
|
|
75
|
+
기다리고, 끝내 없으면 `MIMQE_NOT_AVAILABLE (queue leader for <큐>)` 입니다. 이동 순간의 커밋은 지금처럼
|
|
76
|
+
`MIMQE_CLUSTER_HALTED (... commit in doubt)` 로 올 수 있습니다(다시 보내지 않습니다).
|
|
77
|
+
- **트랜잭션은 묶음 하나**: 트랜잭션의 첫 작업이 세션을 그 큐의 묶음에 묶습니다. 다른 묶음의 큐(묶음 없는 큐도 하나의 묶음)를 같은
|
|
78
|
+
트랜잭션에서 쓰면 **보내기 전에** `MIMQE_CROSS_LEADER_TRX (queues of different leader groups: <묶음> / <묶음>)` 입니다
|
|
79
|
+
(`SYSTEM.*` 큐는 셈에서 빠집니다). `commit` / `rollback` / `commit_and_get` 은 그 묶음 리더 연결로 가고, 끝나면 묶음이 풀립니다.
|
|
80
|
+
트랜잭션 도중 리더가 옮겨 가면 커밋이 그 사유로 실패합니다(엔진이 롤백 - 트랜잭션을 처음부터 다시).
|
|
81
|
+
- `flush_queue` 는 큐 리더로 보내고, 깊이 · 존재 조회는 지금 연결 그대로입니다(팔로워가 답합니다).
|
|
82
|
+
- 진단: `get_queue_leader(queue)` -> `"<묶음>|<host:port>|<epoch>"`(묶음 모드가 아니면 빈 문자열).
|
|
83
|
+
- **큐 속성 `leaderGroup`**(엔진 7.0.1.3535 이상): 큐 묶음 이름(`""` = 묶음 없음). **값을 지정했거나 엔진에서 읽어 온 객체일 때만**
|
|
84
|
+
엔진에 보냅니다 - 지정 없이 새 객체로 `setQueueProperty` 하면 엔진의 저장값이 그대로입니다(묶음이 바뀌면 큐 리더가 옮겨 가므로
|
|
85
|
+
다른 필드와 달리 덮어쓰지 않습니다). 묶음을 풀려면 `""` 를 지정합니다. 세터는 47바이트를 검사하고, 글자 규칙 · 지정할 수 없는 큐는
|
|
86
|
+
엔진이 거절합니다.
|
|
87
|
+
- 소켓 타임아웃(서비스 `timeoutSocketSend` / `timeoutSocketRecv`, 큐 관리자 `sendTimeout` / `recvWaitTimeout`): 범위를
|
|
88
|
+
`1~864000` 또는 `-1`(무한)로 고쳐 적었습니다. 엔진 7.0.1.3528 부터 `-1` 을 다시 받습니다(ILink6 6.3.4 와 같음). `0` 과 다른 음수는
|
|
89
|
+
엔진이 `MIMQE_INVALID_TIMEOUT (<이름>=<n>, need >= 1 or -1=infinite)` 로 거부합니다. 클라는 값을 검사하지 않으므로 동작 변경은 없습니다.
|
|
90
|
+
|
|
91
|
+
## 0.19.5 변경 - 멱등 producer 도 FLUSH TOPIC / epoch 변경을 넘긴다 (자바 v2.15.5 와 같음, 짝 엔진 7.0.1.3512 이상)
|
|
92
|
+
|
|
93
|
+
> **FLUSH TOPIC 과 겹친 발행의 유실 0 은 엔진 7.0.1.3527(FLUSHSYNC-1) 이상에서 성립합니다.** 그 전 엔진은 FLUSH 가 모든 브로커를
|
|
94
|
+
> 비우기 전에 응답해, 아직 비우지 않은 브로커가 받은 레코드가 성공 응답 뒤 지워질 수 있었습니다(엔진 결함). 계약: FLUSH 응답 뒤에
|
|
95
|
+
> 받은 성공은 새 토픽에 있고, 응답 전에 받은 성공은 FLUSH 가 지울 수 있습니다. (배포 뒤 추가한 문서 - 배포된 휠의 README 에는 없습니다)
|
|
96
|
+
|
|
97
|
+
> 0.19.4 는 시험 저장소(test.pypi)에만 올렸고 배포하지 않았습니다. 0.19.4 는 앞선 시도의 결과를 모르는 배치가 이 거절을
|
|
98
|
+
> 받으면 `commit in doubt` 로 올렸는데, FLUSH 순간 복제 확인을 기다리던 배치가 늘 그 경우라(엔진이 "결과 모름" 으로 답합니다)
|
|
99
|
+
> FLUSH 때마다 앱에 드러났습니다(회귀 FLUSHOLDPROD-1). 아래가 고친 동작입니다.
|
|
100
|
+
|
|
101
|
+
- 멱등 producer(기본)가 `MIMQE_TOPIC_FLUSHED` / `MIMQE_TOPIC_EPOCH_MISMATCH` 로 거절된 배치를 **앱에 올리지 않고**
|
|
102
|
+
메타데이터를 다시 받아 다시 보냅니다. 0.19.3 까지는 멱등이면 실패로 올렸습니다(0.7.1 절) - FLUSH 로 새로 만든 토픽은
|
|
103
|
+
처음 받은 번호를 기준선으로 삼아, 같은 번호로 다시 보낸 배치가 적재 없이 성공으로 답해질 수 있었기 때문입니다.
|
|
104
|
+
이제 번호를 다시 매겨 보내므로 그렇게 가려지지 않습니다(엔진 r3525 에서 계약 확인: 이 두 거절은 그 시도가
|
|
105
|
+
적재되지 않은 것이 확정입니다).
|
|
106
|
+
- FLUSH 와 겹친 구간에서는 **파티션 안 순서가 바뀔 수 있습니다** - 거절된 배치가 이미 나가 있던 뒤 배치보다 늦게
|
|
107
|
+
적재됩니다(비멱등과 같습니다).
|
|
108
|
+
- 앞선 시도의 결과를 모르는 배치(연결 끊김으로 답이 없었거나 엔진이 "결과 모름" 으로 답한 것)가 이 거절을 받으면
|
|
109
|
+
번호를 다시 매기지 않고 **같은 번호로** 다시 보냅니다. epoch 는 FLUSH 없이도 바뀌므로(데이터는 그대로) 앞선 시도가
|
|
110
|
+
적재돼 있을 수 있습니다 - 그러면 같은 번호가 중복 응답(원래 좌표)으로 끝나고, FLUSH 였다면 새 토픽이 그 번호로 적재합니다.
|
|
111
|
+
- 가를 수 없는 경우 하나만 앱에 올립니다: 그 재전송에 엔진이 **좌표 없는 중복**으로 답하면
|
|
112
|
+
`MIMQE_TOPIC_EPOCH_MISMATCH (commit in doubt - ... answered duplicate without coordinates; the batch may not be stored)` 입니다.
|
|
113
|
+
이미 적재돼 있는 것인지, FLUSH 로 새로 만든 토픽에서 뒤 배치에 가려진 것인지 클라가 알 수 없습니다.
|
|
114
|
+
- 재시도 예산은 그대로입니다(`retries` / `retryBackoffMs` / `deliveryTimeoutMs`).
|
|
115
|
+
|
|
63
116
|
## 0.19.3 변경 - 코디네이터 이동을 기다려 넘긴다, 문서 (자바 v2.15.3 과 같음, 짝 엔진 7.0.1.3512 이상)
|
|
64
117
|
|
|
65
118
|
- `addNode` / `removeNode`: 파티션 배치(PLACEMENT)를 켠 정의에서는 `replicas` 가 파티션당 복제본 수라 노드를 더해도
|
|
@@ -605,7 +658,7 @@ svc.createCluster(c)
|
|
|
605
658
|
멱등이 저절로 꺼집니다. `enableIdempotence(True)` 를 명시하고 그 둘과 함께 쓰면 전처럼 생성 때 `ValueError` 입니다.
|
|
606
659
|
- 멱등이면 토픽을 비우거나(FLUSH) epoch 가 바뀐 순간 걸려 있던 배치를 다시 보내지 않고 실패로 올립니다
|
|
607
660
|
(`MIMQE_TOPIC_FLUSHED` / `MIMQE_TOPIC_EPOCH_MISMATCH`). 0.7.0 기본처럼 조용히 다시 보내려면 `enableIdempotence(False)` 를
|
|
608
|
-
명시하십시오.
|
|
661
|
+
명시하십시오. **(0.19.5 부터 멱등도 조용히 다시 보냅니다 - 0.19.5 절)**
|
|
609
662
|
- `batchSize` 기본값이 **16384** 입니다(파티션 배치 하나의 상한). 0.7.0 은 262144 였습니다 - 처리량이 모자라면
|
|
610
663
|
`batchSize(262144)` 처럼 늘리십시오.
|
|
611
664
|
- producer 수 안내: 한 토픽의 producer(연결)는 1~4개면 충분합니다(엔진 7.0.1.3407 실측).
|
|
@@ -10,6 +10,59 @@ lz4 배치 압축을 쓰려면 `pip install "jetstream-api[lz4]"`, zstd 배치
|
|
|
10
10
|
## 설치 방법
|
|
11
11
|
pip install jetstream-api
|
|
12
12
|
|
|
13
|
+
## 0.20.0 변경 - 큐 묶음 리더 라우팅, 큐 속성 leaderGroup (자바 v2.16.0 과 같음, 짝 엔진 7.0.1.3512 이상)
|
|
14
|
+
|
|
15
|
+
> 큐 묶음(LEADERGROUP) 라우팅은 엔진 **7.0.1.3540** 이상에서 묶음 모드가 켜졌을 때만 동작합니다(묶음 모드는 엔진 3-b3 에서 켜집니다).
|
|
16
|
+
> 그 밖의 엔진(옛 판 · 묶음 모드 꺼짐)에서는 0.19.5 와 같이 동작하고, 7.0.1.3540 이상이면 세션마다 처음 큐를 쓸 때 조회 왕복이 한 번
|
|
17
|
+
> 늡니다. FLUSH TOPIC 과 겹친 발행의 유실 0 은 지금처럼 엔진 7.0.1.3527 이상입니다(0.19.5 절).
|
|
18
|
+
|
|
19
|
+
- **큐 묶음 리더 라우팅**(엔진 QLOOKUP-1): 클러스터의 큐마다 리더가 다를 수 있다(같은 묶음의 큐는 같은 리더). `ILQmgr` 는 처음
|
|
20
|
+
큐를 쓸 때 그 큐의 리더를 묻고(3397 "Q"), 리더 노드마다 연결 하나(세션 이름 `<클라이언트 이름>-Q<노드 id>`)로 그 큐의 요청을
|
|
21
|
+
보냅니다. 공개 API 는 바뀌지 않습니다. 앱 세션의 접속 능력 선언에 `qlead=1` 이 실립니다.
|
|
22
|
+
- `access_queue(name)`(존재 확인)은 묶음 모드면 그 조회 하나로 존재와 리더를 함께 봅니다.
|
|
23
|
+
- 자동 확인 세션: 리더가 옮겨 가 엔진이 `MIMQE_FOLLOWER_NODE (leader=…)` 로 거절하면 다시 물어 **한 번** 새 리더로 다시 보냅니다
|
|
24
|
+
(그 요청은 적용되지 않은 것이 확정입니다). 큐 연결이 끊겨도 한 번 다시 보냅니다. 리더가 정해지지 않은 동안은 세션 타임아웃까지
|
|
25
|
+
기다리고, 끝내 없으면 `MIMQE_NOT_AVAILABLE (queue leader for <큐>)` 입니다. 이동 순간의 커밋은 지금처럼
|
|
26
|
+
`MIMQE_CLUSTER_HALTED (... commit in doubt)` 로 올 수 있습니다(다시 보내지 않습니다).
|
|
27
|
+
- **트랜잭션은 묶음 하나**: 트랜잭션의 첫 작업이 세션을 그 큐의 묶음에 묶습니다. 다른 묶음의 큐(묶음 없는 큐도 하나의 묶음)를 같은
|
|
28
|
+
트랜잭션에서 쓰면 **보내기 전에** `MIMQE_CROSS_LEADER_TRX (queues of different leader groups: <묶음> / <묶음>)` 입니다
|
|
29
|
+
(`SYSTEM.*` 큐는 셈에서 빠집니다). `commit` / `rollback` / `commit_and_get` 은 그 묶음 리더 연결로 가고, 끝나면 묶음이 풀립니다.
|
|
30
|
+
트랜잭션 도중 리더가 옮겨 가면 커밋이 그 사유로 실패합니다(엔진이 롤백 - 트랜잭션을 처음부터 다시).
|
|
31
|
+
- `flush_queue` 는 큐 리더로 보내고, 깊이 · 존재 조회는 지금 연결 그대로입니다(팔로워가 답합니다).
|
|
32
|
+
- 진단: `get_queue_leader(queue)` -> `"<묶음>|<host:port>|<epoch>"`(묶음 모드가 아니면 빈 문자열).
|
|
33
|
+
- **큐 속성 `leaderGroup`**(엔진 7.0.1.3535 이상): 큐 묶음 이름(`""` = 묶음 없음). **값을 지정했거나 엔진에서 읽어 온 객체일 때만**
|
|
34
|
+
엔진에 보냅니다 - 지정 없이 새 객체로 `setQueueProperty` 하면 엔진의 저장값이 그대로입니다(묶음이 바뀌면 큐 리더가 옮겨 가므로
|
|
35
|
+
다른 필드와 달리 덮어쓰지 않습니다). 묶음을 풀려면 `""` 를 지정합니다. 세터는 47바이트를 검사하고, 글자 규칙 · 지정할 수 없는 큐는
|
|
36
|
+
엔진이 거절합니다.
|
|
37
|
+
- 소켓 타임아웃(서비스 `timeoutSocketSend` / `timeoutSocketRecv`, 큐 관리자 `sendTimeout` / `recvWaitTimeout`): 범위를
|
|
38
|
+
`1~864000` 또는 `-1`(무한)로 고쳐 적었습니다. 엔진 7.0.1.3528 부터 `-1` 을 다시 받습니다(ILink6 6.3.4 와 같음). `0` 과 다른 음수는
|
|
39
|
+
엔진이 `MIMQE_INVALID_TIMEOUT (<이름>=<n>, need >= 1 or -1=infinite)` 로 거부합니다. 클라는 값을 검사하지 않으므로 동작 변경은 없습니다.
|
|
40
|
+
|
|
41
|
+
## 0.19.5 변경 - 멱등 producer 도 FLUSH TOPIC / epoch 변경을 넘긴다 (자바 v2.15.5 와 같음, 짝 엔진 7.0.1.3512 이상)
|
|
42
|
+
|
|
43
|
+
> **FLUSH TOPIC 과 겹친 발행의 유실 0 은 엔진 7.0.1.3527(FLUSHSYNC-1) 이상에서 성립합니다.** 그 전 엔진은 FLUSH 가 모든 브로커를
|
|
44
|
+
> 비우기 전에 응답해, 아직 비우지 않은 브로커가 받은 레코드가 성공 응답 뒤 지워질 수 있었습니다(엔진 결함). 계약: FLUSH 응답 뒤에
|
|
45
|
+
> 받은 성공은 새 토픽에 있고, 응답 전에 받은 성공은 FLUSH 가 지울 수 있습니다. (배포 뒤 추가한 문서 - 배포된 휠의 README 에는 없습니다)
|
|
46
|
+
|
|
47
|
+
> 0.19.4 는 시험 저장소(test.pypi)에만 올렸고 배포하지 않았습니다. 0.19.4 는 앞선 시도의 결과를 모르는 배치가 이 거절을
|
|
48
|
+
> 받으면 `commit in doubt` 로 올렸는데, FLUSH 순간 복제 확인을 기다리던 배치가 늘 그 경우라(엔진이 "결과 모름" 으로 답합니다)
|
|
49
|
+
> FLUSH 때마다 앱에 드러났습니다(회귀 FLUSHOLDPROD-1). 아래가 고친 동작입니다.
|
|
50
|
+
|
|
51
|
+
- 멱등 producer(기본)가 `MIMQE_TOPIC_FLUSHED` / `MIMQE_TOPIC_EPOCH_MISMATCH` 로 거절된 배치를 **앱에 올리지 않고**
|
|
52
|
+
메타데이터를 다시 받아 다시 보냅니다. 0.19.3 까지는 멱등이면 실패로 올렸습니다(0.7.1 절) - FLUSH 로 새로 만든 토픽은
|
|
53
|
+
처음 받은 번호를 기준선으로 삼아, 같은 번호로 다시 보낸 배치가 적재 없이 성공으로 답해질 수 있었기 때문입니다.
|
|
54
|
+
이제 번호를 다시 매겨 보내므로 그렇게 가려지지 않습니다(엔진 r3525 에서 계약 확인: 이 두 거절은 그 시도가
|
|
55
|
+
적재되지 않은 것이 확정입니다).
|
|
56
|
+
- FLUSH 와 겹친 구간에서는 **파티션 안 순서가 바뀔 수 있습니다** - 거절된 배치가 이미 나가 있던 뒤 배치보다 늦게
|
|
57
|
+
적재됩니다(비멱등과 같습니다).
|
|
58
|
+
- 앞선 시도의 결과를 모르는 배치(연결 끊김으로 답이 없었거나 엔진이 "결과 모름" 으로 답한 것)가 이 거절을 받으면
|
|
59
|
+
번호를 다시 매기지 않고 **같은 번호로** 다시 보냅니다. epoch 는 FLUSH 없이도 바뀌므로(데이터는 그대로) 앞선 시도가
|
|
60
|
+
적재돼 있을 수 있습니다 - 그러면 같은 번호가 중복 응답(원래 좌표)으로 끝나고, FLUSH 였다면 새 토픽이 그 번호로 적재합니다.
|
|
61
|
+
- 가를 수 없는 경우 하나만 앱에 올립니다: 그 재전송에 엔진이 **좌표 없는 중복**으로 답하면
|
|
62
|
+
`MIMQE_TOPIC_EPOCH_MISMATCH (commit in doubt - ... answered duplicate without coordinates; the batch may not be stored)` 입니다.
|
|
63
|
+
이미 적재돼 있는 것인지, FLUSH 로 새로 만든 토픽에서 뒤 배치에 가려진 것인지 클라가 알 수 없습니다.
|
|
64
|
+
- 재시도 예산은 그대로입니다(`retries` / `retryBackoffMs` / `deliveryTimeoutMs`).
|
|
65
|
+
|
|
13
66
|
## 0.19.3 변경 - 코디네이터 이동을 기다려 넘긴다, 문서 (자바 v2.15.3 과 같음, 짝 엔진 7.0.1.3512 이상)
|
|
14
67
|
|
|
15
68
|
- `addNode` / `removeNode`: 파티션 배치(PLACEMENT)를 켠 정의에서는 `replicas` 가 파티션당 복제본 수라 노드를 더해도
|
|
@@ -555,7 +608,7 @@ svc.createCluster(c)
|
|
|
555
608
|
멱등이 저절로 꺼집니다. `enableIdempotence(True)` 를 명시하고 그 둘과 함께 쓰면 전처럼 생성 때 `ValueError` 입니다.
|
|
556
609
|
- 멱등이면 토픽을 비우거나(FLUSH) epoch 가 바뀐 순간 걸려 있던 배치를 다시 보내지 않고 실패로 올립니다
|
|
557
610
|
(`MIMQE_TOPIC_FLUSHED` / `MIMQE_TOPIC_EPOCH_MISMATCH`). 0.7.0 기본처럼 조용히 다시 보내려면 `enableIdempotence(False)` 를
|
|
558
|
-
명시하십시오.
|
|
611
|
+
명시하십시오. **(0.19.5 부터 멱등도 조용히 다시 보냅니다 - 0.19.5 절)**
|
|
559
612
|
- `batchSize` 기본값이 **16384** 입니다(파티션 배치 하나의 상한). 0.7.0 은 262144 였습니다 - 처리량이 모자라면
|
|
560
613
|
`batchSize(262144)` 처럼 늘리십시오.
|
|
561
614
|
- producer 수 안내: 한 토픽의 producer(연결)는 1~4개면 충분합니다(엔진 7.0.1.3407 실측).
|
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
"""큐 묶음 리더(QLOOKUP-1, 엔진 7.0.1.3540) - 내부용.
|
|
2
|
+
|
|
3
|
+
큐마다 리더가 다를 수 있다(묶음 = LEADERGROUP). :class:`QueueRouter` 는 :class:`~ilink.qmgr.ILQmgr` 하나에 붙어
|
|
4
|
+
큐 -> 묶음 -> (리더 노드, epoch) 를 3397 "Q" 로 묻고 캐시하며, 리더 노드마다 큐 링크(자식 세션) 하나를 든다.
|
|
5
|
+
설계 = 저장소 DESIGN-QUEUE-LEADER.md(초안 2). 묶음 모드가 꺼진 엔진(``S|0``)과 옛 엔진에서는 지금 동작 그대로다.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import re
|
|
11
|
+
import threading
|
|
12
|
+
import time
|
|
13
|
+
|
|
14
|
+
from . import _groupwire as G
|
|
15
|
+
from .common import ILC
|
|
16
|
+
from .exception import ILOperationException
|
|
17
|
+
from .msg import ILMsgUtil
|
|
18
|
+
|
|
19
|
+
#: 3397 "Q" 를 아는 엔진 리비전(7.x)
|
|
20
|
+
QLEAD_REVISION = 3540
|
|
21
|
+
_VERSION_FULL = re.compile(r"\s*([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)(?:\s|$)")
|
|
22
|
+
#: 리더 없음(-1) 동안 다시 묻는 간격 - 처음 / 상한(초)
|
|
23
|
+
_BACKOFF_S = 0.1
|
|
24
|
+
_BACKOFF_MAX_S = 1.0
|
|
25
|
+
#: 묶음 없는 큐의 묶음 이름(엔진 Q 줄의 "-")
|
|
26
|
+
DEFAULT_GROUP = "-"
|
|
27
|
+
_HINT = re.compile(r"leader=(\S+?)(?:\s+id=(-?\d+))?\s*\)")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def engine_qlookup(version: str) -> bool:
|
|
31
|
+
"""엔진 판 문자열이 3397 "Q" 를 아는 판인지(7.x 리비전 3540 이상).
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
version (str): :meth:`ILQmgr.get_version` 값(예 ``"7.0.1.3540 jetstream"``).
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
bool: 판 줄 major 가 7 이고 리비전이 3540 이상이면 True.
|
|
38
|
+
"""
|
|
39
|
+
m = _VERSION_FULL.match(version or "")
|
|
40
|
+
return m is not None and int(m.group(1)) == 7 and int(m.group(4)) >= QLEAD_REVISION
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class Lookup:
|
|
44
|
+
"""3397 "Q" 응답 하나를 푼 것.
|
|
45
|
+
|
|
46
|
+
Attributes:
|
|
47
|
+
mode (int): 묶음 모드(0 / 1).
|
|
48
|
+
seq (int): 묶음 표 판(단조 증가).
|
|
49
|
+
cluster (int): 클러스터면 1.
|
|
50
|
+
queues (dict): 큐 -> (묶음, 리더 노드 id, epoch).
|
|
51
|
+
missing (dict): 없는 큐 -> 사유.
|
|
52
|
+
nodes (dict): 노드 id -> ``"host:port"``.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
__slots__ = ("mode", "seq", "cluster", "queues", "missing", "nodes")
|
|
56
|
+
|
|
57
|
+
def __init__(self) -> None:
|
|
58
|
+
self.mode = 0
|
|
59
|
+
self.seq = -1
|
|
60
|
+
self.cluster = 0
|
|
61
|
+
self.queues: dict = {}
|
|
62
|
+
self.missing: dict = {}
|
|
63
|
+
self.nodes: dict = {}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def parse(text: str) -> Lookup:
|
|
67
|
+
"""3397 "Q" 의 LONG_REPORT 본문을 푼다. 모르는 줄은 건너뛴다.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
text (str): ``S|모드|seq|클러스터``, ``Q|큐|묶음|리더|epoch``, ``E|큐|사유``, ``N|id|host|port`` 줄.
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
Lookup: 푼 결과.
|
|
74
|
+
"""
|
|
75
|
+
lk = Lookup()
|
|
76
|
+
for kind, f in G.lines(text):
|
|
77
|
+
if kind == "S" and len(f) >= 3:
|
|
78
|
+
lk.mode, lk.seq, lk.cluster = G._int(f[0]), G._int(f[1], -1), G._int(f[2])
|
|
79
|
+
elif kind == "Q" and len(f) >= 4:
|
|
80
|
+
lk.queues[f[0]] = (f[1] or DEFAULT_GROUP, G._int(f[2], -1), G._int(f[3], 0))
|
|
81
|
+
elif kind == "E" and f:
|
|
82
|
+
lk.missing[f[0]] = f[1] if len(f) > 1 else ""
|
|
83
|
+
elif kind == "N" and len(f) >= 3:
|
|
84
|
+
lk.nodes[G._int(f[0], -1)] = "%s:%s" % (f[1], f[2])
|
|
85
|
+
return lk
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def follower_hint(reason: str):
|
|
89
|
+
"""요청 단위 팔로워 거절 사유를 푼다.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
reason (str): 엔진 사유(arg2). ``MIMQE_FOLLOWER_NODE (leader=<host:port> id=<n>)`` /
|
|
93
|
+
``(leader=<host:port>)`` / ``(leader=-1)`` / ``(leader=unknown)``.
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
tuple | None: 팔로워 거절이 아니면 None. 맞으면 ``(주소 또는 None, id 또는 None)`` - 주소가 None 이면 리더를
|
|
97
|
+
모른다(-1 / unknown).
|
|
98
|
+
"""
|
|
99
|
+
if not reason or not reason.startswith("MIMQE_FOLLOWER_NODE ("):
|
|
100
|
+
return None
|
|
101
|
+
m = _HINT.search(reason)
|
|
102
|
+
if m is None:
|
|
103
|
+
return (None, None)
|
|
104
|
+
tok = m.group(1)
|
|
105
|
+
nid = int(m.group(2)) if m.group(2) is not None else None
|
|
106
|
+
if tok in ("-1", "unknown") or tok.rfind(":") <= 0:
|
|
107
|
+
return (None, nid)
|
|
108
|
+
return (tok, nid)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def cross_error(have: str, want: str) -> ILOperationException:
|
|
112
|
+
"""한 트랜잭션에 다른 묶음 큐를 섞으려 할 때의 오류(엔진 사유와 같은 머리).
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
have (str): 트랜잭션이 묶인 묶음.
|
|
116
|
+
want (str): 새로 쓰려는 큐의 묶음.
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
ILOperationException: ``MIMQE_CROSS_LEADER_TRX (...)`` 사유의 예외.
|
|
120
|
+
"""
|
|
121
|
+
return ILOperationException("MIMQE_CROSS_LEADER_TRX (queues of different leader groups: %s / %s)" % (have, want))
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class QueueRouter:
|
|
125
|
+
"""ILQmgr 하나의 큐 리더 라우팅 상태.
|
|
126
|
+
|
|
127
|
+
모드: None = 아직 모름(처음 큐를 쓸 때 묻는다), ``"legacy"`` = "Q" 를 모르는 엔진, ``"off"`` = 묶음 모드 꺼짐
|
|
128
|
+
(``S|0``), ``"on"`` = 묶음 모드. legacy / off 에서는 모든 큐가 부모 연결로 간다(지금 동작).
|
|
129
|
+
"""
|
|
130
|
+
|
|
131
|
+
def __init__(self, qmgr) -> None:
|
|
132
|
+
self.qm = qmgr
|
|
133
|
+
self.lock = threading.RLock()
|
|
134
|
+
self.mode: str | None = None
|
|
135
|
+
self.seq = -1
|
|
136
|
+
self.cluster = 0
|
|
137
|
+
self.q_group: dict = {} # 큐 -> 묶음
|
|
138
|
+
self.g_leader: dict = {} # 묶음 -> (리더 노드 id, epoch)
|
|
139
|
+
self.nodes: dict = {} # 노드 id -> "host:port"
|
|
140
|
+
self.links: dict = {} # "host:port" -> 자식 ILQmgr
|
|
141
|
+
self.trx_group: str | None = None
|
|
142
|
+
self.trx_session = None
|
|
143
|
+
|
|
144
|
+
# ── 조회 ──────────────────────────────────────────────────────────
|
|
145
|
+
def lookup(self, names) -> Lookup | None:
|
|
146
|
+
"""3397 "Q" 를 부모 연결로 묻고 캐시를 고친다. "Q" 를 모르는 엔진이면 None(모드 legacy).
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
names (list[str]): 큐 이름들(하나 이상).
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
Lookup | None: 푼 응답, 또는 옛 엔진이면 None.
|
|
153
|
+
"""
|
|
154
|
+
qm = self.qm
|
|
155
|
+
if not engine_qlookup(qm.get_version() or ""):
|
|
156
|
+
self.mode = "legacy"
|
|
157
|
+
return None
|
|
158
|
+
names = list(names)
|
|
159
|
+
msg = ILMsgUtil.create_request_msg_with_ext(ILC.MIMQ_TOPIC_METADATA, G.enc_topics(names), names[0], "Q", "")
|
|
160
|
+
svc = qm.service
|
|
161
|
+
res = svc._call(msg, svc.get_session_timeout())
|
|
162
|
+
if res.getType() != ILC.MIMQ_LONG_REPORT_MESSAGE:
|
|
163
|
+
# 옛 엔진은 arg1 을 토픽 이름으로 보고 REPORT 로 답한다(같은 이름 토픽이 있으면 성공 REPORT) - 응답 타입만 본다
|
|
164
|
+
self.mode = "legacy"
|
|
165
|
+
return None
|
|
166
|
+
data = res.getDataMsg().getData()
|
|
167
|
+
lk = parse((data or b"").decode("utf-8", "replace"))
|
|
168
|
+
with self.lock:
|
|
169
|
+
self.mode = "on" if lk.mode == 1 else "off"
|
|
170
|
+
self.cluster = lk.cluster
|
|
171
|
+
if lk.nodes:
|
|
172
|
+
self.nodes.update(lk.nodes)
|
|
173
|
+
newer = lk.seq >= self.seq
|
|
174
|
+
if newer:
|
|
175
|
+
self.seq = lk.seq
|
|
176
|
+
for q, (grp, leader, epoch) in lk.queues.items():
|
|
177
|
+
if newer or q not in self.q_group:
|
|
178
|
+
self.q_group[q] = grp
|
|
179
|
+
prev = self.g_leader.get(grp)
|
|
180
|
+
if leader < 0:
|
|
181
|
+
# 표에 아직 없다 / 리스 반납 - 지금은 아무도 받지 않는다. epoch 는 내리지 않는다
|
|
182
|
+
self.g_leader[grp] = (-1, prev[1] if prev else 0)
|
|
183
|
+
elif prev is None or epoch >= prev[1]:
|
|
184
|
+
self.g_leader[grp] = (leader, epoch)
|
|
185
|
+
# 같은 묶음에 더 낮은 epoch = 늦은 팔로워의 낡은 표 - 버린다(엔진 Q6)
|
|
186
|
+
for q in lk.missing:
|
|
187
|
+
self.q_group.pop(q, None)
|
|
188
|
+
return lk
|
|
189
|
+
|
|
190
|
+
def ensure_mode(self, name: str) -> None:
|
|
191
|
+
"""모드를 아직 모르면 그 큐로 한 번 묻는다."""
|
|
192
|
+
if self.mode is None:
|
|
193
|
+
self.lookup([name])
|
|
194
|
+
|
|
195
|
+
# ── 라우팅 ────────────────────────────────────────────────────────
|
|
196
|
+
def target(self, name: str, txn: bool):
|
|
197
|
+
"""그 큐의 요청을 보낼 세션과 묶음.
|
|
198
|
+
|
|
199
|
+
Args:
|
|
200
|
+
name (str): 큐 이름.
|
|
201
|
+
txn (bool): 트랜잭션 세션의 작업이면 True - 묶음이 다르면 보내기 전에 거절한다.
|
|
202
|
+
|
|
203
|
+
Returns:
|
|
204
|
+
tuple: ``(세션 ILQmgr, 묶음 또는 None)``. legacy / off 이면 ``(부모, None)``.
|
|
205
|
+
|
|
206
|
+
Raises:
|
|
207
|
+
ILOperationException: 다른 묶음 큐를 한 트랜잭션에 섞을 때(``MIMQE_CROSS_LEADER_TRX``), 리더 없음이 세션
|
|
208
|
+
타임아웃까지 이어질 때(``MIMQE_NOT_AVAILABLE``).
|
|
209
|
+
"""
|
|
210
|
+
self.ensure_mode(name)
|
|
211
|
+
qm = self.qm
|
|
212
|
+
if self.mode != "on":
|
|
213
|
+
return qm, None
|
|
214
|
+
if txn and self.trx_session is not None and name.startswith("SYSTEM."):
|
|
215
|
+
return self.trx_session, self.trx_group # SYSTEM.* 는 묶음 셈에서 빠진다(엔진과 같다) - 트랜잭션 연결로
|
|
216
|
+
grp = self.q_group.get(name)
|
|
217
|
+
if grp is None:
|
|
218
|
+
self.lookup([name])
|
|
219
|
+
if self.mode != "on":
|
|
220
|
+
return qm, None
|
|
221
|
+
grp = self.q_group.get(name)
|
|
222
|
+
if grp is None:
|
|
223
|
+
return qm, None # 없는 큐 - 부모로 보내 엔진 사유를 그대로 받는다
|
|
224
|
+
if txn and self.trx_group is not None and grp != self.trx_group and not name.startswith("SYSTEM."):
|
|
225
|
+
raise cross_error(self.trx_group, grp)
|
|
226
|
+
addr = self._leader_addr(name, grp)
|
|
227
|
+
return self._session_for(addr), grp
|
|
228
|
+
|
|
229
|
+
def _leader_addr(self, name: str, grp: str) -> str | None:
|
|
230
|
+
# 묶음 리더의 주소(부트스트랩이면 None). 리더 없음(-1)이면 쉬며 다시 묻는다 - 상한 = 세션 타임아웃
|
|
231
|
+
deadline = None
|
|
232
|
+
pause = _BACKOFF_S
|
|
233
|
+
while True:
|
|
234
|
+
leader = self.g_leader.get(grp, (-1, 0))[0]
|
|
235
|
+
if leader >= 0:
|
|
236
|
+
if not self.cluster:
|
|
237
|
+
return None # 비클러스터: 0 = 붙은 노드
|
|
238
|
+
return self.nodes.get(leader)
|
|
239
|
+
now = time.monotonic()
|
|
240
|
+
if deadline is None:
|
|
241
|
+
st = self.qm.get_session_timeout()
|
|
242
|
+
deadline = now + (st / 1000.0 if st and st > 0 else 30.0)
|
|
243
|
+
if now >= deadline:
|
|
244
|
+
raise ILOperationException("MIMQE_NOT_AVAILABLE (queue leader for %s)" % name)
|
|
245
|
+
time.sleep(min(pause, deadline - now))
|
|
246
|
+
pause = min(pause * 2, _BACKOFF_MAX_S)
|
|
247
|
+
self.lookup([name])
|
|
248
|
+
grp = self.q_group.get(name, grp)
|
|
249
|
+
|
|
250
|
+
def _boot_addr(self) -> str:
|
|
251
|
+
return "%s:%s" % (self.qm.host, self.qm.port)
|
|
252
|
+
|
|
253
|
+
def _session_for(self, addr: str | None):
|
|
254
|
+
qm = self.qm
|
|
255
|
+
if addr is None or addr == self._boot_addr():
|
|
256
|
+
return qm
|
|
257
|
+
with self.lock:
|
|
258
|
+
s = self.links.get(addr)
|
|
259
|
+
if s is not None and s.is_connected():
|
|
260
|
+
return s
|
|
261
|
+
if s is not None:
|
|
262
|
+
self.links.pop(addr, None)
|
|
263
|
+
_quiet_disconnect(s)
|
|
264
|
+
s = qm._open_queue_link(addr, self._link_id(addr))
|
|
265
|
+
with self.lock:
|
|
266
|
+
other = self.links.get(addr)
|
|
267
|
+
if other is not None and other.is_connected():
|
|
268
|
+
_quiet_disconnect(s) # 다른 스레드가 먼저 열었다
|
|
269
|
+
return other
|
|
270
|
+
self.links[addr] = s
|
|
271
|
+
return s
|
|
272
|
+
|
|
273
|
+
def _link_id(self, addr: str) -> str:
|
|
274
|
+
for nid, a in self.nodes.items():
|
|
275
|
+
if a == addr:
|
|
276
|
+
return str(nid)
|
|
277
|
+
return addr.replace(":", "_")
|
|
278
|
+
|
|
279
|
+
# ── 트랜잭션 ──────────────────────────────────────────────────────
|
|
280
|
+
def bind(self, grp: str | None, session) -> None:
|
|
281
|
+
"""트랜잭션 작업이 걸린 뒤 세션을 그 묶음에 묶는다(처음 한 번)."""
|
|
282
|
+
if grp is None or self.trx_group is not None:
|
|
283
|
+
return
|
|
284
|
+
self.trx_group = grp
|
|
285
|
+
self.trx_session = session
|
|
286
|
+
|
|
287
|
+
def unbind(self) -> None:
|
|
288
|
+
"""commit / rollback 뒤(또는 트랜잭션이 깨진 뒤) 묶음을 푼다."""
|
|
289
|
+
self.trx_group = None
|
|
290
|
+
self.trx_session = None
|
|
291
|
+
|
|
292
|
+
def trx_target(self):
|
|
293
|
+
"""commit / rollback 을 보낼 세션(묶인 트랜잭션이 없으면 부모)."""
|
|
294
|
+
return self.trx_session if self.trx_session is not None else self.qm
|
|
295
|
+
|
|
296
|
+
# ── 거절 / 끊김 ───────────────────────────────────────────────────
|
|
297
|
+
def on_reject(self, name: str, reason: str) -> bool:
|
|
298
|
+
"""요청 단위 팔로워 거절을 받았다. 캐시를 버리고 다시 묻는다.
|
|
299
|
+
|
|
300
|
+
Args:
|
|
301
|
+
name (str): 큐 이름.
|
|
302
|
+
reason (str): 엔진 사유.
|
|
303
|
+
|
|
304
|
+
Returns:
|
|
305
|
+
bool: 팔로워 거절이었으면 True(부른 쪽이 새 연결로 한 번 다시 보낸다).
|
|
306
|
+
"""
|
|
307
|
+
hint = follower_hint(reason)
|
|
308
|
+
if hint is None:
|
|
309
|
+
return False
|
|
310
|
+
grp = self.q_group.get(name)
|
|
311
|
+
with self.lock:
|
|
312
|
+
if grp is not None:
|
|
313
|
+
self.g_leader.pop(grp, None)
|
|
314
|
+
self.q_group.pop(name, None)
|
|
315
|
+
self.lookup([name])
|
|
316
|
+
return True
|
|
317
|
+
|
|
318
|
+
def forget_group(self, grp: str | None) -> None:
|
|
319
|
+
"""그 묶음의 리더와 그 묶음 큐들의 소속을 버린다 - 다음 요청이 다시 묻는다.
|
|
320
|
+
|
|
321
|
+
트랜잭션 PUT 은 거절돼도 답이 없어(엔진 Q1) 리더 이동을 COMMIT 실패로야 안다. 그때 이것을 부르지 않으면 다음
|
|
322
|
+
트랜잭션도 옛 리더로 가서 또 실패한다.
|
|
323
|
+
|
|
324
|
+
Args:
|
|
325
|
+
grp (str | None): 묶음 이름(None 이면 아무것도 안 한다).
|
|
326
|
+
"""
|
|
327
|
+
if grp is None:
|
|
328
|
+
return
|
|
329
|
+
with self.lock:
|
|
330
|
+
self.g_leader.pop(grp, None)
|
|
331
|
+
for q in [q for q, g in self.q_group.items() if g == grp]:
|
|
332
|
+
self.q_group.pop(q, None)
|
|
333
|
+
|
|
334
|
+
def on_link_error(self, session) -> None:
|
|
335
|
+
"""큐 링크가 끊겼다 - 그 링크를 버리고, 그 노드를 리더로 든 묶음 캐시를 버린다."""
|
|
336
|
+
with self.lock:
|
|
337
|
+
addr = None
|
|
338
|
+
for a, s in list(self.links.items()):
|
|
339
|
+
if s is session:
|
|
340
|
+
addr = a
|
|
341
|
+
self.links.pop(a, None)
|
|
342
|
+
if addr is None:
|
|
343
|
+
return
|
|
344
|
+
for grp, (leader, _e) in list(self.g_leader.items()):
|
|
345
|
+
if self.nodes.get(leader) == addr:
|
|
346
|
+
self.g_leader.pop(grp, None)
|
|
347
|
+
if self.trx_session is session:
|
|
348
|
+
self.unbind()
|
|
349
|
+
_quiet_disconnect(session)
|
|
350
|
+
|
|
351
|
+
def leader_of(self, name: str) -> str:
|
|
352
|
+
"""진단용 리더 문자열.
|
|
353
|
+
|
|
354
|
+
Args:
|
|
355
|
+
name (str): 큐 이름.
|
|
356
|
+
|
|
357
|
+
Returns:
|
|
358
|
+
str: ``"<묶음>|<host:port>|<epoch>"``(모르면 빈 문자열).
|
|
359
|
+
"""
|
|
360
|
+
grp = self.q_group.get(name)
|
|
361
|
+
if grp is None:
|
|
362
|
+
return ""
|
|
363
|
+
leader, epoch = self.g_leader.get(grp, (-1, 0))
|
|
364
|
+
addr = self.nodes.get(leader, "") if self.cluster else self._boot_addr()
|
|
365
|
+
return "%s|%s|%d" % (grp, addr if leader >= 0 or not self.cluster else "", epoch)
|
|
366
|
+
|
|
367
|
+
def reset(self) -> None:
|
|
368
|
+
"""재접속 / 끊기 - 링크를 닫고 캐시와 모드를 버린다(다시 붙은 엔진이 다를 수 있다)."""
|
|
369
|
+
with self.lock:
|
|
370
|
+
links = list(self.links.values())
|
|
371
|
+
self.links.clear()
|
|
372
|
+
self.q_group.clear()
|
|
373
|
+
self.g_leader.clear()
|
|
374
|
+
self.nodes.clear()
|
|
375
|
+
self.mode = None
|
|
376
|
+
self.seq = -1
|
|
377
|
+
self.unbind()
|
|
378
|
+
for s in links:
|
|
379
|
+
_quiet_disconnect(s)
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
def _quiet_disconnect(s) -> None:
|
|
383
|
+
try:
|
|
384
|
+
s.disconnect()
|
|
385
|
+
except Exception:
|
|
386
|
+
pass
|
|
@@ -1084,7 +1084,7 @@ class ILC:
|
|
|
1084
1084
|
return True
|
|
1085
1085
|
|
|
1086
1086
|
@staticmethod
|
|
1087
|
-
def client_caps_extension(plead: bool = False):
|
|
1087
|
+
def client_caps_extension(plead: bool = False, qlead: bool = False):
|
|
1088
1088
|
"""접속 메시지에 실을 **능력 선언** 확장을 만든다(0.18.0).
|
|
1089
1089
|
|
|
1090
1090
|
Args:
|
|
@@ -1092,6 +1092,10 @@ class ILC:
|
|
|
1092
1092
|
노드도 데이터 연결을 받아 달라**는 뜻이다. 엔진은 이것이 없으면 팔로워가 접속을 거절하고 리더 주소를
|
|
1093
1093
|
알려 준다(지금 연결들이 리더로 옮겨 가는 방법). 파티션별로 길을 찾는 연결(소비 v2 구독 세션,
|
|
1094
1094
|
producer 리더 링크)에만 켠다.
|
|
1095
|
+
qlead (bool): True 면 ``qlead=1`` 을 싣는다(0.20.0) - 이 연결은 큐마다 묶음 리더를 찾아 가므로(3397 "Q")
|
|
1096
|
+
묶음 모드의 노드 팔로워도 접속을 받아 달라는 뜻이다. 앱의 큐 세션(:class:`~ilink.qmgr.ILQmgr`)과 그
|
|
1097
|
+
큐 링크에 켠다. 묶음 모드가 꺼진 엔진은 무시한다. ☠ 엔진은 ``","+caps+","`` 에서 ``",qlead=1,"`` 를
|
|
1098
|
+
찾으므로 ``CAP=`` 바로 뒤 첫 토큰에 두지 않는다(``clsblob`` 뒤).
|
|
1095
1099
|
|
|
1096
1100
|
Returns:
|
|
1097
1101
|
bytes | None: ``b"CAP=clsblob=2,ver=0.18.0"`` 꼴. 길이가
|
|
@@ -1110,10 +1114,11 @@ class ILC:
|
|
|
1110
1114
|
있어야 하고 그 표는 반드시 낡는다.
|
|
1111
1115
|
"""
|
|
1112
1116
|
import ilink # 판 문자열은 한 곳에만 있다
|
|
1113
|
-
text = "%sclsblob=%d,%sver=%s" % (ILC.MIMQ_CLIENT_CAPS_PREFIX,
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
+
text = "%sclsblob=%d,%s%sver=%s" % (ILC.MIMQ_CLIENT_CAPS_PREFIX,
|
|
1118
|
+
ILC.MIMQ_CLUSTER_BLOB_VERSION,
|
|
1119
|
+
"plead=1," if plead else "",
|
|
1120
|
+
"qlead=1," if qlead else "",
|
|
1121
|
+
getattr(ilink, "__version__", "?"))
|
|
1117
1122
|
raw = bytes(c for c in text.encode("utf-8", "replace") if 0x20 <= c <= 0x7E)
|
|
1118
1123
|
if len(raw) > ILC.MIMQ_CLIENT_CAPS_MAX:
|
|
1119
1124
|
return None # 자르지 않는다 - 뜻이 달라진다
|