1 服务拆解和治理
1.1 服务拆解
微服务的核心就是服务拆分,将传统的大项目拆分为多个微型服务(服务或微服务),实现服务之间"高内聚(微服务职责单一),低耦合(微服务功能相对独立)"的目的
(1) 水平(横向)拆分:先搭出拆分框架,比如【公共服务】(比如:common服务,client服务...)可以作为一层,【业务微服务】可以作为一层
(2) 垂直(竖向)拆分:在水平拆分的基础上,对某一层继续拆分
1.2 服务治理
一旦服务做了拆分,各个服务之间就有了隔离,导致无法直接相互调用,而实现服务之间的相互调用就涉及到服务治理,服务治理包含3个角色:
注册中心:记录并监控各个微服务实例状态(心跳机制),推送服务变更信息
服务提供者:暴露服务接口,供其他微服务调用,当某个提供者有多个实例时,可以利用负载均衡算法,从多实例中选择一个实例提供服务
服务消费者:调用其他微服务接口(提供者和消费者可以同时兼顾)
2 注册中心(Nacos)
它是阿里巴巴的产品,目前已加入SpringCloudAlibaba中
本文介绍的是基于Docker部署的Nacos
2.1 配置数据库(mysql)
新建nacos数据库,nacos数据库中新建下表:
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/******************************************/
/* 表名称 = config_info */
/******************************************/
CREATE TABLE `config_info` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
`data_id` varchar(255) NOT NULL COMMENT 'data_id',
`group_id` varchar(128) DEFAULT NULL COMMENT 'group_id',
`content` longtext NOT NULL COMMENT 'content',
`md5` varchar(32) 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 COMMENT 'source user',
`src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip',
`app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',
`tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',
`c_desc` varchar(256) DEFAULT NULL COMMENT 'configuration description',
`c_use` varchar(64) DEFAULT NULL COMMENT 'configuration usage',
`effect` varchar(64) DEFAULT NULL COMMENT '配置生效的描述',
`type` varchar(64) DEFAULT NULL COMMENT '配置的类型',
`c_schema` text COMMENT '配置的模式',
`encrypted_data_key` varchar(1024) NOT NULL DEFAULT '' COMMENT '密钥',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_configinfo_datagrouptenant` (`data_id`,`group_id`,`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info';
/******************************************/
/* 表名称 = config_info_aggr */
/******************************************/
CREATE TABLE `config_info_aggr` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
`data_id` varchar(255) NOT NULL COMMENT 'data_id',
`group_id` varchar(128) NOT NULL COMMENT 'group_id',
`datum_id` varchar(255) NOT NULL COMMENT 'datum_id',
`content` longtext NOT NULL COMMENT '内容',
`gmt_modified` datetime NOT NULL COMMENT '修改时间',
`app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',
`tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_configinfoaggr_datagrouptenantdatum` (`data_id`,`group_id`,`tenant_id`,`datum_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='增加租户字段';
/******************************************/
/* 表名称 = config_info_beta */
/******************************************/
CREATE TABLE `config_info_beta` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
`data_id` varchar(255) NOT NULL COMMENT 'data_id',
`group_id` varchar(128) NOT NULL COMMENT 'group_id',
`app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',
`content` longtext NOT NULL COMMENT 'content',
`beta_ips` varchar(1024) DEFAULT NULL COMMENT 'betaIps',
`md5` varchar(32) 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 COMMENT 'source user',
`src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip',
`tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',
`encrypted_data_key` varchar(1024) NOT NULL DEFAULT '' COMMENT '密钥',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_configinfobeta_datagrouptenant` (`data_id`,`group_id`,`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info_beta';
/******************************************/
/* 表名称 = config_info_tag */
/******************************************/
CREATE TABLE `config_info_tag` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
`data_id` varchar(255) NOT NULL COMMENT 'data_id',
`group_id` varchar(128) NOT NULL COMMENT 'group_id',
`tenant_id` varchar(128) DEFAULT '' COMMENT 'tenant_id',
`tag_id` varchar(128) NOT NULL COMMENT 'tag_id',
`app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',
`content` longtext NOT NULL COMMENT 'content',
`md5` varchar(32) 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 COMMENT 'source user',
`src_ip` varchar(50) 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=utf8 COLLATE=utf8_bin COMMENT='config_info_tag';
/******************************************/
/* 表名称 = config_tags_relation */
/******************************************/
CREATE TABLE `config_tags_relation` (
`id` bigint(20) NOT NULL COMMENT 'id',
`tag_name` varchar(128) NOT NULL COMMENT 'tag_name',
`tag_type` varchar(64) DEFAULT NULL COMMENT 'tag_type',
`data_id` varchar(255) NOT NULL COMMENT 'data_id',
`group_id` varchar(128) NOT NULL COMMENT 'group_id',
`tenant_id` varchar(128) DEFAULT '' COMMENT 'tenant_id',
`nid` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'nid, 自增长标识',
PRIMARY KEY (`nid`),
UNIQUE KEY `uk_configtagrelation_configidtag` (`id`,`tag_name`,`tag_type`),
KEY `idx_tenant_id` (`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_tag_relation';
/******************************************/
/* 表名称 = group_capacity */
/******************************************/
CREATE TABLE `group_capacity` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`group_id` varchar(128) NOT NULL DEFAULT '' COMMENT 'Group ID,空字符表示整个集群',
`quota` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '配额,0表示使用默认值',
`usage` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '使用量',
`max_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个配置大小上限,单位为字节,0表示使用默认值',
`max_aggr_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '聚合子配置最大个数,,0表示使用默认值',
`max_aggr_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个聚合数据的子配置大小上限,单位为字节,0表示使用默认值',
`max_history_count` int(10) 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=utf8 COLLATE=utf8_bin COMMENT='集群、各Group容量信息表';
/******************************************/
/* 表名称 = his_config_info */
/******************************************/
CREATE TABLE `his_config_info` (
`id` bigint(20) unsigned NOT NULL COMMENT 'id',
`nid` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'nid, 自增标识',
`data_id` varchar(255) NOT NULL COMMENT 'data_id',
`group_id` varchar(128) NOT NULL COMMENT 'group_id',
`app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',
`content` longtext NOT NULL COMMENT 'content',
`md5` varchar(32) 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 COMMENT 'source user',
`src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip',
`op_type` char(10) DEFAULT NULL COMMENT 'operation type',
`tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',
`encrypted_data_key` varchar(1024) NOT NULL DEFAULT '' 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=utf8 COLLATE=utf8_bin COMMENT='多租户改造';
/******************************************/
/* 表名称 = tenant_capacity */
/******************************************/
CREATE TABLE `tenant_capacity` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`tenant_id` varchar(128) NOT NULL DEFAULT '' COMMENT 'Tenant ID',
`quota` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '配额,0表示使用默认值',
`usage` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '使用量',
`max_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个配置大小上限,单位为字节,0表示使用默认值',
`max_aggr_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '聚合子配置最大个数',
`max_aggr_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个聚合数据的子配置大小上限,单位为字节,0表示使用默认值',
`max_history_count` int(10) 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=utf8 COLLATE=utf8_bin COMMENT='租户容量信息表';
CREATE TABLE `tenant_info` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
`kp` varchar(128) NOT NULL COMMENT 'kp',
`tenant_id` varchar(128) default '' COMMENT 'tenant_id',
`tenant_name` varchar(128) default '' COMMENT 'tenant_name',
`tenant_desc` varchar(256) DEFAULT NULL COMMENT 'tenant_desc',
`create_source` varchar(32) DEFAULT NULL COMMENT 'create_source',
`gmt_create` bigint(20) NOT NULL COMMENT '创建时间',
`gmt_modified` bigint(20) 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=utf8 COLLATE=utf8_bin COMMENT='tenant_info';
CREATE TABLE `users` (
`username` varchar(50) NOT NULL PRIMARY KEY COMMENT 'username',
`password` varchar(500) NOT NULL COMMENT 'password',
`enabled` boolean NOT NULL COMMENT 'enabled'
);
CREATE TABLE `roles` (
`username` varchar(50) NOT NULL COMMENT 'username',
`role` varchar(50) NOT NULL COMMENT 'role',
UNIQUE INDEX `idx_user_role` (`username` ASC, `role` ASC) USING BTREE
);
CREATE TABLE `permissions` (
`role` varchar(50) NOT NULL COMMENT 'role',
`resource` varchar(128) NOT NULL COMMENT 'resource',
`action` varchar(8) NOT NULL COMMENT 'action',
UNIQUE INDEX `uk_role_permission` (`role`,`resource`,`action`) USING BTREE
);
2.2 Docker中部署Nacos
2.2.1 拉取Nacos镜像
docker pull nacos/nacos-server
2.2.2 配置docker-compose.yml
services:
nacos: #如果是集群模式就用nacos1,nacos2,nacos3.... #云服务器内存最低选4G的
image: nacos/nacos-server
container_name: nacos_container
restart: always #开机自启
ports:
- "8848:8848" #界面访问端口
- "9848:9848" #程序使用grpc连接的端口
- "9849:9849" #
environment:
MODE: standalone #standalone表示单机模式,集群模式用cluster
PREFER_HOST_MODE: hostname #支持IP还是域名模式(默认ip),体现在访问地址时地址栏的显示(hostname显示会更简洁一点)
#数据库用mysql
SPRING_DATASOURCE_PLATFORM: mysql
MYSQL_SERVICE_HOST: 123.123.123.123
MYSQL_SERVICE_PORT: 3306
MYSQL_SERVICE_DB_NAME: nacos
MYSQL_SERVICE_USER: root
MYSQL_SERVICE_PASSWORD: 123456
MYSQL_DB_PARAM: "characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai" # 设置mysql的连接参数
volumes:
- "/root/docker_nacos/logs:/home/nacos/logs"
networks:
- sunner_network
2.2.3 生成nacos容器
利用nacos镜像和docker-compose.yml生成nacos容器
docker compose up -d
3 服务注册
3.1 引入依赖
父工程依赖管理:
<!--spring cloud-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2022.0.4</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--spring cloud alibaba(可以管理nacos注册,发现版本)-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2022.0.0.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
子项目引入依赖:
<!--nacos"注册,发现"(被spring cloud alibaba管理版本)-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
3.2 配置application.yaml
spring:
application:
name: engineering #微服务名称
cloud:
nacos:
server-addr: 118.89.91.89:8848
3.3 访问nacos(8848端口)
nacos是根据微服务名划分组别,因此即使多个同名的微服务部署在不同地址的服务器上,也会被nacos归为一组,当调用者调用此微服务时,nacos会根据负载均衡算法,从这组微服务中取出一个供调用者去调用
4 服务发现(OpenFeign)
OpenFeign是一个声明式的http客户端,是SpringCloud在Eureka公司开源的Feign的基础上改造而来
4.1 调用微服务流程
①服务调用者去Nacos中获取某个微服务的分组列表
②微服务的分组列表中,利用"负载均衡"算法获取一个"包含HTTP的微服务请求地址"
③向获取的微服务发起http请求
4.2 引入依赖
<!--nacos"注册,发现"(被spring cloud alibaba管理版本)-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--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> <!--早期用的是Ribbon-->
</dependency>
4.3 通过@EnableFeignClients注解,开启OpenFeign功能
/**
* 启动SpringBoot项目的主入口程序
*/
@MapperScan({"xyz.aboluo.cloud_test.dao"}) //dao接口包扫描
@EnableFeignClients("xyz.aboluo.cloud_test.client") //配置包扫描,扫描不到的Client不生效,也无法通过@Autowired进行注入
@SpringBootApplication(scanBasePackages = "xyz.aboluo.cloud_test") //标记这是一个SpringBoot应用
public class MainApplication {
public static void main(String[] args) {
ConfigurableApplicationContext iocContext = SpringApplication.run(MainApplication.class, args);
// Teacher teacher = iocContext.getBean(Teacher.class);
}
}
4.4 编写FeignClient
注意事项:
①@FeignClient只会提供从nacos中获取的某个engineering实例的"IP地址+端口",不会提供"项目路径"
②需要明确注明@GetMapping或@PostMapping,不要使用@RequestMapping
③注解@PostMapping需要提供"项目路径+接口路径",拼接后就形成"IP地址+端口+项目路径+接口路径"
/*@FeignClient只会提供从nacos中获取的某个engineering实例的"IP地址+端口",不会提供"项目路径"*/
@FeignClient(value = "engineering")
public interface EngineeringClient {
/**
* 需要提供"项目路径+接口路径",拼接后就形成"IP地址+端口+项目路径+接口路径"
* 需要明确使用@GetMapping@PostMapping,不要使用@RequestMapping
*/
@PostMapping("/engineering/alWindow/queryAlWindow.do")
Result queryAlWindow(@RequestBody AlWindow alWindow);
}
4.5 使用FeignClient,实现远程调用
@RestController
@RequestMapping("/test")
public class EngineeringTestController {
@Autowired
private EngineeringClient engineeringClient;
@RequestMapping("/callEngineering.do")
public Result queryAlWindow(@RequestBody AlWindow alWindow) {
Result result = engineeringClient.queryAlWindow(alWindow);
return Result.success(result);
}
}
4.6 OpenFeign连接池
OpenFeign最终发起远程调用是通过Client,而每次调用都需要重新创建Client对象进行连接,非常消耗资源,效率低,因此需要用到连接池(可以选择Apache HttpClient或OKHttp)
4.6.1 引入依赖
<!--ok-http连接池-->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
</dependency>
4.6.2 开启连接池功能
spring:
application:
name: cloud_test #微服务名称
cloud:
nacos:
server-addr: 123.123.123.123:8848
openfeign:
okhttp:
enabled: true
5 服务拆解优化
给所有需要提供微服务接口暴露(服务提供者)的方法上增加"支持Client"的注解,提醒开发者,如果此方法发生改变时,还需要去改变对应的Client
5.1 微服务拆解问题
假设有3个微服务A、B、C,在B和C中都需要调用A,因此在B和C中都要写一个AClient供B或C的接口调用,并且要在B和C中添加Apojo
此时就有2个问题:
①重复编写AClient和Apojo
②一旦A中的接口或pojo发生变化,B和C中的AClient或Apojo都需要做修改,导致微服务之间的耦合度高
5.2 拆解优化的2种思路
5.2.1 思路1(一拆三)
单体项目拆分为多个服务a、b、c......,每个服务再拆分为3个服务:
①pojo服务:向其他微服务提供"对象关系映射(ORM)类"
②client服务:向其他微服务提供"调用本服务接口的Client"
③business服务:放置本服务的业务代码
如果a是服务提供者,b、c是服务消费者,此时b和c就需要用到a中的pojo和client,此时只需要在b服务和c服务的pom.xml中引入a_pojo和a_client的依赖(此时b的子项目b_business和c的子项目c_business也同步引入了对应依赖)
优点:①解决了重复编写pojo和client的问题 ②因为所有pojo和client都在a中进行维护,解决了高耦合问题
5.2.2 思路2(归总)
新建一个client项目:
①将所有微服务的pojo都写在此项目的pojos文件夹下的对应文件夹里
②将所有微服务的Client都写在此项目的clients文件夹里
③微服务的pom.xml中引入client的依赖
④在"服务消费者"的启动类上加上@EnableFeignClients("xyz.aboluo.client.clients")
水平拆分:
水平A:公共模块作为一层
水平B:业务模块作为一层
垂直拆分:在水平拆分的基础上继续拆分
公共模块:client模块,common模块.......
业务模块:a模块、b模块、c模块......