Spring Cloud深入浅出

小知识点

  1. application.yml中,"—"分割多个配置断,有spring.profiles=xxx的,表示要spring.profiles.active=xxx才能激活,没有spring.profiles的代码段是默认生效的配置
  2. @SpringCloudApplication是@SpringBootApplication、@EnableDiscoveryClient、@EnableCircuitBreaker组成的
  3. yml配置:和value之间必须要有个空格,name后面必须要有“:”

注册中心Eureka

原理:
  • 服务注册表,提供服务名称、IP、端口的映射关系,即查询API
  • 服务管理,即服务管理API,注册服务和注销服务
  • 服务检查,即服务提供者主动发送心跳,注册中心定时检查注册服务的组件,超时都主动剔除
    在这里插入图片描述
实现:
  1. Eureka
  2. Consul
  3. Zookeeper
Eureka:
  1. Eureka节点间相互复制来实现注册表数据的同步
  2. Eureka客户端会缓存注册表数据,即可以减少和注册中心的请求流量,又可以在注册中心宕机之后也能运行
Eureka服务搭建:
1、依赖
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>
2、添加注解
@EnableEurekaServer
3、application.properties配置
server.port=1111
eureka.instance.hostname=localhost
eureka.client.register-with-eureka=false  //是否将自己注册到eureka,由于自己已经是eureka负责,所以false
eureka.client.fetch-registry=false //是否从eureka获取注册信息,由于是单点服务,所以为false
//eureka.client.serviceUrl.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka/ //注册到其他Eureka的地址
Eureka客户端搭建
1、依赖
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
2、添加注解
@EnableDiscoveryClient //服务注册的通用注解,可以注册Eureka、Consul、Zookeeper,spring-cloud-commons提供
@EnableEurekaClient //当确定使用Eureka时,可以替代@EnableDiscoveryClient,spring-cloud-netflix提供
3、application.properties配置
spring.application.name=hello-service  //注册到eureka的服务名
eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/
eureka.instance.prefer-ip-addresss=true //是否将自己的ip注册到eureka,默认未false表示注册hostname
Eureka高可用
1、注册中心配置application.properties
eureka.client.serviceUrl.defaultZone=http://ip:port/eureka/  //把自己注册到其他Eureka
2、注册客户端配置
eureka.client.serviceUrl.defaultZone=http://ip1:port/eureka/,http://ip2:port/eureka/
Eureka添加用户认证
1、服务端依赖
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-security</artifactId>
</dependency>
2、服务端配置
security.basic.enabled=true
security.user.name=tuyou
security.user.password=123456
3、客户端配置
eureka.client.serviceUrl.defaultZone=http://tuyou:123456@localhost:1111/eureka/
Eureka元数据
配置

eureka.instance.metadata-map.mykey=myvalue //自定义元数据

客户端获取配置
@Autowired
private DiscoveryClient client;

client.getInstances()
Eureka的Rest接口

eureka提供了一些接口,方便其他非jvm的服务接入进来,并且eureka的client也是调用的这些接口
在这里插入图片描述

Eureka其他配置
//禁用自我保护模式,当eureka认为出现网络分区的时候,会启动自我保护,也就是不会删除已注册的微服务
eureka.server.enable-self-preservation=false 

//客户端启用健康检查,如果想实现更细腻度的健康检查,可以实现检查接口
eureka.client.healthcheck=true

客户端负载均衡Ribbon

在这里插入图片描述

集成Ribbon
1、maven依赖
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-ribbon</artifactId>
</dependency>
2、代码修改
@EnableDiscoveryClient   //必须加上
@SpringBootApplication
public class RibbonApplication {

    @Bean
    @LoadBalanced //ribbon负载均衡
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

    public static void main(String[] args) {

        SpringApplication.run(RibbonApplication.class, args);
    }
}
3、获取ribbon路由信息
@Autowired
LoadBalancerClient loadBalancerClient;

@RequestMapping(value = "/ribbon-consumer", method = RequestMethod.GET)
public String helloConsumer() {
    ServiceInstance instance = loadBalancerClient.choose("HELLO-SERVICE");
    logger.info("serviceId:{},host:{},port:{}", instance.getServiceId(), instance.getHost(), instance.getPort());
    return restTemplate.getForEntity("http://HELLO-SERVICE/hello", String.class).getBody();
}
自定义Ribbon配置

ribbon的默认配置类是RibbonClientConfiguration
在这里插入图片描述

Ribbon脱离Eureka使用

前提:SpringCloud Netflix版本要高于1.2.0

  1. 去除Eureka依赖,去除@EnableDiscoveryClient注解,继续保留@LoadBalanced注解
  2. 加上配置
HELLO-SERVICE.ribbon.listOfServers=localhost:8081,localhost:8082

Feign声明式调用

引入Feign
1、添加依赖
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
2、创建一个接口,并添加@FeignClient注解
//name对应要调用服务的spring.application.name
//如果FeignClient不想使用Eureka,可以直接指定ribbon.listOfServers
//或者指定url,@FeignClient(name="服务名称", url="http://localhost:8080/")
@FeignClient(name = "hello-service")
public interface HelloService {
    @RequestMapping(value = "/hello", method = RequestMethod.GET)
    String index();
}
3、启动FeignClient
@EnableFeignClients
//如果要使用Eureka,则要启用注册发现客户端
@EnableDiscoveryClient
自定义配置
  • feign的默认配置是FeignClientsConfiguration
  • 可以用@FeignClient自定义feign配置
演示使用feign默认契约
@Configuration
public class FeignConfiguration {

    @Bean
    public Contract modifyDefaultFeignContract(){
        return new feign.Contract.Default();
    }
}


@FeignClient(name = "hello-service", configuration = FeignConfiguration.class)
public interface DefaultFeignService {

    @RequestLine("GET /hello")
    String index();
}
手动创建Feign
@Import(FeignClientsConfiguration.class)  //必须加上
@RestController
@RequestMapping("/custom")
public class CustomFeignController {

    HelloService helloService;

    @Autowired
    public CustomFeignController(Client client, Decoder decoder, Encoder encoder, Contract contract) {
        helloService = Feign.builder().client(client)
                .decoder(decoder)
                .encoder(encoder)
                .contract(contract)
                .target(HelloService.class, "http://hello-service/");
    }

    @RequestMapping(value = "/feign-consumer", method = RequestMethod.GET)
    public String helloConsumer() throws InterruptedException {
        return helloService.index();
    }
}
Feign支持继承

这样就可以服务端把controller接口封装到interface里面,然后controller和FeignClient都继承这个interface,达到服务端和客户端共享接口的目的

共享的接口
public interface CommonInterface {
    @RequestMapping("/hello")
    String hello(@RequestParam String name, @RequestParam Integer age);
    //@RequestParam在controller可以不写,但是在feign中要写
}
controller实现
@RestController
public class HelloController implements CommonInterface {
    @Autowired
    private DiscoveryClient client;

    public String hello(String name, Integer age) {
        ServiceInstance instance = client.getLocalServiceInstance();
        return "/hello, service_id:" + instance.getServiceId() + ", host:" + instance.getHost() + ", port:" + instance.getPort();
    }
}
feign客户端实现
@FeignClient(name = "hello-service") //name对应要调用服务的spring.application.name
public interface CommonService extends CommonInterface {
    
}
Feign支持压缩
feign.compression.request.enable=true
feign.compression.response.enable=true
feign.compression.request.mime-types=text/html,application/xml,application/json
feign.compression.request.min-request-size=1
Feign修改日志打印等级
@Bean
public Logger.Level loggerLevel() {
    return Logger.Level.FULL;
}

在这里插入图片描述
由于feign打印的日志是debug等级,修改日志打印等级为debug

<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
  <encoder>
     <pattern>%-4relative [%thread] %-5level %logger{35} - %msg %n</pattern>
  </encoder>
</appender>

<root level="DEBUG">
  <appender-ref ref="STDOUT" />
</root>

Hystrix熔断、限流

  • 当一段时间内,请求失败率达到一定阈值,熔断器将打开
  • 打开后的熔断是半开状态,也就是依旧允许一个请求访问服务,达到检测服务是否恢复的目的,如果已恢复就自动关闭熔断
Hystirx配置属性

在这里插入图片描述

隔离策略
  1. 线程隔离(默认):HystrixCommand将在单独的线程上执行,并受到线程池终的线程数量限制,适用于并发不是非常大,网络IO的情况
  2. 信号量隔离:HystrixCommand将在调用线程上执行,开销较小,并发量受信号量个数限制
feign和hystrix

feign默认已经集成了hystrix,只要发现hystrix在classpath中,就会使用断路器包裹feign客户端方法,只需在@FeignClient上指定fallback属性即可指定回退方法

@FeignClient(value = "HELLO-SERVICE", fallback = HelloFeignFallbackService.class)
public interface HelloFeignService {

    @RequestMapping(value = "/hello", method = RequestMethod.GET)
    String hello();

    @RequestMapping(value = "/param", method = RequestMethod.GET)
    String param(@RequestParam("name") String name, @RequestParam("age") Integer age) throws InterruptedException;
}


@Component
public class HelloFeignFallbackService implements HelloFeignService {
    @Override
    public String hello() {
        return "hello默认回退返回";
    }

    @Override
    public String param(String name, Integer age) throws InterruptedException {
        return "param默认回退返回";
    }
}
feign.hystrix.enabled=false //禁用hystrix

Zuul服务网关

接入zuul
1、依赖
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-zuul</artifactId>
</dependency>
2、添加注解
@EnableZuulProxy
@SpringCloudApplication
public class ZuulApplication {

    public static void main(String[] args) {

        new SpringApplicationBuilder(ZuulApplication.class).web(true).run(args);
    }

//    @Bean
//    public TestFilter createFilter() {
//        return new TestFilter();
//    }
}
3、配置
spring.application.name=api-gateway
server.port=5555

zuul.routes.hello.path=/hello/**
zuul.routes.hello.url=http://localhost:8080/hello/

zuul.routes.feign.path=/feign-consumer
zuul.routes.feign.service-id=feign-consumer

zuul.routes.ribbon.path=/ribbon-consumer
zuul.routes.ribbon.service-id=ribbon-consumer


eureka.client.serviceUrl.defaultZone=http://localhost:1110/eureka/
详细配置
/routes 端点返回配置的路由信息
zuul.ignored-services=service1,service2
zuul.ignored-services='*' //禁用所有服务
zuul.ignored-patterns=**/admin/** //全局忽略代理指定的路径
zuul.prefix=/api
zuul.strip-prefix=true //全局指定是否切除前缀

zuul.routes.hello.path=/hello/**
zuul.routes.hello.url=http://localhost:8080/hello/
zuul.routes.hello.strip-prefix=false //局部指定是否切除前缀


logging.level.com.netflix=debug //修改zuul的日志等级,方便调试

//header配置
//敏感header,即只会在服务内部传输,不会外泄
zuul.sensitive-headers=Cookie,Set-Cookie,Authorization //全局设置敏感header,这三个也是默认值
zuul.service-id.sensitive-headers=header1,header2 //局部设置
//忽略header
zuul.ignored-headers=header1,header2 //默认为空,如果有security则为Cache-Control,Expires
zuul.ignored-secutiry-headers=false //关闭security禁用的header
zuul大文件上传

使用/zuul前缀上传大文件,并增加超时时间

上传接收服务配置
spring:
  application:
    name: hello-service
  http:
    multipart:
      max-file-size: 2000mb //默认1mb
      max-request-size: 2000mb //默认10mb
zuul配置
hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 6000
ribbon:
  ConnectionTime: 3000
  ReadTimeout: 60000
Zuul过滤器
类型说明
pre这种过滤器在请求被路由之前调用
routing这种过滤器将请求路由到微服务
post这种过滤器在请求路由到微服务之后执行
error在其他阶段发生错误执行过滤器
继承ZuulFilter
public class TestFilter extends ZuulFilter {

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

    @Override
    public Object run() {
        RequestContext requestContext = RequestContext.getCurrentContext();
//        Map<String, List<String>> requestQueryParams = requestContext.getRequestQueryParams();
//        System.out.println(requestQueryParams);

        HttpServletRequest request = requestContext.getRequest();

//        System.out.println(request.getQueryString());
        Map<String, String[]> parameterMap = request.getParameterMap();
        System.out.println(parameterMap);
//        try {
//            ServletInputStream inputStream = request.getInputStream();
//            BufferedReader bufferedInputStream = new BufferedReader(new InputStreamReader(inputStream));
//            String line = null;
//            System.out.println("参数:");
//            while ((line = bufferedInputStream.readLine()) != null) {
//                System.out.println(line);
//            }
//        } catch (IOException e) {
//            e.printStackTrace();
//        }


        return null;
    }

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

    @Override
    public int filterOrder() {
        return 0;
    }
}
禁用过滤器

默认的过滤器:spring-cloud-netflix-core.jar包的org.springframework.cloud.netflix.zuul.filters包下面

zuul.<filterName>.<filterType>.disable=true
Zuul回退
@Component
public class HelloFallback implements ZuulFallbackProvider {
    @Override
    public String getRoute() {
        return "hello-service";
    }

    @Override
    public ClientHttpResponse fallbackResponse() {
        return new ClientHttpResponse() {
            @Override
            public HttpStatus getStatusCode() throws IOException {
                return HttpStatus.OK;
            }

            @Override
            public int getRawStatusCode() throws IOException {
                return this.getStatusCode().value();
            }

            @Override
            public String getStatusText() throws IOException {
                return this.getStatusCode().getReasonPhrase();
            }

            @Override
            public void close() {

            }

            @Override
            public InputStream getBody() throws IOException {
                return new ByteArrayInputStream("微服务不可能用".getBytes());
            }

            @Override
            public HttpHeaders getHeaders() {
                HttpHeaders headers = new HttpHeaders();
                MediaType mediaType = new MediaType("application", "json", Charset.forName("utf-8"));
                headers.setContentType(mediaType);
                return headers;
            }
        };
    }
}
Zuul高可用
  1. 注册到Eureka(仅供内部使用时可行)
  2. 使用负载均衡代理,比如nginx、haproxy、f5,这是这种场景更多,由于供外部调用无法使用eureka
  3. 使用sidecar整合非jvm服务

Sleuth链路跟踪

Sleuth术语
  • 一条请求链路中包含一个TraceID,多个SpanID,由16位16进制组成
  • span export:表示是否将此条记录发送到zipkin等用来收集和展示
  • annotation(标注):用来标注请求的开始和结束
    1. CS:客户端发送请求,span的开始
    2. SR:服务端收到请求
    3. SS:服务端发送请求
    4. CR:客户端收到请求,span的结束
Sleuth接入
1、maven依赖
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
2、修改日志打印级别
logging:
  level:
    root: INFO
    org.springframework.web.servlet.DispatchServlet: DEBUG
    org.springframework.cloud.sleuth: DEBUG
Sleuth整合ELK
1、maven依赖
<dependency>
    <groupId>net.logstash.logback</groupId>
    <artifactId>logstash-logback-encoder</artifactId>
    <version>4.6</version>
</dependency>
2、logback日志打印配置
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <include resource="org/springframework/boot/logging/logback/defaults.xml" />

    <springProperty scope="context" name="springAppName" source="spring.application.name" />
    <!-- Example for logging into the build folder of your project -->
    <property name="LOG_FILE" value="/Users/mac/Downloads/logstash.log" />

    <property name="CONSOLE_LOG_PATTERN"
              value="%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr([${springAppName:-},%X{X-B3-TraceId:-},%X{X-B3-SpanId:-},%X{X-B3-ParentSpanId:-},%X{X-Span-Export:-}]){yellow} %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}" />
    <!-- Appender to log to console -->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <!-- Minimum logging level to be presented in the console logs -->
            <level>DEBUG</level>
        </filter>
        <encoder>
            <pattern>${CONSOLE_LOG_PATTERN}</pattern>
            <charset>utf8</charset>
        </encoder>
    </appender>
    <!-- Appender to log to file -->
    <appender name="flatfile" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_FILE}</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_FILE}.%d{yyyy-MM-dd}.gz</fileNamePattern>
            <maxHistory>7</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>${CONSOLE_LOG_PATTERN}</pattern>
            <charset>utf8</charset>
        </encoder>
    </appender>

    <!-- Appender to log to file in a JSON format -->
    <appender name="logstash" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_FILE}.json</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_FILE}.json.%d{yyyy-MM-dd}.gz</fileNamePattern>
            <maxHistory>7</maxHistory>
        </rollingPolicy>
        <encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
            <providers>
                <timestamp>
                    <timeZone>UTC</timeZone>
                </timestamp>
                <pattern>
                    <pattern>
                        {
                        "severity": "%level",
                        "service": "${springAppName:-}",
                        "trace": "%X{X-B3-TraceId:-}",
                        "span": "%X{X-B3-SpanId:-}",
                        "parent": "%X{X-B3-ParentSpanId:-}",
                        "exportable": "%X{X-Span-Export:-}",
                        "pid": "${PID:-}",
                        "thread": "%thread",
                        "class": "%logger{40}",
                        "rest": "%message"
                        }
                    </pattern>
                </pattern>
            </providers>
        </encoder>
    </appender>

    <root level="INFO">
        <appender-ref ref="console" />
        <appender-ref ref="logstash" />
        <!--<appender-ref ref="flatfile"/> -->
    </root>
</configuration>
3、启动logstash进程
logstash -f logstash.conf

logstash.conf配置

input {
  file {
    codec => json
    path => "/Users/mac/Downloads/*.json"
  }
}
filter {
  grok {
    match => { "message" => "%{TIMESTAMP_ISO8601:timestamp}\s+%{LOGLEVEL:severity}\s+\[%{DATA:service},%{DATA:trace},%{DATA:span},%{DATA:exportable}\]\s+%{DATA:pid}---\s+\[%{DATA:thread}\]\s+%{DATA:class}\s+:\s+%{GREEDYDATA:rest}" }
  }
}
output {
  elasticsearch {
    hosts => "localhost:9200"
  }
}
Sleuth和Zipkin整合
搭建zipkinServer
1、maven依赖
<dependency>
    <groupId>io.zipkin.java</groupId>
    <artifactId>zipkin-autoconfigure-ui</artifactId>
</dependency>
<dependency>
    <groupId>io.zipkin.java</groupId>
    <artifactId>zipkin-server</artifactId>
</dependency>
2、添加注解
@SpringBootApplication
@EnableZipkinServer
public class ZipkinApplication {
    public static void main(String[] args) {
        SpringApplication.run(ZipkinApplication.class, args);
    }
}

3、配置

server:
  port: 7777
微服务接入zipkin
1、maven依赖
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-sleuth-zipkin</artifactId>
</dependency>
2、配置
spring:
  zipkin:
    base-url: http://localhost:7777/
sleuth:
  sampler:
    percentage: 1.0
访问zipkin页面

http://localhost:7777/

zipkin使用mq搜索数据
zipkin-server数据持久化
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值