第六课 Spring Cloud分布式微服务实战-微服务Spring Cloud

第六课 Spring Cloud分布式微服务实战-微服务Spring Cloud

tags:

  • Java
  • 慕课网

第一节 SpringCloud介绍

1.1 SpringCloud 概述与版本选型

  1. springcloud官网介绍:https://spring.io/microservices
  2. springcloud相关版本:https://spring.io/projects/spring-cloud
  3. 这里因为使用Boot 2.2.5所以spring cloud使用Hoxton。

1.2 构建eureka注册服务

  1. 新建pringcloud-eureka模块,配置启动类。引用api模块。
package com.imooc.eureka;


import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.ComponentScan;
import tk.mybatis.spring.annotation.MapperScan;

@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class,
                                  MongoAutoConfiguration.class})
@EnableEurekaServer // 开启注册中心
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}
    <dependencies>
        <dependency>
            <groupId>com.imooc</groupId>
            <artifactId>imooc-news-dev-service-api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
        	<groupId>org.springframework.cloud</groupId>
        	<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
   		</dependency>
    </dependencies>
  1. application.yml配置文件。测试一下。http://localhost:7000/
############################################################
#
# eureka 注册中心
# web访问端口号  约定:7000
#
############################################################
server:
  port: 7000
  tomcat:
    uri-encoding: UTF-8

############################################################
#
# 配置项目信息
#
############################################################
spring:
  application:
    name: springcloud-eureka

############################################################
#
# eureka 配置信息
#
############################################################
eureka:
  instance:
    # eureka 实例的hostname,可以是hostname,也可以自定义配置hostname
    hostname: eureka
  client:
    # 是否要把当前的eureka server注册到自己
    register-with-eureka: false
    # 从注册中心获得检索服务实例,server没有必要,直接false即可
    fetch-registry: false
    # 单实例配置自己的服务地址,高可用集群则配置多个地址
    service-url:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/

1.3 其他服务注册到eureka上

  1. api模块中引入依赖。
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
  1. 对user服务中application.yml配置eureka相关信息。
eureka:
  # 自定义eureka server的信息
  server:
    hostname: eureka
    port: 7000
  client:
    # 所有的微服务都必须注册到eureka中
    register-with-eureka: true
    # 从注册中心获得检索服务实例
    fetch-registry: true
    # 注册中心的服务地址
    service-url:
      defaultZone: http://${eureka.server.hostname}:${eureka.server.port}/eureka/
  1. 启动类配置。刷新验证:http://localhost:7000/
@EnableEurekaClient // 开启eureka client注册到server中
  1. 同样的方法注册article服务。通过eureka调用别的服务的接口
    // 注入服务发现,可以获得已经注册的服务相关信息
    @Autowired
    private DiscoveryClient discoveryClient;
    
        String serviceId = "SERVICE-USER";
        List<ServiceInstance> instanceList = discoveryClient.getInstances(serviceId);
        ServiceInstance userService = instanceList.get(0);

        String userServerUrlExecute
                = "http://" + userService.getHost() +
                ":"
                + userService.getPort()
                + "/user/queryByIds?userIds=" + JsonUtils.objectToJson(idSet);

1.4 动态构建eureka集群

  1. 和上面创建工程eureka一样,修改配置文件。因为eureka服务之前需要通信,所以需要 defaultZone配置别的节点的地址即可。
eureka:
  instance:
    # 集群中每个eureka的名字都是唯一的
    hostname: eureka-cluster-${server.port}
  other-node-port2: ${p2:7002}
  other-node-port3: ${p3:7003}
  client:
#    register-with-eureka: false
#    fetch-registry: false
    # 单实例配置自己的服务地址,高可用集群则配置多个地址
    service-url:
      defaultZone: http://eureka-cluster-${eureka.other-node-port2}:${eureka.other-node-port2}/eureka/,http://eureka-cluster-${eureka.other-node-port3}:${eureka.other-node-port3}/eureka/
  server:
    enable-self-preservation: false   # 关闭eureka的自我保护功能
    eviction-interval-timer-in-ms: 5000   # 清理无效节点的时间,可以缩短为5s,默认60s
  1. 通过配置启动JVM数,启动三个实例。eureka集群搭建就可以启动起来。
  2. 注册别的服务到eureka集群中。这里要注册三个服务上去。如果要启用多个客户端用动态传入port的方式启动多个服务。默认轮询的访问客户端。
eureka:
  # 自定义eureka server的信息
  server:
    hostname: eureka
    port: 7000
  client:
    # 所有的微服务都必须注册到eureka中
    register-with-eureka: true
    # 从注册中心获得检索服务实例
    fetch-registry: true
    # 注册中心的服务地址
    service-url:
      #      defaultZone: http://${eureka.server.hostname}:${eureka.server.port}/eureka/
      defaultZone: http://eureka-cluster-7001:7001/eureka/,http://eureka-cluster-7002:7002/eureka/,http://eureka-cluster-7003:7003/eureka/
  instance:
    lease-renewal-interval-in-seconds: 3      # 调整微服务(eureka client)和注册中心(eureka server)的心跳时间
    lease-expiration-duration-in-seconds: 5   # eureka 举例最近的一次心跳等待提出的时间,默认90s

1.5 eureka自我保护功能

  1. 默认自我保护功能是开启的,它可以防止网络中断导致客户端连接不上服务端,但是各个服务都能正常运行。这时候开启自我保护功能可以防止eueka下掉客户端。
  2. 开发环境可以关闭,生产环境可以开一下自我保护功能。
  server:
    enable-self-preservation: false   # 关闭eureka的自我保护功能
    eviction-interval-timer-in-ms: 5000   # 清理无效节点的时间,可以缩短为5s,默认60s
  1. 客户端的心跳时间配置
  instance:
    lease-renewal-interval-in-seconds: 3      # 调整微服务(eureka client)和注册中心(eureka server)的心跳时间
    lease-expiration-duration-in-seconds: 5   # eureka 举例最近的一次心跳等待提出的时间,默认90s

第二节 Ribbon和feign

2.1 Ribbon介绍

  1. 服务间通信的负载均衡工具,提供完善的超时重试机制。
  2. Ribbon它是一种客户端的负载均衡,Nginx是常见的服务端的负载均衡。它本身就存在于eureka的客户端包中,maven中可见。
  3. com.imooc.api.config.CloudConfig加上@LoadBalanced实现默认轮询的调用。
    @Bean
    @LoadBalanced // 默认轮询的调用
    public RestTemplate restTemplate() {
        return new RestTemplate(new OkHttp3ClientHttpRequestFactory());
    }

2.2 自定义负载规则

  1. api服务中com.rule.MyRule。这个包不要被启动类扫描到。
  2. 客户端调用使用这个规则,方法一。article启动类上加注解
package com.rule;

import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MyRule {

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

}
@RibbonClient(name = "SERVICE-USER", configuration = MyRule.class)
  1. 客户端调用使用这个规则,方法二。直接在application.yaml配置
SERVICE-USER:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

2.3 ribbon重试机制

  1. api模块中导入依赖。
        <dependency>
            <groupId>org.springframework.retry</groupId>
            <artifactId>spring-retry</artifactId>
        </dependency>
  1. 重试策略配置,application.yaml配置。防止网络抖动。
ribbon:
  ConnectTimeout: 5000          # 创建连接的超时时间,单位:ms
  ReadTimeout: 5000             # 在连接创建好以后,调用接口的超时时间,单位:ms
  MaxAutoRetries: 1             # 最大重试次数
  MaxAutoRetriesNextServer: 2   # 切换到下个微服务实例的重试次数
  # 当请求到某个微服务5s,超时后会进行重试,先重试连接自己当前的这个实例
  # 如果当前重试失败1次,则会切换到访问集群中的下一个微服务实例,切换最大为2次
  
logging:
  level:
    com.imooc.api.controller.user.UserControllerApi: debug
#    root: debug

2.4 feign简化服务间调用

  1. api中导入依赖。
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
  1. 调用端article服务启动类中加入注解。
@EnableFeignClients({"com.imooc"})
  1. 被调用端User的api的上方,加入注解。
// 别名叫做service-use
// @FeignClient(value = "service-user")
@FeignClient(value = MyServiceList.SERVICE_USER)
  1. article具体调用的地方注入并使用

    @Autowired
    private UserControllerApi userControllerApi;

    // 发起远程调用,获得用户的基本信息
    private List<AppUserVO> getPublisherList(Set idSet) {

        GraceJSONResult bodyResult = userControllerApi.queryByIds(JsonUtils.objectToJson(idSet));

        List<AppUserVO> publisherList = null;
        if (bodyResult.getStatus() == 200) {
            String userJson = JsonUtils.objectToJson(bodyResult.getData());
            publisherList = JsonUtils.jsonToList(userJson, AppUserVO.class);
        } else {
            publisherList = new ArrayList<>();
        }
        return publisherList;
    }
  1. feign的日志调试开启。
logging:
  level:
    com.imooc.api.controller.user.UserControllerApi: debug
#    root: debug

# 配置feign
feign:
  client:
    config:
      # 配置服务提供方的名称
      service-user:
        loggerLevel: FULL
  1. 统一验证处理。这里注意如果使用feign在一个方法中不能写两个以上的对象,因为对feign来说就是传了俩个body。如下面所示。
@FeignClient(value = MyServiceList.SERVICE_USER)

    @ApiOperation(value = "完善修改用户信息", notes = "完善修改用户信息", httpMethod = "POST")
    @PostMapping("/updateUserInfo")
    public GraceJSONResult updateUserInfo(@RequestBody @Valid UpdateUserInfoBO updateUserInfoBO,
                                          BindingResult result);
  1. 对这种方法改造一下。com.imooc.exception.GraceExceptionHandler这里捕获这个参数验证异常同一处理。
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseBody
    public GraceJSONResult returnException(MethodArgumentNotValidException e) {
        BindingResult result = e.getBindingResult();
        Map<String, String> map = getErrors(result);
        return GraceJSONResult.errorMap(map);
    }

    public Map<String, String> getErrors(BindingResult result) {
        Map<String, String> map = new HashMap<>();
        List<FieldError> errorList = result.getFieldErrors();
        for (FieldError error : errorList) {
            // 发送验证错误的时候所对应的某个属性
            String field = error.getField();
            // 验证的错误消息
            String msg = error.getDefaultMessage();
            map.put(field, msg);
        }
        return map;
    }
  1. 这里把第二个验证的参数去掉即可。
    @ApiOperation(value = "完善修改用户信息", notes = "完善修改用户信息", httpMethod = "POST")
    @PostMapping("/updateUserInfo")
    public GraceJSONResult updateUserInfo(@RequestBody @Valid UpdateUserInfoBO updateUserInfoBO);

第三节 hystrix熔断和网关zuul

3.1 hystrix断路器

  1. 提供容错机制,避免微服务系统雪崩。
  2. api中引入依赖。
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
  1. 服务提供者的接口上加上熔断的注解。一旦异常去请求回调函数queryByIdsFallback
   @HystrixCommand(fallbackMethod = "queryByIdsFallback")
  1. 服务提供者启动类上加上熔断的注解。
@EnableCircuitBreaker // 开启hytrix的熔断机制
  1. 如果是超时异常可以在application.yml中加入配置。超过两秒进入降级。
# 配置hystrix
hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 2000   # 设置hystrix超时时间,超过2秒触发降级
      circuitBreaker:   # 配置断路器
        enabled: true
        requestVolumeThreshold: 10    # 触发熔断最小请求次数,默认:20
        sleepWindowInMilliseconds: 15000    # 熔断后过几秒后尝试半开状态(请求重试),默认:5s
        errorThresholdPercentage: 50  # 触发熔断的失败率(异常率/阈值),默认:50
  1. 全局降级,如果方法比较多,每个方法都去加一个这个回调函数很麻烦。可以在服务提供者controll上加一个默认的回调函数。defaultFallbackfunc写在当前controller中。
@DefaultProperties(defaultFallback = "defaultFallbackfunc")

  @HystrixCommand // 默认就是调用"defaultFallbackfunc"方法
  1. 服务调用者降级。application.yaml中配置增加。如果服务端全挂了,客户端降级就派上用场了。
  hystrix:
    enabled: true   # 打开feign客户端的内置hystrix
  1. 在服务调用的调用者上加上注解。UserControllerFactoryFallback是自定义的回调函数。
@FeignClient(value = MyServiceList.SERVICE_USER, fallbackFactory = UserControllerFactoryFallback.class)
  1. com.imooc.api.controller.user.fallbacks.UserControllerFactoryFallback内容如下。
package com.imooc.api.controller.user.fallbacks;

import com.imooc.api.controller.user.UserControllerApi;
import com.imooc.grace.result.GraceJSONResult;
import com.imooc.grace.result.ResponseStatusEnum;
import com.imooc.pojo.bo.UpdateUserInfoBO;
import com.imooc.pojo.vo.AppUserVO;
import feign.hystrix.FallbackFactory;
import org.springframework.stereotype.Component;

import javax.validation.Valid;
import java.util.ArrayList;
import java.util.List;

@Component
public class UserControllerFactoryFallback
        implements FallbackFactory<UserControllerApi> {

    @Override
    public UserControllerApi create(Throwable throwable) {
        return new UserControllerApi() {
            @Override
            public GraceJSONResult getUserInfo(String userId) {
                return GraceJSONResult.errorCustom(ResponseStatusEnum.SYSTEM_ERROR_FEIGN);
            }

            @Override
            public GraceJSONResult getAccountInfo(String userId) {
                return GraceJSONResult.errorCustom(ResponseStatusEnum.SYSTEM_ERROR_FEIGN);
            }

            @Override
            public GraceJSONResult updateUserInfo(@Valid UpdateUserInfoBO updateUserInfoBO) {
                return GraceJSONResult.errorCustom(ResponseStatusEnum.SYSTEM_ERROR_FEIGN);
            }

            @Override
            public GraceJSONResult queryByIds(String userIds) {
                System.out.println("进入客户端(服务调用者)的降级方法");
                List<AppUserVO> publisherList = new ArrayList<>();
                return GraceJSONResult.ok(publisherList);
            }
        };
    }
}

3.2 zuul微服务网关

  1. 微服务的网关,可以实现动态路由、过滤器等功能。
  2. 重新创建springcloud-zuul-server模块。
  3. api服务中添加依赖。
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
        </dependency>
  1. springcloud-zuul-server启动类注解。
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class,
                                    MongoAutoConfiguration.class})
@ComponentScan(basePackages = {"com.imooc", "org.n3r.idworker"})
@EnableEurekaClient
//@EnableZuulServer
@EnableZuulProxy       // @EnableZuulProxy是@EnableZuulServer的一个增强升级版,当zuul和eureka、ribbon等组件共同使用,则使用增强版即可
public class Application {

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

}
  1. application.yaml的zuul配置。这里地址写死的,如果变动要修改会比较麻烦。
zuul:
  routes:
      service-article:                  # 配置微服务的路由id,微服务的实例id
        path: /service-article/**       # 请求路径(前缀)
        url: http://192.168.1.2:8001    # 请求转发到指定的微服务所在的ip地址
  prefix: /api   
  1. 可以通过eureka的服务,请求实例。
zuul:
  routes:
      service-article:                  # 配置微服务的路由id,微服务的实例id
        path: /service-article/**       # 请求路径(前缀)
        service-id: service-article     # 请求转发的微服务实例id
  prefix: /api                        # 请求前缀
  1. 简化上面路径**由于路由id和微服务实例id相同,我们可以简化转发的配置*
# 路由规则: http://[网关地址]:[端口号]/[prefix]/[微服务实例id]/[请求地址路径]
zuul:
  routes:
    # 由于路由id和微服务实例id相同,我们可以简化转发的配置
    service-article: /service-article/**
  #    service-article:                  # 配置微服务的路由id,微服务的实例id
#      path: /service-article/**       # 请求路径(前缀)
#      service-id: service-article     # 请求转发的微服务实例id
#      url: http://192.168.1.2:8001    # 请求转发到指定的微服务所在的ip地址
  prefix: /api                        # 请求前缀

3.3 zuul自定义过滤器

  1. com.imooc.zuul.filters.MyFilter
package com.imooc.zuul.filters;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.stereotype.Component;

/**
 * 构建zuul的自定义过滤器
 */
@Component
public class MyFilter extends ZuulFilter {

    /**
     * 定义过滤器的类型
     *      pre:    在请求被路由之前执行
     *      route:  在路由请求的时候执行
     *      post:   请求路由以后执行
     *      error:  处理请求时发生错误的时候执行
     * @return
     */
    @Override
    public String filterType() {
        return "pre";
    }

    /**
     * 过滤器执行的顺序,配置多个有顺序的过滤
     * 执行顺序从小到大
     * @return
     */
    @Override
    public int filterOrder() {
        return 1;
    }

    /**
     * 是否开启过滤器
     *      true:开启
     *      false:禁用
     * @return
     */
    @Override
    public boolean shouldFilter() {
        return true;
    }

    /**
     * 过滤器的业务实现
     * @return
     * @throws ZuulException
     */
    @Override
    public Object run() throws ZuulException {

        System.out.println("display pre zuul filter...");

        return null;    // 没有意义可以不用管。
    }
}

3.4 限制ip黑名单的频繁访问

  1. 首先appliction.yaml中配置。
# ip请求限制的参数配置
blackIp:
  continueCounts: ${counts:10}    # ip连续请求的次数
  timeInterval: ${interval:10}    # ip判断的事件间隔,单位:秒
  limitTimes: ${times:15}         # 限制的事件,单位:秒
  1. 自定义过滤器实现com.imooc.zuul.filters.BlackIPFilter
package com.imooc.zuul.filters;

import com.imooc.grace.result.GraceJSONResult;
import com.imooc.grace.result.ResponseStatusEnum;
import com.imooc.utils.IPUtil;
import com.imooc.utils.JsonUtils;
import com.imooc.utils.RedisOperator;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;

@Component
@RefreshScope
public class BlackIPFilter extends ZuulFilter {

    @Value("${blackIp.continueCounts}")
    public Integer continueCounts;
    @Value("${blackIp.timeInterval}")
    public Integer timeInterval;
    @Value("${blackIp.limitTimes}")
    public Integer limitTimes;

    @Autowired
    private RedisOperator redis;

    @Override
    public String filterType() {
        return "pre";
    }

    @Override
    public int filterOrder() {
        return 2;
    }

    @Override
    public boolean shouldFilter() {
        return true;
    }

    @Override
    public Object run() throws ZuulException {

        System.out.println("执行【ip黑名单】过滤器...");

        System.out.println("continueCounts: " + continueCounts);
        System.out.println("timeInterval: " + timeInterval);
        System.out.println("limitTimes: " + limitTimes);


        // 获得上下文对象
        RequestContext context = RequestContext.getCurrentContext();
        HttpServletRequest request = context.getRequest();

        // 获得ip
        String ip = IPUtil.getRequestIp(request);

        /**
         * 需求:
         *  判断ip在10秒内的请求次数是否超过10次
         *  如果超过,则限制这个ip访问15秒,15秒以后再放行
         */

        final String ipRedisKey = "zuul-ip:" + ip;
        final String ipRedisLimitKey = "zuul-ip-limit:" + ip;

        // 获得当前ip这个key的剩余时间
        long limitLeftTime = redis.ttl(ipRedisLimitKey);
        // 如果当前限制ip的key还存在剩余时间,说明这个ip不能访问,继续等待
        if (limitLeftTime > 0) {
            stopRequest(context);
            return null;
        }

        // 在redis中累加ip的请求访问次数
        long requestCounts = redis.increment(ipRedisKey, 1);
        // 从0开始计算请求次数,初期访问为1,则设置过期时间,也就是连续请求的间隔时间
        if (requestCounts == 1) {
            redis.expire(ipRedisKey, timeInterval);
        }

        // 如果还能取得请求次数,说明用户连续请求的次数落在10秒内
        // 一旦请求次数超过了连续访问的次数,则需要限制这个ip的访问
        if (requestCounts > continueCounts) {
            // 限制ip的访问时间
            redis.set(ipRedisLimitKey, ipRedisLimitKey, limitTimes);
            stopRequest(context);
        }

        return null;
    }

    private void stopRequest(RequestContext context) {
        // 停止zuul继续向下路由,禁止请求通信
        context.setSendZuulResponse(false);
        context.setResponseStatusCode(200);
        String result = JsonUtils.objectToJson(
                GraceJSONResult.errorCustom(
                        ResponseStatusEnum.SYSTEM_ERROR_ZUUL));
        context.setResponseBody(result);
        context.getResponse().setCharacterEncoding("utf-8");
        context.getResponse().setContentType(MediaType.APPLICATION_JSON_VALUE);
    }
}

第四节 配置中心config和总线BUS

4.1 分布式配置中心

  1. SpringCloud Config为所有服务提供统一的配置管理服务
  2. 优点:统一管理配置、管理不同环境下的配置和动态调整配置
  3. 新建配置中心服务springcloud-config,启动配置注解。
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-server</artifactId>
        </dependency>
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class,
                                    MongoAutoConfiguration.class})
@ComponentScan(basePackages = {"com.imooc", "org.n3r.idworker"})
@EnableEurekaClient
@EnableConfigServer
public class Application {

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

}
  1. 从git中获取配置文件。直接访问:192.168.44.128:7080/zuul-dev.yml 查看配置
spring:
  application:
    name: springcloud-config
  cloud:
    config:
      server:
        git:
          uri: https://github.com/qnhyn/imooc-news-config.git
  1. application.yaml属于用户层次的配置文件,bootstrap.yaml是系统级的配置文件。优先级比application.yaml更高
  2. 配置客户端获得配置文件。
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-config</artifactId>
        </dependency>
spring:
  application:
    name: springcloud-zuul-server
  cloud:
    config:
      label: master # 代表配置中心git的分支
      name: zuul
      profile: prod # 环境变量
#      uri: http://192.168.1.2:7080
      discovery:
        enabled: true
        service-id: springcloud-config
  1. 动态刷新git配置。zuul中引入actuator
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
# 配置动态刷新git配置的路径终端请求地址
management:
  endpoints:
    web:
      exposure:
        include: refresh
  1. 配置完毕后,需要主动发起请求刷新配置。http://localhost7070/actuator/refresh 需要配置@RefreshScope注解,开启自刷新功能。
@RefreshScope
public class BlackIPFilter extends ZuulFilter

4.2 消息总线BUS

  1. 上面的config的问题:手动刷新与业务耦合。N个微服务端需要N次手动刷新。
  2. SpringCloud Bus 为SpringCloud Config提供增益buff,可以实现配置自动刷新
  3. git发生配置更改config就会根据消息总线推给我们的各个服务进行更改。Bus支持RabbitMQ和kafka
  4. config服务引入bus配置文件,zuul-server服务也需要引入这个。
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bus-amqp</artifactId>
        </dependency>
  1. 配置zuul-server服务和config服务的rabbitmq
  rabbitmq:
    host: 192.168.44.128
    port: 5672
    username: admin
    password: 123456
    virtual-host: imooc-news-dev
  1. config服务配置application.yaml
# 配置动态刷新git配置的路径终端请求地址
management:
  endpoints:
    web:
      exposure:
        include: "*"
  1. 这里直接请求:http://localhost7070/actuator/bus-refresh/ 刷新配置端 再次请求就是最新的配置了。全量刷新。
  2. 精确刷线:http://localhost7070/actuator/bus-refresh/{微服务的实例id}:{port} 只刷新一个服务的配置。

第五节 消息驱动和链路追踪

5.1 Spring Cloud Stream

  1. 统一封装消息的服务框架
  2. RabbitMQ, RocketMQ, Kafka,ActiveMQ,ZeroMQ … 每种消息队列如果都要学习就会很繁琐。通过 Spring Cloud Stream可以把这些MQ整合到一起,提供一个统一的接口去调用。现在支持RabbitMQ和Kafka
  3. Stream 消息模型
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-idxhYiJh-1654791009866)(.\printscreen\6节_1.png)]
  4. 引入stream依赖。article客户端调用user服务端。这两个服务都需要引用。
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-stream-rabbit</artifactId>
        </dependency>
  1. article和user都配置一下。
# article
  cloud:
    stream:
      bindings:                           # 绑定通道和交换机
        myOutput:                         # 定义生产者的通道
          # 自定义交换机的名字,也就是代码里构建的消息,交给底层mq的交换机
          destination: streamExchange
        myInput:                          # 定义消费者的通道
          # 自定义交换机的名字,也就是消息从底层mq输入到消费端进行消费
          destination: streamExchange
          group: boys          
# user
  cloud:
    stream:
      bindings:                           # 绑定通道和交换机
        myOutput:                         # 定义生产者的通道
          # 自定义交换机的名字,也就是代码里构建的消息,交给底层mq的交换机
          destination: streamExchange
        myInput:                          # 定义消费者的通道
          # 自定义交换机的名字,也就是消息从底层mq输入到消费端进行消费
          destination: streamExchange
          group: girls         
  1. rticle和user都配置新建通道channel的接口com.imooc.article.stream.MyStreamChannel
/**
 * 声明构建通道channel
 */
@Component
public interface MyStreamChannel {

    String OUTPUT = "myOutput";
    String INPUT = "myInput";

    @Output(MyStreamChannel.OUTPUT)
    MessageChannel output();

    @Input(MyStreamChannel.INPUT)
    SubscribableChannel input();
}
  1. com.imooc.article.stream.StreamService生产者用来发送消息和它的实现com.imooc.article.stream.StreamServiceImpl通过@EnableBinding(MyStreamChannel.class)绑定。
/**
 * 开启绑定器
 * 绑定通道channel
 */
@Component
@EnableBinding(MyStreamChannel.class)
public class StreamServiceImpl implements StreamService {

    @Autowired
    private MyStreamChannel myStreamChannel;

    @Override
    public void sendMsg() {
        AppUser user = new AppUser();
        user.setId("10101");
        user.setNickname("imooc");

        // 消息通过绑定器发送给mq
        myStreamChannel.output()
                .send(MessageBuilder.withPayload(user).build());
    }

    @Override
    public void eat(String dumpling) {
        myStreamChannel.output()
                .send(MessageBuilder.withPayload(dumpling).build());
    }
}
  1. 消费者绑定com.imooc.article.stream.MyStreamConsumer
/**
 * 构建消费端
 */
@Component
@EnableBinding(MyStreamChannel.class)
public class MyStreamConsumer {

    /**
     * 监听并且实现消息的消费和相关业务处理
     */
//    @StreamListener(MyStreamChannel.INPUT)
//    public void receiveMsg(AppUser user) {
//        System.out.println(user.toString());
//    }

    @StreamListener(MyStreamChannel.INPUT)
    public void receiveMsg(String dumpling) {
        System.out.println(dumpling);
    }
}
  1. 控制器测试。com.imooc.article.controller.HelloController
    @Autowired
    private StreamService streamService;

    @GetMapping("/stream")
    public Object stream() {
//        streamService.sendMsg();

        for (int i = 0 ; i < 10 ; i ++ ) {
            streamService.eat("我吃了第" + (i+1) + "只饺子~");
        }

        return "ok~~!!!";
    }
  1. 启动article和启动俩个user客户端,进行测试。localhost:8001/producer/stream 。因为配置了group所以开启了持久化,重启user客户端会继续消费。

5.2 链路追踪Sleuth

  1. 贯穿整个分布式微服务系统中,追踪一个请求的过程。zipkin可视化控制面板
  2. 下载服务端zipkin,启动访问:localhost:9411/zipkin
java -jar zipkin-server-2.12.9-exec.jar
  1. 客户端引入依赖。
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-sleuth</artifactId>
        </dependency>
       <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zipkin</artifactId>
        </dependency>
  zipkin:
    # 配置zipkin采集的服务地址,数据会发送到这里
    base-url: http://192.168.1.2:9411/
    sender:
      # 数据采集的传输通信方式,web http的形式
      type: web
  sleuth:
    sampler:
      # 数据采样比例(百分数),0~1
      probability: 1
  1. 一般企业中用到的链路追踪skywalking比较多。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值