zoukankan      html  css  js  c++  java
  • SPRING CLOUD微服务DEMO-上篇

    1. 微服务架构

    系统架构的演变从单应用,到根据每个模块垂直拆分,到分布式服务,SOA,到目前进化成了微服务形态。

    微服务的特点

    • 单一职责:微服务中每一个服务都对应唯一的业务能力,做到单一职责
    • 微:微服务的服务拆分粒度很小,例如一个用户管理就可以作为一个服务。每个服务虽小,但“五脏俱全”。
    • 面向服务:面向服务是说每个服务都要对外暴露服务接口API。并不关心服务的技术实现,做到与平台和语言无关,也不限定用什么技术实现,只要提供Rest的接口即可。
    • 自治:自治是说服务间互相独立,互不干扰
      • 团队独立:每个服务都是一个独立的开发团队,人数不能过多。
      • 技术独立:因为是面向服务,提供Rest接口,使用什么技术没有别人干涉
      • 前后端分离:采用前后端分离开发,提供统一Rest接口,后端不用再为PC、移动段开发不同接口
      • 数据库分离:每个服务都使用自己的数据源
      • 部署独立,服务间虽然有调用,但要做到服务重启不影响其它服务。有利于持续集成和持续交付。每个服务都是独立的组件,可复用,可替换,降低耦合,易维护

    2. 远程调用方式

    2.1 RPC/RMI

    RPC:Remote Produce Call远程过程调用,类似的还有RMI。自定义数据格式,基于原生TCP通信,速度快,效率高。早期的webservice,现在热门的dubbo,都是RPC的典型

    2.2 Http

    Http:http其实是一种网络传输协议,基于TCP,规定了数据传输的格式。现在客户端浏览器与服务端通信基本都是采用Http协议。也可以用来进行远程服务调用。缺点是消息封装臃肿。

    2.3 如何选择

    既然两种方式都可以实现远程调用,我们该如何选择呢?

    • 速度来看,RPC要比http更快,虽然底层都是TCP,但是http协议的信息往往比较臃肿,不过可以采用gzip压缩。
    • 难度来看,RPC实现较为复杂,http相对比较简单
    • 灵活性来看,http更胜一筹,因为它不关心实现细节,跨平台、跨语言。RPC方式需要在API层面进行封装,限制了开发的语言环境。

    因此,两者都有不同的使用场景:

    • 如果对效率要求更高,并且开发过程使用统一的技术栈,那么用RPC还是不错的。
    • 如果需要更加灵活,跨语言、跨平台,显然http更合适

    微服务,更加强调的是独立、自治、灵活。而RPC方式的限制较多,因此微服务框架中,一般都会采用基于Http的Rest风格服务。

    3. Http客户端工具

    可以使用一些流行的开源Http客户端工具请求Rest接口

    • HttpClient
    • OKHttp
    • URLConnection

    3.1 RestTemplate

    使用Http客户端工具请求Rest接口,得到数据后反序列化成对象。这样做比较麻烦,Spring提供了一个RestTemplate模版工具类,对Http客户端进行了封装(默认使用URLConnection),实现了对象和Json的序列化和反序列化,方便得很。下面搭建一个Spring Boot工程,同时演示RestTemplate如何使用。

    4. Spring Boot 搭建项目

    微服务的一个重要特点是每个服务都是一个可以独立运行的项目,要是按照以前的SSM的搭建方式,无法做到服务的快速搭建和部署。于是Spring Boot应运而生,它的理念就是约定大于配置,你选好模块,自动帮你搭建好环境,非常便捷。

    接下来用Spring Boot搭建两个简单的用户微服务:user-serviceuser-consume,实现的功能是:user-consume使用RestTemplate调用user-service服务

    具体参考这篇笔记(子文章)SPRING BOOT搭建两个微服务模块

    5. Spring Cloud简介

    Spring Cloud是实现微服务架构的技术。它集成了Netflix公司的一些微服务组件。(如果你不知道这家公司,那你肯定不爱看美剧。)

    Netflix微服务架构图

    • Eureka:注册中心
    • Zuul:服务网关
    • Ribbon:负载均衡
    • Feign:服务调用
    • Hystix:熔断器

    6. 微服务场景模拟

    在第四节我们已经搭建好了user-serviceuser-consume两个微服务。

    consume使用RestTemplate调用service提供的rest接口,获得json数据,反序列化成User对象返回到前台。其中存在着一些问题:

    • consume中调用的rest接口的地址是硬编码的,不方便维护。
    • 如果service的rest接口变更或关闭了,consume并不知情,最终什么都得不到。
    • service只有1台,一旦宕机,整个服务就不可用了。如果扩展多台,那consume又要自己考虑负载均衡。

    接下来介绍的几个组件就是为解决这些问题而生的。

    7. Eureka注册中心

    7.1 简介

    Eureka负责微服务的管理。

    如果一个项目有数十个微服务,调用者想要自己找到一个适合的,可用的微服务是很麻烦的一件事。

    就比如你想要坐车出门,自己上街拦出租车就很麻烦,要么司机拒载,要么车里已经有人了,要么干脆就没有车来...后来就出现了滴滴,做为一个网约车的“注册中心”,可以为你分配离你最近的空闲出租车。

    Eureka就好比是滴滴,负责管理、记录服务提供者的信息。服务调用者无需自己寻找服务,而是把自己的需求告诉Eureka,然后Eureka会把符合你需求的服务告诉你。

    同时,服务提供方与Eureka之间通过“心跳”机制进行监控,当某个服务提供方出现问题,Eureka自然会把它从服务列表中剔除。

    这就实现了服务的自动注册、发现、状态监控。

    7.2 原理图

    • EurekaServer:注册中心,可以是多个Eureka的集群,对外暴露自己的地址。
    • 服务提供者:启动后在注册中心中注册,成功后定期使用http方法向注册中心发送心跳包,表明自己还活着。
    • 客户端消费者:向注册中心订阅服务。注册中心会向消费者发送合适的服务提供者列表,并且定期更新。需要使用某个服务时,可以从列表中找到并调用。

    7.3 搭建注册中心

    搭建过程和第4节说的一样,注意选择Eureka模块即可。

    7.3.1 代码和配置文件

    • 启动类

    @EnableEurekaServer注解表示这是一个注册中心

    @EnableEurekaServer
    @SpringBootApplication
    public class EurekaApplication {
        public static void main(String[] args) {
            SpringApplication.run(EurekaApplication.class, args);
        }
    }
    
    • 配置文件

    现在我们只启动一个eureka作为注册中心。

    这是Spring官方文档中的Standalone Eureka Server的建议配置。

    注意eureka下的client配置,这个配置意思是eureka应用作为一个客户端,对注册中心的动作。

    其中service-url的配置必须要填写,内容是注册中心的地址,如果有多个,逗号隔开。

    defaultZone路径后面必须加上/eureka后缀,别问我为啥。

    server:
      port: 10086 # 端口
    spring:
      application:
        name: eureka-server # 应用名称,会在Eureka中显示
    eureka:
      client:
        register-with-eureka: false # 是否注册自己的信息到EurekaServer,默认是true
        fetch-registry: false # 是否拉取服务列表,默认是true
        service-url: # EurekaServer的地址,现在是自己的地址,如果是集群,需要加上其它Server的地址。
          defaultZone: http://127.0.0.1:${server.port}/eureka
    

    访问http://localhost:10086即可看到注册中心的内容,eureka只有一个,服务列表为空:

    7.4 将user-service注册到eureka

    7.4.1 添加依赖

    eureka客户端依赖

    <!-- Eureka客户端 -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    

    Spring Cloud的依赖,注意我这里的版本是Greenwich.SR1

        <!-- SpringCloud的依赖 -->
        <dependencyManagement>
            <dependencies>
                <dependency>
                    <groupId>org.springframework.cloud</groupId>
                    <artifactId>spring-cloud-dependencies</artifactId>
                    <version>Greenwich.SR1</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
            </dependencies>
        </dependencyManagement>
        <!-- Spring的仓库地址 -->
        <repositories>
            <repository>
                <id>spring-milestones</id>
                <name>Spring Milestones</name>
                <url>https://repo.spring.io/milestone</url>
                <snapshots>
                    <enabled>false</enabled>
                </snapshots>
            </repository>
        </repositories>
    

    7.4.2 开启EurekaClient功能

    添加@EnableDiscoveryClient注解

    @SpringBootApplication
    @EnableDiscoveryClient // 开启EurekaClient功能
    public class UserServiceApplication {
        public static void main(String[] args) {
            SpringApplication.run(UserServiceApplication.class, args);
        }
    }
    

    7.4.3 配置eureka客户端属性

    server:
      port: 8081
    spring:
      datasource:
        url: jdbc:mysql://localhost:3306/XXXXX?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
        username: XXXXX
        password: XXXXX
        hikari:
          maximum-pool-size: 20
          minimum-idle: 10
      application:
        name: user-service # 应用名称
    mybatis:
      type-aliases-package: com.vplus.demo.userservice.pojo
    eureka:
      client:
        service-url: # EurekaServer地址
          defaultZone: http://127.0.0.1:10086/eureka
      instance:
        prefer-ip-address: true # 当调用getHostname获取实例的hostname时,返回ip而不是host名称
        ip-address: 127.0.0.1 # 指定自己的ip信息,不指定的话会自己寻找
    

    7.4.4 效果

    7.5 user-consume从eureka中获取服务

    7.5.1 添加依赖

    同上7.4.1

    7.5.2 开启EurekaClient功能

    同上7.4.2

    7.5.3 配置eureka客户端属性

    server:
      port: 8082
    spring:
      application:
        name: user-consume # 应用名称
    eureka:
      client:
        service-url: # EurekaServer地址
          defaultZone: http://127.0.0.1:10086/eureka
      instance:
        prefer-ip-address: true # 当其它服务获取地址时提供ip而不是hostname
        ip-address: 127.0.0.1 # 指定自己的ip信息,不指定的话会自己寻找
    

    7.5.4 修改UserService

    • 之前是调用UserDao,UserDao使用RestTemplate请求远程接口得到数据。
    • 现在我们在service层,从eureka中拉取服务列表,得到接口地址后请求数据。
    @Service
    public class UserService {
        @Autowired
        private RestTemplate restTemplate;
    
        @Autowired
        private DiscoveryClient discoveryClient;// Eureka客户端,可以获取到服务实例信息
    
        public List<User> queryUserByIds(List<Long> ids) {
            List<User> users = new ArrayList<>();
            // String baseUrl = "http://localhost:8081/user/";
            // 根据服务名称,获取服务实例
            List<ServiceInstance> instances = discoveryClient.getInstances("user-service");
            // 因为只有一个UserService,因此我们直接get(0)获取
            ServiceInstance instance = instances.get(0);
            // 获取ip和端口信息
            String baseUrl = "http://"+instance.getHost() + ":" + instance.getPort()+"/user/";
            ids.forEach(id -> {
                // 我们测试多次查询,
                users.add(this.restTemplate.getForObject(baseUrl + id, User.class));
                // 每次间隔500毫秒
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
            return users;
        }
    
    }
    

    7.6 eureka集群

    7.6.1 搭建集群

    Eureka可以集群搭建形成高可用的注册中心。多个Eureka Server之间也会互相注册为服务,当服务提供者注册到Eureka Server集群中的某个节点时,该节点会把服务的信息同步给集群中的每个节点,从而实现数据同步。因此,无论客户端访问到Eureka Server集群中的任意一个节点,都可以获取到完整的服务列表信息。

    我们要两个Eureka,端口号分别是10086、10087

    可以使用Idea的启动器复制功能复制eureka的启动器。

    将应用A的启动器复制出一个A2,A启动后,修改配置再启动A2,这样我们就可以得到两个配置不同的应用了。

    先把原来的eureka配置信息改为

    server:
      port: 10086 # 端口
    spring:
      application:
        name: eureka-server # 应用名称,会在Eureka中显示
    eureka:
      client:
        service-url:
          defaultZone: http://127.0.0.1:10087/eureka
    

    启动EurekaApplication,启动起来后再将配置改为

    server:
      port: 10087 # 端口
    spring:
      application:
        name: eureka-server # 应用名称,会在Eureka中显示
    eureka:
      client:
        service-url:
          defaultZone: http://127.0.0.1:10086/eureka
    

    再启动EurekaApplication2,此时两个注册中心就形成了集群。

    两个eureka相互注册,10086的defaultZone的url地址的端口号为10087,注意这一点。

    再将两个客户端的Eureka相关配置改为

    defaultZone: http://127.0.0.1:10086/eureka,http://127.0.0.1:10087/eureka
    

    访问http://localhost:10086http://localhost:10087都可以看到效果:

    7.7 Eureka相关配置

    7.7.1 服务提供者

    服务提供者主要做两个动作:服务注册服务续约

    • 服务注册的对应配置,true表示服务启动后会向注册中心发送注册请求,默认就是开着的。
    eureka
    	client
    		register-with-erueka: true
    
    • 服务续约的对应配置,这些都是默认值
    eureka:
      instance:
        #服务失效时间,默认值90秒
        lease-expiration-duration-in-seconds: 90
        #服务续约(renew)的间隔,默认为30秒
        lease-renewal-interval-in-seconds: 30
    

    默认情况下每个30秒服务会向注册中心发送一次心跳,证明自己还活着。如果超过90秒没有发送心跳,EurekaServer就会认为该服务宕机,会从服务列表中移除,注意,这是服务提供者告诉注册中心我每30秒续约一次,90秒没有续约,就表示我失效了,是配置在服务提供者上的,不是配置在注册中心上的。

    我实验的时候,关闭一个服务,eureka几乎是瞬间就知道服务down了

    试了好几次都是这样,可能我的关闭动作出发了什么东西吧,时间有限,先不管这个问题,留个坑将来看。

    后来我注意到服务关闭后会输出一句:Unregistering ...,推测服务正常关闭时会自己通知注册中心。

    • 实例ID名的修改

    在页面上,服务提供者实例ID显示为:localhost:user-service:8081,

    格式为:${hostname} + ${spring.application.name} + ${server.port},对应配置为

    eureka:
      instance:
        instance-id: ${spring.application.name}:${server.port}
    

    修改后启动,变成了

    7.7.2 服务消费者

    消费者需要拉取服务列表,拉取时间间隔默认为30秒1次,对应配置为,如果是开发环境可适当缩小方便开发

    eureka:
      client:
        registry-fetch-interval-seconds: 30
    

    7.7.3 失效剔除和自我保护

    • 失效剔除:每个一段时间剔除掉失效的服务,默认为60s,为了方便开发设置为1s。

      服务器怎么知道失效了?看看上面服务提供者的配置,注意这几个配置的联系。

    • 自我保护:生产环境中,由于网络延迟等原因,失效服务并不一定是真正失效了。如果被标记为失效的服务太多,超过了85%,此时eureka会把这些服务保护起来,先不剔除,保证大多数服务还可用。在开发中将自我保护模式关掉,方便开发。

    eureka:
      server:
        enable-self-preservation: false # 关闭自我保护模式(默认为打开)
        eviction-interval-timer-in-ms: 1000 # 	扫描失效服务的间隔时间为1s(默认为60s)
    

    8. Robbin负载均衡

    8.1 开启两个user-service

    具体操作参照7.6.1,这里设置两个service的端口为8080和8081

    8.2 开启负载均衡

    注意,是在调用端即consume上开启负载均衡,Eureka中已经集成了Ribbon,无需引入新的依赖,只需要在调用端的RestTemplate的注册Bean方法上添加注解:@LoadBalanced即可。这个方法位于UserConsumerApplication里。

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

    具体的调用方式也需要修改,将UserService中的queryUserByIds方法修改成这样。

    public List<User> queryUserByIds(List<Long> ids) {
           List<User> users = new ArrayList<>();
            // 地址直接写服务名称即可
            String baseUrl = "http://user-service/user/";
            ids.forEach(id -> {
                // 我们测试多次查询,
                users.add(this.restTemplate.getForObject(baseUrl + id, User.class));
                // 每次间隔500毫秒
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
            return users;
        }
    }
    

    8.3 默认负载均衡策略分析

    org.springframework.cloud.client.loadbalancer包里面有一个LoadBalancerInterceptor,它就是实现负载均衡的拦截器。

    跟踪源码,找到了RibbonLoadBalancerClient,用consume多次请求接口,断点调试

    execute(String serviceId, LoadBalancerRequest<T> request, Object hint)方法:

    就是轮询

    8.4 修改负载均衡策略

    一个配置即可修改,有多种配置规则,这里使用随机规则。

    注意格式,是以服务名称开头的。

    server:
      port: 8082
    spring:
      application:
        name: user-consume # 应用名称
    eureka:
      client:
        service-url: # EurekaServer地址
          defaultZone: http://127.0.0.1:10086/eureka,http://127.0.0.1:10087/eureka
        registry-fetch-interval-seconds: 5
      instance:
        prefer-ip-address: true # 当其它服务获取地址时提供ip而不是hostname
        ip-address: 127.0.0.1 # 指定自己的ip信息,不指定的话会自己寻找
    ###################################负载均衡配置###############################################
    user-service:
      ribbon:
        NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
    

    8.5 重试机制

    如果有一台服务突然挂掉了,而eureka还来不及将其清除出服务列表,或者消费者拉取的服务列表还有缓存,一旦请求到这台挂掉的服务就会报错。虽然多次请求后结果也能出来,但体验非常不好。

    Ribbon的重试机制就是解决这个问题的。

    引入依赖

    <dependency>
        <groupId>org.springframework.retry</groupId>
        <artifactId>spring-retry</artifactId>
    </dependency>
    

    开启Spring Cloud的重试机制并配置

    server:
      port: 8082
    spring:
      application:
        name: user-consume # 应用名称
      cloud:
        loadbalancer:
          retry:
            enabled: true # 开启Spring Cloud的重试功能
    eureka:
      client:
        service-url: # EurekaServer地址
          defaultZone: http://127.0.0.1:10086/eureka,http://127.0.0.1:10087/eureka
        registry-fetch-interval-seconds: 5
      instance:
        prefer-ip-address: true # 当其它服务获取地址时提供ip而不是hostname
        ip-address: 127.0.0.1 # 指定自己的ip信息,不指定的话会自己寻找
    
    user-service:
      ribbon:
        ConnectTimeout: 250 # Ribbon的连接超时时间
        ReadTimeout: 1000 # Ribbon的数据读取超时时间
        OkToRetryOnAllOperations: true # 是否对所有操作都进行重试
        MaxAutoRetriesNextServer: 1 # 切换实例的重试次数
        MaxAutoRetries: 1 # 对当前实例的重试次数
    

    就好了。

  • 相关阅读:
    2. Add Two Numbers
    1. Two Sum
    leetcode 213. 打家劫舍 II JAVA
    leetcode 48. 旋转图像 java
    leetcode 45. 跳跃游戏 II JAVA
    leetcode 42. 接雨水 JAVA
    40. 组合总和 II leetcode JAVA
    24. 两两交换链表中的节点 leetcode
    1002. 查找常用字符 leecode
    leetcode 23. 合并K个排序链表 JAVA
  • 原文地址:https://www.cnblogs.com/qianbixin/p/10936456.html
Copyright © 2011-2022 走看看