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

  • Try Cloud Foundry 13 - 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 12 - 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.
  • Microservices - API Gateway Implement an API gateway that is the single entry point for all clients. The API gateway handles requests in one of two ways. Some requests are simply proxied/routed to the appropriate service. It handles other requests by fanning out to multiple services.
  • Microservices - Circuit Breaker 访问远程服务时, 比依赖超时时间更好一些的方式是一种叫断路器(Circuit Breaker)的模式. Circuit Breaker 就像一位交通警察, 在前方道路畅通的情况下, 他会放行; 当前方道路由于各种原因拥堵时, 他会告诉你前方道路不通请回; 如果他是个更智能的交警的话, 还会告诉你前方道路部分拥堵, 只允许部分车辆通过, 比如实行单双号.
  • Redis Redis
  • Microservices - Architecture Microservice architecture (MSA) is an approach to building software systems that decomposes business domain models into smaller, consistent, bounded-contexts implemented by services. These services are isolated and autonomous yet communicate to provide some piece of business functionality. Microservices are typically implemented and operated by small teams with enough autonomy that each team and service can change its internal implementation details (including replacing it outright!) with minimal impact across the rest of the system.

Comments

comments powered by Disqus
Back to Top