一、项目简介
1. 项目背景
(1)电商模式
市面上有五种常见的电商模式:B2B,B2C,C2B,C2C,O2O
a. B2B:商家与商家建立的商业关系,例如阿里巴巴
b. B2C:常看到的供应商直接把商品卖给用户,即:“商对客”模式,也即常说的商业零售,直接面向消费者销售产品和服务,例如苏宁易购,京东,天猫,小米商城
c. C2B:消费者对企业,先有消费者提出需求,后有生产企业按需求组织生产:脸蛋网
d. C2C:客户之间自己把东西放到网上去卖,例如:淘宝、咸鱼
e. O2O:Online To Offline,将线下商务的机会与互联网结合在一起,让互联网成为线下交易的前台。线上快速支付,线下优质服务,例如饿了么,美团,淘票票,京东到家
(2)谷粒商城
B2C模式的电商平台,销售直营商品给客户
2. 项目框架图
3. 项目技术&特色
前后端分离,并开发基于vue的后台管理系统
springcloud全新的解决方案,及springcloud alibaba
应用监控、限流、网关、熔断降级等分布式方案,全方位涉及
分布式事务,分布式锁
高并发场景的编码方式,线程池,异步编排
压力测试与性能优化
各种集群技术的区别以及使用
CI/CD的使用
……
4. 项目前置要求
熟悉springboot以及常见整合方案:springboot+mybatis
了解springcloud
熟悉git,maven
熟悉Linux,Redis,docker基本操作
了解HTML,css,js,vue
二、分布式基础概念
1. 微服务
微服务架构风格,就像是一个单独的应用程序开发为一套小服务,每个小服务运行在自己的进程中(互相隔离),并使用轻量级机制通信,通常是HTTP API,这些服务围绕业务能力来构建,并通过完全自动化部署机制来独立部署,这些服务使用不同的编程语言书写,以及不同数据存储技术,并保持最低限度的集中式管理。简而言之:拒绝大型单机应用,基于业务边界进行服务微化拆分,各个服务独立部署运行。
(非微服务模式:如果有一处代码出现了问题,可能会导致整个应用不可用,所以有了微服务模式:将一个大的单体拆分成若干个小模块,每个小模块即为一个微服务,所有的小模块合起来成为一个大的单体应用,其中各个小模块独立运行,其中某个出现问题不会影响到其他)
2. 集群&分布式&节点
集群是一个物理形态,分布式是个工作方式。
只要是一堆机器,就可以叫集群,他们是不是一起协作着干活,没人知道;
《分布式系统原理与范型》定义:分布式系统是若干独立计算机的集合,这些计算机对于用户来说就像单个相关系统,分布式系统是建立在网络之上的软件系统。
分布式是指将不同的业务分布在不同的地方。
集群是指将几台服务器集中在一起,实现同一业务。
例如:京东是一个分布式系统,众多业务运行在不同的机器,所有业务构成一个大型的业务集群。每一个小的业务比如用户系统,访问压力大的时候一台服务器是不够的,此时就应该将用户系统部署到多个服务器,也就是每一个业务系统可以做集群化。
分布式中的每一个节点,都可以做集群,而集群不一定是分布式的。节点:集群中的一个服务器。
3. 远程调用
在分布式系统中,各个服务可能处于不同主机,但是服务之间不可避免的需要相互调用,称为远程调用。
SpringCloud中使用HTTP+JSON的方式完成远程调用。
4. 负载均衡
分布式系统中,A服务需要调用B服务,B服务在多台机器中都存在,A调用任意一个服务器均可完成功能,为了使每一个服务器都不要太忙或者太闲,可以负载均衡地调用每一个服务器,提升网站的健壮性。
常见的负载均衡算法:
轮询:为第一个请求选择健康池中的第一个后端服务器,然后按顺序往后一次选择,直到最后一个,然后循环。
最小连接:优先选择连接数量少,也就是压力最小的后端服务器,在会话较长的情况下可以考虑采取这种方式。
散列:根据请求源的IP的散列(hash)来选择要转发的服务器。这种方式可以一定程度上保证特定用户能连接到相同的服务器。如果你的应用需要处理状态而要求用户能连接到和之前相同的服务器,可以考虑采用这种方式。
5. 服务注册/发现&注册中心
A服务调用B服务,A服务并不知道B服务当前在哪几台服务器有,哪些正常的,那些服务已经下线,解决这个问题可以引入注册中心;
例如:商品服务B一上线就注册到【注册中心】,相当于告知【注册中心】B服务的某台机器上线了,该过程为【服务注册】,如果三台机器都上线了,便都在注册中心注册了该服务,即告诉【注册中心】商品服务B在三个机器都有,当A服务想要调用B服务时,A服务先去注册中心发现一下,该过程叫【服务发现】,看一下商品服务B都分布在哪些机器,再去随机调用。当某个机器下线,注册中心也能实时感知到,此时A服务再想调用B服务,会从【注册中心】发现B服务还有哪几台机器可用。
如果某些服务下线,其他人可以实时地感知到其他服务的状态,从而避免调用不可用的服务。
6. 配置中心
每一个服务最终都有大量的配置,并且每个服务都有可能部署在多台机器上。经常需要变更配置,可以让每个服务在配置中心获取自己的配置。
配置中心用来集中管理微服务的配置信息。
7. 服务熔断&服务降级
在微服务架构中,微服务之间通过网络进行通信,存在相互依赖,当其中一个服务不可用时,有可能会造成雪崩效应。要防止这样的情况,必须要有容错机制来保护服务。
(1)服务熔断
设置服务的超时,当被调用的服务经常失败到达某个阈值,可以开启断路保护机制,后来的请求不再去调用这个服务,本地直接返回默认的数据,便不会有请求积压的情况。
(2)服务降级
在运维期间,当系统处于高峰期,系统资源紧张,可以让非核心业务降级运行。降级:某些服务不处理,或者简单处理【抛异常,返回null,调用Mock数据,调用Fallback处理逻辑】
8. API网关
在微服务架构中,API Gateway作为整体架构的重要组件,抽象了微服务中都需要的公共功能,同时提供了客户端负载均衡,服务自动熔断,灰度发布,统一认证,限流流控,日志统计等丰富的功能,帮助解决很多的API管理难题。
三、环境搭建
1. 安装Linux虚拟机
虚拟机使用环境:VMware Workstation + FinalShell
安装配置及基本知识点见:Linux(VMware + FinalShell)_柒桉的博客-CSDN博客
2. 安装docker
docker是一个虚拟化容器技术。基于镜像,可以秒级启动各种容器,各种容器都是一个完整的运行环境,容器之间互相隔离。
Linux安装docker参考:Linux安装docker及在docker安装mysql_柒桉的博客-CSDN博客
3. docker安装mysql
Linux安装docker及在docker安装mysql_柒桉的博客-CSDN博客
4. docker安装redis
Linux安装docker及在docker安装mysql_柒桉的博客-CSDN博客
5. 开发环境统一
IDE:
Maven:
VScode:
6. 创建项目微服务
商品服务、仓储服务、订单服务、优惠券服务、用户服务
几个服务之间的共同点:
- 导入web、openfeign模块
- 每一个服务,包名都是:com.wlp.gulimail.xxx (product/order/ware/coupon/member)
- 模块名都是:gulimai-xxx (product/order/ware/coupon/member)
依次创建其他几个微服务:
给中的项目建一个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.wlp.gulimail</groupId>
<artifactId>gulimail</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>gulimail</name>
<description>聚合服务</description>
<packaging>pom</packaging>
<modules>
<module>gulimail-coupon</module>
<module>gulimail-member</module>
<module>gulimail-order</module>
<module>gulimail-product</module>
<module>gulimail-ware</module>
</modules>
</project>
在总项目的gitignore文件中设置提交到远程时要忽略的文件:
target/
pom.xml.tag
pom.xml.releaseBackup
pom.xml.versionsBackup
pom.xml.next
release.properties
dependency-reduced-pom.xml
buildNumber.properties
.mvn/timing.properties
# https://github.com/takari/maven-wrapper#usage-without-binary-jar
**/mvnw
**/mvnw.cmd
**/.mvn
**/target/
.idea
**/.gitignore
把剩下的文件纳入版本控制
给IDE安装Git的插件:
提交到码云:
然后再到码云刷新查看就可以看到刚提交的这一版。
7. 数据库初始化
开启虚拟机后,打开SQLyog
分别创建五个库:sql脚本在:谷粒商城数据库源码SQL_柒桉的博客-CSDN博客
8. 人人开源搭建后台管理系统
人人开源网:人人开源
将以上两个模块的代码clone下来
打开gulimail总项目所在的目录,将下载下来的renren-fast文件夹中的.git目录删掉后,将renren-fast文件夹放到gulimail总项目的目录下,并加入总项目的子模块。
将renren-fast模块中的mysql.sql脚本拿出来创建一个数据库表gulimail_admin
打开renren-fast的application.yml文件,看到是dev开发环境,
然后打开application-dev.yml文件进行配置:修改ip地址为虚拟机地址,数据库名为刚刚建立的数据库gulimail_admin,并修改连接数据库的用户名和密码
修改完成后,点击主启动类启动,如果爆红则是SDK没配置,
启动完成后进入浏览器,输入:localhost:8080/renren-fast/ 看到启动成功:
然后将renren-fast文件夹拖动到VScode中
安装node.js,配置npm使用淘宝镜像:npm config set registry http://registry.npm.taobao.org/
打开VScode终端,运行
npm install
如果执行npm install 报错:npm ERR! code EPERM npm ERR! errno -4048 npm ERR! Error: EPERM: operation not permitted, open-CSDN博客
npm install成功运行结束后,在终端控制台运行前端项目:
npm run dev
成功运行的结果:
登陆 http://localhost:8001/#/login 即可看到前端页面:
点击一下验证码,在IDE的控制台可以看到SQL语句:
达到一个前后端联调的效果。
8. 逆向工程搭建&使用
在人人开源下载代码生成器的源代码,并放到gulimail文件夹下,将renren-generator加到module
修改renren-generator的yml配置文件:
启动renrne-generator的类,并访问:
在代码生成器里展示了所有的pms开头的数据表。点击【生成代码】则生成“产品模块”的SQL和java代码,将里面对应的java--main代码替换掉gulimail-product模块的main:
生成的代码需要导入common和mybatis的包,
创建一个maven工程,将每个服务需要的依赖都写在这个公共的maven工程里:
给gulimail-product模块添加common依赖:
<dependency>
<groupId>com.wlp.gulimail</groupId>
<artifactId>gulimail-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
再给gulimail-common中添加各个模块需要的依赖
依赖分三种方式获得:
(1)看起来是自创路径的包,要从renren-fast模块获取,注意文件路径要放对
生成器生成的代码点开其中一个文件会出现爆红,是缺少相应的包导致的,如下图。此图中缺少common.utils包下的R,可以从renren-fast模块的common.utils中找到R,将其复制到自己创建的com.wlp包下的common.utils路径下,爆红就会消失。
(2)来自网络端(非renrne项目自带)例如:
需要从网上下载加到common模块的依赖中:
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
<version>4.4.12</version>
</dependency>
(3)从renren-fast的pom文件中获得
将renren-fast模块下pom文件中的该依赖复制到爆红文件所在模块的pom依赖中,注意版本号要保持一致。
此时除controller之外的大多数的爆红可以解决,还剩controller里如下爆红:
这个爆红可以先忽略,忽略的办法:
在renrne-generator中生成controller的文件中将该注解注释掉:
并将其包也注释掉:
再重新运行renren-generator
用新生成的代码中的controller文件夹替换之前product模块中的controller文件夹,可以看到新生成的controller类不再爆红
9. 配置&测试微服务基本CRUD功能:
(1)整合mybatis-plus
a. 导入依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.2.0</version>
</dependency>
b. 配置
(i)配置数据源
首先导入数据库的驱动:将驱动写到common微服务中(驱动要跟数据库版本保持一致)
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.17</version>
</dependency>
然后在application.yml中配置数据源相关信息
spring:
datasource:
username: root
password: root
url: jdbc:mysql://192.168.153.129:3306/gulimail_pms
driver-class-name: com.mysql.cj.jdbc.Driver
(ii)配置mybatis-plus
首先使用@MapperScane注解为主启动类配置扫描路径:
@MapperScan("com.wlp.gulimail.product.dao")
然后告诉mybatis-plus,SQL的映射文件所在的位置
mybatis-plus:
mapper-locations: classpath:/mapper/**/*.xml
global-config:
db-config:
id-type: auto #设置主键自增
进行CRUD测试:
@SpringBootTest
class GulimailProductApplicationTests {
@Autowired
BrandService brandService;
@Test
void contextLoads() {
BrandEntity brandEntity = new BrandEntity();
brandEntity.setName("华为");
brandService.save(brandEntity); //保存
System.out.println("保存成功");
brandEntity.setBrandId(1L);
brandEntity.setDescript("华为");
brandService.updateById(brandEntity); //更新
// 查询
List<BrandEntity> list = brandService.list(new QueryWrapper<BrandEntity>().eq("brand_id", 1L));
for (BrandEntity entity : list) {
System.out.println(entity);
}
}
}
运行以上测试类,可以看到数据库中进行了相应的更新:
10. 逆向生成所有微服务基本CRUD代码
(1)修改generator.properties配置文件中的模块名和表前缀
微服务名 | 模块名 | 表前缀 | 数据库表名 | 端口号 |
优惠券 | coupon | sms_ | gulimail_sms | 7000 |
会员/消费者 | member | ums_ | gulimail_ums | 8000 |
订单 | order | oms_ | gulimail_oms | 9000 |
商品 | product | pms_ | gulimail_pms | 10000 |
库存 | ware | wms_ | gulimail_wms | 11000 |
(2)修改application.yml中的数据库表名(见上表微服务与数据库表的对应关系)
(3)启动逆向工程的主启动类
(4)访问 http://localhost/#generator.html 并【生成代码】
(5)将生成的代码解压复制main文件夹替换相应微服务的main
(6)给微服务添加common依赖:
<dependency>
<groupId>com.wlp.gulimail</groupId>
<artifactId>gulimail-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
(7)新建application.yml配置文件进行配置,与其他微服务不同的只有数据库名字
spring:
datasource:
username: root
password: root
url: jdbc:mysql://192.168.153.129:3306/gulimail_sms
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis-plus:
mapper-locations: classpath:/mapper/**/*.xml
global-config:
db-config:
id-type: auto #设置主键自增
(8)启动该微服务的主启动类,并进行访问:
其中【1】是该服务端口号(各微服务的端口号设置如下,微服务与端口号的对应关系如上表),【2】是controller的类路径名,【3】是controller的方法路径名
各微服务端口号设置:
在application.yml文件中设置:
server:
port: 10000
四、分布式组件
1. SpringCloud Alibaba
在分布式开发中,每一个微服务上线,都应该将其注册到注册中心,
注册中心操作的好处:如果服务1想要调用服务2,可以先去注册中心查证一下都有哪些机器的服务2注册进来了,服务1就可以随意挑选一个服务2的机器进行远程调用;
配置中心:集中管理每个服务众多机器的配置,从配置中心修改配置,则对应服务的各个机器的配置都会进行修改
网关:所有前端的请求通过网关进行过滤,由网关与其他服务建立联系;
以上功能在SpringCloud中对应的组件,以及使用SpringCloud Alibaba替代SpringCloud的原因见:
SpringCloud Alibaba (VS. SpringCloud)_柒桉的博客-CSDN博客
2. 前端相关(ES6 + Node.js + Vue + Babel + Webpack)
(1)ES6
let & const 的用法
<body>
<script>
// {
// // var 声明的变量往往会越域
// // let 声明的变量没有严格局部作用域
// var a = 1;
// let b = 2;
// console.log(a); // 1
// console.log(b); // ReferenceError: b is not defined
// }
// var m = 1 // var 可以声明多次
// var m = 2
// let n = 3 // let 只能声明1次,多了报错
// // let n = 4
// console.log(m)
// console.log(n)
// var 会变量提升, let 不存在变量提升
// console.log(x) // undefined
// var x = 10;
// console.log(y) // ReferenceError: Cannot access 'y' before initialization
// let y = 20;
// const 声明常量:声明之后不允许改变,一旦声明必须初始化,否则会报错
const a = 1;
a = 2; // Uncaught TypeError: Assignment to constant variable.
</script>
</body>
let 和 const 是经常用的两个关键字。
解构表达式
<script>
// 数组解构
let arr = [1,2,3];
let [a, b, c] = arr;
console.log(a,b,c)
// 对象解构
const person = {
name: 'jack',
age: 18,
language: ['java', 'js', 'css']
}
const {name:newname, age, language} = person; // newname 为从person中取出name赋值给newname
console.log(newname, age, language)
</script>
字符串扩展
几个新的API
<script>
// 字符串扩展
let str = "hello.vue";
console.log(str.startsWith("hello")); //true
console.log(str.endsWith(".vue")); //true
console.log(str.includes("e")); //true
console.log(str.includes("hello")); //true
</script>
字符串模板:
相当于加强版的字符串,用反引号 ` ,除了作为普通字符串,还可以用来定义多行字符串,还可以在字符串中加入变量和表达式。
<script>
let str = `
<div>
<span>hello world</span>
</div>`;
console.log(str); //输出以上三行代码
</script>
<script>
const name = 'haha';
const age = 10;
let info = `我是${name}, 今年${age + 10}岁了, 我想说句话:${f()}`;
console.log(info); //我是haha, 今年20岁了, 我想说句话:这是一个函数
function f(){
return "这是一个函数";
}
</script>
函数优化
对象优化
map、reduce
promise异步编排
模块化
(2) VUE