SpringCloud微服务进阶

SpringCloud进阶

1. 服务之间的通讯

使用支付服务调用用户服务

  1. 服务与服务之间远程调用,可以用json的格式返回数据,也可以返回一个对象!

    支付服务与用户服务都使用User来封装对象。

    抽取User作为一个公共模块,支付服务与用户服务依赖于它

    ①:创建一个模块

    package com.hannfengyi;
    
    /**
     * User: Han
     * Date: 2020/2/14
     * Time: 9:54
     * Description:
     */
    public class User {
        private Long id;
        private String username;
        private String intro;
    
        public Long getId() {
            return id;
        }
    
        public void setId(Long id) {
            this.id = id;
        }
    
        public String getUsername() {
            return username;
        }
    
        public void setUsername(String username) {
            this.username = username;
        }
    
        public String getIntro() {
            return intro;
        }
    
        public void setIntro(String intro) {
            this.intro = intro;
        }
        public User(Long id, String username, String intro) {
            this.id = id;
            this.username = username;
            this.intro = intro;
        }
    
        public User() {
        }
    }
    

    ②:支付模块与用户模块引用它,封装数据

            
                com.hanfengyi
                springcloud-user-common
                1.0-SNAPSHOT
            
    

    ③:浏览器访问支付微服务,支付微服务调用用户微服务,用户微服务把数据返回给支付微服务,支付微服务把数据响应浏览器!

    需要在java代码中发送http请求,使用Spring集成的RestTemplate工具类,它是基于Restful风格的http工具!

    在支付微服务中配置类中注册RestTemplate,交给Spring管理

    @SpringBootApplication
    public class PayServerApplication
    {
        public static void main( String[] args )
        {
            SpringApplication.run(PayServerApplication.class);
        }
        @LoadBalanced
        @Bean
        public RestTemplate restTemplate(){
            return new RestTemplate();
        }
    }
    

    @LoadBalanced表示作为负载均衡器

    在支付微服务控制器注入RestTemplate使用http发送请求!

    @RestController
    public class PayController {
        @Autowired
        private RestTemplate restTemplate;
        @GetMapping("/pay/user/{id}")
        public User pay(@PathVariable("id") Long id){
             String url = "http://localhost:1000/user/"+id;
            return restTemplate.getForObject(url,User.class);
        }
    }
    

    浏览器访问支付微服务http://localhost:1000/user/1测试,可以看到响应!

2. 负载均衡器-Ribbon

上面用户服务与支付服务之间通信,仅仅是两台服务之间的通信,本身是没有注册到eureka(注册中心)的

对用户模块做集群,使用Ribbon客户端负载均衡器分发请求到不同的用户服务!

Ribbon与ngnix的区别

两者都是负载均衡器,做请求分发,ngnix是服务端负载均衡器,是一个独立的服务,Ribbon是客户端负载均衡器,集成在客户端中!

Ribbon与Feign的工作原理

支付服务调用用户服务,要指定用户服务服务名“user-server”,用户服务做了集群之后,"user-server"对应两个服务,分别有不同的ip和端口,支付服务使用Ribbon通过服务名“user-server”寻找到两个服务,然后按照负载均衡算法(轮询、随机...)对用户服务发起请求!

①:对用户服务做集群,创建一个新的用户服务,与之前的用户服务代码一样!

修改端口号为1001

eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:3000/eureka/,http://localhost:3001/eureka/ #注册中心服务端的注册地址
  instance:
    prefer-ip-address: true
    instance-id: user-server:1001 #使用ip进行注册
server:
  port: 1001
spring:
  application:
    name: user-server

两个服务的spring.application.name必须一致,表示在一个集群

②:支付服务集成Ribbon

        
            org.springframework.cloud
            spring-cloud-starter-netflix-ribbon
        

③:Ribbon已经集成了RestTemplate,但是要使用@LoadBalanced注解赋予它具备负载均衡的能力,在支付微服务配置类中配置!

@SpringBootApplication
public class PayServerApplication
{
    public static void main( String[] args )
    {
        SpringApplication.run(PayServerApplication.class);
    }
    @LoadBalanced
    @Bean
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}

④:修改url,在user-server这个集群中寻找服务

@RestController
public class PayController {
    @Autowired
    private RestTemplate restTemplate;
    @GetMapping("/pay/user/{id}")
    public User pay(@PathVariable("id") Long id){
        String url = "http://user-server/user/"+id;
        return restTemplate.getForObject(url,User.class);
    }
}

3. 负载均衡器-Feign

Feign:掉用本地接口的方式调用远程服务,与Ribbon的不同的是,解决了参数过多拼接url带来的麻烦!

  1. 新建一个Order服务,使用Feign负载均衡器,同样的访问用户服务!

    ①:导入Feign的依赖

     
         
                org.springframework.cloud
                spring-cloud-starter-openfeign
            
    

    ②:配置端口号

    eureka:
      client:
        serviceUrl:
          defaultZone: http://localhost:3000/eureka/,http://localhost:3001/eureka/ #注册中心服务端的注册地址
      instance:
        prefer-ip-address: true
        instance-id: pay-server:4000 #使用ip进行注册
    server:
      port: 4000
    spring:
      application:
        name: order-server
    

    ③:创建一个接口

    @FeignClient(value = "user-server")
    public interface UserFeignClient {
        @GetMapping(value = "/user/{id}")
        User user(@PathVariable("id") Long id);
    }
    

    @FeignClient(value = "user-server"),参数需要目标服务名,通过服务名去注册中心找到目标服务,再根据@GetMapping(value = "/user/{id}")资源路径找到目标服务的Controller!

    ④:Controller中注入接口

    @RestController
    public class OrderController {
        @Autowired
        private UserFeignClient userFeignClient;
        @GetMapping("/order/user/{id}")
        public User order(@PathVariable("id") Long id){
            return userFeignClient.user(id);
        }
    }
    

    在浏览器中访问http://localhost:4000/order/user/1 该路径时,会创建UserFeignClient这个接口的代理对象,调用user方法,在这个方法中,Feign负载均衡器会根据指定的服务名和资源路径去寻找到多台服务,此时采用轮询或者随机等算法,对其中某台服务发起调用!

    ⑤:修改Feign的算法为随机算法,只需要在配置类中配置一个Bean即可

     @Bean
        public IRule randomRule(){
            return new RandomRule();
        }
    

    4. Hystrix断路器(熔断器)

    4.1:概述

    Hystrix:用来对微服务做隔离和监控,防止一个微服务故障,导致整个微服务群被拖垮引发的雪崩效应!

    Hystrix通过如下机制来解决雪崩效应问题 :

    ①:资源隔离(限制请求数量):包括线程池隔离和信号量隔离,限制调用分布式服务的资源使用,某一个调用的服务出现问题不会影响其他服务调用。

    线程池隔离:通过线程池中线程数量来限制请求并发量!

    信号量隔离:使用计数器计数的方式,当请求数量达到被设置的阀值的时候,限制请求并发量!

    ②:熔断:当请求失败率达到阀值自动触发降级(如因网络故障/超时造成的失败率高),

    正常情况下,断路器处于关闭状态(Closed),

    如果调用持续出错或者超时,电路被打开进入熔断状态(Open),后续一段时间内的所有调用都会被拒绝(Fail Fast),

    一段时间以后,保护器会尝试进入半熔断状态(Half-Open),允许少量请求进来尝试,

    ​ 如果调用仍然失败,则回到熔断状态

    ​ 如果调用成功,则回到电路闭合状态;

    ③:降级机制:超时降级、资源不足时(线程或信号量)降级,降级后可以配合降级接口返回托底数据。

    ④:缓存:提供了请求缓存、请求合并实现。

    4.2:Ribbon实现

    1.在消费者服务集成Hystrix,不在提供者集成,如果用户服务故障,那么Hystrix也会故障,不能起到熔断的作用!

    在支付服务中导入Hystrix依赖

         
                org.springframework.cloud
                spring-cloud-starter-netflix-hystrix
            
    

    2.在配置类中开启Hystrix功能

    @SpringBootApplication
    @EnableCircuitBreaker
    public class PayServerApplication
    {
        public static void main( String[] args )
        {
            SpringApplication.run(PayServerApplication.class);
        }
        @LoadBalanced
        @Bean
        public RestTemplate restTemplate(){
            return new RestTemplate();
        }
    }
    

    @EnableCircuitBreaker注解的作用开启Hystrix功能

    3.在需要发生熔断的服务方法中使用注解 @HystrixCommand(fallbackMethod = "ErrorMethod"),如果发生错误,会执行指定的托底方法,触发熔断机制,返回托底数据!

    @RestController
    public class PayController {
        @Autowired
        private RestTemplate restTemplate;
        @GetMapping("/pay/user/{id}")
        @HystrixCommand(fallbackMethod = "ErrorMethod")
        public User pay(@PathVariable("id") Long id){
           // String url = "http://localhost:1000/user/"+id;
            String url = "http://user-server/user/"+id;
            return restTemplate.getForObject(url,User.class);
        }
    
        public User ErrorMethod(@PathVariable("id") Long id){
            return new User(-1L, "无此用户", "用户服务错误!");
        }
    }
    

    上面这个例子表示在访问user-server服务集群的时候,如果发生异常,或者该服务不可用,则会执行ErrorMethod该托底方法中的代码!

    4.3:Feign的实现

    因为Feign已经集成了Hystrix,不需要导入依赖

    ①:在application.yml中开启Hystrix

    feign:
      hystrix:
        enabled: true
    

    ②:在Feign用于访问服务的接口中@FeignClient注解中添加属性fallback用来指定托底方法

    @FeignClient(value = "user-server",fallback = UserFeignClientFallBack.class)
    public interface UserFeignClient {
        @GetMapping(value = "/user/{id}")
        User user(@PathVariable("id") Long id);
    }
    

    ③:因为fallback的属性要求是一个字节码对象,需要创建一个类,必须实现该接口,重写方法,重写的方法就是托底方法,该类必须交给Spring管理!

    @Component
    public class UserFeignClientFallBack implements UserFeignClient {
        @Override
        public User user(Long id) {
            return new User(-1l, "用户不存在", "用户服务异常!");
        }
    }
    

    5. Zuul网关

    Zuul网关:作为整个微服务群的请求入口,可以保护微服务群的安全,可以通过Zuul来实现权限校验、限流、日志、监控、负载均衡等。

    Zuul作为一个独立的应用,默认集成了Ribbon,其本质是一个servlet

    1.集成Zuul

    ①:新建一个模块,创建Zuul服务

    ②:导入依赖

         
                org.springframework.boot
                spring-boot-starter-web
            
            
            
                org.springframework.cloud
                spring-cloud-starter-netflix-eureka-client
                    
         
                org.springframework.cloud
                spring-cloud-starter-netflix-zuul
            
    

    ③:在配置类中使用@EnableZuulProxy开启Zuul

    @SpringBootApplication
    @EnableDiscoveryClient
    @EnableEurekaClient
    @EnableZuulProxy
    public class ZuulServer5000
    {
        public static void main( String[] args )
        {
            SpringApplication.run(ZuulServer5000.class);
        }
    }
    

    ④:application.yml的配置

    eureka:
      client:
        serviceUrl:
          defaultZone: http://localhost:3000/eureka/,http://localhost:3001/eureka/ #注册中心服务端的注册地址
      instance:
        prefer-ip-address: true
        instance-id: zuul-server:5000 #使用ip进行注册
    server:
      port: 5000
    spring:
      application:
        name: zuul-server
    

    Zuul是需要注册到注册中心的,此时通过浏览器访问用户服务http://localhost:5000/user-server/user/1 ,请求到达Zuul,Zuul会通过服务名user-server去注册中心寻找到目标服务,通过Ribbon负载均衡器发起远程调用!

    2.Zuul访问地址的优化

    通过上面的例子可以看出,如果我们需要访问某个服务,需要在地址栏中暴露出服务名字,显然这是不好的,可以在application.yml中配置一下属性,通过指定的别名来访问!

    zuul:
      ignoredServices: '*'
      routes:
        pay-server: /pay/**
        user-server: /user/**
        order-server: /order/**
    

    ignoredServices:表示忽略使用服务名的方式访问

    routes:可以指定访问某个服务名使用的资源路径!

    现在如果要访问用户服务可以通过http://localhost:5000/user/user/1来进行访问!这里的user就已经代表了user-server!

    3.Hystrix超时处理

    使用Zuul网关发起请求,发生异常,触发熔断机制,执行托底方法超时在application.yml中配置

    zuul:
     host:
         connect-timeout-millis: 150000 #HTTP连接超时要比Hystrix的大
         socket-timeout-millis: 150000   #socket超时
    
    ribbon: #ribbon超时
      ReadTimeout: 50000
      ConnectTimeout: 50000
    hystrix:
      command:
        default:
          execution:
            isolation:
              thread:
                timeoutInMilliseconds: 100000
    

    6. filter实现登录拦截

    1. 自定义类继承ZuulFilter重写核心方法!把该类交给spring容器管理!
    @Component
    public class LoginZuulFilter extends ZuulFilter {
        @Override
        public String filterType() {
            return FilterConstants.PRE_TYPE;
        }
    
        @Override
        public int filterOrder() {
            return 1;
        }
    
        @Override
        public boolean shouldFilter() {
            //1.获得当前请求上下文
            RequestContext currentContext = RequestContext.getCurrentContext();
            //2.根据当前请求上下文获得请求对象
            HttpServletRequest request = currentContext.getRequest();
            //3.拿到请求头的uri路径
            String requestURI = request.getRequestURI();
            //4.如果访问路径是"/login",则不做校验
            if(StringUtils.isEmpty(requestURI) && requestURI.endsWith("login")){
                return false;
            }
            return true;
        }
    
        @Override
        public Object run() throws ZuulException {
            //1.获得当前请求上下文
            RequestContext currentContext = RequestContext.getCurrentContext();
            //2.根据当前请求上下文获得请求对象,和响应对象
            HttpServletRequest request = currentContext.getRequest();
            HttpServletResponse response = currentContext.getResponse();
            //3.拿到请求头的token令牌
            String token = request.getHeader("token");
            Map hashmap = new HashMap<>();
            //4.如果没有token信息,返回提示信息
            if(StringUtils.isEmpty(token)){
                hashmap.put("success", false);
                hashmap.put("message","当前用户还未登录");
                try {
                    response.getWriter().print(JSONObject.toJSONString(hashmap));
                    //5.阻止请求继续往下执行
                    currentContext.setSendZuulResponse(false);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
    
            return null;
        }
    }
    

    使用阿里巴巴的fastjson可以将对象转换为json格式

            
                com.alibaba
                fastjson
                1.2.62
            
    
  • shouldFilter:返回一个Boolean值,判断该过滤器是否需要执行。返回true执行,返回false不执行。

  • run:过滤器的具体业务逻辑。

  • filterType:返回字符串,代表过滤器的类型。包含以下4种:

    • pre:请求在被路由之前执行

    • routing:在路由请求时调用

    • post:在routing和errror过滤器之后调用

    • error:处理请求时发生错误调用

  • filterOrder:通过返回的int值来定义过滤器的执行顺序,数字越小优先级越高。

  1. 过滤器执行周期
1581767595304.png

正常流程:

- 请求到达首先会经过pre类型过滤器,而后到达routing类型,进行路由,请求就到达真正的服务提供者,执行请求,返回结果后,会到达post过滤器。而后返回响应。

- 异常流程:

- 整个过程中,pre或者routing过滤器出现异常,都会直接进入error过滤器,再error处理完毕后,会将请求交给POST过滤器,最后返回给用户。

- 如果是error过滤器自己出现异常,最终也会进入POST过滤器,而后返回。

- 如果是POST过滤器出现异常,会跳转到error过滤器,但是与pre和routing不同的时,请求不会再到达POST过滤器了。

7. SpringCloud Config分布式配置中心

7.1 概述

在分布式系统中,由于服务数量巨多,为了方便服务配置文件统一管理,实时更新,所以需要分布式配置中心组件。在Spring Cloud中,有分布式配置中心组件spring cloud config ,它支持配置服务放在配置服务的内存中(即本地),也支持放在远程Git仓库中。在spring cloud config 组件中,分两个角色,一是config server,二是config client。

7.2 作用

①:集中管理配置文件

②:动态化的配置更新,不同环境不同部署

③:运行时动态调整配置,不需要在部署服务的时候编写配置文件,服务会向配置中心统一拉取自己的配置信息

④:配置发生变动时,服务不需要重启就可以感知配置变化,自动应用最新的配置

⑤:配置信息以restful接口的方式暴露

7.3 与git/svn集成

①:在github/码云上创建仓库,创建所需服务的配置文件

②:新建一个config-server独立的应用,maven中导入依赖

    
        org.springframework.cloud
        spring-cloud-config-server
    

③:在配置类中使用注解@EnableConfigServer开启配置中心支持

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

}

④:在配置文件application.yml中配置git的配置,拉取配置文件

eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:3000/eureka/,http://localhost:3001/eureka/ #注册中心服务端的注册地址
  instance:
    prefer-ip-address: true
    instance-id: config-server:6000 #使用ip进行注册
server:
  port: 6000
spring:
  application:
    name: config-server
  cloud:
    config:
      server:
        git:
          uri: https://gitee.com/immerseshe/spring-cloud-config.git
          username: ***
          password: ***

这里的url要指向仓库地址

⑤:测试,访问http://localhost:6000/文件名.yml,会返回配置数据!

如果遇到 此网址使用了一个通常用于网络浏览以外目的的端口。出于安全原因,Firefox 取消了该请求。错误

解决方式:

在Firefox地址栏输入 about:config

右键新建一个字符串键:

首选项名称 填写 network.security.ports.banned.override
值 的填写有三种方式:

a、如果只有单个端口号时,只接输入的端口号即可,如 6666 ;
b、如果要填写多个端口号时,端口号之间用逗号隔开,如:6666,7777,8888 ;
c、一个一个添加端口号特别麻烦,在能保证安全的前提下,还简化直接输入 0-65535 。

添加端口号允许访问!

7.4 客户端配置

在服务中配置不再使用自身的配置文件,而是去配置中心拉取配置信息!

①:导入config客户端依赖

        
            org.springframework.cloud
            spring-cloud-starter-config
        

②:删除掉原来的application.yml,创建bootstrap.yml,该配置文件相比较application.yml的优先级更高。在配置文件中指向配置中心的服务地址,配置文件名字和环境

spring:
  cloud:
    config:
      name: order-server
      profile: dev
      label: master
      uri: http://localhost:6000

③:测试,先在git上修改配置,修改端口号,启动服务,查看端口是否变化!

你可能感兴趣的:(SpringCloud微服务进阶)