SpringCloud01

Spring Cloud是一系列框架的有序集合。它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用Spring Boot的开发风格做到一键启动和部署。Spring Cloud并没有重复制造轮子,它只是将各家公司开发的比较成熟、经得起实际考验的服务框架组合起来,通过Spring Boot风格进行再封装屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂、易部署和易维护的分布式系统开发工具包。
共分四天进行学习

一、微服务基础知识

1.1 项目架构的演变

项目架构的演变:单体应用架构→垂直应用架构→分布式架构→微服务架构
项目架构的演变在这里插入图片描述分布式架构的缺点:

  • 抽取服务的粒度较大
  • 服务提供方与调用接口方耦合度较高

为了解决以上缺点,微服务架构被提出。
在这里插入图片描述微服务的优点

  • 通过服务的原子化拆分,以及微服务的独立打包、部署和升级,小团队的服务周期将缩短,运维成本也将大幅下降
  • 微服务遵循单一原则。微服务之间采用Restful等轻量协议传输

微服务的缺点:

  • 微服务过多,服务治理成本高,不利于系统维护
  • 分布式系统开发的技术成本高(容错、分布式事务等)
1.2核心概念
1.2.1远程调用技术

在这里插入图片描述比较流行的远程调用技术:

  • RPC:RPC协议假定某些传输协议的存在,如TCP或UDP,为通信程序之间携带信息数据。在OSI网络通信模型中,RPC跨越了传输层和应用层。RPC使得开发包括网络分布式多程序在内的应用程序更加容易。 下图为RPC的过程。
    在这里插入图片描述

  • HTTP:发出请求,得到响应,比较简单。
    HTTP与RPC进行比较
    在这里插入图片描述

1.2.2CAP原理

在这里插入图片描述

1.3 常见的微服务框架
1.3.1 SpringCloud介绍

Spring Cloud是一系列框架的有序集合。它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用Spring Boot的开发风格做到一键启动和部署。Spring Cloud并没有重复制造轮子,它只是将各家公司开发的比较成熟、经得起实际考验的服务框架组合起来,通过Spring Boot风格进行再封装屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂、易部署和易维护的分布式系统开发工具包。

1.3.2 通过RestTemplate远程调用服务
  • 1、创建商品服务

使用RestTemplate进行服务之间的远程调用,首先创建商品服务product_service,按照MVC设计模式,使用SpringBoot创建一个简单的能够完成增删改查功能的项目,代码比较简单就不再写了。

  • 2 创建订单服务,调用商品服务
    在工程内创建订单服务,跨模块调用商品服务。此处使用HTTP协议进项远程调用。步骤如下:
  1. 在订单服务的启动器类中创建RestTemplate 对象,用来访问商品服务。代码如下:
    /**
     * 创建RestTemplate对象,交给IOC容器管理,用来访问product_service
     * @param args
     */
    @Bean
    public RestTemplate restTemplate(){
        
        return new RestTemplate();
    }

  1. 在订单的controller中调用商品服务。代码如下:
package com.runze.order.controller;

import com.runze.order.domain.Product;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
@RequestMapping("/order")
public class OrderController {

    @Autowired
    private RestTemplate restTemplate;
    /**
     * 参数:商品ID
     *  通过订单系统,调用商品服务根据商品id查询商品信息
     *  1、需要配置商品对象
     *  2、需要调用商品服务
     *
     */
    @GetMapping("/buy/{id}")
    public Product findProductById(@PathVariable Long id){
        Product product = null;
        product=restTemplate.getForObject("http://127.0.0.1:9001/product/"+id,Product.class);
        return product;
    }
}

如上,商品就做为一个的小型的微服务供其他服务调用,但是这种微服务有很多弊端,如下图:
在这里插入图片描述
所以,我们使用SpringCloud来创建微服务项目。

二 SpringCloud框架

SpringCloud中常见的组件介绍

2.1 注册中心

注册中心的作用如下图
常见的注册中心有以下四种
在这里插入图片描述

2.1.1 Eureka介绍

Eureka是Netflix开发的服务发现框架,本身是一个基于REST的服务,主要用于定位运行在AWS域中的中间层服务,以达到负载均衡和中间层服务故障转移的目的。
SpringCloud将它集成在其子项目spring-cloud-netflix中,以实现SpringCloud的服务发现功能。其工作流程如下图所示:
在这里插入图片描述使用步骤

  1. 搭建eureka server
    1.1 创建工程
    1.2 导入坐标
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
    </dependencies>

1.3 配置application.yml

server:
  port: 9000

eureka:
  instance:
    hostname: localhost
  client:
    register-with-eureka: false #是否将自己注册到注册中心
    fetch-registry: false #是否从eureka中获取注册信息
    service-url: #暴露给eureka.Client的请求地址
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/

1.4 配置启动类

package com.runze.eureka;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@SpringBootApplication
//激活eureka Server
@EnableEurekaServer
public class EurekaServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaServerApplication.class,args);
    }
}

  1. 将服务提供者注册到eureka Server上
    2.1 引入EurekaClient的坐标
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netfix-eureka-client</artifactId>
        </dependency>

2.2 修改application.yml添加EurekaClient的信息

eureka:
  client:
    service-url: #暴露给eureka.Client的请求地址
      defaultZone: http://localhost:9000/eureka/

2.3修改启动类,添加服务发现的支持(可选)

  1. 服务消费者通过注册中心获取服务列表并调用
    3.1 引入EurekaClient的坐标
    3.2 修改application.yml添加EurekaClient的信息
    3.3修改启动类,添加服务发现的支持(可选)
    这三步同上,不写了
    4.3 获取注册中心中服务提供者的信息
package com.runze.order.controller;


import com.runze.order.domain.Product;
import net.bytebuddy.asm.Advice;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import java.util.List;

@RestController
@RequestMapping("/order")
public class OrderController {

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private DiscoveryClient discoveryClient;
    /**
     * 参数:商品ID
     *  通过订单系统,调用商品服务根据商品id查询商品信息
     *  1、需要配置商品对象
     *  2、需要调用商品服务
     *
     */
    @GetMapping("/buy/{id}")
    public Product findProductById(@PathVariable Long id){

        //从eureka service中获取服务提供者的信息
        List<ServiceInstance> instances = discoveryClient.getInstances("service-product");
        ServiceInstance serviceInstance = instances.get(0);
        //无需将服务提供方的url写死,随用随改。
        Product product=restTemplate.getForObject("http://"+serviceInstance.getHost()+":"+serviceInstance.getPort()+"/product/"+id,Product.class);
        return product;
    }
}

2.1.2 高可用的引入,Eureka Server之间相互注册在这里插入图片描述##### 2.1.3 Eureka的细节问题
  1. 在Eureka的注册中心中显示服务提供者的ip地址
eureka:
  client:
    service-url: #暴露给eureka.Client的请求地址
      defaultZone: http://localhost:9000/eureka/
    instance:
      prefer-ip-address: true #使用ip注册

  instance:
    instance-id: ${spring.cloud.client.ip-address}:${server.port} #设置Eureka中显示服务提供者的ip地址
    

  1. 设置续约到期的时间以及发送心跳的间隔
  instance:
    #设置Eureka中显示服务提供者的ip地址
    instance-id: ${spring.cloud.client.ip-address}:${server.port}
    # 设置心跳的间隔
    lease-renewal-interval-in-seconds: 5
    # 设置续约到期的时间
    lease-expiration-duration-in-seconds: 10
  1. Eureka的自我保护机制
eureka:
  instance:
    hostname: localhost
  client:
    register-with-eureka: false #是否将自己注册到注册中心
    fetch-registry: false #是否从eureka中获取注册信息
    service-url: #暴露给eureka.Client的请求地址
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
  server:
    enable-self-preservation: false #关闭自我保护机制
    eviction-interval-timer-in-ms: 4000 #测试阶段设置剔除服务间隔

2与3测试阶段可用,服务上线后不建议使用

2.1.4 Eureka源码详解
  1. Spring Boot的自动装载,使用一个小案例演示
package com.runze.damain;

import lombok.Data;

@Data
public class User {
    private String name;
    private Integer id;
}

package com.runze.damain;

import org.springframework.context.annotation.Bean;

public class UserConfiguration {

    @Bean
    public User getUser(){
        User user=new User();
        user.setName("李华");
        user.setId(1);
        return user;
    }
}

package com.runze.damain;

import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;

public class UserImportSelector implements ImportSelector{
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        return new String[]{UserConfiguration.class.getName()};
    }
}

package com.runze.damain;

import org.springframework.context.annotation.Import;

import java.lang.annotation.*;

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(UserImportSelector.class)
public @interface EnableUserBean {
}

package com.runze.test;

import com.runze.damain.EnableUserBean;
import com.runze.damain.User;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

@EnableUserBean
public class TestEnableUserBean {
    public static void main(String[] args) {
        /**
        *@ClassName: -->EnableUserBean--> UserImportSelector--> UserConfiguration--> User
        *@Param
        *@Author 李润泽
        *@Return
        *@Time
        */
        AnnotationConfigApplicationContext ac=new AnnotationConfigApplicationContext(TestEnableUserBean.class);
        User bean = ac.getBean(User.class);
        System.out.println(bean);
    }
}

运行流程:测试类–>EnableUserBean–> UserImportSelector–> UserConfiguration–> User
2. Eureka的启动流程
.

2.2 Ribbon
2.2.1Ribbon概述

Spring Cloud Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,它基于Netflix Ribbon实现。通过Spring Cloud的封装,可以让我们轻松地将面向服务的REST模版请求自动转换成客户端负载均衡的服务调用。Spring Cloud Ribbon虽然只是一个工具类框架,它不像服务注册中心、配置中心、API网关那样需要独立部署,但是它几乎存在于每一个Spring Cloud构建的微服务和基础设施中。因为微服务间的调用,API网关的请求转发等内容,实际上都是通过Ribbon来实现的。所以,对Spring Cloud Ribbon的理解和使用,对于我们使用Spring Cloud来构建微服务非常重要

2.2.2 Ribbon功能

1.服务调用

  • 在RestTemplate上加上注解 @LoadBalanced
  • 使用服务名代替IP地址
 @GetMapping("/buy/{id}")
    public Product findProductById(@PathVariable Long id){
        Product product=restTemplate.getForObject("http://service_product/product/1",Product.class);
        return product;
    }

2.负载均衡
在这里插入图片描述Ribbon是一个典型的客户端负载均衡器,Ribbon会获取服务的所有地址,根据其内部的负载均衡算法,获取本次请求的有效地址。Ribbon的负载均衡策略如下:
在这里插入图片描述
3. ribbon源码分析
在这里插入图片描述

2.3 Consul

consul是google开源的一个使用go语言开发的服务发现、配置管理中心服务。内置了服务注册与发现框 架、分布一致性协议实现、健康检查、Key/Value存储、多数据中心方案,不再需要依赖其他工具(比如ZooKeeper等)。服务部署简单,只有一个可运行的二进制的包。每个节点都需要运行agent,他有两种运行模式server和client。每个数据中心官方建议需要3或5个server节点以保证数据安全,同时保证server-leader的选举能够正确的进在这里插入图片描述
Consul与Eureka的区别与联系
在这里插入图片描述
启动Consul
在这里插入图片描述

2.3.1 Consul入门

导入maven坐标

        <!--springcloud 提供的对基于consul的服务发现-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-consul-discovery</artifactId>
        </dependency>
        <!--actuator的健康检查-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

服务提供方的配置

  cloud:
    consul:
      host: 127.0.0.1 #consul服务器的主机地址
      port: 8500 #consul服务器的ip地址
      discovery:
        #是否需要注册
        register: true
        #注册的实例ID (唯一标志)
        instance-id: ${spring.application.name}-1
        #服务的名称
        service-name: ${spring.application.name}
        #服务的请求端口
        port: ${server.port}
        #指定开启ip地址注册
        prefer-ip-address: true
        #当前服务的请求ip
        ip-address: ${spring.cloud.client.ip-address}


服务消费者的配置

  cloud:
    consul:
      host: 127.0.0.1 #consul服务器的主机地址
      port: 8500 #consul服务器的ip地址
      discovery:
        #是否需要注册
        register: true
        #注册的实例ID (唯一标志)
        instance-id: ${spring.application.name}-1
        #服务的名称
        service-name: ${spring.application.name}
        #服务的请求端口
        port: ${server.port}
        #指定开启ip地址注册
        prefer-ip-address: true
        #当前服务的请求ip
        ip-address: ${spring.cloud.client.ip-address}

在服务消费者的启动器上@LoadBalanced,Ribbon的负载均衡支持

@SpringBootApplication
@EntityScan("cn.runze.order.entity")
public class OrderApplication {


	/**
	 * springcloud对consul进行了进一步的处理
	 *  向其中集成了ribbon的支持
	 */

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

	public static void main(String[] args) {
		SpringApplication.run(OrderApplication.class,args);
	}
}

在调用服务的地方直接写服务名称即可调用

package cn.runze.order.controller;

import cn.runze.order.entity.Product;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
@RequestMapping("/order")
public class OrderController {

	@Autowired
	private RestTemplate restTemplate;

	@RequestMapping(value = "/buy/{id}",method = RequestMethod.GET)
	public Product findById(@PathVariable Long id) {
		Product product = restTemplate.getForObject("http://service-product/product/1",Product.class);;
		return product;
	}

}

2.3.2 Consul高可用集群

暂时没法演示

2.3 Feign

Feign是Netflix开发的声明式,模板化的HTTP客户端,其灵感来自Retrofit,JAXRS-2.0以及WebSocket.

  • Feign可帮助我们更加便捷,优雅的调用HTTP API。
  • 在SpringCloud中,使用Feign非常简单——创建一个接口,并在接口上添加一些注解,代码就完成了。
  • Feign支持多种注解,例如Feign自带的注解或者JAX-RS注解等。
  • SpringCloud对Feign进行了增强,使Feign支持了SpringMVC注解,并整合了Ribbon和Eureka,
    从而让Feign的使用更加方便。
2.3.1 Feign入门
  1. 导入依赖
 <!--在消费者的pom文件上添加feign依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
  1. 配置调用接口
package com.runze.order.feign;

import com.runze.order.domain.Product;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

/**
 * 生命需要调用的微服务名称
 *  @FeignClient
 *      *name:调用的服务的名称
 */

@FeignClient("service-product")
@Component
public interface ProductFeignClient {
    /**
     * 配置需要调用的微服务接口
     */
    @RequestMapping(value="/product/{id}",method= RequestMethod.GET)
    public Product findById(@PathVariable("id") Long id);


}

  1. 在启动类上激活feign
@SpringBootApplication
@EntityScan("com.runze.order.domain")
@EnableEurekaClient
@EnableFeignClients
  1. 通过自动的接口调用远程微服务
package com.runze.order.controller;


import com.runze.order.domain.Product;
import com.runze.order.feign.ProductFeignClient;
import net.bytebuddy.asm.Advice;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import java.util.List;

@RestController
@RequestMapping("/order")
public class OrderController {

    @Autowired
    private ProductFeignClient feignClient;

    //使用Ribbon进行远程调用
    @GetMapping("/buy/{id}")
    public Product findProductById(@PathVariable Long id){
        Product product = feignClient.findById(id);
        return product;
    }

2.3.2 Feign的负载均衡

以轮询的方式进行访问服务提供者

2.3.3 feign配置日志输出

在服务调用者的配置文件中配置如下内容即可


# 配置feign的日志输出
# 日志级别NONE:不输出日志 BASIC:适用于生产环境追踪问题 HEADERS:在BASIC的基础上,记录请求和响应头问题 FULL:记录所有
feign:
  client:
    config:
     service-product:
      loggerlevel: FULL
logging:
  level:
    com.runze.order.feign.ProductFeignClient: debug

2.3.3 feign源码分析

在这里插入图片描述

2.4高并发问题
2.4.1使用JMeter模拟高并发情况

在这里插入图片描述如何解决由于请求积压造成的服务崩溃问题:服务隔离的方式,具体如下图
在这里插入图片描述服务隔离分为两类:

  1. 线程池隔离:就是对多个服务单独创建线程池,防止由于某个服务访问量过多导致其他服务无法使用的问题。
  2. 信号量隔离:实际就是一个计数器,设置某个服务的最大访问量,如果超出这个阈值,就会直接报错,无法访问。
2.4.2使用线程池隔离解决某一服务访问量过大的问题
  1. 导入坐标
       <dependency>
            <groupId>com.netflix.hystrix</groupId>
            <artifactId>hystrix-metrics-event-stream</artifactId>
            <version>1.5.12</version>
        </dependency>
        <dependency>
            <groupId>com.netflix.hystrix</groupId>
            <artifactId>hystrix-javanica</artifactId>
            <version>1.5.12</version>
        </dependency>

2.创建OrderCommand类,设置线程池参数

package com.runze.order.command;


import com.netflix.hystrix.*;
import com.runze.order.domain.Product;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.client.RestTemplate;

public class OrderCommand extends HystrixCommand<Product> {

    private RestTemplate restTemplate;

    private Long id;

    public OrderCommand(RestTemplate restTemplate, Long id) {
        super(setter());
        this.restTemplate = restTemplate;
        this.id = id;
    }

    private static Setter setter() {

        // 服务分组
        HystrixCommandGroupKey groupKey = HystrixCommandGroupKey.Factory.asKey("order_product");
        // 服务标识
        HystrixCommandKey commandKey = HystrixCommandKey.Factory.asKey("product");
        // 线程池名称
        HystrixThreadPoolKey threadPoolKey = HystrixThreadPoolKey.Factory.asKey("order_product_pool");
        /**
         * 线程池配置
         *     withCoreSize :  线程池大小为10
         *     withKeepAliveTimeMinutes:  线程存活时间15秒
         *     withQueueSizeRejectionThreshold  :队列等待的阈值为100,超过100执行拒绝策略
         */
        HystrixThreadPoolProperties.Setter threadPoolProperties = HystrixThreadPoolProperties.Setter().withCoreSize(50)
                .withKeepAliveTimeMinutes(15).withQueueSizeRejectionThreshold(100);

        // 命令属性配置Hystrix 开启超时
        HystrixCommandProperties.Setter commandProperties = HystrixCommandProperties.Setter()
                // 采用线程池方式实现服务隔离
                .withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.THREAD)
                // 禁止
                .withExecutionTimeoutEnabled(false);
        return Setter.withGroupKey(groupKey).andCommandKey(commandKey).andThreadPoolKey(threadPoolKey)
                .andThreadPoolPropertiesDefaults(threadPoolProperties).andCommandPropertiesDefaults(commandProperties);

    }

    @Override
    protected Product run() throws Exception {
        System.out.println(Thread.currentThread().getName());
        return restTemplate.getForObject("http://service-product/product/1",Product.class);
    }

    @Override
    protected Product getFallback(){
        Product product=new Product();
        product.setProductName("不好意思,出错了");
        return product;
}
}

3.在OrderController中调用OrderCommand的方法

        System.out.println(Thread.currentThread().getName());
        return new OrderCommand(restTemplate, id).execute();

4.测试,使用JMeter模拟过量访问findProductById,同时在浏览器端访问Order服务的另一个方法findById,会发现访问findProductById与访问findById的不是同一线程
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值