专栏目录
0.docker快速入门
1.初识微服务
2.Gateway网关路由
3.微服务配置管理
微服务是一种软件架构风格,以专注于单一职责的小型项目为基础,组合出复杂的大型应用。
微服务顾名思义,就是很小的服务,所以它属于面向服务架构的一种。通俗一点来说,微服务类似于古代著名的发明,活字印刷术,每个服务都是一个组件,通过编排组合的方式来使用,从而真正做到了独立、解耦、组件化、易维护、可复用、可替换、高可用、最终达到提高交付质量、缩短交付周期的效果。
从专业的角度来看,微服务架构是一种架构模式,它提倡将单一应用程序划分成一组小的服务,服务之间互相协调、互相配合,为用户提供最终价值。每个服务运行在其独立的进程中,服务与服务间采用轻量级的通信机制互相沟通。每个服务都围绕着具体业务进行构建,并且能够被独立的部署到生产环境、类生产环境等。另外,应当尽量避免统一的、集中式的服务管理机制,对具体的一个服务而言,应根据业务上下文,选择合适的语言、工具对其进行构建。
特点:
Spring-Cloud官方文档
SpringCloud继承了各种微服务功能组件,并基于SpringBoot实现了组件的装配
注意SpringCloud、SpringBoot和JDK的版本适配
将业务的所有功能集中在一个项目中开发,打成一个包部署
优点:架构简单、部署成本低
缺点:团队协作成本高、系统发布效率低(如项目过大每次编译时间长等)、系统可用性差(无法应对高并发状况)
适合开发功能相对简单,规模较小的项目
通过maven的父子工程进行依赖管理
微服务架构是服务化思想指导下的一套最佳实践架构方案。
服务化: 将单体架构中功能模块拆分为多个独立项目
服务拆分一定要考虑几个问题:
一般情况下,对于一个初创的项目,首先要做的是验证项目的可行性。因此这一阶段的首要任务是敏捷开发,快速产出生产可用的产品,投入市场做验证。为了达成这一目的,该阶段项目架构往往会比较简单,很多情况下会直接采用单体架构,这样开发成本比较低,可以快速产出结果,一旦发现项目不符合市场,损失较小。
如果这一阶段采用复杂的微服务架构,投入大量的人力和时间成本用于架构设计,最终发现产品不符合市场需求,等于全部做了无用功。
所以,对于大多数小型项目来说,一般是先采用单体架构,随着用户规模扩大、业务复杂后再逐渐拆分为微服务架构。这样初期成本会比较低,可以快速试错。但是,这么做的问题就在于后期做服务拆分时,可能会遇到很多代码耦合带来的问题,拆分比较困难(前易后难)。
而对于一些大型项目,在立项之初目的就很明确,为了长远考虑,在架构设计时就直接选择微服务架构。虽然前期投入较多,但后期就少了拆分服务的烦恼(前难后易)。
之前我们说过,微服务拆分时粒度要小,这其实是拆分的目标。具体可以从两个角度来分析:
高内聚首先是单一职责,但不能说一个微服务就一个接口,而是要保证微服务内部业务的完整性为前提。目标是当我们要修改某个业务时,最好就只修改当前微服务,这样变更的成本更低。
一旦微服务做到了高内聚,那么服务之间的耦合度自然就降低了。
当然,微服务之间不可避免的会有或多或少的业务交互,比如下单时需要查询商品数据。这个时候我们不能在订单服务直接查询商品数据库,否则就导致了数据耦合。而应该由商品服务对应暴露接口,并且一定要保证微服务对外接口的稳定性(即:尽量保证接口外观不变)。虽然出现了服务间调用,但此时无论你如何在商品服务做内部修改,都不会影响到订单微服务,服务间的耦合度就降低了。
明确了拆分目标,接下来就是拆分方式了。我们在做服务拆分时一般有两种方式:
所谓纵向拆分,就是按照项目的功能模块来拆分。例如黑马商城中,就有用户管理功能、订单管理功能、购物车功能、商品管理功能、支付功能等。那么按照功能模块将他们拆分为一个个服务,就属于纵向拆分。这种拆分模式可以尽可能提高服务的内聚性。
而横向拆分,是看各个功能模块之间有没有公共的业务部分,如果有将其抽取出来作为通用服务。例如用户登录是需要发送消息通知,记录风控数据,下单时也要发送短信,记录风控数据。因此消息发送、风控数据记录就是通用的业务功能,因此可以将他们分别抽取为公共服务:消息中心服务、风控管理服务。这样可以提高业务的复用性,避免重复开发。同时通用业务一般接口稳定性较强,也不会使服务之间过分耦合。
微服务进行了拆分,实现了数据和服务的隔离,需要在网络上进行相互调用
通过服务的接口进行相互调用,通过发起HTTP请求实现
Spring提供的工具,可方便实现Http请求的发送
①注入Rest Template到Spring容器
②调用Api发起远程调用
示例:
// 利用RestTemplate.exchange()发起http请求,得到http的响应
ResponseEntity<List<ItemDTO>> response = restTemplate.exchange(
"http://localhost:8081/items?ids={ids}",//请求url
HttpMethod.GET,//请求方式
null,//requestEntity
new ParameterizedTypeReference<List<ItemDTO>>() {},
//查询的集合使用ParameterizedTypeReferrence
Map.of("ids", CollUtil.join(itemIds, ","))//url中占位符的参数,将itemIds中用","连接
);
//解析响应
if(!response.getStatusCode().is2xxSuccessful()){
// 查询失败,直接结束
return;
}
List<ItemDTO> items = response.getBody();
if (CollUtils.isEmpty(items)) {
return;
}
服务调用时的服务地址是不确定的,无法事先写死
心跳:所有服务定期向注册中心发送自己的服务信息
阿里巴巴的产品,已加入SpringCloudAlibaba,除此之外还有Eureka,Consul等注册中心
数据库的SQL文件在
../nacos/conf/
中
图片内容来自:黑马程序员
/root/nacos/custom.env
,关于数据库的配置图片内容来自:黑马程序员
# localhost:8848/nacos
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
①引入nacos discovery依赖:
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
②配置Nacos地址
spring:
application:
name: #服务名称
cloud:
nacos:
server-addr: localhost:8848 #nacos地址
①引入nacos discovery依赖
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
②配置nacos地址
spring:
cloud:
nacos:
server-addr: 192.168.150.101:8848
③服务发现
private final DiscoveryClient discoveryClient;
private void handler(List<BeanClass> vos){
//1.根据服务名称,拉取服务实例列表
List<ServiceInstance> instances = discoveryClient.getInstances("服务名称");
//2.负载均衡,选择一个实例
ServiceInstance instance = instances.get(RandomUtil.randomInt(instances.size()));
//3.获取实例的IP和端口
URI uri = instance.getUri();
//4.进行服务调用
}
声明式的Http客户端,基于SpringMVC常见注解优化服务调用的Http请求
①引入依赖,包括OpenFeign和负载均衡组件SpingCloudLoadBalancer
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-loadbalancerartifactId>
dependency>
②启用OpenFeign,使用EnableFeignClients的注解在启动项上
@EnableFeignClients
@SpringBootApplication
public class ProjectApplication(){...}
③编写OpenFeign客户端
@FeignClient(value="服务名称")
public interface ServiceClient{
@GetMapping("/select")//请求路径
List<DTO> queryItemByIds(@RequetrParam("ids") Collection<Long> ids);//请求参数
}
④使用FeignClient实现远程调用
List<DTO> items = serviceClient.queryItemByIds(List.of(1,2,3));
Feign底层发起http请求,依赖于其它的框架。其底层支持的http客户端实现包括:
因此我们通常会使用带有连接池的客户端来代替默认的HttpURLConnection。比如,我们使用OK Http.
①引入依赖
<dependency>
<groupId>io.github.openfeigngroupId>
<artifactId>feign-okhttpartifactId>
dependency>
②开启连接池
feign:
okhttp:
enabled: true # 开启OKHttp功能
OpenFeign只会在FeignClient所在包的日志级别为DEBUG时,才会输出日志。而且其日志级别有4级:
Feign默认的日志级别就是NONE,所以默认我们看不到请求日志。
①定义日志级别
import feign.Logger;
import org.springframework.context.annotation.Bean;
public class DefaultFeignConfig {
@Bean
public Logger.Level feignLogLevel(){
return Logger.Level.FULL;
}
}
②配置日志
FeignClient
中配置,只对该客户端生效@FeignClient(value = "item-service", configuration = DefaultFeignConfig.class)
//configuration使用定义的日志级别
@EnableFeignClients
中配置,针对所有客户端
生效@EnableFeignClients(defaultConfiguration = DefaultFeignConfig.class)