Java web · 2019年8月27日 0

一步一步带你构建Spring Cloud

​ Spring Cloud是一系列框架的有序集合。它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用Spring Boot的开发风格做到一键启动和部署。Spring Cloud并没有重复制造轮子,它只是将目前各家公司开发的比较成熟、经得起实际考验的服务框架组合起来,通过Spring Boot风格进行再封装屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂、易部署和易维护的分布式系统开发工具包。

  • Spring Cloud Netflix
      是对Netflix开发的一套分布式服务框架的封装,包括服务的发现和注册,负载均衡、断路器、REST客户端、请求路由等。

  • Spring Cloud Config
      将配置信息中央化保存, 配置Spring Cloud Bus可以实现动态修改配置文件

  • Spring Cloud Bus
      分布式消息队列,是对Kafka, MQ的封装

  • Spring Cloud Security
      对Spring Security的封装,并能配合Netflix使用

  • Spring Cloud Zookeeper
      对Zookeeper的封装,使之能配置其它Spring Cloud的子项目使用

  • Spring Cloud Eureka

    Spring Cloud Eureka 是 Spring Cloud Netflix 微服务套件中的一部分,它基于Netflix Eureka 做了二次封装,主要负责完成微服务架构中的服务治理功能

​ Spring cloud 是微服务架构的集大成者,将一系列优秀的组件进行了整合。

本章Spring cloud 使用,一步一步带你构建Spring cloud整个应用

  1. 构建Spring cloud服务端
  2. 构建Spring cloud 服务提供端
  3. 构建Spring cloud 消费端(服务调用端)

img

一、Spring Cloud 服务端

POM.xml 配置

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.6.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.test.cloud</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Greenwich.SR2</spring-cloud.version>
    </properties>

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

application.properties配置

server.port=8761
eureka.instance.hostname=127.0.0.1
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
eureka.client.service-url.defaultZone=http://{spring.security.user.name}:{spring.security.user.password}@{eureka.instance.hostname}:{server.port}/eureka/

# suppress inspection "SpringBootApplicationProperties"
spring.security.basic.enabled=true
spring.security.user.name=user
spring.security.user.password=123456

项目入口Application配置

  1. 添加 @EnableEurekaServer 注解 在Application中

如果遇到csrf问题的时候,需要实现WebSecurityConfigurerAdapter的configure 方法

import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable();
        super.configure(http);
    }
}

直接运行项目 出现下面表示项目启动成功

[ Thread-46] o.s.c.n.e.server.EurekaServerBootstrap : Initialized server context
[ Thread-46] c.n.e.r.PeerAwareInstanceRegistryImpl : Got 1 instances from neighboring DS node
[ Thread-46] c.n.e.r.PeerAwareInstanceRegistryImpl : Renew threshold is: 1
[ Thread-46] c.n.e.r.PeerAwareInstanceRegistryImpl : Changing status to UP
[ Thread-46] e.s.EurekaServerInitializerConfiguration : Started Eureka Server
[ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8761 (http) with context path ''
[ main] .s.c.n.e.s.EurekaAutoServiceRegistration : Updating port to 8761
[ main] com.test.cloud.demo.DemoApplication : Started DemoApplication in 18.421 seconds (JVM running for 26.067)
[on(2)-127.0.0.1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
[on(2)-127.0.0.1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
[on(2)-127.0.0.1] o.s.web.servlet.DispatcherServlet : Completed initialization in 16 ms

在浏览器输入 127.0.0.1:8761 出现 输入我们在application.properties中配置的用户名和密码, 出现下面界面,表示服务注册中心启动成功,等待服务注册。

image-20190827132029270

image-20190827132220517

二、构建Spring cloud 服务提供端

pom.xml 配置

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.6.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>clientdemo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>clientdemo</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Greenwich.SR2</spring-cloud.version>
    </properties>

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

application.properties配置,注意名称不能包含下划线(—)

server.port=8762
eureka.client.service-url.defaultZone=http://user:123456@127.0.0.1:8761/eureka/
spring.application.name=client-test-demo
spring.security.user.name=user
spring.security.user.password=123456

入口Application配置

服务提供者需要在Application添加@EnableEurekaClient 注解

添加一个测试的Controller

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
@RequestMapping("index")
public class IndexController {
    @GetMapping("test")
    @ResponseBody
    public String test(){
        return "测试-服务提供者";
    }
}

启动服务提供者,出现如下表示启动成功,可以通过接口调用下上面写的Controller的测试方法是否成功。

scoveryClient : Starting heartbeat executor: renew interval is: 30
[ main] c.n.discovery.InstanceInfoReplicator : InstanceInfoReplicator onDemand update allowed rate per min is 4
[ main] com.netflix.discovery.DiscoveryClient : Discovery Client initialized at timestamp 1566883730293 with initial instances count: 0
[ main] o.s.c.n.e.s.EurekaServiceRegistry : Registering application CLIENT-TEST-DEMO with eureka with status UP
[ main] com.netflix.discovery.DiscoveryClient : Saw local status change event StatusChangeEvent [timestamp=1566883730299, current=UP, previous=STARTING]
[nfoReplicator-0] com.netflix.discovery.DiscoveryClient : DiscoveryClient_CLIENT-TEST-DEMO/192.168.1.78:client-test-demo:8762: registering service…
[ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8762 (http) with context path ''
[ main] .s.c.n.e.s.EurekaAutoServiceRegistration : Updating port to 8762
[ main] c.e.clientdemo.ClientdemoApplication : Started ClientdemoApplication in 14.831 seconds (JVM running for 21.023)
[nfoReplicator-0] com.netflix.discovery.DiscoveryClient : DiscoveryClient_CLIENT-TEST-DEMO/192.168.1.78:client-test-demo:8762 – registration status: 204

查看服务注册中心是否有启动的注册的服务

image-20190827133202151

三、构建Spring cloud 消费端(服务调用端)

pom.xml配置

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.6.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>springxiaofeidemo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springxiaofeidemo</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Greenwich.SR2</spring-cloud.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- 添加Actuator监控 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>{spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <!--<finalName>{project.artifactId}</finalName>-->
        <!--<resources>-->
            <!--<resource>-->
                <!--<directory>src/main/resources</directory>-->
                <!--<filtering>true</filtering>-->
            <!--</resource>-->
        <!--</resources>-->
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <!--<plugin>-->
                <!--<groupId>org.apache.maven.plugins</groupId>-->
                <!--<artifactId>maven-resources-plugin</artifactId>-->
                <!--<configuration>-->
                    <!--<delimiters>-->
                        <!--<delimit>$</delimit>-->
                    <!--</delimiters>-->
                <!--</configuration>-->
            <!--</plugin>-->
        </plugins>
    </build>

</project>

application.properties配置

server.port=8763
eureka.client.service-url.defaultZone=http://user:123456@127.0.0.1:8761/eureka/
spring.application.name=client-request-demo
spring.security.user.name=user
spring.security.user.password=123456
feign.hystrix.enabled=true

入口Application中添加@EnableEurekaClient和@EnableFeignClients 注解 FeignClients是用来服务间调用的。

@EnableEurekaClient
@SpringBootApplication
@EnableFeignClients
public class SpringxiaofeidemoApplication {

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

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

添加测试Controller类

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
@RequestMapping("/index")
public class IndexController {
    @Autowired
    HelloService helloService;

    @Autowired
    RestTemplate restTemplate;
    @GetMapping("test")
    public String indexTest(){// 通过HelloService中的注解绑定调用 参考 Helloservice
       return this.helloService.hello();
    }

    @GetMapping("test1")
    public String ribbonTest(){
        //注解@HystrixCommand 不能加在控制器层,而应该加在Service 层  使用Hystrix进行容错和服务降级
       return restTemplate.getForObject("http://CLIENT-TEST-DEMO/index/test",String.class);
    }
}
HelloService接口
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;

@FeignClient(value = "CLIENT-TEST-DEMO",fallback = HelloServiceFallback.class)
public interface HelloService {
    @GetMapping(value = "index/test")
    String hello();
}

HelloServiceFallback 类

import org.springframework.stereotype.Component;

@Component
public class HelloServiceFallback implements HelloService{
    @Override
    public String hello() {
        return "测试-服务降权容错提供者";
    }
}

启动服务

[ main] com.netflix.discovery.DiscoveryClient : Getting all instance registry info from the eureka server
[ main] com.netflix.discovery.DiscoveryClient : The response status is 200
[ main] com.netflix.discovery.DiscoveryClient : Starting heartbeat executor: renew interval is: 30
[ main] c.n.discovery.InstanceInfoReplicator : InstanceInfoReplicator onDemand update allowed rate per min is 4
[ main] com.netflix.discovery.DiscoveryClient : Discovery Client initialized at timestamp 1566884583081 with initial instances count: 1
[ main] o.s.c.n.e.s.EurekaServiceRegistry : Registering application CLIENT-REQUEST-DEMO with eureka with status UP
[ main] com.netflix.discovery.DiscoveryClient : Saw local status change event StatusChangeEvent [timestamp=1566884583086, current=UP, previous=STARTING]
[nfoReplicator-0] com.netflix.discovery.DiscoveryClient : DiscoveryClient_CLIENT-REQUEST-DEMO/192.168.1.78:client-request-demo:8763: registering service…
[nfoReplicator-0] com.netflix.discovery.DiscoveryClient : DiscoveryClient_CLIENT-REQUEST-DEMO/192.168.1.78:client-request-demo:8763 – registration status: 204
[ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8763 (http) with context path ''
[ main] .s.c.n.e.s.EurekaAutoServiceRegistration : Updating port to 8763
[ main] c.e.s.SpringxiaofeidemoApplication : Started SpringxiaofeidemoApplication in 17.69 seconds (JVM running for 23.897)
[on(6)-127.0.0.1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
[on(6)-127.0.0.1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
[on(6)-127.0.0.1] o.s.web.servlet.DispatcherServlet : Completed initialization in 15 ms

在浏览器中输入
http://127.0.0.1:8763/index/test

结果:测试-服务提供者

http://127.0.0.1:8763/index/test1

结果:测试-服务提供者

如果我们关闭服务提供者会是什么情况呢?

关闭服务提供者

输入:http://127.0.0.1:8763/index/test

结果:测试-服务降权容错提供者

输入:http://127.0.0.1:8763/index/test1

结果:Whitelabel Error Page

This application has no explicit mapping for /error, so you are seeing this as a fallback.

Tue Aug 27 13:48:14 CST 2019

There was an unexpected error (type=Internal Server Error, status=500).

No instances available for CLIENT-TEST-DEMO

项目控制台输出java.lang.IllegalStateException: No instances available for CLIENT-TEST-DEMO

如果服务都启动之后关闭注册中心请大家自己试试?

四、其他

上面的3个工程打包为zip放到百度云盘

链接: https://pan.baidu.com/s/1IOWaF_uUzL65_6GQWf6pDA 提取码: kuw8

背景介绍:eureka默认开启了自我保护机制,导致实际上已经停止服务的实例无法从注册中心剔除!

解决方案:

在注册中心(eureka-server端,而不是eureka-client端)添加如下配置:

以下配置仅在开发环境中使用

关闭注册中心的自我保护机制,防止已关闭的实例无法从注册中心剔除

eureka.server.enable-self-preservation=false

背景:由于Eureka拥有自我保护机制,当其注册表里服务因为网络或其他原因出现故障而关停时,Eureka不会剔除服务注册,而是等待其修复。这是AP的一种实现。
为了让其有精准的 CP健康检查,可以采取让其剔除不健康节点。

server端:
eureka.server.enable-self-preservation//(设为false,关闭自我保护主要)
eureka.server.eviction-interval-timer-in-ms//清理间隔(单位毫秒,默认是60*1000)
client端:
eureka.client.healthcheck.enabled = true//开启健康检查(需要spring-boot-starter-actuator依赖)
eureka.instance.lease-renewal-interval-in-seconds =10//租期更新时间间隔(默认30秒)
eureka.instance.lease-expiration-duration-in-seconds =30//租期到期时间(默认90秒)

实例:

server端配置:
eureka:
server:
enableSelfPreservation: false
evictionIntervalTimerInMs: 4000
client配置:
eureka:
instance:
leaseRenewalIntervalInSeconds: 10
leaseExpirationDurationInSeconds: 30

lease-renewal-interval-in-seconds 每间隔10s,向服务端发送一次心跳,证明自己依然”存活“

lease-expiration-duration-in-seconds 告诉服务端,如果我30s之内没有给你发心跳,就代表我“死”了,将我踢出掉。

注意:更改Eureka更新频率将打破服务器的自我保护功能

参考

https://zhuanlan.zhihu.com/p/26472547

Share this: