Spring Cloud学习

Spring Cloud学习

微服务

1. Spring-boot

Restful API

@RestController
public class DemoController {
    @GetMapping("/hello")
    public String demoRest(){
        return "hello";
    }
}

pom.xml:

	<properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Finchley.RELEASE</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.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>

MockMvc Test

@RunWith(SpringRunner.class)
@SpringBootTest
@WebAppConfiguration
public class DemoControllerTest {
    MockMvc mvc;
    @Before
    public void setUp() {
        mvc = MockMvcBuilders.standaloneSetup(new DemoController()).build();
    }
    @Test
    public void demoRest() throws Exception {
        /**
         * 1、mockMvc.perform执行一个请求。
         * 2、MockMvcRequestBuilders.get("XXX")构造一个请求。
         * 3、ResultActions.param添加请求传值
         * 4、ResultActions.accept(MediaType.TEXT_HTML_VALUE))设置返回类型
         * 5、ResultActions.andExpect添加执行完成后的断言。
         * 6、ResultActions.andDo添加一个结果处理器,表示要对结果做点什么事情
         *   比如此处使用MockMvcResultHandlers.print()输出整个响应结果信息。
         * 5、ResultActions.andReturn表示执行完成后返回相应的结果。
         */

        mvc.perform(MockMvcRequestBuilders.get("/hello").accept(MediaType.APPLICATION_JSON)
        ).andExpect(MockMvcResultMatchers.status().isOk())
                .andExpect(MockMvcResultMatchers.content().string("hello"));
    }
}

多环境配置

多环境配置文件名需要满足application-{profile}.properties的格式,其中{profile}对应环境标识.

  • application-dev.properties 开发
  • application-dev.properties 测试
  • application-dev.properties 生产

application.properties中通过spring.profile.active属性指定


2. Spring Cloud Erueka

服务治理

  • 服务注册
    在服务治理框架中,通常都会构建一个注册中心,每个服务单元想注册中心登记自己提供的服务,将主机与端口、版本号、通信协议等一些附加信息告知注册中心,注册中心按服务名分类组织服务清单。
    | 服务名 | 位置 |
    | ------ | :---------------------------------------------------------- |
    | 服务A | 192.168.0.100:8000、192.168.0.101:8000 |
    | 服务B | 192.168.0.1000:9000、192.168.0.101:9000、192.168.0.102:9000 |

  • 服务发现
    由于在服务治理框架下运作,服务间的调用不再通过指定具体的实例地址来实现,而是通过向服务名发起请求调用实现。所以,服务调用方在调用服务提供方接口的时候,并不知道具体的服务实例位置。因此,调用方需要向服务注册中心咨询服务,并获取所有服务的实例清单,以实现对具体服务实例的访问。

Netflix Erueka

Eureka服务端,我们也称服务注册中心。它同其他服务注册中心一样,支持高可用配置。它依托于强一致性1提供良好的服务实例可用性,可以应对多种不同的故障场景。如果Eureka以集群模式部署,当集群中有分片2出现故障时,那么Eureka就转入自我保护模式。它允许在分片故障期间继续提供服务的发现和注册,当故障分片恢复运行时,集群中的其他分片会把他们的状态再次同步回来。以在AWS上的实践为例,Netflix推荐每个可用的区域运行一个Eureka服务端,通过它来形成集群。不同可用区域的服务注册中心通过异步模式互相复制各自的状态,这意味着在任意给定的时间点每个实例关于所有服务的状态是有细微差别的。
Eureka客户端,主要处理服务的注册与发现。客户端服务通过注解和参数配置的方式,嵌入在客户端应用程序的代码中,在应用程序运行时,Eureka客户端向注册中心注册自身提供的服务并周期性地发送心跳来更新它的服务租约。同时,它也能从服务端查询当前注册的服务信息并把他们缓存到本地并周期性地刷新服务状态。

搭建服务注册中心

我这边新的版本有点问题用的老版本,请忽略对版本不够新的顾忌。

  • pom.xml
   <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <properties>
        <java.version>1.8</java.version>
          <spring-cloud.version>Finchley.RELEASE</spring-cloud.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</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>
  • 通过@EnableEurekaServer注解启动一个服务注册中心提供给其他应用进行对话。这一步非常简单,只需要在一个普通的Spring Boot应用中添加这个注解就能开启此功能,
@SpringBootApplication
@EnableEurekaClient
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}
  • 在默认设置下,该服务注册中心也会将自己作为客户端来尝试注册它自己,所以我们需要禁用它的客户端注册行为,只需要在application。properties中增加如下配置即可:
#端口号
server:
  port: 10001
spring:
  #项目名称
  application:
    name: eureka-server
#服务注册
eureka:
  instance:
    hostname: localhost
  client:
  	register-with-eureka: false
    fetch-registry: false
    service-url:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
  • 完成上面的配置后,启动并访问ip:port 可以看到Eureka信息面板,其中Instances currently registered with Eureka;栏为空的,说明该注册中心还没有注册任何服务。
    在这里插入图片描述

  • 注册服务提供者

pom.xml

<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
         <version>2.0.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Finchley.RELEASE</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.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>
修改/hello请求处理接口,通过注入[^@Autowired]DiscoveryClient对象,在日志中打印出服务的相关信息。
@RestController
public class HelloController {
    @Autowired
    private DiscoveryClient client;
    @GetMapping("/hello")
    public String index() {
        List<String> services = client.getServices();
        services.forEach(System.out::println);
        return "hello";
    }
}

​ 同时需要在application.yml中将自己注册到Erueka注册中心中。

#端口号
server:
  port: 8609
spring:
  #项目名称
  application:
    name: demo-client
#服务注册
eureka:
  instance:
    hostname: localhost
    prefer-ip-address: true
  client:
    service-url:
      defaultZone: http://${eureka.instance.hostname}:10001/eureka/

​ 然后,在主类上通过加上@EnableDiscoveryClient 注解,激活Eureka中的DiscoveryClient实现(自动化配置,创建DiscoveryClient接口针对Eureka客户端的EurekaDiscoveryClient实例),才能实现上述Controller中对服务信息的输出。

@SpringBootApplication
@EnableEurekaClient
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

在这里插入图片描述

​ 分别启动服务注册中心以及这里改造的demo-client服务。在client控制台中打印了该服务的注册信息,表示服务注册成功。

​ 也可以通过访问Eureka信息面板,在Instances currently registered with Eureka中看到服务的注册信息。

在这里插入图片描述

​ 通过访问localhost:8609/hello,直接向服务器发起请求,在控制台可以看到如下输出:

在这里插入图片描述

​ 这些输出内容就是之前我们在HelloController中注入DiscoveryClient接口对象,从服务注册中心获取的服务相关信息。

高可用注册中心

待添加!!!

服务发现与消费

​ Ribbon是一个基于HTTP和TCP的客户端负载均衡器,它可以在通过客户端中配置的ribbonServerList服务端列表去轮询访问以达到均衡负载的作用。当Ribbon与Eureka联合使用是,Ribbon的服务实例清单RibbonServerList会被DiscoveryEnabledNIWSServerList重写,扩展成从Eureka注册中心获取服务端列表。同时它会用NIWSDiscoveryPing取代IPing,它将职责委托给Eureka来确定服务端是否已经启动。

使用java -Dfile.encoding=UTF-8 -jar demo-0.0.1-SNAPSHOT.jar --server.port=XXX启动2个服务,注意改变端口。

在这里插入图片描述

创建一个SpringBoot的基础工程来实现服务消费者。

pom.xml

 <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Finchley.RELEASE</spring-cloud.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</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.boot</groupId>
            <artifactId>spring-boot-starter-web</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>

application.yml

#端口号
server:
  port: 8705
spring:
  #项目名称
  application:
    name: consumer
#服务注册
eureka:
  instance:
    hostname: localhost
    prefer-ip-address: true
  client:
    service-url:
      defaultZone: http://${eureka.instance.hostname}:10001/eureka/

创建应用ConsumerApplication,必须增加@EnableDiscoveryClient,将自己注册到Eureka上,以获取服务发现的能力。同时,在主类中创建RestTemplate的Spring Bean实例,并通过@LoadBalanced注解开启客户端负载均衡。

@SpringBootApplication
@EnableEurekaClient
public class ConsumerApplication {
    @Bean
    @LoadBalanced
    RestTemplate restTemplate(){
        return new RestTemplate();
    }
    public static void main(String[] args) {
        SpringApplication.run(ConsumerApplication.class, args);
    }
}

增加ConsumerController,通过RestTemplate来实现对HELLO-SERVICE服务提供的、hello接口进行调用。

@RestController
public class ConsumerController {
    @Autowired
    RestTemplate restTemplate;
    @RequestMapping(value = "/consumer", method = RequestMethod.GET)
    public String hello() {
        return restTemplate.getForEntity("http://demo-client", String.class).getBody();
    }
}

启动consumer应用后,可以在Eureka信息面板上看到,除了当前的DEMO-CLIENT外,还多CONSUMER服务。

在这里插入图片描述通过 localhost:8705/consumer发起GET请求,成功返回hello,对应的在后台控制台可以看到如下信息,Ribbon输出了当前客户端维护的demo-client的服务列表情况。其中包含了各个实例的位置,Ribbon就是按照此信息进行轮询访问,以实现基于客户端的负载均衡。还输出了各个实例的请求总数量、第一次连接信息、上一次连接信息、总的请求失败数量等有用的信息。

在这里插入图片描述

尝试多次发送请求,会发现两个客户端交替打印如下信息。以此也可以判断当前ribbon-consumer对DEMO-CLIENT的调用是否是负载均衡的。
在这里插入图片描述

Eureka详解

在这里插入图片描述


  1. 分布式系统之数据分片 ↩︎

  2. 租约机制简介 ↩︎

posted @ 2019-05-31 16:38  MysticalYcc  阅读(11)  评论(0)    收藏  举报