单体架构: 将业务的所有功能集中在一个项目中开,打成一个包部署。
优点:
缺点:
分布式架构: 根据业务功能对系统进行拆分,每个业务模块作为独立的项目开发,称为一个服务。
微服务: 微服务是一种经过良好架构设计的分布式架构方案,微服务架构特征:
微服务结构:
微服务这种方案需要技术框架来落地,全球的互联网公司都在积极尝试自己的微服务落地技术,国外内最知名的就是SpringCloud和阿里巴巴的Dubbo。
微服务技术对比:
Dubbo | SpringCloud | SpringCloudAlibaba | |
---|---|---|---|
注册中心 | zookeeper、Redis | Eureka、Consul | Nacos、Eureka |
服务远程调用 | Dubbo协议 | Feign(http协议) | Dubbo、Feign |
配置中心 | 无 | SpringCloudConfig | SpringCloudConfig、Nacos |
服务网关 | 无 | SpringCloudGateway、Zuul | SpringCloudGateway、Zuul |
服务监控和保护 | dubbo-admin,功能弱 | Hystrix | Sentinel |
企业需求:
1.SpringCloud + Feign
2.SpringCloudAlibaba + Feign
3.SpringCloudAlibaba + Dubbo
4.Dubbo原始模式
SpringCloud
SpringCloud是目前国内使用最广泛的微服务架构,官网地址
SpringCloud
SpringCloud集成了各种微服务功能组件,并基于SpringBoot实现了这些组件的自动装配,从而提供了良好的开箱即用体验:
SpringCloud与SpringBoot的兼容关系如下:
Release Train | Boot Version |
---|---|
2021.0.x aka Jubilee | 2.6.x |
2020.0.x aka Ilford | 2.4.x |
Hoxton | 2.2.x,2.3.x(Starting with SR5) |
Grennwich | 2.1.x |
Finchley | 2.0.x |
Edgware | 1.5.x |
Dalston | 1.5.x |
需求:根据订单id查询订单的同时,把订单属性的用户信息一起返回
@MapperScan("cn.itcast.order.mapper")
@SpringBootApplication
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private RestTemplate restTemplate;
public Order queryOrderById(Long orderId) {
// 1.查询订单
Order order = orderMapper.findById(orderId);
// 2.利用RestTemplate发起http请求,查询用户
// 2.1 url路径
String url = "http://localhost:8081/user/" + order.getUserId();
// 2.2 发送http请求,实现远程调用
User user = restTemplate.getForObject(url, User.class);
order.setUser(user);
// 4.返回
return order;
}
}
总结:
提供者与消费者
服务A调用服务B,服务B调用服务C,那么服务C是什么角色?
一个服务即可以是提供者又可以是消费者
搭建EurekaServer服务步骤如下:
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-serverartifactId>
dependency>
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@EnableEurekaServer
@SpringBootApplication
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class, args);
}
}
server:
port: 10086 # 服务端口
spring:
application:
name: eurekaserver # eureka的服务名称
eureka:
client:
service-url: # eureka地址信息
defaultZone: http://localhost:10086/eureka
将user-service服务注册到EurekaServer步骤如下:
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
server:
port: 8081 # 服务端口
spring:
application:
name: userserver # user的服务名称
eureka:
client:
service-url: # eureka地址信息
defaultZone: http://localhost:10086/eureka
我们,可以将user-service多次启动,模拟多实例部署,但为了避免端口冲突,需要修改端口设置:
order-service虽然是消费者,但与user-service一样都是eureka的client端,同意可以实现服务注册:
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
server:
port: 8080 # 服务端口
spring:
application:
name: orderserver # user的服务名称
eureka:
client:
service-url: # eureka地址信息
defaultZone: http://localhost:10086/eureka
总结:
在order-service完成服务拉取
String url = "http://userservice/user/" + order.getUserId();
/**
* 创建RestTemplate并注入Spring容器
* @return
*/
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
Ribbon的负载均衡规则是一个叫IRule的接口来定义的,每一个子接口都是一种规则:
内置负载均衡规则类 | 规则描述 |
---|---|
RoundRobinRule | 简单轮询服务列表来选择服务器。它是Ribbon默认的负载均衡规则。 |
AvailabilityFilteringRule | 对以下两种服务器进行忽略: (1)在默认情况下,这台服务器如果3次连接失败,这台服务器就会被设置为“短路”状态。短路状态将持续30秒,如果再次连接失败,短路的持续时间就会几何级地增加。 (2)并发数过高的服务器。如果一个服务器的并发连接数过高了,配置了AvaikabilityFilteringRule规则的客户端也会将其忽略。并发连接数的上限,可由客户端的< clientName >.< clientConfigNameSpace >.ActiveConnectionsLimit属性进行配置。 |
WeightedResponseTimeRule | 为每一个服务器赋予一个权重值。服务器响应时间越长,这个服务器的权重就越小。这个规则会随机选择服务器,这个权重值会影响服务器的选择。 |
ZoneAvoidanceRule | 以区域可用的服务器为基础进行服务器的选择。使用Zone对服务器进行分类,这个Zone可以理解为一个机房、一个机架等。而后再对Zone内的多个服务做轮询。 |
BestAvailableRule | 忽略哪些短路的服务器,并选择并发数较低的服务器。 |
RandomRule | 随机选择一个可用的服务器。 |
RetryRule | 重试机制的选择逻辑。 |
通过定义IRule实现可以修改负载均衡规则,有两种方式:
@Bean
public IRule randomRule(){
return new RandomRule();
}
userserver:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 负载均衡规则
Ribbon默认是采用懒加载,即第一次访问时才会去创建LoadBalanceClient,请求时间会很长。而饥饿加载则会在项目中启动时创建,降低第一次访问的耗时,通过下面的配置开启饥饿加载:
ribbon:
eager-load:
enabled: true # 开启饥饿加载
clients: userservice # 指定对userservice这个服务饥饿加载
startup.cmd -m standalone # 单机启动
执行成功如图:
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-alibaba-dependenciesartifactId>
<version>2.2.5.RELEASEversion>
<type>pomtype>
<scope>importscope>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
spring:
cloud:
nacos:
server-addr: localhost:8848 # nacos服务地址
spring:
cloud:
nacos:
server-addr: localhost:8848 # nacos服务地址
discovery:
cluster-name: HZ # 配置集群名称,也就是机房位置,例如杭州:HZ,杭州
spring:
cloud:
nacos:
server-addr: localhost:8848 # nacos 服务端地址
discovery:
cluster-name: HZ # 集群名,也就是机房的位置
userserver:
ribbon:
NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule # 负载均衡规则
实际部署中会出现的场景:
Nacos提供了权重配置来控制访问频率,权重越大则访问频率越高
Nacos中服务存储和数据存储的最外层都是一个namespace 东西,用来做最外的隔离
spring:
application:
name: orderserver # order的服务名称
datasource:
url: jdbc:mysql://localhost:3306/cloud_order?useSSL=false
username: root
password: root
driver-class-name: com.mysql.jdbc.Driver
cloud:
nacos:
server-addr: localhost:8848 # nacos服务地址
discovery:
cluster-name: HZ # 配置集群名称,也就是机房位置,例如杭州:HZ,杭州
namespace: 82874223-5275-4594-867c-b6166d639249 # 命名空间 填ID
总结
nacos注册中心细节分析
服务注册到Nacos时,可以选择注册为临时或者非临时实例,通过以下的配置设置:
spring:
cloud:
nacos:
discovery:
ephemeral: false # 是否是临时实例
临时实例宕机时,会从nacos的服务列表中剔除,而非临时实例则不会
总结:Nacos与Eureka
Nacos与Eureka的共同点
Nacos与Eureka的区别
在Nacos中添加配置信息:
在弹出的表单中填写配置信息:
配置获取的步骤如下:
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-configartifactId>
dependency>
bootstrap.yml
文件,这个文件是个引导文件,优先级高于application.ymlspring:
application:
name: userservice
profiles:
active: dev # 环境
cloud:
nacos:
server-addr: localhost:8848
config:
file-extension: yaml # 文件后缀名
@RestController
@RequestMapping("/user")
public class UserController {
//注入nacos中的配置属性
@Value("${pattern.dateformat}")
private String dateformat;
//编写controller,通过日期格式化器来格式化现在时间并返回
@GetMapping("now")
public String now() {
return LocalDateTime.now().format(DateTimeFormatter.ofPattern(dateformat));
}
}
注意:由于以上在命名空间namespace,创建了一个dev命名空间,这里要把它注释了,如果不注释,在dev里面创建的yaml文件需要在bootstrap.yml文件的spring-cloud-nacos-config-namespace:配置上命名空间的id
Nacos中的配置文件变更后,微服务无需重启就可以感知。不过需要下面两种配置实现:
@RefreshScope
@ConfigurationProperties注解
@Data
@Component
@ConfigurationProperties(prefix = "pattern")
public class PatternPropertie {
private String dateformat;
}
总结:
Nacos配置更改后,微服务可以实现热更新,方式:
①通过@Value注解注入,结合@RefreshScope来刷新
②通过@ConfigurationProperties注入,自动刷新
注意事项:
微服务启动时会从nacos读取多个配置文件:
多种配置的优先级:
总结:微服务会从nacos读取的配置文件:
①[服务名]-[spring.profile.active].ymal,环境配置
②[服务名].yaml,默认配置,多环境共享
优先级:
[服务名]-[环境].yaml > [服务名].yaml > 本地配置
Nacos生产环境下一定要部署为集群状态,部署方式:
节点 | ip | port |
---|---|---|
nacos1 | 192.168.150.1 | 8845 |
nacos2 | 192.168.150.1 | 8846 |
nacos3 | 192.168.150.1 | 8847 |
Nacos默认数据存储在内嵌数据库Derby中,不属于生产可用的数据库。
以单点的数据库为例:
首先建立一个数据库,命名为nacos,导入SQL:
进入nacos的conf目录,修改配置文件cluster.conf.example,重命名为cluster.conf:
然后添加内容:
本地ip:8845
本地ip:8846
本地ip:8847
然后修改application.properties文件,添加数据库配置
spring.datasource.platform=mysql
db.num=1
db.url.0=jdbc:mysql://127.0.0.1:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC
db.user.0=root
db.password.0=root
将nacos文件夹复制三份,分别命名为:Nacos1、Nacos2、Nacos3
分别修改三个文件的application.properties
Nacos1:
server.port=8845
Nacos2:
server.port=8846
Nacos3:
server.port=8847
然后分别启动三个nacos节点:
startup.cmd
修改conf/nginx.conf文件,配置如下:
upstream nacos-cluster {
server 127.0.0.1:8845;
server 127.0.0.1:8846;
server 127.0.0.1:8847;
}
server {
listen 80;
server_name localhost;
location /nacos {
proxy_pass http://nacos-cluster;
}
}
启动nginx
start nginx.exe
集群搭建步骤:
1. 搭建MySQL集群并初始化数据库表
2. 下载解压nacos
3. 修改集群配置(节点信息)、数据库配置
4. 分别启动多个nacos节点
5. nginx反向代理
RestTemplate方式调用存在的问题
利用RestTemplate发起的远程调用的代码:
String url = "http://userservice/user/" + order.getUserId();
User user = restTemplate.getForObject(url,User.class);
存在以下的问题:
Feign的介绍
Feign是一个声明式的http客户端,官方地址:链接
其作用就是帮助我们优雅的实现http请求的发送,解决上面提到的问题。
定义和使用Feign客户端
使用Feign的步骤如下:
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
@EnableFeignClients
@MapperScan("cn.itcast.order.mapper")
@SpringBootApplication
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}
在这里插入代码片
主要是基于SpringMVC的注解来声明远程调用的信息,比如
Feign运行自定义配置来覆盖默认配置,可以修改的配置如下:
类型 | 作用 | 说明 |
---|---|---|
feign.Logger.Level | 修改日志级别 | 包含四种不同的级别:NONE、BASIC、HEADERS、FULL |
feign.codec.Decoder | 响应结果的解析器 | http远程调用的结果做解析,例如解析json字符串为java对象 |
feign.codec.Encoder | 请求参数编码 | 将请求参数编码,便于通过http请求发送 |
feign.Contract | 支持的注解格式 | 默认是SpringMVC的注解 |
feign.Retryer | 失败重试机制 | 请求失败的重试机制,默认是没有,不过会使用Ribbon的重试 |
一般需要配置的就是日志级别
配置Feign日志有两种方式:
方式一:配置文件方式
①全局生效:
feign:
client:
config:
default: # 这里用default就是全局配置,如果是写服务名称,则是针对某个微服务的配置
loggerLevel: FULL # 日志级别
②局部生效:
feign:
client:
config:
userservice: # 这里用default就是全局配置,如果是写服务名称,则是针对某个微服务的配置
loggerLevel: FULL # 日志级别
方式二:Java代码方式,需要先声明一个Bean:
public class DefaultFeignConfiguration {
@Bean
public Logger.Level loglevel(){
return Logger.Level.BASIC;
}
}
①而后如果是全局配置,则把它放到@EnableFeignClients这个注解中:
@EnableFeignClients(defaultConfiguration = DefaultFeignConfiguration.class)
②如果是局部配置,则把它放到@FeignClient这个注解中:
@FeignClient(value = "userservice",configuration = DefaultFeignConfiguration.class)
总结:
Feign底层的客户端实现:
因此优化Feign的性能主要包括:
①使用连接池代替默认的URLConnection
②日志级别,最好用basic或none
Feign性能优化优化-连接池配置
Feign添加HttpClient的支持:
引入依赖:
<dependency>
<groupId>io.github.openfeigngroupId>
<artifactId>feign-httpclientartifactId>
dependency>
配置连接池:
feign:
client:
config:
default: # default全局的配置
loggerLevel: BASIC # 日志级别,BASIC就是基本的请求和响应信息
httpclient:
enabled: true # 开启feign对Httpclient的支持
max-connections: 200 # 最大的连接数
max-connections-per-route: 50 # 每个路径的最大连接数
总结:Feign的优化
方式一(继承): 给消费者的FeignClient和提供者的controller定义统一的父接口作为标准。
总结:
① 让controller和FeignClient继承同一个接口
②将FeignClient、POJO、Feign的默认配置都定义到一个项目中,供所有消费者使用
网关功能:
身份认证和权限校验
(对用户请求做身份验证、权限校验)服务路由、负载均衡
(将用户请求路由到微服务,并实现负载均衡)请求限流
(对用户请求做限流)在SpringCloud中网关的实现包括两种:
搭建网关服务的步骤:
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-gatewayartifactId>
dependency>
server:
port: 10010 # 网关端口
spring:
application:
name: gateway # 网关名字
cloud:
nacos:
server-addr: localhost:8848 # nacos地址
gateway:
routes: # 网关路由配置
- id: user-service # 路由id,自定义,只要唯一即可
# uri: http://127.0.0.1:8081 # 路由的目标地址http就是固定地址
uri: lb://userservice # 路由的目标地址 lb就是负载均衡,后面跟服务名称
predicates: # 路由断言,判断请求是否符合规则
- Path=/user/** # 这个是按照路径匹配,只要/user/开头就符合要求
路由配置包括:
1. 路由id:路由的唯一标示
2. 路由路标(uri):路由的目标地址,http代表固定地址,lb代表根据服务吗负载均衡
3. 路由断言(predicates):判断路由的规则
4. 路由过滤器(filters):对请求或响应做处理
网关路由可以配置的内容包括:
predicates:路由断言,判断请求是否符合要求,符合则转发到路由目的地
例如Path=/user/**是按照路径匹配,这个规则是由
org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory类来处理的
像这样的断言工厂在SpringGateway还有十几个
Spring提供了11种基本的Predicate工厂:
名称 | 说明 | 示例 |
---|---|---|
After | 是某个时间点后的请求 | - After=2037-01-20T17:42:47.789-07:00[America/Denver] |
Before | 是某个时间点之前的请求 | - Before=2031-04-13T15:14:47.433+08:00[Asia/Shanghai] |
Between | 是某两个时间点之前的请求 | - Between=2037-01-20T17:42:47.789-07:00[America/Denver],2037-01-21T17:42:47.789-07:00[America/Denver] |
Cookie | 请求必须包含某些cookie | - Cookie=chocolate,ch.p |
Header | 请求必须包含某些header | - Header=X-Request-Id,\d+ |
Host | 请求必须是访问某个host(域名) | - Host=**.somehost.org,**.anotherhost.org |
Method | 请求方式必须是指定方式 | - Method=GET,POST |
Path | 请求路径必须符合指定规则 | - Path=/red/{segment},/blue/** |
Query | 请求参数必须包含指定参数 | - Query=name,Jack或者- Query=name |
RemoteAddr | 请求者的ip必须是指定范围 | - RemoteAddr=192.168.1.1/24 |
Weight | 权重处理 |
总结:
路由过滤器GatewayFilter
GatewayFilter是网关中提供的一种过滤器,可以对进入网关的请求和微服务返回的响应做处理
:
Spring提供了31种不同的路由过滤器工程:
名称 | 说明 |
---|---|
AddRequestHeader | 给当前请求添加一个请求头 |
RemoveRequestHeader | 移除请求中的一个请求头 |
AddResponseHeader | 给响应头结果中添加一个响应头 |
RemoveResponseHeader | 从响应结果中移除一个响应头 |
RequestRateLimiter | 限制请求的流量 |
... |
案例:给所有进入userservice的请求添加一个请求头
给所有进入userservice的请求添加一个请求头:Truth=itcast is freaking awesome!
实现方式:在gateway中修改application.yml文件,给userservice的路由添加过滤器:
spring:
cloud:
gateway:
routes: # 网关路由配置
- id: user-service # 路由id,自定义,只要唯一即可
# uri: http://127.0.0.1:8081 # 路由的目标地址http就是固定地址
uri: lb://userservice # 路由的目标地址 lb就是负载均衡,后面跟服务名称
predicates: # 路由断言,判断请求是否符合规则
- Path=/user/** # 这个是按照路径匹配,只要/user/开头就符合要求
filters:
- AddRequestHeader=Truth,Itcast is freaking awesome! # 添加请求头 # 添加请求头
默认过滤器
如果要对所有的路由都生效,则可以将过滤器工厂写到default下,格式如下:
spring:
application:
name: gateway # 网关名字
cloud:
nacos:
server-addr: localhost:1111 # nacos地址
gateway:
routes: # 网关路由配置
- id: user-service # 路由id,自定义,只要唯一即可
# uri: http://127.0.0.1:8081 # 路由的目标地址http就是固定地址
uri: lb://userservice # 路由的目标地址 lb就是负载均衡,后面跟服务名称
predicates: # 路由断言,判断请求是否符合规则
- Path=/user/** # 这个是按照路径匹配,只要/user/开头就符合要求
- id: order-service
uri: lb://orderserver
predicates:
- Path=/order/**
- Before=2037-01-20T17:42:47.789-07:00[America/Denver]
default-filters: # 默认过滤器,会对所有的路由请求都生效
- AddRequestHeader=Truth,Itcast is freaking awesome!
总结:
全局过滤器的作用也是处理一切进入网关的请求和微服务响应,与GatewayFilter的作用一样。
区别在于GatewayFilter通过配置定义,处理逻辑是固定的,而GlobalFilter的逻辑需要自己写代码实现。
定义方式是实现GlobalFilter接口
案例:定义全局过滤器,拦截并判断用户身份
需求:定义全局过滤器,拦截请求,判断请求的参数是否满足下面条件:
如果同时满足则放行,否则拦截
自定义类,实现GlobalFilter接口,添加@Order注解:
@Order(-1)
@Component
public class AuthorizeFilter implements GlobalFilter{ //, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//1.获取请求参数
ServerHttpRequest request = exchange.getRequest();
MultiValueMap<String, String> params = request.getQueryParams();
//2.获取参数中的 authorization 参数
String auth = params.getFirst("authorization");
//3.判断参数值是否等于 admin
if ("admin".equals(auth)) {
//4.是放行
return chain.filter(exchange);
}
//5.否拦截
//5.1设置状态码
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
//5.2
return exchange.getResponse().setComplete();
}
/*@Override
public int getOrder() {
return -1;
}*/
}
总结:
请求进入网关会碰到三类过滤器:当前路由的过滤器、DefaultFilter、GlobalFilter请求路由后,会将当前路由过滤器和DefaultFilter、GlobalFilter,合并到一个过滤器链(集合)中,排序后依次执行每个过滤器
跨域:域名不一致就是跨域,主要包括:
网关处理跨域采用CORS的方案,并且只需要简单的配置即可实现:
spring:
cloud:
gateway:
globalcors: # 全局的跨域处理
add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题
corsConfigurations:
'[/**]':
allowedOrigins: # 允许哪些网站的跨域请求
- "http://localhost:8090"
- "http://www.leyou.com"
allowedMethods: # 允许的跨域ajax的请求方式
- "GET"
- "POST"
- "DELETE"
- "PUT"
- "OPTIONS"
allowedHeaders: "*" # 允许在请求中携带的头信息
allowCredentials: true # 是否允许携带cookie
maxAge: 360000 # 这次跨域检测的有效期