微服务架构

微服务架构 1

什么是微服务架构? 2

为甚么选择springcloud来做微服务架构? 2

课程目标 3

课程内容 3

进入干货部分 4

1. Spring Boot 4

1.1 Spring Boot的优缺点 4

1.2  快速搭建一个Spring Boot项目 4

1.2.1  设置springbootparent 4

1.2.2  导入springbootweb支持 5

1.2.3  添加springboot插件 5

1.2.4  编写第一个SpringBoot的应用 5

1.2.5  启动应用 6

1.2.6  关于项目的一些说明 6

1.3  迁移已有项目(房屋估价) 6

1.3.1  房屋估价项目用到的组件 6

1.3.2  springboot项目实现热部署 7

1.3.3  整合swagger2 7

1.3.4  整合mybatismysql连接池 8

1.3.5  整合kafka 12

1.3.6  迁移项目 13

1.4 小结 13

2. Spring Cloud 13

2.1 Spring Cloud Eureka 13

2.1.1 构建服务注册中心(Eureka Server) 15

2.1.2  服务注册与服务发现(Eureka Client) 17

2.1.3  构建一个服务消费者 18

2.1.4  高可用服务注册中心 20

2.2 Spring Cloud Ribbon 22

2.3 Spring Cloud Hystrix 22

2.4 Spring Cloud Feign 22

2.5 Spring Cloud Zuul 23

2.5.1  服务路由 24

2.5.2  请求过滤 26

2.5.3  过滤器动态注册 29

2.6 Spring Cloud Config 29

2.7 Spring Cloud Bus 30

2.8 Spring Cloud Stream 30

2.9  Spring Cloud Sleuth 30

 

什么是微服务架构?

微服务是一种架构风格,一个大型复杂软件应用由一个或多个微服务组成。系统中的各个微服务可被独立部署,各个微服务之间是松耦合的。每个微服务仅关注于完成一件任务。在所有情况下,每个任务代表着一个小的业务能力。

“微服务”这种架构风格没有精确的定义,但其具有一些共同的特性,如应用组件化、对语言及数据的“去集中化”控制等等。

 

通过服务实现应用的组件化: 微服务架构中将组件定义为可被独立替换和升级的软件单元,在应用架构设计中通过将整体应用切分成可独立部署及升级的微服务方式进行组件化设计。

 

 “去中心化”治理: 微服务架构鼓励使用合适的工具完成各自的任务,每个微服务可以考虑选用最佳工具完成(如不同的编程语言)。微服务的技术标准倾向于寻找其他开发者已成功验证能解决类似问题的技术。

 

“去中心化”数据管理: 微服务架构倡导采用多样性持久化的方法,让每个微服务管理其自有数据库,并允许不同微服务采用不同的数据持久化技术。

 

基础设施自动化: 云化及自动化部署等技术极大地降低了微服务构建、部署和运维的难度。

 

故障处理设计: 微服务架构所带来的一个后果是必须考虑每个服务的失败容错机制。因此,微服务非常重视建立架构及业务相关指标的实时监控和日志机制。

 

 

 

为甚么选择springcloud来做微服务架构?

市场上有很多微服务架构中针对不同应用场景出现的各种问题的各种解决方案和开源架构。

 

服务治理: 阿里巴巴开源的Dubbo和当当网在其基础上扩展的DubboX、Netflix的Eureka、Apache的Consul等。

分布式配置管理: 百度的Disconf、Netflix的Archaius、360的Qconf、Spring Cloud的Config、淘宝的Diamond等。

批量任务: 当当网的Elastic-Job、LinkedIn的Azkaban、Spring Cloud的Task等。

服务跟踪: 京东的Hydra、Spring Cloud的Sleuth、Twitter的Zipkin等。

…….

 

Spring Cloud不只是解决了微服务中的某一个问题,而是一个解决微服务架构实施的综合性解决框架,它整合了诸多被时间证明过的框架作为实施的基础部件。具体框架会在下面的内容中进行更详细的介绍。

 

 

 

课题目标

  1. 了解微服务架构都包含哪些内容及其解决方案 。
  2. 独立搭建架构,达到入门,以方便大家在此基础上进行更深入的研究。
  3. Springboot项目搭建

课程内容

因为spring cloud是以spring boot为基础构建的,所以首先需要能够搭建起spring boot项目,并研究spring boot项目是否适合公司业务,以及是否能够与之前业务衔接,或者说已有业务能否平滑的平移到spring boot项目上。

  1. Spring cloud

Spring cloud Eureka 服务治理

Spring cloud Ribbon 客户端负载均衡

Spring cloud Hystrix 服务容错保护

Spring cloud Feign 声明式服务调用

Spring cloud Zuul API网关服务

Spring cloud Config 分布式配置中信

Spring Cloud Bus 消息总线

Spring cloud Stream 消息驱动微服务

Spring Cloud Sleuth 分布式服务跟踪

 

 

进入干货部分

1.  Spring Boot

1.1 Spring Boot的优缺点

优点:

(1) 快速构建项目;

(2) 对主流开发框架的无配置集成

(3) 项目可独立运行,无需外部依赖Servlet容器;

(4) 热部署(包括class及配置文件)

(5) 与云计算的天然集成。(还未涉及到)

缺点:

(1) 无配置集成会损失一定的灵活性,不过可以选择性适用。

(2) 书籍文档较少,而且不够深入

 

1.2  快速搭建一个Spring Boot项目

1.2.1  设置springbootparent

<parent>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-parent</artifactId>

<version>1.5.6.RELEASE</version>

</parent>

 

说明:Springboot的项目必须要将parent设置为springbootparent,该parent包含了大量默认的配置,大大简化了我们的开发。

如果下载不下来的话,可以配置mavensetting中的仓库为阿里云仓库

<mirror>

<id>nexus-aliyun</id>

<mirrorOf>*</mirrorOf>

<name>Nexus aliyun</name>

<url>http://maven.aliyun.com/nexus/content/groups/public</url>

</mirror>

 

 

1.2.2  导入springbootweb支持

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-web</artifactId>

</dependency>

 

 

1.2.3  添加springboot插件

<plugin>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-maven-plugin</artifactId>

</plugin>

1.2.4  编写第一个SpringBoot的应用

@RestController

@SpringBootApplication

@Configuration

publicclass HelloApplication {

 

@RequestMapping("hello")

@ResponseBody

public String hello(){

return"hello world!";

    }

 

publicstaticvoid main(String[] args) {

        SpringApplication.run(HelloApplication.class, args);

    }

 

}

 

代码说明:

1、@SpringBootApplicationSpringBoot项目的核心注解,主要目的是开启自动配置。;

2、@Configuration:这是一个配置Spring的配置类;

3、@Controller:标明这是一个SpringMVC的Controller控制器;

4、main方法:在main方法中启动一个应用,即:这个应用的入口;

1.2.5  启动应用

SpringBoot项目中,启动的方式有两种,一种是直接runJava Application另外一种是通过Spring BootMaven插件运行。

 

1.2.6  关于项目的一些说明

1SpringBoot的项目一般都会有*Application的入口类,入口类中会有main方法,这是一个标准的Java应用程序的入口方法。

2. Spring Boot项目中推荐使用@SpringBootConfiguration替代@Configuration

3. 我们添加了spring-boot-starter-web的依赖,项目中也就会引入SpringMVC的依赖,SpringBoot就会自动配置tomcatSpringMVC

4. 可以关闭自动配置,比如关闭对redis的自动配置,可以通过@SpringBootApplication(exclude={RedisAutoConfiguration.class})来实现。

5. 自定义banner

(1) 打开网站:http://patorjk.com/software/taag/#p=display&h=3&v=3&f=4Max&t=itcast%20Spring%20Boot

(2) 拷贝生成的字符到一个文本文件中,并且将该文件命名为banner.txt

(3) banner.txt拷贝到项目的resources目录中

(4) 重启就可看到效果

6. application.properties

application.propertiesspringboot项目使用的一个全局配置文件,一般放在resources

(1) 修改tomcat的端口为8088

server.port=8088

(2) 修改DispatcherServlet规则为: *.html

Server.servlet-path=*.html

 

7. 更多配置请参考springboot配置&starter pom”文档

 

1.3  迁移已有项目(房屋估价)

1.3.1  房屋估价项目用到的组件

1)  swagger

2)  mybatis

3)  mysql连接池

4)  kafka

1.3.2  springboot项目实现热部署

为了方便以后的开发与测试,我们首先实现springboot的热部署

(1) 添加依赖

<dependency>

  <groupId>org.springframework.boot</groupId>

  <artifactId>spring-boot-devtools</artifactId>

  <optional>true</optional>

  <scope>true</scope>

</dependency>

 

(2) 添加插件

 

<build>

<plugins>

      <plugin>

        <groupId>org.springframework.boot</groupId>

        <artifactId>spring-boot-maven-plugin</artifactId>

        <configuration>

   <!--fork :  如果没有该项配置,肯呢个devtools不会起作用,即应用不会restart -->

         <fork>true</fork>

        </configuration>

      </plugin>

  </plugins>

  </build>

 

1.3.3  整合swagger2

1)添加依赖

<!-- swagger2 -->

<dependency>

    <groupId>io.springfox</groupId>

    <artifactId>springfox-swagger2</artifactId>

    <version>2.2.2</version>

</dependency>

<dependency>

    <groupId>io.springfox</groupId>

    <artifactId>springfox-swagger-ui</artifactId>

    <version>2.2.2</version>

</dependency>

 

2)添加注解

springboot启动类中添加注解 @EnableSwagger2,表示启用swagger

(3) 注册bean

@Bean

    public Docket createRestApi() {

        return new Docket(DocumentationType.SWAGGER_2)

                //.apiInfo(apiInfo())

                .select()

                .apis(RequestHandlerSelectors.basePackage("com.bacic5i5j"))

                .paths(PathSelectors.any())

                .build();

    }

 

(4) 测试swagger集成效果

在浏览器中输入http://localhost:8080/swagger-ui.html

 

1.3.4  整合mybatismysql连接池

1)添加依赖

<!-- Mybatis -->

<dependency>

<groupId>org.mybatis</groupId>

<artifactId>mybatis</artifactId>

<version>3.2.8</version>

</dependency>

<dependency>

<groupId>org.mybatis</groupId>

<artifactId>mybatis-spring</artifactId>

<version>1.2.2</version>

</dependency>

<dependency> 

    <groupId>org.springframework</groupId>

    <artifactId>spring-tx</artifactId>

    <version>4.3.7.RELEASE</version>

</dependency>

<!-- MySql -->

<dependency>

<groupId>mysql</groupId>

<artifactId>mysql-connector-java</artifactId>

</dependency>

<!-- 连接池 -->

<dependency>

<groupId>com.jolbox</groupId>

<artifactId>bonecp-spring</artifactId>

<version>0.8.0.RELEASE</version>

</dependency>

<dependency>

        <groupId>org.springframework.boot</groupId>

        <artifactId>spring-boot-starter-jdbc</artifactId>

      </dependency>

2)添加注解

在启动类中设置扫描包

@ComponentScan(basePackages="com.bacic5i5j")//设置扫描包

3)添加application配置

#数据源配置

jdbc.driverClassName=com.mysql.jdbc.Driver

jdbc.url=jdbc:mysql://10.2.1.135:3306/HousingEvaluation?useUnicode=true&characterEncoding=UTF-8

jdbc.username=app_user_5i5j

jdbc.password=app1q2w3e4r

#分区个数

jdbc.partitionCount=4

#每个分区的最大连接数

jdbc.maxConnectionsPerPartition=100

#每个分区的最小连接数

jdbc.minConnectionsPerPartition=5

#检查数据库连接池中空闲连接的间隔时间,单位分钟

jdbc.idleConnectionTestPeriod=60

#连接池中未使用的链接最大存活时间

jdbc.idleMaxAgeInMinutes=30

 

4)注册数据源bean

@Value("${jdbc.url}")

private String jdbcUrl;

 

@Value("${jdbc.driverClassName}")

private String jdbcDriverClassName;

 

@Value("${jdbc.username}")

private String jdbcUsername;

 

@Value("${jdbc.password}")

private String jdbcPassword;

 

@Value("${jdbc.partitionCount}")

private String jdbcPartitionCount;

 

@Value("${jdbc.maxConnectionsPerPartition}")

private String jdbcMaxConnectionsPerPartition;

 

@Value("${jdbc.minConnectionsPerPartition}")

private String jdbcMinConnectionsPerPartition;

 

@Value("${jdbc.idleMaxAgeInMinutes}")

private String jdbcIdleMaxAgeInMinutes;

 

@Value("${jdbc.idleConnectionTestPeriod}")

private String jdbcIdleConnectionTestPeriod;

 

@Bean(name="dataSource",destroyMethod = "close")  //当数据库连接不使用的时候,就把该连接重新放到数据池中,方便下次使用调用

public DataSource dataSource() {

        BoneCPDataSource boneCPDataSource = new BoneCPDataSource();

        // 数据库驱动

        boneCPDataSource.setDriverClass(jdbcDriverClassName);

        // 相应驱动的jdbcUrl

        boneCPDataSource.setJdbcUrl(jdbcUrl);

        // 数据库的用户名

        boneCPDataSource.setUsername(jdbcUsername);

        // 数据库的密码

        boneCPDataSource.setPassword(jdbcPassword);

        

        boneCPDataSource.setPartitionCount(Integer.parseInt(jdbcPartitionCount));

        

        // 检查数据库连接池中空闲连接的间隔时间,单位是分,默认值:240,如果要取消则设置为0

        boneCPDataSource.setIdleConnectionTestPeriodInMinutes(Integer.parseInt(jdbcIdleConnectionTestPeriod));

        

        // 连接池中未使用的链接最大存活时间,单位是分,默认值:60,如果要永远存活设置为0

        boneCPDataSource.setIdleMaxAgeInMinutes(Integer.parseInt(jdbcIdleMaxAgeInMinutes));

        // 每个分区最大的连接数

        boneCPDataSource.setMaxConnectionsPerPartition(Integer.parseInt(jdbcMaxConnectionsPerPartition));

        // 每个分区最小的连接数

        boneCPDataSource.setMinConnectionsPerPartition(Integer.parseInt(jdbcMinConnectionsPerPartition));

        org.apache.ibatis.logging.LogFactory.useLog4JLogging();

        return boneCPDataSource;

}

 

4)添加mapper扫描器

import org.mybatis.spring.mapper.MapperScannerConfigurer;

import org.springframework.boot.autoconfigure.AutoConfigureAfter;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

 

@Configuration

@AutoConfigureAfter(MybatisConfig.class)  //保证在MyBatisConfig实例化之后再实例化该类

public class MapperScannerConfig {

//mapper接口扫描器

@Bean

public MapperScannerConfigurer mapperScannerConfigurer() {

        MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();

mapperScannerConfigurer.setBasePackage("com.bacic5i5j.dao.mapper");

return mapperScannerConfigurer;

    }

}

5)添加mybatis配置类

import javax.sql.DataSource;

 

import org.mybatis.spring.SqlSessionFactoryBean;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.boot.autoconfigure.AutoConfigureAfter;

import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

 

@Configuration

@AutoConfigureAfter(AssApplication.class)

public class MybatisConfig {

@Autowired

    private DataSource dataSource;

 

@Bean

@ConditionalOnMissingBean//当容器里没有指定的Bean的情况下创建该对象

public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource) {

        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();

// 设置数据源

sqlSessionFactoryBean.setDataSource(dataSource);

// 设置mybatis的主配置文件

return sqlSessionFactoryBean;

}

}

 

1.3.5  整合kafka

1 添加依赖

<!-- kafka -->

    <dependency>

    <groupId>org.springframework.kafka</groupId>

    <artifactId>spring-kafka</artifactId>

    <version>2.0.0.RELEASE</version>

</dependency>

2 添加application配置

#kafka配置

# 指定kafka 代理地址,可以多个

spring.kafka.bootstrap-servers=10.2.1.136:9092,10.2.1.137:9092,10.2.1.138:9092

# 指定默认topic id

spring.kafka.template.default-topic= assessment

# 每次批量发送消息的数量

spring.kafka.producer.batch-size= 1000

#等待kafka cluster 确认接收消息

spring.kafka.producer.acks=1

 

3 添加注解

springboot启动类上添加类注解@EnableKafka

 

 

 

1.3.6迁移项目

(1) 创建如下目录

 

2 将对应层次的代码拷贝到项目中

3) 启动项目测试效果

 

1.4小结

Springboot能够快速构建项目,配置文件少,方便自动化部署,开发测试方便,组件集成快速简单,能够平滑迁移已有项目,不用修改代码结构以及逻辑,能够独立打包部署,简化依赖关系,可以考虑作为以后项目开发的架构选择。

 

 

 

2.  Spring Cloud

2.1 Spring Cloud Eureka

Spring Cloud Eureka主要负责完成微服务架构中的服务治理功能。

 

从图可以看出在这个体系中,有2个角色,即Eureka Server和Eureka Client。而Eureka Client又分为Applicaton Service和Application Client,即服务提供者何服务消费者。 每个区域有一个Eureka集群,并且每个区域至少有一个eureka服务器可以处理区域故障,以防服务器瘫痪。

Eureka Client向Eureka Serve注册,并将自己的一些客户端信息发送Eureka Serve。然后,Eureka Client通过向Eureka Serve发送心跳(每30秒)来续约服务的。 如果客户端持续不能续约,那么,它将在大约90秒内从服务器注册表中删除。 注册信息和续订被复制到集群中的Eureka Serve所有节点。 来自任何区域的Eureka Client都可以查找注册表信息(每30秒发生一次)。根据这些注册表信息,Application Client可以远程调用Applicaton Service来消费服务。

 

 

  • Register:服务注册 
    Eureka客户端向Eureka Server注册时,它提供自身的元数据,比如IP地址、端口,运行状况指示符URL,主页等。
  • Renew:服务续约 
    Eureka客户会每隔30秒发送一次心跳来续约。 通过续约来告知Eureka Server该Eureka客户仍然存在,没有出现问题。 正常情况下,如果Eureka Server在90秒没有收到Eureka客户的续约,它会将实例从其注册表中删除。 建议不要更改续约间隔。
  • Fetch Registries:获取注册列表信息 
    Eureka客户端从服务器获取注册表信息,并将其缓存在本地。客户端会使用该信息查找其他服务,从而进行远程调用。该注册列表信息定期(每30秒钟)更新一次。每次返回注册列表信息可能与Eureka客户端的缓存信息不同, Eureka客户端自动处理。如果由于某种原因导致注册列表信息不能及时匹配,Eureka客户端则会重新获取整个注册表信息。 Eureka服务器缓存注册列表信息,整个注册表以及每个应用程序的信息进行了压缩,压缩内容和没有压缩的内容完全相同。Eureka客户端和Eureka 服务器可以使用JSON / XML格式进行通讯。在默认的情况下Eureka客户端使用压缩JSON格式来获取注册列表的信息。
  • Cancel:服务下线 
    Eureka客户端在程序关闭时向Eureka服务器发送取消请求。 发送请求后,该客户端实例信息将从服务器的实例注册表中删除。该下线请求不会自动完成,它需要调用以下内容: 
    DiscoveryManager.getInstance().shutdownComponent();
  • Eviction 服务剔除 
    在默认的情况下,当Eureka客户端连续90秒没有向Eureka服务器发送服务续约,即心跳,Eureka服务器会将该服务实例从服务注册列表删除,即服务剔除。

 

 

2.1.1  构建服务注册中心(Eureka Server)

(1) 添加依赖

<parent>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-parent</artifactId>

<version>1.5.6.RELEASE</version>

</parent>

 

<dependencies>

<dependency>

<groupId>org.springframework.cloud</groupId>

<artifactId>spring-cloud-starter-eureka-server</artifactId>

</dependency>

<!-- 热部署 -->

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-devtools</artifactId>

<optional>true</optional>

<scope>true</scope>

</dependency>

</dependencies>

<dependencyManagement>

<dependencies>

<dependency>

<groupId>org.springframework.cloud</groupId>

<artifactId>spring-cloud-dependencies</artifactId>

<version>Dalston.SR4</version>

<type>pom</type>

<scope>import</scope>

</dependency>

</dependencies>

</dependencyManagement>

<build>

<plugins>

<plugin>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-maven-plugin</artifactId>

<configuration>

<!--fork : 如果没有该项配置,肯呢个devtools不会起作用,即应用不会restart -->

<fork>true</fork>

</configuration>

</plugin>

</plugins>

</build>

 

(2) 添加application配置

server.port=1111

 

#eureka server

eureka.instance.hostname=localhost

#该服务为注册中心,故设置为false,代表不向注册中心注册自己

eureka.client.register-with-eureka=false

#注册中心的职责是维护服务实例,并不需要去检索服务,所以也设置成false

eureka.client.fetch-registry=false

eureka.client.serviceUrl.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka/

 

(3) 启动服务注册中心

只需要在springboot入口类上加上注解 @EnableEurekaServer 即可

在浏览器中输入http://localhost:1111/查看效果

2.1.2  服务注册与服务发现(Eureka Client)

1)添加依赖

<parent>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-parent</artifactId>

<version>1.5.6.RELEASE</version>

</parent>

 

<dependencies>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-web</artifactId>

</dependency>

<dependency>

<groupId>org.springframework.cloud</groupId>

<artifactId>spring-cloud-starter-eureka</artifactId>

</dependency>

<!-- 热部署 -->

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-devtools</artifactId>

<optional>true</optional>

<scope>true</scope>

</dependency>

</dependencies>

<dependencyManagement>

<dependencies>

<dependency>

<groupId>org.springframework.cloud</groupId>

<artifactId>spring-cloud-dependencies</artifactId>

<version>Dalston.SR4</version>

<type>pom</type>

<scope>import</scope>

</dependency>

</dependencies>

</dependencyManagement>

<build>

<plugins>

<plugin>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-maven-plugin</artifactId>

<configuration>

<!--fork : 如果没有该项配置,肯呢个devtools不会起作用,即应用不会restart -->

<fork>true</fork>

</configuration>

</plugin>

</plugins>

</build>

 

(2) 添加application配置

server.port=8088

spring.application.name=hello-service

eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/

(3) 创建一个rest服务

 

@RestController

public class HelloController {

 

private final Logger logger=Logger.getLogger(HelloController.class);

@Autowired

private DiscoveryClient client;

@RequestMapping(value="/hello",method=RequestMethod.GET)

public String index(){

ServiceInstance instance=client.getLocalServiceInstance();

logger.info("hello,host:"+instance.getHost()+" service_id:"+instance.getServiceId());

return "Hello World";

}

}

(4) 添加注释,启用EurekaClient

@EnableEurekaClient

(5) 测试

启动服务注册中心服务

启动服务提供者,注册服务

查看eureka注册中心,服务是否注册成功http://localhost:1111/

 

2.1.3  构建一个服务消费者

服务消费者其实同时也是一个服务提供者

 

 

1)添加依赖

  • <dependencies>
  • <dependency>
  • <groupId>org.springframework.boot</groupId>
  • <artifactId>spring-boot-starter-web</artifactId>
  • </dependency>
  • <!-- eureka-->
  • <dependency>
  • <groupId>org.springframework.cloud</groupId>
  • <artifactId>spring-cloud-starter-eureka</artifactId>
  • </dependency>
  • <!—ribbon客户端负载均衡-->
  • <dependency>
  • <groupId>org.springframework.cloud</groupId>
  • <artifactId>spring-cloud-starter-ribbon</artifactId>
  • </dependency>
  • <!-- 热部署 -->
  • <dependency>
  • <groupId>org.springframework.boot</groupId>
  • <artifactId>spring-boot-devtools</artifactId>
  • <optional>true</optional>
  • <scope>true</scope>
  • </dependency>
  • </dependencies>
  • <dependencyManagement>
  • <dependencies>
  • <dependency>
  • <groupId>org.springframework.cloud</groupId>
  • <artifactId>spring-cloud-dependencies</artifactId>
  • <version>Dalston.SR4</version>
  • <type>pom</type>
  • <scope>import</scope>
  • </dependency>
  • </dependencies>
  • </dependencyManagement>
  • <build>
  • <plugins>
  • <plugin>
  • <groupId>org.springframework.boot</groupId>
  • <artifactId>spring-boot-maven-plugin</artifactId>
  • <configuration>
  • <!--fork : 如果没有该项配置,肯呢个devtools不会起作用,即应用不会restart -->
  • <fork>true</fork>
  • </configuration>
  • </plugin>
  • </plugins>
  • </build>

 

2)添加application配置

  • spring.application.name=ribbon-consumer
  • server.port=9000
  • eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/

 

3)添加注解

@EnableEurekaClient

 

4)添加一个rest消费服务

@RestController

public class ConsumerController {

 

@Autowired

RestTemplate restTemplate;

 

@RequestMapping(value="/ribbon-consumer",method=RequestMethod.GET)

public String helloConsumer(){

return restTemplate.getForEntity("http://hello-service/hello", String.class).getBody();

}

}

(5)在配置类中注册bean,供消费者注入引用

@Bean

@LoadBalanced  //此注解表示使用负载均衡

public RestTemplate restTemplate(){

return new RestTemplate();

}

 

 

2.1.4  高可用服务注册中心

在注册中心项目中进行构建

  1. 创建application-peer1.properties作为peer1服务注册中心的配置

spring.application.name=eureka-server

server.port=1111

#eureka server

eureka.instance.hostname=peer1

eureka.client.serviceUrl.defaultZone=http://peer2:1112/eureka/

#关闭自我保护机制

eureka.server.enable-self-preservation=false

 

  1. 创建application-peer2.properties作为peer2服务注册中心的配置

spring.application.name=eureka-server

server.port=1112

#eureka server

eureka.instance.hostname=peer2

eureka.client.serviceUrl.defaultZone=http://peer1:1111/eureka/

#关闭自我保护机制

eureka.server.enable-self-preservation=false

 

  1. 修改hosts文件,添加

127.0.0.1 peer1

127.0.0.1 peer2

 

  1. 分别启动peer1注册中心和peer2注册中心

java -jar eureka-server-0.0.1-SNAPSHOT.jar --spring.profiles.active=peer1

java -jar eureka-server-0.0.1-SNAPSHOT.jar --spring.profiles.active=peer2

 

  1. 修改服务提供方(client)的application.properties

server.port=8088

spring.application.name=hello-service

eureka.client.serviceUrl.defaultZone=http://peer1:1111/eureka/,http://peer2:1112/eureka/

eureka.client.healthcheck.enabled = true

 

  1. 启动服务提供方,通过访问http://localhost:1111/http://localhost:1112/,可以观察到hello-service服务同时被注册到了peer1peer2上,若此时断开peer1,在peer2上的其他服务依然能访问到hello-service,从而实现了服务注册中心的高可用。
  2. 如果不想用主机名来定义注册中心地址,也可以使用Ip地址的形式,但需要在配置文件中增加配置参数eureka.instance.prefer-ip-address=true,该配置默认为false

 

 

 

2.2 Spring Cloud Ribbon

Ribbon负责客户端负载均衡

软负载均衡的实现方式有两种,分别是服务端的负载均衡和客户端的负载均衡

服务端负载均衡:当浏览器向后台发出请求的时候,会首先向反向代理服务器发送请求,反向代理服务器会根据客户端部署的ipport映射表以及负载均衡策略,来决定向哪台服务器发送请求,一般会使用到nginx反向代理技术。

客户端负载均衡:当浏览器向后台发出请求的时候,客户端会向服务注册器(例如:Eureka Server),拉取注册到服务器的可用服务信息,然后根据负载均衡策略,直接命中哪台服务器发送请求。这整个过程都是在客户端完成的,并不需要反向代理服务器的参与。

 

2.3 Spring Cloud Hystrix

服务容错保护

 

我们的家庭电路系统少不了保险丝。当电路发生故障或异常时,伴随着电流不断升高,并且升高的电流有可能损坏电路中的某些重要器件或贵重器件,也有可能烧毁电路甚至造成火灾。 
若电路中正确地安置了保险丝,那么保险丝就会在电流异常升高到一定的高度和热度的时候,自身熔断切断电流,从而起到保护电路安全运行的作用。 

当有个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个错误响应,而不是长时间等待。这样就不会使得线程因调用故障服务被长时间占着不释放,避免故障在分布式系统中蔓延。

针对上述问题,Spring Cloud Hystrix实现了断路器、线程隔离等一系列服务保护功能。它是基于Netflix的开源框架Hystrix实现的,该框架的目标在于通过控制那些访问远程系统、服务和第三方库的节点,从而对延迟和故障提供更强大的容错能力。Hystrix具备服务降级、服务熔断、线程和信号隔离、请求缓存、请求合并以及服务监控等强大功能。

2.4 Spring Cloud Feign 

Feign是一个声明式的web服务客户端,它使得写web服务变得更简单。使用Feign,只需要创建一个接口并注解。它具有可插拔的注解特性,包括Feign 注解和JAX-RS注解。Feign同时支持可插拔的编码器和解码器。spring cloud对Spring mvc添加了支持,同时在spring web中次用相同的HttpMessageConverter。当我们使用feign的时候,spring cloud 整和了Ribbon和eureka去提供负载均衡

2.5 Spring Cloud Zuul

前面的文章我们介绍了,Eureka用于服务的注册于发现,Feign支持服务的调用以及均衡负载,Hystrix处理服务的熔断防止故障扩散,Spring Cloud Config服务集群配置中心,似乎一个微服务框架已经完成了。

我们还是少考虑了一个问题,外部的应用如何来访问内部各种各样的微服务呢?在微服务架构中,后端服务往往不直接开放给调用端,而是通过一个API网关根据请求的url,路由到相应的服务。当添加API网关后,在第三方调用端和服务提供方之间就创建了一面墙,这面墙直接与调用方通信进行权限控制,后将请求均衡分发给后台服务端。

 

 

 

 

Zuul的主要功能是路由转发和过滤器。路由功能是微服务的一部分,比如/api/user转发到到user服务,/api/shop转发到到shop服务。zuul默认和Ribbon结合实现了负载均衡的功能。

功能:

1. 作为系统的统一入口,屏蔽了系统内部各个微服务的细节。

2. 可以与服务治理框架结合,实现自动化的服务实力维护以及负载均衡的路由转发。

3. 可以实现接口权限校验与微服务业务逻辑的解耦。

4. 通过服务网管中的过滤器,在各生命周期中去校验请求的内容,将原本在对外服务层做的校验前移,保证了微服务的无状态性,同时降低了微服务的测试难度,让服务本身更集中关注业务逻辑的处理。

 

2.5.1  服务路由

1)添加依赖

<dependencies>

<!-- spring cloud zuul -->

<dependency>

<groupId>org.springframework.cloud</groupId>

<artifactId>spring-cloud-starter-zuul</artifactId>

</dependency>

<!-- api gateway整合eureka,创建面向服务的路由 -->

<dependency>

<groupId>org.springframework.cloud</groupId>

<artifactId>spring-cloud-starter-eureka</artifactId>

</dependency>

</dependencies>

<dependencyManagement>

<dependencies>

<dependency>

<groupId>org.springframework.cloud</groupId>

<artifactId>spring-cloud-dependencies</artifactId>

<version>Dalston.SR4</version>

<type>pom</type>

<scope>import</scope>

</dependency>

</dependencies>

</dependencyManagement>

对于spring-cloud-starter-zuul依赖,可以通过查看它的依赖内容了解该模块中不仅包含了Netflix Zuul的核心以来zuul-core,还包含了下面这些网关服务需要的重要依赖。

spring-cloud-starter-hystrix: 该以来用来在网关服务中实现对服务转发时候的保护机制,通过线程隔离和断路器,防止微服务的故障引发API网关资源无法释放,从而影响其他应用的对外服务。

spring-cloud-starter-ribbon: 该依赖用来实现在网关服务进行路由转发时候的客户端负载均衡以及请求重试。

spring-boot-starter-actuator: 该依赖用来提供常规的微服务管理端点。另外,在Spring Cloud Zuul中还提供了/routes端点来返回当前的所有规则

 

 

 

2)添加application配置

spring.application.name=api-gateway

server.port=5555

#服务注册中心

eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/

#服务路由配置

zuul.routes.api-a.path=/api-a/**

zuul.routes.api-a.serviceId=hello-service

zuul.routes.api-b.path=/api-b/**

zuul.routes.api-b.serviceId=ribbon-consumer

#服务路由配置方式二

#zuul.routes.hello-service=/api-a/*

 

3)创建springboot启动类,并添加注释

@EnableZuulProxy

@SpringCloudApplication

public class ZuulApplication {

 

public static void main(String[] args) {

new SpringApplicationBuilder(ZuulApplication.class).run(args);

}

}

 

4)服务路由的默认规则

当我们为Spring Cloud Zuul构建的API网关服务引入Spring Cloud Eureka之后,zuul会为Eureka中的每个服务都自动创建一个默认路由规则,这些默认规则的path会使用serviceId配置的服务名作为请求前缀。相当于下面的配置

zuul.routes.api-a.path=/hello-service/**

zuul.routes.api-a.serviceId=hello-service

 

使用该默认规则,可以实现服务的动态注册

 

5)测试

http://localhost:5555/api-a/hello

 

 

 

2.5.2  请求过滤

在服务路由的项目基础上进行构建

1)创建用户名过滤器

import javax.servlet.http.HttpServletRequest;

 

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

 

import com.netflix.zuul.ZuulFilter;

import com.netflix.zuul.context.RequestContext;

 

public class AccessUserNameFilter extends ZuulFilter{

 

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

@Override

public Object run() {

RequestContext ctx=RequestContext.getCurrentContext();

HttpServletRequest request=ctx.getRequest();

logger.info("send {} request to {} ",request.getMethod(),request.getRequestURL().toString());

String accessToken=request.getParameter("username");

if(accessToken!=null&&accessToken.equals("zlt")){

ctx.setSendZuulResponse(true);

ctx.setResponseStatusCode(200);

ctx.set("isSuccess",true);//设值,让下一个Filter看到上一个Filter的状态

return null;

}else{

ctx.setSendZuulResponse(false);

ctx.setResponseStatusCode(401);

ctx.set("isSuccess",false);//设值,让下一个Filter看到上一个Filter的状态

return null;

}

}

 

@Override

public boolean shouldFilter() {

// TODO Auto-generated method stub

return true;// 是否执行该过滤器,此处为true,说明需要过滤

}

 

@Override

public int filterOrder() {

// TODO Auto-generated method stub

return 0;// 优先级为0,数字越大,优先级越低

}

 

@Override

public String filterType() {

// TODO Auto-generated method stub

return "pre";// 前置过滤器

}

 

}

 

2)创建密码过滤器

import javax.servlet.http.HttpServletRequest;

 

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

 

import com.netflix.zuul.ZuulFilter;

import com.netflix.zuul.context.RequestContext;

 

public class AccessPasswordFilter extends ZuulFilter{

 

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

@Override

public Object run() {

RequestContext ctx = RequestContext.getCurrentContext();

HttpServletRequest request = ctx.getRequest();

System.out.println(String.format("%s AccessPasswordFilter request to %s", request.getMethod(), request.getRequestURL().toString()));

String username = request.getParameter("password");

if(null != username && username.equals("123456")) {

ctx.setSendZuulResponse(true);

ctx.setResponseStatusCode(200);

ctx.set("isSuccess", true);

return null;

}else{

ctx.setSendZuulResponse(false);

ctx.setResponseStatusCode(401);

ctx.setResponseBody("{\"result\":\"password is not correct!\"}");

ctx.set("isSuccess", false);

return null;

}

}

 

@Override

public boolean shouldFilter() {

RequestContext ctx = RequestContext.getCurrentContext();

/* 如果前一个过滤器的结果为true,则说明上一个过滤器成功了,需要进入当前的过滤,

如果前一个过滤器的结果为false,则说明上一个过滤器没有成功,

则无需进行下面的过滤动作了,直接跳过后面的所有过滤器并返回结果*/

boolean flag=(boolean)ctx.get("isSuccess");

logger.info("&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& flag: "+flag);

return flag;

}

 

@Override

public int filterOrder() {

// TODO Auto-generated method stub

return 1;

}

 

@Override

public String filterType() {

// TODO Auto-generated method stub

return "pre";

}

 

}

 

3)为过滤器创建bean

在实现了自定义过滤器之后,并不会直接生效,还血药为其创建具体的Bean才能启动该过滤器

@Bean

public AccessUserNameFilter accessUserNameFilter(){

return new AccessUserNameFilter();

}

@Bean

public AccessPasswordFilter accessPasswordFilter(){

return new AccessPasswordFilter();

}

到此,过滤器创建成功

 

4)方法说明

在上面实现的过滤器代码中,我们通过集成ZuulFilter抽象类,并重写其4个方法来实现自定义过滤器。

filterType: 过滤器的类型,它决定过滤器的请求在那个生命周期中执行。

(1) PRE:这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。

(2) ROUTING:这种过滤器将请求路由到微服务。这种过滤器用于构建发送给微服务的请求,并使用Apache HttpClient或Netfilx Ribbon请求微服务。

(3) POST:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。

(4) ERROR:在其他阶段发生错误时执行该过滤器。

 

filterOrder:过滤器的执行顺序。当请求在一个阶段中存在多个过滤器时,需要根据该方法返回的值来一次执行。数字越大,优先级越低

shouldFilter:判断该过滤器是否需要被执行。这里我们直接返回了true,因此该过滤器对所有请求都会生效。实际运用中我们可以利用该函数来制定过滤器的有效范围。

run:过滤器的具体逻辑。可以通过ctx.setSendZuulResponse(false)令zuul过滤该请求,不对其进行路由。

 

2.5.3  过滤器动态注册

API网关集群,是spring cloud微服务架构的统一入口,过滤器的更新与添加会影响网关的对外服务,即使可以通过滚动添加,但每次更新或者添加都要滚动操作一遍,很不方便,欣慰的是spring cloud zuul提供了集成了groovy组件,可以通过监控目录来动态添加、更新过滤器。

该部分还未验证通过,暂不做详细说明。

 

2.6 Spring Cloud Config 

配置中心

 

Spring Cloud Config提供了在分布式系统的外部配置的客户端支持。通过配置服务(Config Server)来为所有的环境和应用提供外部配置的集中管理。这些概念都通过Spring的Environment和PropertySource来抽象,所以它可以适用于各类Spring应用,同事支持任何语言的任何应用。它也能为你支持对应用开发环境、测试环境、生产环境的配置、切换、迁移。默认的配置实现通过git实现,同事非常容易的支持其他的扩展(比如svn等)。

2.7 Spring Cloud Bus

消息总线

微服务架构的系统中,通常会使用轻量级的消息代理来构建一个公用的消息主题,让系统中所有为服务实力都连接上来,由于该主题中产生的消息会被所有实例监听和消费,所以我们称它为消息总线。在消息总线上的各个实例都可以很方便地广播一些需要让其他连接在该主题上的实例都知道的消息,例如配置信息的变更或者其他一些管理操作等。

例如:

SpringCloudConfig分服务端和客户端,服务端负责将git(svn)中存储的配置文件发布成REST接口,客户端可以从服务端REST接口获取配置。但客户端并不能主动感知到配置的变化,从而主动去获取新的配置,这需要每个客户端通过POST方法触发各自的/refreshSpringCloudBus提供了通过POST方法访问的endpoint/bus/refresh,这个接口通常由git的钩子功能调用,用以通知各个SpringCloudConfig的客户端去服务端更新配置。

 

2.8 Spring Cloud Stream 

消息驱动的微服务

使用Spring Cloud Stream可以有效简化开发人员对消息中间件的使用复杂度,让开发人员可以有更多的精力关注核心业务逻辑的处理。可以基于SpringBoot来创建独立的、可用于生产的Spring应用程序。

Spring Cloud Stream目前只支持下面两个消息中间件的自动化配置:

rabbitMQ

Kafka

2.9  Spring Cloud Sleuth

微服务架构是一个分布式架构,它按业务划分服务单元,一个分布式系统往往有很多个服务单元。由于服务单元数量众多,业务的复杂性,如果出现了错误和异常,很难去定位。主要体现在,一个请求可能需要调用很多个服务,而内部服务的调用复杂性,决定了问题难以定位。所以微服务架构中,必须实现分布式链路追踪,去跟进一个请求到底有哪些服务参与,参与的顺序又是怎样的,从而达到每个请求的步骤清晰可见,出了问题,很快定位

针对上面所属的跟踪问题,sleuth提供了一套完整的解决方案。

posted @ 2017-11-14 15:21  地球其实挺好  阅读(993)  评论(0编辑  收藏  举报