spring cloud 集成和使用

说明:

父工程文件如下:

  spring boot 版本:2.6.8

  spring cloud 版本:3.1.3

  hystrix,停更以后没有最新版,所以用的:2.2.10.RELEASE

 

父工程 pom.xml 如下:所有子项目使用的依赖版本都在这里面

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.lomi.sc</groupId>
    <artifactId>sc</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>pom</packaging>

    <!--子模块-->
    <modules>
        <module>eureka-server</module>
        <module>user</module>
        <module>order</module>
        <module>goods</module>
        <module>api</module>
        <module>gateway</module>
        <module>config</module>
    </modules>


    <dependencyManagement>
        <dependencies>
            <!--spring部分-->
            <!--spring-boot-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
                <version>2.6.8</version>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-actuator</artifactId>
                <version>2.6.8</version>
            </dependency>


            <!--服务注册和发现部分组件-->
            <!--eureka-server-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
                <version>3.1.3</version>
            </dependency>
            <!--eureka-client-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
                <version>3.1.3</version>
            </dependency>


            <!--cloud部分-->
            <!--spring-cloud-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-context</artifactId>
                <version>3.1.3</version>
            </dependency>
            <!--feign-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-openfeign</artifactId>
                <version>3.1.3</version>
            </dependency>
            <!--hystrix-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
                <version>2.2.10.RELEASE</version>
            </dependency>
            <!--gateway-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-gateway</artifactId>
                <version>3.1.3</version>
            </dependency>
            <!--config-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-config-server</artifactId>
                <version>3.1.3</version>
            </dependency>
            <!--config 客户端需要的两个依赖-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-config</artifactId>
                <version>3.1.3</version>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-bootstrap</artifactId>
                <version>3.1.3</version>
            </dependency>
            <!--bus-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-bus-amqp</artifactId>
                <version>3.1.2</version>
            </dependency>

            <!--sleuth和zipkin  -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-sleuth</artifactId>
                <version>3.1.3</version>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-sleuth-zipkin</artifactId>
                <version>3.1.3</version>
            </dependency>

            <!--spring cloud stream -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-stream-rabbit</artifactId>
                <version>3.2.4</version>
            </dependency>

            <!--日志-->
            <!--<dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-logging</artifactId>
                <version>2.7.1</version>
            </dependency>-->




        </dependencies>
    </dependencyManagement>




    <build>
        <plugins>
            <!-- 指定项目jdk版本 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>

            <!-- 打包 -->
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <executions>
                    <execution>
                        <goals>
                            <!--可以把依赖的包都打包到生成的Jar包中 -->
                            <goal>repackage</goal>
                        </goals>
                        <configuration>
                            <attach>false</attach>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

        </plugins>
    </build>

    <!-- 跳过测试 -->
    <properties>
        <skipTests>true</skipTests>
    </properties>

</project>

  

 

 

 

1 注册中心 Eureka 的集成和使用

  分布式架构注册中心必不可少,dubbo 一般使用 的 zookeeper,spring cloud alibaba 使用 nacos

  1 创建 项目  eureka-server

  2 修改pom.xml 文件  

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>sc</artifactId>
        <groupId>com.lomi.sc</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <modelVersion>4.0.0</modelVersion>
    <artifactId>eureka-server</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

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

        <!--gson 版本过低的问题也许需要这个-->
        <!--<dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.6.2</version>
        </dependency>-->


    </dependencies>

</project>

  3 创建启动类

    

package com.lomi.eureka;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

import java.io.IOException;


/**
 *
 * 1 eureka 里面使用gson 版本太低,需要替换成版本或者使用 ,缺少 com.google.gson.GsonBuilder.setLenient 方法
 * 2 可以用 @SpringBootApplication(exclude = {GsonAutoConfiguration.class}) 修饰,或者 引入高版本的 gson
 *
 */

/**
 *
 *
 * @author ZHANGYUKUN
 */
@EnableEurekaServer
@SpringBootApplication(exclude = {GsonAutoConfiguration.class})
public class Application {

    public static void main(String[] args) throws IOException {
        SpringApplication.run(Application.class, args); 
        System.out.println("*****************************");
        System.out.println("********* eureka server 启动................... ***********");
        System.out.println("*****************************"); 
        System.in.read(); 

    }
    
}

  

   4 访问 http://127.0.0.1:9001/

  

 

 

 

 

2 spring cloud 集成 和 restemple 的使用

   1 创建项目goods,order

  

   2 pow.xml 文件,导入依赖,eureke 和  cloud(goods 和 order一样)

    

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

        <!--cloud-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-context</artifactId>
        </dependency>

 

 3 启动类(goods 和 order 样)   

/**
 *
 *
 * @author ZHANGYUKUN
 */
@SpringBootApplication
@EnableEurekaClient
public class Application {

    public static void main(String[] args) throws IOException {
        SpringApplication.run(Application.class, args); 
        System.out.println("*****************************");
        System.out.println("********* goodsServer 启动................... ***********");
        System.out.println("*****************************"); 
        System.in.read(); 

    }



}

  

  

  4 goods 创建 restTemplate(写在任意spring boot 可以扫描到的地方),使用了 @LoadBalanced 注解以后,获使用负载均衡的方式调用消费者端,这时候 请求url 必须要是服务名,而不是能是ip

/**
 * 创建 bean 的配置类
 * @author ZHANGYUKUN
 * @date 2022/6/24
 */
@Component
public class BeanCreatorConfig {

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


}

  

  5 goods 调用 order controller

package com.lomi.goods.controller;

import com.lomi.api.order.OrderService;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.List;

/**
 * @author ZHANGYUKUN
 * @date 2022/6/24
 */
@RestController
@RequestMapping("shoppingCart")
@EnableDiscoveryClient
public class ShoppingCartController {

    Logger log = LoggerFactory.getLogger(ShoppingCartController.class);

    //引用配置文件里面写入的服务地址
    @Value("${system.serverUrl.order}")
    String orderServerUrl;

    @Autowired
    RestTemplate restTemplate;

    /**
     * 使用 restTemplate 调用 远程服务
     *
     * @return
     */
    @PostMapping("generateOrder")
    public String generateOrder() {
        System.out.println("generateOrder" + ":" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        //查询购物车
        List<String> goodsNames = Arrays.asList("商品1", "商品2", "商品2");

        //调用order服务生成订单
        String rt = restTemplate.postForObject(orderServerUrl +"orderServer/"+ "order/generate", goodsNames, String.class);
        System.out.println(rt);

        //清空购物车
        return "OK";
    }


   


}

 

 6 goods application.yml 主要配置

## web ##
server:
  port: 8001
  servlet:
    context-path: /goodsServer

system:
  serverUrl:
    order: "http://order-server/"
    goods: "http://goods-server/"
feign:
  httpclient:  #配置http连接池
    enabled: true
    max-connection: 200
    max-connections-per-route: 50
    connection-timeout: 2000

  

  7 order 端controller类似

package com.lomi.order.controller;

import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.web.bind.annotation.*;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;

/**
 * @author ZHANGYUKUN
 * @date 2022/6/24
 */
@RestController
@RequestMapping("order")
public class OrderController {
    Logger log = LoggerFactory.getLogger(OrderController.class);

    @Value("${server.port}")
    String port;


    @PostMapping("generate")
    public String generate(@RequestBody List<String> goodsInfo) throws InterruptedException {
        System.out.println( "创建订单中:" + port +":"+ LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));

       //TimeUnit.SECONDS.sleep(5);

       /* int i = 0;
        if( i==0 ){
            throw new RuntimeException("主动抛出异常");
        }*/


        System.out.println( "创建订单完成:" + port +":"+ LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));

        log.debug("创建订单完成");
        return "orderGenerateOK";
    }

    public String generateFB(@RequestBody List<String> goodsInfo) throws InterruptedException {
        return "服务器端FB";
    }




}

  

   

3 feign 的集成和使用

  feign 是 springcloud 对接口和远程 服务的封装,spring cloud 最基本的核心。现在用的一般都是 openfeign,基本使用的是 springcloud alibaba 也依旧会使用 feign 

 

  1 goods 和 order 引入依赖  

        <!--feign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

  

2 goods和order 启动类上添加 @EnableFeignClients启用 feign  ,feign 默认使用负载均衡的方式调用消费者

/**
 *
 *
 * @author ZHANGYUKUN
 */
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
public class Application {

    public static void main(String[] args) throws IOException {
        SpringApplication.run(Application.class, args); 
        System.out.println("*****************************");
        System.out.println("********* goodsServer 启动................... ***********");
        System.out.println("*****************************"); 
        System.in.read(); 

    }

  

  3 goods 端或者创建一个工程用来定义 远程 api OrderService.java 

  

package com.lomi.api.order;

import com.lomi.api.order.fallback.OrderServiceFallback;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;

import java.util.List;

/**
 * @author ZHANGYUKUN
 * @date 2022/6/24
 */
@FeignClient(value="order-server/orderServer")
public interface OrderService {

    @PostMapping("order/generate")
    String generate(@RequestBody List<String> goodsInfo);



}

  

  4 goods 使用 fengn 接口调用远程服务

  

    @Resource
    OrderService orderService;

    /**
     * 使用 feign 调用 远程服务
     *
     * @return
     */
    @PostMapping("generateOrderByFeign")
    public String generateOrderByFeign() {
        System.out.println("generateOrderByFeign" + ":" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        //查询购物车
        List<String> goodsNames = Arrays.asList("商品1", "商品2", "商品2");

        //调用order服务生成订单
        String rt = orderService.generate(goodsNames);
        System.out.println(rt);


        log.debug("调用完成。。。。。。。。。。。。。。。");

        //清空购物车
        return rt;
    }

  

  

 

4 hystrix 的集成和使用

  服务降级和熔断是保护分布式集群可用的重要方式,hystrix 是spring cloud 早期核心的组件,但是现在缺乏维护,一般信项目部建议使用了,可以考虑 spring cloud alibaba 的  sentinel 

 

  1 order 和 goods pom 文件添加依赖

  

        <!--hystrix-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>

 

 2 配置启动类上开启 hystrix

  

@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
@EnableHystrix
public class Application {

  ..............

 }

  

3 hystrix 可以使用 生产者或者消费者的 实现方法上(默认在这个方法抛出异常或者超时的时候返回,调用另外一个降级方法来返回一个默认值)

备注服务出现异常或者超时返回默认值, 这就是spring cloud 的服务降级,如果多次触发返回默认值,就会触发熔断机制,一定实践类直接返回默认值,不会调用实现方法,然后等这段时间窗口过期,获尝试放几个请求过来,如果请求成功者断路器关闭,否者当前时间窗口内继续熔断。熔断是服务降级的升级版。

 

  

    /**
     * 使用 feign 调用 远程服务
     *
     * @return
     */
    @PostMapping("generateOrderByFeign")
    @HystrixCommand(fallbackMethod = "generateOrderByFeignFB" )
    public String generateOrderByFeign() {
        System.out.println("generateOrderByFeign" + ":" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        //查询购物车
        List<String> goodsNames = Arrays.asList("商品1", "商品2", "商品2");

        //调用order服务生成订单
        String rt = orderService.generate(goodsNames);
        System.out.println(rt);


        log.debug("调用完成。。。。。。。。。。。。。。。");

        //清空购物车
        return rt;
    }

    public String generateOrderByFeignFB() {
        return "客户端FB";
    }

 

  4 也可以在 feign 接口上配置服务降级默认值

  

import java.util.List;

/**
 * @author ZHANGYUKUN
 * @date 2022/6/24
 */
@FeignClient(value="order-server",fallback = OrderServiceFallback.class)
public interface OrderService {

    @PostMapping("order/generate")
    String generate(@RequestBody List<String> goodsInfo);



}

  

/**
 * @author ZHANGYUKUN
 * @date 2022/6/27
 */
@Component
public class OrderServiceFallback implements OrderService {


    @Override
    public String generate(List<String> goodsInfo) {
        return "OrderServiceFallback.generate.fallback";
    }
}

 

5 由于hystrix  不怎么更新了,和springcloud 新版本有兼容问题,实测 超时时间和 feign 接口的服务降级默认方法 冲突,结果就是打开了 circuitbreaker=rtue(开启 feign 断路器) 以后,超时时间就无效了。

 

部分yml 文件配置

feign:
  httpclient:  #配置http连接池
    enabled: true
    max-connection: 200
    max-connections-per-route: 50
    connection-timeout: 2000
  client: #配置超时间
    config:
      default:
        connectTimeout: 4000
        readTimeOut: 4000
#  circuitbreaker: #设置了开了feign断路器以后,超时时间就不生效了不知道为什么(hystrix 已经停更了,版本比较老了,适配有问题)
#    enabled: true

  

 

 

 

5 服务网关 gateway 的集成和使用

  正常权限检查,请求过滤之类的东西分布式业务节点可以不处理,这些事情都可以交给 服务网关去做。并且 所有服务节点不会对外暴露,只有服务网关对外暴露,可以把 gateway 理解成一个 cloud 微服务专用的 服务器端代理兼路由器兼过滤器 ,之前是zuul效率比较低,现在的 gateway 使用netty 引入了非阻塞io,效率高了50%以上

 

 

  1 创建项目gateway

  

  2 引入pom.xml依赖文件

    

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

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

        <!--gson-->
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.6.2</version>
        </dependency>

  

 3 application.yml配置文件

## web ##
server:
  port: 7001

#spring
spring:
  application:
    name: gateway-server
  cloud:
    gateway:
      routes:
        - id: shoppingCart_route          #指定一个路由配置的拼命
          uri: lb://goods-server/goods       #路由地址,lb开头的是 负载均衡地址
          predicates:
            - Path=/goods/**            # goods 开头的的地址会被转发到 上面的url 对应的地址
        - id: order_route
          uri: lb://order-server/orderServer
          predicates:
            - Path=/orderServer/**
          filters:                  #为一个路由配置指定局部过滤器
            - name: OrderFilter          #这个局部过滤器使用实现类的名字死 OrderFilter+ GatewayFilterFactroy
              args:                 #下面是指定的局部过滤器的构造参数
                message: My Custom Message
                preLogger: true
                postLogger: true

      discovery:
        locator:
          enabled: true  #启用注册中心额定位器

#注册中心
eureka:
  instance:
    hostname: localhost
    prefer-ip-address: true
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://localhost:9001/eureka/

  

 4 启动类

@SpringBootApplication
@EnableEurekaClient
public class Application {

    public static void main(String[] args) throws IOException {
        SpringApplication.run(Application.class, args); 
        System.out.println("*****************************");
        System.out.println("********* gateway 启动................... ***********");
        System.out.println("*****************************"); 
        System.in.read(); 

    }
    
}

  

5 上面配置指定的局部过滤器(id=order_route的过滤器指定的这个过滤器),需要实现AbstractGatewayFilterFactory接口

/**
 *
 * filters:名字是这个类名的前缀 OrderFilter_GatewayFilterFactory
 *
 * @Author ZHANGYUKUN
 * @Date 2022/6/28
 **/
@Component
public class OrderFilterGatewayFilterFactory extends AbstractGatewayFilterFactory<OrderFilterGatewayFilterFactory.Config> {

    public OrderFilterGatewayFilterFactory() {
        super(Config.class);
    }

    @Override
    public GatewayFilter apply(Config config) {


        return new GatewayFilter() {
            @Override
            public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
                System.out.println( "orderServer的局部过滤器..............." );
                return chain.filter(exchange).then(Mono.fromRunnable(() -> {
                    if (config.isPostLogger()) {
                        System.out.println( "orderServer的局部过滤器 回链做点事..............." );
                    }
                }));
            }
        };


    }

    public static class Config {
        private String message;
        private boolean preLogger;
        private boolean postLogger;

        public Config() {
        }
        public Config(String message, boolean preLogger, boolean postLogger) {
            this.message = message;
            this.preLogger = preLogger;
            this.postLogger = postLogger;
        }

        public String getMessage() {
            return message;
        }

        public void setMessage(String message) {
            this.message = message;
        }

        public boolean isPreLogger() {
            return preLogger;
        }

        public void setPreLogger(boolean preLogger) {
            this.preLogger = preLogger;
        }

        public boolean isPostLogger() {
            return postLogger;
        }

        public void setPostLogger(boolean postLogger) {
            this.postLogger = postLogger;
        }
    }
}

  

 6 配置一个全局过滤器,对所有路由的请求都有效(需要实现GlobalFilter接口)

  

@Component
public class TokenFilter implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        System.out.println("----token 过滤器 ----");
        System.out.println("请求路径是:" +  exchange.getRequest().getPath()  );
        //有些路径不需要过滤,可以在这里处理


        //检查token
        String token  = exchange.getRequest().getHeaders().getFirst("token");

        //如果没有token直接返回失败
        if ( token == null || token.trim().length() == 0 ){
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            ServerHttpResponse response = exchange.getResponse();
            return  response.setComplete();
        }


        //检查token是否合法
        System.out.println("请求的token是:" + token );
        if( token.equals("123") ){
            exchange.getResponse().setStatusCode(HttpStatus.OK);
            ServerHttpResponse response = exchange.getResponse();

            byte[] bits = "全局过滤器直接返回的数据".getBytes(StandardCharsets.UTF_8);
            DataBuffer buffer = response.bufferFactory().wrap(bits);

            return response.writeWith(Mono.just(buffer));
        }


        return chain.filter(exchange).then(Mono.fromRunnable(
                () -> System.out.println("回链响应也可以做点事..")
        ));
    }


    @Override
    public int getOrder() {
        return 1;
    }
}

  

 

6 配置中心spring config 的集成和使用

  spring config 作为分布式配置中心,集中的管理分布式配置文件(类似 dubbo 框架使用 百度  disconf 最为配置管理中心 )

 

   注意关于启动配置类的配置需要配置到bootstrap.xml里面去

 

 1 创建 config 服务工程

  2 config 工程引入 poxm 文件

  

   <!--actuator-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>


        <!--config-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-server</artifactId>
        </dependency>
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.6.2</version>
        </dependency>

        <!--eurake cilent-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

  3 config工程 yml,下面 

cloud.config下面是 git 的地址 账号密码和分支
## web ##
server:
port: 6001


#spring
spring:
application:
name: config-server
cloud:
config:
server:
git:
uri: https://gitee.com/octupus/config.git #git地址
username: XXXX
password: XXX
default-label: master #git分支

#注册中心
eureka:
instance:
hostname: localhost
prefer-ip-address: true
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://localhost:9001/eureka/

  

  4 config 项目启动类

  

@SpringBootApplication
@EnableEurekaClient
@EnableConfigServer
public class Application {

    public static void main(String[] args) throws IOException {
        SpringApplication.run(Application.class, args); 
        System.out.println("*****************************");
        System.out.println("********* configServer 启动................... ***********");
        System.out.println("*****************************"); 
        System.in.read(); 

    }
    
}

  5 goods 引用  config 项目的配置 pom文件引入依赖

  

        <!--config-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-server</artifactId>
        </dependency>
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.6.2</version>
        </dependency>

  6 创建 bootstrap.yml 配置文件,和application.yml同位置,但是bootstrap.yml 加载顺序比 application.yml高,把关于 配置中心的配置都移动过去

  

spring:
  profiles:
    active: dev
  main:
    allow-circular-references: true
  application:
    name: goods-server
  cloud:
    config:
      discovery:
        enabled: true
        service-id: config-server
      #uri: http://localhost:6001/configServer   #配置中心地址
      label: master                  #git分支
      profile: dev                   #项目使用profiles
      name: goodsServer                #配置文件的名字

  

  7 goods 获取配置文件(@RefreshScope,标注的类,里面的配置文件可以在在指定情况下事实刷新)

  

/**
 * 获取配置文件
 */
@RestController
@RequestMapping("config")
@RefreshScope
public class ConfigController {

    //引用config的 远程文件地址
    @Value("${abc:000}")
    String version;

    /**
     * 获取配置文件
     * @return
     */
    @PostMapping("showConfig")
    public String showConfig() {

        System.out.println( "我取到的配置文件是:"  + version  );

        return version;
    }

}

  

  8 默认情况下,goods 只会在启动的时候获取一个 config 的配置,如果要需要手动刷新,可以开放 good是刷新暴露点,然后手动刷新(需要和@RefreshScope配合使用)

加在  application.yml或者 bootstrap.yml都可以 

#暴露刷新点
management:
  endpoints:
    web:
      exposure:
        include: "refresh"

 

调用刷新地址:post  http://服务地址/程序访问前缀/actuator/refresh(这样适合当个引用的刷新)

 

 

7 消息总线spring bus 的使用

  作用就一个,动态的推送分布式配置到各个分布式节点,而不是各个节点重启来拉取

  注意刷新点改名字了

  如果需要全局配置刷新需要配置 spring bus

  

  1 config 导入 bus 的依赖

        <!--bus-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bus-amqp</artifactId>
        </dependency>

 

  2 config 项目 application.yml 导入 mq 的配置

#spring
spring:
  application:
    name: config-server
  cloud:
    config:
      server:
        git:
          uri: https://gitee.com/octupus/config.git
          username: 451740146@qq.com
          password: a5464459480
          default-label: master
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: guest
    password: guest

 3 config项目  application.yml配置 暴露刷新点

#暴露刷新点
management:
  endpoints:
    web:
      exposure:
        include: 'bus-refresh'   #新版本改成 actuator/busrefresh post请求,别得用法都一样,刷新指定节点actuator/busrefresh/服务名字:端口

  

 4 goods 在 pom 中 导入消息中线依赖

        <!--bus 消息总线-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bus-amqp</artifactId>
        </dependency>

 5 goods 在 yml 文件中配置 mq 地址(此配置 和 cloud属性配置缩进的同级别 )

  

#spring
spring:
profiles:
active: dev
main:
allow-circular-references: true
application:
name: goods-server
cloud:
config:
discovery:
enabled: true
service-id: config-server
#uri: http://localhost:6001/configServer
label: master
profile: dev
name: goodsServer
rabbitmq:
host: 127.0.0.1
port: 5672
username: guest
password: guest

  

 6 调用config刷新点就能刷新指定项目的配置

   调用config刷新地址:post  http://服务地址/程序访问前缀/actuator/busrefresh (可以刷新所有使用config 项目配置的其它项目,原理就是通过 mq 发送一个 所有子项目订阅的广播消息 )

   调用config刷新地址:post  http://服务地址/程序访问前缀/actuator/busrefresh/服务名字:端口 可以指定刷新的子项目

 

8 spring stream 消息中间件的 代理

  spring cloud stream ,可把它当做是所有消息中间键的抽象接口(l类似 slf4j 和各种 日志实现的关系),使用它一定程度上不用在关心 mq的具体api(个人觉得没啥用,初级接口可以通用,特性接口依旧需要学习各种mq 的特性)

  

下面例子是 stream-rabbit 3.1 以后的用法,3.1 之前的用法的已经过期,不建议用了

  

  在 goods 中使用 cloud stream 接受 rabbit mq 的消息

  

  1 导入依赖

    

        <!--stream-rabbit-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-stream-rabbit</artifactId>
        </dependency>

  

  2 配置文件 修改

  

spring:
  profiles:
    active: dev
  main:
    allow-circular-references: true
  application:
    name: goods-server
  cloud:
    config:
      discovery:
        enabled: true
        service-id: config-server
      #uri: http://localhost:6001/configServer
      label: master
      profile: dev
      name: goodsServer
    stream:                #stream 和上面的  cloud 属性同级别
      function:
        definition: myChannel     #这里定义那些Spring 托管的bean 是消息监听者(这个非常重要,必不可少,不然spring不知道哪些bean 是消费监听者)
      binders:              # 一个 bingder 就是一个mq地址
        myRabbitMQ:           #这个名字自己取得,指定mq 的名字
          type: rabbit         #指定mq 的类型
          environment:       
            spring:           #下面是mq 的一下具体参数  
              rabbitmq:
                addresses: localhost:5672
                username: guest
                password: guest
                virtual-host: /

      bindings:
        myChannel-out-0:       #指定一个输出渠道的名字,格式:渠道名字-out-N
          destination: myExchange #目的地,在rabbitmq 中赌赢 交换机的名字
        myChannel-in-0:           #输出渠道的名字
          destination: myExchange #交换机的名字,需要额输入渠道交换机名字一样
          group: myTestQueue    #交换机绑定的队列名字
          binder: myRabbitMQ      #使用的那个binder(binders里面的指定的名字)
          consumer:          
            concurrency: 1     #并行数
      rabbit:
        bindings:
          myChannel-in-0:     #如果要指定一些特定参数,比如手动确认,这里给指定 输入渠道 配置参数
            consumer:
              acknowledgeMode: manual  #设置位手动确认

  3 消息生产者

streamBridge.send 的第一个参数是 输出渠道的全名字,第二个参数是消息,可以是直接是我们的消息对象,也可以是org.springframework.messaging.Message<T> 里面包裹着我们的消息对象

  

    @Autowired
    StreamBridge streamBridge;


    //发送消息
    public String sendMessage(Object message, Map<String, Object> properties) {

        streamBridge.send("myChannel-out-0", "这是消息体");
        System.out.println("发送消息成功");


        return null;
    }

  4 消息消费者 Consumer<Message<String>> , 这里的 Consumer<T> 里面的 T可以是我们消息对象的类型,也可以是org.springframework.messaging.Message<T> 里面装着我么你的消息

      备注:默认是自动确认,手动确认需要配置文件里面  acknowledgeMode: manual 配合

    /**
     * 消息接受者,并且手动确认  方法名字 myChannel1 是  myChannel1-in-0 的前缀,在cloud.stream.function 里面申明过。
     * @return
     */
    @Bean
    public Consumer<Message<String>> myChannel() {
        return  message -> {
            System.out.println("******************");
            System.out.println("At Sink1");
            System.out.println("******************");
            System.out.println("Received message " + message.getPayload());

            Channel channel = message.getHeaders().get(AmqpHeaders.CHANNEL, Channel.class);
            Long deliveryTag = message.getHeaders().get(AmqpHeaders.DELIVERY_TAG, Long.class);

            try {
                channel.basicAck(deliveryTag, false);
            } catch (IOException e) {
                e.printStackTrace();
            }

        };
    }

  

 

 

9 分布式链路追踪 zipink 集成和使用

  提供一个可视化的,可控采样率的分布式调用链路追踪工具,效率影响比较大,不建议生产使用,基本是用mq 代替http请求

  

 

  1 引入配置,所有需要链路最终的都需要

  

        <!--sleuth 和 zipkin client -->
        <!-- zipkin 默认使用的 http连接,可以改成 mq方式,存储zipkin 信息可以选择 es 或者 msql  -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-sleuth</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-sleuth-zipkin</artifactId>
        </dependency>

  

  2 修改配置yml

  

zipkin:
  base-url: http://127.0.0.1:9411   #zipkin的地址,如果真要使用建议 下载 zipkin 源码修改 配置中心地址,然后加入到集群环境在使用,而不是直接使用绝对地址
sleuth:
  sampler:
    probability: 1   #采样率0-1,默认使用http 的方式向 zipkin 记录数据,很影响效率,如果一定要用建议使用mq 的方式,采样率越高影响的效率越高

  

3 引入以后 默认会修改日志文件 ,把 traceId 和 spanId加入到日志中
  
  traceId:一次跨多次节点调用的唯一ID
  spanId:每个节点在本次分布式过程中的id

4 如果需要手动指定 上面两个分布式追踪ID的 位置

  手动打印格式为:[%X{traceId},%X{spanId}]

 

  5 所有调追踪记录都会出现在 zipkin 服务中。默认是不存盘的,需要存盘可以选择mysql  es 之类的存储工具

    浏览器访问:http://127.0.0.1:9411 可以查看最终信息。或者日志里面的最终ID 也可以参考

 

 

 

 

相关例子代理记录于:https://gitee.com/octupus/sc

posted on 2022-06-29 22:30  zhangyukun  阅读(261)  评论(0编辑  收藏  举报

导航