一 项目背景

1 电商模式

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

B2B 模式
B2B(Business to Business),是指商家和商家建立的商业关系,如阿里巴巴

B2C 模式
B2C(Business to Consumer) 就是我们经常看到的供应商直接把商品买个用户,即 “商对客” 模式,也就是我们呢说的商业零售,直接面向消费销售产品和服务,如苏宁易购,京东,天猫,小米商城

C2B 模式
C2B (Customer to Business),即消费者对企业,先有消费者需求产生而后有企业生产,即先有消费者提出需求,后又生产企业按需求组织生产

C2C 模式
C2C (Customer to Consumer) 客户之间把自己的东西放到网上去卖 。如淘宝、咸鱼

O2O 模式
O2O (Online To Offline),也即将线下商务的机会与互联网结合在一起,让互联网成为线上交易前台,线上快速支付,线上优质服务,如:饿了么,美团,淘票票,京东到家

2 谷粒商城

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

3 项目架构

微服务架构图

《Java开发修仙图》

把Java从入门带到入坟,微服务技术栈差不多都在这了

business partner 客商_mysql

  1. 前后分离开发,分为内网部署和外网部署,外网是面向公众访问的。访问前端项目,可以有手机APP,电脑网页;内网部署的是后端集群,前端在页面上操作发送请求到后端,在这途中会经过Nginx集群,Nginx把请求转交给API网关(springcloud gateway)(网关可以根据当前请求动态地路由到指定的服务,看当前请求是想调用商品服务还是购物车服务还是检索服务),从路由过来如果请求很多,可以负载均衡地调用商品服务器中一台(商品服务复制了多份),当商品服务器出现问题也可以在网关层面对服务进行熔断或降级(使用阿里的sentinel组件),网关还有其他的功能如认证授权、限流(只放行部分到服务器)等。
  2. 到达服务器后进行处理(springboot为微服务),服务与服务可能会相互调用(使用feign组件),有些请求可能经过登录才能进行(基于OAuth2.0的认证中心。安全和权限使用springSecurity控制)
  3. 服务可能保存了一些数据或者需要使用缓存,我们使用redis集群(分片+哨兵集群)。持久化使用mysql,读写分离和分库分表。
  4. 服务和服务之间会使用消息队列(RabbitMQ),来完成异步解耦,分布式事务的一致性。有些服务可能需要全文检索,检索商品信息,使用ElaticSearch。
  5. 服务可能需要存取数据,使用阿里云的对象存储服务OSS。
  6. 项目上线后为了快速定位问题,使用ELK对日志进行处理,使用LogStash收集业务里的各种日志,把日志存储到ES中,用Kibana可视化页面从ES中检索出相关信息,帮助我们快速定位问题所在。
  7. 在分布式系统中,由于我们每个服务都可能部署在很多台机器,服务和服务可能相互调用,就得知道彼此都在哪里,所以需要将所有服务都注册到注册中心。服务从注册中心发现其他服务所在位置(使用阿里Nacos作为注册中心)。
  8. 每个服务的配置众多,为了实现改一处配置相同配置就同步更改,就需要配置中心,也使用阿里的Nacos,服务从配置中心中动态取配置。
  9. 服务追踪,追踪服务调用链哪里出现问题,使用springcloud提供的Sleuth、Zipkin、Metrics,把每个服务的信息交给开源的Prometheus进行聚合分析,再由Grafana进行可视化展示,提供Prometheus提供的AlterManager实时得到服务的警告信息,以短信/邮件的方式告知服务开发人员。
  10. 还提供了持续集成和持续部署。项目发布起来后,因为微服务众多,每一个都打包部署到服务器太麻烦,有了持续集成后开发人员可以将修改后的代码提交到github,运维人员可以通过自动化工具Jenkins Pipeline将github中获取的代码打包成docker镜像,最终是由k8s集成docker服务,将服务以docker容器的方式运行。

微服务划分图

business partner 客商_redis_02

4 项目技术&特色

  • 前后端分离开发,并开发基于Vue的后台管理系统
  • SpringCloud & SpringCloudAlibaba 全套的解决方案
  • 应用监控、限流、网关、熔断降级等分布式治理方案,全方位设计
  • 透彻讲解分布式事务、分布式锁等分布式系统的难点
  • 分析高并发场景的编码方式,线程池、异步编排等使用
  • 压力测试、性能优化
  • 各种集群技术的区别以及使用(redis、MySQL、rabbitMQ、Elasticsearch)
  • CI/CD使用(持续集成、持续交付和持续部署)

5 项目前置要求

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

首先我们会体验到全栈开发,然后就是分布式、微服务的整套方案

二 分布式基础概念

1 微服务

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

简而言之:拒绝大型单体应用,基于业务边界进行服务微化拆分,各个服务独立部署运行。

business partner 客商_business partner 客商_03

2 集群&分布式&节点

集群是个物理状态,分布式是个工作方式。

只要是一堆机器,也可以叫做集群,他们是不是一起协作干活,这谁也不知道。

《分布式系统原理与范型》定义:
分布式系统是若干独立计算机的集合,这些计算机对于用户来说像单个系统(用户不会感知到在使用这一堆计算机,只感受到在使用一个系统)

分布式是指根据不同的业务分布在不同的地方。

集群指的是将几台服务器集中在一起,实现同一业务。

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

分布式中的每一个节点,都可以做集群,而集群并不一定就是分布式的

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

3 远程调用

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

SpringCloud中使用HTTP+JSON的方式来完成远程调用。

business partner 客商_redis_04

4 负载均衡

business partner 客商_business partner 客商_05

分布式系统中,A 服务需要调用B服务,B服务在多台机器中都存在,A调用任意一个服务器均可完成功能。

为了使每一个服务器都不要太或者太闲,我们可以负载均衡调用每一个服务器,提升网站的健壮性。

常见的负载均衡算法:

轮询:为第一个请求选择健康池中的每一个后端服务器,然后按顺序往后依次选择,直到最后一个,然后循环。

最小连接:优先选择链接数最少,也就是压力最小的后端服务器,在会话较长的情况下可以考虑采取这种方式。

散裂:根据请求源的IP散裂(hash)来选择要转发的服务器。这种方式可以一定程度上保证特定用户能连接到相同的服务器。如果你的应用需要处理状态而要求用户能够连接到之前相同的服务器,可以考虑采用这种方式。

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

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

business partner 客商_redis_06

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

6 配置中心

business partner 客商_mysql_07

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

配置中心用来集中管理微服务的配置信息。

7 服务熔断&服务降级

在微服务架构中,微服务之间通过网络来进行通信,存在相互依赖,当其中一个服务不可用时(宕机,指操作系统无法从一个严重系统错误中恢复过来,以致系统长时间无响应),有可能会造成雪崩效应,要防止这种情况,必须要有容错机制来保护服务。

business partner 客商_redis_08

情景:
订单服务 --> 商品服务 --> 库存服务

库存服务出现故障导致响应慢,导致商品服务需要等待,可能等到10s后库存服务才能响应。库存服务的不可用导致商品服务阻塞,商品服务等的期间,订单服务也处于阻塞。一个服务不可用导致整个服务链都阻塞。如果是高并发,第一个请求调用后阻塞10s得不到结果,第二个请求直接阻塞10s。更多的请求进来导致请求积压,全部阻塞,最终服务器的资源耗尽。导致雪崩。

服务熔断:

设置服务的超时,当被调用的服务经常失败到达某个阈值,我们可以开启断路保护机制,后来的请求不再去调用这个服务,本地直接返回默认的数据。

服务降级:

在运维期间,当系统处于高峰期,系统资源紧张,我们可以让非核心业务降级运行,降级:某些服务不处理,或者简单处理【抛异常,返回NULL,调用Mock数据,调用 FallBack 处理逻辑】

8 API网关

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

business partner 客商_business partner 客商_09

三 环境搭建

1 配置Linux虚拟机

我使用centOS7的系统。具体操作略过。

2 安装docker

什么是docker:Docker是虚拟化容器技术。Docker基于镜像,可以秒级启动各种容器。每一个容器都是一个完整的运行环境,容器之间互相隔离。

business partner 客商_business partner 客商_10

若是root用户下,命令前不需要sudo;若不是root用户,命令前需要sudo。

  1. 卸载旧版本:
$ sudo yum remove docker \
                  docker-client \
                  docker-client-latest \
                  docker-common \
                  docker-latest \
                  docker-latest-logrotate \
                  docker-logrotate \
                  docker-engine
  1. 安装必须的依赖包:
$ sudo yum install -y yum-utils \
  device-mapper-persistent-data \
  lvm2
  1. 告诉Linux,docker要去哪里装(使用阿里云的源):
$ sudo yum-config-manager \
    --add-repo \
    http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
  1. 安装docker:
$ sudo yum install docker-ce docker-ce-cli containerd.io
  1. 启动docker:
$ sudo systemctl start docker
  1. 通过运行hello-world镜像来验证是否正确安装了docker:
$ sudo docker run hello-world
  1. 设置docker开机自启:
$ sudo systemctl

docker配置镜像加速

由于docker的仓库是在国外的,我们拉取的时候速度会很慢,所以需要配置国内的镜像服务来快速拉取。(依次执行以下命令)

sudo mkdir -p /etc/docker

sudo tee /etc/docker/daemon.json <<-'EOF'
{
  "registry-mirrors": ["https://e3cu3dao.mirror.aliyuncs.com"]
}
EOF

sudo systemctl daemon-reload

sudo systemctl restart docker

3 docker安装MySQL

3.1 下载镜像文件

若不是root用户下,命令前需要加 sudo

# 安装最新版本的mysql
docker pull mysql

# 安装指定版本的mysql
docker pull mysql:5.7

# 安装完毕后,查看一下当前已下载的镜像
docker images

3.2 创建实例并启动

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

注:若宿主机上的3306端口被占用,会导致端口冲突而报错。解决方案就是先把宿主机3306端口上的服务杀掉。

若3306是宿主机的mysql在运行,还需要杀掉MySQL的守护进程mysqld (killall mysqld)

命令

描述

docker run

创建一个新的容器 , 同时运行这个容器

--name mysql

启动容器的名字

-d

后台运行

-p 3306:3306

将容器的 3306 (后面那个) 端口映射到宿主机的 3306 (前面那个) 端口

--restart unless-stopped

容器重启策略

-v /mydata/mysql/log:/var/log/mysql

将日志文件夹挂载到宿主机

-v /mydata/mysql/data:/var/lib/mysql

将mysql储存文件夹挂载到宿主机

-v /mydata/mysql/conf:/etc/mysql

将配置文件夹挂载到宿主机

-e MYSQL_ROOT_PASSWORD=123456

设置 root 用户的密码

mysql:5.7

启动哪个版本的 mysql (本地镜像的版本)

\

shell 命令换行符

启动后,查看Docker运行中的容器:

docker ps -a

business partner 客商_redis_11

进入容器内部:

docker exec -it <容器id|容器名称> /bin/bash

business partner 客商_redis_12

进入容器中可以看到,docker容器内部也是一个Linux系统。刚才下载的MySQL服务就装在这台小型Linux系统上。

目录挂载的作用(-v):

因为我们的MySQL服务装在docker容器内,所以MySQL的配置文件也在容器内部。如果我们想要修改MySQL的某些配置的话,难道每次都要进入到容器内部来进行修改吗?太麻烦了。所以我们把一些经常要看的文件夹或内容映射到宿主机的目录里,就像桌面快捷方式一样。在宿主机上修改配置文件,容器内部也会修改。(:前面的是宿主机的目录,后面的是docker容器中的目录)。

MySQL将数据挂载到宿主机上,如果容器内的数据没了(容器实例删除),容器外的数据还会在。

3.3 修改配置文件

因为安装MySQL的时候进行了目录挂载,所以想修改MySQL配置文件时,直接在宿主机修改就行了。

[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

business partner 客商_docker_13

可以看到,docker容器内部的mysql配置文件也被修改了

4 docker安装redis

4.1 下载镜像文件

# 安装最新版redis
docker pull redis

4.2 创建实例并启动

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

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

business partner 客商_business partner 客商_14

由于redis的文件是保存在内存上的,所以我们需要做持久化处理。

redis默认开启rdb持久化方式。所以我们可以利用配置文件来打开aof持久化方式。

# 在/mydata/redis/conf/redis.conf中写入如下内容,开启aof:
appendonly yes

business partner 客商_redis_15

4.3 可视化redis

可以使用 Redis Desktop Manager 来快速查看redis数据

下载地址:https://wwm.lanzouv.com/iyVqF08zdkxa,密码:94wg

现在,docker是可以随着Linux系统启动而自动启动的,但是docker里的mysql和redis并不会自动启动,为了方便,我们要设置这两个也能够自动启动。

docker update redis --restart=always

docker update mysql --restart=always

5 开发环境

Maven、IDEA、WebStorm/VsCode、Git

6 创建项目

6.1 从gitee初始化一个项目

  1. 在Gitee中新建仓库

business partner 客商_docker_16

  1. 复制仓库的地址,并导入到IDEA中打开。

business partner 客商_mysql_17

business partner 客商_business partner 客商_18

6.2 创建各个微服务

商品服务、仓储服务、订单服务、优惠券服务、用户服务

business partner 客商_business partner 客商_19

每个服务的共同点:

  • 都需要导入SpringWeb、OpenFeign依赖
  • 每一个服务,包名:com.example.gulimall.xxx(product/order/ware/coupon/member)
  • 模块名:gulimall-coupon

business partner 客商_mysql_20

6.3 聚合服务

复制一个 pom.xml 文件在项目根目录下,与各个微服务同级,作为聚合服务,可以统一管理每一个微服务。

business partner 客商_redis_21

7 初始化数据库

谷粒商城建表语句:https://wwm.lanzouv.com/iigXX08zqv9a

由于数据量大的时候,外键会影响效率,所以表中不设外键。

8 逆向工程搭建

8.1 下载项目

需要使用到人人开源的三个项目:后台管理、后台管理的前端、代码生成器

business partner 客商_business partner 客商_22

8.2 启动后台管理后端

renren-fastrenren-generator 克隆到本地,删掉里面的.git文件夹,然后放到微服务项目中,并配置 pom.xml 。然后在renren-fast里面有一个db的文件夹,复制mysql.sql中的建表语句,最后再修改这个模块下的数据库配置,连到刚创建的数据库:

business partner 客商_business partner 客商_23

8.3 启动后台管理前端

renren-fast-vue 克隆到本地,删除里面的.git.idea文件夹,然后导入到webstorm中,npm install --> npm run dev。这时,renren-fastrenren-fast-vue就可以进行联调,构成一个完整的后台管理系统。(账号密码:admin/admin)

business partner 客商_mysql_24

8.4 使用代码生成器

  1. 首先要修改MySQL配置(似乎一次只能处理一张表、MP感觉更NB):
#MySQL配置
driverClassName: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.121.138:3306/gulimall_pms?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: 123456

business partner 客商_redis_25

business partner 客商_business partner 客商_26

勾选全部-->生成代码,然后就能生成相应的代码文件和结构,选择需要的导入到项目中:

business partner 客商_docker_27

business partner 客商_business partner 客商_28

8.5 依赖管理

此时,代码什么都导入了,可是里面有很多的依赖咱们还没导入,所以爆红

business partner 客商_docker_29

创建公共模块

因为每个微服务可能都需要使用到这些util,在每一个微服务里面都写很麻烦,所以我们可以新建一个公共模块(gulimall-common)放这些工具类:

公共模块放每一个微服务的依赖、bean、工具类等

business partner 客商_docker_30

business partner 客商_mysql_31

公共模块作为依赖引入

business partner 客商_mysql_32

最后把gulimall-common模块也添加到root下(根目录下的pom.xml中的module)

在公共模块中导入依赖

引入代码生成器的代码后,会有很多的报错,需要手动导入依赖和导包。

咱这里不用shiro,用springsecurity,所以有关shiro的依赖的东西删掉就好。

注:renren-generator/src/main/resources/template下可以修改模板,可以提取把shiro相关的东西删掉,这样自动生成的时候就没有了。

<dependencies>
    <!--Mysql-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.29</version>
    </dependency>
    <!-- MybatisPlus -->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.5.1</version>
    </dependency>
    <!-- lombok -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.24</version>
    </dependency>
    <!-- apache.httpcomponents(用于Java代码发送请求) -->
    <dependency>
        <groupId>org.apache.httpcomponents</groupId>
        <artifactId>httpcore</artifactId>
        <version>4.4.14</version>
    </dependency>
    <!--Apache Commons Lang,Java.lang的扩展-->
    <dependency>
        <groupId>commons-lang</groupId>
        <artifactId>commons-lang</artifactId>
        <version>2.6</version>
    </dependency>
    <!-- spring-boot-starter-web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <version>2.7.2</version>
    </dependency>
    <!--校验-->
    <dependency>
        <groupId>jakarta.validation</groupId>
        <artifactId>jakarta.validation-api</artifactId>
        <version>2.0.2</version>
        <scope>compile</scope>
    </dependency>
    <!-- commons-io -->
    <dependency>
        <groupId>commons-io</groupId>
        <artifactId>commons-io</artifactId>
        <version>2.11.0</version>
    </dependency>
</dependencies>

8.6 启动项目

由于项目中还使用了Mybatis-Plus,所以我们还要配置一下MP:

  1. 导入MP依赖(还有数据库依赖)
  2. 配置数据源(application.yaml)
  3. 配置MybatisPlus(使用@MapperScan、告诉MP,sql映射文件的位置,在配置文件中告诉)
server:
  port: 9005
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://192.168.121.138:3306/gulimall_wms?serverTimezone=%2B8
    username: root
    password: 123456

mybatis-plus:
  mapper-locations: classpath:/mapper/**/*.xml
  global-config:
    db-config:
      id-type: auto

8.7 逆向生成所有微服务

business partner 客商_redis_33