模块说明
一个媒资模块,用于查询所有的媒资信息;
一个订单模块,调用媒资模块中的接口,查询媒资信息;
基础工程搭建
- 创建父工程,pom.xml
<!-- ⽗⼯程打包⽅式为pom -->
<packaging>pom</packaging>
<!-- spring boot ⽗启动器依赖 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
</parent>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- web依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- ⽇志依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
<!-- 测试依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- lombok⼯具 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.4</version>
</dependency>
<!-- Actuator可以帮助你监控和管理Spring Boot应⽤ -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- 热部署 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<!-- 引⼊Jaxb,开始 -->
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-core</artifactId>
<version>2.2.11</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>2.2.11</version>
</dependency>
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>2.2.10-b140310.1920</version>
</dependency>
<dependency>
<groupId>javax.activation</groupId>
<artifactId>activation</artifactId>
<version>1.1.1</version>
</dependency>
<!-- 引⼊Jaxb,结束 -->
<!-- 引入spring cloud common模块 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-commons</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<!-- 编译插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>11</source>
<target>11</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<!-- 打包插件 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
- 创建New Module,service_common,pom.xml
<dependencies>
<!--Spring Data Jpa-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
- 建立实体类
package com.lx.pojo;
import lombok.Data;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import java.io.Serializable;
@Data
@Entity
@Table(name = "media")
public class Media implements Serializable {
private static final long serialVersionUID = 4355137647179605524L;
@Id
private Integer id;
private String name;
}
- 创建New Module,service_media,pom.xml
<dependencies>
<dependency>
<groupId>com.lx</groupId>
<artifactId>service_common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
- 创建dao层
package com.lx.dao;
import com.lx.pojo.Media;
import org.springframework.data.jpa.repository.JpaRepository;
public interface IMediaDao extends JpaRepository<Media, Integer> {
}
- 创建service层和实现类
package com.lx.service;
import com.lx.pojo.Media;
public interface IMediaService {
Media findOneById(Integer id);
}
package com.lx.service.impl;
import com.lx.dao.IMediaDao;
import com.lx.pojo.Media;
import com.lx.service.IMediaService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class MediaServiceImpl implements IMediaService {
@Autowired
private IMediaDao mediaDao;
@Override
public Media findOneById(Integer id) {
return mediaDao.findById(id).get();
}
}
- 创建controller层,port参数用于后面测试访问的哪个端口服务
package com.lx.controller;
import com.lx.service.IMediaService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/media")
public class MediaController {
@Value("${server.port}")
private Integer port;
@Autowired
private IMediaService mediaService;
@GetMapping("/findMeidaById/{id}")
public String findMeidaById(@PathVariable Integer id) {
return mediaService.findOneById(id).getName() + " ---->>>> " + port;
}
}
- 创建application.yml
server:
port: 8080
servlet:
context-path: /
spring:
application:
name: service-media
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/sc_test?useUnicode=true&characterEncoding=utf8
username: root
password: 123456
jpa:
database: MySQL
show-sql: true
hibernate:
naming:
# 避免将驼峰命名转换为下划线命名
physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
- 创建启动类
package com.lx;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@EntityScan("com.lx.pojo")
public class MediaApplication {
public static void main(String[] args) {
SpringApplication.run(MediaApplication.class, args);
}
}
-
启动服务,测试
http://localhost:8080/media/findMeidaById/1
-
创建New Module,service_order,controller层
package com.lx.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/getMediaNameById/{id}")
public String getMediaNameById(@PathVariable Integer id) {
String url = "http://localhost:8080/media/findMeidaById/" + id;
String name = restTemplate.getForObject(url, String.class);
return name;
}
}
- 创建启动类
package com.lx;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
@Bean
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}
- 创建application.yml
server:
port: 8090
servlet:
context-path: /
spring:
application:
name: service-order
-
启动服务,测试
http://localhost:8090/order/getMediaNameById/1
至此,基础工程搭建完毕,下面用SpringCloud组件改造工程。
工程改造
Eureka注册中心
- 创建New Module,eureke_server,pom.xml
<dependencies>
<!--Eureka server依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
-
创建application.yml
eureka.server.use-read-only-response-cache和eureka.server.response-cache-update-interval-ms:针对eureka发现慢的设置
eureka.client.service-url.defaultZone:在集群模式下应该指向其他eureka server,如果有多个,逗号拼接
eureka.client.register-with-eureka和eureka.client.fetch-registry:在集群模式下可以改成true
server:
port: 8761
spring:
application:
name: eureka-server
eureka:
server:
use-read-only-response-cache: false
response-cache-update-interval-ms: 0
instance:
hostname: eurekaServer
client:
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
register-with-eureka: false
fetch-registry: false
- 创建启动类
package com.lx;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
-
启动服务,测试
http://localhost:8761/
-
可以把eureka_server复制一份,改下端口:8762,搭建高可用集群
媒资微服务注册到Eureka Server
- pom.xml
<!-- eureka client 客户端依赖引入 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
-
application.yml
eureka.client.registryFetchIntervalSeconds:拉取Eureka Server实例信息到本地,默认30秒,解决快速发现新服务
eureka.instance.metadata-map:自定义Eureka元数据
eureka:
client:
registryFetchIntervalSeconds: 3
service-url:
defaultZone: http://eurekaServer:8761/eureka
instance:
prefer-ip-address: true
instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}:@project.version@
metadata-map:
cluster: cl1
region: rn1
-
启动类
增加@EnableDiscoveryClient,可以使用@EnableEurekaClient,功能一样,从通用性考虑,使用@EnableDiscoveryClient,从SpringCloud的Edgware版本开始,不加注解也可以,但是建议加上
@EnableDiscoveryClient
- 重启server_media,查看eureka server,可以看到server_media已经注册到eureka server
- 复制一份server_media,修改端口:8081,搭建集群
订单微服务注册到Eureka Server
- pom.xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
- application.yml
eureka:
client:
registryFetchIntervalSeconds: 3
service-url:
defaultZone: http://eurekaServer:8761/eureka
instance:
prefer-ip-address: true
instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}:@project.version@
- 启动类
@EnableDiscoveryClient
- controller改造
package com.lx.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.List;
import java.util.Map;
@Slf4j
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private RestTemplate restTemplate;
@Autowired
private DiscoveryClient discoveryClient;
@GetMapping("/getMediaNameById/{id}")
public String getMediaNameById(@PathVariable Integer id) {
List<ServiceInstance> instances = discoveryClient.getInstances("service_media");
ServiceInstance serviceInstance = instances.get(0);
String host = serviceInstance.getHost();
int port = serviceInstance.getPort();
// 自定义元数据获取
for(Map.Entry<String, String> entry : serviceInstance.getMetadata().entrySet()) {
log.info(entry.getKey() + " -->> " + entry.getValue());
}
String url = "http://" + host + ":" + port + "/media/findMeidaById/" + id;
String name = restTemplate.getForObject(url, String.class);
return name;
}
}
- 重启server_order,查看eureka server,可以看到server_order已经注册到eureka server
- 复制一份server_order,修改端口:8091,搭建集群
Feign远程调用
Feign = RestTemplate + Ribbon + Hystrix,所以整合直接用Feign,以后有机会在单独整合Bibbon、Hystrix
订单微服务引入Feign依赖
- pom.xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
- 启动类
package com.lx;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}
-
创建Feign接口和failback实现类
@PathVariable(“id”) 的value必须 设置,否则会抛异常
@FeignClient中value的值不能用下划线,坑
package com.lx.service;
import com.lx.service.impl.MediaFailback;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@FeignClient(value = "service-media", fallback = MediaFailback.class, path = "/media")
public interface IMediaServiceFeignClient {
@GetMapping("/findMeidaById/{id}")
public String findMeidaById(@PathVariable("id") Integer id);
}
package com.lx.service.impl;
import com.lx.service.IMediaServiceFeignClient;
import org.springframework.stereotype.Component;
@Component
public class MediaFailback implements IMediaServiceFeignClient {
@Override
public String findMeidaById(Integer id) {
return "==============>>>>>>>>>进入failback " + id;
}
}
- controller改造
package com.lx.controller;
import com.lx.service.IMediaServiceFeignClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private IMediaServiceFeignClient mediaServiceFeignClient;
@GetMapping("/getMediaNameById/{id}")
public String getMediaNameById(@PathVariable Integer id) {
String name = mediaServiceFeignClient.findMeidaById(id);
return name;
}
}
Feign对Bibbon的支持
-
application.yml
增加service_media,针对被调用方微服务生效,不加就是全局生效
service_media.ribbon.ReadTimeout:Feign的超时时间
负载均衡策略 RoundRobinRule:轮询策略 RandomRule:随机策略 RetryRule:重试策略 BestAvailableRule:最⼩连接数策略 AvailabilityFilteringRule:可⽤过滤策略 ZoneAvoidanceRule:区域权衡策略(默认策略)
service_media:
ribbon:
ConnectTimeout: 2000
ReadTimeout: 3000
OkToRetryOnAllOperations: true
MaxAutoRetries: 0
MaxAutoRetriesNextServer: 0
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule
Feign对Hystrix的支持
-
application.yml
timeoutInMilliseconds:Hystrix的超时时间
如果设置了多个超时时间,熔断的时候是根据两个时间的最小值来进行的,进入回退降级逻辑
feign:
hystrix:
enabled: true
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 5000
Feign对请求压缩和响应压缩的支持
- application.yml
feign:
compression:
request:
enabled: true
mime-types: text/html,application/xml,application/json
min-request-size: 2048
response:
enabled: true
Feign的日志级别设置
- application.yml
logging:
level:
com.lx.service.IResumeServiceFeignClient: debug
-
增加配置类
Feign的⽇志级别(Feign请求过程信息)
NONE:默认的,不显示任何⽇志----性能最好
BASIC:仅记录请求⽅法、URL、响应状态码以及执⾏时间----⽣产问题追踪
HEADERS:在BASIC级别的基础上,记录请求和响应的header
FULL:记录请求和响应的header、body和元数据----适⽤于开发及测试环境定位问题
package com.lx.config;
import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FeignConfig {
@Bean
Logger.Level feignLevel() {
return Logger.Level.FULL;
}
}
- 启动服务,测试
GateWay网关组件
GateWay是Spring Cloud的一个全新项目,是取代Netflix Zuul的,是基于Spring5.0 + SpringBoot2.0 + WebFlux(异步非阻塞模型)等技术开发,性能高于Zuul,官方测试,GateWAy是Zuul的1.6倍。
-
创建New Module,gateway_server,pom.xml
GateWay不需要Web模块,它引入WebFlux(类似SpringMVC),所以建立New Module的时候,parent选择none
<!--spring boot 父启动器依赖-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-commons</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--GateWay 网关-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--引入webflux-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<!--日志依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
<!--测试依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--lombok工具-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.4</version>
<scope>provided</scope>
</dependency>
<!--引入Jaxb,开始-->
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-core</artifactId>
<version>2.2.11</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>2.2.11</version>
</dependency>
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>2.2.10-b140310.1920</version>
</dependency>
<dependency>
<groupId>javax.activation</groupId>
<artifactId>activation</artifactId>
<version>1.1.1</version>
</dependency>
<!--引入Jaxb,结束-->
<!-- Actuator可以帮助你监控和管理Spring Boot应用-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--热部署-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<!--链路追踪-->
<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>
</dependencies>
<dependencyManagement>
<!--spring cloud依赖版本管理-->
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<!--编译插件-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>11</source>
<target>11</target>
<encoding>utf-8</encoding>
</configuration>
</plugin>
<!--打包插件-->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
-
application.yml
动态路由设置,uri以lb://开头(lb代表从注册中心获取服务),后面是需要转发到的服务名称
GateWay内置了很多predicates功能,实现了各种路由匹配规则
时间点后匹配 - After=2017-01-20T17:42:47.789-07:00[America/Denver] 时间点前匹配 - Before=2017-01-20T17:42:47.789-07:00[America/Denver] 时间区间匹配 - Between=2017-01-20T17:42:47.789-07:00[America/Denver], 2017-01-21T17:42:47.789-07:00[America/Denver] 指定Cookie正则匹配指定值 - Cookie=chocolate, ch.p 指定Header正则匹配指定值 - Header=X-Request-Id, \d+ 请求Host匹配指定值 - Host=.somehost.org,.anotherhost.org 请求Method匹配指定请求⽅式 - Method=GET,POST 请求路径正则匹配 - Path=/red/{segment},/blue/{segment} 请求包含某参数 - Query=green 请求包含某参数并且参数值匹配正则表达式 - Query=red, gree. 远程地址匹配 - RemoteAddr=192.168.1.1/24 StripPrefix:可以去掉meida之后转发
server:
port: 9002
eureka:
client:
serviceUrl:
defaultZone: http://eurekaServer:8761/eureka/
instance:
prefer-ip-address: true
instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}:@project.version@
spring:
application:
name: gateway-server
cloud:
gateway:
routes:
- id: service-order-router
uri: lb:/service-order
predicates:
- Path=/order/**
- id: service-media-router
uri: lb://service-media
predicates:
- Path=/media/**
filters:
- StripPrefix=1
- 启动类
package com.lx;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class GateWayApplication {
public static void main(String[] args) {
SpringApplication.run(GateWayApplication.class, args);
}
}
-
启动服务,测试
订单测试:http://localhost:9002/order/getMediaNameById/1
媒资测试:http://localhost:9002/media/media/findMeidaById/1
-
自定义全局过滤器实现IP访问限制
过滤器类型 影响范围 GateWayFilter 应⽤到单个路由路由上 GlobalFilter 应⽤到所有的路由上
package com.lx.filter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.ArrayList;
import java.util.List;
@Component
public class BlackListFilter implements GlobalFilter, Ordered {
// 模拟黑名单列表,正常去数据库或缓存中查询
private static List<String> blackList = new ArrayList<>();
static {
blackList.add("0:0:0:0:0:0:0:1"); // 模拟本机地址
}
/**
* 过滤器核心方法
* @param exchange 封装了request和response对象的上下文
* @param chain 网关过滤器链(包含全局过滤器和单路过滤器)
* @return
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 思路: 获取客户端ip,判断是否在黑名单中,在的话就拒绝访问,不在的话就放行
// 从上下文中取出request和response对象
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
// 从request对象中获取客户端ip
String clientIp = request.getRemoteAddress().getHostString();
// 拿着clientIp去黑名单中查询,存在的话就拒绝访问
if(blackList.contains(clientIp)) {
// 拒绝访问,返回
response.setStatusCode(HttpStatus.UNAUTHORIZED); // 状态码
System.out.println("===============>>>>>IP : " + clientIp + " 在黑名单中,将被拒绝访问!");
String data = "Request be denied!";
DataBuffer wrap = response.bufferFactory().wrap(data.getBytes());
return response.writeWith(Mono.just(wrap));
}
// 合法请求, 放行,执行后续的过滤器
return chain.filter(exchange);
}
/**
* 返回值表示当前过滤器的顺序(优先级),数值越小,优先级越高
* @return
*/
@Override
public int getOrder() {
return 0;
}
}
-
GateWay高可用
复制一份gateway-server,修改端口:9003,在GateWay上游使用Nginx进行负载转发。
# 配置多个GateWay实例
upstream gateway {
server 127.0.0.1:9002;
server 127.0.0.1:9003;
}
location / {
proxy_pass http://gateway;
}
Config分布式配置中心 + Bus消息总线 + RabbitMq
- 建立New Module,config_server,pom.xml
<dependencies>
<!--eureka client 客户端依赖引入-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--config配置中心服务端-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
</dependencies>
- 启动类
package com.lx;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.config.server.EnableConfigServer;
@SpringBootApplication
@EnableDiscoveryClient
@EnableConfigServer
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class, args);
}
}
-
application.yml
Spring Cloud Bus支持Kafka,RabbitMq,Config + Bus结合实现配置信息的自动刷新。
server:
port: 9006
eureka:
client:
service-url:
defaultZone: http:/eurekaServer:8761/eureka
instance:
prefer-ip-address: true
instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}:@project.version@
spring:
application:
name: config-server
cloud:
config:
server:
git:
uri: https://github.com/username/test.git
username: username
password: password
search-paths:
- test
label: master
rabbitmq:
host: 127.0.0.1
port: 5672
username: guest
password: guest
management:
endpoints:
web:
exposure:
include: "*"
endpoint:
health:
show-details: always
-
启动服务,访问git下master下的配置文件
http://localhost:9006/master/service-media-dev.yml
-
在媒资微服务上构建client端,pom.xml
<!-- config配置中⼼服务端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-client</artifactId>
</dependency>
<!-- Config配置手动刷新 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
-
application.yml改名为bootstrap.xml
bootstrap.xml是系统级别的,优先级比application.yml高,应用启动时会检查这个配置文件,在这个配置文件中指定配置中心的服务地址,会自动拉取所有应用配置并且启用。
spring:
cloud:
config:
name: service-media
profile: dev
label: master
uri: http://localhost:9006
rabbitmq:
host: 127.0.0.1
port: 5672
username: guest
password: guest
- 增加config测试controller
package com.lx.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/config")
@RefreshScope
public class ConfigController {
@Value("${alert.message}")
private String alertMessage;
@Value("${mysql.url}")
private String mySqlUrl;
@GetMapping("/viewConfig")
public String viewConfig() {
return "lxMessage ====>>> " + alertMessage + "<br />mySqlUrl =====>>>> " + mySqlUrl;
}
}
-
重启媒资微服务,测试链接
http://localhost:8080/config/viewConfig
-
测试自动刷新,向配置中心发送post请求,用Postman测试
全部刷新:http://localhost:9006/actuator/bus-refresh
指定刷新:http://localhost:9006/actuator/bus-refresh/server-media:8080
Sleuth + Zipkin分布式链路追踪
链路追踪的本质就是日志记录,把Sleuth的数据信息发送给Zipkin进行聚合,利用Zipkin存储并展示数据。
核心概念:Trace、TraceID、Span、SpanID、事件
CS :client send/start 客户端/消费者发出⼀个请求,描述的是⼀个span开始
SR: server received/start 服务端/⽣产者接收请求 SR-CS属于请求发送的⽹络延迟
SS: server send/fifinish 服务端/⽣产者发送应答 SS-SR属于服务端消耗时间
CR:client received/fifinished 客户端/消费者接收应答 CR-SS表示回复需要的时间(响应的⽹络延迟)
- 每一个需要被追踪的微服务都引入依赖
<!--链路追踪-->
<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>
- 每一个需要被追踪的微服务application.yml
logging:
level:
org.springframework.web.servlet.DispatcherServlet: debug
org.springframework.cloud.sleuth: debug
重启服务后,请求到来时,在控制台就可以看到Sleuth输出的日志
- 创建New Module,zipkin_server,pom.xml
<dependencies>
<!--zipkin-server的依赖坐标-->
<dependency>
<groupId>io.zipkin.java</groupId>
<artifactId>zipkin-server</artifactId>
<version>2.12.3</version>
<exclusions>
<!--排除掉log4j2的传递依赖,避免和springboot依赖的⽇志组件冲突-->
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--zipkin-server ui界⾯依赖坐标-->
<dependency>
<groupId>io.zipkin.java</groupId>
<artifactId>zipkin-autoconfigure-ui</artifactId>
<version>2.12.3</version>
</dependency>
<dependency>
<groupId>io.zipkin.java</groupId>
<artifactId>zipkin-autoconfigure-storage-mysql</artifactId>
<version>2.12.3</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
</dependency>
</dependencies>
- 启动类
package com.lx;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import zipkin2.server.internal.EnableZipkinServer;
import javax.sql.DataSource;
@SpringBootApplication
@EnableZipkinServer
public class ZipkinServerApplication {
public static void main(String[] args) {
SpringApplication.run(ZipkinServerApplication.class, args);
}
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
-
application.yml
zipkin可以持久化到mysql
server:
port: 9411
management:
metrics:
web:
server:
auto-time-requests: false
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/zipkin?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true
username: root
password: 123456
druid:
initialSize: 10
minIdle: 10
maxActive: 30
maxWait: 50000
zipkin:
storage:
type: mysql
-
对被追踪的微服务application.yml增加
spring.zipkin.sender.type:web / kafka / rabbit
spring.sleuth.sampler.probability:采样率,1代表100%,默认0.1,代表10%
spring:
zipkin:
base-url: http://127.0.0.1:9411
sender:
type: web
sleuth:
sampler:
probability: 1
-
zipkin持久化到mysql,需要创建数据库zipkin,语句官方有提供
https://github.com/openzipkin/zipkin/blob/master/zipkin-storage/mysql-v1/src/main/resources/mysql.sql
-
启动服务,测试,先请求order微服务,然后在zipkin中查看
查看链路追踪:http://localhost:9411