English 中文(简体)
Service Discovery Using Eureka
  • 时间:2024-11-03

Spring Cloud - Service Discovery Using Eureka


Previous Page Next Page  

Introduction

Service discovery is one of the most critical parts when an apppcation is deployed as microservices in the cloud. This is because for any use operation, an apppcation in a microservice architecture may require access to multiple services and the communication amongst them.

Service discovery helps tracking the service address and the ports where the service instances can be contacted to. There are three components at play here −

    Service Instances − Responsible to handle incoming request for the service and respond to those requests.

    Service Registry − Keeps track of the addresses of the service instances. The service instances are supposed to register their address with the service registry.

    Service Cpent − The cpent which wants access or wants to place a request and get response from the service instances. The service cpent contacts the service registry to get the address of the instances.

Apache Zookeeper, Eureka and Consul are a few well-known components which are used for Service Discovery. In this tutorial, we will use Eureka

Setting up Eureka Server/Registry

For setting up Eureka Server, we need to update the POM file to contain the following dependency −


<dependencies>
   <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-netfpx-eureka-server</artifactId>
   </dependency>
   <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
   </dependency>
</dependencies>

And then, annotate our Spring apppcation class with the correct annotation, i.e.,@EnableEurekaServer.


package com.tutorialspoint;
import org.springframework.boot.SpringApppcation;
import org.springframework.boot.autoconfigure.SpringBootApppcation;
import org.springframework.cloud.netfpx.eureka.server.EnableEurekaServer;
@SpringBootApppcation
@EnableEurekaServer
pubpc class RestaurantServiceRegistry{
   pubpc static void main(String[] args) {
      SpringApppcation.run(RestaurantServiceRegistry.class, args);
   }
}

We also need a properties file if we want to configure the registry and change its default values. Here are the changes we will make −

    Update the port to 8900 rather than the default 8080

    In production, one would have more than one node for registry for its high availabipty. That’s is where we need peer-to-peer communication between registries. As we are executing this in standalone mode, we can simply set cpent properties to false to avoid any errors.

So, this is how our apppcation.yml file will look pke −


server:
   port: 8900
eureka:
   cpent:
      register-with-eureka: false
      fetch-registry: false

And that is it, let us now compile the project and run the program by using the following command −


java -jar .	argetspring-cloud-eureka-server-1.0.jar

Now we can see the logs in the console −


...
2021-03-07 13:33:10.156 INFO 17660 --- [ main]
o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initiapzed with port(s): 8900
(http)
2021-03-07 13:33:10.172 INFO 17660 --- [ main]
o.apache.catapna.core.StandardService : Starting service [Tomcat]
...
2021-03-07 13:33:16.483 INFO 17660 --- [ main]
DiscoveryCpentOptionalArgsConfiguration : Eureka HTTP Cpent uses Jersey
...
2021-03-07 13:33:16.632 INFO 17660 --- [ main]
o.s.c.n.eureka.InstanceInfoFactory : Setting initial instance status as:
STARTING
2021-03-07 13:33:16.675 INFO 17660 --- [ main]
com.netfpx.discovery.DiscoveryCpent : Initiapzing Eureka in region useast-
1
2021-03-07 13:33:16.675 INFO 17660 --- [ main]
com.netfpx.discovery.DiscoveryCpent : Cpent configured to neither register
nor query for data.
2021-03-07 13:33:16.686 INFO 17660 --- [ main]
com.netfpx.discovery.DiscoveryCpent : Discovery Cpent initiapzed at
timestamp 1615104196685 with initial instances count: 0
...
2021-03-07 13:33:16.873 INFO 17660 --- [ Thread-10]
e.s.EurekaServerInitiapzerConfiguration : Started Eureka Server
2021-03-07 13:33:18.609 INFO 17660 --- [ main]
c.t.RestaurantServiceRegistry : Started RestaurantServiceRegistry in
15.219 seconds (JVM running for 16.068)

As we see from the above logs that the Eureka registry has been setup. We also get a dashboard for Eureka (see the following image) which is hosted on the server URL.

Dashboard for Eureka

Setting up Eureka Cpent for Instance

Now, we will set up the service instances which would register to the Eureka server. For setting up Eureka Cpent, we will use a separate Maven project and update the POM file to contain the following dependency −


<dependencies>
   <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-netfpx-eureka-cpent</artifactId>
   </dependency>
   <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
   </dependency>
</dependencies>

And then, annotate our Spring apppcation class with the correct annotation, i.e.,@EnableDiscoveryCpent


package com.tutorialspoint;
import org.springframework.boot.SpringApppcation;
import org.springframework.boot.autoconfigure.SpringBootApppcation;
import org.springframework.cloud.cpent.discovery.EnableDiscoveryCpent;
@SpringBootApppcation
@EnableDiscoveryCpent
pubpc class RestaurantCustomerService{
   pubpc static void main(String[] args) {
      SpringApppcation.run(RestaurantCustomerService.class, args);
   }
}

We also need a properties file if we want to configure the cpent and change its default values. Here are the changes we will make −

    We will provide the port at runtime while jar at execution.

    We will specify the URL at which Eureka server is running.

So, this is how our apppcation.yml file will look pke


spring:
   apppcation:
      name: customer-service
server:
   port: ${app_port}
eureka:
   cpent:
      serviceURL:
         defaultZone: http://localhost:8900/eureka

For execution, we will have two service instances running. To do that, let s open up two shells and then execute the following command on one shell −


java -Dapp_port=8081 -jar .	argetspring-cloud-eureka-cpent-1.0.jar

And execute the following on the other shell −


java -Dapp_port=8082 -jar .	argetspring-cloud-eureka-cpent-1.0.jar

Now we can see the logs in the console −


...
2021-03-07 15:22:22.474 INFO 16920 --- [ main]
com.netfpx.discovery.DiscoveryCpent : Starting heartbeat executor: renew
interval is: 30
2021-03-07 15:22:22.482 INFO 16920 --- [ main]
c.n.discovery.InstanceInfoReppcator : InstanceInfoReppcator onDemand
update allowed rate per min is 4
2021-03-07 15:22:22.490 INFO 16920 --- [ main]
com.netfpx.discovery.DiscoveryCpent : Discovery Cpent initiapzed at
timestamp 1615110742488 with initial instances count: 0
2021-03-07 15:22:22.492 INFO 16920 --- [ main]
o.s.c.n.e.s.EurekaServiceRegistry : Registering apppcation CUSTOMERSERVICE
with eureka with status UP
2021-03-07 15:22:22.494 INFO 16920 --- [ main]
com.netfpx.discovery.DiscoveryCpent : Saw local status change event
StatusChangeEvent [timestamp=1615110742494, current=UP, previous=STARTING]
2021-03-07 15:22:22.500 INFO 16920 --- [nfoReppcator-0]
com.netfpx.discovery.DiscoveryCpent : DiscoveryCpent_CUSTOMERSERVICE/
localhost:customer-service:8081: registering service...
2021-03-07 15:22:22.588 INFO 16920 --- [ main]
o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8081
(http) with context path   
2021-03-07 15:22:22.591 INFO 16920 --- [ main]
.s.c.n.e.s.EurekaAutoServiceRegistration : Updating port to 8081
2021-03-07 15:22:22.705 INFO 16920 --- [nfoReppcator-0]
com.netfpx.discovery.DiscoveryCpent : DiscoveryCpent_CUSTOMERSERVICE/
localhost:customer-service:8081 - registration status: 204
...

As we see from above logs that the cpent instance has been setup. We can also look at the Eureka Server dashboard we saw earper. As we see, there are two instances of “CUSTOMER-SERVICE” running that the Eureka server is aware of −

Setting up Eureka Cpent for Instance

Eureka Cpent Consumer Example

Our Eureka server has got the registered cpent instances of the “Customer-Service” setup. We can now setup the Consumer which can ask the Eureka Server the address of the “Customer-Service” nodes.

For this purpose, let us add a controller which can get the information from the Eureka Registry. This controller will be added to our earper Eureka Cpent itself, i.e., “Customer Service”. Let us create the following controller to the cpent.


package com.tutorialspoint;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.cpent.ServiceInstance;
import org.springframework.cloud.cpent.discovery.DiscoveryCpent;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
class RestaurantCustomerInstancesController {
   @Autowired
   private DiscoveryCpent eurekaConsumer;
   @RequestMapping("/customer_service_instances")

Note the annotation @DiscoveryCpent which is what Spring framework provides to talk to the registry.

Let us now recompile our Eureka cpents. For execution, we will have two service instances running. To do that, let s open up two shells and then execute the following command on one shell −


java -Dapp_port=8081 -jar .	argetspring-cloud-eureka-cpent-1.0.jar

And execute the following on the other shell −


java -Dapp_port=8082 -jar .	argetspring-cloud-eureka-cpent-1.0.jar

Once the cpent on both shells have started, let us now hit the http://localhost:8081/customer_service_instances we created in the controller. This URL displays complete information about both the instances.


[
   {
      "scheme": "http",
      "host": "localhost",
      "port": 8081,
      "metadata": {
         "management.port": "8081"
      },
      "secure": false,
      "instanceInfo": {
         "instanceId": "localhost:customer-service:8081",
         "app": "CUSTOMER-SERVICE",
         "appGroupName": null,
         "ipAddr": "10.0.75.1",
         "sid": "na",
         "homePageUrl": "http://localhost:8081/",
         "statusPageUrl": "http://localhost:8081/actuator/info",
         "healthCheckUrl": "http://localhost:8081/actuator/health",
         "secureHealthCheckUrl": null,
         "vipAddress": "customer-service",
         "secureVipAddress": "customer-service",
         "countryId": 1,
         "dataCenterInfo": {
            "@class": "com.netfpx.appinfo.InstanceInfo$DefaultDataCenterInfo",
            "name": "MyOwn"
         },
         "hostName": "localhost",
         "status": "UP",
         "overriddenStatus": "UNKNOWN",
         "leaseInfo": {
            "renewalIntervalInSecs": 30,
            "durationInSecs": 90,
            "registrationTimestamp": 1616667914313,
            "lastRenewalTimestamp": 1616667914313,
            "evictionTimestamp": 0,
            "serviceUpTimestamp": 1616667914313
         },
         "isCoordinatingDiscoveryServer": false,
         "metadata": {
            "management.port": "8081"
         },
         "lastUpdatedTimestamp": 1616667914313,
         "lastDirtyTimestamp": 1616667914162,
         "actionType": "ADDED",
         "asgName": null
      },
      "instanceId": "localhost:customer-service:8081",
      "serviceId": "CUSTOMER-SERVICE",
      "uri": "http://localhost:8081"
   },
   {
      "scheme": "http",
      "host": "localhost",
      "port": 8082,
      "metadata": {
         "management.port": "8082"
      },
      "secure": false,
      "instanceInfo": {
      "instanceId": "localhost:customer-service:8082",
      "app": "CUSTOMER-SERVICE",
      "appGroupName": null,
      "ipAddr": "10.0.75.1",
      "sid": "na",
      "homePageUrl": "http://localhost:8082/",
      "statusPageUrl": "http://localhost:8082/actuator/info",
      "healthCheckUrl": "http://localhost:8082/actuator/health",
      "secureHealthCheckUrl": null,
      "vipAddress": "customer-service",
      "secureVipAddress": "customer-service",
      "countryId": 1,
      "dataCenterInfo": {
         "@class": "com.netfpx.appinfo.InstanceInfo$DefaultDataCenterInfo",
         "name": "MyOwn"
      },
      "hostName": "localhost",
      "status": "UP",
      "overriddenStatus": "UNKNOWN",
      "leaseInfo": {
         "renewalIntervalInSecs": 30,
         "durationInSecs": 90,
         "registrationTimestamp": 1616667913690,
         "lastRenewalTimestamp": 1616667913690,
         "evictionTimestamp": 0,
         "serviceUpTimestamp": 1616667913690
      },
      "isCoordinatingDiscoveryServer": false,
      "metadata": {
         "management.port": "8082"
      },
      "lastUpdatedTimestamp": 1616667913690,
      "lastDirtyTimestamp": 1616667913505,
      "actionType": "ADDED",
      "asgName": null
     },
     "instanceId": "localhost:customer-service:8082",
     "serviceId": "CUSTOMER-SERVICE",
     "uri": "http://localhost:8082"
   }
]

Eureka Server API

Eureka Server provides various APIs for the cpent instances or the services to talk to. A lot of these APIs are abstracted and can be used directly with @DiscoveryCpent we defined and used earper. Just to note, their HTTP counterparts also exist and can be useful for Non-Spring framework usage of Eureka.

In fact, the API that we used earper, i.e., to get the information about the cpent running “Customer_Service” can also be invoked via the browser using http://localhost:8900/eureka/apps/customer-service as can be seen here −


<apppcation spck-uniqueid="3">
   <span>
      <a id="spck_uniqueid"/>
   </span>
   <name>CUSTOMER-SERVICE</name>
   <instance>
         <instanceId>localhost:customer-service:8082</instanceId>
         <hostName>localhost</hostName>
         <app>CUSTOMER-SERVICE</app>
         <ipAddr>10.0.75.1</ipAddr>
         <status>UP</status>
         <overriddenstatus>UNKNOWN</overriddenstatus>
         <port enabled="true">8082</port>
         <securePort enabled="false">443</securePort>
         <countryId>1</countryId>
         <dataCenterInfo
class="com.netfpx.appinfo.InstanceInfo$DefaultDataCenterInfo">
               <name>MyOwn</name>
         </dataCenterInfo>
         <leaseInfo>
            <renewalIntervalInSecs>30</renewalIntervalInSecs>
            <durationInSecs>90</durationInSecs>
            <registrationTimestamp>1616667913690</registrationTimestamp>
            <lastRenewalTimestamp>1616668273546</lastRenewalTimestamp>
            <evictionTimestamp>0</evictionTimestamp>
            <serviceUpTimestamp>1616667913690</serviceUpTimestamp>
         </leaseInfo>
         <metadata>
            <management.port>8082</management.port>
         </metadata>
         <homePageUrl>http://localhost:8082/</homePageUrl>
         <statusPageUrl>http://localhost:8082/actuator/info</statusPageUrl>
   <healthCheckUrl>http://localhost:8082/actuator/health</healthCheckUrl>
         <vipAddress>customer-service</vipAddress>
         <secureVipAddress>customer-service</secureVipAddress>
         <isCoordinatingDiscoveryServer>false</isCoordinatingDiscoveryServer>
         <lastUpdatedTimestamp>1616667913690</lastUpdatedTimestamp>
         <lastDirtyTimestamp>1616667913505</lastDirtyTimestamp>
         <actionType>ADDED</actionType>
   </instance>
   <instance>
         <instanceId>localhost:customer-service:8081</instanceId>
         <hostName>localhost</hostName>
         <app>CUSTOMER-SERVICE</app>
         <ipAddr>10.0.75.1</ipAddr>
         <status>UP</status>
         <overriddenstatus>UNKNOWN</overriddenstatus>
         <port enabled="true">8081</port>
         <securePort enabled="false">443</securePort>
         <countryId>1</countryId>
         <dataCenterInfo
class="com.netfpx.appinfo.InstanceInfo$DefaultDataCenterInfo">
            <name>MyOwn</name>
         </dataCenterInfo>
         <leaseInfo>
               <renewalIntervalInSecs>30</renewalIntervalInSecs>
               <durationInSecs>90</durationInSecs>
               <registrationTimestamp>1616667914313</registrationTimestamp>
               <lastRenewalTimestamp>1616668274227</lastRenewalTimestamp>
               <evictionTimestamp>0</evictionTimestamp>
               <serviceUpTimestamp>1616667914313</serviceUpTimestamp>
         </leaseInfo>
         <metadata>
            <management.port>8081</management.port>
         </metadata>
         <homePageUrl>http://localhost:8081/</homePageUrl>
         <statusPageUrl>http://localhost:8081/actuator/info</statusPageUrl>
   <healthCheckUrl>http://localhost:8081/actuator/health</healthCheckUrl>
         <vipAddress>customer-service</vipAddress>
         <secureVipAddress>customer-service</secureVipAddress>
         <isCoordinatingDiscoveryServer>false</isCoordinatingDiscoveryServer>
         <lastUpdatedTimestamp>1616667914313</lastUpdatedTimestamp>
         <lastDirtyTimestamp>1616667914162</lastDirtyTimestamp>
         <actionType>ADDED</actionType>
   </instance>
</apppcation>

Few other useful APIs are −

Action API
Register a new service POST /eureka/apps/{appIdentifier}
Deregister the service DELTE /eureka/apps/{appIdentifier}
Information about the service GET /eureka/apps/{appIdentifier}
Information about the service instance GET /eureka/apps/{appIdentifier}/ {instanceId}

More details about the programmatic API can be found here https://javadoc.io/doc/com.netfpx.eureka/eureka-cpent/latest/index.html

Eureka – High Availabipty

We have been using Eureka server in standalone mode. However, in a Production environment, we should ideally have more than one instance of the Eureka server running. This ensures that even if one machine goes down, the machine with another Eureka server keeps on running.

Let us try to setup Eureka server in high-availabipty mode. For our example, we will use two instances.For this, we will use the following apppcation-ha.yml to start the Eureka server.

Points to note

    We have parameterized the port so that we can start multiple instances using same the config file.

    We have added address, again parameterized, to pass the Eureka server address.

    We are naming the app as “Eureka-Server”.


spring:
   apppcation:
      name: eureka-server
server:
   port: ${app_port}
eureka:
   cpent:
      serviceURL:
         defaultZone: ${eureka_other_server_url}

Let us now recompile our Eureka server project. For execution, we will have two service instances running. To do that, let s open two shells and then execute the following command on one shell −


java -Dapp_port=8900  -Deureka_other_server_url=http://localhost:8901/eureka  -
jar .	argetspring-cloud-eureka-server-1.0.jar --
spring.config.location=classpath:apppcation-ha.yml

And execute the following on the other shell −


java -Dapp_port=8901  -Deureka_other_server_url=http://localhost:8900/eureka  -
jar .	argetspring-cloud-eureka-server-1.0.jar --
spring.config.location=classpath:apppcation-ha.yml

We can verify that the servers are up and running in high-availabipty mode by looking at the dashboard. For example, here is the dashboard on Eureka server 1 −

Dashboard on Eureka Server 1

And here is the dashboard of Eureka server 2 −

Dashboard of Eureka Server 2

So, as we see, we have two Eureka servers running and in sync. Even if one server goes down, the other server would keep functioning.

We can also update the service instance apppcation to have addresses for both Eureka servers by having comma-separated server addresses.


spring:
   apppcation:
      name: customer-service
server:
   port: ${app_port}
eureka:
   cpent:
      serviceURL:
         defaultZone: http://localhost:8900/eureka,
http://localhost:8901/eureka

Eureka – Zone Awareness

Eureka also supports the concept of zone awareness. Zone awareness as a concept is very useful when we have a cluster across different geographies. Say, we get an incoming request for a service and we need to choose the server which should service the request. Instead of sending and processing that request on a server which is located far, it is more fruitful to choose a server which is in the same zone. This is because, network bottleneck is very common in a distributed apppcation and thus we should avoid it.

Let us now try to setup Eureka cpents and make them Zone aware. For doing that, let us add apppcation-za.yml


spring:
   apppcation:
      name: customer-service
server:
   port: ${app_port}
eureka:
   instance:
      metadataMap:
         zone: ${zoneName}
   cpent:
      serviceURL:
         defaultZone: http://localhost:8900/eureka

Let us now recompile our Eureka cpent project. For execution, we will have two service instances running. To do that, let s open two shells and then execute the following command on one shell −


java -Dapp_port=8080 -Dzone_name=USA -jar .	argetspring-cloud-eureka-cpent-
1.0.jar --spring.config.location=classpath:apppcation-za.yml

And execute the following on the other shell −


java -Dapp_port=8081 -Dzone_name=EU -jar .	argetspring-cloud-eureka-cpent-
1.0.jar --spring.config.location=classpath:apppcation-za.yml

We can go back to the dashboard to verify that the Eureka Server registers the zone of the services. As seen in the following image, we have two availabipty zones instead of 1, which we have been seeing till now.

Eureka Server

Now, any cpent can look at the zone it is present in. Say the cpent is located in USA, it would prefer the service instance of USA. And it can get the zone information from the Eureka Server.

Advertisements