day03-微服务 01

YangeIT大约 53 分钟基础服务框架微服务

day03-微服务 01

相关信息

之前我们学习的项目一是单体项目,可以满足小型项目或传统项目的开发。而在互联网时代,越来越多的一线互联网公司都在使用微服务技术。

从谷歌搜索指数来看,国内从自 2016 年底开始,微服务热度突然暴涨:

那么:

  • 到底什么是微服务?
  • 企业该不该引入微服务?
  • 微服务技术该如何在企业落地?

接下来几天,我们就一起来揭开它的神秘面纱。

计划是这样的,课前资料中给大家准备了一个单体的电商小项目:黑马商城,我们会基于这个单体项目来演示从单体架构到微服务架构的演变过程、分析其中存在的问题,以及微服务技术是如何解决这些问题的。

你会发现每一个微服务技术都是在解决服务化过程中产生的问题,你对于每一个微服务技术具体的应用场景和使用方式都会有更深层次的理解。

今天作为课程的第一天,我们要完成下面的内容:

  • 知道单体架构的特点
  • 知道微服务架构的特点
  • 学会拆分微服务
  • 会使用 Nacos 实现服务治理
  • 会使用 OpenFeign 实现远程调用

0.导入黑马商城项目

导入黑马商城项目

在课前资料中给大家提供了黑马商城项目的资料,我们需要先导入这个单体项目。不过需要注意的是,本篇及后续的微服务学习都是基于 Centos7 系统下的 Docker 部署,因此你必须做好一些准备:

  • Centos7 的环境及一个好用的 SSH 客户端
  • 安装好 Docker
  • 会使用 Docker

如果你没有这样的 Linux 环境,或者不是 Centos7 的话,那么这里有一篇参考文档:

建议按照上面的文档来搭建虚拟机环境,使用其它版本会出现一些环境问题,比较痛苦。

如果已经有 Linux 环境,但是没有安装 Docker 的话,那么这里还有一篇参考文档:

如果不会使用 Docker 的话可以参考的微服务前置 Docker 课程 👈

代码操作

安装MySQL

在课前资料提供好了 MySQL 的一个目录:

其中有 MySQL 的配置文件和初始化脚本:

我们将其复制到虚拟机的 /root 目录。如果 /root 下已经存在 mysql 目录则删除旧的,如果不存在则直接复制本地的:

然后创建一个通用网络:

docker network create hm-net

使用下面的命令来安装 MySQL:

docker run -d \
  --name mysql \
  -p 3306:3306 \
  -e TZ=Asia/Shanghai \
  -e MYSQL_ROOT_PASSWORD=123 \
  -v /root/mysql/data:/var/lib/mysql \
  -v /root/mysql/conf:/etc/mysql/conf.d \
  -v /root/mysql/init:/docker-entrypoint-initdb.d \
  --network hm-net\
  mysql

此时,通过命令查看 mysql 容器:

docker ps

如图:

发现 mysql 容器正常运行。

注:图片中的 dps 命令是我设置的别名,等同于 docker ps --format,可以简化命令格式。你可以参考黑马的 day02-Dockeropen in new window 的 2.1.3 小节来配置。

此时,如果我们使用 MySQL 的客户端工具连接 MySQL,应该能发现已经创建了黑马商城所需要的表:

总结

课堂作业

  1. 按照上述的步骤,将黑马商城的单体项目部署起来,并访问🎤

1.认识微服务

本章从单体架构的优缺点来分析,探寻开发大型项目采用单体架构存在哪些问题,而微服务架构又是如何解决这些问题的。🎯

1.1.单体架构

单体架构

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

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

  2. 但随着项目的业务规模越来越大,团队开发人员也不断增加,单体架构就呈现出越来越多的问题

    • 团队协作成本高:试想一下,你们团队数十个人同时协作开发同一个项目,由于所有模块都在一个项目中,不同模块的代码之间物理边界越来越模糊。最终要把功能合并到一个分支,你绝对会陷入到解决冲突的泥潭之中。
    • 系统发布效率低:任何模块变更都需要发布整个系统,而系统发布过程中需要多个模块之间制约较多,需要对比各种文件,任何一处出现问题都会导致发布失败,往往一次发布需要数十分钟甚至数小时。
    • 系统可用性差:单体架构各个功能模块是作为一个服务部署,相互之间会互相影响,一些热点功能会耗尽系统资源,导致其它服务低可用。

在上述问题中,前两点相信大家在实战过程中应该深有体会。对于第三点系统可用性问题,很多同学可能感触不深。接下来我们就通过黑马商城这个项目,给大家做一个简单演示。

演示系统可用性差操作

首先,修改 hm-service 模块下的 com.hmall.controller.HelloController 中的 hello 方法,模拟方法执行时的耗时

image
image

接下来,启动项目,目前有两个接口是无需登录即可访问的:

  • http://localhost:8080/hi - 耗时300ms的接口
  • http://localhost:8080/search/list - 正常请求接口

经过测试,目前 /search/list 是比较正常的,访问耗时在 30 毫秒左右。

网络请求截图
网络请求截图

总结

课堂作业

  1. 单体架构的优缺点是什么?🎤
  2. Jemeter的作用是什么?🎤

1.2.微服务

微服务

微服务架构 ,首先是服务化,就是将单体架构中的功能模块从单体应用中拆分出来,独立部署为多个服务。

同时要满足下面的一些特点:

  • 单一职责:一个微服务负责一部分业务功能,并且其核心数据不依赖于其它模块。
  • 团队自治:每个微服务都有自己独立的开发、测试、发布、运维人员,团队人员规模不超过 10 人
  • 服务自治:每个微服务都独立打包部署,访问自己独立的数据库。并且要做好服务隔离,避免对其它服务产生影响

例如,黑马商城项目,我们就可以把商品、用户、购物车、交易等模块拆分,交给不同的团队去开发,并独立部署:

那么,单体架构存在的问题有没有解决呢?

点击查看
  • 团队协作成本高?

    • 由于服务拆分,每个服务代码量大大减少,参与开发的后台人员在 1~3 名,协作成本大大降低
  • 系统发布效率低?

    • 每个服务都是独立部署,当有某个服务有代码变更时,只需要打包部署该服务即可
  • 系统可用性差?

    • 每个服务独立部署,并且做好服务隔离,使用自己的服务器资源,不会影响到其它服务。

综上所述,微服务架构解决了单体架构存在的问题,特别适合大型互联网项目的开发,因此被各大互联网公司普遍采用。

总结

  1. 微服务架构解决了单体架构存在的问题,特别适合大型互联网项目的开发,因此被各大互联网公司普遍采用。

课堂作业

  1. 微服务架构有什么特点?他是怎么解决单体架构的存在的问题的?🎤
  2. 微服务架构虽然能解决单体架构的各种问题,但在拆分的过程中,还会面临哪些问题?🎤

1.3.SpringCloud

SpringCloud

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

官网:https://spring.io/projects/spring-cloudopen in new windowimage

Spring Cloud 为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理、服务发现、熔断器、智能路由、微代理)

SpringCloud 依托于 SpringBoot 的自动装配能力,大大降低了其项目搭建、组件使用的成本。对于没有自研微服务组件能力的中小型企业,使用 SpringCloud全家桶来实现微服务开发可以说是最合适的选择!

总结

课堂作业

  1. SpringCloud是什么?有什么作用?能够解决什么问题?🎤
  2. SpringCloudAlibabaopen in new window是什么,和SpringCloud有什么关系?是哪家公司开发的?🎤

2.微服务拆分

接下来,将黑马商城这个单体项目拆分为微服务项目,并解决其中出现的各种问题。🎯

2.1.熟悉黑马商城

熟悉黑马商城

黑马商城是类似京东open in new window的电商商城系统,旨在给用户一个高效,流畅的购物体验,

主要模块:image

通过debug模式熟悉各个模块

首先,我们需要熟悉黑马商城项目的基本结构:

大家可以直接启动该项目,测试效果。不过,需要修改数据库连接参数,在 application-local.yaml 中:

hm:
  db:
    host: 192.168.150.101 # 修改为你自己的虚拟机IP地址
    pw: 123 # 修改为docker中的MySQL密码

同时配置启动项激活的是 local 环境:

总结

课堂作业

  1. 黑马商城是一个什么的项目?他有哪些友商?🎤
  2. 黑马商城主要有哪些模块,简单介绍一下?🎤
  3. 找一个和黑马商城很类似的项目,对比一下功能,看是否类似?--迁移能力✏️

2.2.服务拆分原则

本章讲解拆分成微服务,应该遵循的原则。 🎯

服务拆分原则

服务拆分一定要考虑几个问题:

  • 什么时候拆?
  • 如何拆?

一般情况下,对于初创的项目

  1. 首先要做的是验证项目的可行性。因此首要任务是敏捷开发,快速产出生产可用的产品,投入市场做验证。
  2. 为了达成验证目的,该阶段项目架构往往会比较简单,很多情况下会直接采用单体架构,这样开发成本比较低,可以快速产出结果,一旦发现项目不符合市场,损失较小。
  3. 如果这一阶段采用复杂的微服务架构,投入大量的人力和时间成本用于架构设计,最终发现产品不符合市场需求,等于全部做了无用功。

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

对于大型项目:

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

总结

课堂作业

  1. 初创的项目,一般采用什么架构,为什么?🎤
  2. 对于大型项目,一般采用什么架构,为什么?
  3. 服务拆分一定要考虑拆分方向?请问苍穹外卖要拆分的话,是横向还是纵向?🎤

2.3.拆分购物车、商品服务

拆分购物车、商品服务

接下来,先把商品管理功能、购物车功能抽取为两个独立服务。🎯

一般微服务项目有两种不同的工程结构:

  • 完全解耦:每一个微服务都创建为一个独立的工程,甚至可以使用不同的开发语言来开发,项目完全解耦。

    • 优点 :服务之间耦合度低
    • 缺点 :每个项目都有自己的独立仓库,管理起来比较麻烦
  • Maven聚合:整个项目为一个 Project,然后每个微服务是其中的一个 Module

    • 优点 :项目代码集中,管理和运维方便
    • 缺点 :服务之间耦合,编译时间较长

在 hmall 父工程之中,已经提前定义了 SpringBoot、SpringCloud 的依赖版本,所以为了方便期间,直接在这个项目中创建微服务 module.

image
image

拆分代码操作

涉及的模块

  1. 商品服务
  2. 购物车服务

商品服务

在 hmall 中创建 module

选择maven模块,并设定 JDK 版本为 11

image
image

商品模块,我们起名为 item-service

image
image
  1. 引入依赖:
<?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>item-service</artifactId>

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
    </properties>
    <dependencies>
        <!--common-->
        <dependency>
            <groupId>com.heima</groupId>
            <artifactId>hm-common</artifactId>
            <version>1.0.0</version>
        </dependency>
        <!--web-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--数据库-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!--mybatis-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
        </dependency>
        <!--单元测试-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
    </dependencies>
    <build>
        <finalName>${project.artifactId}</finalName>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

2.编写启动类:

代码如下:

package com.hmall.item;
@MapperScan("com.hmall.item.mapper")
@SpringBootApplication
public class ItemApplication {
    public static void main(String[] args) {
        SpringApplication.run(ItemApplication.class, args);
    }
}

接下来是配置文件,可以从 hm-service 中拷贝:

image
image

其中,application.yaml 内容如下:

server:
  port: 8081 #修改端口
spring:
  application:
    name: item-service #修改名字
  profiles:
    active: dev
  datasource:
    url: jdbc:mysql://${hm.db.host}:3306/hmall?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&serverTimezone=Asia/Shanghai
    driver-class-name: com.mysql.jdbc.Driver
    username: ${hm.db.user}
    password: ${hm.db.pw}
mybatis-plus:
  configuration:
    default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler
  global-config:
    db-config:
      update-strategy: not_null
      id-type: auto
logging:
  level:
    com.hmall: debug
  pattern:
    dateformat: HH:mm:ss:SSS
  file:
    path: "logs/${spring.application.name}"
knife4j:
  enable: true
  openapi:
    title: 商品服务接口文档 #修改接口文档
    description: "商品服务接口文档"
    email: huyan@itcast.cn
    concat: yangeit
    url: http://www.yangeit.cn:21010/
    version: v1.0.0
    group:
      default:
        group-name: default
        api-rule: package
        api-rule-resources:
          - com.hmall.item.controller #修改扫描位置


 


 
























 
 
 
 
 
 
 
 
 
 
 
 

剩下的 application-dev.yamlapplication-local.yaml 直接从 hm-service 拷贝即可。

item-service中创建对应的包:

image
image

然后拷贝 hm-service 中与商品管理有关的代码到 item-service,如图:

这里有一个地方的代码需要改动,就是 ItemServiceImpl 中的 deductStock 方法:

这也是因为 ItemMapper 的所在包发生了变化,因此这里代码必须修改包路径。 image

最后,还要导入数据库表。默认的数据库连接的是虚拟机,在你 docker 数据库执行课前资料提供的 SQL 文件:

最终,会在数据库创建一个名为 hm-item 的 database,将来的每一个微服务都会有自己的一个 database:

image
image

在yaml配置文件中,修改数据库名字

image
image

接下来,就可以启动测试了,在启动前我们要配置一下启动项,让默认激活的配置为 local 而不是 dev

在打开的编辑框填写 active profiles:

接着,启动 item-service,访问商品微服务的 swagger 接口文档:http://localhost:8081/doc.htmlopen in new window

image
image

然后测试其中的根据 id 批量查询商品这个接口:

测试参数:100002672302,100002624500,100002533430,结果如下:

image
image

🎉恭喜你🎉,商品微服务抽取成功了。

总结

课堂作业

  1. 上述操作汇总,为什么要将数据库单独部署?🎤
  2. 参考上述步骤,完成商品和购物车模块的拆分!!✏️

2.4.服务调用

接下来解决: cart-service 服务中实现对 item-service 服务的调用🎯

服务调用

在拆分的时候,我们发现一个问题:

就是购物车业务中需要查询商品信息,但商品信息查询的逻辑全部迁移到了 item-service 服务,导致我们无法查询。

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

因此,现在查询购物车列表的流程变成了这样: image

点击查看流程图open in new window

代码中需要变化的就是这一步:

image
image

那么问题来了:我们该如何跨服务调用,准确的说,如何在 cart-service 中获取 item-service 服务中的提供的商品数据呢?

大家思考一下,我们以前有没有实现过类似的远程查询的功能呢?

案是肯定的,在苍穹外卖中我们通过httpclient调用了微信的服务实现微信openid的获取,就是服务调用服务。

那么:我们该如何用 Java 代码发送 Http 的请求呢?👇

使用RestTemplate可以方便的实现 Http 请求的发送

代码操作

核心步骤

  1. 熟悉RestTemplate,并注入RestTemplate对象
  2. 远程调用

1.RestTemplate

Spring 给我们提供了一个 RestTemplate 的 API,可以方便的实现 Http 请求的发送。

org.springframework.web.client public class RestTemplate extends InterceptingHttpAccessor implements RestOperations


同步客户端执行 HTTP 请求,在底层 HTTP 客户端库(如 JDK HttpURLConnection、Apache HttpComponents 等)上公开一个简单的模板方法 API。RestTemplate 通过 HTTP 方法为常见场景提供了模板,此外还提供了支持不太常见情况的通用交换和执行方法。 RestTemplate 通常用作共享组件。然而,它的配置不支持并发修改,因此它的配置通常是在启动时准备的。如果需要,您可以在启动时创建多个不同配置的 RestTemplate 实例。如果这些实例需要共享 HTTP 客户端资源,它们可以使用相同的底层 ClientHttpRequestFactory。 注意:从 5.0 开始,这个类处于维护模式,只有对更改和错误的小请求才会被接受。请考虑使用 org.springframework.web.react .client. webclient,它有更现代的 API,支持同步、异步和流场景。


自: 3.0 参见: HttpMessageConverter, RequestCallback, ResponseExtractor, ResponseErrorHandler

其中提供了大量的方法,方便我们发送 Http 请求,例如:

可以看到常见的 Get、Post、Put、Delete 请求都支持,如果请求参数比较复杂,还可以使用 exchange 方法来构造请求。

我们在 cart-service 服务中定义一个配置类:

先将 RestTemplate 注册为一个 Bean: image

在CartServiceImpl中注入RestTemplate对象

image
image

总结

什么时候需要拆分微服务?

  • 如果是创业型公司,最好先用单体架构快速迭代开发,验证市场运作模型,快速试错。当业务跑通以后,随着业务规模扩大、人员规模增加,再考虑拆分微服务。
  • 如果是大型企业,有充足的资源,可以在项目开始之初就搭建微服务架构。

如何拆分?

  • 首先要做到高内聚、低耦合
  • 从拆分方式来说,有横向拆分和纵向拆分两种。纵向就是按照业务功能模块,横向则是拆分通用性业务,提高复用性

服务拆分之后,不可避免的会出现跨微服务的业务,此时微服务之间就需要进行远程调用。微服务之间的远程调用被称为 RPC,即远程过程调用。RPC 的实现方式有很多,比如:

  • 基于 Http 协议
  • 基于 Dubbo 协议

我们课堂中使用的是 Http 方式,这种方式不关心服务提供者的具体技术实现,只要对外暴露 Http 接口即可,更符合微服务的需要。

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

  • 注册 RestTemplate 到 Spring 容器

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

    • getForObject:发送 Get 请求并返回指定类型对象
    • PostForObject:发送 Post 请求并返回指定类型对象
    • put:发送 PUT 请求
    • delete:发送 Delete 请求
    • exchange:发送任意类型请求,返回 ResponseEntity

课堂作业

  1. RestTemplate是什么,有何作用?🎤
  2. 参考上述步骤,完成购物车服务对商品服务的调用 ✏️

3.服务注册和发现

3.1.注册中心原理

注册中心是服务治理中的重要组件,用于管理服务实例的数量和状态,提供服务注册和发现的功能。🎯

注册中心原理

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

试想一下,假如商品微服务被调用较多,为了应对更高的并发,我们进行了多实例部署,如图:

image
image

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

  • item-service 这么多实例,cart-service 如何知道每一个实例的地址
  • http 请求要写 url 地址,cart-service 服务到底该调用哪个实例呢?
  • 如果在运行过程中,某一个 item-service 实例宕机cart-service 依然在调用该怎么办?
  • 如果并发太高,item-service 临时多部署了N台实例cart-service 如何知道新实例的地址?

为了解决上述问题,就必须引入注册中心的概念了,接下来我们就一起来分析下注册中心的原理。🎯

总结

  • 服务治理中的三个角色分别是什么?
    • 服务提供者:暴露服务接口,供其它服务调用
    • 服务消费者:调用其它服务提供的接口
  • 注册中心:记录并监控微服务各实例状态,推送服务变更信息
    • 消费者如何知道提供者的地址?
    • 服务提供者会在启动时注册自己信息到注册中心,消费者可以从注册中心订阅和拉取服务信息
  • 消费者如何得知服务状态变更?
    • 服务提供者通过心跳机制向注册中心报告自己的健康状态,当心跳异常时注册中心会将异常服务剔除,并通知订阅了该服务的消费者
  • 当提供者有多个实例时,消费者该选择哪一个?
    • 消费者可以通过负载均衡算法,从多个实例中选择一个

课堂作业

  1. 什么是注册中心?他有什么功能?🎤
  2. 服务的提供者向注册中心提供什么信息?🎤
  3. 服务的调用者从注册中心获取什么信息?🎤
  4. 点击图片,补充空白区域的内容open in new window

3.2.Nacos 注册中心

Nacos注册中心

目前开源的注册中心框架有很多,国内比较常见的有:

  • Eureka:Netflix 公司出品,目前被集成在 SpringCloud 当中,一般用于 Java 应用
  • Nacos:Alibaba 公司出品,目前被集成在 SpringCloudAlibaba 中,一般用于 Java 应用
  • Consul:HashiCorp 公司出品,目前集成在 SpringCloud 中,不限制微服务语言

以上几种注册中心都遵循 SpringCloud 中的 API 规范,因此在业务开发使用上没有太大差异。由于 Nacos 是国内产品,中文文档比较丰富,而且同时具备配置管理功能(后面会学习),因此在国内使用较多,我们会 Nacos 为例来学习。

官方网站如下: https://nacos.io/zh-cn/docs/quick-start.htmlopen in new window

image
image

nacos部署方式:

  1. win部署
  2. docker部署实际开发

nacos部署操作

我们基于 Docker 来部署 Nacos 的注册中心,首先我们要准备 MySQL 数据库表,用来存储 Nacos 的数据。由于是 Docker 部署,所以大家需要将资料中的 SQL 文件导入到你 Docker 中的 MySQL 容器中:

最终表结构如下:

image
image

然后,找到课前资料下的 nacos 文件夹:

其中的 nacos/custom.env 文件中,有一个MYSQL_SERVICE_HOST 也就是 mysql 地址,需要修改为你自己的虚拟机 IP 地址:

image
image

总结

课堂作业

  1. Nacos是什么?能解决什么问题?🎤
  2. Nacos安装后,默认的端口是什么?🎤
  3. 参考上述文档,在docker部署nacos注册中心?✏️

3.3.服务注册

服务注册

接下来,我们把 item-service 注册到 Nacos,🎯 步骤如下:

  • 引入依赖
  • 配置 Nacos 地址
  • 重启

代码操作

3.3.1.添加依赖

item-servicepom.xml 中添加依赖:

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

🎉恭喜你🎉,掌握了nacos服务注册,距离微服务调用越来越近了

总结

课堂作业

  1. 为什么要进行服务注册?🎤
  2. 向注册中心注册服务,主要要配置哪些信息?🎤

3.4.服务发现

服务发现

接下来在cart-service中发现注册中心的服务,服务的消费者cart-service要去 nacos 订阅服务,这个过程就是服务发现,步骤如下:

  • 引入依赖
  • 配置 Nacos 地址
  • 发现并调用服务

代码操作

引入依赖

服务发要引入 nacos 依赖

我们在 cart-service 中的 pom.xml 中添加下面的依赖:

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

可以发现,这里 Nacos 的依赖于服务注册时一致,这个依赖中同时包含了服务注册和发现的功能。因为任何一个微服务都可以调用别人,也可以被别人调用,即可以是调用者,也可以是提供者。

等下,cart-service 启动,同样会注册到 Nacos

总结

课堂作业

  1. 服务是从哪里发现的?如何获取服务的调用路径🎤
  2. 谈谈对负载均衡理解?🎤
  3. 使用RestTemplate+DiscoveryClient存在什么问题吗?open in new window🎤

4.OpenFeign

4.1.OpenFeign定义和入门案例

本节目标:改变远程调用的开发模式,让远程调用像本地方法调用一样简单 🎯

OpenFeign

利用 Nacos 实现了服务的治理,利用 RestTemplate 实现了服务的远程调用。但是远程调用的代码太复杂了:

image
image

而且这种调用方式,与原本的本地方法调用差异太大,编程时的体验也不统一,一会儿远程调用,一会儿本地调用。

因此,我们必须想办法改变远程调用的开发模式,让远程调用像本地方法调用一样简单🎯。而这就要用到 OpenFeign 组件了。


Feign一个声明式的http客户端,官方地址:https://github.com/OpenFeign/feignopen in new window

其作用就是帮助我们优雅的实现http请求的发送 🎯。

远程调用的关键点

image
image

OpenFeign 就利用 SpringMVC 的相关注解来声明上述 4 个参数,然后基于动态代理帮我们生成远程调用的代码,而无需我们手动再编写,非常方便。

OpenFeign代码操作

步骤

接下来,我们就通过一个快速入门的案例来体验一下 OpenFeign 的便捷吧。

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

  1. 引入openfeign依赖和loadbalancer依赖
  2. 启动类添加注解启用OpenFeign
  3. 编写 OpenFeign 客户端

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>

总结

RestTemplate存在下面的问题:👇

  • 代码可读性差 ,编程体验不统一
  • 参数复杂URL难以维护

OpenFeign优雅的解决了上述问题

课堂作业

  1. OpenFeign是什么?他和RestTemplate相同点??为什么要使用OpenFeign?🎤

4.2.连接池

连接池

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

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

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

通过debug查看:

代码操作

4.2.1.引入依赖

cart-servicepom.xml 中引入依赖:

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

总结

课堂作业

  1. 为什么要配置连接池?🎤

4.3.最佳实践 🍐 ❤️ ✏️

本节目标:解决不同微服务调用同一个微服务,避免在各自的微服务中重复书写ItemClient接口的问题🎯

最佳实践

将来我们要把与下单有关的业务抽取为一个独立微服务:trade-service,不过我们先来看一下 hm-service 中原本与下单有关的业务逻辑。

入口在 com.hmall.controller.OrderControllercreateOrder 方法,然后调用了 IOrderService 中的 createOrder 方法。

由于下单时前端提交了商品 id,为了计算订单总价,需要查询商品信息:

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

因此,我们就需要在 trade-service 中再次定义 ItemClient 接口,这不是重复编码吗? 有什么办法能加避免重复编码呢?

方案1操作

4.3.2.抽取 Feign 客户端

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

image
image

其依赖如下:

<?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 都拷贝过来,最终结构如下:

image
image

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

🎉恭喜你🎉,掌握Feign的最佳实践,和企业实际开发越来越近了

总结

课堂作业

  1. 最佳实践主要的原则是避免重复编码,省事儿,那主要有哪2种思路?各有什么特点?🎤

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;
    }
}

课后作业

🚩 1. 重点完成上述的课堂作业

  1. 晚自习第一节课的前30分钟,总结完毕之后,每个同学先必须梳理今日知识点 (记得写不知道的,以及感恩三件事);整理好的笔记可以发给组长,组长交给班长,意在培养大家总结的能力)

  2. 晚自习第一节课的后30分钟开始练习(记住:程序员是代码堆起来的):

    • 先要把今天的所有案例或者课堂练习,如果没练完的,练完他
    • 拆分微服务 👈
  3. 剩余的时间:预习第二天的知识,预习的时候一定要注意:

  • 预习不是学习,不要死看第二天的视频(很容易出现看了白看,为了看视频而看视频)
  • 预习看第二天的笔记,把笔记中标注重要的知识,可以找到预习视频,先看一遍,如果不懂的 ,记住做好标注。

5.作业:拆分微服务

作业

5.1.拆分微服务

将 hm-service 中的其它业务也都拆分为微服务,包括:

  • user-service:用户微服务,包含用户登录、管理等功能
  • trade-service:交易微服务,包含订单相关功能
  • pay-service:支付微服务,包含支付相关功能

其中交易服务、支付服务、用户服务中的业务都需要知道当前登录用户是谁,目前暂未实现,先将用户 id 写死。

思考:如何才能在每个微服务中都拿到用户信息?如何在微服务之间传递用户信息?

5.2.定义FeignClient

在上述业务中,包含大量的微服务调用,将被调用的接口全部定义为 FeignClient,将其与对应的 DTO 放在 hm-api 模块

5.3.将微服务与前端联调

课前资料提供了一个 hmall-nginx 目录,其中包含了 Nginx 以及我们的前端代码:

image
image

将其拷贝到一个不包含中文、空格、特殊字符的目录,启动后即可访问到页面:

  • 18080 是用户端页面
  • 18081 是管理端页面

之前 nginx 内部会将发向服务端请求全部代理到 8080 端口,但是现在拆分了 N 个微服务,8080 不可用了。请通过 Nginx 配置,完成对不同微服务的反向代理。

认真思考这种方式存在哪些问题有什么好的解决方案