微服务是什么?
微服务是一种架构风格,将一个大型应用程序拆分为一组小型、自治的服务。每个服务都运行在自己独立的进程中,使用轻量级的通信机制(通常是HTTP或消息队列)进行相互之间的通信。这种方式使得每个服务可以独立开发、部署和扩展,同时也降低了整个应用程序的复杂性。微服务架构可以提高系统的可伸缩性、灵活性和可维护性,使得团队可以更快地开发和交付新的功能。
微服务项目结构图
单体的Java项目和分布式的Java项目的区别
单体项目
微服务项目
微服务结构
微服务对比
jdk1.8,。
springboot版本为:2.3.9.RELEASE。
springcloud版本为:Hoxton.SR10。
eureka的作用:作为服务中心。
1.创建eureka的注册中心
导入依赖
org.springframework.cloud
spring-cloud-starter-netflix-eureka-server
在application.yml中配置对应的注册信息
#在注册中心中的名字
spring:
application:
name: eurekaService
对应的服务端口
server:
port: 10086
因为eureka作为服务注册中心,所以也需要将自己注册到服务中心中
eureka:
client:
service-url:
defaultZone: http://localhost:10086/eureka
在启动类上添加注解驱动 @EnableEurekaServer
对应的效果图
此时服务端和客户端需要添加的依赖为下
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
在application.yml编写注册信息
#要注册到的注册中心位置
eureka:
client:
service-url:
defaultZone: http://localhost:10086/eureka
spring:
application:
name: ”对应名字“
在restTemplate中将原本对应的地址位置修改为在eureka中的要调用的服务名字
注册中心效果图为下:
在未来我们的某个模块不一定只有一个,它可能是多个相同的模块,防止因为宕机的缘故导致服务无法使用。
这里我们就复制一个相同的服务
在对用的restTemplate的配置类添加 @LoadBalanced
进行测试,在调用两次接口后发现两个服务都被调用了一次,说明实现了负载均衡。
Ribbon流程
Ribbon负载均衡流程源码
在LoadBalancerInterceptor类中会调用intercept方法,其会获取对应的host名字,通过这个host去注册中心中查找对应的服务地址。
效果图为下:
通过后序loadBalancer的execute方法,其会调用实现类下的execute方法,也就是RibbonLoadBalancerClient下的execute方法
通过getServer方法去eureka中拉去对应的名字的服务,可以得到user-service的实例,这里就是两个user-service实例。
在getServer方法中最终会调用到rule.choose方法
在该方法的作用就是选择对应的负载均衡的策略。
总共有四大种策略,默认使用轮番查询策略,也就是 RoundRobinRule。
最终只要通过添加@LoadBalanced就可以实现负载均衡。
负载均衡策略
设置策略的方法
1. 通过@Bean配置对应的策略,其作用域为全局。
@Bean
//设置为随机策略
public IRule getIRule() {
return new RandomRule();
}
2.通过在application.yml中进行配置,此方法的优点就是作用域小,可以只作用在某个服务模块上。
#对应要设置策略的服务名
user-service:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
饥饿加载
在默认情况下,只有当消费端访问对用的服务端时,服务端才会进行加载,所以在第一次访问服务端的耗时会很长。
为了提高访问效率,我们可以在项目启动时提前加载好对应的服务端,这里就可以使用饥饿加载。
配置方法为下:
ribbon:
eager-load:
enabled: true
#clients是个列表,所以可以设置多个
clients:
- user-service
这样在消费端第一次访问对应的服务端时就会大大减少消耗的时间。
nacos安装
下载并解压对应的nacos文件后,通过cmd进入nacos的bin目录,执行以下指令进行启动。
startup.cmd -m standalone
此时我们就可以访问localhost:8848/nacos,在该界面中的用户名和密码都是:nacos。
将对应的服务端和消费端的注册中心都改为nacos。
1.导入依赖
在父目录中导入的依赖为下:
com.alibaba.cloud
spring-cloud-alibaba-dependencies
2.2.5.RELEASE
pom
import
在服务端和消费端导入的依赖为下:
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-discovery
在服务端中的application.yml中配置为下:
spring:
cloud:
nacos:
server-addr: localhost:8848
启动微服务,效果图为下:
nacos服务分级存储模型
实现方法就是:将每个服务分布到不同的集群,这样就可以保证在某个集群失效时不会影响服务的使用。
例子:将所有的user-service设置到集群HZ,将order-service设置到集群BJ。
在application.yml的设置为下:
spring:
cloud:
nacos:
discovery:
#设置对应的集群名称
cluster-name: BJ
实现效果为下:
服务实例的权重设置
权重范围为:0~1,数字越大权重就越大。
在nacos中的是设置方式为下:
权重的使用的特殊情况。
当我们需要对某个模块进行升级时,我们不再需要重新发布项目,而是将当前服务的权重设置为0,这样用户访问是就不会调用当前的服务模块,用户对访问到项目功能的其他模块。
nacos设置负载均衡
在application.yml中配置以下信息:
#对应要设置策略的服务名
user-service:
ribbon:
#设置的哦负载均衡的策略
NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule
该策略为:存在多个拥有同样功能的集时,我们会先去访问,距离我们更近的集群,当该集群宕机时,才会访问其他相同功能的模块。
nacos环境隔离
id不设置的话,就会通过UUID自动生成。
在代码application.tml中设置服务的环境空间。
spring:
cloud:
nacos:
server-addr: localhost:8848
discovery:
cluster-name: BJ
namespace: 419f5dac-5806-4e04-9017-6f7bf726ba41
注意:不同环境间的服务模块是无法相互调用的,所以如果出现500异常可能就是跨环境的错误。
nacos和eureka的区别
nacos设置非临时实例的设置为下:
spring:
cloud:
nacos:
discovery:
#设置为非临时实例
ephemeral:true
nacos配置管理
在nacos中配置对应的application.yml。
配置文件的名字的格式为:[服务名]-[对应的环境名].[文件格式]。(userservice-dev.yaml)
编写效果为下:
为了配合使用线上的配置文件,所以我们需要在springboot说明服务模块对应的线上的配置文件名。
需要在对应的服务模块引入对应的依赖
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-config
在对应的服务上创建bootstrap.yaml 文件,在该文件中配置对应的线上配置文件信息。
spring:
application:
name: orderservice # 服务名称
profiles:
active: dev #开发环境,这里是dev
cloud:
nacos:
server-addr: localhost:8848 # Nacos地址
config:
file-extension: yaml # 文件后缀名
# discovery:
# namespace: 419f5dac-5806-4e04-9017-6f7bf726ba41
在controller类中编写对应的属性
启动测试。
确实读到线上的配置,但此时我们在去更改线上的配置,发现获取的name还是历史版本的值,需要我们重启对应的服务模块才会进行更新,为了实现热更新,我们提供了两种解决方案。
热更新
1. 在属性对应的类上使用就注解:@RefreshScope。
进行测试,效果图为下:
更改前:
更新后:
2.创建对用的属性类,将该属性类交给spring容器管理,实现热更新。
创建对用的属性类:OrderProperties
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties("com")
@Data
public class OrderProperties {
private String name;
}
在controller层中注入OrderProperties
import cn.itcast.order.pojo.Order;
import cn.itcast.order.pojo.OrderProperties;
import cn.itcast.order.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("order")
public class OrderController {
@Autowired
private OrderService orderService;
@Autowired
OrderProperties orderProperties;
@GetMapping("{orderId}")
public Order queryOrderByUserId(@PathVariable("orderId") Long orderId) {
// 根据id查询订单并返回
return orderService.queryOrderById(orderId);
}
@GetMapping("/test")
public String test1() {
System.out.println("线上对应的名字为:" + orderProperties.getName());
return orderProperties.getName();
}
}
测试结果和方案一效果相同。
配置共享
在某个服务模块中,该模块中会存在多个环境: dev, pro, test,这些环境的配置在nacos中都是相互隔离的,为使得各个模块都可以读到某个配置,我们可以在nacos中创建一个配置共享文件。
测试环境就是:
orderservice-dev.yaml, orderservice-pro.yaml, orderservice-test.yaml。
我们可以创建一个配置文件,该文件的名字是固定的 ,[spring.application.name].yaml。
进行测试。
让orderservice服务去读取线上模块的共享配置。
修改对应的OrderProperties。
测试结果为下:
搭建nacos集群
集群结构
搭建步骤为下:
1.在本地数据库中创建nacos需要的数据库信息。
创建一个名字为 nacos的数据库,然后导入对应的sql。
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(255) DEFAULT NULL,
`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,
`tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',
`c_desc` varchar(256) DEFAULT NULL,
`c_use` varchar(64) DEFAULT NULL,
`effect` varchar(64) DEFAULT NULL,
`type` varchar(64) DEFAULT NULL,
`c_schema` text,
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';
/******************************************/
/* 数据库全名 = nacos_config */
/* 表名称 = 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(255) 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,
`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='增加租户字段';
/******************************************/
/* 数据库全名 = nacos_config */
/* 表名称 = 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 '租户字段',
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';
/******************************************/
/* 数据库全名 = nacos_config */
/* 表名称 = 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';
/******************************************/
/* 数据库全名 = nacos_config */
/* 表名称 = 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,
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';
/******************************************/
/* 数据库全名 = nacos_config */
/* 表名称 = 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容量信息表';
/******************************************/
/* 数据库全名 = nacos_config */
/* 表名称 = his_config_info */
/******************************************/
CREATE TABLE `his_config_info` (
`id` bigint(64) unsigned NOT NULL,
`nid` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`data_id` varchar(255) NOT NULL,
`group_id` varchar(128) NOT NULL,
`app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',
`content` longtext NOT NULL,
`md5` varchar(32) DEFAULT NULL,
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`src_user` text,
`src_ip` varchar(50) DEFAULT NULL,
`op_type` char(10) DEFAULT NULL,
`tenant_id` varchar(128) 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='多租户改造';
/******************************************/
/* 数据库全名 = nacos_config */
/* 表名称 = 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,
`password` varchar(500) NOT NULL,
`enabled` boolean NOT NULL
);
CREATE TABLE `roles` (
`username` varchar(50) NOT NULL,
`role` varchar(50) NOT NULL,
UNIQUE INDEX `idx_user_role` (`username` ASC, `role` ASC) USING BTREE
);
CREATE TABLE `permissions` (
`role` varchar(50) NOT NULL,
`resource` varchar(255) NOT NULL,
`action` varchar(8) NOT NULL,
UNIQUE INDEX `uk_role_permission` (`role`,`resource`,`action`) USING BTREE
);
INSERT INTO users (username, password, enabled) VALUES ('nacos', '$2a$10$EuWPZHzz32dJN7jexM34MOeYirDdFAZm2kuWj7VEOJhhZkDrxfvUu', TRUE);
INSERT INTO roles (username, role) VALUES ('nacos', 'ROLE_ADMIN');
下载对应的nacos压缩包,解压后将conf/cluster.conf.example文件重命名为cluster.conf。
在cluster.conf中怕配置各个 nacos的地址。
startup.cmd
修改/conf/application.properties中的mysql信息
复制三份nacos,并在application.properties中配置对应的端口,启动这些nacos。
启动指令为下:
startup.cmd
2. 配置nginx进行负载均衡操作了。
修改/conf/nginx.conf的配置。
#配置对应的nacos集群的地址
upstream nacos-cluster {
server 127.0.0.1:8841;
server 127.0.0.1:8842;
server 127.0.0.1:8843;
}
#配置代理
server {
listen 80;
server_name localhost;
location /nacos {
proxy_pass http://nacos-cluster;
}
}
启动nginx,在我们使用项目的配置中nacos的地址就是localhost:80,80端口就是nginx代理后的结果。
feign的使用
在对应的服务模块引入依赖
org.springframework.cloud
spring-cloud-starter-openfeign
在启动类上夹feign的注解驱动: @EnableFeignClients
在对用服务模块中创建被调用服务对应的feign 接口
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;
@FeignClient("userservice")//被调用模块的名称
public interface orderFeignClient {
@GetMapping("/user/{id}")
public User queryOrderByUserId(@PathVariable("id") Long id);
}
将调用服务的模块中controller的代码为下
import cn.itcast.order.FeignClient.UserFeignClient;
import cn.itcast.order.mapper.OrderMapper;
import cn.itcast.order.pojo.Order;
import cn.itcast.order.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
@Service
public class OrderService {
@Resource
private OrderMapper orderMapper;
@Autowired
UserFeignClient userFeignClient;
public Order queryOrderById(Long orderId) {
// 1.查询订单
Order order = orderMapper.findById(orderId);
//通过获取的userId,调用userService获取对应的信息
User user = userFeignClient.queryOrderByUserId(order.getUserId());
System.out.println(user);
order.setUser(user);
// 4.返回
return order;
}
}
测试效果图为下
feign自定义配置
配置我们做的配置一般也就是 feign.logger.level。
logger的等级为下:
NONE:不记录任何日志信息,这是默认值。
BASIC:仅记录请求的方法,URL以及响应状态码和执行时间
HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息
FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据。
logger的配置为下:
feign:
client:
config:
default: #如果是default表示全局配置,如果是对应的服务名,也就是作用在对应的服务模块
loggerLevel: FULL
使用java代码实现logger等级的配置
创建一个类进行配置。
import feign.Logger;
import org.springframework.context.annotation.Bean;
public class orderConfiguration {
@Bean
public Logger.Level loginLevel() {
return Logger.Level.BASIC;
}
}
如果需要作用在全局,那么就在启动类上进行配置。
@MapperScan("cn.itcast.order.mapper")
@SpringBootApplication
@EnableFeignClients(defaultConfiguration = orderConfiguration.class)
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}
如果需要作用在对用的服务上,我们只需要在对应服务的FeignClient接口上配置即可。
@FeignClient(value = "userservice", configuration = orderConfiguration.class)//被调用模块的名称
public interface UserFeignClient {
@GetMapping("/user/{id}")
public User queryOrderByUserId(@PathVariable("id") Long id);
}
feign使用优化
Feign底层发起http请求,依赖于其它的框架。其底层客户端实现包括:
•URLConnection:默认实现,不支持连接池
•Apache HttpClient :支持连接池
•OKHttp:支持连接池
因此提高Feign的性能主要手段就是使用连接池代替默认的URLConnection。
实现方法为下:
引入对应的依赖
io.github.openfeign
feign-httpclient
在application.yml中配置对应的信息
feign:
httpclient:
enabled: true #开启feign对httpClient的支持
max-connections: 200 #最大的连接数
max-connections-per-route: 50 #每个路径找到的连接数
使用优化的方案
1.日志级别尽量用basic,保证日志的输出最少。
2.使用HttpClient或OKHttp代替URLConnection
① 引入feign-httpClient依赖
② 配置文件开启httpClient功能,设置连接池参数
最佳实现方案
1.继承方式。
一样的代码可以通过继承来共享:
1)定义一个API接口,利用定义方法,并基于SpringMVC注解做声明。
2)Feign客户端和Controller都集成改接口
优点:
简单。
实现了代码共享。
缺点:
服务提供方、服务消费方紧耦合。
参数列表中的注解映射并不会继承,因此Controller中必须再次声明方法、参数列表、注解。
2.将Feign单独抽取成一个包,在未来需要使用时直接导入包即可。
方案二实现步骤
创建对应的Fiegn包,将相关的feign接口和pojo,配置类都抽取到此包。
在对应的服务模块中引入feign-api包。
cn.itcast.demo
feign-api
1.0
将对应的配置进行导包,我们此时运行项目会发现userFeignClient在spring容器中找不到,这是因为Feign接口没有被项目在开始时扫描到,是可以为了让其扫描到,我们需要添加一些配置。
在@EnableFeignClients中配置配置扫描的包
@MapperScan("cn.itcast.order.mapper")
@SpringBootApplication
@EnableFeignClients(basePackages = "com.huang.FeignClient")
//如果需要指定某个class的话就使用{}进行指定
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}
进行测试,测试结果为下:
什么是Gateway:
Spring Cloud Gateway 是 Spring Cloud 的一个全新项目,该项目是基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等响应式编程和事件流技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。
Gateway的使用
创建一个新得模块,用于创建gateway。
导入相应得依赖。
org.springframework.cloud
spring-cloud-starter-gateway
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-discovery
在applicatino.yml中配置规则
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/开头就符合要求
- id: order-service
uri: lb://orderservice
predicates:
- Path=/order/**
测试结果为下:
断言规则
gateway过滤器
过滤器的作用
名称 | 说明 |
---|---|
AddRequestHeader | 给当前请求添加一个请求头 |
RemoveRequestHeader | 移除请求中的一个请求头 |
AddResponseHeader | 给响应结果中添加一个响应头 |
RemoveResponseHeader | 从响应结果中移除有一个响应头 |
RequestRateLimiter | 限制请求的流量 |
测试:在此我们测试AddRequestHeader过滤器,也就是添加请求头过滤器。
在application.yml配置过滤器
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/开头就符合要求
- id: order-service
uri: lb://orderservice
predicates:
- Path=/order/**
filters:
#添加对应的请求头, 此时请求头的名字为hello,值为:ostkaka!
- AddRequestHeader=hello, ostkaka!
进行测试。
在controller层中编写代码
@GetMapping("{orderId}")
public Order queryOrderByUserId(@PathVariable("orderId") Long orderId, @RequestHeader(value = "hello", required = false) String str) {
// 根据id查询订单并返回
System.out.println("请求头的信息未为:" + str);
return orderService.queryOrderById(orderId);
}
在我们访问orderservice模块时,就会出现以下效果图
如果需要将该请求头设置作用在所有的服务模块上,我们可以使用default-filters。
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/开头就符合要求
- id: order-service
uri: lb://orderservice
predicates:
- Path=/order/**
default-filters: #默认的过滤器配置,作用在全局
- AddRequestHeader=hello, ostkaka!
进行测试,和之前效果用于,在其他服务模块也可以获得对用的请求头。
全局过滤器
不同于default-filters,全局过滤器可以自定义过滤方式。
自定义步骤为下:
创建个类,用于实现GlobalFilter接口。
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.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.List;
@Component
@Order(0)//设置权重
public class GlobalFilterImpl implements GlobalFilter {
@Override
public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
MultiValueMap queryParams = request.getQueryParams();
List name = queryParams.get("name");
if(name.equals("admin")) {
//说明name = admin
//放行
return chain.filter(exchange);
}
//不符合条件,不放行
exchange.getResponse().setStatusCode(HttpStatus.valueOf(401));
//直接完成就不向下放行
return exchange.getResponse().setComplete();
}
}
该过滤器会直接在ioc容器中创建,直接作用在全局。
效果图为下:
过滤器的执行顺序
在网关模块中的application.yml中配置即可
spring:
cloud:
gateway:
# 。。。
globalcors: # 全局的跨域处理
add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题
corsConfigurations:
'[/**]':
allowedOrigins: # 允许哪些网站的跨域请求
- "http://localhost:8090"
allowedMethods: # 允许的跨域ajax的请求方式
- "GET"
- "POST"
- "DELETE"
- "PUT"
- "OPTIONS"
allowedHeaders: "*" # 允许在请求中携带的头信息
allowCredentials: true # 是否允许携带cookie
maxAge: 360000 # 这次跨域检测的有效期
后续使用CV即可。
雪崩问题
解决雪崩问题方案
1.超时处理
设置访问服务的超时时间,请求超过一定时间就返回错误信息,不会进行永久等待。
2.仓壁模式
船舱都会被隔板分离为多个独立空间,当船体破损时,只会导致部分空间进入,将故障控制在一定范围内,避免整个船体都被淹没。
于此类似,我们可以限定每个业务能使用的线程数,避免耗尽整个tomcat的资源,因此也叫线程隔离。
分配线程给对应的服务,如果对应二点线程耗尽后,也不会占用其他的线程,保证项目的其他模块正常运行。
3.断路器模式
由断路器统计业务执行的异常比例,如果超出阈值则会熔断该业务,拦截访问该业务的一切请求。(也就是我们未来最常使用的解决方案)
当发现对应的服务模块的异常比例过高时,断路器就会执行熔断操作,就直接无法访问异常的服务模块,直接返回错误信息,保证其他服务模块的正常运行。
限流
流量控制:限制业务访问的QPS,避免服务因流量的突增而故障。
熔断技术的对比
sentinel整合springboot
1.运行sentinel。
2.引入依赖(springcloud对应的版本为:Hoxton.SR8)
com.alibaba.cloud
spring-cloud-starter-alibaba-sentinel
3.在application.yml中配置sentinel的配置
spring:
cloud:
sentinel:
transport:
dashboard: localhost:8080 #sentinel的面板地址
访问对应的模块,进行测试
sentine就在实时监测对应模块的并发和异常情况。