微服务-1 认识微服务

目录​​​​​​​

[1 认识微服务](#1 认识微服务)

[1.1 单体架构](#1.1 单体架构)

[1.2 微服务](#1.2 微服务)

[1.3 SpringCloud](#1.3 SpringCloud)

[2 服务拆分原则](#2 服务拆分原则)

[2.1 什么时候拆](#2.1 什么时候拆)

[2.2 怎么拆](#2.2 怎么拆)

[2.3 服务调用](#2.3 服务调用)

[3. 服务注册与发现](#3. 服务注册与发现)

[3.1 注册中心原理](#3.1 注册中心原理)

[3.2 Nacos注册中心](#3.2 Nacos注册中心)

[3.3 服务注册](#3.3 服务注册)

[3.3.1 添加依赖](#3.3.1 添加依赖)

[3.3.2 配置Nacos](#3.3.2 配置Nacos)

[3.3.3 启动服务实例](#3.3.3 启动服务实例)

[3.4 服务发现](#3.4 服务发现)

[3.4.1 发现并调用服务](#3.4.1 发现并调用服务)

[4 OpenFeign](#4 OpenFeign)

[4.1 快速入门](#4.1 快速入门)

[4.1.1 引入依赖](#4.1.1 引入依赖)

[4.1.2 启用OpenFeign](#4.1.2 启用OpenFeign)

[4.1.3 编写OpenFeign客户端](#4.1.3 编写OpenFeign客户端)

[4.1.4 使用FeignClient](#4.1.4 使用FeignClient)

[4.2 连接池](#4.2 连接池)

[4.2.1 引入依赖](#4.2.1 引入依赖)

[4.2.2 开启连接池](#4.2.2 开启连接池)

[4.3 最佳实践](#4.3 最佳实践)

[4.3.1 思路分析](#4.3.1 思路分析)

[4.3.2 抽取Feign客户端](#4.3.2 抽取Feign客户端)

[4.3.3 扫描包](#4.3.3 扫描包)

[4.4 日志配置](#4.4 日志配置)

[4.4.1 定义日志级别](#4.4.1 定义日志级别)

[4.4.2 配置](#4.4.2 配置)

[4 总结](#4 总结)

[4.1 如何利用 OpenFeign 实现远程调用?](#4.1 如何利用 OpenFeign 实现远程调用?)

[4.2 如何配置 OpenFeign 的连接池?](#4.2 如何配置 OpenFeign 的连接池?)

[4.3 OpenFeign 使用的最佳实践方式是什么?](#4.3 OpenFeign 使用的最佳实践方式是什么?)

[4.4 如何配置 OpenFeign 输出日志的级别?](#4.4 如何配置 OpenFeign 输出日志的级别?)


1 认识微服务

1.1 单体架构

单体架构(monolithic structure):顾名思义,整个项目中所有功能模块都在一个工程中开发;项目部署时需要对所有模块一起编译、打包;项目的架构设计、开发模式都非常简单。

当项目规模较小时,这种模式上手快,部署、运维也都很方便,因此早期很多小型项目都采用这种模式。

缺点:

团队协作成本高:试想一下,你们团队数十个人同时协作开发同一个项目,由于所有模块都在一个项目中,不同模块的代码之间物理边界越来越模糊。最终要把功能合并到一个分支,你绝对会陷入到解决冲突的泥潭之中。

系统发布效率低:任何模块变更都需要发布整个系统,而系统发布过程中需要多个模块之间制约较多,需要对比各种文件,任何一处出现问题都会导致发布失败,往往一次发布需要数十分钟甚至数小时。

系统可用性差:单体架构各个功能模块是作为一个服务部署,相互之间会互相影响,一些热点功能会耗尽系统资源,导致其它服务低可用。

1.2 微服务

微服务架构,首先是服务化,就是将单体架构中的功能模块从单体应用中拆分出来,独立部署为多个服务。同时要满足下面的一些特点:

单一职责:一个微服务负责一部分业务功能,并且其核心数据不依赖于其它模块。

团队自治:每个微服务都有自己独立的开发、测试、发布、运维人员,团队人员规模不超过10人(2张披萨能喂饱)

服务自治:每个微服务都独立打包部署,访问自己独立的数据库。并且要做好服务隔离,避免对其它服务产生影响

分布式就是服务拆分的过程,其实微服务架构正是分布式架构的一种最佳实践的方案。

1.3 SpringCloud

微服务拆分以后碰到的各种问题都有对应的解决方案和微服务组件,而SpringCloud框架可以说是目前Java领域最全面的微服务组件的集合了。

2 服务拆分原则

2.1 什么时候拆

对于大多数小型项目来说,一般是先采用单体架构 ,随着用户规模扩大、业务复杂后再逐渐拆分为 微服务架构 。这样初期成本会比较低,可以快速试错。但是,这么做的问题就在于后期做服务拆分时,可能会遇到很多代码耦合带来的问题,拆分比较困难(前易后难)。

而对于一些大型项目,在立项之初目的就很明确,为了长远考虑,在架构设计时就直接选择微服务架构。虽然前期投入较多,但后期就少了拆分服务的烦恼(前难后易)。

2.2 怎么拆

高内聚:每个微服务的职责要尽量单一,包含的业务相互关联度高、完整度高。

低耦合:每个微服务的功能要相对独立,尽量减少对其它微服务的依赖,或者依赖接口的稳定性要强。

hmall可以分为以下几个微服务

  • 用户服务

  • 商品服务

  • 订单服务

  • 购物车服务

  • 支付服务

拆分方式:

  1. 纵向拆分:就是按照项目的功能模块来拆分

  2. 抽取公共部分,提高复用性

2.3 服务调用

在拆分的时候发现一个问题:就是购物车业务中需要查询商品信息,但商品信息查询的逻辑全部迁移到了item-service服务,导致无法查询。

最终结果就是查询到的购物车数据不完整,因此要想解决这个问题就必须改造其中的代码,把原本本地方法调用,改造成跨微服务的远程调用(RPC,即R emote P roduce Call)。

查询购物车流程:

Java发送http请求可以使用Spring提供的RestTemplate,使用的基本步骤如下:

  • 注册RestTemplate到Spring容器

  • 调用RestTemplate的API发送请求,常见方法有:

    • getForObject:发送Get请求并返回指定类型对象

    • PostForObject:发送Post请求并返回指定类型对象

    • put:发送PUT请求

    • delete:发送Delete请求

    • exchange:发送任意类型请求,返回ResponseEntity

3. 服务注册与发现

我们通过Http请求实现了跨微服务的远程调用,不过这种手动发送Http请求的方式存在一些问题。

此时,每个item-service的实例其IP或端口不同,问题来了:

  • item-service这么多实例,cart-service如何知道每一个实例的地址?

  • http请求要写url地址,cart-service服务到底该调用哪个实例呢?

  • 如果在运行过程中,某一个item-service实例宕机,cart-service依然在调用该怎么办?

  • 如果并发太高,item-service临时多部署了N台实例,cart-service如何知道新实例的地址?

为了解决上述问题,引入注册中心概念。

3.1 注册中心原理

在微服务远程调用的过程中 包括俩角色:

1.服务提供者:提供接口供其他微服务访问

2.服务消费者:调用其他微服务提供的接口

在大型微服务项目中,服务提供者的数量会非常多,为了管理这些服务就引入了注册中心的概念。注册中心、服务提供者、服务消费者三者间关系如下:

流程如下:

  • 服务启动时就会注册自己的服务信息(服务名、IP、端口)到注册中心

  • 调用者可以从注册中心订阅想要的服务,获取服务对应的实例列表(1个服务可能多实例部署)

  • 调用者自己对实例列表负载均衡,挑选一个实例

  • 调用者向该实例发起远程调用

当服务提供者的实例宕机或者启动新实例时,调用者如何得知呢?

  • 服务提供者会定期向注册中心发送请求,报告自己的健康状态(心跳请求)

  • 当注册中心长时间收不到提供者的心跳时,会认为该实例宕机,将其从服务的实例列表中剔除

  • 当服务有新实例启动时,会发送注册服务请求,其信息会被记录在注册中心的服务实例列表

  • 当注册中心服务列表变更时,会主动通知微服务,更新本地服务列表

3.2 Nacos注册中心

基于Docker来部署Nacos的注册中心

  1. 首先要准备MySQL数据库表,用来存储Nacos的数据

  2. 导入nacos文件夹

  3. 进入root目录 执行

    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

启动完成后,访问下面地址:http://192.168.79.132:8848/nacos/,注意将IP地址替换为你自己的虚拟机IP地址。

首次访问会跳转到登录页,账号密码都是nacos

3.3 服务注册

步骤如下:

  • 引入依赖

  • 配置Nacos地址

  • 重启

3.3.1 添加依赖

<!--nacos 服务注册发现-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

3.3.2 配置Nacos

spring:
  application:
    name: item-service # 服务名称
  cloud:
    nacos:
      server-addr: 192.168.150.101:8848 # nacos地址

3.3.3 启动服务实例

为了测试一个服务多个实例的情况,我们再配置一个item-service的部署实例

然后配置启动项,注意重命名并且配置新的端口,避免冲突

重启这两个实例

访问nacos控制台,可以发现服务注册成功

3.4 服务发现

服务的消费者要去nacos订阅服务,这个过程就是服务发现,步骤如下:

  • 引入依赖

  • 配置Nacos地址

  • 发现并调用服务

3.4.1 发现并调用服务

接下来,服务调用者cart-service就可以去订阅item-service服务了。不过item-service有多个实例,而真正发起调用时只需要知道一个实例的地址。

因此,服务调用者必须利用负载均衡的算法,从多个实例中挑选一个去访问。常见的负载均衡算法有:

  • 随机

  • 轮询

  • IP的hash

  • 最近最少访问

  • ...

我们可以选择最简单的随机负载均衡。

另外,服务发现需要用到一个工具,DiscoveryClient,SpringCloud已经帮我们自动装配,我们可以直接注入使用:

我们通过DiscoveryClient发现服务实例列表,然后通过负载均衡算法,选择一个实例去调用:

4 OpenFeign

4.1 快速入门

以cart-service中的查询我的购物车为例。因此下面的操作都是在cart-service中进行。

4.1.1 引入依赖

cart-service服务的pom.xml中引入OpenFeign的依赖和loadBalancer依赖:

  <!--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>

4.1.2 启用OpenFeign

接下来,我们在cart-serviceCartApplication启动类上添加注解,启动OpenFeign功能:

4.1.3 编写OpenFeign客户端

cart-service中,定义一个新的接口,编写Feign客户端:

package com.hmall.cart.client;

import com.hmall.cart.domain.dto.ItemDTO;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

import java.util.List;

@FeignClient("item-service")
public interface ItemClient {

    @GetMapping("/items")
    List<ItemDTO> queryItemByIds(@RequestParam("ids") Collection<Long> ids);
}

这里只需要声明接口,无需实现方法。接口中的几个关键信息:

  • @FeignClient("item-service") :声明服务名称

  • @GetMapping :声明请求方式

  • @GetMapping("/items") :声明请求路径

  • @RequestParam("ids") Collection<Long> ids :声明请求参数

  • List<ItemDTO> :返回值类型

有了上述信息,OpenFeign就可以利用动态代理帮我们实现这个方法,并且向http://item-service/items发送一个GET请求,携带ids为请求参数,并自动将返回值处理为List<ItemDTO>

我们只需要直接调用这个方法,即可实现远程调用了。

4.1.4 使用FeignClient

我们在cart-servicecom.hmall.cart.service.impl.CartServiceImpl中改造代码,直接调用ItemClient的方法:

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

4.2 连接池

Feign底层发起http请求,依赖于其它的框架。其底层支持的http客户端实现包括:

  • HttpURLConnection:默认实现,不支持连接池

  • Apache HttpClient :支持连接池

  • OKHttp:支持连接池

因此我们通常会使用带有连接池的客户端来代替默认的HttpURLConnection。比如,我们使用OK Http.

4.2.1 引入依赖

cart-servicepom.xml中引入依赖:

<!--OK http 的依赖 -->
<dependency>
  <groupId>io.github.openfeign</groupId>
  <artifactId>feign-okhttp</artifactId>
</dependency>

4.2.2 开启连接池

cart-serviceapplication.yml配置文件中开启Feign的连接池功能:

feign:
  okhttp:
    enabled: true # 开启OKHttp功能

4.3 最佳实践

如果拆分了交易微服务(trade-service),它也需要远程调用item-service中的根据id批量查询商品功能。这个需求与cart-service中是一样的。

因此,我们就需要在trade-service中再次定义ItemClient接口,这就是重复编码了

4.3.1 思路分析

避免重复编码的办法就是抽取。不过这里有两种抽取思路:

  • 思路1:抽取到微服务之外的公共module

  • 思路2:每个微服务自己抽取一个module

方案1抽取更加简单,工程结构也比较清晰,但缺点是整个项目耦合度偏高。

方案2抽取相对麻烦,工程结构相对更复杂,但服务之间耦合度降低。

4.3.2 抽取Feign客户端

hmall下定义一个新的module,命名为hm-api

其依赖如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>hmall</artifactId>
        <groupId>com.heima</groupId>
        <version>1.0.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>hm-api</artifactId>

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
    </properties>

    <dependencies>
        <!--open feign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <!-- load balancer-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
        </dependency>
        <!-- swagger 注解依赖 -->
        <dependency>
            <groupId>io.swagger</groupId>
            <artifactId>swagger-annotations</artifactId>
            <version>1.6.6</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>
</project>

然后把ItemDTO和ItemClient都拷贝过来。

现在,任何微服务要调用item-service中的接口,只需要引入hm-api模块依赖即可,无需自己编写Feign客户端了。

4.3.3 扫描包

我们在cart-servicepom.xml中引入hm-api模块:

  <!--feign模块-->
  <dependency>
      <groupId>com.heima</groupId>
      <artifactId>hm-api</artifactId>
      <version>1.0.0</version>
  </dependency>

因为ItemClient现在定义到了com.hmall.api.client包下,而cart-service的启动类定义在com.hmall.cart包下,扫描不到ItemClient

解决办法很简单,在cart-service的启动类上添加声明即可,两种方式:

  • 方式1:声明扫描包:
  • 方式2:声明要用的FeignClient

4.4 日志配置

OpenFeign只会在FeignClient所在包的日志级别为DEBUG时,才会输出日志。而且其日志级别有4级:

  • NONE:不记录任何日志信息,这是默认值。

  • BASIC:仅记录请求的方法,URL以及响应状态码和执行时间

  • HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息

  • FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据。

Feign默认的日志级别就是NONE,所以默认我们看不到请求日志。

4.4.1 定义日志级别

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

package com.hmall.api.config;

import feign.Logger;
import org.springframework.context.annotation.Bean;

public class DefaultFeignConfig {
    @Bean
    public Logger.Level feignLogLevel(){
        return Logger.Level.FULL;
    }
}

4.4.2 配置

接下来,要让日志级别生效,还需要配置这个类。有两种方式:

  • 局部 生效:在某个FeignClient中配置,只对当前FeignClient生效

    @FeignClient(value = "item-service", configuration = DefaultFeignConfig.class)

  • 全局 生效:在@EnableFeignClients中配置,针对所有FeignClient生效。

    @EnableFeignClients(defaultConfiguration = DefaultFeignConfig.class)

4 总结

4.1 如何利用 OpenFeign 实现远程调用?
  1. 引入依赖:

    引入 OpenFeign 和 Spring Cloud LoadBalancer 的相关依赖。

  2. 启用 OpenFeign 功能:

    在主程序类上添加 @EnableFeignClients 注解,开启 OpenFeign 的功能。

  3. 定义 FeignClient 接口:

    编写 @FeignClient 注解的接口,指定远程服务名称和对应的路径,通过方法调用实现远程服务的访问。


4.2 如何配置 OpenFeign 的连接池?
  1. 引入 Http 客户端依赖:

    根据需求选择适合的 Http 客户端,例如 OKHttp 或 HttpClient,并引入相关依赖。

  2. 配置连接池参数:

    application.yml 文件中配置 OpenFeign 的连接池:

    • 开启连接池功能。
    • 配置最大连接数、超时时间等参数。

4.3 OpenFeign 使用的最佳实践方式是什么?
  1. 服务提供者抽取公共模块:

    服务提供者将 FeignClient 接口及 DTO(数据传输对象)抽取到一个独立的模块中,供调用方直接依赖使用,保证代码的一致性。

  2. 接口复用:

    调用方通过依赖服务提供者的公共模块,减少代码冗余,避免重复定义接口和 DTO。


4.4 如何配置 OpenFeign 输出日志的级别?
  1. 声明日志级别的 Bean:

    定义一个类型为 Logger.Level 的 Bean,例如:

    @Bean
    public Logger.Level feignLoggerLevel() {
        return Logger.Level.FULL;
    }
    
  2. 启用日志配置:

    @FeignClient@EnableFeignClients 注解中,通过 defaultConfiguration 属性指定包含日志配置的类。例如:

    @EnableFeignClients(defaultConfiguration = DefaultFeignConfig.class)
    
  3. 日志级别说明:

    根据实际需求选择不同的日志级别:

    • NONE: 不输出任何日志。
    • BASIC: 记录请求方法、URL、响应状态码及执行时间。
    • HEADERS: 记录 BASIC 级别的内容以及请求和响应的头信息。
    • FULL: 记录请求和响应的所有内容,包括头信息和正文。

相关推荐
Ethel L9 分钟前
Postman[8] 断言
java·测试工具·postman
啊烨疯狂学java2 小时前
EasyExcel监听器详解
java·easyexcel
北极糊的狐3 小时前
SQL中,# 和 $ 用于不同的占位符语法
java·开发语言
漫漫不慢.4 小时前
九进制转10进制
java·开发语言
大小科圣4 小时前
windows配置jdk
java·开发语言
鲤籽鲲5 小时前
C# 内置值类型
android·java·c#
N串5 小时前
供应链系统设计-供应链中台系统设计(七)- 商品中心设计篇
经验分享·架构·系统架构
顾北辰205 小时前
利用Deeplearning4j进行 图像识别
java·spring boot·机器学习
斯普信专业组5 小时前
KAFKA入门:原理架构解析
架构·kafka
虫本初阳5 小时前
【Java】集合中的List【主线学习笔记】
java·学习·list