《谷粒商城基础篇》分布式基础&环境搭建

Posted 爱编程的大李子

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《谷粒商城基础篇》分布式基础&环境搭建相关的知识,希望对你有一定的参考价值。

前沿:思考一个问题,为啥要做笔记?

为了知识更有条理,为了自己学过之后下次遇到立刻可以想起来,即使想不起,也可以通过自己的笔记快速定位~ 毕竟互联网的知识迭代速度非常之快

笔记更是知识输入的一条路径,没有输入就难以自我成长~

一、项目简介

1、项目背景

市面上有5 种常见的电商模式 B2B、B2C、C2B、C2C、O2O;

1、B2B 模式
B2B (Business to Business), 是指商家与商家建立的商业关系。如:阿里巴巴
2、B2C 模式
B2C (Business to Consumer), 就是我们经常看到的供应商直接把商品卖给用户,即“商对客”模式,也就是通常说的商业零售,直接面向消费者销售产品和服务。如:苏宁易购、京东、天猫、小米商城
3、C2B 模式
C2B (Customer to Business),即消费者对企业。先有消费者需求产生而后有企业生产,即先有消费者提出需求,后有生产企业按需求组织生产
4、C2C 模式
C2C (Customer to Consumer) ,客户之间自己把东西放上网去卖,如:淘宝,闲鱼
5、O2O 模式
O2O 即Online To Offline,也即将线下商务的机会与互联网结合在了一起,让互联网成为线下交易的前台。线上快速支付,线下优质服务。如:饿了么,美团,淘票票,京东到家

谷粒商城是一个B2C 模式的电商平台,销售自营商品给客户

2、项目架构图

项目微服务架构图:

微服务划分图:

3、项目技术&特色

  • 前后分离开发,并开发基于vue 的后台管理系统
  • SpringCloud 全新的解决方案
  • 应用监控、限流、网关、熔断降级等分布式方案全方位涉及
  • 透彻讲解分布式事务、分布式锁等分布式系统的难点
  • 分析高并发场景的编码方式,线程池,异步编排等使用
  • 压力测试与性能优化
  • 各种集群技术的区别以及使用
  • CI/CD 使用

4、项目前置要求

学习项目的前置知识

  • 熟悉SpringBoot 以及常见整合方案
  • 了解SpringCloud
  • 熟悉git,maven
  • 熟悉linux,redis,docker 基本操作
  • 了解html,css,js,vue
  • 熟练使用idea 开发项目

二、分布式基础概念

1、微服务

微服务架构风格,就像是把一个单独的应用程序开发为一套小服务,每个小服务运行在自己的进程中,并使用轻量级机制通信,通常是HTTP API。这些服务围绕业务能力来构建,并通过完全自动化部署机制来独立部署。这些服务使用不同的编程语言书写,以及不同数据存储技术,并保持最低限度的集中式管理。
简而言之:拒绝大型单体应用,基于业务边界进行服务微化拆分,各个服务独立部署运行。

2、集群&分布式&节点

集群是个物理形态,分布式是个工作方式。
只要是一堆机器,就可以叫集群,他们是不是一起协作着干活,这个谁也不知道:

《分布式系统原理与范型》定义:
“分布式系统是若干独立计算机的集合,这些计算机对于用户来说就像单个相关系统”
分布式系统(distributed system)是建立在网络之上的软件系统。

分布式是指将不同的业务分布在不同的地方。
集群指的是将几台服务器集中在一起,实现同一业务。

例如:京东是一个分布式系统,众多业务运行在不同的机器,所有业务构成一个大型的业务集群。每一个小的业务,比如用户系统,访问压力大的时候一台服务器是不够的。我们就应该将用户系统部署到多个服务器,也就是每一个业务系统也可以做集群化分布式中的每一个节点,都可以做集群。而集群并不一定就是分布式的。

节点:集群中的一个服务器

3、远程调用

在分布式系统中,各个服务可能处于不同主机,但是服务之间不可避免的需要互相调用,我们称为远程调用。
SpringCloud 中使用HTTP+JSON 的方式完成远程调用

4、负载均衡

分布式系统中,A 服务需要调用B 服务,B 服务在多台机器中都存在,A 调用任意一个服务器均可完成功能。
为了使每一个服务器都不要太忙或者太闲,我们可以负载均衡的调用每一个服务器,提升网站的健壮性。
常见的负载均衡算法:

  • 轮询:为第一个请求选择健康池中的第一个后端服务器,然后按顺序往后依次选择,直到最后一个,然后循环。
  • 最小连接:优先选择连接数最少,也就是压力最小的后端服务器,在会话较长的情况下可以考虑采取这种方式。
  • 散列:根据请求源的IP 的散列(hash)来选择要转发的服务器。这种方式可以一定程度上保证特定用户能连接到相同的服务器。如果你的应用需要处理状态而要求用户能连接到和之前相同的服务器,可以考虑采取这种方式。

5、服务注册/发现&注册中心

A 服务调用B 服务,A 服务并不知道B 服务当前在哪几台服务器有,哪些正常的,哪些服务已经下线。解决这个问题可以引入注册中心;

如果某些服务下线,我们其他人可以实时的感知到其他服务的状态,从而避免调用不可用的服务

服务已上线就会被注册到注册中心,一下线就会从注册中心移除~
A服务想要调用B服务,就先去注册中心看一下哪些机器上有B服务,从而再根据策略进行调用,避免服务不可调用的情况

6、配置中心

每一个服务最终都有大量的配置,并且每个服务都可能部署在多台机器上。我们经常需要变更配置,我们可以让每个服务在配置中心获取自己的配置。
配置中心用来集中管理微服务的配置信息

7、服务熔断&服务降级

在微服务架构中,微服务之间通过网络进行通信,存在相互依赖,当其中一个服务不可用时,有可能会造成雪崩效应。要防止这样的情况,必须要有容错机制来保护服务。

1)、服务熔断
a. 设置服务的超时,当被调用的服务经常失败到达某个阈值,我们可以开启断路保护机制,后来的请求不再去调用这个服务。本地直接返回默认的数据
2)、服务降级
a. 在运维期间,当系统处于高峰期,系统资源紧张,我们可以让非核心业务降级运行。降级:某些服务不处理,或者简单处理【抛异常、返回NULL、调用Mock 数据、调用Fallback 处理逻辑】。

8、API 网关

在微服务架构中,API Gateway 作为整体架构的重要组件,它抽象了微服务中都需要的公共功能,同时提供了客户端负载均衡,服务自动熔断,灰度发布,统一认证,限流流控,日志统计等丰富的功能,帮助我们解决很多API 管理难题。

三、环境搭建

1、安装linux 虚拟机

老师使用的是VirtualBox搭建的,中间可能会有好多问题吧~ 建议使用VMware+Centos搭建,具体文档可参考尚硅谷韩顺平老师的Linux课程~

2、安装docker

参考文档:https://blog.csdn.net/LXYDSF/article/details/121514373

3、docker 安装mysql

① 下载镜像文件

docker pull mysql:5.7

② 创建并启动MySQL实例

docker run -p 3306:3306 --name mysql -v /mydata/mysql/log:/var/log/mysql -v /mydata/mysql/data:/var/lib/docker_mysql -v /mydata/mysql/conf:/etc/mysql/conf.d -e MYSQL_ROOT_PASSWORD=123456 -d mysql:5.7

参数说明:
-p 3306:3306:将容器的3306 端口映射到主机的3306 端口
-v /mydata/mysql/conf:/etc/mysql:将配置文件夹挂载到主机
-v /mydata/mysql/log:/var/log/mysql:将日志文件夹挂载到主机
-v /mydata/mysql/data:/var/lib/mysql/:将配置文件夹挂载到主机
-e MYSQL_ROOT_PASSWORD=root:初始化root 用户的密码

③ 修改MySQL 配置

vi /mydata/mysql/conf/my.cnf
[client]
default-character-set=utf8
[mysql]
default-character-set=utf8
[mysqld]
init_connect='SET collation_connection = utf8_unicode_ci'
init_connect='SET NAMES utf8'
character-set-server=utf8
collation-server=utf8_unicode_ci
skip-character-set-client-handshake
skip-name-resolve 

注意:解决MySQL 连接慢的问题
在配置文件中加入如下,并重启mysql
[mysqld]
skip-name-resolve #跳过域名解析

④通过容器的mysql 命令行工具连接

docker exec -it mysql mysql -uroot -p123456

⑤设置root 远程访问

grant all privileges on *.* to 'root'@'%' identified by 'root' with grant option;
flush privileges;

⑥进入容器文件系统

docker exec -it mysql /bin/bash

4、docker 安装Redis

① 下载镜像文件

docker pull redis

② 创建容器数据卷

mkdir -p /mydata/redis/conf
touch /mydata/redis/conf/redis.conf

因为 Redis容器中 /etc/redis下面默认是没有redis.conf的,所以会导致挂在时 /mydata/redis/conf/redis.conf创建的会是一个目录。。。
解决办法:我们提前创建好配置文件

③ 创建并启动Redis实例

#创建并启动RedisL实例
docker run -p 6379:6379 --name redis -v /mydata/redis/data:/data -v /mydata/redis/conf/redis.conf:/etc/redis/redis.conf -d redis redis-server /etc/redis/redis.conf

④使用redis 镜像执行redis-cli 命令连

docker exec -it redis redis-cli

5、开发环境统一

  • Maven
  • Idea&VsCode
  • Git

四、项目搭建

1、创建各个微服务项目

①从gitee 初始化一个项目

②使用SpringInitializer创建各个微服务项目

共同点:

1)、都需要web、openfeign工具
2)、每一个服务,包名com.atguigu.gulimall.xxx(product/order/ware/coupon/member)

模块名:

  • gulimall-product:商品服务

  • gulimall-ware:仓储服务、

  • gulimall-order:订单服务、

  • gulimall-coupon:优惠券服务、

  • gulimall-member:用户服务

注意:本次使用的Spring-boot版本为2.1.8.RELEASE ,SpringCloud版本为Greenwich.SR3

③整合项目

1、使gulimall聚合其他子项目

为gulimall父项目增加一个POM文件

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.atguigu.gulimall</groupId>
    <artifactId>gulimall</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>gulimall</name>
    <description>聚合服务</description>
    <packaging>pom</packaging>

    <modules>
        <module>gulimall-coupon</module>
        <module>gulimall-member</module>
        <module>gulimall-order</module>
        <module>gulimall-product</module>
        <module>gulimall-ware</module>
    </modules>

</project>

2、修改父项目的.gitignore的模板,并提交到Github上

项目中的mvnw mvnw.cmd .mvn /target/ .idea 均可以删掉,只用保留父项目的.gitignore 即可

2、数据库初始化

1、设置Docker容器开机自启动

[root@lxyStudy ~]# docker update redis --restart=always
redis
[root@lxyStudy ~]# docker update mysql --restart=always
mysql

注意:前提需要Docker服务需要开启自启哦~

2、将资料中的SQL在数据库中执行

3、人人开源搭建后台管理系统

码云上搜“人人开源”,随便进一个项目,然后到它的主页

操作步骤:

1)克隆这两个项目。git clone xxxxxxxx

2)把renren-fast的后端项目里的.git删掉,然后整个项目拖进我们的guli后端项目中,并在pom里加上<module>renren-fast</module>。然后启动该项目

3)在db文件夹下找到mysql.sql文件,导入gulimall_admin数据库。修改dev配置文件,如数据库的账号密码、库名

4)前端,把renren-fast-vue在VS Code中打开,npm install,然后解决bug… 。之后npm run dev启动项目

登录账号:admin 密码:admin

企业开发中不可能从0开始构建项目的,可以pull一个类似的开源项目,然后在里面增加一个我们需要的功能~

4、逆向工程搭建并生成所有微服务CRUD代码

①下载并导入 代码生成器:https://gitee.com/renrenio/renren-generator

② 创建gulimall-common项目,存放公共内容。如:下步导入生成CRUD代码使用的公共Utils、公共依赖:mysql-connector…

③ 修改renren-generator的application.yml,生成对应微服务的CRUD代码

运行以后会生成一个main文件夹和一堆sql文件。把生成的main文件夹复制到相应的模块中,替换原来src下的main。

resources中的src不要删除,后面会用到

修改renren-generator,对所有模块的CRUD代码进行生成~

④ 配置并测试每个项目

以商品模块为例:

  • applicaiton.yml
spring:
    datasource:
        username: root
        password: 123456
        url: jdbc:mysql://192.168.174.128:3306/gulimall_pms
        driver-class-name: com.mysql.cj.jdbc.Driver

mybatis-plus:
    mapper-locations: classpath:/mapper/**/*.xml #配置xml位置
    global-config:
        db-config:
            id-type: auto #设置id自增
  • 添加common依赖
<!--公共依赖-->
 <dependency>
    <groupId>com.atguigu.gulimall</groupId>
    <artifactId>gulimall-common</artifactId>
    <version>0.0.1-SNAPSHOT</version>
 </dependency>

之后可以启动项目,然后调用某一个接口,如:http://localhost:8080/product/brand/list,判断是否访问成功

其他模块也进行如上的配置和修改~

⑤ 为每个服务配置端口

coupon 7000
member 8000
order 9000
product 10000
ware 11000
server:
  port: xxxx

目的:之后对相应服务进行横向扩展的话很方便,比如coupon在服务器上部署n个,则对应的端口分别为:7000、7001、7002、7003…

五、分布式组件

1、SpringCloud Alibaba 简介

1、简介

Spring Cloud Alibaba 致力于提供微服务开发的一站式解决方案。此项目包含开发分布式应用微服务的必需组件,方便开发者通过Spring Cloud 编程模型轻松使用这些组件来开发分布式应用服务。

依托Spring Cloud Alibaba,您只需要添加一些注解和少量配置,就可以将Spring Cloud 应用接入阿里微服务解决方案,通过阿里中间件来迅速搭建分布式应用系统。

官方文档:https://github.com/alibaba/spring-cloud-alibaba

2、为什么使用

SpringCloud 的几大痛点

  • SpringCloud 部分组件停止维护和更新,给开发带来不便;
  • SpringCloud 部分环境搭建复杂,没有完善的可视化界面,我们需要大量的二次开发和定制
  • SpringCloud 配置复杂,难以上手,部分配置差别难以区分和合理应用

SpringCloud Alibaba 的优势:

  • 阿里使用过的组件经历了考验,性能强悍,设计合理,现在开源出来大家用
  • 成套的产品搭配完善的可视化界面给开发运维带来极大的便利
  • 搭建简单,学习曲线低。

结合SpringCloud Alibaba 我们最终的技术搭配方案:
SpringCloud Alibaba - Nacos:注册中心(服务发现/注册)
SpringCloud Alibaba - Nacos:配置中心(动态配置管理)
SpringCloud - Ribbon:负载均衡
SpringCloud - Feign:声明式HTTP 客户端(调用远程服务)

SpringCloud Alibaba - Sentinel:服务容错(限流、降级、熔断)
SpringCloud - Gateway:API 网关(webflux 编程模式)
SpringCloud - Sleuth:调用链监控

SpringCloud Alibaba - Seata:原Fescar,即分布式事务解决方案

3、版本选择

由于Spring Boot 1 和Spring Boot 2 在Actuator 模块的接口和注解有很大的变更,且spring-cloud-commons 从1.x.x 版本升级到2.0.0 版本也有较大的变更,因此我们采取跟SpringBoot 版本号一致的版本:

  • 1.5.x 版本适用于Spring Boot 1.5.x
  • 2.0.x 版本适用于Spring Boot 2.0.x
  • 2.1.x 版本适用于Spring Boot 2.1.x

4、项目中的依赖

在gulimall-common 项目中引入如下。方便进行统一管理

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-dependencies</artifactId>
            <version>2.1.0.RELEASE</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

2、SpringCloud Alibaba-Nacos[作为注册中心]

Nacos 是阿里巴巴开源的一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。他是使用java 编写。需要依赖java 环境。

Nacos Discovery Exaple:https://github.com/alibaba/spring-cloud-alibaba/blob/2022.x/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/readme-zh.md

1、引入依赖

修改common模块的 pom.xml 文件,引入 Nacos Discovery Starter。

 <dependency>
     <groupId>com.alibaba.cloud</groupId>
     <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
 </dependency>

2、下载Nacos

参考文档:https://nacos.io/zh-cn/docs/quick-start.html

CSDN博客:https://blog.csdn.net/LXYDSF/article/details/122776664

3、添加配置

在application.yml中配置:

spring:
  cloud:
    nacos:
      discovery:
        server-addr: 192.168.174.128:8848 #自己的IP+端口
  application:
    name: gulimall-order

为启动类添加 @EnableDiscoveryClient 注解,开启服务注册与发现功能

4、测试

启动服务,访问http://192.168.174.128:8848/nacos

3、Open-Feign远程调用

  • Feign是Netflix开发的声明式、模板化的HTTP客户端 Feign可以帮助我们更快捷、优雅地调用HTTP API
  • Feign支持多种注解,例如Feign自带的注解或者JAX-RS注解等。
  • Spring Cloud对Feign进行了增强,使Feign支持了Spring MVC注解,并整合了Ribbon和Eureka,从而让Feign的使用更加方便。
  • Spring Cloud Feign是基于Netflix feign实现,整合了Spring Cloud Ribbon和Spring Cloud Hystrix,除了提供这两者的强大功能外,还提供了一种声明式的Web服务客户端定义的方式。
  • Spring Cloud Feign帮助我们定义和实现依赖服务接口的定义。在Spring Cloud feign的实现下,只需要创建一个接口并用注解方式配置它,即可完成服务提供方的接口绑定,简化了在使用Spring Cloud Ribbon时自行封装服务调用客户端的开发量。

举例:注册member模块。然后测试它和coupon模块间的互通。

1、引入依赖

我们想要会员服务远程调用优惠券服务,就要在会员服务的pom中引入open-feign依赖。

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

2 在coupon中编写接口

@RequestMapping("/member/list")
public R memberCoupons()
    CouponEntity coupon = new CouponEntity();
    coupon.setCouponName("满100减10");
    return R.ok().put("coupons",Arrays.asList(coupon));

3、声明远程服务

在member服务的controller同级目录下新建一个feign目录,专门用来放远程调用的接口。

在该目录下新建一个接口,叫CouponFeignService

/**
 * 这是一个声明式的远程调用,告知SpringCloud这个接口需要远程服务
 * @FeignClient 指定远程服务的name
 */
@FeignClient("gulimall-coupon")
public interface CouponFeignService 

    @RequestMapping("/coupon/coupon/member/list")
    R memberCoupons();

4、主启动类里开启feign功能

为主启动类添加:@EnableFeignClients(basePackages = "com.atguigu.gulimall.member.feign")

5、测试

在member服务的控制层写测试方法

    @Autowired
    CouponFeignService couponFeignService;

    @RequestMapping("/coupons")
    public R test()
        MemberEntity member = new MemberEntity();
        member.setNickname("张三");
        R r = couponFeignService.memberCoupons();
        return R.ok().put("member",member).put("coupons",r.get("coupons"));
    

浏览器http://localhost:8000/member/member/coupons,有结果就是成功了~

4、SpringCloud Alibaba-Nacos[作为配置中心]

参考文档:https://github.com/alibaba/spring-cloud-alibaba/blob/2022.x/spring-cloud-alibaba-examples/nacos-example/nacos-config-example/readme-zh.md

1、引入依赖

 <dependency>
     <groupId>com.alibaba.cloud</groupId>
     <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
 </dependency>

2、配置

增加bootstrap.properties ,并进行如下配置,以Coupon服务为例

# 服务名称
spring.application.name=gulimall-coupon
# 配置中心地址
spring.cloud.nacos.config.server-addr=192.168.174.128:8848

3、写测试接口

application.properties增加数据:

coupon.user.name=张三
coupon.user.age=20

这里我们使用 @Value 注解来将application.properties中写好的配置注入到控制层的 userName 和 age 字段

@Value("$coupon.user.name")
private String name;

@Value("$coupon.user.age")
private Integer age;

@RequestMapping("test")
public R test()
    return R.ok().put("name",name).put("age",age);

4、测试

访问:localhost:7000/coupon/coupon/test,便可以从网页中读取到配置文件中的内容

5、实际应用

如果直接在配置文件里改变量的值,那么每次修改后都要重新将项目部署上线,这样不好!可以利用nacos平台对我们的配置进行管理。

查看我们的启动日志,发现每次项目启动时都会从gulimall-coupon.properties读取配置。

2021-06-10 19:19:26.694  INFO 2320 --- [      main] b.c.PropertySourceBootstrapConfiguration : Located property source: CompositePropertySource name='NACOS', propertySources=[NacosPropertySource name='gulimall-coupon.properties']

6、应用一:如何使用Nacos作为配置中心统一管理配置

  • 在Nacos上创建gulimall-coupon.properties文件,内容和本地的那个相同

  • 控制层的类上面添加 @RefreshScope 打开动态刷新功能。
  • 进行测试:访问http://localhost:7000/coupon/coupon/test,发现可以读取到Nacos的gulimall-coupon.properties中的数据。数据修改后,重新访问,发现读取到的是最新的~

7、应用二:实现环境配置的隔离。

企业开发中经常利用命名空间来做环境隔离:开发、测试、生产

然后使用哪个环境,就在bootstrap.properties里面添加:spring.cloud.nacos.config.namespace=xxxxxxx(命名空间id)

8、应用三:每个微服务之间隔离配置

每个微服务各自创建命名空间,使用Group来区分不同的环境

我们的项目采用的方案:每个微服务创建自己的命名空间,使用配置分组区分环境:dev、test、prod

9、应用四:使用配置集加载多个配置文件

如果配置比较多,我们可以把gulimall-properties分成:datasource.yml、mybatis.yml、other.yml…

# 命名空间
spring.cloud.nacos.config.namespace=4aca1a7f-494c-438a-9399-e8d22d81624a
# 分组[相当于指定默认gulimall-coupon.properties的group]
spring.cloud.nacos.config.group=dev

#加载配置文件
spring.cloud.nacos.config.ext-config[0].data-id=datasource.yml
spring.cloud.nacos.config.ext-config[0].group=dev
spring.cloud.nacos.config.ext-config[0].refresh=true

spring.cloud.nacos.config.ext-config[1].data-id=mybatis.yml
spring.cloud.nacos.config.ext-config[1].group=dev
spring.cloud.nacos.config.ext-config[1].refresh=true

spring.cloud.nacos.config.ext-config[2].data-id=other.yml
spring.cloud.nacos.config.ext-config[2].group=dev
spring.cloud.nacos.config.ext-config[2].refresh=true

5、网关Gateway

1、简介

网关作为流量的入口,常用功能包括路由转发、权限校验、限流控制等。而springcloud gateway作为SpringCloud 官方推出的第二代网关框架,取代了Zuul 网关。

性能对比:

网关提供API 全托管服务,丰富的API 管理功能,辅助企业管理大规模的API,以降低管理成本和安全风险,包括协议适配、协议转发、安全策略、防刷、流量、监控日志等功能。

Spring Cloud Gateway 旨在提供一种简单而有效的方式来对API 进行路由,并为他们提供切面,例如:安全性,监控/指标和弹性等。

官方文档地址:https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.1.3.RELEASE/single/spring-cloud-gateway.html

Spring Cloud Gateway 特点:

  • 基于Spring5,支持响应式编程和SpringBoot2.0
  • 支持使用任何请求属性进行路由匹配
  • 特定于路由的断言和过滤器
  • 集成Hystrix 进行断路保护
  • 集成服务发现功能
  • 易于编写Predicates 和Filters
  • 支持请求速率限制
  • 支持路径重写

思考:为什么使用API 网关?

API 网关出现的原因是微服务架构的出现,不同的微服务一般会有不同的网络地址,而外部客户端可能需要调用多个服务的接口才能完成一个业务需求,如果让客户端直接与各个微服务通信,会有以下的问题:

  • 客户端会多次请求不同的微服务,增加了客户端的复杂性。
  • 存在跨域请求,在一定场景下处理相对复杂。
  • 认证复杂,每个服务都需要独立认证。
  • 难以重构,随着项目的迭代,可能需要重新划分微服务。例如,可能将多个服务合并成一个或者将一个服务拆分成多个。如果客户端直接与微服务通信,那么重构将会很难实施。
  • 某些微服务可能使用了防火墙/ 浏览器不友好的协议,直接访问会有一定的困难。

以上这些问题可以借助API 网关解决。API 网关是介于客户端和服务器端之间的中间层,所有的外部请求都会先经过API 网关这一层。也就是说,API 的实现方面更多的考虑业务逻辑,而安全、性能、监控可以交由API 网关来做,这样既提高业务灵活性又不缺安全性。

使用API 网关后的优点如下:

  • 易于监控。可以在网关收集监控数据并将其推送到外部系统进行分析。
  • 易于认证。可以在网关上进行认证,然后再将请求转发到后端的微服务,而无须在每个微服务中进行认证。
  • 减少了客户端与各个微服务之间的交互次数。

2、核心概念

  • 路由。路由是网关最基础的部分,路由信息有一个ID、一个目的URL、一组断言和一组Filter 组成。如果断言路由为真,则说明请求的URL 和配置匹配
  • 断言。Java8 中的断言函数。Spring Cloud Gateway 中的断言函数输入类型是Spring5.0 框架中的ServerWebExchange。Spring Cloud Gateway 中的断言函数允许开发者去定义匹配来自于http request 中的任何信息,比如请求头和参数等。
  • 过滤器。一个标准的Spring webFilter。Spring cloud gateway 中的filter 分为两种类型的Filter,分别是Gateway Filter 和Global Filter。过滤器Filter 将会对请求和响应进行修改处理

工作原理:

客户端向 Spring Cloud Gateway 发出请求。 如果 Gateway Handler Mapping 确定请求与路由匹配,则将其发送到 Gateway WebHandler。这个WebHandler 将请求交给一个过滤器链,请求到达目标服务之前,会执行所有过滤器的pre 方法。请求到达目标服务处理之后再依次执行所有过滤器的post 方法。

一句话:请求到达网关,断言判断这个请求是否符合路由规则,如果符合,则按照这个规则 经过一系列filter过滤最终 到指定地方===》满足某些断言(predicates)就路由到指定的地址(uri),使用指定的过滤器(filter)

3、使用

1、新建springboot模块,命名为gulimall-gateway

2、导入gateway依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

3、进行配置

  • application.yml
server:
  port: 88

谷粒商城笔记合集

分布式基础篇分布式高级篇高可用集群篇
===简介&环境搭建======Elasticsearch===
项目简介与分布式概念(第一、二章)Elasticsearch:全文检索(第一章)
基础环境搭建(第三章)===商品服务开发===
===整合SpringCloud===商品服务 & 商品上架(第二章)
整合SpringCloud、SpringCloud alibaba(第四、五章)===商城首页开发===
===前端知识===商城业务:首页整合、Nginx 域名访问、性能优化与压力测试 (第三、四、五章)
前端开发基础知识(第六章)缓存与分布式锁(第六章)
===商品服务开发======商城检索开发===
商品服务开发:基础概念、三级分类(第七、八章)商城业务:商品检索(第七章)
商品服务开发:品牌管理(第九章)
商品服务开发:属性分组、平台属性(第十、十一章)
商品服务:商品维护(第十二、十三章)
===仓储服务开发===
仓储服务:仓库维护(第十四章)
基础篇总结(第十五章)

七、商城业务 & 商品检索⚠️

7.1 整合页面:Thymeleaf⚠️

  1. 添加本机的域名映射规则并 清除DNS缓存 :C:\\Windows\\System32\\drivers\\etc\\hosts

    192.168.10.10 bilimall.com
    192.168.10.10 search.bilimall.com
    #清除DNS缓存内容
    PS C:\\Users\\ZW L> ipconfig /flushdns
    
    Windows IP 配置
    
    已成功刷新 DNS 解析缓存。
    #显示DNS缓存内容
    PS C:\\Users\\ZW L> ipconfig /displaydns
    
  2. 引入 Thymeleaf 依赖

    <!-- thymeleaf模板引擎 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    
  3. 检索服务 中关闭 Thymeleaf 缓存application.yaml

    spring:
      thymeleaf:
        cache: false
    
  4. 拷贝检索首页到 检索服务 中:bilimall-search/src/main/resources/templates/index.html

  5. 检索服务 中修改 检索首页 :静态资源路径、thymeleaf命名空间、html文件格式等

    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
        <!-- 修改为: -->
        <link rel="stylesheet" href="/static/search/css/index.css">
        <script src="/static/search/js/jquery-1.12.4.js"></script>
        <img src="/static/search/image/down-@1x.png" />
        <img src="/static/search/img/01.png" />
        ...
    
  6. 拷贝静态资源到 nginx 中,修改 nginx 配置文件并重新启动 nginx 服务:F:\\software\\Nginx\\conf\\nginx.conf

    ...
    http 
    	...
        server 
            listen       80;
            server_name  *.bilimall.com;
            ...
        
        ...
    
    
  7. 网关服务 中添加 检索系统 的路由规则:application.yaml

    spring:
      cloud:
        nacos:
          discovery:
            server-addr: 114.132.162.129:8848
        gateway:
          routes:
            - id: bilimall_route
              uri: lb://bilimall-product
              predicates:
                - Host=bilimall.com
            - id: bilimall_search_route
              uri: lb://bilimall-search
              predicates:
                - Host=search.bilimall.com
    

7.2 整合 dev-tools⚠️

  1. 在检索服务的 pom.xml 中引入dev-tools依赖

    <!-- devtools -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <optional>true</optional>
    </dependency>
    
  2. 让页面修改实时生效:CTRL+F9、CTRL+SHIFT+F9

7.3 商城首页(跳转)检索首页

  1. 修改 商城系统 中点击三级分类的跳转地址:F:\\software\\Nginx\\html\\static\\index\\js\\catalogLoader.js

    var cata3link = $("<a href=\\"http://search.bilimall.com/list.html?catalog3Id="+ctg3.id+"\\" 
    

  2. 在 检索服务 中修改首页文件名称,并创建首页控制器:list.html、cn.lzwei.bilimall.search.controller.SearchController

    @Controller
    public class SearchController 
    
        @GetMapping(value = "/","/list.html")
        public String index()
            return "list";
        
    
    
  3. 修改 商城系统 中检索商品的跳转地址:bilimall-product/src/main/resources/templates/index.html

    function search() 
        var keyword=$("#searchText").val()
        window.location.href="http://search.gulimall.com/list.html?keyword="+keyword;
    
    

  4. 在 检索服务 中修改检索页点击商城首页的跳转地址

    <!--头部-->
    <div class="header_head">
        <div class="header_head_box">
            <b class="header_head_p">
                <div style="overflow: hidden">
                    <a href="http://bilimall.com" class="header_head_p_a1" style="width:73px;">
                        谷粒商城首页
                    </a>
    <!--搜索导航-->
    <div class="header_sous">
        <div class="logo">
            <a href="http://bilimall.com">...</a>
        </div>
    

7.4 检索业务分析💡

7.4.1 检索条件VO分析💡

打个比例吧 你肯定上过京东、淘宝买过东西吧? 那么你想要购买什么东西,你需要在搜索框中搜索你想要购买的物品,那么系统就会给你响应

我在京东搜索 手机 他会显示出相对应的产品

我们分析可能存在的检索条件,并创建对应的VO类:cn.lzwei.bilimall.search.vo.SearchParamVo

/**
 * 封装页面所有可能传递过来的查询条件
 */
@Data
public class SearchParamVo 
    //页面传递过来的全文匹配关键字
    private String keyword;

    //三级分类id
    private Long catalog3Id;

    /**
     * sort=saleCout_asc/desc
     * sort=skuPrice_asc/desc
     * sort=hotScore_asc/desc
     */
    //排序条件
    private String sort;

    /**
     * hasStock=0/1
     * skuPrice=1_500/_500/500_
     * brandId=1
     * attrs=1_红色:黑色&attrs=2_5寸:8寸&
     */
    private Integer hasStock;//是否有库存:1-有库存、0-无库存
    private String skuPrice;//价格区间查询
    private List<Long> brandId;//按照品牌进行查询,可以多选:&1_5寸:8寸&
    private List<String> attrs;//按照属性进行筛选
    private Integer pageNum = 1;//页码

7.4.2 检索结果VO分析💡

那么返回的数据我们是不是也要创建一个 VO 用来返回页面的数据?借鉴京东的实例来做参考

抽取出结果VO类:cn.lzwei.bilimall.search.vo.SearchResultVo

/**
 * 检索结果返回
 */
@Data
public class SearchResultVo 
    /**
     * 查询到所有商品的商品信息
     */
    private List<SpuUpTo> products;

    /**
     * 以下是分页信息
     */
    private Integer pageNum;//当前页码
    private Long total;//总共记录数
    private Integer totalPages;//总页码

    /**
     * 当前查询到的结果,所有设计的品牌
     */
    private List<BrandVo> brands;

    /**
     * 当前查询结果,所有涉及到的分类
     */
    private List<CatalogVo> catalogs;

    /**
     * 当前查询到的结果,所有涉及到的所有属性
     */
    private List<AttrVo> attrs;

    /**
     * 页码
     */
    private List<Integer> pageNavs;

    //==================以上是要返回给页面的所有信息
    /**
     * 品牌信息
     */
    @Data
    public static class BrandVo 

        private Long brandId; //品牌id
        private String brandName; //品牌名字
        private String brandImg; //品牌图片
    
    /**
     * 分类信息
     */
    @Data
    public static class CatalogVo 

        private Long catalogId; //分类id
        private String CatalogName; //品牌名字
    
    /**
     * 属性信息
     */
    @Data
    public static class AttrVo 
        
        private Long attrId; //属性id
        private String attrName; //属性名字
        private List<String> attrValue; //属性值
    

7.4.3 检索语句分析:DSL

那么这个 DSL 编写我们就在 Kibana 中测试

#GET gulimall_product/_search

  "query": 
    "bool": 
      "must": [
        
          "match": 
            "skuTitle": "华为"
          
        
      ],
      "filter": [
        
          "term": 
            "catalogId": "225"
          
        ,
        
          "terms": 
            "brandId": [
              "7",
              "8",
              "9"
            ]
          
        ,
        
          "nested": 
            "path": "attrs",
            "query": 
              "bool": 
                "must": [
                  
                    "term": 
                      "attrs.attrId": 
                        "value": "3"
                      
                    
                  ,
                  
                    "terms": 
                      "attrs.attrValue": [
                        "2019"
                      ]
                    
                  
                ]
              
            
          
        ,
        
          "term":  
            "hasStock": 
              "value": "true"
            
          
        ,
        
          "range": 
            "skuPrice": 
              "gte": 0,
              "lte": 7000
            
          
        
      ]
    
  ,
  "sort": [
    
      "skuPrice": 
        "order": "desc"
      
    
  ],
  "from": 0,
  "size": 5,
  "highlight":  
    "fields": "skuTitle": ,
    "pre_tags": "<b style=color:red>",
    "post_tags": "</b>"
  ,
  "aggs": 
    "brand_agg":  
      "terms": 
        "field": "brandId",
        "size": 10
      ,
      "aggs": 
        "brand_name_agg":  
          "terms": 
            "field": "brandName",
            "size": 1
          
        ,
        "brand_img_agg":  
          "terms": 
            "field": "brandImg",
            "size": 1
          
        
      
    ,
    "catalog_agg":  
      "terms": 
        "field": "catalogId",
        "size": 10
      ,
      "aggs": 
        "catalog_name_agg":  
          "terms": 
            "field": "catalogName",
            "size": 10
          
        
      
    ,
    "attr_agg":
      "nested": 
        "path": "attrs"
      ,
      "aggs":  
        "attr_id_agg": 
          "terms": 
            "field": "attrs.attrId",
            "size": 10
          ,
          "aggs": 
            "attr_name_agg":  
              "terms": 
                "field": "attrs.attrName",
                "size": 10
              
            ,
            "attr_value_agg": 
              "terms": 
                "field": "attrs.attrValue",
                "size": 10
              
            
          
        
      
    
  

7.4.4 /product/_mapping

#PUT product

  "mappings":
    "properties":
      "skuId":
        "type":"long"
      ,
       "spuId":
        "type":"keyword"
      ,
       "skuTitle":
        "type":"text",
        "analyzer": "ik_smart"
      ,
       "skuPrice":
        "type":"keyword"
      ,
       "skuImg":
        "type":"text",
        "analyzer": "ik_smart"
      ,
       "saleCount":
        "type":"long"
      ,
       "hasStock":
        "type":"boolean"
      ,
      "hotScore":
        "type":"long"
      ,
      "brandId":
        "type":"long"
      ,
      "catelogId":
        "type":"long"
      ,
      "brandName":
        "type":"keyword"
      ,
      "brandImg":
        "type":"keyword"
      ,
      "catalogName":
        "type":"keyword"
      ,
      "attrs":
        "type":"nested",
        "properties": 
          "attrId":
            "type":"long"
          ,
          "attrName":
            "type":"keyword"
          ,
          "attrValue": 
            "type":"keyword"
          
        
      
    
  

7.5 API:构建检索请求条件💡

7.5.1 代码编写

代码对照着 7.4.3小节 生成的DSL检索条件进行编写

term 和 terms 不要调用错误

  1. cn.lzwei.bilimall.search.constant.EsConstant

    public class EsConstant 
        //商品索引
        public static final String PRODUCT_INDEX="product";
        //页大小
        public static final int SEARCH_PAGESIZE=12;
    
    
  2. cn.lzwei.bilimall.search.controller.SearchController

    /**
     * 检索系统首页控制器
     */
    @Controller
    public class SearchController 
    
        @Resource
        SearchService searchService;
    
        @GetMapping(value = "/","/list.html")
        public String index(SearchParamVo searchParamVo, Model model)
            //在返回页面中设置检索结果
            SearchResultVo result=searchService.search(searchParamVo);
            model.addAttribute("result",result);
            return "list";
        
    
    
  3. cn.lzwei.bilimall.search.service.SearchService

    /**
     * 检索业务
     */
    public interface SearchService 
        /**
         * 返回检索的结果
         */
        SearchResultVo search(SearchParamVo searchParamVo);
    
    
  4. cn.lzwei.bilimall.search.service.impl.SearchServiceImpl

    /**
     * 检索业务
     */
    @Service(value = "searchService")
    public class SearchServiceImpl implements SearchService 
    
        @Resource
        RestHighLevelClient restHighLevelClient;
        /**
         * 返回检索的结果
         */以上是关于《谷粒商城基础篇》分布式基础&环境搭建的主要内容,如果未能解决你的问题,请参考以下文章

    谷粒商城高级篇缓存与分布式锁

    谷粒商城高级篇商城业务:商品检索

    谷粒商城-分布式基础篇-环境搭建

    谷粒商城-分布式基础篇-环境搭建

    谷粒商城-分布式基础篇-环境搭建

    谷粒商城高级篇Elasticsearch:全文检索

(c)2006-2024 SYSTEM All Rights Reserved IT常识