Серия Learning Microservices (7): анализ принципов nacos

Микросервисы

Обзор серии:

Серия Learning Microservices (1): Общие сведения о микросервисах

Серия Learning Microservices (2): Создание сервисов на основе Springboot

Изучаем серию микросервисов (3): разделение фронтенда и бэкенда страницы springboot+ и написание интерфейса в стиле RESTFUL

Серия учебных микросервисов (4): шлюз сервисного шлюза springboot

Серия Learning Microservices (5): микросервисы springboot используют nacos в качестве реестра

Изучаем серию микросервисов (6): микросервис springboot использует nacos в качестве центра конфигурации

Наши первые две статьи касались роли nacos в обнаружении реестра служб и распределенной конфигурации. Когда вы используете nacos в реальном производстве, вы поймете, насколько удобен nacos.Возможность регистрации службы на основе nacos может выполнять изящную функцию остановки службы.Отныне нам не нужно ждать до полуночи, чтобы выпустить версию. Просто найдите сервисный кластер, соответствующий низкопиковой бизнес-версии, в любое время. Далее давайте рассмотрим принцип накоса.

Анализ принципа регистрации и обнаружения службы Nacos

Одна из функций nacos — служить модулем регистрации и обнаружения служб, также известным как центр регистрации. nacos поддерживает конфигурацию регистрации и управление всеми основными платформами служб. Промежуточное программное обеспечение, с которым мы впервые связались с микрослужбами, — это Dubbo. очень люблю даббо.Если вы с ним знакомы, то следующая картина не будет незнакомой:

在这里插入图片描述

На самом деле, nacos и dubbo являются открытым исходным кодом Alibaba, поэтому идеи дизайна в основном одинаковы. Давайте возьмем приведенную выше картинку для иллюстрации и сначала опишем значение каждого узла.

узел инструкция
Provider Поставщик услуг, предоставляющий услугу
Consumer Потребитель службы, вызывающий удаленную службу
Registry Реестр для регистрации и обнаружения служб
Monitor Центр мониторинга, который подсчитывает сервисные вызовы и время вызова
Container работающий сервисный контейнер
Наш nacos в это время действует как значение узла реестра.

Как регистрируются микросервисы

Регистрация сервиса, это действие, так где же регистрация, тогда в реестре должен быть контейнер для хранения зарегистрированных сервисов. Этот контейнер может быть картой, массивом, постоянным mysql, mongodb. После того, как у нас есть контейнерное хранилище, мы задаемся вопросом, что регистрируется? То есть, каково содержимое контейнера? По сути, основная информация, размещенная в контейнере, — это ip+порт, на котором находится каждая нода микросервиса. Только тогда вызывающий абонент может узнать, где ваш поставщик услуг предоставляет услуги, чтобы вызывающий абонент мог вызвать службу по этой линии.

在这里插入图片描述

Как показано на рисунке выше, есть служба поставщика услуг 1. У этой службы 2 экземпляра.Если вы плохо разбираетесь, то можете понять, что эта служба 1 размещена на 2 серверах и оба запускают и уведомляют центр регистрации ip и порт, на котором находится служба, то есть nacos.

在这里插入图片描述Реестр обрабатывает каждую службу, поддерживая serviceHolder через пространство экземпляров для каждой службы. После того, как каждая служба запущена и зарегистрирована в реестре, используется режим пульса, чтобы убедиться, что реестр может знать статус выживания каждой службы. Если обнаружена служба с пульсом или без него, реестр немедленно предложит недопустимый экземпляр службы.Период пульса по умолчанию составляет 5 секунд.Сервер Nacos установит экземпляр как неработоспособный через 15 секунд отсутствия пульса и не получит в течение 30 секунд. Этот временный экземпляр удаляется во время такта.

在这里插入图片描述

Как обнаруживаются микросервисы

После того, как сервис зарегистрирован в реестре, потребитель сервиса может подписаться на сервис в реестре и отправить прослушиватель.Когда сервис в реестре изменится, слушатель получит уведомление, и потребитель обновит локальный A список экземпляров службы, чтобы убедиться, что все службы доступны.在这里插入图片描述

Как показано на рисунке выше, если потребитель подписывается на услугу, список информации об услугах будет поддерживаться локально на основе памяти, а затем вызов службы будет получен непосредственно из локального списка.

Как звонить между двумя

Поставщик услуг и потребитель услуг вызываются через feign+ленту Feign обеспечивает инкапсуляцию и вызов HTTP-запросов, а лента обеспечивает балансировку нагрузки. Существует множество способов реализации балансировки нагрузки, включая метод опроса, случайный метод и модуль после хеширования IP-адреса запроса и т. д. После того, как клиент Nacos получит полный список экземпляров службы, он выполнит на клиенте алгоритм балансировки нагрузки, чтобы получить доступный экземпляр.Метод по умолчанию — получение его случайным образом.

在这里插入图片描述

Анализ принципа реализации Nacos Config

Для nacosconfig он фактически предоставляет ряд грубых интерфейсов доступа, чтобы приложение могло выполнять операции добавления, удаления, изменения и проверки конфигурации. Для конфигурации nacos это то, как хранить данные и как сохранять данные. Давайте нарисуем картинку, чтобы увидеть:

在这里插入图片描述

В этом суть картинки выше. Затем, когда конфигурация конфигурации обновляется и изменяется, связанные приложения должны следовать за соответствующими изменениями.Вот вопрос.Как клиент узнает, что конфигурация изменилась, и когда клиент ее обновляет? Есть два способа обновления: push и pull;

  • Клиент регулярно активно извлекает конфигурацию с сервера и заменяет ее, если есть какие-либо изменения.
  • Сервер активно отправляет измененный контент клиенту.

Оба метода имеют свои преимущества и недостатки, например, для режима push требуется длительное соединение между сервером и клиентом, в этом случае серверу необходимо тратить много ресурсов на поддержание соединения, а сердцебиение необходимо добавить механизм для поддержания этого соединения. Для режима извлечения клиент должен регулярно посещать сервер, поэтому будет временной интервал, и данные в реальном времени не могут быть гарантированы. Какую модель использует nacos? nacos использует режим вытягивания, который является особым режимом вытягивания, который представляет собой механизм длительного опроса, который мы обычно слушаем.

  1. Если конфигурация сервера изменилась

在这里插入图片描述

  1. Если конфигурация сервера и клиента не менялись

Если клиент потянет и обнаружит, что конфигурация клиента и сервера согласована (фактически об этом судят по MD5), то сервер сначала придержит запрос и не вернет его, пока конфигурация не изменится в течение этого периода. возвращение. Его шаг — проверить, изменилась ли конфигурация после получения запроса сервером nacos, если нет, запустить запланированную задачу и отложить выполнение на 29,5 с. При этом запрос на подключение текущего клиента ставится в очередь. Тогда сервер не возвращает результат клиенту в это время, а возврат срабатывает при наличии следующих двух ситуаций.

  • Просто подождите 29,5 с, чтобы запустить автоматическую проверку.
  • Были изменения конфигурации в 29.5s

После этих двух ситуаций операция извлечения завершена. Преимущество этого заключается в том, что конфигурация клиента может быть изменена и обновлена ​​вовремя, а также это снижает нагрузку на сервер при опросе. Итак, в предыдущей статье мы сказали, что тайм-аут по умолчанию для этой длинной ссылки составляет 30 секунд.

在这里插入图片描述

Небольшая яма на основе элегантного сервиса остановки nacos.

В процессе публикации с помощью Canary Grayscale было обнаружено, что сервис уже перевел исходный зарегистрированный сервис в автономный режим, но по-прежнему поступали запросы на трафик, что нарушало официальную функцию оффлайн и автономный режим в секундах. Среди них компонент балансировки нагрузки, используемый балансировкой нагрузки нашего сервиса, — лента. Давайте посмотрим на исходный код:

Основной механизм обновления реестра

public String update(HttpServletRequest request) throws Exception {
        String serviceName = WebUtils.required(request, CommonParams.SERVICE_NAME);
        String namespaceId = WebUtils.optional(request, CommonParams.NAMESPACE_ID, Constants.DEFAULT_NAMESPACE_ID);
        String agent = request.getHeader("Client-Version");
        if (StringUtils.isBlank(agent)) {
            agent = request.getHeader("User-Agent");
        }
        ClientInfo clientInfo = new ClientInfo(agent);
        if (clientInfo.type == ClientInfo.ClientType.JAVA &&
                clientInfo.version.compareTo(VersionUtil.parseVersion("1.0.0")) >= 0) {
            serviceManager.updateInstance(namespaceId, serviceName, parseInstance(request));
      n "ok";
}

В приведенном выше коде показана логика перехода naco в онлайн и офлайн.Вы можете видеть, что основной логикой является метод parseInstance(), а информация об экземпляре в нем получается из запроса. Взгляните еще раз на метод updateInstance:

public void updateInstance(String namespaceId, String serviceName, Instance instance) throws NacosException {
	Service service = getService(namespaceId, serviceName);
	if (service == null) {
		throw new NacosException(NacosException.INVALID_PARAM, "service not found, namespace: " + namespaceId + ", service: " + serviceName);
	}
	if (!service.allIPs().contains(instance)) {
		throw new NacosException(NacosException.INVALID_PARAM, "instance not exist: " + instance);
	}
	addInstance(namespaceId, serviceName, instance.isEphemeral(), instance);
}
public void addInstance(String namespaceId, String serviceName, boolean ephemeral, Instance... ips) throws NacosException {
	String key = KeyBuilder.buildInstanceListKey(namespaceId, serviceName, ephemeral);
	Service service = getService(namespaceId, serviceName);
	List<Instance> instanceList = addIpAddresses(service, ephemeral, ips);
	Instances instances = new Instances();
	instances.setInstanceList(instanceList);
	consistencyService.put(key, instances);
}

Вы можете видеть, что экземпляр находится в автономном режиме, и информационные данные экземпляра на сервере немедленно обновляются.

Механизм обновления балансировки нагрузки ленты

NacosServerList наследует AbstractServerList и, наконец, загружает все серверы в DynamicServerListLoadBalancer.

public class NacosServerList extends AbstractServerList<NacosServer> {
	private NacosDiscoveryProperties discoveryProperties;
	private String serviceId;
	public NacosServerList(NacosDiscoveryProperties discoveryProperties) {
		this.discoveryProperties = discoveryProperties;
	}
	@Override
	public List<NacosServer> getInitialListOfServers() {
		return getServers();
	}
	@Override
	public List<NacosServer> getUpdatedListOfServers() {
		return getServers();
	}
	private List<NacosServer> getServers() {
		try {
			List<Instance> instances = discoveryProperties.namingServiceInstance()
					.selectInstances(serviceId, true);
			return instancesToServerList(instances);
		}
		catch (Exception e) {
			throw new IllegalStateException(
					"Can not get service instances from nacos, serviceId=" + serviceId,
					e);
		}
	}
	private List<NacosServer> instancesToServerList(List<Instance> instances) {
		List<NacosServer> result = new ArrayList<>();
		if (null == instances) {
			return result;
		}
		for (Instance instance : instances) {
			result.add(new NacosServer(instance));
		}
		return result;
	}
	public String getServiceId() {
		return serviceId;
	}
	@Override
	public void initWithNiwsConfig(IClientConfig iClientConfig) {
		this.serviceId = iClientConfig.getClientName();
	}
}
protected final ServerListUpdater.UpdateAction updateAction = new ServerListUpdater.UpdateAction() {
        @Override
        public void doUpdate() {
            updateListOfServers();
        }
    };
public DynamicServerListLoadBalancer(IClientConfig clientConfig) {
	initWithNiwsConfig(clientConfig);
}

@Override
public void initWithNiwsConfig(IClientConfig clientConfig) {
	try {
		super.initWithNiwsConfig(clientConfig);
		String niwsServerListClassName = clientConfig.getPropertyAsString( CommonClientConfigKey.NIWSServerListClassName, DefaultClientConfigImpl.DEFAULT_SEVER_LIST_CLASS);
		ServerList<T> niwsServerListImpl = (ServerList<T>) ClientFactory
                    .instantiateInstanceWithClientConfig(niwsServerListClassName, clientConfig);
                //得到所有的server实现
		this.serverListImpl = niwsServerListImpl;

		if (niwsServerListImpl instanceof AbstractServerList) {
			AbstractServerListFilter<T> niwsFilter = ((AbstractServerList) niwsServerListImpl)
                        .getFilterImpl(clientConfig);
			niwsFilter.setLoadBalancerStats(getLoadBalancerStats());
			this.filter = niwsFilter;
		}

		String serverListUpdaterClassName = clientConfig.getPropertyAsString( CommonClientConfigKey.ServerListUpdaterClassName, DefaultClientConfigImpl.DEFAULT_SERVER_LIST_UPDATER_CLASS);
               // 获取Updater对象
		this.serverListUpdater = (ServerListUpdater) ClientFactory.instantiateInstanceWithClientConfig(serverListUpdaterClassName, clientConfig);

		restOfInit(clientConfig);
	} catch (Exception e) {
		throw new RuntimeException(
                    "Exception while initializing NIWSDiscoveryLoadBalancer:"
                            + clientConfig.getClientName()
                            + ", niwsClientConfig:" + clientConfig, e);
	}
}
void restOfInit(IClientConfig clientConfig) {
	boolean primeConnection = this.isEnablePrimingConnections();
	// turn this off to avoid duplicated asynchronous priming done in BaseLoadBalancer.setServerList()
	this.setEnablePrimingConnections(false);
        //采用定时任务进行定时刷新实例信息缓存
	enableAndInitLearnNewServersFeature();//最重要的点
        //进行一次实例拉取操作
	updateListOfServers();
	if (primeConnection && this.getPrimeConnections() != null) {
		this.getPrimeConnections() .primeConnections(getReachableServers());
	}
	this.setEnablePrimingConnections(primeConnection);
	LOGGER.info("DynamicServerListLoadBalancer for client {} initialized: {}", clientConfig.getClientName(), this.toString());
}
// 这里就是进行实例信息缓存更新的操作
@VisibleForTesting
public void updateListOfServers() {
	List<T> servers = new ArrayList<T>();
	if (serverListImpl != null) {
    // 调用拉取新实例信息的方法
		servers = serverListImpl.getUpdatedListOfServers();
		LOGGER.debug("List of Servers for {} obtained from Discovery client: {}", getIdentifier(), servers);
    // 用Filter对拉取的servers列表进行更新
		if (filter != null) {
			servers = filter.getFilteredListOfServers(servers);
			LOGGER.debug("Filtered List of Servers for {} obtained from Discovery client: {}", getIdentifier(), servers);
		}
	}
  // 更新实例列表
	updateAllServerList(servers);
}

Затем мы рассмотрим работу самого важного метода enableAndInitLearnNewServersFeature.

@Override
public synchronized void start(final UpdateAction updateAction) {
	if (isActive.compareAndSet(false, true)) {
		final Runnable wrapperRunnable = new Runnable() {
			@Override
			public void run() {
				if (!isActive.get()) {
					if (scheduledFuture != null) {
						scheduledFuture.cancel(true);
					}
					return;
				}
				try {
                   // 这里就是在DynamicServerListLoadBalancer中的Servers实现
					updateAction.doUpdate();
					lastUpdated = System.currentTimeMillis();
				} catch (Exception e) {
					logger.warn("Failed one update cycle", e);
				}
			}
		};
                // 默认定时任务执行时间间隔为30s
		scheduledFuture = getRefreshExecutor().scheduleWithFixedDelay(
                    wrapperRunnable,
                    initialDelayMs,
                    refreshIntervalMs,
                    TimeUnit.MILLISECONDS);
	} else {
		logger.info("Already active, no-op");
	}
}

В конце концов, несмотря на то, что экземпляр переходит в онлайн и автономный режим за считанные секунды, потому что в Spring Cloud обновление информации об экземпляре загрузочного компонента кролика выполняется в виде временной задачи.Возможно, что эта задача только что была выполнена в последнем секунду, и вы выполните его в следующую секунду.Экземпляр переходит в режим онлайн и офлайн, поэтому, если лента должна воспринять это изменение, она должна ждать refreshIntervalMs секунд, прежде чем сможет его воспринять. Поэтому, если мы поймем основную причину, мы сможем лучше использовать эти компоненты снизу.