SpringCloud
认识微服务
单体架构
在认识微服务之前,我先来说说单体架构,我们之前所学的SpringBoot做的最多项目就是单体架构项目。而单体架构是将业务的所有功能集中在一个项目中开发,打成一个包部署。所以单体架构的优缺点就十分的明显,如下图表示。
微服务架构
而微服务就是把单体架构中某些功能或者某些模块给拆分出来,拆分单个项目或者单个模块,成为一个微服务集群
SpringCloud
SpringCloud是目前国内使用最广泛的微服务框架,它集成了各种微服务功能组件,并且基于SpringBoot实现了这些组件的自动装配,从而提供了良好的开箱。
SpringCloud基于SpringBoot实现了微服务组件的自动装配,从而提供了良好的开箱即用体验。对于SpringBoot的版本也有要求所以使用的版本
准备工作
熟悉黑马商城
黑马商城包括:
- 用户模块
- 商品模块
- 购物车模块
- 订单模块
- 支付模块
一会我们进行纵向拆分
服务拆分原则
什么时候拆分
在企业里面使用微服务拆分一般分为两种
- 创业项目:先采用单体架构,快速开发,快速试错。随着规模扩大,逐渐拆分
- 确定大型项目:资金充足,目标明确,可以直接选择微服务架构,避免后续拆分的麻烦
怎么拆分
拆分目标来说,要做到以下两个方面
- 高内聚:每个微服务的职责要尽量单一,包含的业务相互关联度高、完整度高
- 低耦合:每个微服务的功能要相对独立,尽量减少对其它微服务的依赖
拆分方式来说,包含以下两个方面
- 纵向拆分:按照业务模块来拆分
- 横向拆分:抽取公共服务,提高复用性
拆分服务
将一个项目拆成多个模块,把hm-service先拆出来item-service和cart-service,我们首先拆分itme-service
新建一个model
然后将hm-service里面的item相关代码复制粘贴到item-service里面
添加Maven依赖
<dependencies>
<!--common-->
<dependency>
<groupId>com.heima</groupId>
<artifactId>hm-common</artifactId>
<version>1.0.0</version>
</dependency>
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--加密-->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-crypto</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-rsa</artifactId>
<version>1.0.9.RELEASE</version>
</dependency>
<!--数据库-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--mybatis-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<!--单元测试-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!--redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
修改yml文件
另外一个模块同理
不过注意一下,itemService注释掉,以及UserContext如下图,拆分成所以我们拿不到一会我们使用远程调用来解决
远程调用
如我刚刚说的,因为我们把单一项目拆分成了模块,模块与模块直接请求需要用到远程调用,而Spring给我们提供了一个RestTemplate工具,可以方便实现Http请求调用。
- 注入RestTemplate到Spring容器当中
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
- 发起调用
public <T> ResponseEntity<T> exchange(
String url, // 请求路径
HttpMethod method, // 请求方式
@Nullable HttpEntity<?> requestEntity, // 请求实体不为空
Class<T> responseType, // 返回值类型
Map<String, ?> uriVariables // 请求参数
)
下面我们直接使用RestTemplate来进行远程调用,先在Card模块注入
然后在到service层注入RestTemplate
再注入的同时我们发现@autowired有黄色的下黄线,我们查看发现idea提示我们不推荐这样注入,要求我们直接写一个构造方法。
但是加入我们的成员变量很多,那么我们使用构造函数的列表就会很长,这样我们后续去阅读代码就会很麻烦,所以我们使用Lombok的@RequiredArgsConstructor的注解并且给RestTemplate加上final这样就变成了必备成员变量的构造函数。
接下来我们直接远程调用
private void handleCartItems(List<CartVO> vos) {
// 1.获取商品id
Set<Long> itemIds = vos.stream().map(CartVO::getItemId).collect(Collectors.toSet());
// 2.查询商品
// List<ItemDTO> items = itemService.queryItemByIds(itemIds);
ResponseEntity<List<ItemDTO>> response = restTemplate.exchange(
"http://localhost:8081/items?ids={ids}",
HttpMethod.GET,
null,
new ParameterizedTypeReference<>() {
},
Map.of("ids", CollUtils.join(itemIds, ","))
);
if(!response.getStatusCode().is2xxSuccessful()) {
return;
}
List<ItemDTO> items = response.getBody();
if (CollUtils.isEmpty(items)) {
return;
}
// 3.转为 id 到 item的map
Map<Long, ItemDTO> itemMap = items.stream().collect(Collectors.toMap(ItemDTO::getId, Function.identity()));
// 4.写入vo
for (CartVO v : vos) {
ItemDTO item = itemMap.get(v.getItemId());
if (item == null) {
continue;
}
v.setNewPrice(item.getPrice());
v.setStatus(item.getStatus());
v.setStock(item.getStock());
}
}
服务治理
刚刚我们已经使用远程调用,但是刚刚远程调用我们后续会遇到一个问题,那就是发送请求的路劲是写死的。如果某一天我们写死的路径发生了变化或者它崩掉了,我们应该怎么办。所以这个时候我们就要用到注册中心。
注册中心
注册中心原理
注册中心主要是帮助服务提供者,提供给服务调用者,比如说我们有服务三个接口,注册中心会把这个三个接口统计下来,等到服务调用者来调用时,注册中心把统计好的服务给调用者,调用者拿到服务通过负载均衡去调用相应的接口。
如果提供者的某个服务挂掉注册中心也会知道,并把挂掉的服务告诉调用者,调用者就不用去请求到挂掉的服务。
总结:
- 服务治理中的三个角色分别是什么?
- 服务提供者:暴露服务接口,供其它服务调用
- 服务消费者:调用其它服务提供的接口•注册中心:记录并监控微服务各实例状态,
- 推送服务变更信息消费者如何知道提供者的地址?
- 服务提供者会在启动时注册自己信息到注册中心,消费者可以从注册中心订阅和拉取服务信息
- 消费者如何得知服务状态变更?
- 服务提供者通过心跳机制向注册中心报告自己的健康状态,当心跳异常时注册中心会将异常服务剔除,并通知订阅了该服务的消费者
- 当提供者有多个实例时,消费者该选择哪一个?
- 消费者可以通过负载均衡算法,从多个实例中选择一个
Nacos注册中心
安装nacos
GitHub下载地址:https://github.com/alibaba/nacos/releases
Nacos官网:https://nacos.io/zh-cn/docs/quick-start.html
目前最稳定版本是2.2.3,下载2.2.3版本即可
启动nacos
解压到某盘中,进入到nacos/bin下,双击startup.cmd,或者在cmd输入 <font style="color:#DF2A3F;">startup.cmd -m standalone
导入nacos数据源
nacos本事有数据库的,我们直接导入到MySQL中就好
-- --------------------------------------------------------
-- 主机: 192.168.150.101
-- 服务器版本: 8.0.27 - MySQL Community Server - GPL
-- 服务器操作系统: Linux
-- HeidiSQL 版本: 12.2.0.6576
-- --------------------------------------------------------
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET NAMES utf8 */;
/*!50503 SET NAMES utf8mb4 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
-- 导出 nacos 的数据库结构
DROP DATABASE IF EXISTS `nacos`;
CREATE DATABASE IF NOT EXISTS `nacos` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci */ /*!80016 DEFAULT ENCRYPTION='N' */;
USE `nacos`;
-- 导出 表 nacos.config_info 结构
DROP TABLE IF EXISTS `config_info`;
CREATE TABLE IF NOT EXISTS `config_info` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT 'id',
`data_id` varchar(255) COLLATE utf8_bin NOT NULL COMMENT 'data_id',
`group_id` varchar(128) COLLATE utf8_bin DEFAULT NULL,
`content` longtext COLLATE utf8_bin NOT NULL COMMENT 'content',
`md5` varchar(32) COLLATE utf8_bin DEFAULT NULL COMMENT 'md5',
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
`src_user` text COLLATE utf8_bin COMMENT 'source user',
`src_ip` varchar(50) COLLATE utf8_bin DEFAULT NULL COMMENT 'source ip',
`app_name` varchar(128) COLLATE utf8_bin DEFAULT NULL,
`tenant_id` varchar(128) COLLATE utf8_bin DEFAULT '' COMMENT '租户字段',
`c_desc` varchar(256) COLLATE utf8_bin DEFAULT NULL,
`c_use` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`effect` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`type` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`c_schema` text COLLATE utf8_bin,
`encrypted_data_key` text COLLATE utf8_bin NOT NULL COMMENT '秘钥',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_configinfo_datagrouptenant` (`data_id`,`group_id`,`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8_bin COMMENT='config_info';
-- 正在导出表 nacos.config_info 的数据:~0 rows (大约)
DELETE FROM `config_info`;
-- 导出 表 nacos.config_info_aggr 结构
DROP TABLE IF EXISTS `config_info_aggr`;
CREATE TABLE IF NOT EXISTS `config_info_aggr` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT 'id',
`data_id` varchar(255) COLLATE utf8_bin NOT NULL COMMENT 'data_id',
`group_id` varchar(128) COLLATE utf8_bin NOT NULL COMMENT 'group_id',
`datum_id` varchar(255) COLLATE utf8_bin NOT NULL COMMENT 'datum_id',
`content` longtext COLLATE utf8_bin NOT NULL COMMENT '内容',
`gmt_modified` datetime NOT NULL COMMENT '修改时间',
`app_name` varchar(128) COLLATE utf8_bin DEFAULT NULL,
`tenant_id` varchar(128) COLLATE utf8_bin DEFAULT '' COMMENT '租户字段',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_configinfoaggr_datagrouptenantdatum` (`data_id`,`group_id`,`tenant_id`,`datum_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8_bin COMMENT='增加租户字段';
-- 正在导出表 nacos.config_info_aggr 的数据:~0 rows (大约)
DELETE FROM `config_info_aggr`;
-- 导出 表 nacos.config_info_beta 结构
DROP TABLE IF EXISTS `config_info_beta`;
CREATE TABLE IF NOT EXISTS `config_info_beta` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT 'id',
`data_id` varchar(255) COLLATE utf8_bin NOT NULL COMMENT 'data_id',
`group_id` varchar(128) COLLATE utf8_bin NOT NULL COMMENT 'group_id',
`app_name` varchar(128) COLLATE utf8_bin DEFAULT NULL COMMENT 'app_name',
`content` longtext COLLATE utf8_bin NOT NULL COMMENT 'content',
`beta_ips` varchar(1024) COLLATE utf8_bin DEFAULT NULL COMMENT 'betaIps',
`md5` varchar(32) COLLATE utf8_bin DEFAULT NULL COMMENT 'md5',
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
`src_user` text COLLATE utf8_bin COMMENT 'source user',
`src_ip` varchar(50) COLLATE utf8_bin DEFAULT NULL COMMENT 'source ip',
`tenant_id` varchar(128) COLLATE utf8_bin DEFAULT '' COMMENT '租户字段',
`encrypted_data_key` text COLLATE utf8_bin NOT NULL COMMENT '秘钥',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_configinfobeta_datagrouptenant` (`data_id`,`group_id`,`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8_bin COMMENT='config_info_beta';
-- 正在导出表 nacos.config_info_beta 的数据:~0 rows (大约)
DELETE FROM `config_info_beta`;
-- 导出 表 nacos.config_info_tag 结构
DROP TABLE IF EXISTS `config_info_tag`;
CREATE TABLE IF NOT EXISTS `config_info_tag` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT 'id',
`data_id` varchar(255) COLLATE utf8_bin NOT NULL COMMENT 'data_id',
`group_id` varchar(128) COLLATE utf8_bin NOT NULL COMMENT 'group_id',
`tenant_id` varchar(128) COLLATE utf8_bin DEFAULT '' COMMENT 'tenant_id',
`tag_id` varchar(128) COLLATE utf8_bin NOT NULL COMMENT 'tag_id',
`app_name` varchar(128) COLLATE utf8_bin DEFAULT NULL COMMENT 'app_name',
`content` longtext COLLATE utf8_bin NOT NULL COMMENT 'content',
`md5` varchar(32) COLLATE utf8_bin DEFAULT NULL COMMENT 'md5',
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
`src_user` text COLLATE utf8_bin COMMENT 'source user',
`src_ip` varchar(50) COLLATE utf8_bin DEFAULT NULL COMMENT 'source ip',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_configinfotag_datagrouptenanttag` (`data_id`,`group_id`,`tenant_id`,`tag_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8_bin COMMENT='config_info_tag';
-- 正在导出表 nacos.config_info_tag 的数据:~0 rows (大约)
DELETE FROM `config_info_tag`;
-- 导出 表 nacos.config_tags_relation 结构
DROP TABLE IF EXISTS `config_tags_relation`;
CREATE TABLE IF NOT EXISTS `config_tags_relation` (
`id` bigint NOT NULL COMMENT 'id',
`tag_name` varchar(128) COLLATE utf8_bin NOT NULL COMMENT 'tag_name',
`tag_type` varchar(64) COLLATE utf8_bin DEFAULT NULL COMMENT 'tag_type',
`data_id` varchar(255) COLLATE utf8_bin NOT NULL COMMENT 'data_id',
`group_id` varchar(128) COLLATE utf8_bin NOT NULL COMMENT 'group_id',
`tenant_id` varchar(128) COLLATE utf8_bin DEFAULT '' COMMENT 'tenant_id',
`nid` bigint NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`nid`),
UNIQUE KEY `uk_configtagrelation_configidtag` (`id`,`tag_name`,`tag_type`),
KEY `idx_tenant_id` (`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8_bin COMMENT='config_tag_relation';
-- 正在导出表 nacos.config_tags_relation 的数据:~0 rows (大约)
DELETE FROM `config_tags_relation`;
-- 导出 表 nacos.group_capacity 结构
DROP TABLE IF EXISTS `group_capacity`;
CREATE TABLE IF NOT EXISTS `group_capacity` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`group_id` varchar(128) COLLATE utf8_bin NOT NULL DEFAULT '' COMMENT 'Group ID,空字符表示整个集群',
`quota` int unsigned NOT NULL DEFAULT '0' COMMENT '配额,0表示使用默认值',
`usage` int unsigned NOT NULL DEFAULT '0' COMMENT '使用量',
`max_size` int unsigned NOT NULL DEFAULT '0' COMMENT '单个配置大小上限,单位为字节,0表示使用默认值',
`max_aggr_count` int unsigned NOT NULL DEFAULT '0' COMMENT '聚合子配置最大个数,,0表示使用默认值',
`max_aggr_size` int unsigned NOT NULL DEFAULT '0' COMMENT '单个聚合数据的子配置大小上限,单位为字节,0表示使用默认值',
`max_history_count` int unsigned NOT NULL DEFAULT '0' COMMENT '最大变更历史数量',
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_group_id` (`group_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8_bin COMMENT='集群、各Group容量信息表';
-- 正在导出表 nacos.group_capacity 的数据:~0 rows (大约)
DELETE FROM `group_capacity`;
-- 导出 表 nacos.his_config_info 结构
DROP TABLE IF EXISTS `his_config_info`;
CREATE TABLE IF NOT EXISTS `his_config_info` (
`id` bigint unsigned NOT NULL,
`nid` bigint unsigned NOT NULL AUTO_INCREMENT,
`data_id` varchar(255) COLLATE utf8_bin NOT NULL,
`group_id` varchar(128) COLLATE utf8_bin NOT NULL,
`app_name` varchar(128) COLLATE utf8_bin DEFAULT NULL COMMENT 'app_name',
`content` longtext COLLATE utf8_bin NOT NULL,
`md5` varchar(32) COLLATE utf8_bin DEFAULT NULL,
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`src_user` text COLLATE utf8_bin,
`src_ip` varchar(50) COLLATE utf8_bin DEFAULT NULL,
`op_type` char(10) COLLATE utf8_bin DEFAULT NULL,
`tenant_id` varchar(128) COLLATE utf8_bin DEFAULT '' COMMENT '租户字段',
`encrypted_data_key` text COLLATE utf8_bin NOT NULL COMMENT '秘钥',
PRIMARY KEY (`nid`),
KEY `idx_gmt_create` (`gmt_create`),
KEY `idx_gmt_modified` (`gmt_modified`),
KEY `idx_did` (`data_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8_bin COMMENT='多租户改造';
-- 正在导出表 nacos.his_config_info 的数据:~0 rows (大约)
DELETE FROM `his_config_info`;
-- 导出 表 nacos.permissions 结构
DROP TABLE IF EXISTS `permissions`;
CREATE TABLE IF NOT EXISTS `permissions` (
`role` varchar(50) COLLATE utf8mb4_general_ci NOT NULL,
`resource` varchar(255) COLLATE utf8mb4_general_ci NOT NULL,
`action` varchar(8) COLLATE utf8mb4_general_ci NOT NULL,
UNIQUE KEY `uk_role_permission` (`role`,`resource`,`action`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- 正在导出表 nacos.permissions 的数据:~0 rows (大约)
DELETE FROM `permissions`;
-- 导出 表 nacos.roles 结构
DROP TABLE IF EXISTS `roles`;
CREATE TABLE IF NOT EXISTS `roles` (
`username` varchar(50) COLLATE utf8mb4_general_ci NOT NULL,
`role` varchar(50) COLLATE utf8mb4_general_ci NOT NULL,
UNIQUE KEY `idx_user_role` (`username`,`role`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- 正在导出表 nacos.roles 的数据:~1 rows (大约)
DELETE FROM `roles`;
INSERT INTO `roles` (`username`, `role`) VALUES
('nacos', 'ROLE_ADMIN');
-- 导出 表 nacos.tenant_capacity 结构
DROP TABLE IF EXISTS `tenant_capacity`;
CREATE TABLE IF NOT EXISTS `tenant_capacity` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`tenant_id` varchar(128) COLLATE utf8_bin NOT NULL DEFAULT '' COMMENT 'Tenant ID',
`quota` int unsigned NOT NULL DEFAULT '0' COMMENT '配额,0表示使用默认值',
`usage` int unsigned NOT NULL DEFAULT '0' COMMENT '使用量',
`max_size` int unsigned NOT NULL DEFAULT '0' COMMENT '单个配置大小上限,单位为字节,0表示使用默认值',
`max_aggr_count` int unsigned NOT NULL DEFAULT '0' COMMENT '聚合子配置最大个数',
`max_aggr_size` int unsigned NOT NULL DEFAULT '0' COMMENT '单个聚合数据的子配置大小上限,单位为字节,0表示使用默认值',
`max_history_count` int unsigned NOT NULL DEFAULT '0' COMMENT '最大变更历史数量',
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_tenant_id` (`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8_bin COMMENT='租户容量信息表';
-- 正在导出表 nacos.tenant_capacity 的数据:~0 rows (大约)
DELETE FROM `tenant_capacity`;
-- 导出 表 nacos.tenant_info 结构
DROP TABLE IF EXISTS `tenant_info`;
CREATE TABLE IF NOT EXISTS `tenant_info` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT 'id',
`kp` varchar(128) COLLATE utf8_bin NOT NULL COMMENT 'kp',
`tenant_id` varchar(128) COLLATE utf8_bin DEFAULT '' COMMENT 'tenant_id',
`tenant_name` varchar(128) COLLATE utf8_bin DEFAULT '' COMMENT 'tenant_name',
`tenant_desc` varchar(256) COLLATE utf8_bin DEFAULT NULL COMMENT 'tenant_desc',
`create_source` varchar(32) COLLATE utf8_bin DEFAULT NULL COMMENT 'create_source',
`gmt_create` bigint NOT NULL COMMENT '创建时间',
`gmt_modified` bigint NOT NULL COMMENT '修改时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_tenant_info_kptenantid` (`kp`,`tenant_id`),
KEY `idx_tenant_id` (`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8_bin COMMENT='tenant_info';
-- 正在导出表 nacos.tenant_info 的数据:~0 rows (大约)
DELETE FROM `tenant_info`;
-- 导出 表 nacos.users 结构
DROP TABLE IF EXISTS `users`;
CREATE TABLE IF NOT EXISTS `users` (
`username` varchar(50) COLLATE utf8mb4_general_ci NOT NULL,
`password` varchar(500) COLLATE utf8mb4_general_ci NOT NULL,
`enabled` tinyint(1) NOT NULL,
PRIMARY KEY (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- 正在导出表 nacos.users 的数据:~1 rows (大约)
DELETE FROM `users`;
INSERT INTO `users` (`username`, `password`, `enabled`) VALUES
('nacos', '$2a$10$EuWPZHzz32dJN7jexM34MOeYirDdFAZm2kuWj7VEOJhhZkDrxfvUu', 1);
/*!40103 SET TIME_ZONE=IFNULL(@OLD_TIME_ZONE, 'system') */;
/*!40101 SET SQL_MODE=IFNULL(@OLD_SQL_MODE, '') */;
/*!40014 SET FOREIGN_KEY_CHECKS=IFNULL(@OLD_FOREIGN_KEY_CHECKS, 1) */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40111 SET SQL_NOTES=IFNULL(@OLD_SQL_NOTES, 1) */;
服务注册
服务注册步骤如下:
- 引入nacos discovery服务:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
- 配置nacos
spring:
application:
name: item-service # 服务名称
cloud:
nacos:
server-addr: 192.168.179.1:8848 # nacos地址
配置好两个文件我们直接重启端口去nacos就会发现注册中心有服务了
如果我们想多服务接口的话需要在idea里面在添加一个,点击鼠标右键或者Ctrl+D
点击add VM options
在配置好端口号即可
启动好就会在nacos服务中看到两个端口
服务发现
我直接
private final DiscoveryClient discoveryClient;
private void handleCartItems(List<CartVO> vos) {
// 1.获取商品id
Set<Long> itemIds = vos.stream().map(CartVO::getItemId).collect(Collectors.toSet());
// 2.查询商品
// List<ItemDTO> items = itemService.queryItemByIds(itemIds);
// 2.1 根据服务名称获取服务列表
List<ServiceInstance> instances = discoveryClient.getInstances("item-service");
if (CollUtils.isEmpty(instances)) {
return;
}
// 2.2 负载均衡
ServiceInstance serviceInstance = instances.get(RandomUtil.randomInt(instances.size()));
ResponseEntity<List<ItemDTO>> response = restTemplate.exchange(
serviceInstance.getUri() + "/items?ids={ids}",
HttpMethod.GET,
null,
new ParameterizedTypeReference<>() {
},
Map.of("ids", CollUtils.join(itemIds, ","))
);
if(!response.getStatusCode().is2xxSuccessful()) {
return;
}
List<ItemDTO> items = response.getBody();
if (CollUtils.isEmpty(items)) {
return;
}
// 3.转为 id 到 item的map
Map<Long, ItemDTO> itemMap = items.stream().collect(Collectors.toMap(ItemDTO::getId, Function.identity()));
// 4.写入vo
for (CartVO v : vos) {
ItemDTO item = itemMap.get(v.getItemId());
if (item == null) {
continue;
}
v.setNewPrice(item.getPrice());
v.setStatus(item.getStatus());
v.setStock(item.getStock());
}
}
Openfeign
快速入门
为什么要使用openfeign呢?首先我们来分析之前写的代码
通过图我们会发现这样子太繁琐了,什么都要自己写,能不能直接自己获取呢?所以我们可以使用到Openfeign
Openfeign
已经被SpringCloud
自动装配,实现起来非常简单
- 引入依赖
<!--openFeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--负载均衡-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
- 通过
@EnableFeignCilents
注解,启动Openfeign
功能
@SpringBootApplication
@EnableFeignClients
public class CardApplication {
public static void main(String[] args) {
SpringApplication.run(CardApplication.class, args);
}
- 在
<font style="color:#DF2A3F;">card-service</font>
模块中创建一个Client
包编写一个FeignClient
接口
@FeignClient("item-service") // 服务名称
public interface ItemClient {
@GetMapping("/items") // 请求路径
List<ItemDTO> queryItemByIds(@RequestParam("ids")Collection<Long> ids); // 请求
}
- 在服务层去注入
ItemClient
这个接口
private final ItemClient itemClient;
private void handleCartItems(List<CartVO> vos) {
// 1.获取商品id
Set<Long> itemIds = vos.stream().map(CartVO::getItemId).collect(Collectors.toSet());
// 调用
List<ItemDTO> items = itemClient.queryItemByIds(itemIds);
if (CollUtils.isEmpty(items)) {
return;
}
// 3.转为 id 到 item的map
Map<Long, ItemDTO> itemMap = items.stream().collect(Collectors.toMap(ItemDTO::getId, Function.identity()));
// 4.写入vo
for (CartVO v : vos) {
ItemDTO item = itemMap.get(v.getItemId());
if (item == null) {
continue;
}
v.setNewPrice(item.getPrice());
v.setStatus(item.getStatus());
v.setStock(item.getStock());
}
}
连接池
为什么使用连接池呢?回答这个问题就不得不去看Openfeign的源码了,看源码很痛苦但只有看过了才能懂的
我们可以打断点调试一下,我们会发现我们之前创建的ItemClient
接口并没有去实现它,我们直接能拿到这个对象说明它是一个代理对象,如图我们都知道代理对象都是由InvocationHandler
实现的我们去FeignInvocationHandler
实现类去看看
我们都知道代理对象最终实现都是走invoke
方法,所以我们直接跟入这个invoke方法。
跟入进来之后,我们发现如图所示,这些东西都是我们之前ItemClient
接口里面获取来得到这个请求,但是这个请求路径还差一点,差目标服务器的IP地址以及端口,所以我们还要接着往下看我进入到executeAndDecode
这个方法里面
如图进入方法里面之后,我们发现路径请求是服务器的名称,而这个名称是无法发送的继续往下看,跟入到client.execute(request, options);
我们跟入进来之后,我们会发现下面这段代码跟我们之前写的有点熟系,我们可以来分析一下这段代码了
我们往下走会发现把路劲已经拼接好了,但是还是服务名称我们继续往下走
走到ServiceInstance instance = this.loadBalancerClient.choose(serviceId, lbRequest);
这一步我们会发现它已经获取到我们服务的端口了,负载均衡成功了。
所以接下来这段代码,把获取到的IP和端口来替换之前的服务url,交给delegate
去发送
而这个delegate
是一个Client,而这个Clint是一个接口专门用来发送Http请求的接口
而这个底层发送请求的底层逻辑采用了HttpURLConnection
而这个是jdk自带的请求,而这个请求每一次都要手动的去建立链接,然后利用stream流的方式去建立读写,所以它的效率是不高的。
所以接下来我们要使用连接池来替换,而连接池包括以下三种。
- HttpURLConnection:默认实现,不支持连接池
- Apache HttpClinet:支持连接池
- OKHttp:支持连接池
而我们使用OKHttp,使用步骤如下在card-service
模块中
- 引入依赖
<!--ok-http-->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
</dependency>
- 开启连接池功能
feign:
okhttp:
enabled: true # 开启连接池
最佳实践
我们之前在cart-service
中创建了一个client
创建了一个itemclient
,发现这样并解耦。所以我们要在进行拆分,有两种拆分模式
- 在模块下面再次拆分,拆成几个子模块,这样子就是会麻烦,但是很解耦。
- 在创建一个公共模块,但这种就是代码耦合度增加了
我们这个项目比较聚合,所以我们使用第二种
- 创建一个hm-api模块
- 将card-service模块的依赖剪切到hm-api中
<dependencies>
<!--openFeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--负载均衡-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<!--ok-http-->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
</dependency>
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
<version>1.6.6</version>
<scope>compile</scope>
</dependency>
</dependencies>
- 更改目录
- 去card-service中导入api依赖
<!--导入api依赖-->
<dependency>
<groupId>com.heima</groupId>
<artifactId>hm-api</artifactId>
<version>1.0.0</version>
</dependency>
- 修改card-service中业务,现在业务中是无法扫描到的,因为包路径不一致,当定义的FeignClient不在SpringBootApplication的扫描包范围时,这些FeignClient无法使用。有两种方式解决:
// 指定所在包
@EnableFeignClients(basePackages = "com.hmall.api.client")
// 指定字节码
@EnableFeignClients(client = {ItemClient.class})
日志
OpenFeign只会在FeignClient所在包的日志级别为DEBUG
时,才会输出日志。而且其日志级别有4级
- NONE: 不记录任何日志信息,这是默认值。
- BASIC:仅记录请求的方法,URL以及响应状态码和执行时间
- HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息
- FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据
由于Feign默认的日志级别就是NONE,所以默认我们看不到请求日志
要自定义日志需要声明一个类型为Logger.LeveL
的Bean,在其中定义日志级别
/**
* Openfeign日志类 配置
*/
public class DefaultFeignConfig {
@Bean
public Logger.Level feignLogLevel() {
return Logger.Level.FULL;
}
}
但是这个Bean并没有生效,要想配置某个FeignClient的日志,可以在@FeignClient注解中声明:
@FeignClient(value = "item-service", configuration = DefaultFeignConfig.class)
全局配置
@EnableFeignClients(basePackages = "com.hmall.api.client", defaultConfiguration = DefaultFeignConfig.class)
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步