spring cloud netflix之萌新上路

265 阅读29分钟

基础

  1. 提醒: 本文代码基于spring boot 2.3,spring cloud Hoxton.SR5测试通过.
  2. 代码地址: gitee.com/moralland/s…
  3. 将本地已有代码上传到gitee的步骤: 如何将本地项目上传到Gitee

什么是集群?

  1. 多个服务器,执行同一个业务.
  2. 一般是单机处理到达瓶颈的时候,把单机复制几份构成集群.
  3. 从单机结构到集群结构,代码基本无需任何修改,仅仅是多部署几台服务器.

什么是分布式?

将一个业务拆分多个子业务,部署在不同的服务器上执行.

什么是微服务?

  1. 将业务系统彻底的组件化服务化.
  2. 单个业务拆分成多个可以独立开发,设计,运行和运维的小应用,这些小应用之间通过服务完成交互和继承.每个小应用从前端web ui,到控制层,逻辑层,数据库访问,数据库都是完全独立的一套.

什么是SOA?

  1. Service Oriented Architecture: 面向服务的架构.企业应用领域提出的,将紧耦合的系统,划分为面向业务的,粗粒度,松耦合,无状态的服务.服务发布出来共其他服务调用,一组相互依赖的服务构成了SOA架构下的系统.
  2. SOA高度依赖服务总线(ESB).
    SOA
    SOA
    微服务
    微服务

SOA和微服务的区别是什么?

  1. 微服务剔除了SOA中复杂的ESB企业服务总线,业务相关的逻辑在服务内部处理,使用HTTP(Rest API)进行轻量化通讯.
  2. SOA强调的是异构系统之间的通信和解耦合,微服务架构强调的是按照业务边界进行细粒度的拆分和部署.微服务的切分粒度会更小.

什么是IaaS?

  1. Infrastructure-as-a-service: 基础设施服务.
  2. IssS是云服务的最底层,主要提供一些基础资源.用户需要自己控制底层,实现基础设施的使用逻辑.

什么是PaaS?

  1. Platform-as-a-service: 平台服务.
  2. PaaS提供软件部署平台,抽象掉了硬件操作系统细节,可以无缝拓展.开发者只需要关心自己的业务细节,不需要关注底层.

什么是SaaS?

  1. Software-as-a-service: 软件服务.
  2. 软件的开发,管理,部署都交给第三方,不关心技术问题,拿来即用.普通用户接触的互联网服务,几乎都是Saas.

IaaS,PaaS,SaaS 的区别是什么?

区别
区别

有哪些解决方案?

  1. Spring Cloud NetFlix: 一站式解决方案.
  2. Apache Dubbo ZooKeeper: 高性能的RPC通信框架.
  3. Spring Cloud Alibaba: 新一代的一站式解决方案.

Spring Cloud NetFlix的解决方案是什么?

  1. 服务注册与发现: Eureka
  2. http通信方式: Feign
  3. 熔断机制: Hystrix
  4. api网关: zuul组件

Apache Dubbo ZooKeeper的解决方案是什么?

  1. 服务注册与发现: zookeeper
  2. RPC通信: Dubbo
  3. api网关: 没有
  4. 熔断机制: 没有

什么是服务网格?

  1. Service Mesh是一个基础设施层,用于处理服务间通信.服务网格通常由一系列轻量级的网络代理组成的,它们与应用程序部署在一起,但是应用程序不需要知道他们的存在.
  2. 服务间通信层既不会与应用程序的代码耦合,又能捕捉到底层环境高度动态的特点.
  3. 服务网格是云原生技术栈中非常关键的组件.
  4. docker和kubernetes解决了部署的问题,服务网格解决微服务运行时的问题.

怎么写HTTP,RPC?

微服务的优点是什么?

  1. 服务内聚性高,代码容易理解.
  2. 开发简单
  3. 松耦合
  4. 不同微服务可以使用不同的语言开发
  5. 易于和第三方集成,容易集成自动部署.

微服务的缺点是什么?

  1. 开发人员要处理分布式系统的复杂性
  2. 多服务运维难度增加
  3. 服务间通信成本增加
  4. 数据一致性可能会被破坏

什么是CAP原则?

  1. C: consistency.数据一致性.所有节点拥有数据的最新版本.
  2. A: availability.可用性.数据具备高可用性.
  3. P: partion-tolerance.分区容错性. 容忍网络之间出现分区,分区之间网络不可达.这个是必需的,客观存在的.
  4. 在容忍网络分区的条件下,强一致性和极致可用性无法同时达到.所以我们可以选择AP,也可以选择CP.注意:不是选择了AP,C就完全抛弃了,反之同理.

微服务的技术栈有哪些?

技术栈

为什么选择Spring Cloud作为微服务架构进行学习?

  1. 功能齐全
  2. 支持Rest
  3. 社区活跃性高
  4. 文档丰富

架构选型的依据是什么?

  1. 整体解决方案和框架成熟度
  2. 社区热度
  3. 可维护性
  4. 学习曲线

各大IT公司用的微服务架构有哪些?

  1. 阿里: dubbo+HFS
  2. 京东: JSF
  3. 新浪: Motan
  4. 当当: Dubbox

各微服务架构有什么区别?

各种微服务框架对比

参考: 各种微服务框架对比

有哪些微服务落地技术?

微服务落地技术

什么是Spring Cloud?

Spring Cloud是关注全局的微服务协调整理治理框架,它将springboot开发的一个个单体微服务整合并管理起来,为各个微服务提供: 配置管理,服务发现,断路器,路由,微代理,事件总线,全局锁,决策竞选,分布式会话等集成服务.

Spring cloud架构

Spring Cloud和springboot的关系是什么?

Spring Boot专注于开发快速,方便的开发单个个体微服务,Spring Cloud关注全局的服务治理框架.

Dubbo和Spring Cloud有什么区别?

  1. dubbo定位是一款RPC框架,Spring Cloud的目标是微服务架构下的一站式解决方案.
  2. dubbo和Spring Cloud最大的区别是Spring Cloud抛弃了dubbo的RPC通信,而采用HTTP的REST方式.RPC通信的优点是服务调用的性能更好.RPC最主要的缺陷就是服务提供方和调用方式之间依赖太强,我们需要为每一个微服务进行接口的定义,并通过持续集成发布到私有仓库中,需要严格的版本控制才不会出现服务提供方和调用之间因为版本不同而产生冲突.而REST是轻量级的接口,服务的提供和调用不存在代码之间的耦合,只是通过一个约定进行规范.但有可能出现文档和接口不一致而导致服务集成问题
  3. Spring Cloud的功能比dubbo更加强大,涵盖更广,而且作为Spring的拳头项目,与spring的其他项目完美融合,这对微服务而言至关重要.
  4. Spring Cloud的社区活跃度更高,而dubbo曾经停更了5年.
    spring cloud和dubbo对比

现在大型网站架构图?

架构图

学习spring cloud参考网站:

  1. Spring Cloud中文网: www.springcloud.cc/
  2. 纯洁的微笑: www.ityouknow.com/spring-clou…

新建Spring Cloud项目

  1. 新建一个maven工程
  2. 删除src目录

@RequestBody和@RequestParam有什么区别?

RestTemplate的作用是什么?

它是spring框架提供的用来调用rest服务的工具类.它简化了http服务的通信方式,只需要传入url,参数,返回值类型即可.主要有headForHearders(),getForObject(),postForObject(),put()和delete()方法.

Eureka

什么是Eureka?

服务注册与发现. 分为服务端和客户端.服务端即Eureka服务注册中心,客户端完成微服务向Eureka服务的注册与发现. 参考: 深入理解Eureka之源码解析 参考: Eureka工作原理

eureka的server和client
复杂eureka应用
类图

为什么需要Eureka?

  1. 微服务数量众多,要进行远程调用就需要服务端的ip地址和端口,注册中心帮助我们管理这些服务的ip和端口
  2. 微服务会实时上报自己的状态,注册中心统一管理这些微服务的状态,将存在问题的服务踢出服务列表,客户端获取到可用的服务进行调用.

Eureka有哪些特点?

  1. 服务启动时会生成服务的基本信息对象InstanceInfo,然后会注册到服务治理中心.
  2. 注册完成后会从服务治理中心拉取所有的服务信息缓存到本地.客户端会使用该信息查找其他服务,从而进行远程调用.
  3. 默认服务每30s发送一个心跳信息,续约服务.
  4. 如果服务治理中心90s内没有收到一个服务的续约,会任务服务已经挂了,会把服务注册信息删掉.
  5. 服务停止前,服务可以主动发送一个停止请求,服务治理中心收到请求会删除这个服务的信息.停止请求: DiscoveryManager.getInstance().shutdownComponent();
  6. 默认如果Eureka Server收到的心跳包不足正常值得85%,就会进入自我保护状态,在这种模式下,eureka server不会删除任何服务信息.
//服务主动下线
@RestController
public class HelloController {
 @Autowired
 private DiscoveryClient client;
 @RequestMapping(value = "/hello", method = RequestMethod.GET)
 public String index() {
  java.util.List<ServiceInstance> instances = client.getInstances("hello-service"); 
    return "Hello World";
 }
 @RequestMapping(value = "/offline", method = RequestMethod.GET)
 public void offLine(){
    DiscoveryManager.getInstance().shutdownComponent();
 } 
}

什么是eureka的自我保护机制?

自我保护机制的作用是: 保护微服务的实例不受网络分区故障的影响. eureka server在运行期间会统计心跳失败比例在15分钟之内是否低于85%,如果低于85%,eureka server会讲这些实例保护起来,让这些实例不会过期,但是在保护期内,如果这个服务提供者非正常下线,此时服务消费者就会拿到一个无效的服务实例,会调用失败.为了解决这个问题,需要消费者端有一些容错机制,如重试,断路器等.

zookeeper是什么?

  1. zookeeper维护一个类似于文件系统的数据结构,每个子目录项都被称为znode(目录节点),可以自由的增删.客户端监听它关心的目录节点,当目录节点发生变化(数据改变,被删除,子目录节点增加删除)时,zookeeper会通知客户端.
  2. zookeeper是一个分布式协调服务,它的职责是保证所有服务之间数据的一致性和同步性.
  3. zookeeper可以用来做注册中心,也可以用来做分布式事务锁,统一命名服务(生成全局唯一的路径作为名字,客户端应用根据名字来获取资源或者服务的地址,提供者等信息),配置管理(数据发布/订阅),Master选举,等.
  4. zookeeper有三种角色: leader,follower,observer.一个zookeeper集群同一时刻只会有一个leader.leader服务器为客户端提供读和写服务.follower和observer都能提供读服务,不能提供写服务.两者唯一的区别在于:observer服务器不参与leader选举过程,也不参与写操作的"过半写成功"策略,因此observer可以在不影响写性能的情况下提升集群的读性能.
  5. 参考地址: ZooKeeper原理与应用

Eureka和zookeeper的区别是什么?

  1. 两者都可以用作服务注册中心.但是eureka满足CAP原则中的分区容错性和可用性AP,zookeeper满足的是CAP原则中的分区容错性和一致性CP.
  2. 当zookeeper的leader节点因为网络故障和其他的节点失去联系是,剩余节点会进行leader选举,选举时间大概30-120s,直到选举结束,集群才能对外提供服务.
  3. eureka的各个节点都是平等的,几个节点挂掉不会影响正常节点的工作,剩余的节点依旧可以提供注册和查询服务.而eureka的客户端如果发现连接节点失败,会自动切换到其他节点.只要有一台eureka还在,就能保证注册服务可用,只不过信息不一定最新.eureka的自我保护机制,也能更好的保障eureka节点的可用性.
  4. 参考链接: Eureka的工作原理以及它与ZooKeeper的区别

套路:

  1. 导入依赖
  2. 编写配置文件
  3. 开启这个功能
  4. 配置类

怎么配置eureka?

  1. 导入依赖
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
  1. 修改配置文件application.yml:
server:
  port: 7001 #端口号
eureka:
  instance:
    hostname: eureka7001.com #eureka服务端的实例名称
  client:
    register-with-eureka: false #是否将自己注册到服务器
    fetch-registry: false #服务发现,是否从Eureka中获取注册信息
    service-url: #Eureka客户端与Eureka服务端的交互地址,高可用状态配置对方的地址,单机状态配置自己(如果不配置则默认本机8761端口)
      #单机: efaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
      defaultZone: http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/ #集群
  1. 启动类添加注解开启注册服务
@SpringBootApplication
@EnableEurekaServer//开启服务类
public class SpringcloudEureka7001Application {
    public static void main(String[] args) {
        SpringApplication.run(SpringcloudEureka7001Application.class, args);
    }
}
  1. 访问页面: http://localhost:7001/
  2. 给提供服务类添加依赖: spring-cloud-starter-netflix-eureka-client
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
  1. 修改提供服务类配置文件: application.yml
eureka:
  client:
    service-url:
      #单机: defaultZone: http://localhost:7001/eureka/
      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/ #集群
  instance:
    #appname: provider-dept #服务名,默认取 spring.application.name 配置值,如果没有则为 unknown.最好配置spring.application.name(自测服务发现是用的spring.application.name)
    instance-id: springcloud-provider-dept8001 #实例ID
    prefer-ip-address: true #客户端在注册时使用自己的IP而不是主机名,缺省:false
  1. 给提供服务类启动类添加注解,来开启服务注册自己
@SpringBootApplication
@EnableDiscoveryClient//开启服务发现
@EnableEurekaClient//启动后会自动将此服务注册到eureka
public class SpringcloudProviderDept8001Application {
    public static void main(String[] args) {
        SpringApplication.run(SpringcloudProviderDept8001Application.class, args);
    }
}
  1. 访问eureka查看服务注册情况: http://localhost:7001/

怎么配置discoveryClient?

  1. 提供服务类中添加一个controller方法
@Autowired
private DiscoveryClient client;
@GetMapping("/discovery")
public Object discovery(){
    List<String> list = client.getServices();
    System.out.println("client.services:"+list);
    List<ServiceInstance> instances = client.getInstances("springcloud-provider-dept");
    for (ServiceInstance instance : instances) {
        System.out.println(
                instance.getServiceId()+"\n"+
                instance.getHost()+"\n"+
                instance.getPort()+"\n"+
                instance.getUri()
                );
    }
    return client;
}
  1. 提供服务类添加服务发现的注解
@SpringBootApplication
@EnableDiscoveryClient//开启服务发现
@EnableEurekaClient//启动后会自动将此服务注册到eureka
public class SpringcloudProviderDept8001Application {
    public static void main(String[] args) {
        SpringApplication.run(SpringcloudProviderDept8001Application.class, args);
    }
}
  1. 访问网址: http://localhost:8001/dept/discovery,查看控制台输出.
client.services:[springcloud-provider-dept]
SPRINGCLOUD-PROVIDER-DEPT
192.168.31.180
8001
http://192.168.31.180:8001

怎么搭建eureka集群?

  1. 添加新的eureka server项目和依赖.
  2. 修改eureka.server.client.service-url.defaultZone为其他的eureka server的地址
server:
  port: 7003
eureka:
  instance:
    hostname: eureka7003.com #eureka服务端的实例名称
  client:
    register-with-eureka: false #是否将自己注册到服务器
    fetch-registry: false #服务发现,是否从Eureka中获取注册信息
    service-url: #Eureka客户端与Eureka服务端的交互地址,高可用状态配置对方的地址,单机状态配置自己(如果不配置则默认本机8761端口)
      #defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/
  1. 修改提供服务的项目的配置文件: application.yml,由原来向一个eureka server注册,改为向多台eureka server注册
eureka:
  client:
    service-url:
      #单机: defaultZone: http://localhost:7001/eureka/
      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/ #集群
  instance:
    #appname: provider-dept #服务名,默认取 spring.application.name 配置值,如果没有则为 unknown.最好配置spring.application.name(自测服务发现是用的spring.application.name)
    instance-id: springcloud-provider-dept8001 #实例ID
    prefer-ip-address: true #客户端在注册时使用自己的IP而不是主机名,缺省:false
  1. 访问eureka页面查看eureka server集群情况: http://localhost:7001/

怎么配置集群注册中心

什么是负载均衡?

  1. 就是平均一下各个服务器的工作量,避免一台服务器负荷过大难以提供正常的服务.
  2. 常见的负载均衡方式有: http重定向,dns负载均衡,反向代理负载均衡.
  3. 常用的软件负载均衡方式: 集中式和进程式.集中式就是服务提供方和消费方中间的一个独立的负载均衡设施.进程式就是在消费方这里进行负载均衡,消费方从注册中心获知有哪些地址可用,然后根据算法从中选择一个合适的.

什么是ribbon?

  1. 基于http和tcp的客户端负载均衡工具.它可以让我们轻松的将rest服务转换成客户端负载均衡服务调用.feign是基于ribbon实现的工具.
  2. ribbon的负载均衡依赖于注册中心(eureka)实现.
  3. Spring Cloud Ribbon配置详解

ribbon原理是什么?

  1. 先选择一个eureka server,优先选择同一个区域内负载均衡较少的server.
  2. 根据用户指定的策略,从eureka server的服务注册表中选择一个地址.

有哪些负载均衡的软件?

负载均衡分类?

配置多个服务提供方的时候需要注意什么?

多个服务提供方的服务的名称一致

怎么使用多个服务提供者,多个注册中心,进行负载均衡?

  1. 准备工作: 准备多个服务提供者.每个服务提供者都使用不同的数据库,并且数据表里加上每个数据库的名字.这样调用查询接口的时候可以看到是调用的哪个服务提供者的服务.
  2. 给服务消费方添加pom依赖: eureka client和ribbon.
 <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
  1. 修改配置文件application.yml,配置eureka.
server:
  port: 80
eureka:
  client:
    register-with-eureka: false #不向注册中心注册自己
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/ #集群
  1. 在RestTemplate的注入bean的类上加上@LoadBalanced注解.在默认情况下,使用轮询的负载均衡算法.
  2. 在主启动类上加上@EnableEurekaClient来启动eureka client的注册发现.
  3. 在controller中不再使用具体的服务提供者的网址来调用服务,而是应该使用eureka server的名字来调用.例如: private final String PRE_URL = "http://SPRINGCLOUD-PROVIDER-DEPT";
  4. 访问服务消费者的controller接口来查看ribbon运行情况.

怎么修改负载均衡策略?

将IRule类加入spring管理即可.然后就会默认启用这个策略.

@Configuration
public class RuleBean {
    //默认的负载均衡策略是轮询,通过将IRule类型的bean交给spring,可以实现别的负载均衡策略.
    @Bean
    public IRule myRule(){
        return new RandomRule();
    }
}

怎么自定义负载均衡算法?

  1. 写一个负载均衡算法类,继承AbstractLoadBalancerRule类.主要是重写chose方法.
public class MyseltRule extends AbstractLoadBalancerRule {
    private AtomicInteger times;
    private AtomicInteger thisServer;
    private static final int REPEAT = 3;
    private static Logger log = LoggerFactory.getLogger(RoundRobinRule.class);
    public MyseltRule() {
    }
    public MyseltRule(ILoadBalancer lb) {
        this();
        setLoadBalancer(lb);
    }
    public Server choose(ILoadBalancer lb, Object key) {
        if (lb == null) {
            log.warn("no load balancer");
            return null;
        }
        Server server = null;
        int count = 0;
        while (server == null && count++ < 10) {
            List<Server> reachableServers = lb.getReachableServers();
            List<Server> allServers = lb.getAllServers();
            int upCount = reachableServers.size();
            int serverCount = allServers.size();
            if ((upCount == 0) || (serverCount == 0)) {
                log.warn("No up servers available from load balancer: " + lb);
                return null;
            }
            if(times == null){
                times = new AtomicInteger(0);
            }
            if(thisServer == null){
                thisServer = new AtomicInteger(0);
            }
            server = allServers.get(thisServer.get());
            System.out.println(server);
            System.out.println(server.getPort());
            if(times.get() < REPEAT-1 ){
                times.getAndIncrement();
            }else{
                times.set(0);
                if(thisServer.get() < serverCount-1){
                    thisServer.getAndIncrement();
                }else{
                    thisServer.set(0);
                }
            }
            if (server == null) {
                /* Transient. */
                Thread.yield();
                continue;
            }
            if (server.isAlive() && (server.isReadyToServe())) {
                return (server);
            }
            // Next.
            server = null;
        }
        if (count >= 10) {
            log.warn("No available alive servers after 10 tries from load balancer: "
                    + lb);
        }
        return server;
    }
    @Override
    public Server choose(Object key) {
        return choose(getLoadBalancer(), key);
    }
    @Override
    public void initWithNiwsConfig(IClientConfig clientConfig) {
    }
}
  1. 配置这个类.注意: 配置类必须不在componentscan的扫描包内,否则该负载均衡算法会和其他服务消费方共享.
@Configuration
public class RuleBean {
    //默认的负载均衡策略是轮询,通过将IRule类型的bean交给spring,可以实现别的负载均衡策略.
    @Bean
    public IRule myRule(){
        return new MyseltRule();
    }
}
  1. 在启动类上添加注解@RibbonClient(name="xxxservername",configuration="xxx.class"),例如: @RibbonClient(name = "SPRINGCLOUD-PROVIDER-DEPT",configuration = RuleBean.class)
@SpringBootApplication
@EnableEurekaClient
//name是eureka的服务名,configuration是配置的负载均衡的策略的类
@RibbonClient(name = "SPRINGCLOUD-PROVIDER-DEPT",configuration = RuleBean.class)
public class SpringcloudConsumerDeptRibbon80Application {
    public static void main(String[] args) {
        SpringApplication.run(SpringcloudConsumerDeptRibbon80Application.class, args);
    }
}
  1. 其他如controller类的调用的url使用server-name,以及eureka配置等和普通ribbon的客户端配置一样.
    官网提示

Ribbon和Feign的区别?

feign其实是基于ribbon的,将ribbon进行了二次封装,负载均衡算法是一样的,但是调用接口的方式不一样.不再通过restTemplate等HTTP工具来直接消费服务提供者提供的接口,而是将这些接口封装为本地接口,然后通过本地接口来调用.不管是配置文件还是java配置负载均衡策略,都是使用的ribbon的负载均衡策略.

怎么配置feign的负载均衡?

  1. 添加依赖.在ribbon的基础上添加了spring-cloud-starter-openfeign.
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
  1. 配置同ribbon配置.另外也可以通过配置文件来设置负载均衡策略.
server:
  port: 80
eureka:
  client:
    register-with-eureka: false #不向注册中心注册自己
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/
#      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/ #集群
#通过配置文件来设置负载均衡策略.同ribbon一致.
spring:
  application:
    name: consumer-feign-80
SPRINGCLOUD-PROVIDER-DEPT:
  ribbon:
    NFLoadBalancerRuleClassName: com.guohao.config.RuleBean
  1. 自定义负载均衡策略.
public class MyseltRule extends AbstractLoadBalancerRule {
    private AtomicInteger times;
    private AtomicInteger thisServer;
    private static final int REPEAT = 3;
    private static Logger log = LoggerFactory.getLogger(RoundRobinRule.class);
    public MyseltRule() {
    }
    public MyseltRule(ILoadBalancer lb) {
        this();
        setLoadBalancer(lb);
    }
    public Server choose(ILoadBalancer lb, Object key) {
        if (lb == null) {
            log.warn("no load balancer");
            return null;
        }
        Server server = null;
        int count = 0;
        while (server == null && count++ < 10) {
            List<Server> reachableServers = lb.getReachableServers();
            List<Server> allServers = lb.getAllServers();
            int upCount = reachableServers.size();
            int serverCount = allServers.size();
            if ((upCount == 0) || (serverCount == 0)) {
                log.warn("No up servers available from load balancer: " + lb);
                return null;
            }
            if(times == null){
                times = new AtomicInteger(0);
            }
            if(thisServer == null){
                thisServer = new AtomicInteger(0);
            }
            server = allServers.get(thisServer.get());
            System.out.println(server);
            System.out.println(server.getPort());
            if(times.get() < REPEAT-1 ){
                times.getAndIncrement();
            }else{
                times.set(0);
                if(thisServer.get() < serverCount-1){
                    thisServer.getAndIncrement();
                }else{
                    thisServer.set(0);
                }
            }
            if (server == null) {
                /* Transient. */
                Thread.yield();
                continue;
            }
            if (server.isAlive() && (server.isReadyToServe())) {
                return (server);
            }
            // Next.
            server = null;
        }
        if (count >= 10) {
            log.warn("No available alive servers after 10 tries from load balancer: "
                    + lb);
        }
        return server;
    }
    @Override
    public Server choose(Object key) {
        return choose(getLoadBalancer(), key);
    }
    @Override
    public void initWithNiwsConfig(IClientConfig clientConfig) {
    }
}
@Configuration
public class RuleBean {
    //默认的负载均衡策略是轮询,通过将IRule类型的bean交给spring,可以实现别的负载均衡策略.
    @Bean
    public IRule myRule(){
        return new MyseltRule();
    }
}
  1. 通过本地接口的方式来配置远程提供的服务.注意: 使用的是@FeignClient注解,不需要@Service注解.
@FeignClient(value = "SPRINGCLOUD-PROVIDER-DEPT")
public interface DeeService {
    @PostMapping("/dept/add")
    public boolean addDept(@RequestBody Dept dept);
    @GetMapping("/dept/get/{id}")
    public Dept dept(@PathVariable("id") long id);
    @GetMapping("/dept/list")
    public List<Dept> queryAll();
}
  1. 在controller中调用本地接口.
@RestController
@RequestMapping("/consumer")
public class DeptConsumerController {
   @Autowired
   private DeeService deeService;
    @PostMapping("/dept/add")
    public boolean addDept(@RequestBody Dept dept){
        boolean flag = deeService.addDept(dept);
        return flag;
    }
    @RequestMapping("/dept/list")
    public List<Dept> getList(){
        List<Dept> depts = deeService.queryAll();
        return depts;
    }
    @RequestMapping("/dept/get/{id}")
    public Dept getDept(@PathVariable("id") long id){
        Dept dept = deeService.dept(id);
        return dept;
    }
}
  1. 配置主启动类
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients//启动feign
//如果是在java中配置负载均衡策略,就同ribbon一样开启下面的注解.
//@RibbonClient(name = "SPRINGCLOUD-PROVIDER-DEPT",configuration = RuleBean.class)
public class SpringcloudConsumerDeptFeign80Application {
    public static void main(String[] args) {
        SpringApplication.run(SpringcloudConsumerDeptFeign80Application.class, args);
    }
}
  1. 启动eureka server,启动服务提供者,启动feign项目.
  2. 访问http://localhost/consumer/dept/list,看看是否可以正常访问项目,以及负载均衡策略是否生效. 参考: feign的使用入门篇

什么是Hystrix?

断路器

Hystrix的作用是什么?

  1. hystrix的作用就是一个或者多个依赖出现问题时,系统依然可以稳定的运行,hystrix可以进行隔离,限流和降级等操作.(另外还有: 负载均衡,超时与重试,回滚,压测和预案.)
  2. 隔离: hystrix支持的隔离策略isolationStrategy包括信号量和线程池两种.信号量: 对于共享的资源,有一个初始信号量,被获取之后减1,为0后,hystrix直接返回失败.线程池: 采用jdk的线程池,默认选用不使用阻塞队列的线程池,如果某个时刻线程均被使用,则直接返回失败,起到限流的作用.原理是用户的请求不再直接访问服务,而是通过线程池中的空闲线程来访问服务,如果线程池已满,则会进行降级处理,用户的请求不会被阻塞,至少看到一个执行结果.线程池和信号量的区别如下:
    线程池隔离和信号量隔离
  3. 限流: 包括信号量,线程池,断路器.
  4. 降级: hystrix通过配置fallbackMethod指定降级时的处理方法.触发降级动作的4中情况如下:
    1. run()方法抛出非HystrixBadRequestException异常.
    2. run()方法调用超时
    3. 熔断器开启拦截调用
    4. 线程池/队列/信号量是否跑满
  5. 参考文章: Hystrix快速入门
  6. 参考文章: Hystrix原理与实战

什么是雪崩效应?

  1. 分布式系统环境下,服务间相互依赖非常常见,一个业务调用通常依赖多个基础服务.对于同步调用,当一个服务不可用时,调用它的服务可能也被拖垮,,这种不可用可能沿请求调用链向上传递,这种现象称为雪崩效应.造成雪崩效应的场景有:
  2. 硬件故障.解决: 多机房容灾,异地多活等.
  3. 流量激增.解决: 服务器自动扩容,流量控制(限流,关闭重试等)
  4. 缓存穿透: 一般发生在应用重启,所有缓存失效或者短时间大量缓存失效时.大量的缓存不命中,是请求直击后端服务,造成服务提供者超负荷运行,造成服务不可用.解决: 缓存预加载,缓存异步加载等.
  5. 程序bug: 如程序逻辑导致内存泄漏,JVM长时间fullGC等.
  6. 同步等待: 资源隔离,MQ解耦,不可用服务调用快速失败等.

什么是服务熔断?

  1. 生产者服务出问题了,生产者为了不拖垮别人,直接返回结果.
  2. 如果某个服务调用失败,调用慢或者大量超时,那么熔断该服务的调用,后续的请求,不再继续调用目标服务,而是直接返回.熔断器circuit breaker是位于线程池之前的组件,用户请求某一个服务之后,hystrix会先经过熔断器,如果熔断器的状态是打开(跳起),则说明已经熔断,直接降级处理,不会继续讲请求发送到线程池.熔断器相当于线程池之前的一层屏障.

什么是服务降级?

生产者服务出问题了,或者因为不重要被停了(降级),调用它们的服务的消费者为了不被拖垮,直接返回结果.

怎么配置生产者Hystrix熔断?

  1. 服务提供者添加依赖.在其他依赖的基础上添加: spring-cloud-starter-netflix-hystrix
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
  1. 修改配置文件中的实例名instance-id.
  2. 修改对外提供的接口,添加@HystrixCommand注解,如果该方法有问题,返回熔断方法.
@RestController
@RequestMapping("/dept")
public class DeptController {
    @Autowired
    private DeptService deptService;
    @PostMapping("/add")
    public boolean addDept(@RequestBody Dept dept){
        return deptService.addDept(dept);
    }
    @GetMapping("/get/{id}")
    @HystrixCommand(fallbackMethod = "hystrix_dept")//如果方法抛出异常,会调用fallbackMethod
    public Dept dept(@PathVariable("id") long id) throws InterruptedException {
        Dept dept = deptService.queryById(id);
        if(dept == null){
            throw new RuntimeException("不存在dept的id为"+id);
        }
        int mill = (int)(Math.random()*3000+500);
        System.out.println(mill);
        TimeUnit.MILLISECONDS.sleep(mill);
        return dept;
    }
    public Dept hystrix_dept(@PathVariable("id") long id){
        return new Dept().setDeptname("不存在该dept").setDeptno(id).setDb_source("数据库中不存在");
    }
    @GetMapping("/list")
    public List<Dept> queryAll(){
        return deptService.queryAll();
    }
}
  1. 主启动类添加@EnableCircuitBreaker注解来开启断路器.
  2. 访问http://localhost/consumer/dept/get/2进行测试.将id从2改为100试试是否走了熔断方法.

怎么配置消费者Hystrix服务降级?

  1. 由于负载均衡feign自带hystrix支持,如果有feign依赖,无需添加hystrix依赖.
  2. 修改配置文件,添加feign开启服务降级支持.
#其他配置省略
#开启服务降级
feign:
  hystrix:
    enabled: true
  1. 编写降级后执行的接口.该接口必须实现FallbackFactory接口,并务必加上@Component注解.
@Component//一定要交给spring容器管理
public class DeeServiceFallbackService implements FallbackFactory<DeeService> {
    @Override
    public DeeService create(Throwable throwable) {
        return new DeeService() {
            @Override
            public boolean addDept(Dept dept) {
                return false;
            }
            @Override
            public Dept dept(long id) {
                return new Dept().setDeptname("没有该dept").setDeptno(id).setDb_source("数据库中不存在");
            }
            @Override
            public List<Dept> queryAll() {
                return null;
            }
        };
    }
}
  1. 给消费者的接口添加@FeignClient注解,并且设置降级后执行的接口.
@FeignClient(value = "SPRINGCLOUD-PROVIDER-DEPT",fallbackFactory = DeeServiceFallbackService.class)
public interface DeeService {
    @PostMapping("/dept/add")
    public boolean addDept(@RequestBody Dept dept);
    @GetMapping("/dept/get/{id}")
    public Dept dept(@PathVariable("id") long id);
    @GetMapping("/dept/list")
    public List<Dept> queryAll();
}
  1. 启动eureka server,生产者和消费者进行测试.然后关掉生产者后再进行测试.查看效果.

什么是信号量隔离?

请求和执行下游依赖都在同一个线程内完成,不存在线程上下文切换带来的开销.

什么是线程池隔离?

用户的请求会被提交到各自的线程池中执行,把接收请求和执行请求分离.

怎么配置dashboard?

怎么解读dashboard监控页面?

什么是服务网关?

  1. 网关=路由转发+过滤器.
  2. 路由转发: 接收提供者的请求,转发到后端的微服务上.
  3. 过滤器: 在服务网关上完成一系列的横切功能,例如权限校验,限流,监控等.
  4. 网关的这些供你都是通过过滤器实现的.

为什么需要路由网关?

  1. 权限校验等功能,可以写在: 1. 每个服务中 2. 写一个公共的服务,每个服务都依赖这个服务. 3. 卸载服务网关的前置过滤器中,所有请求来进行权限校验.
  2. 如果每个服务都写,代码冗余.
  3. 如果写在公共服务中,相当于每个服务都引入了相同的代码,jar包增加了一些.代码之间的相互依赖会造成: 公共服务的代码修改后,其他的服务都需要重新引包,重新编译部署.
  4. 而将权限校验功能写在网关过滤器中,服务消费者不需要关注权限校验的代码,修改权限校验的逻辑只需要修改网关中的权限校验过滤器即可,不需要升级其他的服务.

什么是zuul?

zuul是netflix开源的一个API Gateway服务器,本质上是一个web servlet应用.zuul的核心是一系列的filters,其作用可以类比servlet框架的filter,或者aop.

zuul核心

zuul有哪些功能?

  1. 认证和安全: 识别每个需要认证的资源,拒绝不符合要求的请求.
  2. 性能监测: 在服务边界追踪并统计数据,提供精确的生产视图.
  3. 动态路由: 根据需要将请求动态路由到后端集群.
  4. 压力测试: 逐渐增加对集群的流量以了解其性能.
  5. 负载和卸载: 预先为每种类型的请求分配容量,当请求超过容量时自动丢弃.
  6. 静态资源处理: 直接在边界返回某些响应.

zuul有哪些过滤器类型?

  1. PRE: 这种过滤器在请求被路由之前调用.可以利用这种过滤器实现身份验证,在集群中选择请求的微服务,记录调试信息等.
  2. ROUTING: 这种过滤器将请求路由到微服务.这种过滤器用于构建发送给微服务的请求,并使用Apache HttpClient或者Netflix Ribbon请求微服务.
  3. POST: 这种过滤器在路由到微服务以后执行.这种过滤器可以为响应添加标准的HTTP Header,收集统计信息和指标,将响应从微服务发送给客户端.
  4. ERROR: 在其他阶段发生错误时执行该过滤器.
    zuul过滤器执行顺序
    参考链接: 微服务网关基础组件 - zuul入门

怎么使用zuul?

  1. 引入依赖: spring-cloud-starter-netflix-eureka-client和spring-cloud-starter-netflix-zuul.
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
  1. 修改配置
server:
  port: 9527
spring:
  application:
    name: springcloud-zuul-gateway #本服务的服务id
eureka:
  client:
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/ #eureka集群
  instance:
    prefer-ip-address: false #true时访问路径可以显示ip地址
    instance-id: gateway9527.com #本服务的实例id
zuul:
  routes:
    dept:
      serviceId: springcloud-provider-dept #原服务的eureka serviceId,注意是小写的,而不是页面上大写的.
      path: /d/** #新的访问路径
  ignored-services: "*" #原路径不再可以访问
ribbon:
  eureka:
    enabled: false #禁止eureka服务,防止ribbon,hystrix服务失效
springcloud-provider-dept:
  ribbon:
    listOfServers: localhost:8001,localhost:8002,localhost:8003 #禁止eureka服务后,手动列出服务列表
  1. 在主启动类加上@EnableZuulProxy注解,开启zuul服务.
@SpringBootApplication
@EnableZuulProxy//开启zuul
public class SpringcloudZuulGateway9527Application {
    public static void main(String[] args) {
        SpringApplication.run(SpringcloudZuulGateway9527Application.class, args);
    }
}

什么是springcloud config?

  1. 配置文件上云.通过config server来访问配置文件.
  2. 分布式系统的配置管理方案,它包含了client和server两个部分,server提供配置文件的存储,以接口的形式将配置文件的内容提供出去.client通过接口获取数据,并依据此数据初始化自己的应用.
  3. 参考文章: Spring Cloud Config 实现配置中心,看这一篇就够了

怎么把本地文件提交到码云?

  1. 现在码云创建仓库
  2. 将仓库git clone到本地
  3. 在本地添加文件
  4. git add .
  5. git commint -m "xxx"
  6. git push origin

怎么配置config-server?

  1. 添加config server的依赖: spring-cloud-config-server
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-config-server</artifactId>
</dependency>
  1. 修改配置: 配置端口号和git地址
server:
  port: 3344
spring:
  application:
    name: springcloud-config-server
  cloud:
    config:
      server:
        git:
          uri: https://gitee.com/moralland/springcloudconfig.git #git地址
  1. 使用@EnableConfigServer注解开启config server功能
@SpringBootApplication
@EnableConfigServer
public class SpringcloudConfigServer3344Application {
    public static void main(String[] args) {
        SpringApplication.run(SpringcloudConfigServer3344Application.class, args);
    }
}
  1. 可以访问http://localhost:3344/client-config.yml访问配置,或者http://localhost:3344/client-config-dev.yml看不同版本的配置.

怎么配置config-client?

  1. 引入config client依赖: spring-cloud-starter-config
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-config</artifactId>
</dependency>
  1. 修改配置文件
spring:
  cloud:
    config:
      label: master #分支
      name: client-config #配置文件名,去掉后缀
      uri: http://localhost:3344 #config server服务器地址
      profile: test #profile
  1. 将配置文件上传到git地址
spring:
  profiles:
    active: test #这里可以不配置.版本以本地的配置为准.
---
server:
  port: 8201
spring:
  profiles: dev
  application:
    name: srpingcloud-provider-dept
eureka:
  client:
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/
---
server:
  port: 8202
spring:
  profiles: test
  application:
    name: srpingcloud-provider-dept
eureka:
  client:
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/
  1. 写一个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 serverPort;
    @RequestMapping("/config")
    public String getConfig(){
        return
                "application:"+applicationName+
                ",eurekaServer:"+eurekaServer+
                ",serverPort:"+serverPort;
    }
}
  1. 启动项目.访问配置文件中的端口测试
  2. 通过config-server的ip和端口号访问配置文件: http://localhost:3344/client-config.yml.如果是不同的profile,在文件名后加上profile,例如: -dev.

bootstrap.yml和application.yml有什么区别?

加载顺序不一样.bootstrap.yml>application.yml>application-dev.yml.

疑问: 配制了congfig-server之后,切换环境是不是还是要在本地进行?

本地指定的profile优先级是最高的.可以通过修改云端对应的profile的内容来达到远程修改配置的效果.但是云端确实无法修改profile.