后台管理、网关、商品、检索、订单、购物车、仓库、秒杀、物流、会员、优惠、支付
动静分离 - Nginx
注册中心、配置中心 - nacos
认证中心(社交登录,weibo) - OAuth2.0
权限控制 - Spring Security
统一网关 - SpringCloud Gateway
客户端负载均衡 - Ribbon
服务熔断降级 - Sentinel
缓存系统 - redis
分布式事务 - Seata
远程调用 - openFeign
对象存储 - 阿里云对象存储 OSS
全文检索 - Elasticsearch
消息队列 - RabbitMQ
链路追踪 - Zipkin + Sleuth
线上监控系统 - Prometheus
日志系统 - ELK
压力测试 - Jmeter
性能优化
人人快速开发平台
支付宝API
VMnet0:使用桥接网络
在Bridged模式下,虚拟机同主机是相同的地位
VMnet1:仅主机网络
虚拟机外部网络访问都通过物理主机「Host」
VMnet8:NAT网络
源地址转换网络,最简单的组网方式,NAT和Host-Only区别在于NAT模式会多一个NAT服务
-
虚拟机网络地址转换「NAT」 - 端口转发
需要从主机映射端口到虚拟机端口,这样每个服务软件都需要端口转发
windows 3306 --> vm ware linux 3306
-
虚拟机使用桥接网络「为虚拟机配置与主机同一网段的ip,这样通过ip即可访问虚拟机」
主机和虚拟机可互ping
我们需要将虚拟机设置为桥接网络
Docker容器化技术,基于镜像,可以秒级启动容器,每个容器都是一个完整的运行环境,容器之间互相隔离
hub.docker.com
安装docker-ce
docs.docker.com
# 卸载
$ sudo yum remove docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-engine
# 安装依赖
$ sudo yum install -y yum-utils
# docker安装地址配置
$ sudo yum-config-manager \
--add-repo \
https://download.docker.com/linux/centos/docker-ce.repo
# 安装ce
$ sudo yum install docker-ce docker-ce-cli containerd.io
# 启动
$ sudo systemctl start docker
# 运行hello-world
$ sudo docker run hello-world
# 开启自启动
$ sudo systemctl enable docker
# 配置镜像加速
# 登录阿里云,控制台,容器镜像服务
sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": ["https://r6gnoa7o.mirror.aliyuncs.com"]
}
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker
docker安装所需中间件
docker pull mysql:8.0.23
# whereis mysql
# 端口映射与文件挂在启动
sudo docker run -p 3306:3306 --name mysql\
-v /mall/mysql/log:/var/log/mysql\
-v /mall/mysql/data:/var/lib/mysql\
-v /mall/mysql/conf:/etc/mysql\
-e MYSQL_ROOT_PASSWORD=123456\
-d mysql:8.0.23
# -p 端口映射 宿主机端口/容器端口
# -v 目录挂载 宿主机目录/容器目录
# -e MYSQL_ROOT_PASSWORD=123456,初始化root密码
docker exec -it mysql /bin/bash
docker update mysql --restart=always
# 查看启动的容器
docker ps
# 在挂载目录修改my.cnf
docker restart mysql
docker pull redis
# 在安装docker的主机创建目录
mkdir -p /mall/redis/conf
touch /mall/redis/conf/redis.conf
# 配置端口映射与文件挂在
docker run -p 6379:6379 --name redis\
-v /mall/redis/data:/data\
-v /mall/redis/conf/redis.conf:/etc/redis/redis.conf\
-d redis redis-server /etc/redis/redis.conf # 以/etc/redis/redis.conf启动redis-server
# 开启redis持久化 vim /mall/redis/conf/redis.conf
# rdb / aof
appendonly yes
# docker版redis-cli
docker exec -it redis redis-cli
docker update mysql --restart=always
使用medis连接redis测试
memory info
cpu info
...
nginx.conf
http {
include mime.types;
default_type application/octet-stream;
# zero-copy
sendfile on;
#tcp_nopush on;
keepalive_timeout 65;
#gzip on;
# http include 20210418
include D:/yangzl/sgg-mall/nginx-1.19.6/conf/mall.conf;
}
mall.conf
# 配置网关服务器「nginx 统一访问网关」
upstream mall {
server 192.168.0.104:88 weight=1;
}
server {
listen 80;
server_name mall.com;
# static 静态资源路由
location /static {
root html;
}
# dynamic 动态请求路由
location / {
# nginx 转发请求时会丢失部分请求首部「Cookie、Host」
proxy_set_header Host $host;
proxy_pass http://mall;
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
- 安装vscode
- 安装插件
# 生成ssh免密连接使用的公钥,以github用户名为salt
ssh-keygen -t rsa -C "775698011@qq.com"
# 将密钥完整复制到码云的SSH公钥 KEY中
cat ~/.ssh/id_rsa.pub
# 测试免密设置是否成功
ssh -T git@gitee.com
持续集成
流程:
Developer - Github - Docker - Kubernetes API - Jekins Pipeline - OP运维
-
父项目mall
依赖:
web-starter
openfeign
-
子项目「springboot」
包:com.yangzl.mall
.product
.order
.member
.cupon
.ware
-
gitignore配置
...
-
commit
-
push
https://github.com/Timesless/mall
- 不建立外键关联
# 5个业务库 + 1个后台系统库
CREATE DATABASE `mall_oms` CHARACTER SET 'utf8mb4' COLLATE 'utf8mb4_0900_ai_ci';
`mall_pms`
`mall_sms`
`mall_ums`
`mall_wms`
`mall_admin`
git clone git@gitee.com:renrenio/renren-generator.git
git clone git@gitee.com:renrenio/renren-fast.git
git clone git@gitee.com:renrenio/renren-fast-vue.git
# 删除.git
# 1. 运行renren-fast-vue
# 2.1 修改renren-generator代码生成配置文件(generator.properties)
# 2.2 修改 generator GenUtils.javva
# 3. 启动renren-generator
# 4. 逆向生成代码
QPS
TPS
90% 响应时间
吞吐量
错误率
- regedit 打开注册表
- HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters下
- 右键 parameter,添加一个 DWORD,名为 MaxUserPort
- MaxUserPort 选择十进制输入数值 65534
- 重启电脑
业务实际流程:
- 访问 nginx「动静请求分离」
- nginx 统一访问 gateway
- gateway 路由到具体服务
程序 | 90%响应时间 | 99% 响应时间 | 吞吐量 |
---|---|---|---|
nginx | |||
gateway | 8ms | 20ms | 9122/s |
简单服务 | 4 | 30 | 16885/s |
gateway + 简单服务 | 26 | 70 | 4420/s |
全链路简单服务「nginx + gateway + hello」 | 34 | 46 | 1946/s |
一级菜单渲染「db + thymeleaf渲染」 | 150 | 354 | 747/s |
一级菜单渲染「打开tymeleaf 缓存,关闭debug日志」 | 46 | 158 | 1412/s |
三级分类数据「多次 db」 | 2687 | 6982 | 51/s |
三级分类数据「一次 db,内存计算,关闭 debug 日志」 | 530 | 1625 | 244/s |
三级分类数据「添加 redis 缓存」 | 80 | 193 | 1130/s |
首页全量数据「三级分类 + 静态资源」 + nginx 动静态请求分离 |
CPU:i5-8300H
RAM:24G
gateway:-Xms256m -Xmx256m
product服务:-Xms1024m -Xmx1024m
每个压力测试都以 50 线程,循环压测 30 s
nginx:127.0.0.1
gateway:127.0.0.1:88
简单服务:127.0.0.1:7076/hello
网关 + 简单服务:127.0.0.1:88/hello
全链路简单服务:mall.com/hello
一级菜单渲染「一次 db + thymeleaf 页面渲染」:http://127.0.0.1:7076/index
gateway + 一级菜单渲染:
全链路一级菜单渲染:
三级分类「多次 db 」:http://127.0.0.1:7076/index/catalog
gateway + 三级分类:
全链路三级分类:
首页全量数据获取「多次 db + 静态资源(css,img等)」:jmeter 高级设置 —>下载所有资源
- 数据库
- 应用程序
- 中间件「tomcat、nginx」
- 网络和操作系统
data = cache.load();
if (null == data) {
data = db.load();
cache.put(key, data);
}
- 无法设置淘汰策略
- 无法应用于分布式架构
/**
* TODO: 压测时 Redis Lettuce 客户端导致直接内存溢出:OutOfDirectMemoryError
* 实际原因:Lettuce 使用 Netty 做网络通信,是 Lettuce 操作 Netty 代码 bug
* -XX:MaxDirectMemorySize 默认等于 -Xmx
*
* 解决方案:
* 1. 使用 Jedis
* 2. 升级 Lettuce
*
* @return map
*/
访问一个高频热点已失效的 key
- db 查询添加互斥锁
- bloom flter
查询一个一定不存在的数据
- 空值缓存一小段时间
- db 查询加互斥锁
- bloom filter
同一时刻,大面积缓存过期。
- db 查询添加互斥锁
- 过期时间随机
/**
* 分布式缓存一致性保障:
* 1. 双写「修改数据后,将新数据写入缓存」
* node1 先修改数据后,写入缓存卡顿,耗时 2s
* node2 后修改数据后,写入缓存耗时 1s,则此时缓存数据依然为 node1 先写入的数据「脏数据」
* 2. 失效「修改数据后,将缓存删除」
* node1 先修改数据后,删除缓存
* node2 后修改数据,卡顿 3s 后删除缓存
* node3 读取数据,在 3s 之间,此时读到 node1 修改的数据,再放入缓存「脏数据」
*
* 2.1 延时双删「修改数据后,将缓存删除,延时一段时间后再删除」
*/
修改数据后,将新数据写入缓存
考虑以下例程
node1 先修改数据后,写入缓存卡顿,耗时 2s
node2 后修改数据后,写入缓存耗时 1s,则此时缓存数据依然为 node1 先写入的数据「脏数据」
修改数据后,将缓存删除
node1 先修改数据后,删除缓存,耗时 1s
node2 后修改数据,卡顿 3s 删除缓存
node3 在 1 ~ 3s 之间读取数据,此时读到 node1 修改的数据,3s 后再放入缓存「在node2 删除之后放入缓存,脏数据」
==失效模式前提下,延时再删除一次缓存==
- 修改请求添加写锁 / 分布式写锁「
Redisson.getReadWriteLock(key).writeLock()
」 - 读取请求添加读锁 / 分布式读锁「
Redisson.getReadWriteLock(key).readLock()
」
- 用户维度数据,无并发,缓存添加过期时间即可
- 菜单,商品介绍等基础数据。使用 Canal 订阅 binglog
- 缓存 + 过期能解决大部分业务的缓存需求
- 读写锁
- 频繁修改的数据不应该添加缓存,读库
- 不应该过度设计,增加系统复杂性
阿里开源产品,可以做数据异构「推荐功能」
统一缓存抽象
提供声明式缓存应用
-
导入依赖 org.springframework.boot:spring-boot-starter-cache
-
Interface CacheManager
- RedisCacheManager
- ConcurrentMapCacheManager
-
@EnableCaching
-
yaml 添加配置
spring.cache.type: redis
-
配置 value 序列化
@Bean
public RedisCacheConfiguration redisCacheConfiguration() {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()))
.entryTtl(Duration.ofMinutes(30));
return config;
}
@Cachable
@CacheEvict
@Caching:组合
@CachePut
@CacheConfig:类级别配置共享
Spring Session解决的问题:
- 子域父域session共享
- 分布式session后端统一存储
- redis
- jdbc
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
dependencyManagement {
imports {
mavenBom 'org.springframework.session:spring-session-bom:Corn-SR2'
}
}
dependencies {
compile 'org.springframework.session:spring-session-data-redis'
}
spring.session.store-type=redis # Session store type.
server.servlet.session.timeout= # Session timeout. If a duration suffix is not specified, seconds is used.
spring.session.redis.flush-mode=on_save
spring.session.redis.namespace=spring:session
# redis 配置
spring.redis.host=localhost
spring.redis.password=
spring.redis.port=6379
@EnableRedisHttpSession
public class Client {
@Resource
private StringRedisTemplate redisTemplate;
}
不同系统域名不同,让三个系统同步一个用户的票据:
- 认证服务器:ssoserver.com
- 所有系统登陆取ssoserver,登陆成功跳转回来
- 其中一个系统登陆其它系统不用的公路
- 全局统一一个sso.sessionID