SpringCloud
基本概念
系统架构
我们之前做的所有的项目都属于单体架构,下面我们将要学习更适合大型项目的分布式架构
单体架构: 将业务的所有功能几种在一个项目中开发,打成一个包部署。
优点:架构简单、部署成本低 缺点:耦合度高
分布式架构: 根据业务功能对系统进行拆分、每个业务模块作为独立项目开发,称为一个服务。
优点:降低服务耦合、有利于服务升级拓展。 缺点:架构复杂、运维、监控、部署难度高。
微服务
微服务是一种经过良好的架构设计的分布式架构方案。
微服务架构特征:
- 单一职责:微服务拆分粒度更小,每一个服务都对应唯一的业务能力,做到单一职责。
- 面向服务:微服务对外暴露业务接口
- 自治:团队独立,技术独立,数据独立,部署独立。
- 隔离性强:服务调用做好隔离、容错、降级,避免出现
级联问题。
级联问题常涉及数据的关联性操作引发的一系列连锁变化。
微服务结构:
最知名的两种技术架构:SpringCloud、阿里巴巴Dubbo
SpringCloud集成了各种微服务功能组件。
并基于SpringBoot实现了这些组件的自动装配,从而提供了良好的体验。
服务的拆分总结:
- 不同微服务,不能重复开发相同业务。
- 微服务数据独立,不能访问其他微服务的数据库。
- 微服务将自己的业务暴露为接口,供其他微服务使用。
远程调用
案例:有两个服务分别是用户服务和订单服务,要求根据订单ID查询订单的同时,把订单所属的用户信息一起返回。
前面知道不同服务之间数据库互相独立,所以我们就只能在后端再发一次http请求,去调用其他服务的接口。
这就需要在Java代码中,发起HTTP请求,此处使用 RestTemplate
RestTemplate 是 Spring 框架提供的一个同步的 HTTP 客户端工具,用于在 Java 应用程序中发送 HTTP 请求并处理响应。
// 我们需要在配置类中声明一个Bean,启动类也属于配置类,所以此方法也可写入启动类中。
/**
* 创建RestTemplate并注入Spring容器
* @return
*/
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
然后就可以在Java代码中发送请求了
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private RestTemplate restTemplate;
public Order queryOrderById(Long orderId) {
// 1.查询订单
Order order = orderMapper.findById(orderId);
String url = "http://localhost:8081/user/" + order.getUserId();
//使用RestTemplate发送请求,第一个参数为请求地址,第二个参数为序列化返回对象
User user = restTemplate.getForObject(url, User.class);
order.setUser(user);
// 4.返回
return order;
}
}
提供者与消费者
- 服务提供者:一次业务中,被其它微服务调用的服务。(提供接口给其它微服务)
- 服务消费者:一次业务中,调用其它微服务的服务。(调用其它微服务提供的接口)
服务调用关系:
- 服务提供者:暴露接口给其他微服务调用
- 服务消费者:调用其他微服务提供的接口
- 提供者与消费者角色是相对的
- 一个服务可以同时是服务提供者和服务消费者
Eureka注册中心
前面书写存在一些问题,有硬编码的部分,请求地址是写死了。
Eureka作用
消费者该如何获取服务提供者具体信息?
- 服务提供者启动时向eureka注册自己的信息
- eureka保存这些信息
- 消费者根据服务名称向eureka拉取提供者信息
如果有多个服务提供者,消费者该如何选择?
- 服务消费者利用负载均衡算法,从服务列表中挑选一个
消费者如何感知服务提供者健康状态?
- 服务提供者会每隔30秒向
EurekaServer发送心跳请求,报告健康状态eureka会 - 更新记录服务列表信息,心跳不正常会被剔除
- 消费者就可以拉取到最新的信息
在Eureka架构中,微服务角色有两类:
EurekaServer : 服务端,注册中心
- 记录服务信息
- 心跳监控
EurekaClient : 客户端
- Provider:服务提供者,例如案例中的user-service
- 注册自己的信息到 Eureka Server
- 每隔30秒向Eureka Server发送心跳
- consumer:服务消费者,例如案例中的order-service
- 根据服务名称从Eureka Server拉取服务列表
- 基于服务列表做负载均衡,选中一个微服务后发起远程调用
搭建Eureka Server服务端
第一步:创建新的Maven模块,引入eureka-server依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
第二步:为启动类添加启动注解
@EnableEurekaServer
@SpringBootApplication
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class,args);
}
}
第三步:添加配置信息
server:
port: 10086
spring:
application:
name: eurekaserver
eureka:
client:
service-url: #eureka地址信息
defaultZone: http://127.0.0.1:10086/eureka
注册user-service
第一步:在需要注册的服务中引入eureka-client依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
第二步:在配置文件中,添加配置
spring:
application:
name: userserver
eureka:
client:
service-url: #eureka地址信息
defaultZone: http://127.0.0.1:10086/eureka
同理也可以注册order-service
复制服务,右键要copy的服务,点击Copy Configuration 起一个名称,然后在VM options中配置一个新的端口 -Dserver.port=8082
服务拉取
服务拉取是基于服务器名称获取服务列表,然后在对服务列表做负载均衡
- 修改
OrderService的代码,修改访问的url路径,用服务器名代替ip、端口
String url = "http://userservice/user/" + order.getUserId();
- 在RestTemplate的Bean中,添加
负载均衡注解
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
Ribbon负载均衡
Nacos注册中心
启动方式:
startup.cmd -m standalone
服务注册
在父工程中添加spring-cloud-alibaba的管理依赖
<!-- Nacos 管理依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.5.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
注释原有的eureka依赖,添加nacos的客户端依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
修改配置文件
spring:
application:
name: orderservice
cloud:
nacos:
server-addr: localhost:8848 #nacos服务地址
Nacos服务分级存储模型
一个服务可以有多个实例,大型公司会将实例部署在不同的服务器内。一个服务器机房就称为一个集群。 服务调用尽可能调用本地集群的服务,跨集群调用延迟较高,只有当本地集群不可访问时,再去访问其他的集群。
配置服务集群属性,修改yml
spring:
cloud:
nacos:
server-addr: localhost:8848 # Nacos 服务端地址
discovery:
cluster-name: HB # 配置集群名称,机房位置
如果想要设置优先访问同集群服务,则需要修改负载均衡的IRule
userservice: # 要做配置的微服务名称
ribbon:
NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule # 负载均衡规则
注意将user-service的权重都设置为1
小结NacosRule负载均衡策略:
- 优先选择同集群服务实例列表
- 本地集群找不到提供者,才会到其他集群找,并且会报警告
- 确定了可用实例列表后,再采用随机负载均衡挑选实例
根据权重负载均衡
实际部署中会出现这样的场景: 服务器设备性能有差异,部分实例所在机器性能较好,另一些较差,我们希望性能好的机器承担更多的用户请求
Nacos提供了权重配置来控制访问频率,权重越大访问频率越高。 在Nacos控制台可以设置实例的权重值,点击编辑按钮
小结:实例的权重控制
- Nacos控制台可以设置实例的权重值,0~1之间
- 同集群内的多个实例,权重越高被访问的频率越高
- 权重设置为0则完全不会被访问
环境隔离 - namespace
Nacos 中服务存储和数据存储的最外层都是一个名为namespace的东西,用来做最外层隔离 注意:服务只能访问当前命名空间,无法访问其他命名空间的服务
Namespace 下有 Group 下有 Service / Data
新建命名空间:在Nacos控制台中 - > 命名空间 -> 新建命名空间 在代码中修改服务到新的命名空间:
spring:
cloud:
nacos:
server-addr: localhost:8848 #nacos服务地址
discovery:
cluster-name: HB # 集群名称
namespace: xxxx # 命名空间id
Nacos环境隔离
- namespace用来做环境隔离
- 每个namespace都有唯一id
- 不同namespace下的服务不可见
临时实例与非临时实例
服务注册到Nacos时,可以选择注册为临时或非临时实例,通过下面的配置来设置
spring:
cloud:
nacos:
server-addr:
discovery:
namespace:
ephemeral: false #是否为临时实例
临时实例宕机时,会从nacos的服务列表中剔除,而非临时实例则不会
Nacos与Eureka的对比
Nacos与eureka的共同点
- 都支持服务注册和服务拉取
- 都支持服务提供者心跳方式做健康检测
Nacos与Eureka的区别
- Nacos支持服务端主动检测提供者状态:临时实例采用心跳模式,非临时实例采用主动检测模式
- 临时实例心跳不正常会被剔除,非临时实例则不会被剔除
- Nacos支持服务列表变更的消息推送模式,服务列表更新更及时
- Nacos集群默认采用AP方式,当集群中存在非临时实例时,采用CP模式;Eureka采用AP方式
AP 可用性保证 CP 一致性保证
统一配置管理
将一部分配置信息写到Nacos配置文件中,可以统一管理,并且这个文件支持热加载。
新建配置步骤:配置管理 -> 配置列表 -> 新建配置 Data ID(配置文件名id) 命名规范:服务名称-dev(profile运行环境).yaml Group(分组) :DEFAULT_GROUP
原先后端启动的步骤是:
- 项目启动
- 读取本地配置application.yml
- 创建spring容器
- 加载bean
现在要从Nacos中读取配置文件,而次操作必须要在读取本地配置文件之前。 那么获取Nacos地址的配置就要在application.yml之前进行 所以需要建立一个优先级比application.yml优先级更高的配置文件:bootstrap.yml并将Nacos地址的配置信息写进去。
操作 第一步:引入Nacos的配置管理客户端依赖
<!--Nacos 客户端依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--Nacos 配置管理依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
第二步:新建bootstrap.yml引导文件,将服务名,nacos地址,环境,等信息写到里面
spring:
application:
name: userservice # 服务名称
profiles:
active: dev #环境
cloud:
nacos:
server-addr: localhost:8848 # Nacos 地址
config:
file-extension: yaml # 文件后缀
discovery:
cluster-name: HB # 集群名称
第三步:后端想要获取该配置信息,可以通过@Value("${}")的方式获取
@Value("${pattern.dateformat}")
private String dateformat;
/**
* 获取当前时间
* @return
*/
@GetMapping("/now")
public String now(){
return LocalDateTime.now().format(DateTimeFormatter.ofPattern(dateformat));
}
配置热更新
Nacos中的配置文件变更后,微服务无需重启就可以感知。不过需要通过下面两种配置实现:
方式一:在@Value注入的变量所在类上添加注解@RefreshScope
@Slf4j
@RestController
@RequestMapping("/user")
@RefreshScope
public class UserController {
@Value("${pattern.dateformat}")
private String dateformat;
/**
* 获取当前时间
* @return
*/
@GetMapping("/now")
public String now(){
return LocalDateTime.now().format(DateTimeFormatter.ofPattern(dateformat));
}
}
方式二:新建一个配置类,使用@ConfigurationProperties注解
@Component
@Data
@ConfigurationProperties(prefix = "pattern")
public class PatternProperties {
private String dateformat;
}
注意:
- 不是所有的配置都适合放到配置中心,维护比较麻烦
- 建议将一些关键参数,需要运行时调整的参数放到Nacos配置中心,一般都是自定义配置。
多环境配置共享
此处暂时省略
Nacos集群搭建
第一步:搭建数据库 推荐使用MySQL,建立一个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 since 2.5.0 */
/******************************************/
CREATE TABLE `config_info_gray` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'id',
`data_id` varchar(255) NOT NULL COMMENT 'data_id',
`group_id` varchar(128) NOT NULL COMMENT 'group_id',
`content` longtext NOT NULL COMMENT 'content',
`md5` varchar(32) DEFAULT NULL COMMENT 'md5',
`src_user` text COMMENT 'src_user',
`src_ip` varchar(100) DEFAULT NULL COMMENT 'src_ip',
`gmt_create` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT 'gmt_create',
`gmt_modified` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT 'gmt_modified',
`app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',
`tenant_id` varchar(128) DEFAULT '' COMMENT 'tenant_id',
`gray_name` varchar(128) NOT NULL COMMENT 'gray_name',
`gray_rule` text NOT NULL COMMENT 'gray_rule',
`encrypted_data_key` varchar(256) NOT NULL DEFAULT '' COMMENT 'encrypted_data_key',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_configinfogray_datagrouptenantgray` (`data_id`,`group_id`,`tenant_id`,`gray_name`),
KEY `idx_dataid_gmt_modified` (`data_id`,`gmt_modified`),
KEY `idx_gmt_modified` (`gmt_modified`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='config_info_gray';
/******************************************/
/* 表名称 = 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 '密钥',
`publish_type` varchar(50) DEFAULT 'formal' COMMENT 'publish type gray or formal',
`gray_name` varchar(50) DEFAULT NULL COMMENT 'gray name',
`ext_info` longtext DEFAULT NULL COMMENT 'ext info',
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
);
第二步:下载并且解压nacos 在conf目录下,有配置文件cluster.conf,每行配置成ip(三个或以上节点)
#it is ip ip:port
#example
192.168.16.101:8847
192.168.16.102
192.168.16.103
第三步:修改配置文件nacos/conf/application.properties
增加支持MySQL数据源配置,添加MySQL数据源的url、用户名和密码。
spring.sql.init.platform=mysql
db.num=1
db.url.0=jdbc:mysql://${mysql_host}:${mysql_port}/${nacos_database}?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true
db.user=${mysql_user}
db.password=${mysql_password}
开启默认鉴权插件(可选,推荐)
nacos.core.auth.enabled=true
nacos.core.auth.system.type=nacos
nacos.core.auth.plugin.nacos.token.secret.key=${自定义,保证所有节点一致}
nacos.core.auth.server.identity.key=${自定义,保证所有节点一致}
nacos.core.auth.server.identity.value=${自定义,保证所有节点一致}
第四步:启动Nacos集群 每个节点都执行以下命令
# Linux/Unix/Mac
sh startup.sh
# Ubuntu
bash startup.sh
# Windows
startup.cmd
第五步:使用Nginx反向代理,修改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 1ocalhost;
location /nacos {
proxy_pass http://nacos-cluster;
}
}
最后一步:修改Java代码
spring:
application:
name: userservice # 服务名称
profiles:
active: dev #环境
cloud:
nacos:
#现在Nacos地址改为Nginx反向代理的80端口
server-addr: localhost:80 # Nacos 地址
config:
file-extension: yaml # 文件后缀
discovery:
cluster-name: HB # 集群名称
Feign
RestTemplet存在的问题
String url = "http: //userservice/user/" + order.getUserId();
User user = restTemplate.getFor0bject(url, User.class);
- 可读性差
- 参数复杂
- URL难以维护
Feign声明式的Http客户端,官方地址:https://github.com/OpenFeign/feign
使用案例
- 引入坐标
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
- 在需要发起请求的服务的启动类添加注解开启Feign的功能
@EnableFeignClients
- 编写Feign客户端
@FeignClient("userservice") //指明服务名称
public interface UserClient {
@GetMapping("/user/{id}") //请求类型和请求路径
User findById(@PathVariable("id") Long id); //返回值与请求参数
}
- 使用声明出的方法代替原始的RestTemplate
@Autowired
private UserClient userClient;
public Order queryOrderById(Long orderId) {
// 1.查询订单
Order order = orderMapper.findById(orderId);
/*
String url = "http://userservice/user/" + order.getUserId();
//使用RestTemplate发送请求
User user = restTemplate.getForObject(url, User.class);
*/
User user = userClient.getUser(order.getUserId());
order.setUser(user);
// 4.返回
return order;
}
可以发现简化很多,并且代码十分美观
Feigen的自定义配置
| 配置项 | 描述 | 说明 |
|---|---|---|
feign.Logger.Level | 修改日志级别 | NONE、BASIC、HEADERS、FULL |
feign.codec.Decoder | 响应结果的解析器 | 默认解析 JSON 字符串为 Java 对象,可自定义解析器 |
feign.codec.Encoder | 请求参数编码 | 默认将请求参数编码为 HTTP 请求格式,可自定义编码器 |
feign.Contract | 支持的注解格式 | 默认支持 SpringMVC 注解,可自定义注解格式 |
feign.Retryer | 失败重试机制 | 默认无重试机制(但会使用 Ribbon 的重试),可自定义重试策略 |
一般只需要配置日志级别
配置文件写法:
#全局生效:
feign:
client:
config:
default: #这里配置全局生效
loggerLevel: FULL
# 或者
userservice: #这里配置局部生效
loggerLevel: full
通过配置类来配置Feign
//声明一个类,并且在其中声明一个Bean
public class FeignClientConfigurationP{
@Bean
public Logger.Level feignLogLevel(){
return Logger.Level.BASIC;
}
}
/*
如果是全局配置,则吧它放到@EnableFeignClients这个注解中(启动类上)
@EnableFeignclients(defaultConfiguration = FeignclientConfiguration.class)
如果是局部配置,则把它放到@FeignClient这个配置类中(配置类上)
@FeignClient(value = "userservice", configuration = FeignClientConfiguration.class)
*/
性能优化
Feign底层客户端实现
- URLConnection:默认实现,不支持连接池
- Apache Httpclient :支持连接池
- OKHttp:支持连接池
因此优化Feign的性能主要包括:
- 使用连接池代替默认的URLConnection
- 日志级别,使用basic或none
此处以添加HttpClient的支持举例
- 导坐标
<dependency>
<groupid>io.github. openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>
- 配置连接池
feign:
client:
config:
default: #default全局的配置
loggerLevel: BASIC #日志级别,BASIC就是基本的请求和响应信息
httpclient:
enabled: true #开启feign对HttpClient的支持
max-connections: 200 #最大的连接数
max-connections-per-route: 50 #每个路径的最大连接数
实际项目中的使用方式
将FeignClient抽取为独立模块,并且把接口有关的POJO、默认的Feign配置都放到这个模块当中提供给消费者使用。
- 创建一个feign-api的module,引入feign的依赖
- 将order-service中编写的UserClient、User、DefaultFeignConfiguration都复制到feign-api项目中
- 在order-service中引入feign-api的依赖
- 修改order-service中的所有与上述三个组件有关的import部分,改成导入feign-api中的包
修改后存在的问题:自定义的FeignClient不再SpringBootApplication扫描包范围时,有两种解决方法: 1.指定FeignClient所在包
@EnableFeignClients(basePackages = 'cn.itcast.feign.clients')
2.指定FeignClient字节码 //更推荐
@EnableFeignClients(clients = {UserClient.class})
