一、认识微服务
1、单体架构
单体架构:将业务的所有功能集中在一个项目中开发,打成一个包部署。
优点:架构简单、部署成本低。
缺点:团队协作成本高、系统发布效率低、系统可用性差。
2、微服务架构
微服务架构:是服务化思想指导下的一套最佳实践架构方案。服务化,就是把单体架构中的功能模块拆分成多个独立的项目。
粒度小、团队自治、服务自治
官网地址:https://spring.io/projects/spring-cloud.
SpringCloud集成了各种微服务功能组件,并基于SpringBoot实现了这些组件的自动装配,从而提供了良好的开箱即用体验:
服务注册发现:Eureka、Nacos、Consul
统一配置管理:SpringCloudConfig、Nacos
服务远程调用:OpenFeign、Dubbo
统一网关路由:SpringCloudGateway、Zuul
服务链路监控:Zipkin、Sleuth
流控、降级、保护:Hystix、Sentinel
二、拆分原则
一、什么时候拆分
1、创业型项目:先采用单体架构,快速开发,快速试错。随着规模扩大,逐渐拆分。
2、确定的大型项目:资金充足,目标明确,可以直接选择微服务架构,避免后续拆分的麻烦。
二、怎么拆分
1、从拆分目标来说,要做到:
高内聚:每个微服务的职责要尽量单一,包含的业务相互关联度高、完整度高。
低耦合:每个微服务的功能要相对独立,尽量减少对其它微服务的依赖。
2、从拆分方式来说,一般包含两种方式:
纵向拆分:按照业务模块来拆分。
横向拆分:抽取公共服务,提高复用性。
三、总结
什么时候拆分微服务?
初创型公司或项目尽量采用单体项目,快速试错。随着项目发展到达一定规模再做拆分。
如何拆分微服务?
目标:高内聚、低耦合。
方式:纵向拆分、横向拆分。
拆分后碰到的第一个问题是什么,如何解决?
拆分后,某些数据在不同服务,无法直接调用本地方法查询数据。
利用RestTemplate发送Http请求,实现远程调用。
远程调用示例:
替代以前的:@AutoWired private RestTemplate restTemplate;
使用
@RequiredArgsConstructor private final RestTemplate restTemplate;
// 1.获取商品id Set<Long> itemIds = vos.stream().map(CartVO::getItemId).collect(Collectors.toSet()); // 2.查询商品 ResponseEntity<List<ItemDTO>> response = restTemplate.exchange( "http://localhost:8081/items?ids={ids}", HttpMethod.GET, null, new ParameterizedTypeReference<List<ItemDTO>>() { }, Map.of("ids",CollUtil.join(itemIds,",")) ); //解析响应 if(!response.getStatusCode().is2xxSuccessful()) { return; } List<ItemDTO> items=response.getBody(); if (CollUtils.isEmpty(items)) { return; }
三、注册中心原理
服务治理中的三个角色分别是什么?
服务提供者:暴露服务接口,供其它服务调用
服务消费者:调用其它服务提供的接口
注册中心:记录并监控微服务各实例状态,推送服务变更信息
消费者如何知道提供者的地址?
服务提供者会在启动时注册自己信息到注册中心,消费者可以从注册中心订阅和拉取服务信息
消费者如何得知服务状态变更?
服务提供者通过心跳机制向注册中心报告自己的健康状态,当心跳异常时注册中心会将异常服务剔除,并通知订阅了该服务的消费者
当提供者有多个实例时,消费者该选择哪一个?
消费者可以通过负载均衡算法,从多个实例中选择一个
Nacos注册中心
nacos配置文件custom.dev:
PREFER_HOST_MODE=hostname
MODE=standalone
SPRING_DATASOURCE_PLATFORM=mysql
MYSQL_SERVICE_HOST=192.168.64.130
MYSQL_SERVICE_DB_NAME=nacos
MYSQL_SERVICE_PORT=3306
MYSQL_SERVICE_USER=root
MYSQL_SERVICE_PASSWORD=luohai
MYSQL_SERVICE_DB_PARAM=characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
将上面的内容添加到文件中,取名随意,我这儿就取:custom.dev。然后在服务器的根目录下创建nacos文件夹,将配置文件上传至文件中。
执行docker命令:
docker run -d --name nacos --env-file ./nacos/custom.env -p 8848:8848 -p 9848:9848 -p 9849:9849 --restart=always nacos/nacos-server:v2.1.0-slim
查看日志:docker logs -f nacos
启动成功后访问:http://192.168.64.130:8848/nacos
账号密码都是:nacos
服务注册
服务注册步骤如下:
1、引入nacos discovery依赖:
<!--nacos服务注册发现--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-nacos-discovery</artifactId> <!--排除nacos中以前已经有的ribbon--> <exclusions> <exclusion> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-ribbon</artifactId> </exclusion> </exclusions>
</dependency>
2、配置Nacos地址
cloud: nacos: discovery: server-addr: 192.168.64.130:8848
服务发现
消费者需要连接nacos以拉取和订阅服务,因此服务发现的前两步与服务注册是一样,后面再加上服务调用即可:
1、引入nacos discovery依赖(同上)
2、配置nacos地址(同上)
服务发现
private final DiscoveryClient discoveryClient;
private void handleCartItems(List<CartVO> vos) {
// 1.根据服务名称,拉取服务的实例列表
List<ServiceInstance> instances = discoveryClient.getInstances("item-service");
// 2.负载均衡,挑选一个实例
ServiceInstance instance = instances.get(RandomUtil.randomInt(instances.size()));
// 3.获取实例的IP和端口
URI uri = instance.getUri();
// ... 略
}
四、OpenFeign
OpenFeign是一个声明式的http客户端,是SpringCloud在Eureka公司开源的Feign基础上改造而来。官方地址:https://github.com/OpenFeign/feign
其作用就是基于SpringMVC的常见注解,帮我们优雅的实现http请求的发送。
以前实现http调用是用如下的代码:
以上调用偏复杂,下面我们就利用OpenFeign这个微服务组件来实现简单化调用
OpenFeign已经被SpringCloud自动装配,实现起来非常简单:
1、引入依赖,包括OpenFeign和负载均衡组件SpringCloudLoadBalancer
<!--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>
2、在启动类上通过@EnableFeignClients注解,启用OpenFeign功能
@EnableFeignClients
@SpringBootApplication
public class CartApplication { // ... 略 }
3、OpenFeign已经被SpringCloud自动装配,实现起来非常简单:
3.1 编写FeignClient
@FeignClient(value = "item-service")
public interface ItemClient {
@GetMapping("/items")
List<ItemDTO> queryItemByIds(@RequestParam("ids") Collection<Long> ids);
}
3.2 使用FeignClient,实现远程调用
List<ItemDTO> items = itemClient.queryItemByIds(List.of(1,2,3));
五、连接池
OpenFeign对Http请求做了优雅的伪装,不过其底层发起http请求,依赖于其它的框架。这些框架可以自己选择,包括以下三种:
HttpURLConnection:默认实现,不支持连接池
Apache HttpClient :支持连接池
OKHttp:支持连接池
在此我选用OKHttp
1、引入依赖
<!--ok-http--> <dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-okhttp</artifactId> </dependency>
2、开启连接池功能
feign: okhttp: enabled: true # 开启OKHttp连接池支持
实例:
当定义的FeignClient不在SpringBootApplication的扫描包范围时,这些FeignClient无法使用。有两种方式解决:
指定FeignClient所在包
@EnableFeignClients(basePackages = "com.hmall.api.clients")
OpenFeign日志
OpenFeign只会在FeignClient所在包的日志级别为DEBUG时,才会输出日志。而且其日志级别有4级:
NONE:不记录任何日志信息,这是默认值。
BASIC:仅记录请求的方法,URL以及响应状态码和执行时间
HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息
FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据。
由于Feign默认的日志级别就是NONE,所以默认我们看不到请求日志。
定义日志级别
要自定义日志级别需要声明一个类型为Logger.Level的Bean,在其中定义日志级别:
public class DefaultFeignConfig {
@Bean
public Logger.Level feignLogLevel() {
return Logger.Level.FULL;
}
}
但此时这个Bean并未生效,要想配置某个FeignClient的日志,可以在@FeignClient注解中声明:
@FeignClient(value = "item-service", configuration = DefaultFeignConfig.class)
如果想要全局配置,让所有FeignClient都按照这个日志配置,则需要在@EnableFeignClients注解中声明:
@EnableFeignClients(defaultConfiguration = DefaultFeignConfig.class)
OpenFeign总结
如何利用OpenFeign实现远程调用?
引入OpenFeign和SpringCloudLoadBalancer依赖
利用@EnableFeignClients注解开启OpenFeign功能
编写FeignClient 如何配置OpenFeign的连接池?
引入http客户端依赖,例如OKHttp、HttpClient 配置yaml文件,打开OpenFeign连接池开关 OpenFeign使用的最佳实践方式是什么?
由服务提供者编写独立module,将FeignClient及DTO抽取
如何配置OpenFeign输出日志的级别?
声明类型为Logger.Level的Bean 在@FeignClient或@EnableFeignClients注解上使用
六、网关
网关:就是网络的关口,负责请求的路由、转发、身份校验
在SpringCloud中网关的实现包括两种:
1、网关路由
配置文件:
spring:
cloud:
gateway:
routes:
- id: item # 路由规则id,自定义,唯一
uri: lb://item-service # 路由目标微服务,lb代表负载均衡
predicates: # 路由断言,判断请求是否符合规则,符合则路由到目标
- Path=/items/** # 以请求路径做判断,以/items开头则符合
- id: xx
uri: lb://xx-service
predicates:
- Path=/xx/**
2、创建gateway的项目,然后引入依赖
<dependencies> <!--common--> <dependency> <groupId>com.heima</groupId> <artifactId>hm-common</artifactId> <version>1.0.0</version> </dependency> <!--网关--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <!--nacos服务注册发现--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-nacos-discovery</artifactId> <version>2.1.0.RELEASE</version> <exclusions> <exclusion> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-ribbon</artifactId> </exclusion> </exclusions> </dependency> <!--负载均衡--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-loadbalancer</artifactId> </dependency> </dependencies> <build> <finalName>${project.artifactId}</finalName> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>