赞
踩
单体架构:将所有的功能集中在一个项目中开发,打成一个包部署
优点:
架构简单
部署成本低
缺点:
团队协作成本高
系统发布效率低
系统可用性差
微服务架构:就是把单体服务中的功能模块拆分成多个独立项目
优点:粒度小、团队自治、服务自治
Spring Cloud
是国内目前使用最广泛的微服务架构
什么时候拆分?
创业型项目:先采用单体架构,快速开发,快速试错。随着规模的扩大,主键拆分。
【例如】:我们是一个小公司,想到了一个点子,但是由于公司资金和试错能力有限,因此可以先开发一个单体项目,等到项目用户量上来了再拆分成微服务项目。
确定的大型项目:资金充足,目标明确,可以直接选择微服务架构,避免后续拆分的麻烦。
【例如】:某个大型的车企,想开发一个网上商城,因为这个车企已经有用户寄出了,所以可以直接开发微服务项目。
怎么拆?
高内聚:每个微服务的职责要尽量单一,包含的业务相互关联度高、完整度高。
低耦合:每个微服务的功能要相对独立,尽量减少对其它微服务的依赖。
拆分方式
纵向拆分:按照业务模块来拆分
【例如】:订单管理、菜品管理、用户管理
横向拆分:抽取公共服务,提高复用性
【例如】:记录风险操作日志的操作和发送短信的操作很多场景都需要用到,因此可以拆分出来,避免写过多的冗余代码
独立Project:将每个模块拆分成一个Project,但是这些Project都放到同一个文件夹中【大型项目使用】
以黑马商城为例,黑马商城包含以下几个模块:用户模块、商品模块、购物车模块、订单模块、支付模块。
由于模块分的很清楚,因此我们纵向拆分,按照模块将单体服务拆分成多个项目,每个项目都是一个project。如果每个项目都是一个Project,那么他们物理上就没有关联了,因此需要创建一个文件夹,将这些Project都放在同一个文件夹中,那么他们物理上就有联系了
Maven聚合:大项目还是一个Project,但是它下面的功能模块变成了一个个Module,每个Module最后都是独立打包部署【小型项目使用】
案例:以Maven聚合的方式拆分黑马商城。
将hm-service中与商品管理相关功能拆分到一个微服务module中,命名为item-service
将hm-service中与购物车有关的功能拆分到一个微服务module中,命名为cart-service
在父工程hmall的基础上,创建一个Maven模块hm-service
给hm-service模块引入需要的依赖
创建模块的启动类
创建包结构
创建配置文件并编写
1.启动端口不能冲突
2.设置spring.application.name名字【微服务名字,很重要】
3.修改数据库的配置
4.不需要的配置就删了
将需要的类拷贝过来【Mapper、Service、Controller】
检查没有错误就可以启动子模块
问题说明:单体项目的时候,多个表的查询可能会用到多个service或者mapper,但是经过我们拆分之后,调用不到对应的service或者mapper,那么该怎么解决呢?
解决办法:一个服务向另一个服务发起网络请求。看下面的服务治理
服务治理分为:服务注册、服务发现
远程调用的代码是我们提前写好的,但是等到项目上线的时候被调用的服务可能会因为接口压力过大,集群部署,我们的代码只会调用其中的一个服务,不能实现负载均衡。
如果我们指定的服务器挂历了,怎么办?这些都是使用HttpClient等客户端存在的问题。
我们找工作的时候,不知道哪个公司需要招聘程序员。外包公司也不知道哪些人想要当程序员。于是我们和外包公司就将信息都报到中介公司,中介公司就会给我们推荐想要的人选。
注册中心就是上面的原理。每一个服务会将自己的信息都注册到注册信息,注册信息会有一个表维护这些信息。如果我们需要找某个服务的信息,注册中心就会将那个服务的信息返回给我们,我们在这些信息中随机选择一个(负载均衡)。
注册中心会和服务保持通讯,以保证服务是否存活,如果长时间没有通讯,会将服务从注册中心剔除。
服务治理中的三个角色:
服务提供者:暴露服务接口,供其它服务调用
服务消费者:调用其它服务提供的接口
注册中心:记录并监控微服务各实例状态,推送服务变更信息
消费者如何知道提供者的地址?
服务提供者会在启动时注册自己信息到注册中心,消费者可以从注册中心订阅和拉取服务信息
消费者如何得知服务状态变更?
服务提供者通过心跳机制向注册中心报告自己的健康状态,当心跳异常时注册中心会将异常服务剔除,并通知订阅了该服务的消费者
当提供者有多个实例时,消费者该选择哪一个?
消费者可以通过负载均衡算法,从多个实例中选择一个
Nacos是目前国内企业中占比最多的注册中心组件。它是阿里巴巴的产品,目前已经加入SpringCloudAlibaba中。
数据库中创建Nacos使用到的库,将他的表创建出来【有SQL脚本】
准备好Nacos的配置文件【里面指定数据库的类型、数据库地址、数据库名、数据库端口、数据库用户名、数据库密码】
PREFER_HOST_MODE=hostname
MODE=standalone
SPRING_DATASOURCE_PLATFORM=mysql
MYSQL_SERVICE_HOST=192.168.150.101
MYSQL_SERVICE_DB_NAME=nacos
MYSQL_SERVICE_PORT=3306
MYSQL_SERVICE_USER=root
MYSQL_SERVICE_PASSWORD=123
MYSQL_SERVICE_DB_PARAM=characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
将配置文件上传到服务器,创建Nacos的docker容器
docker run -d \
--name nacos \
--env-file ./nacos/custom.env \
-p 8848:8848 \
-p 9848:9848 \
-p 9849:9849 \
--network hm-net \
--restart=always \
nacos/nacos-server:v2.1.0-slim
可以通过如下访问
ip地址:端口号/nacos
用户名/密码:nacos
服务注册:服务启动的时候,都应该将信息注册到注册中心里面
引入Nacos的依赖
<!--nacos 服务注册发现-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
配置Nacos地址
spring:
application:
name: item-service # 服务名称
cloud:
nacos:
server-addr: 192.168.150.101:8848 # nacos地址
启动服务即可
服务发现:服务的调用者在调用服务的时候,应该在注册中心拉取服务列表
引入依赖
<!--nacos 服务注册发现-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
配置Nacos地址
spring:
cloud:
nacos:
server-addr: 192.168.150.101:8848
拉取服务列表
//1.注入对象【SpringCloud的规范就是这个】
@Autowired
private DiscoveryClient discoveryClient;
//2.在具体的方法中根据实例的id获取服务的实例
List<ServiceInstance> instances=discoveryClient.getInstances("item-service")//nacos的实例id默认是服务的名称
//3.判断集合是否为空
if(CollUtils.isEmpyt(instances)){
return;
}
//4.不为空,根据负载均衡选一个实例
ServiceInstance instance=instances.get(RandomUtils.randomInt(instances.size()))
//5.然后调用HttpClient等客户端发送方法【instance的getUri方法可以获取ip地址和端口号】
问题:上面的服务发现代码太复杂了,我们单体服务只用写一行代码,到了微服务就要写这么多行代码吗?
解答:OpenFeign可以用来解决这个问题,他是一个声明式的Http客户端,可以让我们更简单的写Http请求
引入依赖【两个】
<!--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注解,启动OpenFeign功能
//如果FeignClient不在SpringBootApplication的扫描范围内可以在@EnableFeignClients中指定扫描范围
@EnableFeignClients(basePackages="come.hmall.api.clients")
编写接口【就代替了原本一大堆的代码】
//@FeignClient注解告诉java,这就是一个Feign的客户端
//value属性就说明以后拉取的是对应id的服务
//里面默认使用了负载均衡算法
@FeignClient("item-service")
public interface ItemClient {
//这个请求路径【请求的端口号和ip在负载均衡那里已经自动获取了】
@GetMapping("/items")
//请求返回的类型会根据反射自动装配到我们的返回值中
//@RequestParam是请求的参数
List<ItemDTO> queryItemByIds(@RequestParam("ids") Collection<Long> ids);
}
在需要发送请求的地方调用接口即可
//注入对象
@AutoWired
private ItemClient itemClient;
//需要的地方调用方法
List<ItemDTO> items=itemClient.queryItemByIds();
OpenFeign允许指定连接方式,但是默认方式由于使用jdk的远程Client,所以不支持连接池。【HttpClient和OkHttp都支持连接池】
引入feign对OKHttp的支持的依赖
<!--OK http 的依赖 -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
</dependency>
配置文件中开启连接池
feign:
okhttp:
enabled: true # 开启OKHttp功能
OpenFeign需要输出日志需要符合两个条件:
- FeignClient所在的包日志级别为debug
- Feign的日志级别在NONE以上
Feign的日志级别:
NONE:不记录任何日志信息,这是默认值。
BASIC:仅记录请求的方法,URL以及响应状态码和执行时间
HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息
FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据。
定义一个类定义Feign的日志级别
public class DefaultFeignConfig {
@Bean
public Logger.Level feignLogLevel(){
return Logger.Level.FULL;//日志级别
}
}
如果需要局部配置,则在对应的FeignClient中配置configuration属性
@FeignClient(value = "item-service", configuration = DefaultFeignConfig.class)
如果需要全局配置,则在启动类的EnableFeignClients,添加defaultConfiguration属性
@EnableFeignClients(defaultConfiguration = DefaultFeignConfig.class)
我们刚才只是在一个服务中使用OpenFeign发送了请求。但是这个请求在很多服务中都能使用到,如果每次都写一边,会很麻烦。我们能否把这个请求抽象成一个公共的呢?
方案一:模块拆分
1.将一个模块抽象成三个子模块,一个模块专门存放Feign的API
2.其他的模块需要发送Feign请求的时候直接引入对应模块的依赖
方案二:再写一个模块
1.创建一个模块,和其他模块同级别,这个模块只存放Feign的API
2.其他接口需要调用的时候直接引入模块API
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。