- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
在学习并掌握了众多基础框架之后,我们的项目繁杂且难以掌握,那么我们就需要开启一门新的课程,也就是我们常说的微服务架构 。
随着互联网行业的发展,对服务的要求也越来越高,服务架构也从单体架构逐渐演变为现在流行的微服务架构.
这篇文章我们将会概括到下面几个知识:
首先我们需要去了解这么一个宏观的概念——微服务 。
在微服务没有出现之前,也就是我们之前的项目书写,一般都是采用单体架构:
我们可以给出单体架构的直观图:
但是单体架构的优缺点也十分明显:
当项目逐渐庞大之后,我们就开始使用分布式架构去处理项目:
我们给出分布式架构的直观图:
同样我们也可以很直观的获得分布式架构的优缺点:
优点:
缺点:
我们从单体架构升级到分布式架构自然会存在一些我们目前无法解决的问题:
而我们的微服务架构为我们的上述问题提供了一个统一的标准,因而微服务就此而生! 。
下面我们就来介绍微服务,微服务架构实际上是分布式架构的一种细化,我们给出微服务的架构特征:
我们同样给出微服务的一张直观图:
微服务的上述特性其实是在给分布式架构制定一个标准,进一步降低服务之间的耦合度,提供服务的独立性和灵活性.
因此,可以认为微服务是一种经过良好架构设计的分布式架构方案 .
微服务这种方案需要技术框架来落地,目前国内知名度较高的就是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,功能弱 | Hystix | Sentinel |
最后我们再给出目前企业所常使用的微服务组合:
最后我们介绍一下SpringCloud:
其中SpringCloud常用组件包括有:
下面一个小节我们来学习服务拆分和远程调用两方面 。
我们前面提及到了分布式架构需要将功能拆分出来并分离开发,那么我们该如何进行拆分:
我们给出一个简单的案例来展示服务拆分操作:
我们首先给出图示逻辑:
我们需要满足一下需求:
那么我们给出案例书写:
# order订单数据库
-- ----------------------------
-- Table structure for tb_order
-- ----------------------------
DROP TABLE IF EXISTS `tb_order`;
CREATE TABLE `tb_order` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '订单id',
`user_id` bigint(20) NOT NULL COMMENT '用户id',
`name` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '商品名称',
`price` bigint(20) NOT NULL COMMENT '商品价格',
`num` int(10) NULL DEFAULT 0 COMMENT '商品数量',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `username`(`name`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 109 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
-- ----------------------------
-- Records of tb_order
-- ----------------------------
INSERT INTO `tb_order` VALUES (101, 1, 'Apple 苹果 iPhone 12 ', 699900, 1);
INSERT INTO `tb_order` VALUES (102, 2, '雅迪 yadea 新国标电动车', 209900, 1);
INSERT INTO `tb_order` VALUES (103, 3, '骆驼(CAMEL)休闲运动鞋女', 43900, 1);
INSERT INTO `tb_order` VALUES (104, 4, '小米10 双模5G 骁龙865', 359900, 1);
INSERT INTO `tb_order` VALUES (105, 5, 'OPPO Reno3 Pro 双模5G 视频双防抖', 299900, 1);
INSERT INTO `tb_order` VALUES (106, 6, '美的(Midea) 新能效 冷静星II ', 544900, 1);
INSERT INTO `tb_order` VALUES (107, 2, '西昊/SIHOO 人体工学电脑椅子', 79900, 1);
INSERT INTO `tb_order` VALUES (108, 3, '梵班(FAMDBANN)休闲男鞋', 31900, 1);
SET FOREIGN_KEY_CHECKS = 1;
# user用户数据库
-- ----------------------------
-- Table structure for tb_user
-- ----------------------------
DROP TABLE IF EXISTS `tb_user`;
CREATE TABLE `tb_user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`username` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '收件人',
`address` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '地址',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `username`(`username`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 109 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
-- ----------------------------
-- Records of tb_user
-- ----------------------------
INSERT INTO `tb_user` VALUES (1, '柳岩', '湖南省衡阳市');
INSERT INTO `tb_user` VALUES (2, '文二狗', '陕西省西安市');
INSERT INTO `tb_user` VALUES (3, '华沉鱼', '湖北省十堰市');
INSERT INTO `tb_user` VALUES (4, '张必沉', '天津市');
INSERT INTO `tb_user` VALUES (5, '郑爽爽', '辽宁省沈阳市大东区');
INSERT INTO `tb_user` VALUES (6, '范兵兵', '山东省青岛市');
SET FOREIGN_KEY_CHECKS = 1;
我们会创建一个如下框架的IDEA框架:
我们对上述信息进行讲解:
/* cloud-demo:父工程,携带pom.xml */
/* order-service订单工程 user-service用户工程 */
// 具有完整的dao,mapper,service,Controller层并完整书写Application启动类
// 具有yml配置文件,包含有port端口信息,mysql数据库信息等
当我们运行程序后,我们可以在浏览器中查询到order相关数据:
但是我们会发现我们是无法查询到user的详细信息的,这是因为我们的order是没有user的数据库信息的 。
所以我们在完成了服务拆分之后就需要去了解远程调用:
那么我们该如何实现远程调用:
下面我们给出具体步骤及相关代码:
// 我们需要将RestTemplate设置为Bean对象(这里直接在Application中设置Bean)
package cn.itcast.order;
import cn.itcast.feign.clients.UserClient;
import cn.itcast.feign.config.DefaultFeignConfiguration;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
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;
@MapperScan("cn.itcast.order.mapper")
@SpringBootApplication
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
/**
* 创建RestTemplate并注入Spring容器
*/
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
package cn.itcast.order.service;
import cn.itcast.feign.clients.UserClient;
import cn.itcast.feign.pojo.User;
import cn.itcast.order.mapper.OrderMapper;
import cn.itcast.order.pojo.Order;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
// 这里在Service业务层获得信息
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
// 这里自动装填RestTemplate对象
@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请求,实现远程调用
// 这里restTemplate具有getForObject方法和postForObject分别针对get和post请求,后面参数分别为url和对应的class
User user = restTemplate.getForObject(url, User.class);
// 3.封装user到Order
order.setUser(user);
// 4.返回
return order;
}
}
最后我们针对服务拆分和远程调用给出两个理论角色概念:
我们需要注意的是:
下面我们来介绍一种注册中心EUreka 。
首先我们需要知道Eureka是什么:
我们给出一个简单的图示展示:
例如上图:
那么我们就需要注意到三个问题:
首先我们给出Eureka的具体结构并对其分析:
我们对上图进行简单介绍:
那么我们就可以回答上述问题:
/* 问题1:order-service如何得知user-service实例地址? */
// - user-service服务实例启动后,将自己的信息注册到eureka-server(Eureka服务端)。这个叫服务注册
// - eureka-server保存服务名称到服务实例地址列表的映射关系
// - order-service根据服务名称,拉取实例地址列表。这个叫服务发现或服务拉取
/* 问题2;order-service如何从多个user-service实例中选择具体的实例? */
// - order-service从实例列表中利用负载均衡算法选中一个实例地址
// - 向该实例地址发起远程调用
/* 问题3:order-service如何得知某个user-service实例是否依然健康,是不是已经宕机? */
// - user-service会每隔一段时间(默认30秒)向eureka-server发起请求,报告自己状态,称为心跳
// - 当超过一定时间没有发送心跳时,eureka-server会认为微服务实例故障,将该实例从服务列表中剔除
// - order-service拉取服务时,就能将故障实例排除了
下面我们逐渐介绍搭建Eureka-server的操作:
<!-- 依赖写在Eureka-server的pom.xml文件里 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
package cn.itcast.eureka;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
// 表示启动Eureka
@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://127.0.0.1:10086/eureka
接下来我们来进行服务注册功能:
<?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">
<parent>
<artifactId>cloud-demo</artifactId>
<groupId>cn.itcast.demo</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>user-service</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<!--eureka客户端依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
<build>
<finalName>app</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
spring:
application: # 配置服务名称
name: userservice
eureka:
client:
service-url: # 配置对应注册的Rureka地址
defaultZone: http://127.0.0.1:10086/eureka
// 1.复制一份user-service启动配置(在启动第一份后在左下角可以找到user-service,右键Copy Configuration)
// 2.在复制界面的VMoptions修改端口:-Dserver.port=8082
// 3.启动即可在Eureka页面看到两个user-service
我们在前面的注册环境已经将两个user-service设置在同一服务中 。
那么我们的order-service如果想要调用user-service的接口,我们就需要稍微修改代码使其在两个服务器中拉取数据:
package cn.itcast.order.service;
import cn.itcast.feign.clients.UserClient;
import cn.itcast.feign.pojo.User;
import cn.itcast.order.mapper.OrderMapper;
import cn.itcast.order.pojo.Order;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private RestTemplate restTemplate;
public Order queryOrderById(Long orderId) {
Order order = orderMapper.findById(orderId);
// url路径(这里的路径直接修改为服务名userservice,使其在相同服务名的服务器中选择)
String url = "http://userservice/user/" + order.getUserId();
User user = restTemplate.getForObject(url, User.class);
order.setUser(user);
return order;
}
}
package cn.itcast.order;
import cn.itcast.feign.clients.UserClient;
import cn.itcast.feign.config.DefaultFeignConfiguration;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
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;
@MapperScan("cn.itcast.order.mapper")
@SpringBootApplication
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
// @LoadBalanced表示负载均衡
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
下面我们来对上述的@Loadbalanced负载均衡进行部分介绍 。
我们首先给出负载均衡流程图:
下面我们采用另一张图来详细解释上述图:
我们对其进行简单解释:
我们在上面的解释中提及到了一个词汇:
我们给出一张IRule的继承图:
我们对其部分规则进行解释:
内置负载均衡规则类 | 规则描述 |
---|---|
RoundRobinRule | 简单轮询服务列表来选择服务器。它是Ribbon默认的负载均衡规则。 |
AvailabilityFilteringRule | 对以下两种服务器进行忽略: (1)在默认情况下,这台服务器如果3次连接失败,这台服务器就会被设置为“短路”状态。短路状态将持续30秒,如果再次连接失败,短路的持续时间就会几何级地增加。 (2)并发数过高的服务器。如果一个服务器的并发连接数过高,配置了AvailabilityFilteringRule规则的客户端也会将其忽略。并发连接数的上限,可以由客户端的
|
WeightedResponseTimeRule | 为每一个服务器赋予一个权重值。服务器响应时间越长,这个服务器的权重就越小。这个规则会随机选择服务器,这个权重值会影响服务器的选择。 |
ZoneAvoidanceRule | 以区域可用的服务器为基础进行服务器的选择。使用Zone对服务器进行分类,这个Zone可以理解为一个机房、一个机架等。而后再对Zone内的多个服务做轮询。 |
BestAvailableRule | 忽略那些短路的服务器,并选择并发数较低的服务器。 |
RandomRule | 随机选择一个可用的服务器。 |
RetryRule | 重试机制的选择逻辑 |
其中默认的实现就是ZoneAvoidanceRule,是一种轮询方案 。
除此之外我们还可以去自定义实现负载均衡策略,下面我们来介绍两种实现方法:
@Bean
public IRule randomRule(){
return new RandomRule();
}
userservice: # 给某个微服务配置负载均衡规则,这里是userservice服务
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 负载均衡规则
我们的Ribbon默认是采用懒加载,即第一次访问时才会去创建LoadBalanceClient,请求时间会很长.
我们可以采用代码去修改使其变为饿汉式加载:
ribbon:
eager-load:
enabled: true # 是否自动加载
clients: userservice # 针对的client
国内公司一般都推崇阿里巴巴的技术,比如注册中心,SpringCloudAlibaba也推出了一个名为Nacos的注册中心.
Nacos是阿里巴巴的技术产品了,我们需要先对其进行下载才可使用:
# 跳转路径
cd 目录名
# 启动startup.cmd
startup.cmd -m standalone
# 这里注意:下载后默认路径8848,可以在conf的properties文件修改port
我们来介绍nacos的服务注册过程:
<!-- cloud-demo 父工程 (SpringCloudAlibaba依赖)-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.6.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- user-service order-service子工程 (nacos-discovery)-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--注意:nacos和Eureka坐标冲突,需要注释掉Eureka依赖-->
# 在user-service和order-service的application.yml中添加nacos地址:
# 同样和Eureka冲突,记得注释掉
spring:
cloud:
nacos:
server-addr: localhost:8848
我们首先给出一张服务分级存储模型图并对其解释:
我们对其进行解释:
下面我们来介绍如何设置集群:
spring:
cloud:
nacos:
server-addr: localhost:8848
discovery:
cluster-name: HZ # 集群名称
但是我们默认的ZoneAvoidanceRule无法实现同集群优先负载均衡操作,所以我们需要对其进行设置:
# 修改order-service的application.yml文件,添加集群配置:
spring:
cloud:
nacos:
server-addr: localhost:8848
discovery:
cluster-name: HZ # 集群名称
# 修改order-service的application.yml文件,修改负载均衡规则:
userservice:
ribbon:
NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule # 负载均衡规则
在我们的Nacos控制系统中的对象都会有一个权重设置:
我们对权重进行简单解释:
Nacos提供了namespace来实现环境隔离功能:
首先我们先来了解如何在Nacos中新创namespace:
在新创命名空间之后,我们如果希望数据上传到指定命名空间,需要手动修改部分代码:
# 例如我们在order-service的application.yml文件中进行修改,那么后面的order服务就会到达新的命名空间中
spring:
cloud:
nacos:
server-addr: localhost:8848
discovery:
cluster-name: HZ
namespace: 478f4b7c-c1a5-4dee-a7c7-84774766654d # 命名空间,填ID
Nacos和Eureka整体结构类似,服务注册、服务拉取、心跳等待,但是也存在一些差异 。
我们首先给出Eureka的展示图:
我们再给出Nacos的展示图:
我们可以发现Nacos相比于Eureka有些许不同之处,首先是临时实例和非临时实例:
此外还有Nacos关于服务消费者的区别:
最后我们给出Nacos和Eureka的相同点与不同点 。
Nacos与Eureka的共同点:
Nacos与Eureka的不同点:
在前面我们学习了Nacos去完成微服务注册功能,下面我们来学习Nacos的配置管理功能 。
首先我们先来学习Nacos中统一配置管理的基本内容:
下面我们需要知道一些关于Nacos和application的相关信息:
下面我们继续开始统一配置管理的内容:
<!--在使用统一配置的服务下-->
<!--nacos配置管理依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
# 注:在userservice服务下
spring:
application:
name: userservice # 服务名称
profiles:
active: dev #开发环境,这里是dev
cloud:
nacos:
server-addr: localhost:8848 # Nacos地址
config:
file-extension: yaml # 文件后缀名
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
// 这里采用@Value注入配置信息,输出成功证明收到Nacos配置信息
@Value("${pattern.dateformat}")
private String dateformat;
@GetMapping("now")
public String now(){
return LocalDateTime.now().format(DateTimeFormatter.ofPattern(dateformat));
}
}
我们首先来介绍一下热更新:
下面我们来介绍两种方法来实现热更新:
@Slf4j
@RestController
@RequestMapping("/user")
@RefreshScope // 热更新注解,使用后Nacos的配置信息即时更新
public class UserController {
@Autowired
private UserService userService;
@Value("${pattern.dateformat}")
private String dateformat;
@GetMapping("now")
public String now(){
return LocalDateTime.now().format(DateTimeFormatter.ofPattern(dateformat));
}
}
// 配置属性实体类
@Component
@Data
@ConfigurationProperties(prefix = "pattern") // ConfigurationProperties表示热更新注解,prefix表示共用前缀
public class PatternProperties {
private String dateformat;
}
// 实时使用
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@Autowired
private PatternProperties patternProperties;
@GetMapping("now")
public String now(){
return LocalDateTime.now().format(DateTimeFormatter.ofPattern(patternProperties.getDateformat()));
}
// 略
}
我们之前采用了特定环境配置,其具体表示为:
[spring.application.name]-[spring.profiles.active].yaml
,例如:userservice-dev.yaml 当我们不使用环境名称时,其配置就会变为共享配置:
[spring.application.name].yaml
,例如:userservice.yaml 我们给出一个简单的示例:
// 配置属性实体类
@Component
@Data
@ConfigurationProperties(prefix = "pattern") // ConfigurationProperties表示热更新注解,prefix表示共用前缀
public class PatternProperties {
private String dateformat;
private String envSharedValue;
}
// Controller层
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@Autowired
private PatternProperties properties;
@GetMapping("prop")
public PatternProperties properties(){
return properties;
}
}
// 在启动userService时
// 以Edit Configuration打开,并在Active profiles中修改名称以修改环境
最后我们给出配置管理的优先级展示:
下面我们来介绍一下Feign 。
首先我们简单介绍一下Feign:
我们这里回忆一下RestTemplate的远程调用方法:
package cn.itcast.order.service;
import cn.itcast.feign.clients.UserClient;
import cn.itcast.feign.pojo.User;
import cn.itcast.order.mapper.OrderMapper;
import cn.itcast.order.pojo.Order;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private RestTemplate restTemplate;
public Order queryOrderById(Long orderId) {
Order order = orderMapper.findById(orderId);
// 需要手动书写url,并且加入id参数
String url = "http://userservice/user/" + order.getUserId();
// 需要调用restTemplate的固定方法并指定类class
User user = restTemplate.getForObject(url, User.class);
order.setUser(user);
return order;
}
}
我们下面给出Feign的基本使用:
<!--在order-service的pom文件中引入Feign依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
package cn.itcast.order;
import cn.itcast.feign.clients.UserClient;
import cn.itcast.feign.config.DefaultFeignConfiguration;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
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;
@MapperScan("cn.itcast.order.mapper")
@SpringBootApplication
@EnableFeignClients
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}
package cn.itcast.order.client;
import cn.itcast.order.pojo.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
// 整体就是SpringMVC的REST风格
// @FeignClient类似RequestMapping,后面跟上具体的服务名
@FeignClient("userservice")
public interface UserClient {
// 这里就是具体的方法,采用REST
@GetMapping("/user/{id}")
User findById(@PathVariable("id") Long id);
}
/*
这个客户端主要是基于SpringMVC的注解来声明远程调用的信息,比如:
- 服务名称:userservice
- 请求方式:GET
- 请求路径:/user/{id}
- 请求参数:Long id
- 返回值类型:User
*/
package cn.itcast.order.service;
import cn.itcast.feign.clients.UserClient;
import cn.itcast.feign.pojo.User;
import cn.itcast.order.mapper.OrderMapper;
import cn.itcast.order.pojo.Order;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private UserClient userClient;
public Order queryOrderById(Long orderId) {
// 1.查询订单
Order order = orderMapper.findById(orderId);
// 2.用Feign远程调用
User user = userClient.findById(order.getUserId());
// 3.封装user到Order
order.setUser(user);
// 4.返回
return order;
}
}
下面我们来介绍一下Feign的自定义配置:
类型 | 作用 | 说明 |
---|---|---|
feign.Logger.Level | 修改日志级别 | 包含四种不同的级别:NONE、BASIC、HEADERS、FULL |
feign.codec.Decoder | 响应结果的解析器 | http远程调用的结果做解析,例如解析json字符串为java对象 |
feign.codec.Encoder | 请求参数编码 | 将请求参数编码,便于通过http请求发送 |
feign. Contract | 支持的注解格式 | 默认是SpringMVC的注解 |
feign. Retryer | 失败重试机制 | 请求失败的重试机制,默认是没有,不过会使用Ribbon的重试 |
大部分内容我们只需要使用默认就足以满足我们日常需求了 。
我们简单介绍一下Logger日志级别:
日志大体分为四种 。
NONE:不记录任何日志信息,这是默认值 。
BASIC:仅记录请求的方法,URL以及响应状态码和执行时间 。
HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息 。
FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据 。
我们给出两种修改默认配置的方法:
# 修改yaml配置文件
# 可以针对某个微服务修改
feign:
client:
config:
userservice: # 针对某个微服务的配置
loggerLevel: FULL # 日志级别
# 也可以针对全部微服务修改
feign:
client:
config:
default: # 这里用default就是全局配置,如果是写服务名称,则是针对某个微服务的配置
loggerLevel: FULL # 日志级别
// 声明一个类,然后声明一个Logger.Level的对象
public class DefaultFeignConfiguration {
@Bean
public Logger.Level feignLogLevel(){
return Logger.Level.BASIC; // 日志级别为BASIC
}
}
// 此外我们还需要将该类配置给该服务:
// 如果要全局生效,将其放到启动类的@EnableFeignClients这个注解中
@EnableFeignClients(defaultConfiguration = DefaultFeignConfiguration .class)
// 如果是局部生效,则把它放到对应的@FeignClient这个注解中
@FeignClient(value = "userservice", configuration = DefaultFeignConfiguration .class)
Feign底层发起http请求,依赖于其它的框架,我们这里给出一些底层框架:
URLConnection:默认实现,不支持连接池 。
Apache HttpClient :支持连接池 。
OKHttp:支持连接池 。
因此提高Feign的性能主要手段就是使用连接池代替默认的URLConnection.
我们给出使用连接池的示例:
<!--我们这里以Apache HttpClient为例-->
<!--httpClient的依赖 -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>
# 在对应的yml文件中配置连接池信息(这里就是order-service服务)
feign:
httpclient:
enabled: true # 开启feign对HttpClient的支持
max-connections: 200 # 最大的连接数
max-connections-per-route: 50 # 每个路径的最大连接数
我们可以发现Feign实际上和Controller的代码十分相似:
// Feign
@FeignClient("userservice")
public interface UserClient {
// 这里就是具体的方法,采用REST
@GetMapping("/user/{id}")
User findById(@PathVariable("id") Long id);
}
// Controller
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/{id}")
public User queryById(@PathVariable("id") Long id,
@RequestHeader(value = "Truth", required = false) String truth) {
System.out.println("truth: " + truth);
return userService.queryById(id);
}
}
我们给出一种抽取方法来减少相同代码的书写:
将Feign的Client抽取为独立模块,并且把接口有关的POJO、默认的Feign配置都放到这个模块中,提供给所有消费者使用.
例如,将UserClient、User、Feign的默认配置都抽取到一个feign-api包中,所有微服务引用该依赖包,即可直接使用.
我们给出具体实例:
<!--Feign依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--首先删除掉order-service的全部Feign相关类和DefaultFeignConfiguration等配置-->
<!--导入我们编写的feign-api类-->
<dependency>
<groupId>cn.itcast.demo</groupId>
<artifactId>feign-api</artifactId>
<version>1.0</version>
</dependency>
<!--修改order-service中的所有与上述三个组件有关的导包部分,改成导入feign-api中的包-->
/*
由于UserClient现在在cn.itcast.feign.clients包下,
而order-service的@EnableFeignClients注解是在cn.itcast.order包下,不在同一个包,无法扫描到UserClient
所以我们需要手动扫描包,其中可以采用两种方法:
- 指定Feign应该扫描的包:@EnableFeignClients(basePackages = "cn.itcast.feign.clients")
- 指定需要加载的Client接口:@EnableFeignClients(clients = {UserClient.class})
*/
@MapperScan("cn.itcast.order.mapper")
@SpringBootApplication
@EnableFeignClients(clients = UserClient.class)
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}
最后我们来介绍一下GateWay服务网关 。
我们首先介绍一下GateWay:
我们给出一张GateWay的示意图:
其中GateWay大致存在三种主要用途:
关于网关大致包括两种:
下面我们通过一个简单的案例来介绍GateWay的基本使用:
<!--在parent-demo中单独创建gateway-demo模块,并加入以下网关依赖-->
<!--网关-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--nacos服务发现依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
package cn.itcast.gateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
# 路由配置包括
# 1. 路由id:路由的唯一标示
# 2. 路由目标(uri):路由的目标地址,http代表固定地址,lb代表根据服务名负载均衡
# 3. 路由断言(predicates):判断路由的规则,
# 4. 路由过滤器(filters):对请求或响应做处理
# 我们将符合`Path` 规则的一切请求,都代理到 `uri`参数指定的地址。
# 本例中,我们将 `/user/**`开头的请求,代理到`lb://userservice`,lb是负载均衡,根据服务名拉取服务列表,实现负载均衡。
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/开头就符合要求
/*
GateWay网关的port是10010
所以我们访问http://localhost:10010/user/1时,符合`/user/**`规则,请求转发到uri:http://userservice/user/1
*/
最后我们给出一张网关过程展示图:
下面我们来介绍一下断言:
我们下面给出几个简单的断言工厂(我们目前只需要PATH断言工厂即可):
名称 | 说明 | 示例 |
---|---|---|
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 | 权重处理 |
我们先简单介绍一下GateWay过滤器:
我们给出一张GateWay过滤器展示图:
其中Spring提供了31种过滤器,这里仅仅介绍几种:
名称 | 说明 |
---|---|
AddRequestHeader | 给当前请求添加一个请求头 |
RemoveRequestHeader | 移除请求中的一个请求头 |
AddResponseHeader | 给响应结果中添加一个响应头 |
RemoveResponseHeader | 从响应结果中移除有一个响应头 |
RequestRateLimiter | 限制请求的流量 |
然后我们给出过滤器的使用方法:
# 在yaml中进行过滤器配置,我们可以通过各种过滤器达到不同目的,例如添加请求头AddRequestHeader
# 我们可以采用服务uri路由名称单独给某个微服务设置过滤器
spring:
cloud:
gateway:
routes:
- id: user-service
uri: lb://userservice
predicates:
- Path=/user/**
filters: # 过滤器
- AddRequestHeader=Truth, Itcast is freaking awesome! # 添加请求头
# 我们也可以采用全局过滤器对所有微服务进行过滤(default-filters)
spring:
cloud:
gateway:
routes:
- id: user-service
uri: lb://userservice
predicates:
- Path=/user/**
default-filters: # 默认过滤项
- AddRequestHeader=Truth, Itcast is freaking awesome!
我们在前面学习了过滤器工厂,但是过滤器工厂只能实现已经设计好的方法 。
如果我们希望拦截业务来完成自己的功能增强或拦截,我们就需要设计过滤器:
全局过滤器的底层原理是实现了GlobalFilter接口:
public interface GlobalFilter {
/**
* 处理当前请求,有必要的话通过{@link GatewayFilterChain}将请求交给下一个过滤器处理
*
* @param exchange 请求上下文,里面可以获取Request、Response等信息
* @param chain 用来把请求委托给下一个过滤器
* @return {@code Mono<Void>} 返回标示当前过滤器业务结束
*/
Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain);
}
我们给出一个简单的业务逻辑:
/*
定义全局过滤器,拦截请求,判断请求的参数是否满足下面条件:
- 参数中是否有authorization,
- authorization参数值是否为admin
*/
package cn.itcast.gateway.filters;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Order(-1) // Order表示执行优先级,越小优先级越高
@Component
public class AuthorizeFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 1.获取请求参数
MultiValueMap<String, String> params = exchange.getRequest().getQueryParams();
// 2.获取authorization参数
String auth = params.getFirst("authorization");
// 3.校验
if ("admin".equals(auth)) {
// 放行
return chain.filter(exchange);
}
// 4.拦截
// 4.1.禁止访问,设置状态码
exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
// 4.2.结束处理
return exchange.getResponse().setComplete();
}
}
目前我们已经接触了三种过滤器:
最后我们需要思考GateWay过滤器的整体优先级:
这篇文章中介绍了SpringCloud的整体框架及其知识点,属于微服务的入门内容,下面我们会继续学习微服务内容~ 。
该文章属于学习内容,具体参考B站黑马程序员的SpringCloud课程 。
这里附上视频链接: 微服务技术栈导学1_哔哩哔哩_bilibili 。
最后此篇关于微服务学习计划——SpringCloud的文章就讲到这里了,如果你想了解更多关于微服务学习计划——SpringCloud的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
关闭。这个问题是opinion-based .它目前不接受答案。 想要改进这个问题? 更新问题,以便 editing this post 可以用事实和引用来回答它. 关闭 9 年前。 Improve
介绍篇 什么是MiniApis? MiniApis的特点和优势 MiniApis的应用场景 环境搭建 系统要求 安装MiniApis 配置开发环境 基础概念 MiniApis架构概述
我正在从“JavaScript 圣经”一书中学习 javascript,但我遇到了一些困难。我试图理解这段代码: function checkIt(evt) { evt = (evt) ? e
package com.fastone.www.javademo.stringintern; /** * * String.intern()是一个Native方法, * 它的作用是:如果字
您会推荐哪些资源来学习 AppleScript。我使用具有 Objective-C 背景的传统 C/C++。 我也在寻找有关如何更好地开发和从脚本编辑器获取更快文档的技巧。示例提示是“查找要编写脚本的
关闭。这个问题不满足Stack Overflow guidelines .它目前不接受答案。 想改善这个问题吗?更新问题,使其成为 on-topic对于堆栈溢出。 4年前关闭。 Improve thi
关闭。这个问题不满足Stack Overflow guidelines .它目前不接受答案。 想改善这个问题吗?更新问题,使其成为 on-topic对于堆栈溢出。 7年前关闭。 Improve thi
关闭。这个问题不符合 Stack Overflow guidelines 。它目前不接受答案。 想改善这个问题吗?更新问题,以便堆栈溢出为 on-topic。 6年前关闭。 Improve this
我是塞内加尔的阿里。我今年60岁(也许这是我真正的问题-笑脸!!!)。 我正在学习Flutter和Dart。今天,我想使用给定数据模型的列表(它的名称是Mortalite,请参见下面的代码)。 我尝试
关闭。这个问题是off-topic .它目前不接受答案。 想改进这个问题? Update the question所以它是on-topic对于堆栈溢出。 9年前关闭。 Improve this que
学习 Cappuccino 的最佳来源是什么?我从事“传统”网络开发,但我对这个新框架非常感兴趣。请注意,我对 Objective-C 毫无了解。 最佳答案 如上所述,该网站是一个好地方,但还有一些其
我正在学习如何使用 hashMap,有人可以检查我编写的这段代码并告诉我它是否正确吗?这个想法是有一个在公司工作的员工列表,我想从 hashMap 添加和删除员工。 public class Staf
我正在尝试将 jQuery 与 CoffeScript 一起使用。我按照博客中的说明操作,指示使用 $ -> 或 jQuery -> 而不是 .ready() 。我玩了一下代码,但我似乎无法理解我出错
还在学习,还有很多问题,所以这里有一些。我正在进行 javascript -> PHP 转换,并希望确保这些做法是正确的。是$dailyparams->$calories = $calories;一条
我目前正在学习 SQL,以便从我们的 Magento 数据库制作一个简单的 RFM 报告,我目前可以通过导出两个查询并将它们粘贴到 Excel 模板中来完成此操作,我想摆脱 Excel 模板。 我认为
我知道我很可能会因为这个问题而受到抨击,但没有人问,我求助于你。这是否是一个正确的 javascript > php 转换 - 在我开始不良做法之前,我想知道这是否是解决此问题的正确方法。 JavaS
除了 Ruby-Doc 之外,哪些来源最适合获取一些示例和教程,尤其是关于 Ruby 中的 Tk/Tile?我发现自己更正常了 http://www.tutorialspoint.com/ruby/r
我只在第一次收到警告。这正常吗? >>> cv=LassoCV(cv=10).fit(x,y) C:\Python27\lib\site-packages\scikit_learn-0.14.1-py
按照目前的情况,这个问题不适合我们的问答形式。我们希望答案得到事实、引用或专业知识的支持,但这个问题可能会引发辩论、争论、投票或扩展讨论。如果您觉得这个问题可以改进并可能重新打开,visit the
As it currently stands, this question is not a good fit for our Q&A format. We expect answers to be
我是一名优秀的程序员,十分优秀!