SpringCloud micro-service actual combat

  docker, java

Order

There are eureka for service discovery, config for distributed configuration, zuul for api-gateway, feign for client load balancing, hystrix for circuit breaker, turbine for aggregate monitor and graphite for index monitoring.

eureka

  • Pom configuration
<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka-server</artifactId>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
  • application.yml
server:
  port: 8761

eureka:
  instance:
    hostname: discovery
  client:
    registerWithEureka: false
    fetchRegistry: false
    serviceUrl:
      defaultZone: http://discovery:${server.port}/eureka/

spring.cloud.config.discovery.enabled: true
  • bootstrap.yml
spring:
  application:
    name: discovery
  • application
@SpringBootApplication
@EnableEurekaServer
public class EurekaApplication {
    public static void main( String[] args ) {
        SpringApplication.run(EurekaApplication.class, args);
    }
}
  • Access

http://192.168.99.100:8761/

clipboard.png

config

  • Pom configuration
<dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-server</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>
    </dependencies>
  • application.yml
spring:
  cloud:
    config:
      server:
        native:
          search-locations: classpath:/config

server:
  port: 8888
  • bootstrap.yml
spring:
  application:
    name: config
  profiles:
    active: native
    
eureka:
  instance:
    preferIpAddress: true
  client:
    service-url:
      defaultZone: http://discovery:8761/eureka/
  • application
@SpringBootApplication
@EnableConfigServer
@EnableEurekaClient
public class ConfigApplication {
    public static void main( String[] args ) {
        SpringApplication.run(ConfigApplication.class,args);
    }
}
  • Access

http://192.168.99.100:8888/review/default/master

Feign instance

  • pom
<dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter</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-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>
        <dependency>
            <groupId>com.codecraft</groupId>
            <artifactId>common</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

        <!--调用其他微服务-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-ribbon</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-feign</artifactId>
        </dependency>

        <!--使用hystrix-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-hystrix</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
        </dependency>

    </dependencies>
  • application.yml
server:
  port: 9001

endpoints:
  restart:
    enabled: true
  shutdown:
    enabled: true
  health:
    sensitive: false

ribbon:
  eureka:
    enabled: true
  • bootstrap.yml
spring:
  application:
    name: product
  cloud:
    config:
      uri: http://config:8888
encrypt:
  failOnError: false

eureka:
  instance:
    preferIpAddress: true
  client:
    registerWithEureka: true
    fetchRegistry: true
    serviceUrl:
      defaultZone: http://discovery:8761/eureka/
  • FeignClient
@FeignClient("recommend")
public interface RemoteRecommendService {
    @RequestMapping(method = RequestMethod.GET,value = "/recommend")
    public List<Recommendation> getRecommendations(
            @RequestParam(value = "productId",  required = true) int productId);
}
  • Feign usage
@RestController
public class ProductController {

    private static final Logger LOG = LoggerFactory.getLogger(ProductController.class);

    @Autowired
    private SetProcTimeBean setProcTimeBean;

    @Autowired
    RemoteRecommendService remoteRecommendService;

    @RequestMapping("/product/recommends")
    @HystrixCommand(fallbackMethod = "callRecommendFallback", commandProperties = {
            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "100")
    })
    public List<Recommendation> remoteRecommends(@RequestParam(value = "productId",  required = true) int productId){
        return remoteRecommendService.getRecommendations(productId);
    }

    public List<Recommendation> callRecommendFallback(int productId) {
        return Collections.emptyList();
    }

    @RequestMapping("/product/{productId}")
    public Product getProduct(@PathVariable int productId) {

        int pt = setProcTimeBean.calculateProcessingTime();
        LOG.info("/product called, processing time: {}", pt);

        sleep(pt);

        LOG.debug("/product return the found product");
        return new Product(productId, "name", 123);
    }
}
  • application
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
@EnableCircuitBreaker
@EnableHystrix
@EnableHystrixDashboard
public class ProductApplication {
    private static final Logger LOG = LoggerFactory.getLogger(ProductApplication.class);

    public static void main(String[] args){
        SpringApplication.run(ProductApplication.class,args);
        LOG.info("Register ShutdownHook");
        Runtime.getRuntime().addShutdownHook(new Thread(){
            @Override
            public void run() {
                LOG.info("Shutting down product service, unregister from Eureka!");
                DiscoveryManager.getInstance().shutdownComponent();
            }
        });
    }
}

api-gateway

  • pom
<dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zuul</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-config</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-actuator</artifactId>
        </dependency>
    </dependencies>
  • application.yml
server:
  port: 10000

#sidecar:
  #port: 8000

endpoints:
  restart:
    enabled: true
  shutdown:
    enabled: true
  health:
    sensitive: false


zuul:
  ignored-services: "*"
  routes:
    product:
      path: /product/**
      url: http://product:9001/product
    recommend:
      path: /recommend/**
      url: http://recommend:9002/recommend
    review:
      path: /review/**
      url: http://review:9003/review
  • bootstrap.yml
spring:
  application:
    name: gateway
  cloud:
    config:
      uri: http://config:8888
encrypt:
  failOnError: false

eureka:
  instance:
    preferIpAddress: true
  client:
    registerWithEureka: true
    fetchRegistry: true
    serviceUrl:
      defaultZone: http://discovery:8761/eureka/
  • application
@SpringBootApplication
@EnableCircuitBreaker
@EnableDiscoveryClient
@EnableZuulProxy
public class ApiGatewayApplication {
    public static void main( String[] args ) {
        new SpringApplicationBuilder(ApiGatewayApplication.class).web(true).run(args);
    }
}
  • Access

http://192.168.99.100:10000/recommend? productId=1
Turn to
http://192.168.99.100:9002/recommend? productId=1

hystrix

http://192.168.99.100:9001/product/recommends? productId=1
http://192.168.99.100:9001/hystrix

clipboard.png

http://192.168.99.100:9003/hystrix/monitor? stream=http%3A%2F%2F192.168.99.100%3A9001%2Fhystrix.stream

clipboard.png

turbine

  • pom
<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-turbine</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>

        <!--使用hystrix-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-hystrix</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
        </dependency>

    </dependencies>
  • application.yml
server:
  port: 8889

eureka:
  instance:
    preferIpAddress: true
  client:
    registerWithEureka: true
    fetchRegistry: true
    serviceUrl:
      defaultZone: http://discovery:8761/eureka/

turbine:
  appConfig: product,review
  clusterNameExpression: new String("default")
  • bootstrap.yml
spring:
  application:
    name: turbine
  cloud:
    config:
      uri: http://config:8888
encrypt:
  failOnError: false
  • application
@SpringCloudApplication
@EnableTurbine
@EnableHystrixDashboard
public class TurbineApplication {
    public static void main(String[] args){
        SpringApplication.run(TurbineApplication.class,args);
    }
}
  • Access

http://192.168.99.100:9001/hystrix

clipboard.png

http://192.168.99.100:9001/product/recommends? productId=1
http://192.168.99.100:9003/review/product/100

clipboard.png

Graphite configuration

  • pom
<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- metrics -->
        <dependency>
            <groupId>io.dropwizard.metrics</groupId>
            <artifactId>metrics-core</artifactId>
            <version>${dropwizard-metrics.version}</version>
        </dependency>
        <dependency>
            <groupId>io.dropwizard.metrics</groupId>
            <artifactId>metrics-graphite</artifactId>
            <version>${dropwizard-metrics.version}</version>
        </dependency>
        <dependency>
            <groupId>io.dropwizard.metrics</groupId>
            <artifactId>metrics-annotation</artifactId>
            <version>${dropwizard-metrics.version}</version>
        </dependency>
        <dependency>
            <groupId>io.dropwizard.metrics</groupId>
            <artifactId>metrics-jvm</artifactId>
            <version>${dropwizard-metrics.version}</version>
        </dependency>

        <dependency>
            <groupId>com.ryantenney.metrics</groupId>
            <artifactId>metrics-spring</artifactId>
            <version>3.1.0</version>
            <exclusions>
                <exclusion>
                    <artifactId>spring-beans</artifactId>
                    <groupId>org.springframework</groupId>
                </exclusion>
                <exclusion>
                    <artifactId>spring-aop</artifactId>
                    <groupId>org.springframework</groupId>
                </exclusion>
            </exclusions>
        </dependency>

    </dependencies>
  • configuration
@Configuration
@AutoConfigureAfter(MetricRepositoryAutoConfiguration.class)
@ConditionalOnProperty(prefix = "graphite", name = "enabled", matchIfMissing = true)
@EnableConfigurationProperties(GraphiteProperties.class)
@EnableScheduling
@EnableMetrics
public class GraphiteAutoConfiguration {

    private static final Logger logger = LoggerFactory.getLogger(GraphiteAutoConfiguration.class);

    @Bean
    public MetricsConfigurerAdapter metricsConfigurerAdapter(final GraphiteProperties graphiteProperties) {
        return new GraphiteReportingManager(graphiteProperties);
    }

    /**
     * https://qbgbook.gitbooks.io/spring-boot-reference-guide-zh/content/IV.%20Spring%20Boot%20features/36.3.3.%20Property%20conditions.html
     * @param graphiteProperties
     * @param metricRegistry
     * @return
     */
    @Bean
    @ConditionalOnProperty(value = "graphite.host",matchIfMissing = true)
    public ConsoleReporter consoleReporter(GraphiteProperties graphiteProperties,MetricRegistry metricRegistry) {
        ConsoleReporter.Builder builder = ConsoleReporter.forRegistry(metricRegistry);
        ConsoleReporter reporter = builder.build();
        reporter.start(graphiteProperties.getReportInterval(), TimeUnit.MILLISECONDS);
        return reporter;
    }
}
  • report
public class GraphiteReportingManager extends MetricsConfigurerAdapter implements DisposableBean {

    private final Logger logger = LoggerFactory.getLogger(getClass());

    private GraphiteProperties props;


    public GraphiteReportingManager(GraphiteProperties props) {
        this.props = props;
    }

    @Override
    public void configureReporters(MetricRegistry metricRegistry) {
        //gc的metrics,目前看来每秒发送一次貌似太频繁,可以另起一个reporter进行
        metricRegistry.register("jvm.gc", new GarbageCollectorMetricSet());
        metricRegistry.register("jvm.mem", new MemoryUsageGaugeSet());
        metricRegistry.register("jvm.thread-states", new ThreadStatesGaugeSet());

        logger.info("graphite host:{},port:{}", props.getHost(), props.getPort());
        GraphiteReporter reporter = GraphiteReporter.forRegistry(metricRegistry)
                .prefixedWith(props.getPrefix())
//                        .convertRatesTo(TimeUnit.SECONDS)
                .convertDurationsTo(TimeUnit.MILLISECONDS)
                .filter(MetricFilter.ALL)
                .build(createSender(props));
        registerReporter(reporter);
        reporter.start(1L, TimeUnit.SECONDS);
    }

    @Override
    public void destroy() throws Exception {
        super.destroy();
    }

    private GraphiteSender createSender(GraphiteProperties props) {
        switch (props.getSenderType()) {
            case udp:
                return new GraphiteUDP(props.getHost(), props.getPort());
            case tcp:
                return new Graphite(props.getHost(), props.getPort());
            case pickled:
                return new PickledGraphite(props.getHost(), props.getPort());
            default:
                return new GraphiteUDP(props.getHost(), props.getPort());
        }
    }

}
  • Access

http://192.168.99.100:8070/

clipboard.png

References