spring cloud 学习

基础知识

本文代码

本文所用代码开源在:
https://gitee.com/kiteff/springcloud_learning_project
欢迎技术交流一起进步

什么是CAP

在一个分布式系统中,C(一致性)、A(可用性)、P(容错性) ,三者最多同时只能满足两个

项目解释
一致性在分布式系统中的所有的数据备份,在同一个时刻,都拥有同样的值。(所有的节点在同一时刻的数据完全一致,节点越多,数据同步越耗时)
可用性负载过大的时候,集群整体是否能够响应客户端的读写请求,(服务一直可用,而且是正常的响应时间)
分区容错性高可用,一个节点奔溃了,并不会影响其他的节点。

为什么CAP只能三选一

组合解释
CA组合数据同步需要时间,也要在正常的时间内响应,那么机器的数量就必须要少,容错性就不能满足了
CP组合数据同步需要时间,机器的数量又要多才能保证容错性,就要耗费大量的时间,可用性就不能满足
AP组合机器数量多,同时又要保持在正常时间内响应,就不能保证数据同步,以此来节约时间

注册中心如何选择

注册中心解释
ZookeeperCP 设计,为了保证一致性,集群某一个节点失效以后,就会进行leader选举,如果板半数以上节点不可以用,集群就无法再提供服务
EurekaAP 设计,无主从节点,一个节点挂了以后,其他节点直接上去,去中心化

Eureka Server 搭建

打开 IDEA -> Spring Initializer->选Cloud Discovery -> Eureka Server

  • 设置yml 文档

    server:
      port: 8761
    
    eureka:
      instance:
        hostname: localhost
        statusPageUrlPath:  http://${eureka.instance.hostname}:${server.port}/info
        healthCheckUrlPath: http://${eureka.instance.hostname}:${server.port}/health
      client:
        #表示这是一个服务端,他不会去获取数据
        registerWithEureka: false
        fetchRegistry: false
        serviceUrl:
          #注册中心地址
          defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
    
    
  • 在application 上加入@EnableEurekaServer

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

启动即可,然后浏览器访问 http://localhost:8761/ (8761就是设置的server.port)

服务提供者

搭建步骤

和之前一样的建设步骤

  • 设置yml 文件

    eureka:
      client:
        serviceUrl:
            #这里的地址要和eureka server 的地址一致
          defaultZone: http://localhost:8761/eureka/
    server:
      port: 8771
    spring:
      application:
        name: product-service #这个名字很重要,之后服务消费者要根据这个地址来调用要唯一
    

小技巧如何启动多个端口服务

为了测试,我们可以在idea 的configuretion -> vm options 里输入 -Dserver.port=8771

这个8771 端口就是服务提供的端口

然后再启动

如何关闭保护模式

Eureka-Server 的yml 文件中

server:
	enable-self-preservation: false

保护模式的作用是:如果Eureka server 发现客户端好像死了,在保护模式下并不会直接移除这个客户端,一般会采用熔断的机制,定时的再去访问访问,如果关闭了保护模式就会直接移除,生产环境建议不要关闭

服务消费者

两种模式的区别

模式解释
RPC远程过程调用,像调用本地服务一样调用远程的的服务,支持同步异步.
客户端和服务器之间建立tcp连接,可以一次建立多个,也可以复用一个,缺点是编解码,序列化,链接,丢包 protobuf
http(rest)http请求支持多种协议和功能,开发方便成本低,但是数据包大

Ribbon

看文档 ,文档为主

两种模式下都要加上 web 依赖

  • 编写 yml 文件 ,这里ribbon的yml 和下面的feign 一致

    eureka:
      client:
        serviceUrl:
          defaultZone: http://localhost:8761/eureka/
    server:
      port: 8781
    spring:
      application:
        name: client-demo  
    
  • 加入依赖

    <dependency>
    		<groupId>org.springframework.cloud</groupId>
    		<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
    </dependency>
    
  • 注入负载均衡器

    在application 里

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

    @LoadBalanced 这个是表示他是一个负载均衡的

  • 使用

    在servcie中

    Object forObject = restTemplate.getForObject("http://product-service/api/v1/product/find?id=" + id, Object.class);
    System.out.println(forObject);
    return forObject;
    

    这里 getForObject 里的地址格式是:http://服务提供者的名字/对应的路径

Feign

  • 引入依赖

    <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    
  • 在 application 上增加注解

    @EnableFeignClients

  • 新增一个接口

    /*
    * value 是要消费的服务名称
    */
    @FeignClient(value = "product-service")
    public interface FeignClient {
        //getMapping 说明这是一个get请求,里面的值就是这个服务的具体地址
        @GetMapping("/api/v1/product/find")
        //方法名随意,里面的参数就是对应 服务提供者所需要的参数
        String findById(@RequestParam(value = "id") int id);
    }
    
  • 在service中调用这个接口

    String client = feignClient.findById(id);
    System.out.println(client);
    return client;
    

熔断和降级

概念

概念解释
熔断熔断的作用是保护服务的调用方和被调用方,一个操作 可能要调用a b c 三个服务,但是比如服务B 现在挂了,这个就会导致,整个操作不能进行,这个时候应该及时熔断,服务b ,这样之后就不会去调用服务b 了,保证了整个操作能完成
降级有时候数据量比较大,比如双十一期间,我们可以把一些,次要的服务降级,系统吃紧的时候不去调用他们而去采用兜底数据

hystrix

  • 引入依赖

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
    </dependency>
    
  • 修改yml配置文件

    feign:
      hystrix:
        enabled: true
    
  • 设置超时时间

    hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 5000 
    

    这一句话的意思是设置5秒超时 , 可以不设置 ,但我建议设置,不然必会发现关闭一个服务时,会有比较明显的抖动

使用方法一

第一种使用的方法是在controller 中使用

  • 在要熔断的controller上添加注解 @EnableCircuitBreaker

    表示开启了断路器

  • 编写代码

    @RequestMapping("/getFeign")
    //指定服务挂了以后用defaultStores方法处理
    @HystrixCommand(fallbackMethod = "defaultStores")
    public Object getProductFeign(@RequestParam (value = "id") int id){
        Object project = productService.getProjectByFeign(id);
        HashMap<Object, Object> map = new HashMap<>();
        map.put("info",project);
        return map;
    }
    //处理的方法参数要和调用者的一致
    public Object defaultStores(int id) {
        HashMap<Object, Object> map = new HashMap<>();
        map.put("code",-1);
        map.put("info","断路器启用");
        return map;
    }
    

使用方法二

第二种方法 要配合Feign使用

  • 实现一个一个异常处理类继承要熔断的 FeignClient

    要加上@Componet 注解 才能被扫描到

    @Component
    public class FeignClientFallBack implements FeignClient {
        @Override
        public String findById(int id) {
            System.out.println("出现异常了");
            return null;
        }
    }
    
  • 在FeignClient中添加一个fallback 指定异常处理类

    //这里的 fallback 就是指定了异常处理类
    @FeignClient(value = "product-service", fallback = FeignClientFallBack.class)
    public interface FeignClient {
        @GetMapping("/api/v1/product/find")
        String findById(@RequestParam(value = "id") int id);
    }
    

Zuul 网关

  • 添加依赖

      	<dependency>
      			<groupId>org.springframework.cloud</groupId>
      			<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
      		</dependency>
    
  • 在application 中 开启网关

      @SpringBootApplication
      @EnableZuulProxy
      public class DemoApplication {
      
          public static void main(String[] args) {
              SpringApplication.run(DemoApplication.class, args);
          }
      }
    
  • 定义拦截器

    @Component
    	//继承ZuulFilter
    	public class MyFilter extends ZuulFilter {
    	    //拦截器的类型  PRE_TYPE 表示前置拦截
    		@Override
    	    public String filterType() {
    	        return PRE_TYPE;
    	    }
    		//拦截器的优先级   越小越先执行
    	    @Override
    	    public int filterOrder() {
    	        return 4;
    	    }
    		//拦截以后的处理方法,返回false 进入下面的异常处理
    	    @Override
    	    public boolean shouldFilter() {
    	        RequestContext currentContext = RequestContext.getCurrentContext();
    	        HttpServletRequest httpServletRequest = currentContext.getRequest();
    	        if("/ps/api/v1/product/list".equals(httpServletRequest.getRequestURI())){
    	            return true;
    	        }
    	        return false;
    	    }
    		//异常处理
    	    @Override
    	    public Object run() throws ZuulException {
    	        System.out.println("欢迎傻逼");
    	        return null;
    	    }
    	}
    
  • 配置yml 文件

    #基础的配置
    server:
      port: 9000
    spring:
      application:
        name: api-gateway
    
    
    eureka:
      client:
        serviceUrl:
          defaultZone: http://localhost:8761/eureka/
    
    # zuul 网关的配置
    zuul:
      routes:
        product-service: /ps/**
      ignored-patterns: /*-service/**
      #ignored-services: product-service
    

下面解释一下上面的网关部分的配置

product-service: 表示将product-service的请求,别名成ps

ignored-patterns 表示不拦截哪些url的请求(可以用这个 关闭掉原来的服务访问方式)

ignored-services 表示不拦截哪些service

比如 http://localhost:9000/product-service/api/v1/product/list
在加上ignored-patterns: /*-service/** 就不能再访问了

全链路追踪

安装sleuth

  • 配置pom文件

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-sleuth</artifactId>
    </dependency>
    
  • 编写代码

    在服务的controller 中加入

    private final Logger logger = LoggerFactory.getLogger(getClass());
    

    然后用日志打印,这里可以不加,但是会出现多次访问的时候日志不打印的情况,所以还是要加上的。

    之后我们就能在日志中看到输出如

    	INFO [product-service,ab6728444294f2ab,ab6728444294f2ab,true] 27528 --- [nio-8771-exec-2] 
    

    下面对参数进行解释

    解释
    第一个值spring.application.name的值
    第二个值sleuth生成的一个ID,叫Trace ID,用来标识一条请求链路,一条请求链路中包含一个Trace ID,多个Span ID
    第三个值panid 基本的工作单元,获取元数据,如发送一个http
    第四个值是否要将该信息输出到zipkin服务中来收集和展示

可视化 zipkin

刚才已经可以打印日志了,但是要做链路追踪还是需要一个可视化的界面才好

  • 加上依赖

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-zipkin</artifactId>
    </dependency>
    

    注意 这个依赖中已经包含了sleuth 的依赖,所以可以将sleuth的依赖去掉

  • 安装zipkin

    docker run -d -p 9411:9411 openzipkin/zipkin
    
  • 配置yml文件

    eureka:
      client:
        serviceUrl:
          defaultZone: http://localhost:8761/eureka/
    server:
      port: 8771
    spring:
      application:
        name: product-service
      # 建立zipkin的地址 
      zipkin:
        base-url: http://47.106.14.61:9411/
      #记录的百分比,默认是0.1,也就是10%,也是是10条访问里记录一条
      sleuth:
        sampler:
          probability: 1
    

    之后访问几次服务,然后打开
    http://47.106.14.61:9411/ 就能看了

配置中心

注册中心的作用是,当我们的微服务如果需要改变一些配置,注册中心可以让各个服务从配置中心拉取配置

安装配置中心

Docker 使用

Docker搭建

参考

https://help.aliyun.com/document_detail/51853.html?spm=a2c4g.11186623.6.820.RaToNY

  • 添加yum源

    # yum install epel-release –y
    # yum clean all
    # yum list
    
  • 安装并运行Docker

    # yum install docker-io –y
    # systemctl start docker
    
  • 检查安装结果

    # docker info
    
  • 基本用法

    # systemctl start docker     #运行Docker守护进程
    # systemctl stop docker      #停止Docker守护进程
    # systemctl restart docker   #重启Docker守护进程
    
  • 常见命令

     docker search mysql               #查询一个镜像
     docker images                     #查看本地的镜像
     docker rmi -f image的Id            #强制删除本地的一个镜像
     docker pull rabbitmq:management   #拉取一个镜像 
     docker run -d --name "xdclass_mq" -p 5672:5672 -p 15672:15672 rabbitmq:management #运行一个容器
     docker stop xdclass_mq # 停止一个容器
     docker start xdclass_mq #启动容器
     docker rm xdclass_mq # 移除一个容器 ,必须先停止
     docker ps -a #查看所有容器,包括没在运行的
     docker exec -it xzhclass_mq /bin/bash # 进入容器内部
    

配置中心

在实际的工程中,我们会去修改配置,如果有很多的服务我们一个个手工区修改配置太麻烦了,所以会使用配置中心,将所有的配置文件放在配置中心上,当服务启动的时候,会从配置中心去拉去配置。

远程仓库的搭建

springcloud 的配置中心要配合git 使用,这里使用码云来搭建这个git ,在码云上注册一个新的工程,这里用product-service 作为演示,在git 上创建一个product-service.yml 文件,这里的文件名 要和你的yml 文件中,spring.application.name的一致,后面会再详细讲解

接下去我们在创建的文件中写入配置

eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/
server:
  port: 8777
spring:
  application:
    name: product-service

sb: xzh

没错这里的配置就是我们product-service 的本地配置,等下product-service启动的时候讲会从远程git 上获取服务信息。

架设配置中心

现在码云上的仓库已经架设好了,我们要在本地架设配置中心,新建一个项目
勾选上 cloud-config->Config servercloud-discovery->eureka-discovery
主要的就是在项目中加上如下依赖

<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-config-server</artifactId>
</dependency>
<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

配置applicaiton.yml文件

spring:
  application:
  	#配置中心在注册中心中的服务名称,等下改造服务的时候要用
    name: CONFIG-SERVER
  cloud:
    config:
      server:
        git:
         # 码云项目的地址
          uri: https://gitee.com/kiteff/test_configuration_center
          username: 账号
          password: 密码

server:
  port: 9100

eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/

修改启动类

@EnableConfigServer
@SpringBootApplication
public class DemoApplication {
	public static void main(String[] args) {
		SpringApplication.run(DemoApplication.class, args);
	}
}

其实就是加了一个@EnableConfigServer注解
现在启动这个配置中心,访问 http://localhost:9100/product-service.yml 可以看到从码云上拉取的yml 配置文件

修改服务

加入依赖
在pom 文件中引入config-client的依赖

 <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-client</artifactId>
  </dependency>

修改application.yml
首先将application.yml 改成 bootstrap.yml 这一步很重要,不然服务启动的时候还是会从本地去拿数据

在这个 bootstrap.yml 文件中写入如下代码

eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/
#server:
#  port: 8771
spring:
  application:
    name: product-service
    #指定从哪个配置中心拿
  cloud:
    config:
      discovery:
      #这个id就是上面的配置中心的name
        service-id: CONFIG-SERVER
        enabled: true
      # label 是版本建议用这个
      #label:
      #profile:

这里还有几个配置
label 和 profile
label 是分支 比如你新建一个git分支dev 然后在再里面写不同环境下的yml文件
profile 是后缀 比如product-service-dev.yml,你要访问这个可以写 dev

建议是用lable
举一个例子,比如现在用profile
仓库里有product-service-dev.yml product-service-dev2.yml 两个文件
程序去product-service-dev.yml获取一个参数 port 的数据,如果碰巧product-service-dev.yml没有这个参数,程序会继续去product-service-dev2.yml找!!!这样你可能会遇到很多莫名其妙的问题,所以还是用lable吧,干净整洁。

全部配置完毕后启动项目,我可以尝试修改git上的 port 端口,可以看到服务每次启动的时候都会根据git 上的端口来启动

消息总线

上一节我们实现了配置中心,但是这个还是不完善的,当修改了git仓库的一个参数的时候,必须重启整个服务才能重新 获取配置,那可不可以不重启的来获取这些数据呢,这就是消息总线了

消息总线,其实就是用消息队列,在git仓库的配置发生变化的时候,通知到我们的服务提供者去读取新的配置。

  • 在服务提供者里加入依赖
 <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bus-amqp</artifactId>
        </dependency>
  • 在yml文件中加入配置
management:
  endpoints:
    web:
      exposure:
        include: "*"
  • 最后在我们要动态更新的类上方加上 @RefreshScope 注解

这里在product-service 的controller上加上注解

@RestController
@RequestMapping("/api/v1/product")
@RefreshScope
public class ProductController {
    private final Logger logger = LoggerFactory.getLogger(getClass());
    @Autowired
    private ProductService productService;
    @Value("${sb}")
    private String sb;

可以看到我们定义了一个新的变量,sb将会从git仓库读取他的配置,所以我们再在git 的 product-service.yml 文件中加一个配置sb

现在这个配置文件就是

eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/
server:
  port: 8777
spring:
  application:
    name: product-service

sb: xzh

我们将service启动后,改变sb的值,再用Post形式访问
http://localhost:8777/actuator/bus-refresh
也就是就是这个product-service 下的这个路径 ,注意,一定要是post形式!!!
访问后,sb 的值就会重新从仓库拉取

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值