Tiven Wang
Wang Tiven August 10, 2017
425 favorite favorites
bookmark bookmark
share share

Concept

Assume we have three services, Hero, Villain and Police office

* Villain       -> Police office "I'm here!"
* Police office -> Hero          "Move!"
* Hero          -> Villain       "Catch you!"
* Hero          -> Police office "Give him to you!"

Service Discovery

Clients of a service use either Client-side discovery or Server-side discovery to determine the location of a service instance to which to send requests.

Why Use Service Discovery? Please refer to the section in Service Discovery in a Microservices Architecture

Service Discovery Products

Spring Cloud Netflix

Spring Cloud Zookeeper

Consul vs. Other Software

Building

Parent POM

首先配置 parent maven 配置文件:

<parent>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-parent</artifactId>
  <version>Dalston.SR2</version>
  <relativePath/>
</parent>

<groupId>wang.tiven.microservices</groupId>
<artifactId>service-registration-and-discovery</artifactId>
<version>0.0.1-SNAPSHOT</version>

<packaging>pom</packaging>

<modules>
  <module>villain-service</module>
  <module>hero-service</module>
  <module>police-service</module>
  <module>eureka-service</module>
</modules>
  • 设置本项目的 parent 为 spring-cloud-starter-parent 版本为本文编写时最新稳定版 Dalston.SR2
  • 并包含几个子模块 eureka-service 提供本地测试用的 service registry 服务
  • 其他三个子模块分别对应我们的 Hero Villain 和 Police

Eureka Service

Eureka is a REST (Representational State Transfer) based service that is primarily used in the AWS cloud for locating services for the purpose of load balancing and failover of middle-tier servers.

At Netflix, Eureka is used for the following purposes apart from playing a critical part in mid-tier load balancing.

Spring Cloud Netflix provides Netflix OSS integrations for Spring Boot apps through autoconfiguration and binding to the Spring Environment and other Spring programming model idioms.

<parent>
  <groupId>wang.tiven.microservices</groupId>
  <artifactId>service-registration-and-discovery</artifactId>
  <version>0.0.1-SNAPSHOT</version>
</parent>

<name>service-registration-and-discovery:eureka-service</name>
<artifactId>eureka-service</artifactId>
<packaging>jar</packaging>
<properties>
  <start-class>wang.tiven.microservices.registry.Application</start-class>
</properties>

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

To run your own server use the spring-cloud-starter-eureka-server dependency and @EnableEurekaServer.

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@SpringBootApplication
@EnableEurekaServer
public class Application {

  public static void main(String[] args) {
    SpringApplication.run(Application.class, args);
  }
}

Config the attribute server port default as 8761 in

server:
  port: 8761

eureka:
  client:
    registerWithEureka: false
    fetchRegistry: false
  server:
    waitTimeInMsWhenSyncEmpty: 0

You can run the eureka server through command: mvn spring-boot:run

Once the server startup, you can access the eureka application cockpit in local through url http://127.0.0.1:8761/

Villain Service

Big villain here. The project most attributes are similar to eureka service’s, so we only introduce the dependencies for this application:

<dependencies>
  <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-eureka</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-mongodb</artifactId>
  </dependency>
</dependencies>
  • spring-cloud-starter-eureka enable this spring boot application to be discovery-aware
  • in order to create a RESTful api we have to include the spring-boot-starter-web package
  • the application need to store the avatar for villain in database, so we include spring-boot-starter-data-mongodb package

The application configuration file:

server:
  port: 8060

eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/
  • The web application port default as 8060
  • The server url for local eureka service is http://localhost:8761/eureka/

The application’s Eureka instance name (the name by which it will be registered in Eureka) will be derived from the value of the spring.application.name property on the application.

@RequestMapping(method = {RequestMethod.POST, RequestMethod.PUT})
ResponseEntity set(String villainId,
                      @RequestParam MultipartFile multipartFile,
                      UriComponentsBuilder uriBuilder) throws IOException {

    InputStream inputStream = multipartFile.getInputStream();
    this.gridFsTemplate.store(inputStream, villainId);

    URI uri = uriBuilder.path("/{villainId}").buildAndExpand(villainId).toUri();
    HttpHeaders headers = new HttpHeaders();
    headers.setLocation(uri);
    return new ResponseEntity(headers, HttpStatus.CREATED);
}
  • The set method receive a villain id and avatar, then store it in mongodb
  • The get method return a villain for who query him
  • @EnableEurekaClient tell the application to registry in eureka service

First startup a mongodb server in local, for example using docker: docker run --rm --name mongoserver -d -p 27017:27017 mongo

Startup the application and check the cockpit of eureka, you will find the registered application in section Instances currently registered with Eureka

mvn spring-boot:run

Police Service

Add the H2 database in this application to provide a datasource to store police offices and villains information.

H2 is a relational database management system written in Java. It can be embedded in Java applications or run in the client-server mode.

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
  <groupId>com.h2database</groupId>
  <artifactId>h2</artifactId>
</dependency>

The police service provide a Restful api to enable villain report himself to the police office:

@RequestMapping(value = "/villains", method = RequestMethod.POST)
@ResponseBody Villain createVillain(@PathVariable String officeName, @RequestBody Villain villain) {

  System.out.println(officeName + ", I'm here, '" + villain.getName() + "'");

  PoliceOffice policeOffice = policeOfficeRepository.findByOfficeName(officeName);

  villain.setPoliceOffice(policeOffice);

  return this.villainRepository.save(villain);
}

Basic Service Discovery

Now we add logic in villain service to report a villain himself to police office.

Use the RestTemplate to call Restful api, and add the @LoadBalanced on it, this tells Spring Cloud that we want to take advantage of its load balancing support.

@SpringBootApplication
@EnableEurekaClient
public class Application {

    ...

    @LoadBalanced
    @Bean
    RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

Then we can auto wire the RestTemplate instance into controller, and call the police office restful api when creating villain. The url of police write as http://<service-name>/<api-path>, e.g. http://police-service/Gotham-City/villains

...

@Autowired
private RestTemplate restTemplate;

@RequestMapping(method = {RequestMethod.POST, RequestMethod.PUT})
ResponseEntity<?> set(@PathVariable String villainId,
                      @RequestParam MultipartFile multipartFile,
                      UriComponentsBuilder uriBuilder) throws IOException {

  ...

  this.restTemplate.exchange(
          "http://police-service/Gotham-City/villains",
          HttpMethod.POST,
          new HttpEntity<Villain>(new Villain(villainId)),
          new ParameterizedTypeReference<Villain>() {
          });

  ...

  return new ResponseEntity<>(headers, HttpStatus.CREATED);
}

Test Basic Service Discovery

Now restart villain service and startup police service,

Send a post request to url http://localhost:8060/xman, then you will get the information Gotham-City, I’m here, ‘xman’ in the console of police server.

You can get the complete application sourcecode for this step on Github

Hero Service

In this hero service, we change the approach of calling restful api to using feign client. Add the spring cloud feign package:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-feign</artifactId>
</dependency>

Enable the application’s feign client ability by @EnableFeignClients

@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
public class Application {
  ...
}

Create a client for external service restful api:

@FeignClient("police-service")
interface PoliceOfficeClient {

    @RequestMapping(method = RequestMethod.POST, value = "/Gotham-City/villains/{villainName}")
    void catched(@PathVariable("villainName") String villainName);
}

Then use it in business logic in the controller class:

@Autowired
PoliceOfficeClient policeOfficeClient;

@RequestMapping(method = RequestMethod.POST)
@ResponseBody void move(@PathVariable String heroName, @RequestBody Villain villain) {

  System.out.println("I " + heroName + " move!");

  Resource resource = villainClient.get(villain.getName());

  System.out.println(villain.getName() + ", catch you!");

  policeOfficeClient.catched(villain.getName());
}

Test Feign Clients

Startup all of the services, Hero Villain and Police services, the send a request to url:

http://localhost:8060/xman

you will get messages

Villain service:

I xman am here!

Police service:

This is Gotham-City, you xman just wait!
Have catched xman

Hero service:

I Batman move!
xman, catch you!

You can get the complete application sourcecode for this step on Github

Dockerization

Probably you have learnt docker in your past project experiences, now we apply docker in our these services:

Before run docker runtime container, you need package the applications by mvn package

1 Startup the eureka server docker run --rm --name eureka-service -h eureka-service -p 8761:8761 -v \<project-path\>/eureka-service:/data -w /data openjdk java -jar ./target/eureka-service.jar

2 Startup a mongodb server docker run --rm --name mongoserver -d -p 27017:27017 mongo

Because applications run own OS container separately, so thy can’t use localhost to access another service. Change the service url for eureka server in all the applications and add mongodb server host address:

eureka:
  client:
    serviceUrl:
      defaultZone: http://eureka-service:8761/eureka/

---
spring:
  data:
    mongodb:
      host: mongoserver
      port: 27017

3 docker run --rm --name villain-service -h villain-service -p 8060:8060 --link eureka-service --link mongoserver -v \<project-path\>/villain-service:/data -w /data openjdk java -jar ./target/villain-service.jar

Add a docker --link as eureka-service in the command to enable this container be able to access the eureka service container. And the mongoserver be too.

4 docker run --rm --name police-service -h police-service -p 9098:9098 --link eureka-service --link hero-service -v \<project-path\>/police-service:/data -w /data openjdk java -jar ./target/police-service.jar

5 docker run --rm --name hero-service -h hero-service -p 8050:8050 --link eureka-service --link police-service --link villain-service -v \<project-path\>/hero-service:/data -w /data openjdk java -jar ./target/hero-service.jar

You can’t startup the applications, because the container link dependencies.

So next

Docker Network

What is Docker networking

Docker container networking

Create a network for our microservices:

docker network create microservices-net

And add the network into all the Docker containers:

docker run --rm --name eureka-service -h eureka-service --network microservices-net -p 8761:8761 -v \<project-path\>/eureka-service:/data -w /data openjdk java -jar ./target/eureka-service.jar

docker run --rm --name mongoserver -d --network microservices-net -p 27017:27017 mongo

docker run --rm --name villain-service -h villain-service --network microservices-net -p 8060:8060 -v \<project-path\>/villain-service:/data -w /data openjdk java -jar ./target/villain-service.jar

docker run --rm --name police-service -h police-service --network microservices-net -p 9098:9098 -v \<project-path\>/police-service:/data -w /data openjdk java -jar ./target/police-service.jar

docker run --rm --name hero-service -h hero-service --network microservices-net -p 8050:8050 -v \<project-path\>/hero-service:/data -w /data openjdk java -jar ./target/hero-service.jar

You can get the complete application sourcecode for this step on Github

Cloud Foundry

In production, how to deploy our services to CloudFoundry platform? Please refer to Try Cloud Foundry 10 - Service Discovery.

References

Similar Posts

  • Microservices - Inter-Process Communication The kernal of Microservices Architecture is inter-process communication
  • Microservices - API Gateway : Spring Cloud Gateway Spring Cloud Gateway 是一个新的基于 Spring Framework 5, Project Reactor and Spring Boot 2.0 的 API Gateway 产品
  • Microservices - API Gateway : Zuul Zuul 是来自 NetFlix 的 Microservice 产品家族的 API Gateway 服务或者说是 edge 服务。 Zuul 为开发者构建微服务架构提供了 Routing,Monitoring,Managing resiliency,Security等功能。简单来说,Zuul 可以被看作是一个反向代理,在服务实例间 Zuul 代理内部使用 Eureka server 作为 service discovery,使用Ribbon 作为 load balancing
  • Microservices - Transactions
  • Try Cloud Foundry - Logs Loggregator is the next generation system for aggregating and streaming logs and metrics from all of the user apps and system components in an Elastic Runtime deployment. Loggregator uses Google's protocol buffers along with gRPC to deliver logs.
  • Try Cloud Foundry - Config Server Config Server for Pivotal Cloud Foundry (PCF) is an externalized application configuration service, which gives you a central place to manage an application’s external properties across all environments.

Comments

Back to Top