How does Spring Cloud implement service governance

Spring Cloud Consul github.com/spring-clou… Spring Cloud Commons github.com/spring-clou…


Table of Contents

  • How does Spring Cloud implement service governance
    • Spring Cloud Commons service Governance analysis
      • The service registry
      • Service discovery
      • Health detection
    • Spring Cloud Consul implementation
      • implementationServiceRegistryfunction
      • conclusion
  • reference

Reading ahead is recommended

Go | Go use consul to do service discovery

Spring Cloud Commons service Governance analysis

Spring is often designed to make it easier to extend and eliminate boilerplate code, and Spring Clond does the same.

By looking at Spring Cloud Commons, the most important project in the Spring Cloud architecture, which defines service registration, service discovery, complex equalization related interfaces, and some common components, We can briefly understand the core process of Spring Cloud registration discovery.

The following project structure is provided in the Spring Clond Commons project (some code files and structure are omitted here)

└ ─ ─ the SRC ├ ─ ─ the main │ ├ ─ ─ Java │ │ └ ─ ─ org │ │ └ ─ ─ springframework │ │ └ ─ ─ cloud │ │ ├ ─ ─ client │ │ │ ├ ─ ─ DefaultServiceInstance. Java │ │ │ ├ ─ ─ ServiceInstance. Java Spring Cloud definition of service instance information │ │ │ ├ ─ ─ the discovery service discovery related │ │ │ │ ├ ─ ─ DiscoveryClient. Java │ │ │ │ ├ ─ ─ EnableDiscoveryClient. Java │ │ │ │ ├ ─ ─ EnableDiscoveryClientImportSelector. Java │ │ │ │ ├ ─ ─ ManagementServerPortUtils. Java │ │ │ │ ├ ─ ─ ReactiveDiscoveryClient. Java │ │ │ │ ├ ─ ─ composite │ │ │ │ │ ├ ─ ─ CompositeDiscoveryClient. Java │ │ │ │ │ ├ ─ ─ CompositeDiscoveryClientAutoConfiguration. Java │ │ │ │ │ └ ─ ─ reactive │ │ │ │ │ ├ ─ ─ ReactiveCompositeDiscoveryClient. Java │ │ │ │ │ └ ─ ─ ReactiveCompositeDiscoveryClientAutoConfiguration. Java │ │ │ │ ├ ─ ─ the health checkup related │ │ │ │ ├ ─ ─ DiscoveryClientHealthIndicator. Java │ │ │ │ ├ ─ ─ DiscoveryClientHealthIndicatorProperties. Java │ │ │ │ ├ ─ ─ DiscoveryCompositeHealthContributor. Java │ │ │ │ ├ ─ ─ DiscoveryHealthIndicator. Java │ │ │ │ └ ─ ─ reactive │ │ │ │ ├ ─ ─ ReactiveDiscoveryClientHealthIndicator. Java │ │ │ │ ├ ─ ─ ReactiveDiscoveryCompositeHealthContributor. Java │ │ │ │ └ ─ ─ ReactiveDiscoveryHealthIndicator. Java │ │ │ ├ ─ ─ Loadbalancer below this load balancing is related to the logical │ │ │ └ ─ ─ serviceregistry service registry related │ │ │ ├ ─ ─ AbstractAutoServiceRegistration. Java │ │ │ ├ ─ ─ AutoServiceRegistration. Java │ │ │ ├ ─ ─ AutoServiceRegistrationAutoConfiguration. Java │ │ │ ├ ─ ─ AutoServiceRegistrationConfiguration. Java │ │ │ ├ ─ ─ AutoServiceRegistrationProperties. Java │ │ │ ├ ─ ─ Registration. Java │ │ │ ├ ─ ─ ServiceRegistry. Java │ │ │ ├ ─ ─ ServiceRegistryAutoConfiguration. Java │ │ ├ ─ ─ Commons │ │ ├ ─ ─ httpclient HTTP The factory class, In the configuration can choose to use Apache Http or OKHttp │ │ │ ├ ─ ─ ApacheHttpClientFactory. Java │ │ │ └ ─ ─ OkHttpClientFactory. Java │ │ └ ─ ─ util │ │ ├ ─ ─ IdUtils. Java generated by this tool class instance id │ │ └ ─ ─ InetUtils. Java Spring Cloud is through this utility class is to get the IP address of the service project │ └ ─ ─ resources │ └ ─ ─ Meta-inf │ ├ ─ ─ additional - spring - the configuration - metadata. Json │ └ ─ ─ spring. Factories └ ─ ─ the test ├ ─ ─ Java test codeCopy the code

In the project structure, you can see the corresponding source code for each part. In service governance, the first is the service information ServiceInstance, which includes

  • The service name is ServiceId. This is our similar xxx-server (spring.application.name).
  • InstanceId Unique identifier of the service instance
  • host
  • port
  • Some extended information metadata, which is mainly used to add the following extended information to a three-party implementation
// Some comments have been deleted to shorten the length
public interface ServiceInstance {

	default String getInstanceId(a) {
		return null;
	}

	String getServiceId(a);

	String getHost(a);

	int getPort(a);

	boolean isSecure(a);

	URI getUri(a);

	Map<String, String> getMetadata(a);

	default String getScheme(a) {
		return null; }}Copy the code

The service registry

Registration is a Registration implementation provided by Spring Cloud

public interface Registration extends ServiceInstance {
	// There is no real code in this
}
Copy the code

The actual interface for service registration is ServiceRegistry

public interface ServiceRegistry<R extends Registration> {

	/**
	 * Registers the registration. A registration typically has information about an
	 * instance, such as its hostname and port.
	 * @param registration registration meta data
	 */
	void register(R registration);

	/**
	 * Deregisters the registration.
	 * @param registration registration meta data
	 */
	void deregister(R registration);

	/** * Closes the ServiceRegistry. This is a lifecycle method. */
	void close(a);

	/**
	 * Sets the status of the registration. The status values are determined by the
	 * individual implementations.
	 * @param registration The registration to update.
	 * @param status The status to set.
	 * @see org.springframework.cloud.client.serviceregistry.endpoint.ServiceRegistryEndpoint
	 */
	void setStatus(R registration, String status);

	/**
	 * Gets the status of a particular registration.
	 * @param registration The registration to query.
	 * @param <T> The type of the status.
	 * @return The status of the registration.
	 * @see org.springframework.cloud.client.serviceregistry.endpoint.ServiceRegistryEndpoint
	 */
	<T> T getStatus(R registration);

}
Copy the code

You can implement a simple ServiceRegistry function by implementing ServiceRegistry

Service discovery

DiscoveryClient and ReactiveDiscoveryClient exist in Discovery

It provides the following functions:

  1. Get all the service names
  2. Obtain a list of service instances based on the service name
public interface DiscoveryClient extends Ordered {

	/** * Default order of the discovery client. */
	int DEFAULT_ORDER = 0;

	/**
	 * A human-readable description of the implementation, used in HealthIndicator.
	 * @return The description.
	 */
	String description(a);

	/**
	 * Gets all ServiceInstances associated with a particular serviceId.
	 * @param serviceId The serviceId to query.
	 * @return A List of ServiceInstance.
	 */
	List<ServiceInstance> getInstances(String serviceId);

	/ * * *@return All known service IDs.
	 */
	List<String> getServices(a);

	/**
	 * Default implementation for getting order of discovery clients.
	 * @return order
	 */
	@Override
	default int getOrder(a) {
		returnDEFAULT_ORDER; }}Copy the code

You can discover services by implementing DiscoveryClient

Health detection

ReactiveDiscoveryClientHealthIndicator provides health detection

  1. Obtain all service names from DiscoveryClient
  2. Obtain the corresponding service instance list based on the service name list
  3. Health checks are performed on each instance and UP if the response is successful otherwise DOWN
public class ReactiveDiscoveryClientHealthIndicator
		implements ReactiveDiscoveryHealthIndicator.Ordered.ApplicationListener<InstanceRegisteredEvent<? >>{

	private final ReactiveDiscoveryClient discoveryClient;

	private final DiscoveryClientHealthIndicatorProperties properties;

	private final Log log = LogFactory.getLog(ReactiveDiscoveryClientHealthIndicator.class);

	private AtomicBoolean discoveryInitialized = new AtomicBoolean(false);

	private int order = Ordered.HIGHEST_PRECEDENCE;

	public ReactiveDiscoveryClientHealthIndicator(ReactiveDiscoveryClient discoveryClient, DiscoveryClientHealthIndicatorProperties properties) {
		this.discoveryClient = discoveryClient;
		this.properties = properties;
	}

	@Override
	public void onApplicationEvent(InstanceRegisteredEvent
        event) {
		if (this.discoveryInitialized.compareAndSet(false.true)) {
			this.log.debug("Discovery Client has been initialized"); }}@Override
	public Mono<Health> health(a) {
		if (this.discoveryInitialized.get()) {
			return doHealthCheck();
		}
		else {
			return Mono.just(
					Health.status(new Status(Status.UNKNOWN.getCode(), "Discovery Client not initialized")).build()); }}private Mono<Health> doHealthCheck(a) {
		// @formatter:off
		return Mono.justOrEmpty(this.discoveryClient)
				.flatMapMany(ReactiveDiscoveryClient::getServices)
				.collectList()
				.defaultIfEmpty(emptyList())
				.map(services -> {
					ReactiveDiscoveryClient client = this.discoveryClient;
					String description = (this.properties.isIncludeDescription())
							? client.description() : "";
					return Health.status(new Status("UP", description))
							.withDetail("services", services).build();
				})
				.onErrorResume(exception -> {
					this.log.error("Error", exception);
					return Mono.just(Health.down().withException(exception).build());
				});
		// @formatter:on
	}

	@Override
	public String getName(a) {
		return discoveryClient.description();
	}

	@Override
	public int getOrder(a) {
		return this.order;
	}

	public void setOrder(int order) {
		this.order = order; }}Copy the code

Through the above interface definition and built-in health check logic can be seen to do a service governance needs to implement the simplest logic

  1. Implement service stry
  2. The DiscoveryClient function is enabled

Spring Cloud Consul implementation

implementationServiceRegistryfunction

In Spring Cloud Consul, the Registration implementation is customized first

NewService indicates some service instance information defined by Consul

public class ConsulRegistration implements Registration {

	private final NewService service;

	private ConsulDiscoveryProperties properties;

	public ConsulRegistration(NewService service, ConsulDiscoveryProperties properties) {
		this.service = service;
		this.properties = properties;
	}

	public NewService getService(a) {
		return this.service;
	}

	protected ConsulDiscoveryProperties getProperties(a) {
		return this.properties;
	}

	public String getInstanceId(a) {
		return getService().getId();
	}

	public String getServiceId(a) {
		return getService().getName();
	}

	@Override
	public String getHost(a) {
		return getService().getAddress();
	}

	@Override
	public int getPort(a) {
		return getService().getPort();
	}

	@Override
	public boolean isSecure(a) {
		return this.properties.getScheme().equalsIgnoreCase("https");
	}

	@Override
	public URI getUri(a) {
		return DefaultServiceInstance.getUri(this);
	}

	@Override
	public Map<String, String> getMetadata(a) {
		returngetService().getMeta(); }}Copy the code

NewService

Consul contains basic information about the service and Consul provides some unique functions such as Tags and Check

// Remove generic getters, setters, toString methods
public class NewService {
  @SerializedName("ID")
  private String id;
  @SerializedName("Name")
  private String name;
  @SerializedName("Tags")
  private List<String> tags;
  @SerializedName("Address")
  private String address;
  @SerializedName("Meta")
  private Map<String, String> meta;
  @SerializedName("Port")
  private Integer port;
  @SerializedName("EnableTagOverride")
  private Boolean enableTagOverride;
  @SerializedName("Check")
  private NewService.Check check;
  @SerializedName("Checks")
  private List<NewService.Check> checks;

  public NewService(a) {}public static class Check {
    @SerializedName("Script")
    private String script;
    @SerializedName("DockerContainerID")
    private String dockerContainerID;
    @SerializedName("Shell")
    private String shell;
    @SerializedName("Interval")
    private String interval;
    @SerializedName("TTL")
    private String ttl;
    @SerializedName("HTTP")
    private String http;
    @SerializedName("Method")
    private String method;
    @SerializedName("Header")
    private Map<String, List<String>> header;
    @SerializedName("TCP")
    private String tcp;
    @SerializedName("Timeout")
    private String timeout;
    @SerializedName("DeregisterCriticalServiceAfter")
    private String deregisterCriticalServiceAfter;
    @SerializedName("TLSSkipVerify")
    private Boolean tlsSkipVerify;
    @SerializedName("Status")
    private String status;
    @SerializedName("GRPC")
    private String grpc;
    @SerializedName("GRPCUseTLS")
    private Boolean grpcUseTLS;

    public Check(a) {}}}Copy the code

ConsulServiceRegistry implementation ServiceRegistry

public class ConsulServiceRegistry implements ServiceRegistry<ConsulRegistration> {

	private static Log log = LogFactory.getLog(ConsulServiceRegistry.class);

	private final ConsulClient client;

	private final ConsulDiscoveryProperties properties;

	private final TtlScheduler ttlScheduler;

	private final HeartbeatProperties heartbeatProperties;

	public ConsulServiceRegistry(ConsulClient client, ConsulDiscoveryProperties properties, TtlScheduler ttlScheduler, HeartbeatProperties heartbeatProperties) {
		this.client = client;
		this.properties = properties;
		this.ttlScheduler = ttlScheduler;
		this.heartbeatProperties = heartbeatProperties;
	}

	@Override
	public void register(ConsulRegistration reg) {
		log.info("Registering service with consul: " + reg.getService());
		try {
			Consul is also used to register services through the API interface provided by Consul
			this.client.agentServiceRegister(reg.getService(), this.properties.getAclToken());
			NewService service = reg.getService();
			if (this.heartbeatProperties.isEnabled() && this.ttlScheduler ! =null&& service.getCheck() ! =null&& service.getCheck().getTtl() ! =null) {
				this.ttlScheduler.add(reg.getInstanceId()); }}catch (ConsulException e) {
			if (this.properties.isFailFast()) {
				log.error("Error registering service with consul: " + reg.getService(), e);
				ReflectionUtils.rethrowRuntimeException(e);
			}
			log.warn("Failfast is false. Error registering service with consul: "+ reg.getService(), e); }}@Override
	public void deregister(ConsulRegistration reg) {
		if (this.ttlScheduler ! =null) {
			this.ttlScheduler.remove(reg.getInstanceId());
		}
		if (log.isInfoEnabled()) {
			log.info("Deregistering service with consul: " + reg.getInstanceId());
		}
		this.client.agentServiceDeregister(reg.getInstanceId(), this.properties.getAclToken());
	}

	@Override
	public void close(a) {}@Override
	public void setStatus(ConsulRegistration registration, String status) {
		if (status.equalsIgnoreCase(OUT_OF_SERVICE.getCode())) {
			this.client.agentServiceSetMaintenance(registration.getInstanceId(), true);
		}
		else if (status.equalsIgnoreCase(UP.getCode())) {
			this.client.agentServiceSetMaintenance(registration.getInstanceId(), false);
		}
		else {
			throw new IllegalArgumentException("Unknown status: "+ status); }}// Service instance status
	@Override
	public Object getStatus(ConsulRegistration registration) {
		String serviceId = registration.getServiceId();
		Response<List<Check>> response = this.client.getHealthChecksForService(serviceId,
				HealthChecksForServiceRequest.newBuilder().setQueryParams(QueryParams.DEFAULT).build());
		List<Check> checks = response.getValue();

		for (Check check : checks) {
			if (check.getServiceId().equals(registration.getInstanceId())) {
				if (check.getName().equalsIgnoreCase("Service Maintenance Mode")) {
					returnOUT_OF_SERVICE.getCode(); }}}returnUP.getCode(); }}Copy the code

ConsulDiscoveryClient implementation DiscoveryClient

The discovery logic is also queried through the API interface provided by Consul

public class ConsulDiscoveryClient implements DiscoveryClient {

	private final ConsulClient client;

	private final ConsulDiscoveryProperties properties;

	public ConsulDiscoveryClient(ConsulClient client, ConsulDiscoveryProperties properties) {
		this.client = client;
		this.properties = properties;
	}

	@Override
	public String description(a) {
		return "Spring Cloud Consul Discovery Client";
	}

	@Override
	public List<ServiceInstance> getInstances(final String serviceId) {
		return getInstances(serviceId, new QueryParams(this.properties.getConsistencyMode()));
	}

	public List<ServiceInstance> getInstances(final String serviceId, final QueryParams queryParams) {
		List<ServiceInstance> instances = new ArrayList<>();

		addInstancesToList(instances, serviceId, queryParams);

		return instances;
	}

	private void addInstancesToList(List<ServiceInstance> instances, String serviceId, QueryParams queryParams) {
		HealthServicesRequest.Builder requestBuilder = HealthServicesRequest.newBuilder()
				.setPassing(this.properties.isQueryPassing()).setQueryParams(queryParams)
				.setToken(this.properties.getAclToken());
		String queryTag = this.properties.getQueryTagForService(serviceId);
		if(queryTag ! =null) {
			requestBuilder.setTag(queryTag);
		}
		HealthServicesRequest request = requestBuilder.build();
		Response<List<HealthService>> services = this.client.getHealthServices(serviceId, request);

		for (HealthService service : services.getValue()) {
			instances.add(newConsulServiceInstance(service, serviceId)); }}public List<ServiceInstance> getAllInstances(a) {
		List<ServiceInstance> instances = new ArrayList<>();

		Response<Map<String, List<String>>> services = this.client
				.getCatalogServices(CatalogServicesRequest.newBuilder().setQueryParams(QueryParams.DEFAULT).build());
		for (String serviceId : services.getValue().keySet()) {
			addInstancesToList(instances, serviceId, QueryParams.DEFAULT);
		}
		return instances;
	}

	@Override
	public List<String> getServices(a) {
		CatalogServicesRequest request = CatalogServicesRequest.newBuilder().setQueryParams(QueryParams.DEFAULT)
				.setToken(this.properties.getAclToken()).build();
		return new ArrayList<>(this.client.getCatalogServices(request).getValue().keySet());
	}

	@Override
	public int getOrder(a) {
		return this.properties.getOrder(); }}Copy the code

conclusion

A brief overview of Spring Cloud Consul’s service governance logic goes something like this, but of course Spring Cloud Consul has a lot of detail to deal with and a lot of code

Consul does not provide the function of forwarding service requests in the Spring Cloud system, but only provides the function of saving, querying, and eliminating health detection of service information

reference

  1. Consul official introduction www.consul.io/docs/intro
  2. Spring Cloud Consul github.com/spring-clou…
  3. Spring Cloud Commons github.com/spring-clou…