单体项目:所有的业务功能集中在一个项目中开发,打包成一个包部署
优点:架构简单,部署成本低
缺点:团队协作成本高,系统发布效率低,系统可用性差
合适开发功能相对简单,规模较小的项目。不适合复杂的项目
微服务
微服务架构,是服务化思想指导下的一套最佳实践架构方案。服务化,就是把单体架构的功能模块拆分成多个独立的项目。
拆分要求:粒度小,团队自治(开发独立),服务自治(打包,编译独立)
缺点:运维难度高,
SpringCloud
SpringCloud框架是目前Java领域较为全面的微服务组件集合。依托于SpringBoot的自动装配能力,大大降低了其项目搭建、组件使用的成本。
微服务拆分
微服务拆分原则
一般情况下,对于一个初创的项目,首先要做的是验证项目的可行性。因此这一阶段的首要任务是敏捷开发,快速产出生产可用的产品,投入市场做验证。为了达成这一目的,该阶段项目架构往往会比较简单,很多情况下会直接采用单体架构,这样开发成本比较低,可以快速产出结果,一旦发现项目不符合市场,损失较小。
所以,对于大多数小型项目来说,一般是先采用单体架构 ,随着用户规模扩大、业务复杂后再逐渐拆分为 微服务架构。这样初期成本会比较低,可以快速试错。但是,这么做的问题就在于后期做服务拆分时,可能会遇到很多代码耦合带来的问题,拆分比较困难。
而对于一些大型项目,在立项之初目的就很明确,为了长远考虑,在架构设计时就直接选择微服务架构。虽然前期投入较多,但后期就少了拆分服务的烦恼。
拆分目标:
之前我们说过,微服务拆分时粒度要小,这其实是拆分的目标。具体可以从两个角度来分析:
高内聚:每个微服务的职责要尽量单一,包含的业务相互关联度高、完整度高。
低 耦合:每个微服务的功能要相对独立,尽量减少对其它微服务的依赖,或者依赖接口的稳定性要强。
并且一定要保证微服务对外接口的稳定性(即:尽量保证接口外观不变)。虽然出现了服务间调用,但此时无论你如何在一个服务做内部修改,都不会影响到其他微服务,服务间的耦合度就降低了。
拆分方式:
纵向 拆分:所谓纵向拆分,就是按照项目的功能模块来拆分。例如,有用户管理功能、订单管理功能、购物车功能、商品管理功能、支付功能等。那么按照功能模块将他们拆分为一个个服务,就属于纵向拆分。这种拆分模式可以尽可能提高服务的内聚性。
横向拆分:是看各个功能模块之间有没有公共的业务部分,如果有将其抽取出来作为通用服务。例如用户登录是需要发送消息通知,记录风控数据,下单时也要发送短信,记录风控数据。因此消息发送、风控数据记录就是通用的业务功能,因此可以将他们分别抽取为公共服务:消息中心服务、风控管理服务。这样可以提高业务的复用性,避免重复开发。同时通用业务一般接口稳定性较强,也不会使服务之间过分耦合。
工程结构
一般微服务项目有两种不同的工程结构:
-
完全解耦:每一个微服务都创建为一个独立的工程,甚至可以使用不同的开发语言来开发,项目完全解耦。
-
优点:服务之间耦合度低
-
缺点:每个项目都有自己的独立仓库,管理起来比较麻烦
-
-
Maven聚合:整个项目为一个Project,然后每个微服务是其中的一个Module
-
优点:项目代码集中,管理和运维方便
-
缺点:服务之间耦合,编译时间较长
-
远程调用
微服务进行拆分后,服务与服务之间的代码和数据库都物理分隔开了,为解决服务与服务之间的数据查询我们可以在java程序之间进行http请求。
RestTemplate
Spring给我们提供了一个RestTemplate的API,可以方便的实现Http请求的发送。
其中提供了大量的方法,方便我们发送Http请求,例如:

1.先将RestTemplate注册为一个Bean:
@Configuration
public class RemoteCallConfig {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
2.发送远程调用


服务治理
通过Http请求实现的跨微服务的远程调用,这个方式存在一些问题。
假如某个微服务被调用较多,为了应对更高的并发,我们进行了多实例部署
此时,每个微服务的实例其IP或端口不同,问题来了:
-
这么多实例,其他微服务如何知道每一个实例的地址?
-
http请求要写url地址,其他服务到底该调用哪个实例呢?
-
如果在运行过程中,某一个实例宕机,其他服务依然在调用该怎么办?
-
如果并发太高,临时多部署了N台实例,其他服务如何知道新实例的地址?
为了解决上述问题,就必须引入注册中心的概念了。
注册中心
在微服务远程调用的过程中,包括两个角色:
服务提供者:提供接口供其它微服务访问
服务消费者:调用其它微服务提供的接口
在大型微服务项目中,服务提供者的数量会非常多,为了管理这些服务就引入了注册中心的概念。注册中心、服务提供者、服务消费者三者间关系如下:

流程如下:
-
服务启动时就会注册自己的服务信息(服务名、IP、端口)到注册中心
-
调用者可以从注册中心订阅想要的服务,获取服务对应的实例列表(1个服务可能多实例部署)
-
调用者自己对实例列表负载均衡,挑选一个实例
-
调用者向该实例发起远程调用
当服务提供者的实例宕机或者启动新实例时,调用者如何得知呢?
-
服务提供者会定期向注册中心发送请求,报告自己的健康状态(心跳请求)
-
当注册中心长时间收不到提供者的心跳时,会认为该实例宕机,将其从服务的实例列表中剔除
-
当服务有新实例启动时,会发送注册服务请求,其信息会被记录在注册中心的服务实例列表
-
当注册中心服务列表变更时,会主动通知微服务,更新本地服务列表
服务注册
(需要搭建nacos注册中心,按名称管理服务实例)
启动多个实例,打开nacos注册中心页面可以发现服务注册成功
服务发现
服务的消费者要去nacos订阅服务,这个过程就是服务发现,步骤如下:
1.引入依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
2.配置Nacos地址
spring:
cloud:
nacos:
server-addr: xxx.xxx.xxx.xxx:8848
3.发现并调用服务
注册中心有多个实例的信息,而真正发起调用时只需要知道一个实例的地址。
服务调用者必须利用负载均衡的算法,从多个实例中挑选一个去访问。常见的负载均衡算法有:
1.随机
2.轮询
3.IP的hash
4.最近最少访问
另外,服务发现需要用到一个工具DiscoveryClient,SpringCloud已经帮我们自动装配,我们可以直接注入使用:


OpenFeign
OpenFeigin是一个声明式的http客户端,是作用就是基于SpringMVC常见注解来实现http请求的发送
其实远程调用的关键点就在于四个:
1.请求方式
2.请求路径
3.请求参数
4.返回值类型
所以,OpenFeign就利用SpringMVC的相关注解来声明上述4个参数,然后基于动态代理帮我们生成远程调用的代码,而无需我们手动再编写。
1.引入依赖
<!--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

3.编写OpenFeign客户端

4.使用OpenFeign客户端,实现远程调用

feign替我们完成了服务拉取、负载均衡、发送http请求的所有工作
连接池

使用其他http框架

重启服务,连接池就生效了。
最佳实践
方法一 服务暴露的接口客户端由对应项目组编写 项目结构复杂

方法二 耦合度高 横向拆分


日志

在api模块下新建一个配置类,定义Feign的日志级别:

接下来,要让日志级别生效,还需要配置这个类。有两种方式:
局部生效:在某个FeignClient
中配置,只对当前FeignClient
生效
@FeignClient(value = "item-service", configuration = DefaultFeignConfig.class)
全局生效:在@EnableFeignClients
中配置,针对所有FeignClient
生效。
@EnableFeignClients(defaultConfiguration = DefaultFeignConfig.class)