SpringCloud

概念

  1. 集群

     多个相同的服务器完成相同的任务;提高吞吐量,减少服务器压力
    
  2. 分布式 、微服务

     功能模块拆分成为单独的项目,独立运行;
     利于解耦,减少单个服务器压力,提高吞吐量
    
  3. SpringCloud

     基于SpringBoot,约定大于配置,开箱即用;
     方便构建分布式系统;
     的一个工具集;
    

组件:Eureka & Ribbon & Feign &Zuul

  • 单体项目性能差

  • ——功能拆分为独立项目(分布式
    ——每个功能多个服务器共同运作(集群

      不同项目的服务需要互相调用协作(分布式)
      运作相同功能的服务器被调用的频率需要控制以达到性能最优(集群)
    
  • ——需要管理服务列表 :名称、地址、可用性,以便各个服务之间方便地互相调用
    ——需要配置服务的访问策略,以及服务宕机时的重试机制/服务熔断

  • ——Eureka通过心跳机制、服务拉取和自我保护机制管理服务清单及其可用性
    ——Ribbon通过多种策略维护访问的频率,通过retry重试机制在轮询到的服务宕机时及时转向请求下一个
    ——Zuul通过服务降级、服务熔断,避免 对单个不可用服务的多个并发请求 导致 线程阻塞,线程资源消耗完毕,引发服务雪崩。

      服务宕机
      	Eureka会剔除失效服务,但需要一定时间,在此时间内若轮询到了失效服务,会出现错误页面,用户体验较差,故还是需要ribbon进行重试
      zuul和ribbon重试机制的区别
      	重试机制适用于单体服务器本身的问题,可以通过重试(换一个服务器)解决;
      	若是业务逻辑本身的问题,只能通过Zuul对资源的处理(服务熔断等)避免产生更大雪崩
    
  • ——feign 简化不同服务之间的调用过程

Eureka-服务治理

  1. 引入依赖(POM.xml)

    服务注册中心引入 spring-cloud-starter-netflix-eureka-server
    服务提供者和消费者引入 spring-cloud-starter-netflix-eureka-client

    (eureka替换为consol也可,以下全部内容无需改动)

  2. 配置Eureka注册中心集群(application.yml)

    1. 维护自己的端口号server:port: 8081
    2. 向集群其他注册中心注册为clienteureka:client:service-url:defaultZone: http://localhost:8082/eureka
    3. 管理向自己注册的服务server: eviction-interval-timer-in-ms: 3000 server: enable-self-preservation: false
  3. 配置服务提供者(application.yml)
    1. 维护自己的端口号server:port: 8089
    2. 向集群全部注册中心注册为clienteureka:client:service-url:defaultZone: http://localhost:8082/eureka,http://localhost:8082/eureka
    3. 向注册中心定期发送心跳,定义多久无心跳即为失效eureka:instance:lease-expiration-duration-in-seconds: 9 eureka:instance:lease-renewal-interval-in-seconds: 3

  4. 配置服务消费者(application.yml)
    1. 维护自己的端口号server:port: 8088
    2. 向集群其他注册中心注册为clienteureka:client:service-url:defaultZone: http://localhost:8082/eureka
    3. 向注册中心定期拉取服务清单的频率:eureka:client:registry-fetch-interval-seconds: 3

所有eureka实例(注册中心、服务端、消费端)均可配置实例名

	 instance:
	    instance-id: ${spring.application.name}:${server.port}
	    prefer-ip-address: true
  1. 消费端向服务端通信
    0. 定义OpenService(即消费端的Controller,对外开放请求)
    1. DiscoveryClient实例.getInstances(“服务名”).get(index) 获取ServiceInstance对象
    2. ServiceInstance对象.getHost() ServiceInstance对象.getProt() 拼接请求地址
    3. RestTemplate对象.getForObject(url,返回类型) 向服务端请求并获取返回结果
复杂类型的通信
  1. 返回结果为List:使用数组接收(见UserController02 中第1个方法)

  2. 自定义的返回结果类中包含泛型:将泛型声明在类上即可(见UserController02 中第2个方法)

@Data
//自定义返回结果类,包含泛型
//注意,泛型若声明在参数中,则消费端无法获取泛型类型导致无法取到值
public class Result<T> {

    Integer code;
    List<T> list;
    String status;

    public Result(){}

    public Result(String status, Integer code, List<T> list){
        this.code=code;
        this.list=list;
        this.status=status;
    }
}


@RestController
public class UserController02 {

    @Resource
    private RestTemplate template;

    @Resource
    private DiscoveryClient client;

    @RequestMapping("/u")
    public User[] getUsers(){
        ServiceInstance serviceInstance = client.getInstances("userServer01").get(0);
        String url = "http://" + serviceInstance.getHost()+":"+serviceInstance.getPort()+"/";
        User[] users = template.getForObject(url, User[].class);
        return  users;
    }

    @RequestMapping("/r")
    public Result getRes(){
        ServiceInstance serviceInstance = client.getInstances("userServer01").get(0);
        String url = "http://" + serviceInstance.getHost()+":"+serviceInstance.getPort()+"/res";
        Result<User> result = template.postForObject(url,null, Result.class);
        return  result;
    }
}

Ribbon-客户端负载均衡

Ribbon源码解析

在eureka的基础上:

  1. 引入 spring-cloud-starter-netflix-ribbon依赖
    在父POM或服务提供者和消费者POM都引入)
  2. 配置服务提供者集群(如下)
    拥有相同的服务功能(controller),相同的服务名称(application:name),不同的端口号(port)
spring:
  application:
    name: provider

server:
  port: 8083

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8081/eureka, http://localhost:8082/eureka
  instance:
    lease-renewal-interval-in-seconds: 3
    lease-expiration-duration-in-seconds: 9
    instance-id: ${spring.application.name}:${server.port}
    prefer-ip-address: true
spring:
  application:
    name: provider

server:
  port: 8083

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8081/eureka, http://localhost:8082/eureka
  instance:
    lease-renewal-interval-in-seconds: 3
    lease-expiration-duration-in-seconds: 9
    instance-id: ${spring.application.name}:${server.port}
    prefer-ip-address: true
  1. 服务消费者启用负载均衡(如下)
    在创建RestTemplate类的方法上增加@LoadBalanced注解

     告知Spring,启用了负载均衡,故后续调用getForObject方法时会对URL按负载均衡的形式进行解析
    
@SpringBootApplication
@EnableDiscoveryClient
public class ConsumerApp {

    @LoadBalanced
    @Bean
    public RestTemplate getRestTemplate(){
        return  new RestTemplate();
    }

    public static void main(String[] args) {
        SpringApplication.run(ConsumerApp.class, args);
    }
}
  1. 服务消费者获取具体服务实例(如下)
    服务消费者web层装配一个LoadBalancerClient类实例,用于调用choose方法,传入serviceId,获取一个服务实例ServiceInstance

     原理:拦截器拦截你的请求后:
     		1. 根据IloadBalancer策略类,默认使用 实现类ZoneAwareLoadBalancer  策略(同一个区域内负载较少的),从中选取一个Eureka中心实例
     		2. 根据实例名,得到字节码对象,从而获取服务实例对象列表
     		3. BaseLoadBalancer根据默认策略(RoundRibbonRuleBalancer轮询) 选择一个服务实例
     		4. 调用LoadBalanceClient的实现类RibbonLoadBalancerClient的execute方法执行请求
    

    修改负载均衡策略(可选)

    provider(你的serviceID):
      ribbon:
        NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule(随机)
    

    自定义负载均衡策略(可选)
    a. 定义一个类,继承AbstractLoadBalancerRule,重写choose方法
    b. 在yaml中配置NFLoadBalancerRuleClassName

  2. 调用服务,获取结果(如下)
    使用RestTemplate实例调用getForObject方法,传入url和返回对象class

     此时URL是http://serviceId/+参数 ,不再是 ServiceInstance.getHost()+ServiceInstance.getPort()。因为spring会解析
    
@RestController
public class ConsumerController {

    @Resource
    private RestTemplate template;

    @Resource
    LoadBalancerClient client;

    @RequestMapping(value = "/",method = RequestMethod.GET)
    public User test(){
        ServiceInstance instance = client.choose("provider");
        String url ="http://provider/";
        User u = template.getForObject(url, User.class);
        System.out.println(u);
        return u;
    }
}

重试机制
  1. 添加依赖spring-retry

  2. 配置重试参数

provider:
  ribbon:
#    200ms连接不上就重试
    ConnectTimeout: 200
#    100ms读不到/读不完就重试
    ReadTimeout: 100
#   对所有操作重试
    OkToRetryOnAllOperations: true
#   切换的间隔
    MaxAutoRetriesNextServer: 1
#   重试次数
    MaxAutoRetries: 1

feign——简化调用

简单使用
  1. 引入依赖spring-cloud-starter-openfeign

    必须是openfeign而不是feign!否则报错!

  2. 消费端仿造接口
    定义一个interface,使用@FeignClient(value="你的serviceID")注解
    接口中定义一个 抽象方法 ,添加请求映射@requestMapping(value="需要调用的对方接口名")

  1. 消费端开启Feign
    启动类使用@EnableFeignClients注解
    调用仿造接口的抽象方法,实现对其他服务的间接调用

     *无需再拼接URL、创建RestTemplate实例、DiscoveryClient实例
    
  2. 默认集成了Ribbon客户端负载均衡
    默认轮询,无需单独引入组件或配置(也可在yaml中照常配置)

聚合项目

背景

原来即便使用了Feign,仍旧需要服务消费者(FeignConsumer)手动编写接口类和抽象方法,使其与服务提供方(Provider)的请求接口一致。但这在实际跨公司跨服务的项目中可行性较低(对方请求接口的变更,调用者无法得到通知)。
使用

  1. 由Provider直接对外提供接口

    1. 将项目变成接口子项目ProviderInterface和实现子项目ProviderImpl的聚合项目(父项目Provider打包成pom)
      在这里插入图片描述

    2. ProviderInterface中定义要提供的服务接口,并封装实体类,以便完整对外提供

      @Data
      @NoArgsConstructor
      @AllArgsConstructor
      public class User {
      
          String username;
          Integer age;
      }
      
      
      public interface Pro1Api {
          @RequestMapping(value = "/",method = RequestMethod.GET)
          User pro1();
      }
      
      
    3. ProviderImpl依赖ProviderInterface并实现具体接口方法

       <dependencies>
              <dependency>
                  <groupId>com.zoya.springcloud</groupId>
                  <artifactId>proAPI1-Interface</artifactId>
                  <version>1.0-SNAPSHOT</version>
              </dependency>
          </dependencies>
      
      @RestController
      public class Pro1Controller implements Pro1Api {
          @RequestMapping(value = "/",method = RequestMethod.GET)
          public User pro1() {
              return new User("pro1Controller",1);
          }
      }
      
    4. ProviderImpl向Eureka注册为服务提供者
      (ProviderImpl的application.yaml中正常配置即可)

    5. 启动ProviderImpl(启动实现子项目作为服务,而非父项目或接口子项目)

      @SpringBootApplication
      @EnableDiscoveryClient
      public class ImplApp {
          public static void main(String[] args) {
              SpringApplication.run(ImplApp.class, args);
          }
      }
      
  2. FeignConsumer依赖ProviderInterface并继承接口即可

    1. 引入ProviderInterface依赖

       <dependencies>
              <dependency>
                  <groupId>com.zoya.springcloud</groupId>
                  <artifactId>proAPI1-Interface</artifactId>
                  <version>1.0-SNAPSHOT</version>
              </dependency>
          </dependencies>
      
    2. 编写接口类继承ProviderInterface提供的接口,@FeignClient(value=“Serviceid”)

       通过ServiceID得到服务列表;
       通过继承的Ribbon负载均衡后得到具体服务实例;
       通过继承得到的方法中的注解映射到具体的对方服务;
       ——消费者端即获得了与对方服务接口对应的众多接口方法
      
      @FeignClient(value = "provider-api")
      public interface myFeign extends Pro1Api {
      }
      
    3. 调用Feign接口的抽象方法,对外提供服务消费

      @RestController
      public class FeignController {
          @Autowired
          private myFeign feign;
      
          @RequestMapping(value = "/get",method = RequestMethod.GET)
          public User getUser(){
              return feign.pro1();
          }
      }
      

请求过程分析

  1. 从浏览器请求consumer对外暴露的接口http://localhost:XXXX/get:
  2. 进入消费端,映射到 消费端web层 的 FeignController 的 getUser() 方法
  3. FeignController的getUser()调用了MyFeign接口的getUser()方法
  4. MyFeign接口通过注解声明自己是一个FeignClient
  5. 根据MyFeign声明的serviceid,获取一个服务实例providerXX
  6. 根据MyFeign继承来的方法及其@requestMapping注解,获取具体请求URL
  7. 调用到服务实例的某个请求方法

Hystrix

  1. 服务降级
    接口/资源 不可用时,不进行阻塞,直接FallBack返回一个固定结果
  2. 服务熔断
    1. 降级的一种,从整个服务全局角度考虑资源
    2. 请求失败已达到阈值频次时,不再尝试请求,直接FallBack返回一个固定结果),定时再尝试恢复
    3. 使用 状态机 模型,判断 一定时间内错误请求的比例 ,达到阈值则状态机 打开 ,一定时间后改为 半开 (放过一些请求),被放过的请求没问题则状态机 关闭 ,请求正常通过。
  3. 资源隔离/限流(一般不通过状态机)
    单独为某个接口限制访问线程数/每秒访问次数
    通过缓存计数,每个IP对资源的访问频率,如果达到阈值则禁止访问

Zuul

过滤:
某些服务不经过网关(如文件上下传,会占用大量资源,不应经过网关)
前缀:
修饰作用,可以用于标识服务走了网关(如 /route 前缀)

Nginx

Nginx和Tomcat的异同

服务器服务器类型功能
Nginx
apahce
WEB服务器接受请求,做出响应
apahce Tomcat
WebLogic
iis
web应用服务器能充当web服务器,也能充当应用的运行环境,来运行程序

背景:高并发问题

通常对一个页面的加载需要向服务器发出几十至上千个请求,在高并发场景下并发请求数量巨大,而Tomcat可以负荷的并发请求理论值为200-500,完全无法应对。

故在请求到达Tomcat之前,提前对静态请求拦截,并直接返回静态资源;只放过需要请求后台数据的给Tomcat,能够有效利用服务器资源。(即反向代理)

	反向代理:为服务器提供代理web请求
	正向代理:为客户端提供代理

作用

  1. 反向代理,提高服务器资源利用率
  2. 集成了负载均衡策略,可以更高效的进行Tomcat集群

安装使用及配置

安装和基础使用参考方志鹏的专栏:Nginx介绍

  1. nginx使用C语言编写,故更适合运行于linux系统下

  2. windows和linux系统下都有/etc/hosts.conf文件,可以配置ip地址对应的server名称(域名);

     对外暴露域名而不暴露IP,提高安全性;
     用户使用域名访问,浏览器会优先查询用户本地的host文件,从本地匹配IP,解析实际的访问IP对象;本地没有则到域名解析器去查找。
    
  3. Tomcat集群+负载均衡
    nginx可以指定tomcat_server_pool(Tomcat集群),将一个域名映射到一个服务器集群,更大的提高Tomcat对并发的支持量;

    对集群中具体访问哪个服务器的分配(负载均衡策略)使用轮询策略

     1.需要强制刷新
     2.可以使用权重策略,如下代码中,5次访问aaa.test2.com中,1次由8220处理,1次由8230处理,3次由8240处理(具体权重值由运维人员根据服务器性能并调试得出)
     3.以上策略,可能出现同一个用户多次访问,会轮询到不同服务器处理,其session信息无法同步的问题。故可以使用 ip_hash; 告知nginx针对同一个ip,访问相同的服务器。
     	此时即便强制刷新也会一直由服务器处理。
    
#nginx.conf文件中配置多个虚拟主机(server)
upstream tomcat_server1 {
        server zoyalinux:8210;
   }

	
upstream tomcat_server_pool{
		ip_hash; 
        server zoyalinux:8220 weight=1;
        server zoyalinux:8230 weight=1;
        server zoyalinux:8240 weight=3;
  }
server {
        listen       80;
        server_name  zoyalinux;
		
        location / {
            proxy_pass   html;
            index  index.html;
        }
    }        
server {
        listen       90;
        server_name  zoyalinux;
		
        location / {
            proxy_pass   http://tomcat_server1;
            index  index2.html;
        }
    }
	server {
        listen       80;
        server_name  aaa.test1.com;
		
        location / {
            proxy_pass   http://tomcat_server1;
            index  index1.html;
        }
    }

	server {
        listen       80;
        server_name  aaa.test2.com;
		
        location / {
            proxy_pass   http://tomcat_server_pool;
            index  index2.html;
        }
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值