自己使用springcloud时,组件搭配如下:服务发现eureka、负载均衡ribbon、Web服务客户端openfeign、断路器hystrix、网关zuul、配置中心config、消息总线bus。文章也将介绍这些组件的使用。

一、服务发现Eureka

Eureka是一个基于REST的服务注册中心,它可以让服务提供者和服务消费者在其中注册和发现对方,从而实现服务间的通信和负载均衡。Eureka由Eureka Server和Eureka Client组成,Eureka Server提供注册服务的功能,Eureka Client提供注册和发现的功能,以及内置的负载均衡器。Eureka还支持集群模式,可以提高注册中心的可用性和容错性。简言之,Eureka用于替换传统的RestTemplate远程调用。

(一)产生的业务场景

Eureka产生的业务场景是为了解决微服务架构中服务间的注册和发现问题。在微服务架构中,服务的数量和分布是动态变化的,因此需要一个中心化的服务来管理服务的信息,如服务名、IP、端口等。Eureka就是这样一个基于REST的服务发现框架。

(二)快速搭建

1.新建一个模块,名为eureka

2.服务端pom.xml文件引入依赖

    <!--Eureka服务端依赖-->
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
    </dependencies>

3.服务端启动类上添加@EnableEurekaServer注解

//启用Eureka服务端
@EnableEurekaServer
@SpringBootApplication
public class EurekaApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaApplication.class, args);
    }
}

4.服务端application.yml文件中配置相关参数

spring:
  application:
    name: eureka #挂载到eureka服务器上显示的名字
server:
  port: 6061  #该资源模块的端口
eureka:       #Eureka核心配置
  instance:
    hostname: localhost   #主机名,服务器IP地址
  client:     #客户端配置,Eureka服务端也可以作为一个客户端
    #    registerWithEureka: false #默认true,eureka server服务器是否注册自己,单机版可以不用注册自己
    #    fetchRegistry: false    #默认true,是否获取注册中心服务服务端的服务信息
    serviceUrl:   #配置eureka server服务器地址,可以打开这个网址
      #      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
      defaultZone: http://localhost:6061/eureka/ #挂载到服务器的地址

5.客户端pom.xml文件引入依赖

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

6.客户端启动类上添加@EnableEurekaClient注解

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

7.客户端application.yml文件中配置相关参数

spring:
  application:
    name: student-provider #挂载到eureka服务器上显示的名字
eureka:
  client:
    #    register-with-eureka: true  默认开启,将这个提供具体功能的模块 挂载 到eureka server服务器上
    #    fetch-registry: true  默认开启,默认true,是否获取注册中心服务服务端的服务信息
    service-url:
      defaultZone: http://localhost:6061/eureka #挂载到服务器的地址

这样,就能将各模块挂载到eureka上了,可以访问http://localhost:6061/eureka/查看。

8.eureka远程调用格式

远程调用依旧需要RestTemplate类的支持,将该类注入。

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

在controller层中,需要注入DiscoveryClient,并自己编写远程调用策略。实例如下:

@Autowired
    private DiscoveryClient discoveryClient;

    public String serviceUrl() {
        List<ServiceInstance> list = discoveryClient.getInstances("student-provider");
        if (list != null && list.size() > 0 ) {
            return list.get(0).getUri()+"";
        }
        return null;
    }

    @Autowired
    RestTemplate restTemplate;

    @GetMapping("/all")
    public Object all(){
       return restTemplate.getForEntity(serviceUrl()+"/student/all", ResponseResault.class);
    }

9.拓展:eureka服务端集群配置

只需修改配置文件,通过-Dport命令新开一个端口模拟集群效果。

spring:
  application:
    name: eureka
server:
  port: ${port:6061}
eureka:       #Eureka核心配置
  instance:   #实例,
    hostname: ${hostname:a}   #主机名,服务器IP地址,集群部署,不能有相同的名字
  client:     #客户端配置,Eureka服务端也可以作为一个客户端
#    registerWithEureka: false #默认true,是否注册到Eureka服务端,单机版可以不用注册自己,集群配置,必须为真
#    fetchRegistry: false    #默认true,是否获取注册中心服务服务端的服务信息
    serviceUrl:   #客户端注册到服务端,这里就是配置服务端地址
#      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
      defaultZone: ${defaultZone:http://localhost:6062/eureka/}

(三)技术要点

1.心跳机制

客户端每过30秒会向服务端发送心跳包,超过3轮90秒,没有发送,服务器可以认为服务故障,可以从服务列表中删除服务。在客户端配置:

eureka:
    instance:
        lease-renewal-interval-in-seconds: 30 #续约时间
        lease-expiration-duration-in-seconds: 90  #失效续约时间

2.自我保护机制

默认是启动的,Eureka会将15分钟内心跳率低于85%的服务保护起来,为了保证服务可用。在服务端配置:

eureka:
  server:
    enable-self-preservation: true #自我保护
    eviction-interval-timer-in-ms:  60000 #服务剔除时间,默认值60s,过多久执行剔除策略

3.剔除策略

Eureka服务端会创建一个定时任务,每隔一段时间(默认为60秒)检查当前清单中超时(默认为90秒)没有续约的服务实例,并将它们从清单中移除,这个操作被称为失效剔除。

二、负载均衡Ribbon

Ribbon是一个客户端负载均衡器,它可以根据一定的规则,从多个服务提供者中选择一个合适的服务进行调用。Ribbon可以与Eureka Client结合使用,从Eureka Server中获取服务列表,并根据负载均衡策略进行选择。Ribbon也提供了多种负载均衡策略,如轮询、随机、权重等,也可以自定义负载均衡策略。简言之,Ribbon进一步简化远程调用,同时支持负载均衡。

(一)产生的业务场景

Ribbon的诞生是为了解决服务端负载均衡器的局限性,例如无法满足不同消费者(客户端)的负载均衡策略需求,以及增加了网络延迟和故障风险等问题。

(二)快速搭建

1.客户端pom.xml引入依赖

客户端在引入eureka-client依赖时,已经自动引入Ribbon的依赖。不需要手动添加依赖。

2.在RestTemplate配置类的方法上加上@LoadBalanced注解

    @Bean
    @LoadBalanced //ribbon方式调用微服务,不需要再手写调用方法,负载均衡
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }

3.修改调用方法

不需要注入DiscoveryCLient了,并重写serviceUrl()方法。

     public String serviceUrl() {
         return "http://STUDENT-PROVIDER"+"/student/";
     }

4.ribbon负载均衡算法,默认是轮询

student-provider: #调用的服务名
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule  #指定为随机   

(三)技术要点

1.nginx和ribbon负载均衡的区别

  • nginx是一个服务端的负载均衡器,它可以作为一个反向代理服务器,拦截客户端的请求,并根据配置的策略转发给后端的服务器。

  • ribbon是一个客户端的负载均衡器,它可以从注册中心获取可用的服务列表,并根据自己的算法选择调用哪个服务。

ribbon在消费者(调用者)配置,由调用者决定选择哪一个生产者。(eureka server服务器可以做集群,同时生产者(提供服务的模块)也可以做集群)。

三、Web服务客户端OpenFeign

OpenFeign是一个声明式的Web服务客户端,它可以让服务消费者通过接口和注解的方式,轻松地调用服务提供者的接口。OpenFeign可以与Eureka和Ribbon结合使用,从而实现服务的注册、发现和负载均衡。OpenFeign还提供了对Hystrix的支持,可以实现服务的熔断和降级。简言之,OpenFeign又进一步简化了远程调用。

(一)产生的业务场景

OpenFeign的诞生是为了简化和优化Feign的使用。

(二)快速搭建

1.在调用模块的pom.xml中添加依赖

	<!--OpenFeign依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

2.启动类上添加@EnableFeignClients注解

@EnableFeignClients //启用openfeign调用
@EnableEurekaClient
@SpringBootApplication
public class StudentConsumerApplication {
    public static void main(String[] args) {
        SpringApplication.run(StudentConsumerApplication.class, args);
    }
}

3.定义远程调用接口

被调用模块中有什么方法,就声明什么方法。

//Feign的远程调用,定义接口,底层使用AOP,自动生成接口的实现类
@FeignClient("student-provider")  //被调用模块的服务名
@RequestMapping("/student")
public interface StudentProviderFeignClient {

    @GetMapping("/")
    public ResponseResult getAllStudents();

    @GetMapping("/{sid}")
    public ResponseResult getStudentBySid(@PathVariable("sid") int sid);

    @PostMapping("/")
    public ResponseResult addStudent(@RequestBody Student student);
}

4.修改调用方法

    @Autowired
    StudentProviderFeignClient studentProviderFeignClient;

    @GetMapping("/all")
    public Object all() {
//        return restTemplate.getForEntity(serviceUrl() + "/all", ResponseResault.class);
        return studentProviderFeignClient.all();
    }

(三)技术要点

1.微服务调用方式总结

  • 原生RestTemplate远程调用
  • Eureka格式的远程调用
  • Ribbon格式的远程调用
  • OpenFeign格式的远程调用

2.openfeign超时策略

openfeign远程调用另一个资源,默认时间超过一秒,会报读取时间超时异常。可以修改配置文件。在调用模块中配置。

feign:
    client:
        config:
            default:
                connectTimeout: 5000
                readTimeout: 5000

四、断路器Hystrix

Hystrix是一个服务熔断和降级的组件,它可以在服务调用出现异常或超时的情况下,触发熔断机制,阻止继续调用该服务,从而避免雪崩效应。Hystrix还可以提供降级机制,当服务不可用时,返回一个预设的默认值或执行一个备用逻辑,从而保证服务的可用性。Hystrix还提供了一个仪表盘,用于监控服务的熔断和降级的状态和指标。简言之,Hystrix断路器提供降级、熔断、限流等功能,避免雪崩效应。

(一)产生的业务场景

Hystrix 的诞生是为了解决分布式系统中的联动故障问题,即当某个服务出现故障时,会导致依赖该服务的其他服务也出现故障,从而引发雪崩效应,影响整个系统的正常运行。

(二)快速搭建

1.在调用模块的pom.xml中添加依赖

<!--hystrix断路器依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>

2.启动类上添加@EnableHystrix注解

//@EnableDiscoveryClient
//断路器通用:Hystrix和Sentinel都可以
//@EnableCircuitBreaker
//Hystrix专用注解
@EnableHystrix
@EnableFeignClients
@EnableEurekaClient
@SpringBootApplication
public class StudentConsumerApplication {
    public static void main(String[] args) {
        SpringApplication.run(StudentConsumerApplication.class, args);
    }
}

3.yml配置文件还需开启使用

feign:
  hystrix:
    enabled: true

4.接口加上fallback属性

在之前openfeign创建的接口的@FeignClient注解上加fallback属性,指定降级方法所在类。

@FeignClient(name = "student-provider",fallback = StudentProviderFallback.class) //根据名字寻找对应模块中的方法,然后自动生成实体类
//注意这里不能直接在类上加映射,否则会报重复生成类错误
public interface StudentProviderFeignClient {

    @GetMapping("/student/all")
    public Object all();

    @GetMapping("/student/getById/{id}")
    public Object getById(@PathVariable("id") String id);

    @PostMapping("/student/add")
    public Object add(@RequestBody Student student);

}

5.定义降级方法

在调用模块中,新建类实现我们创建的远程调用的接口 ,配置降级方法(一般返回响应结果),返回值、参数都必须与被调用模块中的方法一致。如下实例:

@Component
public class StudentProviderFallback implements StudentProviderFeignClient{
    @Override
    public Object all() {
        return new ResponseResault(200,"all-fallback",null);
    }

    @Override
    public Object getById(@PathVariable("id") String id) {
        return new ResponseResault(200,"getById-fallback",null);
    }

    @Override
    public Object add(@RequestBody Student student) {
        return new ResponseResault(200,"add-fallback",null);
    }
}

(三)技术细节

1.Hystrix和异常处理的区别

Hystrix如果已经出了问题,错了几次,跳过该方法,直接执行降级方法(减少服务器压力),过一会会再次执行看好了没有。而异常处理,每次都会执行异常部分,然后再catch处理异常,消耗了很多服务资源。

2.Hystrix底层原理

断路器有三个状态:关闭,打开,半开。
开始,断电器关闭。当请求接口,接口出现问题,执行降级方法(达到一定的阈值条件:10秒,20个请求,错误率超过50%),不会执行请求接口,直接调用降级方法。熔断5秒以后,进入半开状态:放一个请求尝试执行请求接口,如果可以正常请求,断路器关闭,请求链路恢复正常;如果还是出现问题,继续熔断。

五、网关Zuul

Zuul是一个服务网关,它可以提供路由、过滤、安全等功能,对外暴露统一的访问入口,对内屏蔽服务的细节。Zuul可以与Eureka和Ribbon结合使用,实现动态路由和负载均衡。Zuul还提供了多种过滤器,用于实现请求的校验、转换、限流等功能,也可以自定义过滤器。简言之,Zuul可以为微服务统一口(类似nginx),并且可以统一身份认证,鉴权。

(一)产生的业务场景

Zuul 的产生是为了解决微服务系统中的复杂性和多样性问题,例如如何管理多个服务的访问、如何保证服务的可用性和性能、如何保护服务的安全和隐私等。

(二)快速开发

1.新建一个模块,名为zuul

2.服务端pom.xml文件引入依赖

        <!--zuul依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
            <version>2.2.9.RELEASE</version>
        </dependency>

3.服务端启动类上添加@EnableZuulProxy注解

@EnableZuulProxy  //启用zuul网关
@SpringBootApplication
public class ZuulApplication {

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

4.服务端application.yml文件中配置相关参数

一共有四种方式,介绍最简洁的一种。(忘了翻笔记)

zuul:
  routes:
    provider-student: /student/**  #请求路径,将会映射到provider-student名字对应的ip地址

5.zull的权限认证

实现ZuulFilter方法即可,包含了四个方法。

@Component
public class LoginFilter extends ZuulFilter {

    //拦截类型
    //per请求执行之前:认证授权
    //post请求执行之后
    //error发生错误时
    //routing路由时执行
    @Override
    public String filterType() {
        return "pre";
    }

    //执行顺序,数字越小,优先级越高
    @Override
    public int filterOrder() {
        return 0;
    }

    //是否拦截,
    //返回true,执行run()方法
    //返回false,不执行run()方法,放行
    @Override
    public boolean shouldFilter() {
        System.out.println("进入shouldFilter");
        RequestContext currentContext = RequestContext.getCurrentContext();
        HttpServletRequest request = currentContext.getRequest();
        HttpServletResponse response = currentContext.getResponse();
        //获取请求参数uname,有这个参数放行
        if (!StringUtils.isEmpty(request.getParameter("uname"))) {
            System.out.println("进入shouldFilter,有uname,直接放行,不需要run()方法继续判断");
            return false;
        }
        System.out.println("进入shouldFilter,没有uname,进入run()方法继续判断");
        return true;
    }

    //进入run(),可以拦截,也可放行
    @Override
    public Object run() throws ZuulException {
        System.out.println("进入run()方法,继续判断");
        //没有uname参数,再判断token参数,有token也方法
        RequestContext currentContext = RequestContext.getCurrentContext();
        HttpServletRequest request = currentContext.getRequest();
        HttpServletResponse response = currentContext.getResponse();
        response.setContentType("text/html;charset=utf-8");
        if (!StringUtils.isEmpty(request.getParameter("token"))) {
            System.out.println("进入run()方法,有token,放行");
            //放行
            return null;
        } else {
            //没有token拦截,网关拦截
            System.out.println("进入run()方法,没有token,拦截");
            currentContext.setSendZuulResponse(false);
//            currentContext.setResponseStatusCode(401);
            //响应前端信息,使用统一响应体
            ResponseResult responseResult=new ResponseResult(401,"not login","先登录");
            ObjectMapper objectMapper=new ObjectMapper();
            try {
                String strbody = objectMapper.writeValueAsString(responseResult);
//            response.getWriter().write(strbody);
                currentContext.setResponseBody(strbody);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }

            return null;
        }

    }
}

六、配置中心Config

Config是一个分布式配置中心,它可以让服务从一个统一的外部源获取配置信息,而不是在本地配置文件中。Config可以与Git、SVN等版本控制工具结合使用,实现配置的版本管理和备份。Config还可以与Eureka和Bus结合使用,实现配置的动态刷新和广播。简言之,Config可以统一管理微服务的配置文件。

(一)产生的业务场景

为了统一管理项目中所有配置的系统,同时使得服务可以动态地获取和刷新配置,提高配置的灵活性、可维护性和安全性。

(二)快速搭建

1.创建git远程仓库

各模块的配置文件,按照规范传到git。配置文件起名格式如:/master/模块名-dev.yml

2.新建一个模块,名为config

3.服务端pom.xml文件引入依赖

    <dependencies>
        <!--配置中心服务端依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-server</artifactId>
        </dependency>
    </dependencies>

4.服务端启动类上添加@EnableConfigServer注解

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

5.服务端application.yml文件中配置相关参数

server:
  port: 4041

spring:
  cloud:
    config:
      server:
        git:
          uri: https://gitee.com/lbsttt/config.git
#          username:  私有仓库,git账号密码
#          password: 

此时,配置中心服务端可以从远程仓库获取配置文件http://localhost:4041/master/studentconsumer-dev.yml

6.客户端pom.xml文件引入依赖

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

7.客户端创建bootstrap.yml配置文件

SpringBoot的配置文件有两种:应用程序级别的配置文件application.yml,还有一种优先级更高的系统级别的配置文件,bootstrap.yml。因为现在application.yml在git上,我们还需要连接到配置中心服务端才能获取服务。

#连接到配置中心服务端
spring:
  cloud:
    config:
      uri: http://localhost:4041
      label: master
      name: studentconsumer
      profile: dev

现在客户端可以通过配置中心服务端获取配置文件信息,配置中心服务端又从远程git仓库读取出配置文件信息。但是客户端因为缓存的原因,还无法动态刷新,下面的步骤对配置进行动态刷新。

8.客户端pom.xml文件引入依赖

        <!-- SpringBoot 监控插件-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

9.客户端配置暴露监控头

#连接到配置中心服务端
spring:
  cloud:
    config:
      uri: http://localhost:4041
      label: master
      name: studentconsumer
      profile: dev

management:
  endpoints:
    web:
      exposure:
        include: "refresh"  #actuator暴露刷新头,前端发送post请求,通知当前微服务读取修改后的最新的配置文件
                      #这里这个名字可以写include: "*",发送请求的时候必须是refresh这个名字

10.controller添加@RefreshScope 刷新注解

@RefreshScope  //刷新注解
@RestController
@RequestMapping("/studentconsumer")
public class StudentController {

11.发送刷新配置请求

在需要刷新配置的时候,发送刷新请求,这里使用windows的curl命令模拟发送请求,其中 uri+/actuator/refresh是固定写法。

curl -X POST http://localhost:7071/actuator/refresh

七、消息总线Bus

Bus是一个消息总线,它可以用于在分布式系统中传递消息,实现服务间的通信和协作。Bus可以与RabbitMQ、Kafka这两个消息中间件结合使用,实现消息的可靠传输。Bus还可以与Config结合使用,实现配置的动态刷新和广播。简言之,Bus解决发送多次刷新配置请求问题。

(一)产生的业务场景

Bus的诞生是为了解决分布式系统中的配置更新和管理问题。多个微服务更新配置,都发送一个refresh请求,很麻烦,现在一个请求解决。

(二)快速搭建

1.服务端pom.xml文件引入依赖

<!-- 添加Spring Cloud Bus和RabbitMQ的依赖 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

2.服务端application.yml文件中配置相关参数

# 配置RabbitMQ的连接信息
spring:
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest
# 暴露bus-refresh端点,用于接收刷新请求并广播给所有服务实例
management:
  endpoints:
    web:
      exposure:
        include: "bus-refresh"

3.客户端pom.xml文件引入依赖

<!-- 添加Spring Cloud Bus-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>

4.客户端application.yml文件中配置相关参数

# 配置RabbitMQ的连接信息
spring:
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest

到此配置中心就搭建完成,现在发送请求刷新配置文件即可。

5.通知连接到mq的所有微服都刷新

发送POST请求到配置中心服务端的bus-refresh端点,它会将请求转发给消息队列,然后消息队列会通知所有订阅了该队列的服务实例刷新配置

curl -X POST http://localhost:4041/actuator/bus-refresh

6.通知消息队列只给特定的微服务发送刷新请求

发送POST请求到配置中心服务端的bus-refresh/{destination}端点,其中{destination}是服务实例的名称和端口号,它会将请求转发给消息队列,然后消息队列会通知指定的服务实例刷新配置

curl -X POST http://localhost:4041/actuator/bus-refresh/微服务名:端口号

同时,config是配置中心的服务端,可以作为Eureka的客户端,注册到eureka。

7.将配置中心服务端注册到eureka

# 配置Eureka的注册中心地址
eureka:
  client:
    service-url:
      defaultZone: http://localhost:4040/eureka/
# 配置服务的名称和端口号
spring:
  application:
    name: config
  cloud:
    config:
      server:
        git:
          uri: https://github.com/xxx/config-repo.git # 配置Git仓库的地址
server:
  port: 4041

8.配置中心客户端可以通过4041的服务名去查找服务地址

# 配置Eureka的注册中心地址
eureka:
  client:
    service-url:
      defaultZone: http://localhost:4040/eureka/
# 配置服务的名称和端口号
spring:
  application:
    name: 服务名
  cloud:
    config:
      uri: http://config:4041 # 通过配置中心服务端的服务名和端口号获取配置信息
server:
  port: 端口号