小知识点
- application.yml中,"—"分割多个配置断,有spring.profiles=xxx的,表示要spring.profiles.active=xxx才能激活,没有spring.profiles的代码段是默认生效的配置
- @SpringCloudApplication是@SpringBootApplication、@EnableDiscoveryClient、@EnableCircuitBreaker组成的
- yml配置:和value之间必须要有个空格,name后面必须要有“:”
注册中心Eureka
原理:
- 服务注册表,提供服务名称、IP、端口的映射关系,即查询API
- 服务管理,即服务管理API,注册服务和注销服务
- 服务检查,即服务提供者主动发送心跳,注册中心定时检查注册服务的组件,超时都主动剔除
实现:
- Eureka
- Consul
- Zookeeper
Eureka:
- Eureka节点间相互复制来实现注册表数据的同步
- 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
- 去除Eureka依赖,去除@EnableDiscoveryClient注解,继续保留@LoadBalanced注解
- 加上配置
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配置属性
隔离策略
- 线程隔离(默认):HystrixCommand将在单独的线程上执行,并受到线程池终的线程数量限制,适用于并发不是非常大,网络IO的情况
- 信号量隔离: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高可用
- 注册到Eureka(仅供内部使用时可行)
- 使用负载均衡代理,比如nginx、haproxy、f5,这是这种场景更多,由于供外部调用无法使用eureka
- 使用sidecar整合非jvm服务
Sleuth链路跟踪
Sleuth术语
- 一条请求链路中包含一个TraceID,多个SpanID,由16位16进制组成
- span export:表示是否将此条记录发送到zipkin等用来收集和展示
- annotation(标注):用来标注请求的开始和结束
- CS:客户端发送请求,span的开始
- SR:服务端收到请求
- SS:服务端发送请求
- 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/