[从源码学设计]蚂蚁金服SOFARegistry之续约和驱逐

0x00 摘要

SOFARegistry 是蚂蚁金服开源的一个生产级、高时效、高可用的服务注册中心。

本系列文章重点在于分析设计和架构,即利用多篇文章,从多个角度反推总结 DataServer 或者 SOFARegistry 的实现机制和架构思路,让大家借以学习阿里如何设计。

本文为第十五篇,介绍续约和剔除。

0x01 业务范畴

续约和剔除是服务注册与发现的重要功能,比如:

1.1 失效剔除

有些时候,我们的服务实例并不一定会正常下线,可能由于内存溢出,网络故障等原因使服务不能正常工作,而服务注册中心未收到”服务下线“的请求。

为了从服务列表中将这些无法提供服务的实例剔除。Server在启动的时候会创建一个定时任务,默认每隔一段时间(默认60s)将当前清单中,超时(默认为90s)没有续约的服务剔除出去。

1.2 服务续约

在注册完服务之后,服务提供者会维护一个心跳用来持续告诉 Server: "我还活着"。以防止 Server 的”剔除任务“将该服务实例从服务列表中排除出去。我们称该操作为服务续约(Renew)。

0x02 DatumLeaseManager

在 Data Server 端,DatumLeaseManager 实现了 “失效剔除” 和 “服务续约 “功能。

2.1 定义

DatumLeaseManager 的主要变量如下:

  • connectIdRenewTimestampMap 里面会维护每个服务最近一次发送心跳的时间,Eureka 里面也有类似的数据结构;

  • locksForConnectId :为了每次只有一个线程操作;lock for connectId: every connectId allows only one task to be created;

具体定义如下:

  1. public class DatumLeaseManager implements AfterWorkingProcess {
  2. /** record the latest heartbeat time for each connectId, format: connectId -> lastRenewTimestamp */
  3. private final Map<String, Long> connectIdRenewTimestampMap = new ConcurrentHashMap<>();
  4. /** lock for connectId , format: connectId -> true */
  5. private ConcurrentHashMap<String, Boolean> locksForConnectId = new ConcurrentHashMap();
  6. private volatile boolean serverWorking = false;
  7. private volatile boolean renewEnable = true;
  8. private AsyncHashedWheelTimer datumAsyncHashedWheelTimer;
  9. @Autowired
  10. private DataServerConfig dataServerConfig;
  11. @Autowired
  12. private DisconnectEventHandler disconnectEventHandler;
  13. @Autowired
  14. private DatumCache datumCache;
  15. @Autowired
  16. private DataNodeStatus dataNodeStatus;
  17. private ScheduledThreadPoolExecutor executorForHeartbeatLess;
  18. private ScheduledFuture<?> futureForHeartbeatLess;
  19. }

2.2 续约

2.2.1 数据结构

在DatumLeaseManager之中,主要是有如下数据结构对续约起作用。

  1. private ConcurrentHashMap<String, Boolean> locksForConnectId = new ConcurrentHashMap();
  2. private AsyncHashedWheelTimer datumAsyncHashedWheelTimer;

2.2.2 调用

在如下模块会调用到 review,这些都是 AbstractServerHandler。

  1. public class PublishDataHandler extends AbstractServerHandler<PublishDataRequest>
  2. public class DatumSnapshotHandler extends AbstractServerHandler<DatumSnapshotRequest>
  3. public class RenewDatumHandler extends AbstractServerHandler<RenewDatumRequest> implements AfterWorkingProcess
  4. public class UnPublishDataHandler extends AbstractServerHandler<UnPublishDataRequest>

2.2.3 续约

DatumLeaseManager 这里会记录最新的时间戳,然后启动scheduleEvictTask。

  1. public void renew(String connectId) {
  2. // record the renew timestamp
  3. connectIdRenewTimestampMap.put(connectId, System.currentTimeMillis());
  4. // try to trigger evict task
  5. scheduleEvictTask(connectId, 0);
  6. }

具体如下:

  • 如果当前ConnectionId已经被锁定,则返回;
  • 启动时间轮,加入一个定时操作,如果时间到,则:
    • 释放当前ConnectionId对应的lock;
    • 获取当前ConnectionId对应的上次续约时间,如果不存在,说明当前ConnectionId已经被移除,则返回;
    • 如果当前状态是不可续约状态,则设置下次定时操作时间,因为If in a non-working state, cannot clean up because the renew request cannot be received at this time;
    • 如果上次续约时间已经到期,则使用evict进行驱逐
    • 如果没到期,则会调用 scheduleEvictTask(connectId, nextDelaySec); 设置下次操作

具体代码如下:

  1. /**
  2. * trigger evict task: if connectId expired, create ClientDisconnectEvent to cleanup datums bind to the connectId
  3. * PS: every connectId allows only one task to be created
  4. */
  5. private void scheduleEvictTask(String connectId, long delaySec) {
  6. delaySec = (delaySec <= 0) ? dataServerConfig.getDatumTimeToLiveSec() : delaySec;
  7. // lock for connectId: every connectId allows only one task to be created
  8. Boolean ifAbsent = locksForConnectId.putIfAbsent(connectId, true);
  9. if (ifAbsent != null) {
  10. return;
  11. }
  12. datumAsyncHashedWheelTimer.newTimeout(_timeout -> {
  13. boolean continued = true;
  14. long nextDelaySec = 0;
  15. try {
  16. // release lock
  17. locksForConnectId.remove(connectId);
  18. // get lastRenewTime of this connectId
  19. Long lastRenewTime = connectIdRenewTimestampMap.get(connectId);
  20. if (lastRenewTime == null) {
  21. // connectId is already clientOff
  22. return;
  23. }
  24. /*
  25. * 1. lastRenewTime expires, then:
  26. * - build ClientOffEvent and hand it to DataChangeEventCenter.
  27. * - It will not be scheduled next time, so terminated.
  28. * 2. lastRenewTime not expires, then:
  29. * - trigger the next schedule
  30. */
  31. boolean isExpired =
  32. System.currentTimeMillis() - lastRenewTime > dataServerConfig.getDatumTimeToLiveSec() * 1000L;
  33. if (!isRenewEnable()) {
  34. nextDelaySec = dataServerConfig.getDatumTimeToLiveSec();
  35. } else if (isExpired) {
  36. int ownPubSize = getOwnPubSize(connectId);
  37. if (ownPubSize > 0) {
  38. evict(connectId);
  39. }
  40. connectIdRenewTimestampMap.remove(connectId, lastRenewTime);
  41. continued = false;
  42. } else {
  43. nextDelaySec = dataServerConfig.getDatumTimeToLiveSec()
  44. - (System.currentTimeMillis() - lastRenewTime) / 1000L;
  45. nextDelaySec = nextDelaySec <= 0 ? 1 : nextDelaySec;
  46. }
  47. }
  48. if (continued) {
  49. scheduleEvictTask(connectId, nextDelaySec);
  50. }
  51. }, delaySec, TimeUnit.SECONDS);
  52. }

2.2.4 图示

具体如下图所示

  1. +------------------+ +-------------------------------------------+
  2. |PublishDataHandler| | DatumLeaseManager |
  3. +--------+---------+ | |
  4. | | newTimeout |
  5. | | +----------------------> |
  6. doHandle | ^ + |
  7. | | | | |
  8. | renew | +-----------+--------------+ | |
  9. | +--------------> | | AsyncHashedWheelTimer | | |
  10. | | +-----+-----+--------------+ | |
  11. | | | ^ | |
  12. | | | | scheduleEvictTask | |
  13. | | evict | + v |
  14. | | | <----------------------+ |
  15. | +-------------------------------------------+
  16. | |
  17. | |
  18. | |
  19. | |
  20. v v

或者如下图所示:

  1. +------------------+ +-------------------+ +------------------------+
  2. |PublishDataHandler| | DatumLeaseManager | | AsyncHashedWheelTimer |
  3. +--------+---------+ +--------+----------+ +-----------+------------+
  4. | | new |
  5. doHandle +------------------------> |
  6. | renew | |
  7. +-------------------> | |
  8. | | |
  9. | | |
  10. | scheduleEvictTask |
  11. | | |
  12. | | newTimeout |
  13. | +----------> +------------------------> |
  14. | | | |
  15. | | | |
  16. | | | |
  17. | | | No +
  18. | | | <---------------+ if (ownPubSize > 0)
  19. | | | +
  20. | | v |
  21. | +--+ scheduleEvictTask | Yes
  22. | + v
  23. | | evict
  24. | | |
  25. v v v

2.3 驱逐

2.3.1 数据结构

在DatumLeaseManager之中,主要是有如下数据结构对续约起作用。

  1. private ScheduledThreadPoolExecutor executorForHeartbeatLess;
  2. private ScheduledFuture<?> futureForHeartbeatLess;

有两个调用途径,这样在数据变化时,就会看看是否可以驱逐:

  • 启动时调用;
  • 显式调用;

2.3.2 显式调用

LocalDataServerChangeEventHandler 类中,调用了datumLeaseManager.reset(),随之调用了 evict。

  1. @Override
  2. public void doHandle(LocalDataServerChangeEvent localDataServerChangeEvent) {
  3. isChanged.set(true);
  4. // Better change to Listener pattern
  5. localDataServerCleanHandler.reset();
  6. datumLeaseManager.reset();
  7. events.offer(localDataServerChangeEvent);
  8. }

DatumLeaseManager的reset调用了scheduleEvictTaskForHeartbeatLess启动了驱逐线程。

  1. public synchronized void reset() {
  2. if (futureForHeartbeatLess != null) {
  3. futureForHeartbeatLess.cancel(false);
  4. }
  5. scheduleEvictTaskForHeartbeatLess();
  6. }

2.3.3 启动调用

启动时候,会启动驱逐线程。

  1. @PostConstruct
  2. public void init() {
  3. ......
  4. executorForHeartbeatLess = new ScheduledThreadPoolExecutor(1, threadFactoryBuilder
  5. .setNameFormat("Registry-DatumLeaseManager-ExecutorForHeartbeatLess").build());
  6. scheduleEvictTaskForHeartbeatLess();
  7. }

2.3.4 驱逐

具体驱逐是通过启动了一个定时线程 EvictTaskForHeartbeatLess 来完成。

  1. private void scheduleEvictTaskForHeartbeatLess() {
  2. futureForHeartbeatLess = executorForHeartbeatLess.scheduleWithFixedDelay(
  3. new EvictTaskForHeartbeatLess(), dataServerConfig.getDatumTimeToLiveSec(),
  4. dataServerConfig.getDatumTimeToLiveSec(), TimeUnit.SECONDS);
  5. }

当时间端到达之后,会从datumCache获取目前所有connectionId,然后遍历connectionID,看看上次时间戳是否到期,如果到期就驱逐。

  1. /**
  2. * evict own connectIds with heartbeat less
  3. */
  4. private class EvictTaskForHeartbeatLess implements Runnable {
  5. @Override
  6. public void run() {
  7. // If in a non-working state, cannot clean up because the renew request cannot be received at this time.
  8. if (!isRenewEnable()) {
  9. return;
  10. }
  11. Set<String> allConnectIds = datumCache.getAllConnectIds();
  12. for (String connectId : allConnectIds) {
  13. Long timestamp = connectIdRenewTimestampMap.get(connectId);
  14. // no heartbeat
  15. if (timestamp == null) {
  16. int ownPubSize = getOwnPubSize(connectId);
  17. if (ownPubSize > 0) {
  18. evict(connectId);
  19. }
  20. }
  21. }
  22. }
  23. }

这里调用

  1. private void evict(String connectId) {
  2. disconnectEventHandler.receive(new ClientDisconnectEvent(connectId, System
  3. .currentTimeMillis(), 0));
  4. }

具体如下图:

  1. +--------------------------------------------------+
  2. | DatumLeaseManager |
  3. | |
  4. | |
  5. | EvictTaskForHeartbeatLess.run |
  6. | |
  7. | +----------------------------------------------+ |
  8. | | | |
  9. | | | | |
  10. | | | | |
  11. | | v | |
  12. | | | |
  13. | | allConnectIds = datumCache.getAllConnectIds()| |
  14. | | | |
  15. | | | | |
  16. | | | for (allConnectIds) | |
  17. | | v | |
  18. | | | |
  19. | | connectIdRenewTimestampMap | |
  20. | | | |
  21. | | | | |
  22. | | | no heartbeat | |
  23. | | v | |
  24. | | | |
  25. | | evict | |
  26. | | | |
  27. | +----------------------------------------------+ |
  28. +--------------------------------------------------+

2.3.5 驱逐处理业务

2.3.5.1 转发驱逐消息

驱逐消息需要转发出来,就对应到 DisconnectEventHandler . receive 这里,就是 EVENT_QUEUE.add(event);

  1. public class DisconnectEventHandler implements InitializingBean, AfterWorkingProcess {
  2. /**
  3. * a DelayQueue that contains client disconnect events
  4. */
  5. private final DelayQueue<DisconnectEvent> EVENT_QUEUE = new DelayQueue<>();
  6. @Autowired
  7. private SessionServerConnectionFactory sessionServerConnectionFactory;
  8. @Autowired
  9. private DataChangeEventCenter dataChangeEventCenter;
  10. @Autowired
  11. private DataNodeStatus dataNodeStatus;
  12. private static final BlockingQueue<DisconnectEvent> noWorkQueue = new LinkedBlockingQueue<>();
  13. public void receive(DisconnectEvent event) {
  14. if (dataNodeStatus.getStatus() != LocalServerStatusEnum.WORKING) {
  15. noWorkQueue.add(event);
  16. return;
  17. }
  18. EVENT_QUEUE.add(event);
  19. }
  20. }

在 afterPropertiesSet 中会启动一个 Thread,循环从 EVENT_QUEUE 之中取出消息,然后处理,具体就是:

  • 从 sessionServerConnectionFactory 之中移除对应的 Connection;
  • 给 dataChangeEventCenter 发一个 ClientChangeEvent 通知;

具体如下:

  1. @Override
  2. public void afterPropertiesSet() {
  3. Executor executor = ExecutorFactory
  4. .newSingleThreadExecutor(DisconnectEventHandler.class.getSimpleName());
  5. executor.execute(() -> {
  6. while (true) {
  7. try {
  8. DisconnectEvent disconnectEvent = EVENT_QUEUE.take();
  9. if (disconnectEvent.getType() == DisconnectTypeEnum.SESSION_SERVER) {
  10. SessionServerDisconnectEvent event = (SessionServerDisconnectEvent) disconnectEvent;
  11. String processId = event.getProcessId();
  12. //check processId confirm remove,and not be registered again when delay time
  13. String sessionServerHost = event.getSessionServerHost();
  14. if (sessionServerConnectionFactory
  15. .removeProcessIfMatch(processId,sessionServerHost)) {
  16. Set<String> connectIds = sessionServerConnectionFactory
  17. .removeConnectIds(processId);
  18. if (connectIds != null && !connectIds.isEmpty()) {
  19. for (String connectId : connectIds) {
  20. unPub(connectId, event.getRegisterTimestamp());
  21. }
  22. }
  23. }
  24. } else {
  25. ClientDisconnectEvent event = (ClientDisconnectEvent) disconnectEvent;
  26. unPub(event.getConnectId(), event.getRegisterTimestamp());
  27. }
  28. }
  29. }
  30. });
  31. }
  32. /**
  33. *
  34. * @param connectId
  35. * @param registerTimestamp
  36. */
  37. private void unPub(String connectId, long registerTimestamp) {
  38. dataChangeEventCenter.onChange(new ClientChangeEvent(connectId, dataServerConfig
  39. .getLocalDataCenter(), registerTimestamp));
  40. }

如下图所示

  1. +--------------------------------------------------+
  2. | DatumLeaseManager |
  3. | |
  4. | |
  5. | EvictTaskForHeartbeatLess.run |
  6. | |
  7. | +----------------------------------------------+ |
  8. | | | |
  9. | | | | |
  10. | | | | |
  11. | | v | |
  12. | | | |
  13. | | allConnectIds = datumCache.getAllConnectIds()| |
  14. | | | |
  15. | | | | |
  16. | | | for (allConnectIds) | | +------------------------+
  17. | | v | | | |
  18. | | | | | DisconnectEventHandler |
  19. | | connectIdRenewTimestampMap | | | |
  20. | | | | | +-------------+ |
  21. | | | | | | | noWorkQueue | |
  22. | | | no heartbeat | | | +-------------+ |
  23. | | v | | receive | |
  24. | | | | | +--------------+ |
  25. | | evict +---------------------------------> | EVENT_QUEUE | |
  26. | | | | | +--------------+ |
  27. | +----------------------------------------------+ | +------------------------+
  28. +--------------------------------------------------+
2.3.5.1 DataChangeEventCenter 转发

逻辑然后来到了 DataChangeEventCenter,这里也是起到转发作用。

  1. public class DataChangeEventCenter {
  2. /**
  3. * queues of DataChangeEvent
  4. */
  5. private DataChangeEventQueue[] dataChangeEventQueues;
  6. /**
  7. * receive changed publisher, then wrap it into the DataChangeEvent and put it into dataChangeEventQueue
  8. *
  9. * @param publisher
  10. * @param dataCenter
  11. */
  12. public void onChange(Publisher publisher, String dataCenter) {
  13. int idx = hash(publisher.getDataInfoId());
  14. Datum datum = new Datum(publisher, dataCenter);
  15. if (publisher instanceof UnPublisher) {
  16. datum.setContainsUnPub(true);
  17. }
  18. if (publisher.getPublishType() != PublishType.TEMPORARY) {
  19. dataChangeEventQueues[idx].onChange(new DataChangeEvent(DataChangeTypeEnum.MERGE,
  20. DataSourceTypeEnum.PUB, datum));
  21. } else {
  22. dataChangeEventQueues[idx].onChange(new DataChangeEvent(DataChangeTypeEnum.MERGE,
  23. DataSourceTypeEnum.PUB_TEMP, datum));
  24. }
  25. }
  26. }
2.3.5.2 DataChangeEventQueue 处理

具体业务是 DataChangeEventQueue 完成的,就是调用 addTempChangeData 与 handleDatum 处理对应数据,就是处理这些需要驱逐的数据。

当event被取出之后,会根据 DataChangeScopeEnum.DATUM 的不同,会做不同的处理。

  • 如果是DataChangeScopeEnum.DATUM,则判断dataChangeEvent.getSourceType();

    • 如果是 DataSourceTypeEnum.PUB_TEMP,则addTempChangeData,就是往CHANGE_QUEUE添加ChangeData;
    • 如果不是,则handleDatum;
  • 如果是DataChangeScopeEnum.CLIENT,则handleClientOff((ClientChangeEvent) event);
  • 如果是DataChangeScopeEnum.SNAPSHOT,则handleSnapshot((DatumSnapshotEvent) event);

具体参见前文 从源码学设计]蚂蚁金服SOFARegistry之消息总线异步处理

  1. +--------------------------------------------------+
  2. | DatumLeaseManager |
  3. | |
  4. | |
  5. | EvictTaskForHeartbeatLess.run |
  6. | |
  7. | +----------------------------------------------+ |
  8. | | | |
  9. | | | | |
  10. | | | | |
  11. | | v | |
  12. | | | |
  13. | | allConnectIds = datumCache.getAllConnectIds()| |
  14. | | | |
  15. | | | | |
  16. | | | for (allConnectIds) | | +------------------------+
  17. | | v | | | |
  18. | | | | | DisconnectEventHandler |
  19. | | connectIdRenewTimestampMap | | | |
  20. | | | | | +-------------+ |
  21. | | | | | | | noWorkQueue | |
  22. | | | no heartbeat | | | +-------------+ |
  23. | | v | | receive | |
  24. | | | | | +--------------+ |
  25. | | evict +---------------------------------> | EVENT_QUEUE | |
  26. | | | | | +--------------+ |
  27. | +----------------------------------------------+ | +------------------------+
  28. +--------------------------------------------------+ |
  29. |
  30. +----------------------+ | onChange
  31. | DataChangeEventQueue | v
  32. | | +--------+------------------+
  33. | | | DataChangeEventCenter |
  34. | +------------+ | | |
  35. | | eventQueue | | add DataChangeEvent | |
  36. | +------------+ | | +-----------------------+ |
  37. | | <-----------------------------+ | | dataChangeEventQueues | |
  38. | addTempChangeData | | +-----------------------+ |
  39. | | +---------------------------+
  40. | handleDatum |
  41. | |
  42. +----------------------+

0xFF 参考

蚂蚁金服服务注册中心如何实现 DataServer 平滑扩缩容

蚂蚁金服服务注册中心 SOFARegistry 解析 | 服务发现优化之路

服务注册中心 Session 存储策略 | SOFARegistry 解析

海量数据下的注册中心 - SOFARegistry 架构介绍

服务注册中心数据分片和同步方案详解 | SOFARegistry 解析

蚂蚁金服开源通信框架SOFABolt解析之连接管理剖析

蚂蚁金服开源通信框架SOFABolt解析之超时控制机制及心跳机制

蚂蚁金服开源通信框架 SOFABolt 协议框架解析

蚂蚁金服服务注册中心数据一致性方案分析 | SOFARegistry 解析

蚂蚁通信框架实践

sofa-bolt 远程调用

sofa-bolt学习

SOFABolt 设计总结 - 优雅简洁的设计之道

SofaBolt源码分析-服务启动到消息处理

SOFABolt 源码分析

SOFABolt 源码分析9 - UserProcessor 自定义处理器的设计

SOFARegistry 介绍

SOFABolt 源码分析13 - Connection 事件处理机制的设计

[从源码学设计]蚂蚁金服SOFARegistry之续约和驱逐的更多相关文章

  1. [从源码学设计]蚂蚁金服SOFARegistry之程序基本架构

    [从源码学设计]蚂蚁金服SOFARegistry之程序基本架构 0x00 摘要 之前我们通过三篇文章初步分析了 MetaServer 的基本架构,MetaServer 这三篇文章为我们接下来的工作做了 ...

  2. [从源码学设计]蚂蚁金服SOFARegistry之网络封装和操作

    [从源码学设计]蚂蚁金服SOFARegistry之网络封装和操作 目录 [从源码学设计]蚂蚁金服SOFARegistry之网络封装和操作 0x00 摘要 0x01 业务领域 1.1 SOFARegis ...

  3. [从源码学设计]蚂蚁金服SOFARegistry网络操作之连接管理

    [从源码学设计]蚂蚁金服SOFARegistry网络操作之连接管理 目录 [从源码学设计]蚂蚁金服SOFARegistry网络操作之连接管理 0x00 摘要 0x01 业务领域 1.1 应用场景 0x ...

  4. [从源码学设计]蚂蚁金服SOFARegistry之消息总线

    [从源码学设计]蚂蚁金服SOFARegistry之消息总线 目录 [从源码学设计]蚂蚁金服SOFARegistry之消息总线 0x00 摘要 0x01 相关概念 1.1 事件驱动模型 1.1.1 概念 ...

  5. [从源码学设计]蚂蚁金服SOFARegistry之消息总线异步处理

    [从源码学设计]蚂蚁金服SOFARegistry之消息总线异步处理 目录 [从源码学设计]蚂蚁金服SOFARegistry之消息总线异步处理 0x00 摘要 0x01 为何分离 0x02 业务领域 2 ...

  6. [从源码学设计]蚂蚁金服SOFARegistry之存储结构

    [从源码学设计]蚂蚁金服SOFARegistry之存储结构 目录 [从源码学设计]蚂蚁金服SOFARegistry之存储结构 0x00 摘要 0x01 业务范畴 1.1 缓存 1.2 DataServ ...

  7. [从源码学设计]蚂蚁金服SOFARegistry之推拉模型

    [从源码学设计]蚂蚁金服SOFARegistry之推拉模型 目录 [从源码学设计]蚂蚁金服SOFARegistry之推拉模型 0x00 摘要 0x01 相关概念 1.1 推模型和拉模型 1.1.1 推 ...

  8. [从源码学设计]蚂蚁金服SOFARegistry之时间轮的使用

    [从源码学设计]蚂蚁金服SOFARegistry之时间轮的使用 目录 [从源码学设计]蚂蚁金服SOFARegistry之时间轮的使用 0x00 摘要 0x01 业务领域 1.1 应用场景 0x02 定 ...

  9. [从源码学设计]蚂蚁金服SOFARegistry 之 自动调节间隔周期性任务

    [从源码学设计]蚂蚁金服SOFARegistry 之 自动调节间隔周期性任务 目录 [从源码学设计]蚂蚁金服SOFARegistry 之 自动调节间隔周期性任务 0x00 摘要 0x01 业务领域 0 ...

随机推荐

  1. 【Codeforces 1181E】A Story of One Country (Easy & Hard)(分治 & set)

    Description 在一个二维平面上有若干个矩形.定义一个矩形的(或有边在无限远处)区域为符合条件的条件为: 这个区域仅包含一个矩形,且不能使边界穿过任何一个矩形的内部. 这个区域可以用一个水平或 ...

  2. 【NOI2018】你的名字(SAM & 线段树合并)

    Description Hint Solution 不妨先讨论一下无区间限制的做法. 首先"子串"可以理解为"前缀的后缀",因此我们定义一个 \(\lim(i) ...

  3. 题解-CF163E e-Government

    题面 CF163E e-Government 给 \(n\) 个字符串 \(s_i\) 和 \(q\) 个询问,刚开始字符串都服役.每次操作将集合中的一个字符串设为退役或服役,或查询与文本串 \(S_ ...

  4. rsync+inotify-tools实时备份脚本

    1.1 实时备份 1.需求分析: 为什么要实时复制 因为nfs是单点非常的不安全  而通过定时任务备份会造成数据丢失 这是就需要需要实时备份 2实时方案 1).搭建好服务端backup与客户端nfs的 ...

  5. 5.深入Istio源码:Pilot-agent作用及其源码分析

    转载请声明出处哦~,本篇文章发布于luozhiyun的博客:https://www.luozhiyun.com 本文使用的Istio源码是 release 1.5. 介绍 Sidecar在注入的时候会 ...

  6. gnuplot添加直线和箭头

    http://blog.csdn.net/bill_chuang/article/details/18215051 6.在图中添加直线和箭头 gnuplot> set arrow from 0. ...

  7. SQL Server NULL值用法及处理详解

    用法如下: 1.如果表中的某个列是可选的,那么我们可以在不向该列添加值的情况下插入新记录或更新已有的记录,这意味着该字段将以 NULL 值保存. 2.NULL 用作未知的或不适用的值的占位符. 3.定 ...

  8. Linux 设置日期时间

    linux 日期设置 直接设置日期和时间 date -s 2019-02-11 date -s 12:12:12 date -s "2019-02-11 12:12:12"

  9. Kafka中使用Avro编码、解码消息

    1.消费者代码 import com.twitter.bijection.Injection; import com.twitter.bijection.avro.GenericAvroCodecs; ...

  10. Eureka系列(三)获取服务Client端具体实现

    获取服务Client 端流程   我们先看下面这张图片,这张图片简单描述了下我们Client是如何获取到Server已续约实例信息的流程:  从图片中我们可以知晓大致流程就是Client会自己开启一个 ...