1.认识微服务
1.1 单体架构
单体架构:将业务的所有功能集中在一个项目中开发,打成一个包部署;
优点:架构简单、部署成本低;
缺点:团队协作成本高、系统发布效率低、系统可用性差;
单体架构适合开发功能相对简单,规模较小的项目;
1.2 微服务架构
微服务架构:是服务化思想指导下的一套最佳实践架构方案,服务化,就是把单体架构中的功能模块拆分为多个独立项目。
拆分要求:粒度小(单一功能)、团队自治、服务自治;
1.3 SpringCloud
SpringCloud是目前国内使用最广泛的微服务框架,官网;
SpringCloud集成了各种微服务功能组件,并基于SpringBoot实现了这些组件的自动装配,从而提供了良好的开箱即用体验;
2.微服务拆分
2.1 熟悉商城(基础电商项目)
商城模块:用户模块、商品模块、购物车模块、订单模块、支付模块
1. 登录
2. 搜索商品
3. 购物车
4. 下单
5. 支付
2.2 服务拆分原则
1. 什么时候拆
创业型项目:先采用单体架构,快速开发,快速试错。随着规模扩大,逐渐拆分;
确定的大型项目:资金充足,目标明确,可以直接选择微服务架构,避免后续拆分的麻烦;
2. 怎么拆
从拆分目标来说,要做到:
- 高内聚:每个微服务的职责要尽量单一,包含的业务相互关联度高、完整度高;
- 低耦合:每个微服务的功能要相对独立,尽量减少对其他微服务的依赖;从拆分的方式来说,一般包含两种方式:
- 纵向拆分(垂直拆分):按照业务模块来拆分;
- 横向拆分(水平拆分):抽取公共服务,提高复用性;
2.3 拆分服务(基于2.1的商城项目拆分)
微服务项目在企业中有两种不同的结构:
1. 独立Project:将项目中拆分成N个微服务,而非模块,每个微服务都是独立Project,完全分开,完全独立,完全解耦,适用于大项目,每个微服务比较复杂,对商城建立一个文件夹,在该文件夹中存放N个微服务;
2. Maven聚合:将商城创建成Project,而每个微服务不再是project,而是Module,推适用于中小型项目;
拆分商品服务
将商城项目中与商品管理相关的功能拆分到一个微服务module中,命名为item-service
3. 创建module
4. 添加所需依赖
5. 启动类(修改扫描mapper)
6. 配置文件(.yml,修改其中的port不要冲突,微服务要起个名字,独立数据库连接)
7. 具体代码(实体类、mapper、service、controller)
拆分购物车
将商城项目中与购物车有关的功能拆分到一个微服务module中,命名为cart-service
同上
2.4 服务调用
当需要完成微服务拆分后,某些数据在不同服务,无法直接调用本地方法查询数据,需要使用RestTemplate发送http请求,实现远程调用;
前端如何向后端发送网络请求?
答:前端发送http请求;后端如何发送http请求?
Spring提供了一个RestTemplate工具,可以方便的实现Http请求的发送,使用步骤如下:
1. 注入RestTemplate到Spring容器(写入到启动类中);
@Bean
public RestTemplate restTemplate(){return new RestTemplate();
}
2. 发起远程调用
public <T> ResponseEntity<T> exchage(String url, //请求路径HttpMethod method, //请求方式@Nullable HttpEntity<?> requestEntity, //请求实体,可以为空Class<T> responseType, //返回值类型Map<String,?> uriVariables //请求参数
)
服务调用具体实现:
3. 启动类中写Bean
@Bean
public RestTemplate restTemplate(){return new RestTemplate();
}4. 在需要调用服务的service中注入restTemplate
(推荐使用构造函数 或者 使用@RequiredArgsConstructor注解,并在类中定义private final RestTemplate restTemplate);
@Autowired
private RestTemplate restTemplate;5. 调用服务
简单常见的调用方法:
restTemplate.getXXX
restTemplate.postXXX
restTemplate.deleteXXX
restTemplate.putXXX(1)利用RestTemplate发起http请求,得到http的响应
restTemplate.exchange("http://localhost:8081/items?ids={ids}",HttpMethod.GET,null,new ParameterizedTypeReference<List<ItemDTO>>(){},Map.of("ids",Collutil.join(itemIds,","))
)
讲解:
Collutil.join(itemIds,","):将set类型的数据转换成字符串类型;
(2)解析响应
if(!response.getStatusCode().is2xxSuccessful()){ //验证查询结果是否成功//查询失败,直接结束return;
}else{ //查询成功List<ItemDTO> items = response.getBody();
}
(3)可以直接使用解析的响应结果
3.服务治理
3.1 注册中心原理
【服务提供者】暴露服务接口,供其它服务调用 | | 1)注册服务信息 | 心跳续约
【注册中心】调用其它服务提供的接口|| 2)订阅item-service的信息| 推送变更
【服务调用者】记录并监控微服务各实例状态,推送服务变更信息
3)负载均衡(多种算法)
4)服务调用者远程调用服务提供者提供的接口消费者如何知道提供者的地址?
答:服务提供者会在启动时注册自己信息到注册中心,消费者可以从注册中心订阅和拉去服务信息;
消费者如何得知服务状态变更?
答:服务提供者通过心跳机制向注册中心报告自己的健康状态,当心跳异常时注册中心将异常服务剔除,并通知订阅了该服务的消费者;
当提供者有多个实例时,消费者该选择哪一个?
答:消费者可以通过负载均衡算法,从多个实例中选择一个;
3.2 Nacos注册中心
Nacos是目前国内企业中占比最多的注册中心组件,它是阿里巴巴的产品,目前已经加入了SpringCloudAlibaba中;
Nocas是一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台;
安装nacos注册中心
3.3 服务注册
服务注册步骤如下:
1)引入nacos discovery依赖:
<dependency><groupId>com.alibaba.cloud</group><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
2)配置Nacos地址
spring:application:name: item-service #服务名称cloud:nacos:server-addr: 192.168.150.101:8848 #nacos地址
3.4 服务发现
消费者需要连接nacos以拉取和订阅服务,因此服务发现的前两步与服务注册是一样,后面再加上服务调用即可:
(1)引入nacos discovery依赖
(2)配置nacos地址
(3)服务发现
private final DiscoveryClient discoveryClient;
private void handleCartItems(List<CartVO> vos){//1.根据服务名称,拉取服务的实例列表List<ServiceInstance> instances=discoveryClient.getInstances("item-service");//2.负载均衡,挑选一个实例ServiceInstance instance=instance.get(RandomUtil.randomInt(instance.size()));//3.获取实例的IP和端口URI uri=instance.getUri();//...略
}
4.OpenFeign
4.1 快速入门
OpenFeign是一个【声明式的http客户端】,是SpringCloud在Eureka公司开源的Feign基础上改造而来,官方地址:https://github.com/OpenFeign/feign
其作用就是基于SpringMVC的常见注解,帮我们优雅的实现http请求的发送;
List<ServiceInstance> instances=discoveryClient.getInstances("item-service");
ServiceInstance instance=instances.get(RandomUtil.randomInt(instances.size));
ResponseEntity<List<ItemDTO>> response=restTemplate.exchange( instance.getUri()+"/items?ids={ids}", HttpMethod.GET, null, new ParamterizedTypeReference<List<ItemDTO>>(){}, Map.of("ids",CollUtil.join(itemIds,","))
);
<!--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>@EnableFeignClients@SpringBootApplicationpublic class CartApplication{
@FeignClient(value="item-service")
public interface ItemClient{@GetMapping("/items")List<ItemDTO> queryItemByIds(@RequestParam("ids") Collentioon<Long> ids);
}
private final ItemClient itemClient;
List<ItemDTO> items=itemClient.queryItemByIds(List.of(1,2,3));
4.2 连接池
OpenFeign对Http请求做了优雅的伪装,不过其底层发起http请求,依赖于其它的框架,这些框架可以自己选择,包括一下三种:
1. HttpURLConnection:默认实现,不支持连接池
2. Apache HttpClient:支持连接池
3. OKHttp:支持连接池
具体源码可以参考FeignBlockingLoadBalancerClient类中的delegate成员变量;
OpenFeign整合OKHttp的步骤如下:
(1)引入依赖
<dependency><groupId>io.github.openfeign</groupId><artifactId>feign-okhttp</artifactId>
</dependency>
(2)开启连接池功能
feign:okhttp:enabled: true #开启OKHttp连接池支持
4.3 最佳实践
将都可能用到的实体类和接口都放到一个公共的模块,在想要使用这些公共内容的模块的pom中引入公共模块;
当定义的FeignClient不在SpringBootApplication的扫描包范围时,这些FeignClient无法使用,有两种发视解决:
方式一:在启动类中指定FeignClient所在包@EnableFeignClient(basePackages="com.hmall.api.clients")
方式二:指定FeignClient字节码@EnableFeignClients(clients = {UserClient.class})
4.4 日志输出
根据日志信息查询执行出错的详细问题所在。
OpenFeign只会在FeignClient所在包的日志级别为DEBUG时,才会输出日志,而且其日志级别有4级:
NONE:不记录任何日志信息,这是默认值
BASIC:仅记录请求的方法,URL以及响应状态码和执行时间;
HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息;
FULL:记录所有请求和响应的明细,包括头信息,请求体,源数据;
由于Feign默认的日志级别就是NONE,所以默认我们看不到请求日志;要自定义日志级别需要声明一个类型为Logger.Level的Bean,在其中定义日志级别:
public class DefaultFeignConfig{@Beanpublic Logger.Level feignLogLevel(){return Logger.level.FULL;}
}
但此时这个Bean并未生效,要想配置某个FeignClient的日志,可以在@FeignClient注解中声明:
@FeignClient(value="item-service",configuration=DefaultFeignConfig.class)
如果想要全局配置,让所有FeignClient都按照这个日志配置,则需要在启动类上加上@EnableFeignClients注解中声明:
@EnableFeignClients(defaultConfiguration=DefaultFeignConfig.class)