文章目录
Spring Cloud 微服务框架
一、 框架图与解析
框架中,网关服务、服务、配置中心服务都注册到Eureka
服务中,它就相当于作为一个服务管理与调度中心,如果Eureka
开启高可用模式,服务本身也要开启服务的注册与列表拉取;客户端发送请求,至Spring Cloud Gateway
网关服务处,网关负责请求过滤与转发(路由),支持通过Ribbon
负载均衡与Hystrix
熔断机制,并能配置解决跨域问题;请求分发至服务后,服务间通过Feign
简化服务的调用,同时也内嵌支持ribbon
负载均衡与Hystrix
熔断机制,可以通过配置实现请求压缩;而Spring Cloud Config
配置中心服务的目的在于,对各个服务的配置进行统一的管理,通过Git
远程修改与推送;但是这种配置更新无法在服务中立即生效,需要重启服务才行,因此引入Spring Cloud bus
,用轻量消息代理连接分布式节点,可以广播配置文件的更改或者服务的监控管理,开启bus-refresh
节点,并在配置中心和服务A…中配置RabbitMQ
,当配置修改推送后,通过向配置中心发送.../acutator/bus-refresh
的post
请求,使各个监听的服务得知需要更新配置文件了。
二、实践
各个模块的作用在上方已经说明,接下来就是来对每个模块的实践预配置,请根据目录把下列内容当做spec参考;
下面这个demo各个模块介绍:
- parent:对各子模块要用到的依赖做统一版本管理;
- eureka server:服务的发现和注册中心,方便服务间的交互,可以配置服务失效剔除时间、自我保护等;可以将高可用、网关、服务、消费、配置中心都注册;
- gateway server:负责对请求的路由(ip、断言、过滤),转发请求,同时可以配置负载均衡、熔断;跨域问题可以通过配置解决,过滤可以添加全局、服务、自定义(全局、服务)过滤器;
- config server:可以对配置进行git统一管理,动态更新;
- user service:服务提供方,可以直接或通过网关访问;
- customer:服务消费方,可以使用RestTemplate或更简单的feign调用user服务,调用时可以开启负载均衡和熔断;
PS:项目源码下载地址
2.1 parent module 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>
<name>Parent</name>
<description>Version Control</description>
<groupId>com.coderwhat</groupId>
<artifactId>parent</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging> <!-- 父工程要将默认jar打包改为pom -->
<parent>
<artifactId>spring-boot-starter-parent</artifactId>
<groupId>org.springframework.boot</groupId>
<version>2.1.5.RELEASE</version>
<relativePath/> <!-- 父项目的pom.xml文件的相对路径,默认值为../pom.xml;而后再是本地仓库、远程仓库 -->
</parent>
<modules> <!-- 子模块 -->
<module>../eureka</module>
</modules>
<properties>
<!-- 项目编码,自动识别 -->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!-- maven输出文件编码 -->
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.SR1</spring-cloud.version>
<mapper-starter.version>2.1.5</mapper-starter.version>
<mysql.version>5.1.46</mysql.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- 通用Mapper启动器 -->
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
<version>${mapper-starter.version}</version>
</dependency>
<!-- mysql 驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<!-- Maven默认编辑器插件maven-compiler-plugin采用的Java编译级别为1.5,需要调整到1.8 -->
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version> <!-- 与springboot dependencies中保持一致 -->
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.1.2</version> <!-- 与springboot dependencies中保持一致 -->
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>
repackage <!-- 打包时将引用jar包都打包成一个完整jar -->
</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
2.2 Eureka Server
2.2.1 Eureka知识点
eureka
是作为一个服务的注册、发现中心,帮助管理、调用服务的;
使用时有以下几点需要注意:
eureka
依赖分server
和client
的;eureka
服务器配置除非是高可用,否则需要关闭自身的注册与拉取;eureka
jar包中自带spring-cloud-starter-netflix-ribbon
的,所以在配置ribbon
负载均衡时无需再次引入。eureka client
如果需要拉取服务列表,除了在配置文件中配置eureka
外,还需要在启动类注解@EnableDiscoveryClient
2.2.2 代码与配置文件
2.2.2.1 依赖:
<?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.coderwhat</groupId>
<artifactId>eureka-server</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<artifactId>parent</artifactId>
<groupId>com.coderwhat</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<!-- eureka server -->
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
</project>
2.2.2.2 启动类:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@EnableEurekaServer //声明Eureka Server服务
@SpringBootApplication
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
2.2.2.3 配置文件:
server:
port: ${eurekaport:10068} # 如果虚拟机中有eurekaport变量就引用,否则使用10068
spring:
application:
name: eureka-server
eureka:
client:
service-url:
# eureka服务地址,如果是集群的话,需要指定集群eureka地址
defaultZone: ${defaultZone:http://127.0.0.1:10068/eureka}
# 默认情况下,是会注册服务并拉取服务列表的,但在非集群(高可用)情况下,需要关闭
register-with-eureka: false # 不注册本服务
fetch-registry: false # 不拉取服务列表
# 配置eureka高可用,需要配置如下两步:(10069为另一台eureka server)
# 1.配置服务地址,有两种方式:
# a.在当前服务VM Option中配置defaultZone变量:
## -Dport=10068 -DdefaultZone=http://127.0.0.1:10069/eureka
# b.将配置中的defaultZone值改为:
## defaultZone: http://127.0.0.1/10068/eureka,http://127.0.0.1/10069/eureka
# 2. 将eureka server注册服务和拉取列表都打开(默认打开)
server:
# 服务端配置服务失效剔除间隔,默认60s
eviction-interval-timer-in-ms: 60000 # 单位ms
# 关闭自我保护模式(默认打开) 这样就不会出现服务关闭,eureka猜测可能是网络原因导致依旧保留服务状态
enable-self-preservation: false # 开发时需要关闭,方便调试
2.3 Gateway Server
2.3.1 Spring Cloud Gateway 知识点
spring cloud gateway
网关的主要作用是路由和过滤,具体又可以分为以下三种:
- 断言(Predicate):允许开发人员自定义Http Request请求的任意信息,最常见的有请求头、请求参数;
- 过滤(Filter):对请求或响应返回的消息做处理;
- 路由(route) :路由信息的组成:由一个ID、一个目的URL、一组断言工厂、一组Filter组成。如果路由断言为
真,说明请求URL和配置路由匹配。
配置网关需要注意一下几点:
- 导入
eureka client
依赖,并在启动类上加上@EnableDiscoveryClient
开启eureka
发现; - 自定义路由过滤器可以参照已有过滤器实现来写,全局过滤器实现
GlobalFilter
即可,不需再配置文件中配置;
2.3.2 实践
2.3.2.1 依赖
<?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.coderwhat</groupId>
<artifactId>gateway-server</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<artifactId>parent</artifactId>
<groupId>com.coderwhat</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<dependencies>
<!-- gateway -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- eureka client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
</project>
2.3.2.2 启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@EnableDiscoveryClient //开启Eureka客户端发现功能
@SpringBootApplication
public class GatewayServerApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayServerApplication.class, args);
}
}
2.3.2.3 配置文件
server:
port: 10010
spring:
application:
name: gateway-server
cloud:
gateway:
## 默认过滤器,对所有路径都生效
default-filters:
- AddResponseHeader=test-request-message, hello
- AddResponseHeader=how-are-you?, world
## 处理跨域问题
globalcors:
cors-configurations:
'[/**]':
allowedHeaders: "*"
#allowedOrigins: * # 这种写法或者下面的都可以,*表示全部
allowedOrigins:
- "http://docs.spring.io"
allowedMethods:
- GET
- POST
## 配置路由
routes:
- id: user-service-route # 路由id,名字随意
## uri: http://127.0.0.1:7031 # 代理服务地址,写死的方式不好
# lb: loadbalance表示从eureka server获取
uri: lb://user-service # 通过服务名,动态从eureka server获取
# predicates: # 标准设置的是所有满足下列path的都能发送请求
# - Path=/user/**
## 下列则是想实现:所有路径且不带/uer的都能自动添加路径,并使用自定义过滤器获取name属性
predicates: # 路由断言:匹配映射地址,下方即为路径全匹配
- Path=/**
filters: # 过滤器,对上方断言匹配请求过滤操作
- PrefixPath=/user # 请求路径添加指定前缀
- GetParam=name # 自定义过滤器,获取请求参数name
# - StripPrefix=1 # 请求路径去除前缀,1表示一个路径,2表示两个,以此类推
## eureka配置
eureka:
client:
service-url:
defaultZone: ${defaultZone:http://127.0.0.1:10068/eureka}
# 使用ip注册、访问服务
instance:
prefer-ip-address: true
## 配置hystrix熔断策略
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 6000
circuitBreaker:
errorThresholdPercentage: 50 # 触发熔断错误比例阈值,默认值50%
sleepWindowInMilliseconds: 10000 # 熔断后休眠时长,默认值5秒
requestVolumeThreshold: 10 # 熔断触发最小请求次数,默认值是20
## 配置ribbon负载均衡
ribbon:
ConnectTimeout: 1000 # 连接超时时长
ReadTimeout: 2000 # 数据通信超时时长
MaxAutoRetries: 0 # 当前服务器的重试次数
MaxAutoRetriesNextServer: 0 # 重试多少次服务
OkToRetryOnAllOperations: false # 是否对所有的请求方式都重试
2.3.2.4 自定义路由过滤与全局过滤
- 路由过滤器
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
/**
* 这里定义的是断言后filters里的过滤器
*/
@Component
public class GetParamGatewayFilterFactory extends AbstractGatewayFilterFactory<GetParamGatewayFilterFactory.Config> {
static final String PARAM_NAME = "param";
public static class Config {
//对应在配置过滤器的时候指定的参数名:name
private String param;
public String getParam() {
return param;
}
public void setParam(String param) {
this.param = param;
}
}
public GetParamGatewayFilterFactory() {
super(Config.class);
}
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList(PARAM_NAME);
}
@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
// http://127.0.0.1:7031/8?name=zhangsan config.param ==> name
//获取请求参数中param对应的参数名的参数值
ServerHttpRequest request = exchange.getRequest();
if (request.getQueryParams().containsKey(config.param)) {
request.getQueryParams().get(config.param)
.forEach(value -> System.out.printf("----局部过滤器----%s = %s ----", config.param, value));
}
return chain.filter(exchange);
};
}
}
- 全局过滤器
import org.apache.commons.lang.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* 这里定义的是全局过滤器,不用单独配置,只用实现GlobalFilter即可
*/
@Component
public class DemoGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("------------全局过滤器MyGlobalFilter------------");
String token = exchange.getRequest().getQueryParams().getFirst("token");
// 过滤请求参数不带token的
if (StringUtils.isBlank(token)){
//设置相应状态码为未授权
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
//值越小越先执行
return 0;
}
}
2.3 Config Server
2.3.1 Spring Cloud Config 知识点
配置中心是作为对服务的配置文件进行统一管理的组件,集成spring cloud bus
实现配置文件的实时生效;
配置配置中心的时候,有以下几点要注意:
config
配置依赖分为server
和client
;config server
启动类需要注解@EnableConfigServer
,开启配置服务@EnableDiscoveryClient
注解是用来标注从eureka serve
拉取服务列表的,与注册无关,所以像config server
可以不用注解;spring cloud bus
除了在config server
中暴露节点,需要进行统一配置管理的服务,也需要引入actuator
依赖;- 配置的git仓库如果是需要权限校验则需要额外配置;
2.3.2 实践
2.3.2.1 依赖
<?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>
<artifactId>config-server</artifactId>
<parent>
<artifactId>parent</artifactId>
<groupId>com.coderwhat</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<dependencies>
<!-- config server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
<!-- eureka client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- spring cloud bus -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-bus</artifactId>
</dependency>
<!-- RabbitMQ -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-binder-rabbit</artifactId>
</dependency>
</dependencies>
</project>
2.3.2.2 配置文件
server:
port: 10101
spring:
application:
name: config-server
cloud:
config:
server:
git:
# 服务配置文件存放git地址
uri: https://gitee.com/xxxx/xxxx-springcloud-config.git
# 配置rabbitmq信息;如果是都与默认值一致则不需要配置
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
eureka:
client:
service-url:
defaultZone: ${defaultZone:http://127.0.0.1:10068/eureka}
## 集成 spring-cloud-bus 需要配置RabbitMQ并暴露bus-refresh节点
management:
endpoints:
web:
exposure:
# 暴露触发消息总线的地址
include: bus-refresh
2.3.2.3 启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@EnableConfigServer //开启配置服务
@SpringBootApplication
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class, args);
}
}
2.4 User Service
2.4.1 服务(提供方)知识点
user-service服务作为服务的提供方,需要注册、拉取eureka
,集成config client
及springcloud bus
配置实现git托管动态更新,进行简单的数据库操作;
有以下几点要注意:
- 集成
config client
与springcloud bus
实现动态刷新配置,需要在client
端额外引入spring-boot-starter-actuator
依赖; - 当新配置被push上后,发送
post
请求,content-type
为application/json
,访问../actuator/bus-refresh
即可刷新配置立即生效; - 当服务中有使用到配置文件中的变量时,需要加上
@RefreshScope
刷新配置注解,这样才能在调用时实时生效;
2.4.2 实践
2.4.2.1 依赖
<?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.coderwhat</groupId>
<artifactId>user-service</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<artifactId>parent</artifactId>
<groupId>com.coderwhat</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<dependencies>
<!-- web + tomcat -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 通用Mapper -->
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
</dependency>
<!-- mysql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- eureka client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- config client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<!-- spring cloud bus -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-bus</artifactId>
</dependency>
<!-- RabbitMQ -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-binder-rabbit</artifactId>
</dependency>
<!-- actuator -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
</project>
2.4.2.2 配置文件(本地bootstrap.yml及git上user-dev.yml)
- bootstrap.yml
这里主要是配置springcloud config
、springcloud bus
所关联的RabbitMQ
、eureka client
;
spring:
cloud:
config:
## git仓库中的配置文件命令要遵循规则:name-profile.yml
## 这里就要与仓库中的配置文件保持一致: user-dev.yml
name: user # 要与仓库中的配置文件的application保持一致
profile: dev # 要与仓库中的配置文件的profile(环境)保持一致
label: master # 要与仓库中的配置文件所属的版本(分支)一致
discovery:
enabled: true # 使用配置中心
service-id: config-server # eureka中配置中心的服务名
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
eureka:
client:
service-url:
defaultZone: ${defaultZone:http://127.0.0.1:10068/eureka}
- user-dev.yml
存放在git服务器上
# 配置端口、服务名称、数据库连接
server:
port: ${port:7031}
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/springcloud
username: root
password: root
application:
name: user-service
# 配置mybatis
mybatis:
type-aliases-package: com.coderwhat.user.pojo
# 配置eureka client、服务访问ip与eureka续约配置
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10068/eureka
# 除了在VM option中配置defaultZone变量实现eureka高可用,还可以将在这里设置值为:
# http://127.0.0.1:10068/eureka,http://127.0.0.1:10069/eureka
instance:
# 更倾向使用ip地址,而不是host名
prefer-ip-address: true
# ip地址
ip-address: 127.0.0.1
# 续约间隔,默认30s
lease-renewal-interval-in-seconds: 5
# 服务时效时间,默认90s
lease-expiration-duration-in-seconds: 5
# 测试用字段
test:
name: user_lisi_zhaowu
2.4.2.3 启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import tk.mybatis.spring.annotation.MapperScan;
@SpringBootApplication
@MapperScan("com.coderwhat.user.mapper") //扫描mapper包,自动生成dao接口实现类
@EnableDiscoveryClient //开启Eureka客户端发现功能
public class UserServiceApplication {
public static void main(String[] args) {
SpringApplication.run(UserServiceApplication.class, args);
}
}
2.4.2.4 pojo
import lombok.Data;
import tk.mybatis.mapper.annotation.KeySql;
import javax.persistence.Id;
import javax.persistence.Table;
@Data
@Table(name="user")
public class User {
@Id
//开启主键自动回填
@KeySql(useGeneratedKeys = true)
private Long id;
// 用户名
private String userName;
// 密码
private String password;
// 姓名
private String name;
// 年龄
private Integer age;
}
2.4.2.5 mapper
import com.coderwhat.user.pojo.User;
import tk.mybatis.mapper.common.Mapper;
public interface UserMapper extends Mapper<User> {
}
2.4.2.6 service
接口:
import com.coderwhat.user.pojo.User;
public interface UserService {
User queryById(Long id);
}
实现:
import com.coderwhat.user.mapper.UserMapper;
import com.coderwhat.user.pojo.User;
import com.coderwhat.user.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service("userService")
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
/**
* 根据主键查询用户
* @param id 用户id
* @return 用户
*/
@Override
public User queryById(Long id) {
return userMapper.selectByPrimaryKey(id);
}
}
2.4.2.7 controller
import com.coderwhat.user.pojo.User;
import com.coderwhat.user.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/user")
@RefreshScope //刷新配置,因为这里使用的test.name调用了配置文件
public class UserServiceController {
@Autowired
private UserService userService;
@Value("${test.name}") //配置文件中的test.name字段
private String name;
@RequestMapping("/{id}")
public User queryById(@PathVariable Long id){
/*try {
//测试2s延迟是否会造成网关、服务间调用的服务降级和熔断
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
System.out.println("配置文件中的test.name为:" + name);
return userService.queryById(id);
}
}
2.4.2.8 数据创建语句
create database if not exists `springcloud` character set `utf8`
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`user_name` varchar(100) DEFAULT NULL COMMENT '用户名',
`password` varchar(100) DEFAULT NULL COMMENT '密码',
`name` varchar(100) DEFAULT NULL COMMENT '姓名',
`age` int(10) DEFAULT NULL COMMENT '年龄',
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`user_name`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;
INSERT INTO `user` VALUES ('1', 'ux1', 'ux1', 'u1', '21');
INSERT INTO `user` VALUES ('2', 'ux2', 'ux2', 'u2', '22');
INSERT INTO `user` VALUES ('3', 'ux3', 'ux3', 'u3', '23');
INSERT INTO `user` VALUES ('4', 'ux4', 'ux4', 'u4', '24');
INSERT INTO `user` VALUES ('5', 'ux5', 'ux5', 'u5', '25');
select * from `user` where id < 6;
2.5 consumer
2.5.1 消费服务知识点
consumer
作为i服务消费方,通过多方式去调用user-service
服务,同样注册到eureka server
,继承调用时的负载均衡和熔断;
实现如下:
- RestTemplate调用服务:
1.服务调用:启动类实例化
RestTemplate
,然后采用拼接地址和路径的方式调用服务;也可以通过DiscoveryClient
获取注册服务实例的方式,再拼接路径,调用服务;
2.负载均衡:@LoadBalanced
注解RestTemplate
即可;
3.熔断:启动类添加@EnableCircuitBreaker
注解,然后配置文件中配置hystrix
,并编写降级调用方法;
- Feign调用服务:
1.服务调用:启动类添加
@EnableFeignClients
开启feign功能,然后配置相应服务调用的接口(feign会动态代理生成实现类,调用user服务);
2.负载均衡:feign默认集成和配置了,可以在配置文件中手动修改配置;
3.熔断:配置文件中开启熔断(hystrix可以套用之前的配置),然后编写专门的服务降级类;
4.请求压缩:配置即可;
5.配置日志级别:在配置文件中配置package的日志级别,然后编写配置类来配置@FeignClient
代理的客户端的日志内容;
2.5.2 实例
2.5.2.1 依赖
<?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.coderwhat</groupId>
<artifactId>consumer</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<artifactId>parent</artifactId>
<groupId>com.coderwhat</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- eureka -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- feign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- hystrix -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
</dependencies>
</project>
2.5.2.2 配置文件
server:
port: 8989
spring:
application:
name: consumer
eureka:
client:
service-url:
defaultZone: ${defaultZone:http://127.0.0.1:10068/eureka}
# 当请求延迟2s以上未响应时,就会触发consumer的服务降级
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 2000 # ms
circuitBreaker:
errorThresholdPercentage: 50 # 触发熔断错误比例阈值,默认值50%
sleepWindowInMilliseconds: 10000 # 熔断后休眠时长,默认值5秒
requestVolumeThreshold: 10 # 熔断触发最小请求次数,默认值是20
feign:
# 开启feign的熔断功能
hystrix:
enabled: true
# 配置feign的请求、响应压缩
compression:
request:
enabled: true # 开启请求压缩
mime-types: text/html,application/xml,application/json # 设置压缩的数据类型
min-request-size: 2048 # 设置触发压缩的大小下限
response:
enabled: true # 开启响应压缩
# fegin内置的ribbon和自动配置,这里我们对配置信息做一些修改
ribbon:
ConnectTimeout: 1000 # 连接超时时长
ReadTimeout: 2000 # 数据通信超时时长
MaxAutoRetries: 0 # 当前服务器的重试次数
MaxAutoRetriesNextServer: 0 # 重试多少次服务
OkToRetryOnAllOperations: false # 是否对所有的请求方式都重试
# 配置com.coderwhat包的日志级别为debug
logging:
level:
com.coderwhat: debug
2.5.2.3 pojo
import lombok.Data;
@Data
public class User {
private Long id;
// 用户名
private String userName;
// 密码
private String password;
// 姓名
private String name;
// 年龄
private Integer age;
}
2.5.2.4 启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.SpringCloudApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
//@SpringBootApplication
//@EnableDiscoveryClient
//@EnableCircuitBreaker //开启hystrix熔断
@SpringCloudApplication //包含上方三个注解
@EnableFeignClients //开启feign功能
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
@Bean //实例化RestTemplate
@LoadBalanced //注解即可,使其使用负载均衡算法ribbon
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
2.5.2.5 RestTemplate 消费方式
2.5.2.5.1 Controller
import com.coderwhat.consumer.pojo.User;
import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
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;
@RestController
@RequestMapping("/restConsumer")
@Slf4j
@DefaultProperties(defaultFallback = "defaultFallback") //当前controller中@HystrixCommand默认降级方法
public class ConsumerRestController {
@Autowired
private RestTemplate restTemplate;
@Autowired
private DiscoveryClient discoveryClient;
/**
* ①使用RestTemplate采用拼接地址的方式访问user服务
* testPath: http://localhost:8989/restConsumer/3?name=2&token=3
* @param id 用户id
* @return 用户信息
*/
@GetMapping("/{id}")
public User queryById(@PathVariable Long id) {
String url = "http://127.0.0.1:7031/user/" + id;
User user = restTemplate.getForObject(url, User.class);
return user;
}
/**
* ②user-service服务已经注册到eureka server,可以从server服务列表中获取实例;
* 尝试使用RestTemplate以及eureka实例的方式访问user服务
* testPath: http://localhost:8989/restConsumer/eureka/3?name=2&token=3
* @param id
* @return
*/
@RequestMapping("/eureka/{id}")
public User queryByIdEureka(@PathVariable Long id) {
List<ServiceInstance> instances = discoveryClient.getInstances("user-service");
ServiceInstance serviceInstance = instances.get(0);
String url = "http://" + serviceInstance.getHost() + ":" + serviceInstance.getPort() + "/user/" + id;
User user = restTemplate.getForObject(url, User.class);
return user;
}
/**
* ③在使用获取eureka注册服务的方式访问后,熔断配置完后自定义对降级方法的支持
* testPath: http://localhost:8989/restConsumer/eureka/hystrix/4?name=2&token=3
* @param id
* @return
*/
@RequestMapping("/eureka/hystrix/{id}")
@HystrixCommand(fallbackMethod = "fallback")
public String queryByIdEurekaWithHystrix(@PathVariable Long id) {
//模拟异常发生
if (id == 1){
throw new RuntimeException("模拟异常");
}
List<ServiceInstance> instances = discoveryClient.getInstances("user-service");
ServiceInstance serviceInstance = instances.get(0);
String url = "http://" + serviceInstance.getHost() + ":" + serviceInstance.getPort() + "/user/" + id;
String user = restTemplate.getForObject(url, String.class);
return user;
}
/**
* ④对当前controller中的@HystrixCommand添加默认降级方法
* testPath: http://localhost:8989/restConsumer/eureka/defaultHystrix/4?name=2&token=3
* @param id
* @return
*/
@RequestMapping("/eureka/defaultHystrix/{id}")
@HystrixCommand
public String queryByIdEurekaWithDefaultHystrix(@PathVariable Long id) {
//模拟异常发生
if (id == 1){
throw new RuntimeException("模拟异常");
}
List<ServiceInstance> instances = discoveryClient.getInstances("user-service");
ServiceInstance serviceInstance = instances.get(0);
String url = "http://" + serviceInstance.getHost() + ":" + serviceInstance.getPort() + "/user/" + id;
String user = restTemplate.getForObject(url, String.class);
return user;
}
/**
* 重点:熔断方法的"参数列表"和"返回值声明"必须与调用方法相同;
* 如果为全局指定默认熔断方法,则要保证"返回值类型"相同
* @param id
* @return
*/
public String fallback(Long id){
log.error("查询用户信息失败;id={}",id);
return "对不起,网络太拥挤了";
}
/**
* 引申:如果要携带参数,应该怎么做?
* @return
*/
public String defaultFallback(){
return "对不起,全局网络太拥挤了";
}
}
2.5.2.6 Feign 消费方式
2.5.2.6.1 服务调用接口
import com.coderwhat.consumer.client.fallback.UserClientFallback;
import com.coderwhat.consumer.config.FeignConfig;
import com.coderwhat.consumer.pojo.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
//声明当前类是一个Feign客户端,指定服务名为user-service
//Feign会通过动态代理,帮我们生成实现类,类似mybatis的mapper
@FeignClient(name = "user-service",
fallback = UserClientFallback.class,
configuration = FeignConfig.class)
public interface UserClient {
/**
* feign会根据服务名和注解中的后序路径拼接访问地址,例如:
* http:// + user-service + /user/9
* @param id 请求参数报纸一直
* @return
*/
@RequestMapping("/user/{id}")
User queryById(@PathVariable Long id);
}
2.5.2.6.2 服务降级处理类
import com.coderwhat.consumer.client.UserClient;
import com.coderwhat.consumer.pojo.User;
import org.springframework.stereotype.Component;
/**
* 服务降级处理类
*/
@Component
public class UserClientFallback implements UserClient {
//此方法若如法hystrix配置,则响应如下
@Override
public User queryById(Long id) {
User user = new User();
user.setId(id);
user.setName("用户查询异常");
return user;
}
}
2.5.2.6.3 Controller
import com.coderwhat.consumer.client.UserClient;
import com.coderwhat.consumer.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/feignConsumer")
public class ConsumerFeignController {
@Autowired
private UserClient userClient;
@RequestMapping("/{id}")
public User queryById(@PathVariable Long id) {
return userClient.queryById(id);
}
}
2.5.2.6.4 日志配置类
import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
//配置feign中的日志等级
@Configuration
public class FeignConfig {
@Bean
Logger.Level feignLoggerLever(){
//记录所有的请求和响应明细
return Logger.Level.FULL;
}
}