(batchs.keySet());
90 | Collections.sort(result);
91 | return result;
92 | }
93 |
94 | public void subscribe() throws CanalServerException
95 | {
96 | checkStart();
97 |
98 | if (!canalInstance.getMetaManager().isStart()) {
99 | canalInstance.getMetaManager().start();
100 | }
101 |
102 | canalInstance.getMetaManager().subscribe(clientIdentity); // 执行一下meta订阅
103 |
104 | Position position = canalInstance.getMetaManager().getCursor(clientIdentity);
105 | if (position == null) {
106 | position = canalInstance.getEventStore().getFirstPosition();// 获取一下store中的第一条
107 | if (position != null) {
108 | canalInstance.getMetaManager().updateCursor(clientIdentity, position); // 更新一下cursor
109 | }
110 | logger.info("subscribe successfully, {} with first position:{} ", clientIdentity, position);
111 | } else {
112 | logger.info("subscribe successfully, use last cursor position:{} ", clientIdentity, position);
113 | }
114 |
115 | // 通知下订阅关系变化
116 | canalInstance.subscribeChange(clientIdentity);
117 | }
118 |
119 | public void unsubscribe() throws CanalServerException
120 | {
121 | canalInstance.getMetaManager().unsubscribe(clientIdentity); // 执行一下meta订阅
122 |
123 | logger.info("unsubscribe successfully, {}", clientIdentity);
124 | }
125 |
126 | /**
127 | * 获取数据
128 | *
129 | *
130 | * 注意: meta获取和数据的获取需要保证顺序性,优先拿到meta的,一定也会是优先拿到数据,所以需要加同步. (不能出现先拿到meta,拿到第二批数据,这样就会导致数据顺序性出现问题)
131 | *
132 | */
133 | public Message get(int batchSize) throws CanalServerException {
134 | return get(batchSize, null, null);
135 | }
136 |
137 | /**
138 | * 获取数据,可以指定超时时间.
139 | *
140 | *
141 | * 几种case:
142 | * a. 如果timeout为null,则采用tryGet方式,即时获取
143 | * b. 如果timeout不为null
144 | * 1. timeout为0,则采用get阻塞方式,获取数据,不设置超时,直到有足够的batchSize数据才返回
145 | * 2. timeout不为0,则采用get+timeout方式,获取数据,超时还没有batchSize足够的数据,有多少返回多少
146 | *
147 | * 注意: meta获取和数据的获取需要保证顺序性,优先拿到meta的,一定也会是优先拿到数据,所以需要加同步. (不能出现先拿到meta,拿到第二批数据,这样就会导致数据顺序性出现问题)
148 | *
149 | */
150 | public Message get(int batchSize, Long timeout, TimeUnit unit)
151 | throws CanalServerException {
152 |
153 | checkStart();
154 | checkSubscribe();
155 |
156 | synchronized (canalInstance) {
157 | // 获取到流式数据中的最后一批获取的位置
158 | PositionRange positionRanges = canalInstance.getMetaManager().getLastestBatch(clientIdentity);
159 |
160 | if (positionRanges != null) {
161 | throw new CanalServerException(String.format("clientId:%s has last batch:[%s] isn't ack , maybe loss data",
162 | clientIdentity.getClientId(),
163 | positionRanges));
164 | }
165 |
166 | Events events = null;
167 | Position start = canalInstance.getMetaManager().getCursor(clientIdentity);
168 | events = getEvents(canalInstance.getEventStore(), start, batchSize, timeout, unit);
169 |
170 | if (CollectionUtils.isEmpty(events.getEvents())) {
171 | /*logger.trace("get successfully, clientId:{} batchSize:{} but result is null",
172 | clientIdentity.getClientId(),
173 | batchSize);*/
174 | return new Message(-1, true, new ArrayList()); // 返回空包,避免生成batchId,浪费性能
175 | } else {
176 | // 记录到流式信息
177 | Long batchId = canalInstance.getMetaManager().addBatch(clientIdentity, events.getPositionRange());
178 | List entrys = Lists.transform(events.getEvents(), input -> {
179 | try {
180 | return CanalEntry.Entry.parseFrom(input.getRawEntry());
181 | } catch (InvalidProtocolBufferException e)
182 | {
183 | return null;
184 | }
185 | })
186 | .stream()
187 | .filter(Objects::nonNull)
188 | .collect(Collectors.toList());
189 | if (logger.isInfoEnabled()) {
190 | logger.trace("get successfully, clientId:{} batchSize:{} real size is {} and result is [batchId:{} , position:{}]",
191 | clientIdentity.getClientId(),
192 | batchSize,
193 | entrys.size(),
194 | batchId,
195 | events.getPositionRange());
196 | }
197 | // 直接提交ack
198 | ack(batchId);
199 | return new Message(batchId, false, entrys);
200 | }
201 | }
202 | }
203 |
204 | /**
205 | * 不指定 position 获取事件。canal 会记住此 client 最新的 position。
206 | * 如果是第一次 fetch,则会从 canal 中保存的最老一条数据开始输出。
207 | *
208 | *
209 | * 注意: meta获取和数据的获取需要保证顺序性,优先拿到meta的,一定也会是优先拿到数据,所以需要加同步. (不能出现先拿到meta,拿到第二批数据,这样就会导致数据顺序性出现问题)
210 | *
211 | */
212 | public Message getWithoutAck(int batchSize) throws CanalServerException {
213 | return getWithoutAck(batchSize, null, null);
214 | }
215 |
216 | /**
217 | * 不指定 position 获取事件。canal 会记住此 client 最新的 position。
218 | * 如果是第一次 fetch,则会从 canal 中保存的最老一条数据开始输出。
219 | *
220 | *
221 | * 几种case:
222 | * a. 如果timeout为null,则采用tryGet方式,即时获取
223 | * b. 如果timeout不为null
224 | * 1. timeout为0,则采用get阻塞方式,获取数据,不设置超时,直到有足够的batchSize数据才返回
225 | * 2. timeout不为0,则采用get+timeout方式,获取数据,超时还没有batchSize足够的数据,有多少返回多少
226 | *
227 | * 注意: meta获取和数据的获取需要保证顺序性,优先拿到meta的,一定也会是优先拿到数据,所以需要加同步. (不能出现先拿到meta,拿到第二批数据,这样就会导致数据顺序性出现问题)
228 | *
229 | */
230 | public Message getWithoutAck(int batchSize, Long timeout, TimeUnit unit)
231 | throws CanalServerException {
232 |
233 | checkStart();
234 | checkSubscribe();
235 |
236 | synchronized (canalInstance) {
237 | // 获取到流式数据中的最后一批获取的位置
238 | PositionRange positionRanges = canalInstance.getMetaManager().getLastestBatch(clientIdentity);
239 |
240 | Events events = null;
241 | if (positionRanges != null) { // 存在流数据
242 | events = getEvents(canalInstance.getEventStore(), positionRanges.getStart(), batchSize, timeout, unit);
243 | } else {// ack后第一次获取
244 | Position start = canalInstance.getMetaManager().getCursor(clientIdentity);
245 | if (start == null) { // 第一次,还没有过ack记录,则获取当前store中的第一条
246 | start = canalInstance.getEventStore().getFirstPosition();
247 | }
248 |
249 | events = getEvents(canalInstance.getEventStore(), start, batchSize, timeout, unit);
250 | }
251 |
252 | if (CollectionUtils.isEmpty(events.getEvents())) {
253 | /*logger.trace("getWithoutAck successfully, clientId:{} batchSize:{} but result is null",
254 | clientIdentity.getClientId(),
255 | batchSize);*/
256 | return new Message(-1, true, new ArrayList()); // 返回空包,避免生成batchId,浪费性能
257 | } else {
258 | // 记录到流式信息
259 | Long batchId = canalInstance.getMetaManager().addBatch(clientIdentity, events.getPositionRange());
260 | List entrys = Lists.transform(events.getEvents(), input -> {
261 | try {
262 | return CanalEntry.Entry.parseFrom(input.getRawEntry());
263 | } catch (InvalidProtocolBufferException e)
264 | {
265 | return null;
266 | }
267 | })
268 | .stream()
269 | .filter(Objects::nonNull)
270 | .collect(Collectors.toList());
271 | if (logger.isInfoEnabled()) {
272 | logger.trace("getWithoutAck successfully, clientId:{} batchSize:{} real size is {} and result is [batchId:{} , position:{}]",
273 | clientIdentity.getClientId(),
274 | batchSize,
275 | entrys.size(),
276 | batchId,
277 | events.getPositionRange());
278 | }
279 | return new Message(batchId, false, entrys);
280 | }
281 |
282 | }
283 | }
284 |
285 | /**
286 | * 进行 batch id 的确认。确认之后,小于等于此 batchId 的 Message 都会被确认。
287 | *
288 | *
289 | * 注意:进行反馈时必须按照batchId的顺序进行ack(需有客户端保证)
290 | *
291 | */
292 | public void ack(long batchId) throws CanalServerException {
293 | checkStart();
294 | checkSubscribe();
295 |
296 | PositionRange positionRanges = null;
297 | positionRanges = canalInstance.getMetaManager().removeBatch(clientIdentity, batchId); // 更新位置
298 | if (positionRanges == null) { // 说明是重复的ack/rollback
299 | throw new CanalServerException(String.format("ack error , clientId:%s batchId:%d is not exist , please check",
300 | clientIdentity.getClientId(),
301 | batchId));
302 | }
303 |
304 | // 更新cursor最好严格判断下位置是否有跳跃更新
305 | // Position position = lastRollbackPostions.get(clientIdentity);
306 | // if (position != null) {
307 | // // Position position =
308 | // canalInstance.getMetaManager().getCursor(clientIdentity);
309 | // LogPosition minPosition =
310 | // CanalEventUtils.min(positionRanges.getStart(), (LogPosition)
311 | // position);
312 | // if (minPosition == position) {// ack的position要晚于该最后ack的位置,可能有丢数据
313 | // throw new CanalServerException(
314 | // String.format(
315 | // "ack error , clientId:%s batchId:%d %s is jump ack , last ack:%s",
316 | // clientIdentity.getClientId(), batchId, positionRanges,
317 | // position));
318 | // }
319 | // }
320 |
321 | // 更新cursor
322 | if (positionRanges.getAck() != null) {
323 | canalInstance.getMetaManager().updateCursor(clientIdentity, positionRanges.getAck());
324 | if (logger.isInfoEnabled()) {
325 | logger.trace("ack successfully, clientId:{} batchId:{} position:{}",
326 | clientIdentity.getClientId(),
327 | batchId,
328 | positionRanges);
329 | }
330 | }
331 |
332 | // 可定时清理数据
333 | canalInstance.getEventStore().ack(positionRanges.getEnd());
334 |
335 | }
336 |
337 | /**
338 | * 回滚到未进行 {@link #ack} 的地方,下次fetch的时候,可以从最后一个没有 {@link #ack} 的地方开始拿
339 | */
340 | public void rollback() throws CanalServerException {
341 | checkStart();
342 | // 因为存在第一次链接时自动rollback的情况,所以需要忽略未订阅
343 | boolean hasSubscribe = canalInstance.getMetaManager().hasSubscribe(clientIdentity);
344 | if (!hasSubscribe) {
345 | return;
346 | }
347 |
348 | synchronized (canalInstance) {
349 | // 清除batch信息
350 | canalInstance.getMetaManager().clearAllBatchs(clientIdentity);
351 | // rollback eventStore中的状态信息
352 | canalInstance.getEventStore().rollback();
353 | logger.info("rollback successfully, clientId:{}", new Object[] { clientIdentity.getClientId() });
354 | }
355 | }
356 |
357 | /**
358 | * 回滚到未进行 {@link #ack} 的地方,下次fetch的时候,可以从最后一个没有 {@link #ack} 的地方开始拿
359 | */
360 | public void rollback(Long batchId) throws CanalServerException {
361 | checkStart();
362 |
363 | // 因为存在第一次链接时自动rollback的情况,所以需要忽略未订阅
364 | boolean hasSubscribe = canalInstance.getMetaManager().hasSubscribe(clientIdentity);
365 | if (!hasSubscribe) {
366 | return;
367 | }
368 | synchronized (canalInstance) {
369 | // 清除batch信息
370 | PositionRange positionRanges = canalInstance.getMetaManager().removeBatch(clientIdentity,
371 | batchId);
372 | if (positionRanges == null) { // 说明是重复的ack/rollback
373 | throw new CanalServerException(String.format("rollback error, clientId:%s batchId:%d is not exist , please check",
374 | clientIdentity.getClientId(),
375 | batchId));
376 | }
377 |
378 | // lastRollbackPostions.put(clientIdentity,
379 | // positionRanges.getEnd());// 记录一下最后rollback的位置
380 | // TODO 后续rollback到指定的batchId位置
381 | canalInstance.getEventStore().rollback();// rollback
382 | // eventStore中的状态信息
383 | logger.info("rollback successfully, clientId:{} batchId:{} position:{}",
384 | clientIdentity.getClientId(),
385 | batchId,
386 | positionRanges);
387 | }
388 | }
389 |
390 | /**
391 | * 根据不同的参数,选择不同的方式获取数据
392 | */
393 | private Events getEvents(CanalEventStore eventStore, Position start, int batchSize, Long timeout,
394 | TimeUnit unit) {
395 | if (timeout == null) {
396 | return eventStore.tryGet(start, batchSize);
397 | } else {
398 | try {
399 | if (timeout <= 0) {
400 | return eventStore.get(start, batchSize);
401 | } else {
402 | return eventStore.get(start, batchSize, timeout, unit);
403 | }
404 | } catch (Exception e) {
405 | throw new CanalServerException(e);
406 | }
407 | }
408 | }
409 |
410 | }
411 |
--------------------------------------------------------------------------------