学习地址:Java项目《谷粒商城》架构师级Java项目实战,对标阿里P6-P7,全网最强_哔哩哔哩_bilibili
一、项目简介
1.1项目微服务架构图
1.2微服务划分图
二、分布式基本概念
2.1、微服务
微服务架构风格,就像是一把单独的应用程序开发为一套小服务,每个小服务运行在自己的进程中,并使用轻量级机制通信,通常是HTTP API。这些服务围绕业务能力来构建,并通过完全自动化部署机制来独立部署。这些服务使用不同的编程语言书写,以及不同数据存储技术,并保持最低限度的集中式管理。
简而言之,拒绝大型单体应用,基于业务边界进行服务微化拆分,各个服务独立部署运行。
2.2、集群&分布式&节点
集群是物理形态,分布式是个工作方式。
只要是一堆机器,就可以叫集群,他们是不是一起协作着干活,这个谁也不知道;
《分布式系统原理与范型》定义:“分布式系统是若干独立计算机的集合,这些计算机对于用户来说就像单个相关系统”,分布式系统(distributed system)是建立在网络之上的软件系统。
分布式是指将不同的业务分布在不同的地方,集群指的是将几台服务器集中在一起,实现同一业务。
例如:京东是一个分布式系统,众多业务运行在不同的机器,所有业务构成一个大型的业务集群。每一个小的业务,比如用户系统,访问压力大的时候一台服务器是不够的,我们就应该将用户系统部署到多个服务器。也就是每一个业务系统也可以做集群化;分布式中的每一个节点都可以做集群。而集群并不一定就是分布式的。节点:集群中的一个服务器。
2.3、远程调用
在分布式系统中,各个服务可能处于不同主机,但是服务之间不可避免的需要相互调用,我们称之为远程调用。SpringCloud中使用HTTP+JSON的方式完成远程调用
2.4、负载均衡
分布式系统中,A服务需要调用B服务,B服务在多台机器中都存在,A调用任意一个服务器均可完成功能。
为了使每一个服务器都不要太忙或者太闲,我们可以负载均衡的调用每一个服务器,提升网站的健壮性。
常见的负载均衡算法:
轮询:为第一个请求选择健康池中的第一个后端服务器,然后按顺序往后一次选择,直到最后一个,然后循环。
最小连接:优先选择连接数最少,也就是压力最小的后端服务器,在会话较长的情况下可以考虑采取这种方式。
散列:根据请求源的IP的散列(hash)来选择要转发的服务器。这种方式可以一定程度上保证特定用户能连接到相同的服务器,如果你的应用需要处理状态而要求用户能连接到和之前相同的服务器,可以考虑采取这种方式。
2.5、服务注册/发现&注册中心
A服务调用B服务,A服务并不知道B服务当前在那几台服务器有,那些正常的,那些服务已经下线。解决这个问题可以引入注册中心;
如果某些服务下线,我们其他人可以实时的感知到其他服务的状态。从而避免调用不可用的服务。
2.6、配置中心
每个服务最终都有大量的配置,并且每个服务都可能部署在多台机器上。我们经常需要变更配置,我们可以让每个服务在配置中心获取自己的配置。配置中心用来集中管理微服务的配置信息。
2.7、服务熔断&服务降级
在微服务架构中,微服务之间通过网络进行通信,存在相互依赖,当其中一个服务不可用时,有可能会造成雪崩效应。要防止这样的情况,必须要有容错机制来保护服务。
1)服务熔断:设置服务的超时,当被调用的服务经常失败到达某个阈值,我们可以开启保护机制,后来的请求不再去调用这个服务。本地直接返回默认的数据。
2)服务降级:在运维期间,当系统处于高峰期,系统资源紧张。我们可以让非核心业务降级运行。降级:某些服务不处理,或者简单处理【抛异常、返回null、调用MOCK数据、调用Fallback处理逻辑】。
2.8、API网关
在微服务架构中,API Gateway作为整体架构的重要组件,它抽象了微服务中都需要的公共功能,同时提供了客户端负载均衡、服务自动熔断、灰度发布,统一认证、限流控制、日志统计等丰富的功能,帮助我们解决很多API管理难题。
三、环境搭建
3.1、使用docker安装mysql和redis
步骤1:安装虚拟机,步骤2:安装docker,步骤3:在docker中安装mysql,步骤4:在docker中安装redis
docker一些常用命令如下:
以下命令如果不是root用户,执行时命令前加sudo;或者使用su root方式切换到root用户
1、systemctl enable docker:设置docker开机自启
2、docker images:获取docker本地镜像
3、docker pull mysql:5.7 :从docker官网下载5.7版本的mysql镜像,如果没有指定版本默认下载最新版本的4、创建实例并启动:
docker run -p 3306:3306 --name mysql \
-v /mydata/mysql/log:/var/log/mysql \
-v /mydata/mysql/data:/var/lib/mysql \
-v /mydata/mysql/conf:/etc/mysql \
-e MYSQL_ROOT_PASSWORD=root \
-d mysql:5.7
参数说明:
docker run -p 3306:3306 --name mysql 启动容器,将容器的3306端口映射到主机的3306端口,为当前启动容器起名mysql
-v /mydata/mysql/conf:/etc/mysql 将容器配置文件夹/etc/mysql挂载到主机/mydata/mysql/conf
-v /mydata/mysql/log:/var/log/mysql 将容器日志文件夹/var/log/mysql挂载到主机/mydata/mysql/log
-v /mydata/mysql/data:/var/lib/mysql 将容器配置文件夹/var/lib/mysql挂载到主机/mydata/mysql/data
-e MYSQL_ROOT_PASSWORD=root 初始化root用户的密码为root
-d mysql:5.7 后台启动的方式,启动mysql版本为5.7的这个镜像的容器
注意:文件挂载的作用是当我们需要修改容器里面的一些配置时可以不用进入到容器的内部指定目录而只需要在linux外部目录修改即可。5、docker ps:查看docker正在运行中的容器
6、docker exec -it mysql /bin/bash:进入mysql容器的内部
7、docker restart mysql:重启mysql
8、docker exec -it redis redis-cli:进入redis客户端(进入后可执行set a b,get a等操作指令)
3.2、开发环境统一
步骤一:下载jdk配置环境变量
步骤二:下载maven,配置环境变量,配置阿里云镜像,配置使用jdk1.8编译项目
步骤三:在idea中配置jdk和maven
步骤四:在idea中下载简化开发的插件,lombok(简化开发通过注解不需要写get、set等方法),MyBatisX(可以从mapper文件定位到xml文件),gitee(在idea对git进行配置后可以在页面提交代码)
步骤五:下载安装前端开发软件VS code,在vs code安装常用组件
步骤六:安装配置git
步骤七:配置ssh免密登录
3.3、创建项目微服务
1)创建各个微服务模块
2)修改外层服务pom文件,使其聚合其他微服务
3)修改.gitignore文件,提交代码时对一些文件进行忽略
四、分布式组件的使用
4.1、Nacos注册中心
、
4.2、Feign远程调用
使用注解开启远程调用,并告诉springcloud当前feign接口处于那个包下:
编写feign接口注明需要调用那个远程服务的那个路径下接口:
4.3、Nacos配置中心
4.4、Nacos配置中心进阶
4.4.1、核心概念
命名空间:用于进行租户粒度的配置隔离。不同的命名空间下,可以存在相同的Group或Data ID的配置。Namespace的常用场景之一是不同环境的配置的分区隔离,例如开发测试环境和生成环境的资源(如配置、服务)隔离等。Nacos作为配置中心时,默认的配置文件添加到命名空间为public里面的,
(1)为了区分开发测试环境与生成生产环境不同的配置,可以新建不同的命名空间,然后在新的命名空间添加配置信息。如下所示:
(2)每一个微服务之间互相隔离配置,每一个微服务都创建自己的命名空间,只加载自己命名空间下的配置,使用配置分组区分环境dev、test、prod(配置分组的概念见下面内容)
配置集:一组相关或者不相关的配置的集合成为配置集,一个配置文件通常就是一个配置集,包含了系统各个方面的配置。例如,一个配置集可能包含了数据源、线程池、日志级别等配置项。
配置集ID:类似配置文件名,如下
配置分组:默认所有的配置集都属于:DEFAULT_GROUP;
如果一个命名空间下有多个配置分组信息,也可以指定使用那个分组的配置,如下:
加载多配置集:将配置信息拆分为多个注册到Nacos注册中心,最后对多个文件加载
4.5、Gateway网关
4.5.1简介
网关作为流量的入口,常用功能包括路由转发、权限校验、限流控制等。而springcloud gateway作为SpringCloud官方推出的第二代网关框架,取代了zuul网关。
4.5.2Gateway的使用
第一步创建网关服务,并添加依赖:
第二步:在网关服务中,添加Nacos依赖,配置文件中配置Nacos地址,开启服务注册与发现。以便将自己注册到Nacos的网关,并发现其他服务进行调用。
第三步:新建application.yml文件,在里面添加网关的配置,进行路由转发
五、商品服务功能实现
5.1、使用递归查询树形结构菜单数据
5.2路径重写
5.3跨域
跨域:指的是浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对javascript施加的安全限制。
同源策略:是指协议、域名、端口都要相同,其中一个不同都会产生跨域;
解决跨域
(一)使用nginx部署为同一域 (使用较少且麻烦)
(二)配置当前请求允许跨域
在网关服务中配置允许跨域代码如下:
5.4逻辑删除
5.5阿里云对象存储OSS
5.5.1云存储的开通与使用
1、注册登录阿里云网站,个人中心进行实名认证
2、开通对象存储,进入管理控制台的对象存储OSS页面
3、创建bucket
4、阿里云对象存储普通上传方式:用户请求发送的图片通过网关传到商品服务,商品服务通过java代码将要存储的图片或大文件存储到oss,并返回oss上的存储地址存到数据库,下次要获取图片先从数据库获取地址再到oss拿返回到前端展示。
但是这种方式效果并不好,当有大量请求时会给商品服务带来瓶颈。
5、阿里云对象存储-服务端签名后直传:为了防止前端使用oss的账号密码直接传照片文件给oss存储暴漏了密码等信息,用户的请求会先到达应用服务器(商品服务)拿到令牌,之后用户直接上传数据到oss,携带的数据令牌中包含账号密码等信息。
6、阿里云提供的oss实现文件存储方法
第一步在商品服务中引入依赖:
第二步代码实现:
7、SpringCloud提供的oss文件存储实现方法
第一步在商品服务引入依赖:
第二步在商品服务的application.yml中进行配置:配置参数根据实际情况在注册的阿里云网站查找个人的信息
第三步代码实现:
8、oss获取服务端签名实现文件存储
第一步:引入对象存储相关依赖
第二部配置oss相关信息:
第三步编写代码获取签名:
第四步:前端调用上述接口获取签名信息,再向oss发送请求实现文件存储
5.6数据校验
第一步实体类上加校验注解:全部可用的注解可参照javax.validation,dao包里面列举的
第二步在接口接收参数的地方使用@Valid注解通知该参数是需要校验的,上面entity的校验规则才能起到效果
第三步添加自定义错误信息提示:
第四步可以通过BindingResult result获取校验异常的字段和异常原因进行返回处理
5.7统一异常处理
建统一异常处理包,编写统一异常处理类:
异常码统一定义:
5.8JSR303分组校验
第一步使用group给校验注解标注什么情况下需要校验,并创建接口UpdataGroup和AddGroup内容为空即可
第二步将controller中@valid注解修改为@validated并标明使用新增分组的校验规则:
注意:默认没有使用校验分组的注解如@NotBlank,在分组校验情况下不生效;只有在使用@valid不用分组的情况下才生效。
5.9自定义校验
第一步添加依赖:
第二步编写自定义注解接口:
第三步编写规则校验类:
第四步使用自定义注解:
六、全文检索ElasticSearch
6.1docker安装ElasticSearch
第一步安装ElasticSearch镜像
第二步创建实例
第三步:安装Kibana,方便操作ElasticSearch的可视化界面
第四步:启动完成后访问192.168.56.10:5601可以进入Kibana客户端
6.2初步检索
6.2.1_cat
使用postman验证如下:
6.2.2索引一个文档
PUT customer/external/1
{
"name":"John Doe"
}
postman测试截图如下:
返回结果如下:
6.2.3查询文档
6.2.4更新文档
6.2.5删除文档&索引
ES中未直接提供删除类型(mysql中的叫法是表)的操作
6.2.6bulk批量API
6.3进阶检索
6.3.1SearchAPI
ES支持两种基本方式检索:
- 一个是通过使用REST request URL发送搜索参数(url+检索参数)
- 另一个是通过使用REST request body来发送他们(url+请求体)
6.3.2Query DSL
1)基本语法格式
2)返回部分字段
3)match【匹配查询】
4)match_phrase【短语匹配】
5)multi_match【多字段匹配】
6)bool复合查询
7)filter结果过滤
8)term
9)aggregations(执行聚合)
6.3.3Mapping
1、查看mapping:
2、创建mapping:
3、修改mapping添加新的映射字段:
4、更新映射
对于已经存在的映射字段,我们不能更新。更新必须创建新的索引进行数据迁移实现更新映射的目的。
5、数据迁移
创建new_ttwitter的在正确映射。然后使用如下方式进行数据迁移。
例如:
第一步:创建新的索引
第二步:数据迁移
6.3.4分词
6.3.4.1安装ik分词器
使用ik分词器进行智能分词:
6.3.4.2自定义词库
第一步:nginx的设置
第二步:修改配置文件,完成后重启es
第三步:测试
6.4Elasticsearch-Rest-Client
实现方法:
6.4.1SpringBoot整合
第一步:引入依赖
第二步:编写配置文件
第三步:测试
6.4.2测试保存数据到ES
6.4.3测试从ES中检索数据
七、商城业务功能实现
7.1nested数据类型场景
未指定nested数据类型会将数据进行扁平化处理:
为避免出现扁平化处理的现象,我们将数据设置为nested类型:
7.2商品上架-保存数据到Elasticsearch
第一步:编写上架商品属性类
第二步:编写Controller调用上架方法
第三步:在service层编写上架方法的实现类
7.3nginx搭建域名访问环境
八、压力测试
压力测试考察当前软硬件环境下系统所能承受的最大负荷并帮助找出系统瓶颈所在。压测都是为了系统在线上的处理能力和稳定性维持在一个标准范围内,做到心中有数。
使用压力测试,我们有希望找到很多种用其他方法更难发现的错误。有两种错误类型是:内存泄漏,并发与同步。
有效的压力测试系统将应用以下这些关键条件:重复、并发、量级、随机变化。
8.1性能指标
8.2JMeter
8.2.1JMeter安装
https://jmeter.apache.org/download_jmeter.cgi
下载对应压缩包,解压运行bin目录下的jmeter.bat即可启动jmeter
8.2.2JMeter压测示例
1、添加线程组
2、添加http请求:
3、添加查看结果树:
3、添加汇总报告:
3、添加聚合报告:
4、启动请求发送:
5、查看压测结果:
8.2.3JMeter Address Already inuse错误解决
8.3性能监控
8.3.1堆内存与垃圾回收
1、JVM内存模型
2、堆
8.3.2jconsole与jvisualvm
jdk的两个小工具jconsole\jvisualvm(升级版的jconsole);通过命令行启动,可监控本地和远程应用。远程应用需要配置
1、jvisualvm能干什么
2、安装插件方便查看gc
8.3.3监控指标
前端静态资源获取优化、业务优化、sql优化、增加索引、使用缓存、降低日志级别等待;
8.3.4JVM分析与调优
1、Nginx动静分离:
2、线上应用内存崩溃宕机:调大堆内存空间
3、优化业务,避免多次查询数据库,最好一次查询出来再通过业务进行过滤处理
九、缓存与分布式锁
9.1缓存
9.1.1缓存使用
为了系统性能的提升,我们一般都会将部分数据放入缓存中,加速访问。而db承担数据落盘工作。
那些数据适合放入缓存?
- 即时性、数据一致性要求不高的
- 访问量大且更新频率不高的数据(读多,写少)
举例:电商类应用、商品分类,商品列表等适合缓存并加一个失效时间(根据数据更新频率来定),后台如果发布一个商品,买家需要5分钟才能看到新的商品一般还是可以接受的。
9.1.2整合redis作为缓存
第一步:虚拟机安装redis
第二步:引入redis依赖
第三步:配置redis相关参数信息
第四步:使用springboot自动配置好的StringRedisTemplate来操作redis
使用redis改造三级分类业务:
9.2缓存失效问题
缓存穿透:
缓存击穿:
缓存雪崩:
为解决以上问题,对缓存模块代码进行改造:
上述代码中的本地锁,只能锁住当前进程,如果我们的商品服务是部署在多台服务器上运行的,那么每台服务器上都会至少有一个线程对数据库进行操作,所有我们需要使用分布式锁:
9.3缓存数据一致性
9.4分布式锁
9.4.1分布式锁演进
为解决阶段二的问题,修改代码如下,让占锁和设置过期时间同时发生,成为原子的命令:
设置过期时间虽然可以解决阶段二的问题,但是但线程1的锁过期释放了,线程2进来了,此时线程1执行删除锁的操作,可以误删线程2的锁;
为解决阶段三的问题,使用uuid,只有当前锁是自己的锁才删除
使用lua脚本保证判断+删除的原子性:
防止业务还未执行完锁已经过期了,我们需要不断地给锁续期,还可以更简单的处理:直接将锁的过期时间设置久一点,等业务执行完再自己去删除锁即可。
9.4.2Redisson
9.4.2.1Redisson简介与整合
整合redisson作为分布式锁等功能框架
第一步:引入依赖
第二步:配置redisson
第三步:redisson的使用测试
Redission的优势:
1、使用看门狗,当业务还未执行完,锁的过期时间已经到了,会自动续期锁的过期时间,保证业务执行完成后再释放锁;
2、当线程1业务执行完但是还未释放锁出现阻塞等问题无法释放锁,redisson会帮我们释放锁,让其他线程继续执行,避免死锁。
9.4.2.2Redission-lock看门狗原理&Redission如何解决死锁
9.4.2.3读写锁
只有读锁时和没有加锁差不多,多个服务可以同时读取数据;但是当有写锁没释放时,写锁就一直等待,只有等当前写锁写完数据,其他服务才可以读数据或者写数据。
读写锁保证了一定能读取到最新数据,修改期间,写锁是一个排他锁(互斥锁),读锁是一个共享锁。
读+读:相当于无锁,并发读,只会在redis中记录好,所有当前的读锁。他们都会同时加锁成功
写+读:等待写锁释放
写+写:阻塞方式
读+写:先有读锁,写也需要等待
9.4.2.4Redisson-闭锁
使用闭锁实现只有等五个班的人都走了才执行锁大门的操作:
9.4.2.5Redisson-信号量
在redis中设置值(park,3),执行park方法会获取一个信号量key=park对应值减一,执行go方法会释放一个信号量key=park对应值加一。
park.acquire()方法:获取不到信号量的时候会等待有信号量被释放再抢占。
park.tryAcquire()方法:获取不到信号量就不会等待,不会处理。
信号量可以用作分布式限流。