一、项目简介
1、项目背景
1)、电商模式
2)、谷粒商城
谷粒商城是一个 B2C 模式的电商平台,销售自营商品给客户。
2、项目架构图
1、项目微服务架构图
2、微服务划分图
3、项目技术&特色
4、项目前置要求
二、分布式基础概念
2、集群&分布式&节点
分布式是指将不同的业务分布在不同的地方。
集群指的是将几台服务器集中在一起,实现同一业务。
例如:京东是一个分布式系统,众多业务运行在不同的机器,所有业务构成一个大型的业务集群。每一个小的业务,比如用户系统,访问压力大的时候一台服务器是不够的。我们就 应该将用户系统部署到多个服务器,也就是每一个业务系统也可以做集群化; 分布式中的每一个节点,都可以做集群。 而集群并不一定就是分布式的。 节点:集群中的一个服务器
4、负载均衡
7、服务熔断&服务降级
1)、服务熔断 a. 设置服务的超时,当被调用的服务经常失败到达某个阈值,我们可以开 启断路保护机制,后来的请求不再去调用这个服务。本地直接返回默认 的数据
2)、服务降级 a. 在运维期间,当系统处于高峰期,系统资源紧张,我们可以让非核心业 务降级运行。降级:某些服务不处理,或者简单处理【抛异常、返回 NULL、 调用 Mock 数据、调用 Fallback 处理逻辑】。
三、项目架构
架构图
微服务划分图
四、环境搭建
docker
centos安装docker
https://docs.docker.com/engine/install/centos/
设置开机自启
systemctl enable docker
设置开机自启docker容器mysql服务
docker update mysql --restart=always
配置阿里云镜像加速
sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-‘EOF’
{
“registry-mirrors”: [“https://76ra8yjq.mirror.aliyuncs.com”]
}
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker
docker安装mysql
启动docker镜像
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
参数说明-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 用户的密码
进入docker容器
docker exec -it f1178d5b0bd8 /bin/bash
修改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
使配置文件生效
docker ps
docker restart mysql
docker安装redis
挂载可能会把文件当成目录,所以预先创建好文件
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
连接容器内redis客户端
docker exec -it redis redis-cli
当前数据保存在内存中,需要配置持久化规则
vim /mydata/redis/conf/redis.conf
appendonly yes
docker restart redis
maven
git使用ssh连接
git管理仓库
开发生产者模型:开发在dev分支,开发完成合并到master分支
idea新建项目
创建微服务项目
从模块中复制一个pom文件放在根目录下,进行修改
可以取消这两个勾选,它会检查分析代码,包括TODO
数据库设计
整合人人开源-后台管理系统
下载
https://gitee.com/renrenio 下载renren-fast和renren-fast-vue
快速搭建后台管理系统:renren-fast整合到gulimall项目中;vscode打开renren-fast-vue配置开发环境
npm install npm run dev
配置前端运行环境
node.js
代码生成器(逆向工程)
下载:https://gitee.com/renrenio/renren-generator.git
整合进gulimall项目,修改配置文件,先生成product模块基本文件
运行服务,访问localhost,点击生成下载.zip压缩文件,解压放进gulimall-product
缺失很多依赖,创建common模块,引入依赖和renren-fast中的部分依赖文件
shift+f6修改模块名称
修改逆向工程,不使用shiro依赖,重新生成代码
分布式组件
服务注册
1、引入依赖
2、开启服务注册
3、编写配置文件
feign
1、导入依赖
2、开启支持feign远程调用
3、编写声明式接口
配置中心
如何使用nacos作为配置中心统一管理配置
1、引入依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
2、创建一个bootstrap.properties文件
spring.application.name=guli-coupon
spring.cloud.nacos.config.server-addr=127.0.0.1:8848
3、需要给配置中心默认添加一个叫 数据集(Data Id)guli-coupon.properties。默认规则:应用名.properties
4、给 应用名.properties添加任何配置
5、动态获取配置
@RefreshScope:动态获取并刷新配置
@Value(“${配置项的名称}”):获取配置
配置中心优先级>本地配置优先级
gateway
前端内容
ES6
ECMAScript 6.0(以下简称 ES6,ECMAScript 是一种由 Ecma 国际(前身为欧洲计算机制造商 协会,英文名称是 European Computer Manufacturers Association)通过 ECMA-262标准化的脚本 程序设计语言)是 JavaScript 语言的下一代标准
模块化
vue
1、文件夹内终端敲入命令:npm init -y
2、npm install vue
3、引入vue,script中src=“./node_modules/vue/dist/vue.js”
vue模块化开发
目录结构
项目开发
商品服务
1、数据库导入数据
2、运行renren-vue和后台
配置网关
请求转发Service Unavailable问题
https://blog.csdn.net/kitahiragawa/article/details/124229580
原因:这是由于版本不兼容引发的问题,我当前使用的springcloud alibaba版本为2021.0.1.0,而springcloud alibaba在2020版之后不支持ribbon,而springcloud gateway使用ribbon,就导致了gateway无法路由到目标服务
跨域问题
不是简单请求就需要发送一个Options方式的预检请求
https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS
解决跨域
product模块逻辑删除
1)、配置全局的逻辑删除规则(省略)
2)、配置逻辑删除的组件Bean(省略)
3)、给Bean加上逻辑删除注解@TableLogic
商品服务-三级分类
商品服务-品牌管理
oss上传
根据github上springCloudAlibaba演示demo引入
https://github.com/alibaba/spring-cloud-alibaba
nacos config配置:
yml文件配置
注意点:需要加如下依赖
<!–cloud新版本默认将bootstrap移除了,所以需要添加如下依赖–>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
table显示图片
table->自定义模板->在里面配置template,scope内容自定义显示
异常排查:
1、main.js是否引入elementui
2、main.js引入的是src下的elementui
3、elemnetui/index.js中没有引入image
4、去element官网快速上手配置引入image
jsr303数据校验
JSR303
1)、给Bean添加校验注解:javax.validation.constraints,并定义自己的message提示
2)、开启校验功能@Valid
效果:校验错误以后会有默认的响应;
3)、给校验的bean后紧跟一个BindingResult,就可以获取到校验的结果
4)、分组校验(多场景的复杂校验)
1)、 @NotBlank(message = “品牌名必须提交”,groups = {AddGroup.class,UpdateGroup.class})
给校验注解标注什么情况需要进行校验
2)、@Validated({AddGroup.class})
3)、默认没有指定分组的校验注解@NotBlank,在分组校验情况@Validated({AddGroup.class})下不生效,只会在@Validated生效;
5)、自定义校验
1)、编写一个自定义的校验注解
2)、编写一个自定义的校验器 ConstraintValidator
3)、关联自定义的校验器和自定义的校验注解
统一异常处理
统一的异常处理
@ControllerAdvice
1)、编写异常处理类,使用@ControllerAdvice。
2)、使用@ExceptionHandler标注方法可以处理的异常。
spu、sku
SPU:Standard Product Unit(标准化产品单元)
SKU:Stock Keeping Unit(库存量单位)
iphoneX 是 SPU、MI 8 是 SPU
iphoneX 64G 黑曜石 是 SKU
MI8 8+64G+黑色 是 SKU
Object划分
项目运行内存配置
1、新建、配置compound
2、配置最大占用内存
@Transactional分析
断点执行看不到之前代码的入库数据:
1、因为事务没有提交之前,数据是读不出来的;
2、mysql默认的隔离级别是可重复读,就是最起码读到已经提交了的数据,为了测试方便,将当前会话的隔离级别等级设置为读未提交
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
3、然后就可以很方便得查看数据库变化
让@Transactional事务遇到异常不回滚
1、可以使用try catch捕获异常
2、
库存&采购流程
采购需求->合并整单->采购单->库存
基础篇总结
#
分布式高级篇
ElasticSearch
官方文档:https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html
官方中文:https://www.elastic.co/guide/cn/elasticsearch/guide/current/foreword_id.html 社区中文: https://es.xiaoleilu.com/index.html http://doc.codingdict.com/elasticsearch/0/
1、下载镜像文件
docker pull elasticsearch:7.4.2 存储和检索数据
docker pull kibana:7.4.2 可视化检索数据
2、创建实例
1、ElasticSearch
mkdir -p /mydata/elasticsearch/config
mkdir -p /mydata/elasticsearch/data
echo “http.host: 0.0.0.0” >> /mydata/elasticsearch/config/elasticsearch.yml
chmod -R 777 /mydata/elasticsearch/ 保证权限
docker run --name elasticsearch -p 9200:9200 -p 9300:9300 \
-e “discovery.type=single-node”
-e ES_JAVA_OPTS=“-Xms64m -Xmx512m” \
-v /mydata/elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml \
-v /mydata/elasticsearch/data:/usr/share/elasticsearch/data \
-v /mydata/elasticsearch/plugins:/usr/share/elasticsearch/plugins \
-d elasticsearch:7.4.2
http.host: 0.0.0.0 : es可以被远程任何机器访问
-e:配置参数
-p:暴露端口-9200是我们给es的restAPI发请求的端口-9300是es在分布式集群状态下的节点通信端口
discovery.type:单节点模式
ES_JAVA_OPTS:不指定的话es一启动会将内存全部占用,虚拟机就卡死了
-d:后台启动
以后再外面装好插件重启即可;
特别注意: -e ES_JAVA_OPTS=“-Xms64m -Xmx256m” \ 测试环境下,设置 ES 的初始内存和最大内存,否则导 致过大启动不了 ES
查看日志:docker logs elasticsearch
访问:http://192.168.233.141:9200/
2、Kibana
docker run --name kibana -e ELASTICSEARCH_HOSTS=http://192.168.233.141:9200 -p 5601:5601 \ -d kibana:7.4.2
访问:http://192.168.233.141:5601/app/kibana#/dev_tools/console?_g=()
初步检索
_seq_no可以作乐观锁
样本数据 acounts.json
https://github.com/elastic/elasticsearch/blob/7.5/docs/src/test/resources/accounts.json
进阶检索
https://www.elastic.co/guide/en/elasticsearch/reference/7.4/getting-started-search.html
启动docker容器
docker ps -a
docker start containerId
开机自启
docker update containerId --restart=always
mapping
type是keyword类型:不会进行全文检索,只会精确匹配
type是text类型的:全文检索,保存数据的时候进行分词,检索的时候按照分词进行匹配
ES5.0及以后的版本取消了string类型,将原先的string类型拆分为text和keyword两种类型。它们的区别在于text会对字段进行分词处理而keyword则不会进行分词。
也就是说如果字段是text类型,存入的数据会先进行分词,然后将分完词的词组存入索引,而keyword则不会进行分词,直接存储。
text类型的数据被用来索引长文本,例如电子邮件主体部分或者一款产品的介绍,这些文本会被分析,在建立索引文档之前会被分词器进行分词,转化为词组。经过分词机制之后es允许检索到该文本切分而成的词语,但是text类型的数据不能用来过滤、排序和聚合等操作。
keyword类型的数据可以满足电子邮箱地址、主机名、状态码、邮政编码和标签等数据的要求,不进行分词,常常被用来过滤、排序和聚合
安装ik分词器
1、下载https://github.com/medcl/elasticsearch-analysis-ik/releases?after=v6.4.2
2、解压到plugin文件夹,装在ik目录中
3、进入es容器:docker exec -it 容器id /bin/bash,elasticsearch-plugin list
4、重启容器
docker restart elasticsearch
docker安装nginx
es自定义词库
springboot整合es
为什么不用js直接操作es
1、安全性
2、js对es支持度低
分析:
1、方便检索
{
skuId:1
spuId:11
skuTitle:华为xx
price:998
saleCount:99
attrs:[{尺寸:4寸},{CPU:高通945},{分辨率:全高清}]
}
冗余:100w商品 每个商品20个属性,假设合起来2kb数据 100w2kb=2000MB=2G 商城系统多了2G
2、
sku索引{
skuId:1
spuId:11
xxxxxx
}
attr索引{
spuId:11
attrs:[{尺寸:4寸},{CPU:高通945},{分辨率:全高清}]
}
问题: 搜索’小米’ 粮食、手机、电器
1w个 4k个spu
分步:4k个spu对应的所有可能属性
esClient请求搜索4k个spuId,每个都是long型数据 4k8=32000byte=32kb
1w人在商城检索商品:1w*32kb=320MB 一次光数据传输320mb,高并发情况下会网络阻塞
总结:空间和时间不能二者兼得,所以我们这里选择第一种设计模型
PUT product
{
“mappings”: {
“properties”: {
“skuId”: {
“type”: “long”
},
“spuId”: {
“type”: “keyword”
},
“skuTitle”: {
“type”: “text”,
“analyzer”: “ik_smart”
},
“skuPrice”: {
“type”: “keyword”
},
“skuImg”: {
“type”: “keyword”,
“index”: false,
“doc_values”: false
},
“saleCount”: {
“type”: “long”
},
“hasStock”: {
“type”: “boolean”
},
“hotScore”: {
“type”: “long”
},
“brandId”: {
“type”: “long”
},
“catalogId”: {
“type”: “long”
},
“brandName”: {
“type”: “keyword”,
“index”: false,
“doc_values”: false
},
“brandImg”: {
“type”: “keyword”,
“index”: false,
“doc_values”: false
},
“catalogName”: {
“type”: “keyword”,
“index”: false,
“doc_values”: false
},
“attrs”: {
“type”: “nested”,
“properties”: {
“attrId”: {
“type”: “long”
},
“attrName”: {
“type”: “keyword”,
“index”: false,
“doc_values”: false
},
“attrValue”: {
“type”: “keyword”
}
}
}
}
}
}
index:false 表示该属性不可用来被检索
doc_values: true 表示可以用来作聚合、排序、脚本等操作,进行空间节省
“type”: “nested” 表示属性子对象中的某些值进行检索
nginx+windows搭建域名访问环境
域名解析:我们在浏览器上敲gulimall.com,windows如何知道哪个域名对应哪个ip地址?
1、查看系统内部系统映射规则(网卡带我们转过去)
2、如果系统没告诉gulimall.com在哪个地址,那就去网络上的dns(比如114.114.114.114,8.8.8.8),解析出域名(dns中保存了每个域名对应每个ip地址,在公网保存),查看ip映射规则
1、配置hosts
192.168.233.141 gulimall.com,http://gulimall.com:9200/测试配置是否成功
2、配置nginx配置文件,请求发到网关
3、在网关配置转发策略
4、域名请求依然是404
因为nginx代理给网关的时候,会丢失请求的host信息
配置proxy_set_header Host $host
压力测试
java内存模型
压测数据
压测内容 | 路径 | 压测线程数 | 吞吐量/s | 90%响应时间 | 99%响应时间 |
---|---|---|---|---|---|
Nginx | http://192.168.233.141:80/ | 50 | 2335 | 11 | 944 |
Gateway | http://localhost:88/ | 50 | 10367 | 8 | 31 |
简单服务 | localhost:10000/hello | 50 | 11341 | 8 | 17 |
首页一级菜单渲染 | localhost:10000 | 50 | 270(db,thymeleaf) | 267 | 365 |
首页渲染(thymeleaf开缓存) | localhost:10000 | 50 | 290 | 251 | 365 |
首页渲染(thymeleaf开缓存、 优化数据库、降低日志级别日 志(关日志)) | localhost:10000 | 50 | 700 | 105 | 183 |
三级分类数据获取 | localhost:10000/index/catalog.json | 50 | 2(db)/8(加索引) | ||
三级分类(优化业 务,减少与数据库的交互) | localhost:10000/index/catalog.json | 50 | 111 | 571 | 896 |
三 级 分 类 ( 使 用 redis 作为缓存) | localhost:10000/index/catalog.json | 50 | 411 | 153 | 217 |
首页全量数据获取 | localhost:10000 | 50 | 7(静态资源) | ||
Nginx+Gateway | 50 | ||||
Gateway+简单服务 | localhost:88/hello | 50 | 3126 | 30 | 125 |
全链路 | gulimall.com/hello | 50 | 800 | 88 | 310 |
监控内存使用率等数据:docker stats
测nginx
简单服务
首页全量数据获取
开thymeleaf缓存、优化数据库、关日志、nginx动静分离
效果还是不明显,吞吐量从7-11
开thymeleaf缓存、优化数据库、关日志、nginx动静分离、调节内存
-Xms 堆内存的初始大小,默认为物理内存的1/64
-Xmx 堆内存的最大大小,默认为物理内存的1/4
-Xmn 堆内新生代的大小。通过这个值也可以得到老生代的大小:-Xmx减去-Xmn
-Xss 设置每个线程可使用的内存大小,即栈的大小
测试服务崩溃
三 级 分 类 ( 使 用 redis 作为缓存)
问题:压测报错
解决:
TODO:压测产生堆歪内存溢出:OutOfDirectMemoryError
1):springboot2.0以后默认使用lettuce作为操作redis的客户端,它使用netty来进行网络通信
2):lettuce的bug导致netty堆外内存溢出 -Xmx300m netty如果没有指定堆外内存,默认使用-Xmx300m
可以通过-Dio.netty.maxDirectMemory进行设置
解决方案:不能使用-Dio.netty.maxDirectMemory只去调大堆外内存(调大内存只能延缓报错的时间)
1、升级lettuce客户端
2、切换使用jedis客户端
总结:
1、中间件越多,性能损失越大,大多都损失在网络交互了;
2、业务:
Db(MySQL 优化) 模板的渲染速度(缓存) 静态资源
优化数据库
新建索引
动静分离
1、在nginx的html文件夹中新建static文件夹
2、将guli-product中的index下静态资源复制到static文件夹下
3、将href和<script src =""等各路径指向的静态资源加/static路径
4、修改nginx配置文件(这里的share文件夹是因为拷贝的es分词器的,应该是docker默认路径)
缓存与分布式锁
ttl命令:查看key剩余的过期时间
会产生数据一致性问题
redisTemplate和Lettuce、Jedis关系
Lettuce、Jedis都是操作redis的底层客户端,spring再次封装成redisTemplate
所以不管是Lettuce还是Jedis连接redis,都是兼容的
高并发下缓存失效问题
本地锁
本地锁在分布式场景下锁不住所有的服务
本地锁:synchronized、JUC(Lock),在分布式场景下,想要锁住所有的服务,必须使用分布式锁
这里this锁对象指向当前的serviceImpl对象,分布式场景下每个this都不一样
问题:本地锁压力测试,测试结果:依然多次查询了数据库(锁的时序问题)
解决:当前锁时序
当前是在释放锁后存进redis
真正完整合理的逻辑:查数据库和存入缓存应该是一个原子操作,也就是在同一把锁内完成,否则就会有释放锁的时序问题,导致多次查询数据库
分布式锁_redis
本地锁在分布式场景下的问题
1、
2、启动多个服务,压力测试
gulimall.com->nginx->gateway负载均衡
3、现象:
redis分布式锁
setnx是原子性的
xshell发送命令到全部客户端,docker exec -it redis redis-cli,进入redis容器的客户端命令行,发送setnx命令
版本一:
版本二:
如果getDataFromDb出现异常,又或者服务掉线,导致锁未释放,发生死锁
设置过期时间
又为了防止在获取到锁和设置过期时间之间发生宕机,使用setnx ex原子性操作
版本三
版本四
如果由于业务时间很长,锁自己过期了,我们直接删除,有可能把别人正在持有的锁删除了
解决: 占锁的时候,值指定为uuid,每个人匹配是自己 的锁才删除。
版本五
1、如果正好判断是当前值,正要删除锁的时候,锁已经过期(网络延时), 别人已经设置到了新的值。那么我们删除的是别人的锁
解决: 删除锁必须保证原子性。使用redis+Lua脚本完成
redis分布式锁两个核心:
1、加锁保证原子性
2、解锁保证原子性
3、key过期时间放长一点
分布式锁_redisson
文档:https://github.com/redisson/redisson/wiki/%E7%9B%AE%E5%BD%95
可重入锁:a、b方法都加锁,加的是一样的锁,a获取到的锁b可以用,也就等于b方法直接执行
不可重入锁:a、b方法都加锁,加的一样的锁,b需要等a释放锁才可以执行,导致死锁
读写锁
读锁要等写锁释放才可以读取数据,写锁要排队,读锁不用排队
读 + 读 :相当于无锁,并发读,只会在Redis中记录好,所有当前的读锁。他们都会同时加锁成功
写 + 读 :必须等待写锁释放
写 + 写 :阻塞方式
读 + 写 :有读锁。写也需要等待
闭锁
相当于juc中的CountDownLatch
缓存一致性
缓存保证的是最终一致性,不是强一致,实时一致
双写模式
失效模式
解决方案
springCache
@EnableConfigurationProperties
@EnableConfigurationProperties的作用是把springboot配置文件中的值与我们的xxxProperties.java的属性进行绑定,需要配合@ConfigurationProperties使用 我们这里是获取properties的配置文件数据然后将数据和CacheProperties属性对应
检索服务
搭建页面环境
1、将html中搜索页index.html复制进gulimall-search的template目录,将./改成/static/search/
(1、导入thymeleaf依赖,否则路径访问请求不到index.html页面;2、打包指定yml文件,否则报错Param ‘serviceName’ is illegal)
2、将其他静态资源放进nginx的/mydata/nginx/html/static/search目录
3、hosts文件配置search.gulimall.com映射,nginx配置文件配置server_name
4、在gateway配置search.gulimall.com的host映射
5、访问http://search.gulimall.com/
检索服务
1、skuPrice的类型改成double,否则range区间过滤有问题
# 创建索引
PUT gulimall_product
{
"mappings": {
"properties": {
"attrs": {
"properties": {
"attrId": {
"type": "long"
},
"attrName": {
"type": "keyword"
},
"attrValue": {
"type": "keyword"
}
},
"type": "nested"
},
"brandId": {
"type": "long"
},
"brandImg": {
"type": "keyword"
},
"brandName": {
"type": "keyword"
},
"catalogId": {
"type": "long"
},
"catalogName": {
"type": "keyword"
},
"catelogId": {
"type": "long"
},
"catelogName": {
"type": "keyword"
},
"hasStock": {
"type": "boolean"
},
"hosStock": {
"type": "boolean"
},
"hotScore": {
"type": "long"
},
"saleCount": {
"type": "long"
},
"skuId": {
"type": "long"
},
"skuImg": {
"type": "keyword"
},
"skuPrice": {
"type": "keyword"
},
"skuTitle": {
"type": "text"
},
"spuId": {
"type": "long"
}
}
}
}
#检索服务
#模糊匹配、复合类型字段查询
#过滤(按照属性、分类、品牌、价格区间、库存)
#排序、分页、高亮
#聚合分析
GET /gulimall_product/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"skuTitle": "华为"
}
}
],
"filter": [
{
"term": {
"catalogId": "225"
}
},
{
"terms": {
"brandId": [
"1",
"2",
"9"
]
}
},
{
"nested": {
"path": "attrs",
"query": {
"bool": {
"must": [
{
"term": {
"attrs.attrId": {
"value": "15"
}
}
},
{
"terms": {
"attrs.attrValue": [
"海思(Hisilicon)",
"以官网信息为准"
]
}
}
]
}
}
}
},
{
"term":{
"hasStock":{
"value":"true"
}
}
},
{
"range":{
"skuPrice":{
"gte":0,
"lte":6000
}
}
}
]
}
},
"sort": [
{
"skuPrice": {
"order": "desc"
}
}
],
"from": 0,
"size": 5,
"highlight": {
"fields": {
"skuTitle":{}},
"pre_tags":"<b style='color:red'>",
"post_tags":"</b>"
}
}
异步&线程池
面试: 一个线程池 core 7; max 20 ,queue:50,100 并发进来怎么分配的;
先有 7 个能直接得到执行,接下来 50 个进入队列排队,在多开 13 个继续执行。现在 70 个 被安排上了。剩下 30 个默认拒绝策略。
初始化线程的 4 种方式
1、初始化线程的 4 种方式
1)、继承 Thread
2)、实现 Runnable 接口
3)、实现 Callable 接口 + FutureTask (可以拿到返回结果,可以处理异常)
4)、线程池 给线程池直接提交任务
区别:
1、2不能得到返回值,3可以获取返回值
1、2、3都不可以控制资源
4可以控制资源,系统稳定,无论面对多高的并发,不会耗尽资源,让系统崩溃
线程池七大参数
- corePoolSize:核心线程数【一直存在,除非设置了allowCoreThreadTimeOut】;线程池创建好以后就准备就绪的线程数量,就等待来接受异步任务去执行
- maximumPoolSize:最大线程数量,控制资源
- keepAliveTime:存活时间。如果当前正在运行的线程数量大于core数量,释放空闲的线程。只要线程空闲大于指定的keepAliveTime
- unit:时间单位
- BlockingQueue<Runnable> workQueue:阻塞队列。如果任务有很多,就会将目前多的任务放在队列里面。只要有线程空闲,就会去队列里面去除新的任务继续执行
- threadFactory:线程的创建工厂
- RejectedExecutionHandler handler:如果队列满了,按照我们指定的拒绝策略拒绝执行任务
工作顺序:
1、线程池创建,准备好 core 数量的核心线程,准备接受任务
2、新的任务进来,用 core 准备好的空闲线程执行。
(1) 、core 满了,就将再进来的任务放入阻塞队列中。空闲的 core 就会自己去阻塞队 列获取任务执行
(2) 、阻塞队列满了,就直接开新线程执行,最大只能开到 max 指定的数量
(3) 、max 都执行好了。Max-core 数量空闲的线程会在 keepAliveTime 指定的时间后自 动销毁。最终保持到 core 大小
(4) 、如果线程数开到了 max 的数量,还有新任务进来,就会使用 reject 指定的拒绝策 略进行处理
3、所有的线程创建都是由指定的 factory 创建的。
拒绝策略
1、丢弃队列中最老的任务
2、直接拒绝新来的任务,会抛异常
3、直接调用run方法,异步变同步
4、直接拒绝,不抛异常
常见的 4 种线程池
1、core是0,所有都可回收
2、固定大小,core=max,所有都不可回收
3、定时任务的线程池
4、单线程池,后台从队列里面获取任务,挨个执行
CompletableFuture 异步编排
1、2、3可以并发查,4、5、6可以并发查,4、5、6依赖1返回的spuId
商品详情
1、配置hosts的item.gulimall.com,nginx的路由,gateway网关(nginx之前配置过*.gulimall.com,所以不用再配置了)
2、将详情的静态资源文件传到nginx的html/item目录下
3、修改html访问路径,访问http://item.gulimall.com/
自定义配置线程池
异步查询商品详情
认证服务
配置auth.gulimall.com,nginx不用配置,将登陆、注册页面的静态资源文件复制进reg、login文件夹下
jsr303校验
1、实体类加限制
2、加@Valid注解
3、BindingResult result接收错误
OAuth2.0协议
OAuth2.0:对于用户相关的 OpenAPI(例如获取用户信息,动态同步,照片,日志,分 享等),为了保护用户数据的安全和隐私,第三方网站访问用户数据前都需要显式的向 用户征求授权。
微博登录
分布式session
1、导入依赖
2、yml文件配置
3、@EnableRedisHttpSession
问题:
1、不能跨不同域名共享
2、同一个服务,复制多份,session不同步问题
3、不同服务,session不能共享问题
1、session存到redis,
2、解决子域session共享问题
3、使用json序列化的方式来序列化对象到redis
需要在gulimall域名下也可以访问
jdk默认的序列化方式存储到redis
json序列化的方式
单点登录
不用使用springSession域名放大
购物车
ThreadLocal
每个线程互不干扰
消息队列
消息中间件应用场景:
1、异步处理
2、应用解耦(B服务修改接口,A调用B也就需要修改接口)
3、流量控制
RabbitMQ概念
Docker安装RabbitMQ
docker run -d --name rabbitmq -p 5671:5671 -p 5672:5672 -p 4369:4369 -p 25672:25672 -p 15671:15671 -p 15672:15672 rabbitmq:management
docker update rabbitmq --restart=always
Exchange 类型
修改rabbitmq消息序列化方式
发送消息,如果发送的消息是个对象,会使用序列化机制,将对象写出去,对象必须实现Serializable接口
如果要修改为json,分析RabbitAutoConfiguration的RabbitTemplate的bean对象
RabbitMQ测试
参数可以写以下类型:
1、Message message:原生消息详情信息。头+体
2、T<发送的消息的类型> OrderReturnReasonEntity content;
3、Channel chanel:当前传输数据的通道
Queue:可以很多人都来监听。只要收到消息,队列删除消息,而且只能有一个收到此消息
场景:
1):订单服务启动多个(集群场景);同一个消息,只能有一个客户端收到
2):只有一个消息完全处理完,方法运行结束,我们就可以接收到下一个消息
监听消息:使用@RabbitListener; 主启动类必须有@EnableRabbit
@RabbitListener: 类+方法上(监听哪些队列即可)
@RabbitHandler: 标在方法上(重载区分不同的消息)
RabbitMQ消息确认机制-可靠抵达
消息确认机制_发送端确认
两个回调分别表示:
confirm回调:消息抵达Broker
return回调:只要消息没有投递给指定的队列,就触发这个失败回调(比如把routingkey写错)
消费端确认(保证每个消息都被正确消费,此时才可以broker删除这个消息)
订单服务
整合环境:
1、复制订单html进order模块,拷贝静态资源进nginx
2、配置网关
feign远程调用丢失请求头问题
feign异步调用丢失请求头问题
提交订单的幂等性处理
幂等解决方案
1、token 机制
Token 获取、比较和删除必须是原子性
2、各种锁机制
本地事务&分布式事务
本地事务
springboot中的事务的坑
a方法中直接调用b、c方法,使用的事务配置和a保持一致;
如果要使用b、c方法自己的事务配置,需要bService.b(),cService.c()
本地事务失效问题
同一个对象内事务方法互调默认失效,原因是绕过了代理对象,事务使用代理对象来控制的
解决:使用代理对象来调用事务方法
1)、引入aop-starter(因为此依赖引入了aspectj依赖)<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
2)、@EnableAspectJAutoProxy(exposeProxy = true)
//开启aspectj动态代理功能。以后所有的动态代理都是aspectj创建的(即使没有接口也可以创建动态代理,对外暴露代理对象)。注:jdk动态代理必须有接口
3)、AopContext.currentProxy()
分布式事务
七、RabbitMQ延时队列(实现定时任务)
RabbitMQ使用的是惰性检查机制,所以一般是给队列设置过期时间,不是给消息设置过期时间
代码实现
升级后
消息队列流程
解决:在订单释放结束,再给库存服务发送消息,释放库存
最终一致性方案
保证消息可靠性
支付业务
加密
内网穿透
秒杀
定时任务
解决定时任务阻塞问题
使用redisson解决分布式系统的幂等性问题
使用redis的信号量模拟库存,减少并发场景下与数据库的交互
高并发场景应对
我们使用了:
1、我们将秒杀单独抽取出来了,秒杀服务
2、我们使用随机码,只有秒杀时间到了,才能收到随机码
3、我们使用定时任务,将秒杀的商品存进了redis,库存信息使用redisson分布式信号量控制,只有抢到了信号量才会去数据库
4、nginx动静分离
后续再加后四个
5、登陆拦截器(没有在网关层去做恶意请求拦截器)
6、我们使用队列削峰机制
秒杀
使用rabbitmq实现流量的削峰,而不是每个请求都去调用订单服务文章来源:https://uudwc.com/A/RxxwA
SpringCloud Alibaba-Sentinel
功能:熔断、降级、限流
熔断:feign调用的远程服务出现问题,直接返回fallback指定的方法
降级:指定时间内指定请求数超过指定时长,一段时间内再调用该接口,就会触发熔断文章来源地址https://uudwc.com/A/RxxwA