SpringCloud

Spring Cloud

1. 引言

微服务架构的4个核心问题

  • 服务很多,客户端该怎么访问?
  • 服务之间如何通信?
  • 如何治理服务?
  • 服务挂了怎么办?

解决方案
Spring Cloud 是一个生态

  1. Spring Cloud Netflix 一站式解决方案
    • API网关,zuul组件
    • Feign — HttpClient — Http通信方式,同步,阻塞
    • 服务注册发现:Eureka
    • 熔断机制:Hystrix
  2. Apache Dubbo Zookeeper 半自动,需要整合别人的
    • API : 找第三方或自己实现
    • Dubbo
    • Zookeeper
    • 借助Hystrix
  3. Spring Cloud Alibaba 一站式解决方案

总是需要解决4个

  • API
  • HTTP,RPC
  • 注册和发现
  • 熔断机制

2. 微服务概述

2.1 什么是微服务

  • 微服务化的核心就是将传统的一站式应用,根据业务拆分成一个一个的服务,彻底的去耦合,每一个微服务提供单个业务功能的服务,一个服务做一件事情,从技术角度看就是一种小而独立的处理过程,类似进程的概念,能够自行单独启动或销毁,拥有自己独立的数据库。

2.2 微服务与微服务架构

微服务
强调的是服务的大小,关注的是某一个点,是具体解决某一个问题/提供落地对应服务的一个服务应用,狭义的看,可以看做IDEA中的一个个微服务工程,或者Moudel

微服务架构
它是一种架构模式,提倡将单一应用程序划分成一组小的服务,服务之间互相协调,互相配合,为用户提供最终价值。每个服务运行在其独立的进程中,服务间采用轻量级的通信机制互相协作,每个服务都围绕着具体的业务进行构建,并且能够被独立的部署到生产环境中,另外,应尽量避免统一的,集中式的服务管理机制,对具体的一个服务而言,应根据业务上下文,选择合适的语言,工具进行架构。

2.3 微服务优缺点

优点

  • 单一职责原则
  • 每个服务足够内聚,足够小,代码容易理解,这样能聚焦一个指定的业务功能
  • 开发简单,开发效率高
  • 是松耦合的,是有功能意义的服务,无论是在开发阶段或部署阶段都是独立的
  • 能使用不同的语言开发
  • 易于第三方集成,允许容易且灵活的方式集成自动部署
  • 微服务知识业务逻辑的代码,不会和HTML,CSS或其他界面混合
  • 每个微服务都有自己的存储能力,也可以有自己的数据库,也可以统一数据库

缺点

  • 开发人员要处理分布式系统的复杂性
  • 多服务运维难度
  • 服务间通信成本
  • 数据一致性
  • 系统集成测试
  • 性能监控
  • 系统部署依赖

2.4 服务技术栈

微服务条目落地技术
服务开发SpringBoot,Spring,SpringMVC
服务配置与管理Netflix的Archaius、阿里的Diamond
服务注册于发现Eureka、Consul、Zookeeper
服务调用Rest、RPC、gRPC
服务熔断器Hystrix、Envoy
负载均衡Ribbon、Nginx
服务接口调用(客户端调用服务的简化接口)Feign
消息队列Kafka、RabbitMQ、ActiveMQ
服务配置中心管理SpringCloudConfig、Chef
服务路由(API网关)Zuul
服务监控Zabbix、Nagios、Metrics、Specatator
全链路追踪Zipkin、Brave、Dapper
服务部署Docker、OpenStack、Kubernetes
数据流操作开发包SpringCloud Stream(封装Redis、Rabbit、Kafka等发送接收消息)
事件消息总线SpringCloud Bus

3. SpringCloud概述

3.1 SpringCloud是什么

它是基于SpringBoot提供了一套微服务解决方案,包括服务注册与发现,配置中心,全链路监控,服务网关,负载均衡,熔断器等组件。简化了分布式系统基础设施的开发,提供了快速构建分布式系统的一些工具,包括配置管理,服务发现,断路器,路由,微代理,事件总线,全局锁,决策竞选,分布式会话等。最终给开发者留出一套简单易懂,易部署和易维护的分布式系统开发工具包。

3.2 SpringBoot和SpringCloud的关系

  • SpringBoot专注于快速方便的开发单个个体微服务
  • SpringCloud是关注全局的微服务协调整理治理框架,它将SpringBoot开发的一个个单体微服务整合并管理起来,为各个微服务之间提供:配置管理,服务发现,断路器,路由,微代理,事件总线,全局锁,决策竞选,分布式会话等等集成服务。
  • SpringBoot可以离开SpringCloud独立使用,开发项目,但是SpringCloud离不开SpringBoot,属于依赖关系
  • SpringBoot专注于快速方便的开发单个个体微服务,SpringCloud关注全局的服务治理框架

4. Rest环境搭建

4.1 总体介绍

  • 使用一个Dept部门模块做一个微服务通用案例Consumer消费者(Client)通过REST调用Provider提供者(Server)提供的服务。
  • Maven的分包分模块架构
  • 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>

    <groupId>com.kuang</groupId>
    <artifactId>springcloud</artifactId>
    <version>1.0-SNAPSHOT</version>
    <modules>
        <module>springcloud-api</module>
        <module>springcloud-provider-dept-8001</module>
        <module>springcloud-consumer-dept-80</module>
    </modules>

    <!--打包方式 pom-->
    <packaging>pom</packaging>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <junit.version>4.12</junit.version>
        <lombok.version>1.16.12</lombok.version>
        <log4j.version>1.2.17</log4j.version>
        <logback.version>1.2.3</logback.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <!--springcloud依赖-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Greenwich.SR1</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <!--SpringBoot-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>2.1.5.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <!--数据库-->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>5.1.46</version>
            </dependency>
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid</artifactId>
                <version>1.1.10</version>
            </dependency>

            <!--SpringBoot启动器-->
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>1.3.2</version>
            </dependency>

            <!--日志测试-->
            <!--junit-->
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>${junit.version}</version>
            </dependency>

            <!--lombok-->
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>${lombok.version}</version>
            </dependency>

            <!--log4j-->
            <dependency>
                <groupId>log4j</groupId>
                <artifactId>log4j</artifactId>
                <version>${log4j.version}</version>
            </dependency>

            <!--logback-->
            <dependency>
                <groupId>ch.qos.logback</groupId>
                <artifactId>logback-core</artifactId>
                <version>${logback.version}</version>
            </dependency>

        </dependencies>
    </dependencyManagement>

</project>

4.2 数据库

create table dept(
  deptno bigint primary key not null auto_increment,
  dname varchar(60),
  db_source varchar (60)
);
alter table dept comment = '部门表';

insert into dept (dname,db_source) values('开发部',DATABASE());
insert into dept (dname,db_source) values('人事部',DATABASE());
insert into dept (dname,db_source) values('财务部',DATABASE());
insert into dept (dname,db_source) values('市场部',DATABASE());
insert into dept (dname,db_source) values('运维部',DATABASE());

4.3 SpringCloud-api

  • pom.xml
    <!--当前的Module自己需要的依赖,如果父依赖中已经配置了版本,这里就不用写了-->
    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>
  • Dept类
@Data
@NoArgsConstructor
@Accessors(chain = true) //链式写法 dept.set(xx).set(xx)..
public class Dept implements Serializable {

    private Long deptno;
    private String dname;
    private String db_source;

    public Dept(String dname) {
        this.dname = dname;
    }
}

4.4 SpringCloud-provider-dept-8001

4.4.1 main/java
  • pom.xml
    <dependencies>
        <!--我们需要拿到实体类,配置api module-->
        <dependency>
            <groupId>com.kuang</groupId>
            <artifactId>springcloud-api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
        </dependency>

        <!--test-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-test</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--jetty-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jetty</artifactId>
        </dependency>
        <!--热部署-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
        </dependency>
    </dependencies>
  • dao层DeptDao接口
@Mapper
@Repository
public interface DeptDao {

    public boolean addDept(Dept dept);

    public Dept queryById(long id);

    public List<Dept> queryAll();
}
  • service层DeptService接口
public interface DeptService {

    public boolean addDept(Dept dept);

    public Dept queryById(long id);

    public List<Dept> queryAll();
}
  • service层DeptServiceImpl类
@Service
public class DeptServiceImpl implements DeptService {

    @Autowired
    private DeptDao deptDao;

    @Override
    public boolean addDept(Dept dept) {
        return deptDao.addDept(dept);
    }

    @Override
    public Dept queryById(long id) {
        return deptDao.queryById(id);
    }

    @Override
    public List<Dept> queryAll() {
        return deptDao.queryAll();
    }
}
  • controller层DeptController类
@RestController

public class DeptController {

    @Autowired
    private DeptService deptService;

    @PostMapping("dept/add")
    public boolean addDept(Dept dept){
        return deptService.addDept(dept);
    }

    @GetMapping("dept/get/{id}")
    public Dept get(@PathVariable("id") Long id){
        return deptService.queryById(id);
    }

    @GetMapping("dept/list")
    public List<Dept> queryAll(){
        return deptService.queryAll();
    }
}
4.4.2 main/resource
  • application.yaml
server:
  port: 8001

mybatis:
  type-aliases-package: com.kuang.springcloud.pojo

  config-location: classpath:mybatis/mybatis-config.xml
  mapper-locations: classpath:mybatis/mapper/*.xml

spring:
  application:
    name: springcloud-provider-dept
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/DB01?useUnicode=true&characterEncoding=utf-8
    username: root
    password: 
  • mybatis/mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>
    <settings>
        <!--开启二级缓存-->
        <setting name="cacheEnabled" value="true"/>
    </settings>
</configuration>
  • mybatis/mapper/DeptMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.kuang.springcloud.dao.DeptDao">
    <insert id="addDept" parameterType="com.kuang.springcloud.pojo.Dept">
        insert into dept(dname, db_source)
        values (#{dname}, DATABASE())
    </insert>
    
    <select id="queryById" resultType="com.kuang.springcloud.pojo.Dept" parameterType="Long">
        select * from dept where deptno = #{deptno};
    </select>

    <select id="queryAll" resultType="com.kuang.springcloud.pojo.Dept">
        select * from dept;
    </select>
</mapper>
4.4.3 DeptProvider_8001
@SpringBootApplication
public class DeptProvider_8001 {
    public static void main(String[] args) {
        SpringApplication.run(DeptProvider_8001.class,args);
    }
}

4.5 springcloud-consumer-dept-80

4.5.1 main/java
  • pom.xml
    <!--实体类+web-->
    <dependencies>
        <dependency>
            <groupId>com.kuang</groupId>
            <artifactId>springcloud-api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--热部署-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
        </dependency>
    </dependencies>
  • config/ConfigBean类
@Configuration
public class ConfigBean {
    @Bean
    public RestTemplate getRestTemplate(){
        return new RestTemplate();
    }
}
  • controller/DeptConsumerController类
@RestController
public class DeptConsumerController {

    //理解:消费者,不应该有Service层
    //RestTemplate:有很多方法供我们直接调用,注册到Spring中
    //(url,实体:map,Class<T> responseType)
    @Autowired
    private RestTemplate restTemplate;//提供多种便捷访问远程http服务的方法,简单的restful服务模板

    private static final String REST_URL_PREFIX = "http://localhost:8001";

    @RequestMapping("/consumer/dept/add")
    public boolean add(Dept dept){
        return restTemplate.postForObject(REST_URL_PREFIX+"/dept/add",dept,Boolean.class);
    }

    @RequestMapping("/consumer/dept/get/{id}")
    public Dept get(@PathVariable("id") Long id){
        return restTemplate.getForObject(REST_URL_PREFIX+"/dept/get/"+id, Dept.class);
    }

    @RequestMapping("/consumer/dept/list")
    public List<Dept> list(){
        return restTemplate.getForObject(REST_URL_PREFIX+"/dept/list",List.class);
    }

}
4.5.2 main/resource
server:
  port: 80
4.5.3 DeptConsumer_80
@SpringBootApplication
public class DeptConsumer_80 {
    public static void main(String[] args) {
        SpringApplication.run(DeptConsumer_80.class,args);
    }
}

4.6 测试

开启80和8001两个服务,分别测试端口
http://localhost/consumer/dept/list
http://localhost/8001/dept/list

5. Eureka服务注册与发现

5.1 什么是Eureka

  • Netflix在设计Eureka时遵循AP原则
  • Eureka是Netflix的一个核心模块,是一个基于REST的服务,用于定位服务,以实现云端中间层服务发现和故障转移,服务注册与发现对于微服务来说是非常重要的,有了服务发现与注册,只需要使用服务的标识符,就可以访问到服务,而不需要修改服务调用的配置文件,功能类似于Dubbo的注册中心,如Zookeeper。

5.2 原理

  • 基本架构

    • SpringCloud封装了Netflix公司开发的Eureka模块来实现服务注册和发现
    • 采用了cs的架构设计,EurekaServer作为服务注册功能的服务器,它是服务注册中心
    • 而系统中的其他微服务,使用Eureka的客户端连接到EurekaServer并维持心跳连接。这样系统的维护人员就可以通过EurekaServer来监控系统中各个微服务是否正常运行,SpringCloud的一些其他模块(比如Zuul)就可以通过EurekaServer来发现系统中的其他微服务,并执行相关的逻辑
    • 包含两个组件:Eureka Server 和Eureka Client
    • Eureka Server提供服务注册服务,各个节点启动后,会在EurekaServer中进行注册,这样EurekaServer中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观的看到。
    • Eureka Client是一个java客户端,用于简化EurekaServer的交互,客户端同时也具备一个内置的,使用轮询负载算法的负载均衡器。在应用启动后,将会向EurekaServer发送心跳(默认周期为30秒)。如果EurekaServer在多个心跳周期内没有接收到某个节点的心跳,EurekaServer将会从服务注册表中把这个服务节点移除掉(默认周期为90秒)。
  • 三大角色

    • Eureka Server: 提供服务的注册于发现
    • Service Provider:将自身服务注册到Eureka中,从而使消费方能够找到
    • Service Consumer:服务消费方从Eureka中获取注册服务列表,从而找到消费服务
  • 自我保护机制

    • 某时刻某个微服务不可用了,Eureka不会立即清理,会对该微服务的信息进行保存。

5.3 springcloud-eureka-7001

  • pom.xml
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
    <version>1.4.6.RELEASE</version>
</dependency>
  • application.yaml
server:
  port: 7001
eureka:
  instance:
    hostname: localhost #Eureka服务端的实例名称
  client:
    register-with-eureka: false #表示是否向Eureka注册中心注册自己
    fetch-registry: false #如果为false则表示自己为注册中心
    service-url: # 监控页面
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
  • EurekaServer_7001启动类
@SpringBootApplication
@EnableEurekaServer //服务端的启动类,可以接收别人注册进来
public class EurekaServer_7001 {
    public static void main(String[] args) {
        SpringApplication.run(EurekaServer_7001.class,args);
    }
}
  • 访问 http://localhost:7001/ 是否成功

5.4 修改SpringCloud-provider-dept-8001

  • 添加pom.xml
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-eureka</artifactId>
    <version>1.4.6.RELEASE</version>
</dependency>
<!--完善监控信息-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
  • 添加application.yaml
# 服务注册到哪里
eureka:
  client:
    service-url:
      defaultZone: http://localhost:7001/eureka/

# 配置信息
info:
  app.name: kuang
  company.name: cwq
  • 启动类添加@EnableEurekaClient注解,@EnableDiscoveryClient(扩展部分用到的)

  • 先启动7001,再启动8001,访问http://localhost:7001/ 就能看到已经注册进去了,点注册的微服务,就能看到配置信息

  • 扩展部分,controller里加上,可访问http://localhost:8001/dept/discovery

//注册进来的微服务,获取一些消息
@RequestMapping("/dept/discovery")
public Object discovery(){
    //获取微服务列表的清单
    List<String> services = client.getServices();
    System.out.println("discovery=>services:"+services);

    //得到一个具体的微服务信息,通过具体的微服务id,applicationName;
    List<ServiceInstance> instances = client.getInstances("SPRINGCLOUD-PROVIDER-DEPT");
    for (ServiceInstance instance : instances) {
        System.out.println(
                instance.getHost()+"\t"+
                instance.getPort()+"\t"+
                instance.getUri()+"\t"+
                instance.getServiceId()
                );
    }
    return this.client;
}

输出:
discovery=>services:[springcloud-provider-dept]
192.168.124.9	8001	http://192.168.124.9:8001	SPRINGCLOUD-PROVIDER-DEPT

5.4 集群环境配置

  • 新建module,7002和7003,并复制7001的文件
  • mac下搞个域名映射,其实都是主机名,但是要体现出不同机器,有集群的感觉
cwq@cwqdeMacBook-Pro ~ % sudo vim /etc/hosts

加三行
127.0.0.1  eureka7001.com
127.0.0.1  eureka7002.com
127.0.0.1  eureka7003.com

127.0.0.1  www.cwq.com
  • 修改yaml文件

7001,2,3的

server:
  port: 7001
eureka:
  instance:
    hostname: eureka7001.com #Eureka服务端的实例名称
  client:
    register-with-eureka: false #表示是否向Eureka注册中心注册自己
    fetch-registry: false #如果为false则表示自己为注册中心
    service-url:
      # 单机情况 defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
      # 集群:
      defaultZone: http://eureka7002.com:7002/eureka/, http://eureka7003.com:7003/eureka/

8001的

# 服务注册到哪里
eureka:
  client:
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
  instance:
    instance-id: springcloud-provider-dept8001 #修改Eureka的默认描述信息
  • 就可以分别访问7001,7002,7003

5.5 CAP原则

  • C(Consistency)强一致性
  • A(Availability)可用性
  • P(Partition tolerance)分区容错性

CAP的三进二,CA、AP、CP

CAP理论的核心

  • 一个分布式系统不可能同时很好的满足一致性,可用性和分区容错性这三个需求
  • 根据CAP原理,将NoSQL数据库分成了满足CA原则,满足CP原则和满足AP原则三大类:
    • CA:单点集群,可扩展性较差
    • CP:性能不是特别高
    • AP:通常可能对一致性要求低一些

Eureka保证的是AP
可以容忍注册中心返回的是几分钟以前的注册信息,但不能接受服务挂掉。

Zookeeper保证的是CP
Eureka各个节点都是平等的,几个节点挂掉不影响工作

6. 负载均衡及Ribbon

6.1 Ribbon

Ribbon是什么

  • SpringCloud Ribbon 是基于Netflix Ribbon实现的一套客户端负载均衡的工具
  • 简单地说,Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡的算法,将Netflix的中间层服务连接在一起。Ribbon的客户端组件提供一系列完整的配置项如:连接超时,重试等等。就是在配置文件列出LoadBalancer(LB:负载均衡)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器。

Ribbon能干嘛

  • LB,即负载均衡,在微服务或分布式集群中经常用的一种应用。
  • 负载均衡简单地说就是将用户的请求平摊的分配到多个服务商,从而达到系统的HA(高可用)
  • 常见的负载均衡软件有Nginx,Lvs等
  • SpringCloud的负载均衡算法可以自定义
  • 负载均衡分类:
    • 集中式LB:即在服务的消费方和提供方之间使用独立的LB设施,如Nginx,由该设施负责把访问请求通过某种策略转发至服务的提供方。
    • 进程式LB:将LB逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后再从这些地址选择一个合适的服务器。Ribbon就属于进程内LB,它只是一个类库,集成于消费方进程,消费方通过它来获取到服务提供方的地址。

6.2 springcloud-consumer-dept-80

  • 修改pom.xml
<!--Ribbon-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-ribbon</artifactId>
    <version>1.4.6.RELEASE</version>
</dependency>
<!--Eureka-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-eureka</artifactId>
    <version>1.4.6.RELEASE</version>
</dependency>
  • 修改yaml
eureka:
  client:
    register-with-eureka: false # 不向eureka注册自己
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
  • 修改ConfigBean
@Configuration
public class ConfigBean {

    //配置负载均衡实现RestTemplate
    @Bean
    @LoadBalanced //Ribbon
    public RestTemplate getRestTemplate(){
        return new RestTemplate();
    }

}
  • 修改controller
//通过Ribbon实现,我们的地址应该改为变量,通过服务名来访问
//private static final String REST_URL_PREFIX = "http://localhost:8001";
private static final String REST_URL_PREFIX = "http://SPRINGCLOUD-PROVIDER-DEPT";
  • 结论

    Ribbon和Eureka整合以后,客户端可以直接调用,不用关心ip地址和端口号

6.4 再添加相同的8002,8003

将8001复制创建8002,8003,修改一些名字就行

这时候同时开启Eureka集群7001,7002,7003;开启消费者80;开启服务提供者8001,8002,8003

从 http://localhost/consumer/dept/list 访问;刷新就能看到每次使用的服务提供者有可能是8001,2,3;使用的数据库也会从DB01,2,3相对应的更改。说明消费者每次访问—从Eureka集群中查询可用的服务列表—服务提供者8001,2,3;就使用了负载均衡来选择不同的服务提供者。

6.5 自定义负载均衡算法

  • IRule接口
  • RoundRobinRule 轮询
  • RoundRule 随机
  • Retry 会先按照轮询获取服务,如果失败,会在指定的时间内进行重试
  • AvailabilityFilteringRule:会过滤掉,跳闸,访问故障的服务,对剩下的轮询

7. Feign负载均衡

7.1 简介

feign是声明式的web service客户端,它让微服务之间的调用变得简单了,类似controller调用service,SpringCloud集成了Ribbon和Eureka,可在使用Feign时提供负载均衡的http客户端。只需要创建一个接口,然后添加注解。

  • 调用微服务访问的两种方法
    • 微服务名字:Ribbon
    • 接口和注解:Feign

Feign能干什么

  • 旨在使编写Java Http客户端变得更容易
  • 前面在使用Ribbon+RestTemplate时,利用RestTemplate对Http请求的封装处理,形成了一套模板化的调用方法。但在实际开发中,由于对服务依赖的调用可能不止一处,往往一个接口会被多出调用,所以通常都会针对每个微服务自行封装一些客户端类来包装这些依赖服务的调用。所以,Feign在此基础上做了进一步封装,由它来帮助我们定义和实现依赖服务接口的定义,==在Feign的实现下,我们只需要创建一个接口并使用注解的方式来配置它(类似于以前Dao接口上标注Mapper注解,现在是一个微服务接口上面标注一个Feign注解即可。)==这样完成对服务提供方的接口绑定,简化了使用SpringCloud Ribbon时,自动封装服务调用客户端的开发量。

7.2 修改springcloud-api

  • 修改pom.xml
<!--Feign-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-feign</artifactId>
    <version>1.4.6.RELEASE</version>
</dependency>
  • 增加一个service包
@Component
@FeignClient(value = "SPRINGCLOUD-PROVIDER-DEPT")
public interface DeptClientService {

    @GetMapping("/dept/get/{id}")
    public Dept queryById(@PathVariable("id") Long id);

    @GetMapping("/dept/list")
    public List<Dept> queryAll();

    @PostMapping("/dept/add")
    public boolean addDept(Dept dept);
}

7.3 增加module,springcloud-provider-dept-feign

  • 大部分复制springcloud-provider-dept-80
  • pom.xml加上feign
  • controller改一下,不再使用RestTemplate
@RestController
public class DeptConsumerController {

    @Autowired
    private DeptClientService service = null;

    @RequestMapping("/consumer/dept/add")
    public boolean add(Dept dept){
        return this.service.addDept(dept);
    }

    @RequestMapping("/consumer/dept/get/{id}")
    public Dept get(@PathVariable("id") Long id){
        return this.service.queryById(id);
    }

    @RequestMapping("/consumer/dept/list")
    public List<Dept> list(){
        return this.service.queryAll();
    }

}
  • 启动类改一下
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients(basePackages = {"com.kuang.springcloud"})
public class FeignDeptConsumer_80 {
    public static void main(String[] args) {
        SpringApplication.run(FeignDeptConsumer_80.class,args);
    }
}
  • 全部启动就可以用feign来通过接口实现负载均衡

8. 服务熔断Hystrix

8.1 Hystrix

分布式系统面临的问题
复杂分布式体系结构中的应用程序有数十个依赖关系,每个依赖关系在某些时候将不可避免的失败

服务雪崩
多个微服务之间调用的时候,假设微服务A调用微服务B和微服务c,微服务B和微服务C
又调用其他的微服务。这就是所谓的“扇出”、如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,所谓的雪崩。

对于高流量的应用来说,单一的后端依赖可能会导致所有的服务器上的所有资源都在几秒钟内饱和。比失败更糟糕的是,这些应用程序还可能导致服务之间的延迟增加,备份队列,线程和其他系统资源紧张,导致整个系统发生更多的级联故障,这些都表示需要对故障进行隔离和管理,以便单个依赖关系的失败,不能取消整个应用程序或系统。

什么是Hystrix
Hystrix是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败。比如超时,异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。
*“断路器”本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控,向调用方法返回一个服务预期的,可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方法无法处理的异常,这样就可以保证了服务调用方的线程不会被长时间,不必要的占用,*从而避免了故障在分布式系统中的蔓延,乃至雪崩。

能干嘛

  • 服务降级
  • 服务熔断
  • 服务限流
  • 接近实时的监控
  • 。。。

服务熔断
熔断机制是对应雪崩效应的一种微服务链路保护机制。
当扇出链路的某个微服务不可用或者响应时间太长时,会进行服务的降级。==进而熔断该节点微服务的调用,快速返回错误的响应信息。==当检测到该节点微服务调用响应正常后恢复调用链路。在SpringCloud框架里熔断机制通过Hystrix实现。Hystrix会监控微服务调用的状况,当失败的调用到一定阈值,缺省是5秒内20次调用失败就会启动熔断机制。注解是@HystrixCommand

8.2 服务熔断,新增springcloud-provider-dept-hystrix-8001

与服务提供方有关

  • 复制springcloud-provider-dept–8001创建springcloud-provider-dept-hystrix-8001
  • 增加pom.xml
<!--Hystrix-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-hystrix</artifactId>
    <version>1.4.6.RELEASE</version>
</dependency>
  • yaml
instance-id: springcloud-provider-dept-hystrix-8001 #修改Eureka的默认描述信息
prefer-ip-address: true #可以显示服务的ip地址,就不是localhost了(不需要)
  • 修改DeptController,在熔断时有备选方案
@RestController
public class DeptController {

    @Autowired
    private DeptService deptService;

    @GetMapping("/dept/get/{id}")
    @HystrixCommand(fallbackMethod = "hystrixGet")
    public Dept get(@PathVariable("id") Long id){
        Dept dept = deptService.queryById(id);

        if(dept == null){
            throw new RuntimeException("id=>"+id+",不存在该用户,或信息无法找到");
        }

        return dept;
    }

    //备选方法
    public Dept hystrixGet(@PathVariable("id") Long id){
        return new Dept().setDeptno(id)
                .setDname("id=>"+id+"没有对应的信息,null--@Hystrix")
                .setDb_source("no this database in MySQL");
    }

}
  • 启动类添加注解@EnableCircuitBreaker //添加对熔断的支持
  • 测试http://localhost:8001/dept/get/6
    因为找不到数据,会返回备选方案提供的信息,{“deptno”:6,“dname”:“id=>6没有对应的信息,null–@Hystrix”,“db_source”:“no this database in MySQL”}

8.3 服务降级,修改

与客户端有关
当某个服务端要处理高并发的情况下,需要断开其他低并发的服务端,从而处理这些断开服务端的客户端需求

  • 修改springcloud-api,增加service
    • DeptClientService
@FeignClient(value = "SPRINGCLOUD-PROVIDER-DEPT", fallbackFactory = DeptClientServiceFallbackFactory.class)
* DeptClientServiceFallbackFactory
//降级
@Component
public class DeptClientServiceFallbackFactory implements FallbackFactory {

    @Override
    public DeptClientService create(Throwable throwable) {
        return new DeptClientService() {
            @Override
            public Dept queryById(Long id) {
                return new Dept().setDeptno(id)
                        .setDname("id=>"+id+"没有对应的信息,客户端提供了降级的信息,这个服务已被关闭")
                        .setDb_source("没有数据");
            }

            @Override
            public List<Dept> queryAll() {
                return null;
            }

            @Override
            public boolean addDept(Dept dept) {
                return false;
            }
        };
    }
}
  • 修改springcloud-provider-dept-feign
    • 修改yaml
# 开启降级
feign:
  hystrix:
    enabled: true
  • 测试开启7001,8001,feign-80
    • 访问http://localhost/consumer/dept/get/3
    • 当出现故障需要断开8001时,会自动对服务降级

8.4 总结

  • 服务熔断:服务端,某个服务超时或异常,引起熔断

    • 比如8.2的例子,就是7001,hystrix-8001,feign-80开着,如果hystrix-8001出现问题,找不到值;从服务端返回备选方案
  • 服务降级:客户端,从整体网站请求负载考虑,当某个服务熔断或者关闭之后,服务将不再被调用,此时在客户端可以准备一个FallbackFactory,返回一个缺省值;服务水平下降了,但是能用

    • 比如8.3的例子,就是7001,8001,feign-80开着,如果断开8001,会从客户端返回缺省值

8.5 Hystrix:Dashboard流监控

新增springcloud-consumer-hystrix-dashboard

  • pom.xml
    <!--实体类+web-->
    <dependencies>

        <!--Hystrix-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-hystrix</artifactId>
            <version>1.4.6.RELEASE</version>
        </dependency>
        <!--监控-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
            <version>1.4.6.RELEASE</version>
        </dependency>

        <!--Ribbon-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-ribbon</artifactId>
            <version>1.4.6.RELEASE</version>
        </dependency>
        <!--Eureka-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
            <version>1.4.6.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>com.kuang</groupId>
            <artifactId>springcloud-api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--热部署-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
        </dependency>
    </dependencies>
  • DeptConsumerDashboard_9001
@SpringBootApplication
@EnableHystrixDashboard //开启监控
public class DeptConsumerDashboard_9001 {
    public static void main(String[] args) {
        SpringApplication.run(DeptConsumerDashboard_9001.class,args);
    }
}
  • yaml
server:
  port: 9001

修改8001的启动类

//增加一个Servlet
@Bean
public ServletRegistrationBean hystrixMetricsStreamServlet(){
    ServletRegistrationBean registrationBean = new ServletRegistrationBean(new HystrixMetricsStreamServlet());
    registrationBean.addUrlMappings("/actuator/hystrix.stream");
    return registrationBean;
}

开启7001,9001,8001

  • 访问http://localhost:9001/hystrix
  • 访问http://localhost:8001/actuator/hystrix.stream
  • 将http://localhost:8001/actuator/hystrix.stream 写入 http://localhost:9001/hystrix ,设定监控时间,访问 http://localhost:8001/dept/get/1 的时候,就可以看到实时监控页面

9. Zuul路由网关

9.1 Zuul

什么是Zuul

Zuul包含了对请求的路由和过滤两个最主要的功能

其中路由功能负责将外部请求转发到具体的微服务实例上,是实现外部访问统一入口的基础,而过滤器功能则负责对请求的处理过程进行干预,是实现请求校验,服务聚合等功能的基础。Zuul和Eureka进行整合,将Zuul自身注册为Eureka服务治理下的应用,同时从Eureka中获得其他微服务的消息,即以后的访问微服务都是通过Zuul跳转后获得。

注意:Zuul服务最终还是会注册进Eureka

提供:代理+路由+过滤

9.2 新建springcloud-zuul-9527

  • pom.xml
    <!--实体类+web-->
    <dependencies>

        <!--Zuul-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zuul</artifactId>
            <version>1.4.6.RELEASE</version>
        </dependency>

        <!--Hystrix-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-hystrix</artifactId>
            <version>1.4.6.RELEASE</version>
        </dependency>
        <!--监控-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
            <version>1.4.6.RELEASE</version>
        </dependency>

        <!--Ribbon-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-ribbon</artifactId>
            <version>1.4.6.RELEASE</version>
        </dependency>
        <!--Eureka-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
            <version>1.4.6.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>com.kuang</groupId>
            <artifactId>springcloud-api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--热部署-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
        </dependency>
    </dependencies>
  • yaml
server:
  port: 9527

spring:
  application:
    name: springcloud-zuul

eureka:
  client:
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
  instance:
    instance-id: zuul9527.com
    prefer-ip-address: true


info:
  app.name: kuang
  company.name: cwq

# 修改访问微服务的名字
zuul:
  routes:
    mydept.serviceId: springcloud-provider-dept
    mydept.path: /mydept/**
    ignored-services: springcloud-provider-dept #不能再使用这个路径访问
    # ignored-services: "*" #隐藏全部
    # prefix: /cwq #设置公共前缀
  • 启动类
@SpringBootApplication
@EnableZuulProxy
public class ZuulApplication_9527 {
    public static void main(String[] args) {
        SpringApplication.run(ZuulApplication_9527.class,args);
    }
}
  • 域名映射一下www.cwq.com为127.0.0.1

  • 访问

    • http://www.cwq.com:9527/mydept/dept/get/1
    • http://www.cwq.com:9527/springcloud-provider-dept/dept/get/1 (修改之后不能再用这个路径访问)
    • 这样彻底使用自己定义的域名,微服务名,隐藏了信息
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值