springcloud与微服务

springcloud 与 微服务

image-20200415190126813

父工程(按需导入依赖)

<!--打包-->
    <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.13</junit.version>
        <lombok.version>1.18.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.4.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!--mysql-->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>8.0.19</version>
            </dependency>
            <!--连接池-->
            <!-- https://mvnrepository.com/artifact/com.zaxxer/HikariCP -->
            <dependency>
                <groupId>com.zaxxer</groupId>
                <artifactId>HikariCP</artifactId>
                <version>3.4.1</version>
            </dependency>
            <!--springboot 启动器-->
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>2.1.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>
            <!-- https://mvnrepository.com/artifact/ch.qos.logback/logback-core -->
            <dependency>
                <groupId>ch.qos.logback</groupId>
                <artifactId>logback-core</artifactId>
                <version>${logback.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>

新建api module暴露实体类

按需导入依赖
<dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
建表例子
-- ----------------------------
-- Table structure for dept
-- ----------------------------
DROP TABLE IF EXISTS `dept`;
CREATE TABLE `dept`  (
  `dept_no` bigint(0) NOT NULL AUTO_INCREMENT,
  `dept_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `db_source` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  PRIMARY KEY (`dept_no`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;

INSERT INTO dept(dept_name,db_source) VALUES('部门01',DATABASE());
INSERT INTO dept(dept_name,db_source) VALUES('部门02',DATABASE());
INSERT INTO dept(dept_name,db_source) VALUES('部门03',DATABASE());
INSERT INTO dept(dept_name,db_source) VALUES('部门04',DATABASE());
新建pojo
@Data
@NoArgsConstructor
@Accessors(chain = true)//链式写法 new User().set().set()
public class Dept implements Serializable { //网络传输要序列化 Dept实体类 orm 类表关系映射
    private Long deptNo;
    private String deptName;
    //这个数据存在那个数据库,微服务,一个服务对应一个数据库,同一个信息也可能存在不同的数据库
    private String dbSource;

    public void setDeptName(String deptName) {
        this.deptName = deptName;
    }
}

新建服务提供者(Mavan 约定>配置>编码)

按需导入依赖
   <dependencies>
        <dependency>
            <groupId>com.fuck</groupId>
            <artifactId>fuck-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.zaxxer</groupId>
            <artifactId>HikariCP</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>
        <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>
配置application.yaml
server:
  port: 8001
mybatis:
  type-aliases-package: com.antake.pojo
  mapper-locations: classpath:mybatis/mappers/*.xml
  configuration:
    map-underscore-to-camel-case: true
spring:
  application:
    name: fuck-provide-dept
  datasource:
    type: com.zaxxer.hikari.HikariDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/db01?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8
    username: root
    password: 123456
编写Mapper
@Mapper
public interface DeptMapper {
    boolean addDept(Dept dept);
    Dept queryById(Long id);
    List<Dept> queryAll();
}
边写对应的Mapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.antake.mapper.DeptMapper">
    <insert id="addDept">
        insert into dept(dept_name,db_source)
        values (#{deptName},database())
    </insert>
    <select id="queryById" resultType="dept" parameterType="Long">
        select dept_no,dept_name,db_source from dept where dept_no = #{deptNo}
    </select>
    <select id="queryAll" resultType="dept">
        select dept_no,dept_name,db_source from dept
    </select>
</mapper>
编写service
public interface DeptService {
    boolean addDept(Dept dept);
    Dept queryById(Long id);
    List<Dept> queryAll();
}

@Service
@Transactional(rollbackFor = Exception.class) //有mybatisconfig必须开启启用事务支持,不配置就就在启动类上加
public class DeptServiceImpl implements DeptService {
    @Autowired
    DeptMapper deptMapper;
    @Override
    public boolean addDept(Dept dept) {
        return deptMapper.addDept(dept);
    }

    @Override
    public Dept queryById(Long id) {
        return deptMapper.queryById(id);
    }

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

编写controller
@RestController
@RequestMapping("/dept")
public class DeptController {
    @Autowired
    DeptService deptService;
    @PostMapping("/add")
    public boolean addDept(@RequestBody Dept dept){
        return deptService.addDept(dept);
    }
    @GetMapping("/get/{id}")
    public Dept get(@PathVariable("id")Long id){
        return deptService.queryById(id);
    }
    @GetMapping("/list")
    public List<Dept> queryAll(){
        return deptService.queryAll();
    }
}
编写启动类
@SpringBootApplication
public class DeptProvider_8001 {
    public static void main(String[] args) {
        SpringApplication.run(DeptProvider_8001.class,args);
    }
}
结果

image-20200414133846884

新建消费者(不该有service层,通过RestTemplate来调用service)

添加依赖
    <dependencies>
        <dependency>
            <groupId>com.fuck</groupId>
            <artifactId>fuck-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>
配置application.yaml
server:
  port: 80
要用RestTemplate就把他注入容器
//config包下面
@Configuration
public class ConfigBean {
    @Bean
    public RestTemplate getRestTemplate(){
        return new RestTemplate();
    }
}
新建ConsuemerController

为什么方法上面要用包装类而不用基本数据类型?对外暴露的接口,进行数据传输的时,如果不能保证全部不为null,最好用包装类,这样不容易出异常

@RestController
@RequestMapping("/consumer")
public class DeptConsumerController {
    //(url,请求实体Map,Class<T> responseType)
    @Autowired
    RestTemplate restTemplate;//提供多种便捷的远程访问方式
    private static final String REST_URL_PREFIX="http://localhost:8081";
    @RequestMapping("/dept/get/{id}")
    public Dept get(@PathVariable("id")Long id){
        return restTemplate.getForObject(REST_URL_PREFIX+"/dept/get/"+id,Dept.class);
    }
    @PostMapping("/dept/add")
    public Boolean add(@RequestBody Dept dept){
        return restTemplate.postForObject(REST_URL_PREFIX+"/dept/add",dept,Boolean.class);
    }
    @GetMapping("/dept/list}")
    public List<Dept> list(){
        return restTemplate.getForObject(REST_URL_PREFIX+"/dept/list",List.class);
    }
}
编写启动类
@SpringBootApplication
public class DeptConsumer_80 {
    public static void main(String[] args) {
        SpringApplication.run(DeptConsumer_80.class,args);
    }
}
结果

image-20200414144948577

注册中心eureka

添加依赖
<dependencies>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka-server</artifactId>
            <version>1.4.7.RELEASE</version>
        </dependency>
        <!--热部署-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
        </dependency>
    </dependencies>
编写配置文件 application.yaml
server:
  port: 7001
  #Eureka配置
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/
编写启动类
@SpringBootApplication
@EnableEurekaServer
public class EurekaServer_7001 {
    public static void main(String[] args) {
        SpringApplication.run(EurekaServer_7001.class,args);
    }
}
启用了注册中心之后,就需要给服务提供者添加配置
首先添加Eureka client的依赖
<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
            <version>1.4.7.RELEASE</version>
        </dependency>
注册服务,在application.yaml中添加注册中心的地址
eureka:
  client:
    service-url:
      defaultZone: http://localhost:7001/eureka/
开启注解
@SpringBootApplication
@EnableEurekaClient
public class DeptProvider_8001 {
    public static void main(String[] args) {
        SpringApplication.run(DeptProvider_8001.class,args);
    }
}
结果

image-20200414163901815

可以修改一些信息(例如修改provider信息)
eureka:
  client:
    service-url:
      defaultZone: http://localhost:7001/eureka/
  instance:
    instance-id: this is fucking provider at port 8001
PS:自我保护机制

image-20200414164410252

为了通过注册中心直观的检测到provider的状态和信息(点击以下链接),可以做以下步骤

image-20200414164537264

添加依赖(完善监控信息)
<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
            <version>2.2.6.RELEASE</version>
        </dependency>
配置信息
info:
  app.name: this is fucking app name
  company.name: this is your fucking company name
结果

image-20200414165219248

获取一些服务提供者的消息(团队开发要用,类似于swagger)
  • @RestController
    @RequestMapping("/dept")
    public class DeptController {
        @Autowired
        DeptService deptService;
        @Autowired
        DiscoveryClient discoveryClient;
        @PostMapping("/add")
        public boolean addDept(@RequestBody Dept dept){
            return deptService.addDept(dept);
        }
        @GetMapping("/get/{id}")
        public Dept get(@PathVariable("id")Long id){
            return deptService.queryById(id);
        }
        @GetMapping("/list")
        public List<Dept> queryAll(){
            return deptService.queryAll();
        }
        //注册进来的微服务,获取一些消息
        @GetMapping("/discovery")
        public Object discovery(){
            //获得微服务的清单
            List<String> services = discoveryClient.getServices();
            System.out.println(services);
            List<ServiceInstance> instances = discoveryClient.getInstances("fuck-provide-dept");
            for (ServiceInstance instance : instances) {
                System.out.println(instance.getHost()+"\t"
                        +instance.getPort()+"\t"
                        +instance.getUri()+"\t"
                        +instance.getInstanceId());
            }
            return this.discoveryClient;
        }
    }
    
  • 添加注解,使服务发现生效

    @EnableDiscoveryClient
    
  • 打印的信息

    image-20200414191319178

eureka集群(解决其中一个奔溃了还有备用方案)

新建两个注册中心 过程如上
讲集群联系起来

image-20200414201852091

更改eureka yaml
server:
  port: 7001
  #Eureka配置
eureka:
  instance:
    hostname: eureka01.com #eureka服务端的实例名字
  client:
    register-with-eureka: false #表示是否向eureka注册中心注册自己
    fetch-registry: false #如果为false,则表示自己为注册中心
    service-url:
      #单机defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
      defaultZone: http://eureka02.com:7002/eureka/,http://eureka03.com:7003/eureka/ #集群
更改provider yaml 让其在三个注册中心注册
server:
  port: 8001
mybatis:
  type-aliases-package: com.antake.pojo
  mapper-locations: classpath:mybatis/mappers/*.xml
  configuration:
    map-underscore-to-camel-case: true
spring:
  application:
    name: fuck-provide-dept
  datasource:
    type: com.zaxxer.hikari.HikariDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/db01?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8
    username: root
    password: 123456
eureka:
  client:
    service-url:
      defaultZone: http://eureka01.com:7001/eureka/,http://eureka02.com:7002/eureka/,http://eureka03.com:7003/eureka/
  instance:
    instance-id: this is fucking provider at port 8001
info:
  app.name: this is fucking app name
  company.name: this is your fucking company name

CAP原则

回顾CAP原则
  • RDBMS (Mysql、Oracle、SQL Server) ===>ACID
  • NoSQL (Redis、mongdb) ===> CAP
ACID是什么?
  • A(Atomicity)原子性
  • C (Consistency)一致性
  • I(Isolation)隔离性
  • D(Durability)持久性
CAP是什么?
  • C(Consistency)强一致性
  • A(Availability)可用性
  • P(Partition tolerance)分区容错性
CAP理论的核心
  • 一个分布式系统不可能同时很好的满足一致性,可用性和分区容错性这三个需求
  • 根据CAP原理,将NoSQL数据库分为了满足CA原则,满足CP原则和满足AP原则三大类
    • CA:单点集群,满足一致性,可用性的系统,通常可扩展性较差
    • CP:满足一致性,分区容错性的系统,通常性能不是特别高
    • AP:满足可用性,分区容错性的系统,通常可能对一致性要求低一些

作为服务注册中心,Eureka比Zookeeper好在哪里?面试题

著名的CAP理论指出,一个分布式系统不可能同时满足C(一致性)、A(可用性)、P(容错性)。

由于分区容错性P在分布式系统中是必须要保证的,因此我们只能在A和C之间进行权衡

image-20200414204710509

image-20200414205233141

Ribbon

什么是Ribbon

image-20200414210330864

Ribbon能干什么?

image-20200414211502503

集成Ribbon(在客户端也就是消费者里面)
添加依赖
<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-ribbon</artifactId>
            <version>1.4.7.RELEASE</version>
        </dependency>
        <!--需要eureka客户端,因为要发现服务-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
            <version>1.4.7.RELEASE</version>
        </dependency>

配置Eureka (yaml)
server:
  port: 80
eureka:
  client:
    register-with-eureka: false #消费者无需注册自己
    service-url:
      defaultZone: http://eureka02.com:7002/eureka/,http://eureka01.com:7001/eureka/,http://eureka03.com:7003/eureka/
启动Eureka注解
@EnableEurekaClient
配置负载均衡,实现RestTemplate
@Configuration
public class ConfigBean {
    //配置负载均衡,实现RestTemplate
    @Bean
    @LoadBalanced //就是这个注解,实现负载均衡
    public RestTemplate getRestTemplate(){
        return new RestTemplate();
    }
}
修改该controller
@RestController
@RequestMapping("/consumer")
public class DeptConsumerController {
    //(url,请求实体Map,Class<T> responseType)
    @Autowired
    RestTemplate restTemplate;//提供多种便捷的远程访问方式
    //通过Ribbon实现的时候,不应该写死下面这个,而是应该通过服务名拿到服务,再通过负载均衡实现服务选择
    //private static final String REST_URL_PREFIX="http://localhost:8001";
    private static final String REST_URL_PREFIX="http://fuck-provide-dept";
    @GetMapping("/dept/get/{id}")
    public Dept get(@PathVariable("id")Long id){
        return restTemplate.getForObject(REST_URL_PREFIX+"/dept/get/"+id,Dept.class);
    }
    @PostMapping("/dept/add")
    public Boolean add(@RequestBody Dept dept){
        return restTemplate.postForObject(REST_URL_PREFIX+"/dept/add",dept,Boolean.class);
    }
    @GetMapping("/dept/list")
    public List<Dept> list(){
        return restTemplate.getForObject(REST_URL_PREFIX+"/dept/list",List.class);
    }
}
结果
  • 开启两个Eureka 一个服务提供者 一个消费者

    image-20200414213335807

image-20200414213354616

image-20200414213404700

-

  • 但是在这里并不能体现出负载均衡,因为只有一个服务提供者,也没有多个数据库
得到一个结论
  • Eureka和Ribbon整合之后,客户端只需要调用服务即可,不用关心IP地址和端口号,只管用就行,不管怎么实现的
Ribbon实现负载均衡
新增两个数据库,作为数据源
/*db02*/

create DATABASE db02;
use db02;

DROP TABLE IF EXISTS `dept`;
CREATE TABLE `dept`  (
  `dept_no` bigint(0) NOT NULL AUTO_INCREMENT,
  `dept_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `db_source` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  PRIMARY KEY (`dept_no`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;

INSERT INTO dept(dept_name,db_source) VALUES('部门01',DATABASE());
INSERT INTO dept(dept_name,db_source) VALUES('部门02',DATABASE());
INSERT INTO dept(dept_name,db_source) VALUES('部门03',DATABASE());
INSERT INTO dept(dept_name,db_source) VALUES('部门04',DATABASE());

/*db03*/
create DATABASE db03;
use db03;

DROP TABLE IF EXISTS `dept`;
CREATE TABLE `dept`  (
  `dept_no` bigint(0) NOT NULL AUTO_INCREMENT,
  `dept_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `db_source` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  PRIMARY KEY (`dept_no`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;

INSERT INTO dept(dept_name,db_source) VALUES('部门01',DATABASE());
INSERT INTO dept(dept_name,db_source) VALUES('部门02',DATABASE());
INSERT INTO dept(dept_name,db_source) VALUES('部门03',DATABASE());
INSERT INTO dept(dept_name,db_source) VALUES('部门04',DATABASE());
新增两个服务提供者步骤如上 (注意修改数据库)

image-20200415104427885

自定义负载均衡算法
  • RoundRobinRUle:轮询
  • RandomRule:随机
  • AvailabilityFilteringRule:会先过滤掉,跳闸,访问故障的服务,对剩下的的进行轮询
  • RetryRule:先按照轮询获取服务,如果服务获取失败,则会在规定时间内进行重试
  • Weight Round Robin:加权轮询
  • Least Connection:最少连接数
  • Least Connection Slow Start Time:最少连接数慢启动时间
  • Weighted Least Connection:加权最少连接
  • Agent Based Adaptive Balancing:基于代理的自适应负载均衡
  • Fixed Weight:固定权重
  • Weighted Response:加权响应
  • Source IP Hash:源IP哈希
在consumer修改config(注意: 自定义的算法类不能放在主启动类的包及其子包下。)
  • 因为主启动类使用了@SpringBootApplication注解,而这个注解又默认添加了@ComponentScan注解【只要使用了@SpringBootApplication注解就使用了@ComponentScan注解】。下面是@SpringBootApplication的@ComponentScan注解源码:

    @ComponentScan(excludeFilters = {
    		@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
    		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
    

    可以看到,这个注解会把添加了注解的当前类所在的当前包及其子包都进行扫描,并且使用所扫描到的注解的默认配置。
    一旦扫描到@RibbonClient注解,它就会默认使用Spring提供的负载均衡算法(这里是为什么呢?因为@RibbonClient注解又实现了@Import(RibbonClientConfigurationRegistrar.class)注解,引入了spring提供的默认配置算法),因此我们自己的配置就不会起作用。

@Bean//随机的算法
    public IRule getRule(){
        return new RandomRule();
    }
使用自己的算法
  • 修改之后的启动类

    package com.antake;
    
    import com.myRule.MyRule;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
    import org.springframework.cloud.netflix.ribbon.RibbonClient;
    
    @SpringBootApplication
    @EnableEurekaClient
    //在微服务启动的时候就能去加载我们自定义的Ribbon类
    @RibbonClient(name = "fuck-provide-dept",configuration = MyRule.class)
    public class DeptConsumer_80 {
        public static void main(String[] args) {
            SpringApplication.run(DeptConsumer_80.class,args);
        }
    }
    
  • 修改的之后的config.ConfigBean并无变化

  • 增加的配置

    package com.myRule;
    
    import com.netflix.loadbalancer.IRule;
    import com.netflix.loadbalancer.RandomRule;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    public class MyRule {
        @Bean
        public IRule getRule(){
            return new RandomRule();
        }
    }
    

Feign负载均衡

简介

Feign是声明式的web service客户端,它让微服务之间的调用变得更加简单了,类似于controller调用service。SpringCloud集成了Ribbon和Eureka,可以使用Feign时提供负载均衡的的HTTP客户端。(代码可读性变高了,但是因为加了一层,所以效率变低了)

只需要创建一个接口,然后添加注解即可!

feign,主要是社区,大家都习惯面向接口编程。这个是很多开发人员的规范。调用微服务访问的两种方法

  • 微服务名字【ribbon】
  • 接口和注解【feign】
Feign能干什么?

image-20200415135233138

Feign集成了Ribbon
  • 利用Ribbon维护了MicroServiceCloud-Dept的服务列表信息,并且通过轮询实现了客户端的负载均衡,而与Ribbon不同的是,通过Feign只需要定义服务绑定接口且以声明式的方法,优雅而简单的实现了服务调用。
怎么实现
接口和注解
  • 首先引入依赖(在之前ribbon的消费者consumer上)

    <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-feign</artifactId>
                <version>1.4.7.RELEASE</version>
            </dependency>
    
  • 在api上新增接口(需要引入feign的依赖)

    package com.antake.service;
    
    import com.antake.pojo.Dept;
    import org.springframework.cloud.openfeign.FeignClient;
    import org.springframework.stereotype.Service;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestBody;
    
    import java.util.List;
    //@Service
    //@Component
    @FeignClient(value = "fuck-provide-dept")//value填写服务名称,这样就可以不用在消费者中写死
    public interface DeptService {
        @GetMapping("/dept/get/{id}")
        Dept getDeptById(@PathVariable("id") Long id);
        @GetMapping("/dept/list")
        List<Dept> queryAll();
        @PostMapping("/dept/add")
        boolean add(@RequestBody Dept dept);
    }
    

    【】这样配置 deptService 会报错,因为没有扫描到这个Service,需要在启用类上添加@EnableFeignClients(basePackages={"com.antake.service"}),这里诞生出一个问题,直接在Service接口上添加@Service也可以,并不知道有什么区别核问题,有待深究

    又仔细想了一下,回想Spring的自动注入,能注入是因为该实例已经存在于spring的容器中,就可以反推,添加了@FeignClient并不会自动添加到容器中,而使用@Service就可以将其纳入到spring的容器管理当中,用@EnableFeignClients不配置basePackages的时候会默认扫描所有包中有@FeignClient的,然后将其纳入容器管理中,虽然两者都能做到能使用service,但是应该用@EnableFeignClients,因为@Service并不能起到fegin客户端的作用

  • 用feign调用和ribbon主要的区别(还有重要的就是新建了一个service)

    image-20200415154234607

分布式系统面临的问题

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

服务雪崩

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

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

​ 这时候我们就需要弃车保帅

什么是Hystrix(加载服务提供者上面)

image-20200415160758254

官网https://github.com/Netflix/Hystrix/

服务熔断(服务端修改 provider)

image-20200415162058777

步骤

新建一个项目,直接拷贝之前的服务提供者即可,也可以在原有的代码上进行修改

导入依赖
<!--友情提示:也可以用直接用新的架包,还是推荐用springcloud推荐的,毕竟兼容性更好-->
<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-hystrix</artifactId>
            <version>1.4.7.RELEASE</version>
        </dependency>
最好修改一下application.yaml 以示区分(可选)
instance-id: this is fucking provider contains hystrix at port 8001
修改controller开启功能
@RestController
@RequestMapping("/dept")
public class DeptController {
    @Autowired
    DeptService deptService;
    @GetMapping("/get/{id}")
    @HystrixCommand(fallbackMethod = "hystrixGet")
    public Dept get(@PathVariable("id")Long id){
        Dept dept = deptService.queryById(id);
        if (dept==null){
            //异常捕获,虽然该服务没有问题,但是不排除该服务的调用者不出现问题
            //例如该服务的调用者一直在等待dept的返回,返回null就会出现问题
            //据说这个代码最值钱,而不是crud
            throw new RuntimeException("id=>"+id+"\t没有查询到对应的信息");
            //直接抛出异常好吗?显然是不好的,即使是该服务的调用者捕获了异常但是没有,结果
            //此时可以调用备用方法来处理
        }
        return deptService.queryById(id);
    }

    public Dept hystrixGet(@PathVariable("id")Long id){
        return new Dept().setDeptNo(id).setDeptName("id=>"+id+"\t没有查询到对应的信息").setDbSource("no data in database");
    }
}
添加注册,是程序支持熔断
@EnableCircuitBreaker 
prefer-ip-address=true #显示ip
服务降级(客户端修改 consumer)

image-20200415181234104

image-20200415181541340

image-20200415181628325

直接关闭了该服务提供者,但是访问还是可以拿到降级信息

image-20200415182325590

对比

image-20200415182726909

dashboard服务监控(consumer)
添加依赖(在consumer的基础上再添加)

image-20200415183159758

服务端口9001
开启监控,增加Servlet(主意一点,必须要有监控的依赖 actuator)
  • 问:有了springMVC,为什么还要用servlet?有了servlet3的注解,为什么还要使用ServletRegistrationBean注入的方式?

    使用场景:在有些场景下,比如我们要使用hystrix-dashboard,这时候就需要注入HystrixMetricsStreamServlet(第三方的servlet),该servlet是hystrix的组件。

image-20200415184127118

image-20200415185044801

image-20200415185320210

解释

image-20200415185636999

image-20200415185659881

image-20200415185728368

Zuul路由网关

什么是Zuul?

image-20200415204618847

Zuul能干嘛?
  • 路由
  • 过滤

官网文档:https://github.com/Netflix/zuul

步骤
新建项目
<!--在监控面板的基础上再加-->
<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zuul</artifactId>
            <version>1.4.7.RELEASE</version>
        </dependency>
配置application.yaml
server:
  port: 9527
spring:
  application:
    name: springcloud-zuul
eureka:
  client:
    service-url:
      defaultZone: http://eureka01.com:7001/eureka/,http://eureka02.com:7002/eureka/,http://eureka03.com:7003/eureka/
  instance:
    instance-id: zuul9527
    prefer-ip-address: true #显示Ip
info:
  app.name: antake's springcloud
  company.name: my company
  version: 1.0.0
编写启动类
@SpringBootApplication
@EnableZuulProxy
public class FuckZuul_9527 {
    public static void main(String[] args) {
        SpringApplication.run(FuckZuul_9527.class);
    }
}
隐藏真实的微服务名称
zuul:
  ignored-services: fuck-provide-dept #使哪些名称访问失效,set #路径替换的微服务名称 ”*“ 隐藏
  routes:
    mydept.serverId: fuck-provide-dept #微服务名称
    mydept.path: /mydept/**
  prefix: /antake #公共前缀 加了前缀必须通过前缀去访问,没有就报错

image-20200415213523482

SpringCloud config分布式配置

分布式系统面临的--配置文件的问题

image-20200416093053995

什么是SpringCloud config分布式配置中心

image-20200416093138115

SpringCloud config分布式配置中心能干嘛?

image-20200416093534591

SpringCloud config分布式配置中心与github整合

​ 由于SpringCloud Config默认使用Git来存储配置文件(也有其他方式,比如支持SVN和本地文件),但是最推荐的还是Git,而且使用的是http/https访问的形式

git使用
  • git add .
  • git status
  • git commit -m "message"
  • git push
新建config server

image-20200416102104984

添加依赖
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.2.6.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-server</artifactId>
            <version>2.2.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
            <version>2.2.6.RELEASE</version>
        </dependency>
    </dependencies>
配置yaml
server:
  port: 3344
spring:
  application:
    name: fuck-config-server
  #连接远程仓库
  cloud:
    config:
      server:
        git:
          uri: https://gitee.com/antake/lightfoods-admin-config.git
编写启动类
@SpringBootApplication
@EnableConfigServer
public class ConfigServer_3344 {
    public static void main(String[] args) {
        SpringApplication.run(ConfigServer_3344.class,args);
    }
}

HTTP服务具有以下格式的资源:

/{application}/{profile}[/{label}]
/{application}-{profile}.yml
/{label}/{application}-{profile}.yml
/{application}-{profile}.properties
/{label}/{application}-{profile}.properties

image-20200416104342324

读配置

image-20200416104452360

出现问题

image-20200416105547406

解决办法,将config-server版本降低

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-config-server</artifactId>
    <version>2.1.4.RELEASE</version>
</dependency>

成功

image-20200416105736392

image-20200416111733561

有什么用?

config-client就可以通过config-server去访问git,拿到自己的配置文件

新建client
添加yaml config-dept-provider.yaml 并上传到gitee
spring:
    profiles:
        active: dev
---
server:
  port: 8201
spring:
  profiles: dev
  application:
    name: fuck-provide-dept
eureka:
  client:
    service-url:
      defaultZone: http://eureka01.com:7001/eureka/,http://eureka02.com:7002/eureka/,http://eureka03.com:7003/eureka/
---
server:
  port: 8202
spring:
  profiles: test
  application:
    name: fuck-provide-dept
eureka:
  client:
    service-url:
      defaultZone: http://eureka01.com:7001/eureka/,http://eureka02.com:7002/eureka/,http://eureka03.com:7003/eureka/

image-20200416113019627

添加依赖
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.2.6.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-config</artifactId>
            <version>2.1.4.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
            <version>2.2.6.RELEASE</version>
        </dependency>
    </dependencies>
新建bootstrap.yaml

bootstrap 系统级别的配置 application 用户级别的配置

spring:
  cloud:
    config:
      uri: http://localhost:3344 # 服务端
      name: config-dept-provider #从git获取的文件名,不需要后缀
      profile: dev #用什么环境
      label: master #从那个分支去拿
application.yaml
spring:
  application:
    name: dept-provider-3355-test
新建一个controller用于测试展示信息
@RestController
public class ConfigClientController {
    @Value("${spring.application.name}")
    private String applicationName;
    @Value("${eureka.client.service-url.defaultZone}")
    private String eurekaServer;
    @Value("${server.port}")
    private String port;
    @GetMapping("/config")
    public String getConfig(){
        return "applicationName:"+applicationName+"\teurekaServer:"+eurekaServer+"\tport:"+port;
    }
}
编写启动类
@SpringBootApplication
public class DeptProviderConfigClient_3355 {
    public static void main(String[] args) {
        SpringApplication.run(DeptProviderConfigClient_3355.class,args);
    }
}

启动 server和client查看结果

image-20200416115745370

结果
  • 此时发现application.yaml里面配置的应用名未生效,依旧证明了bootstrap的优先级高于application

Springcloud 总结

image-20200416141651200

posted @ 2020-04-24 14:29  Antake  阅读(308)  评论(0)    收藏  举报
Live2D