# bbmall **Repository Path**: LouisJiangjing/bbmall ## Basic Information - **Project Name**: bbmall - **Description**: 电商平台后端项目 - **Primary Language**: Java - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 1 - **Created**: 2022-10-21 - **Last Updated**: 2025-04-13 ## Categories & Tags **Categories**: Uncategorized **Tags**: 项目 ## README # 电商平台微服务后端 ## 简介 1. 重要模块 1. 图片的存储采用阿里云对象存储服务。上传文件:采用服务端签名后直传的方式,客户向我们的服务器发送请求,返回签名,再将携带签名的图片上传到阿里云 2. 商城前后端合并在一起,动静分离 1. 动:图片、css等(实际文件) 2. 静:向服务器发送的请求 3. 每个微服务都可以独立部署、运行、升级;技术、架构、业务独立自治 3. 使用不可逆加密密码。MD5进行密码加密存储!使用BCryptPasswordEncoder类 4. 使用Interceptor实现对购物车用户的判断以及传递给目标方法(controller中的方法)用户的信息 5. 使用ThreadLocal在同一个线程中共享数据 6. 订单的表单提交实现了接口的幂等性(使用令牌机制) 7. 使用RabbitMQ的延时队列实现定时功能,实现最终一致性的订单提交业务。相比SEATA可以解决高并发,系统异构等问题 8. 使用lua脚本实现订单提交令牌的原子性验证过程 9. 【重要】在提交订单中锁定库存失败时,进行事务回滚用到了分布式事务 10. 【重要】使用CompletableFuture.supplyAsync进行多线程异步编排,以提升效率已经减少请求延迟 11. 【重要】实现了商品秒杀功能。设置随机码,防止脚本抢秒杀;使用分布式信号量进行库存扣减,以减少去数据库操作,并且可以限流;使用分布式锁防止多台机器同时执行秒杀商品上架操作,导致重复上架;还保证秒杀是幂等操作,防止刷单 2. 第三方库 1. 采用人人开源的renren-fast生成基本的增删改查 2. 使用JSR303做数据校验,使用@ControllerAdvice统一处理提交的异常数据。最终采用自定义注解的分组校验 3. 使用OpenFeign进行服务之间的调用 4. 使用ElasticSearch进行分布式检索和分析【bbmall-search中的检索】,使用Kibana作为ElasticSearch的可视化界面,Java端使用elsticsearch-rest-high-level-client发送请求。使用nested数据类型:保证数据被es扁平化处理之后的检索不会出问题! 5. 每个微服务的前端页面采用thymeleaf进行开发 6. 使用nginx配置负载均衡 7. 使用Apache JMeter进行压力测试 8. 使用jvisualvm对内存进行监控 9. 使用Redisson实现分布式锁 10. 使用SpringCache实现缓存,使用失效模式保证缓存一致性 11. 使用RabbitMQ作为消息中间件 12. 使用SpringCloud Alibaba的SEATA实现分布式事务 13. 使用Sentinel对微服务进行限流/熔断/降级,保证服务的可靠性 14. 使用Sleuth+Zipkin进行链路追踪 ## 秒杀业务 瞬间高并发的特点,必须做限流+异步+缓存(页面静态化)+独立部署 限流方式 1. 前端限流。如验证码 2. nginx限流。如令牌算法,漏斗算法 3. 网关限流。限流的过滤器 4. 代码中使用分布式信号量 5. rabbitmq限流。能者多劳,保证发挥所有服务器的性能 秒杀功能【将秒杀商品上架作为定时任务】 1. 【完成】服务单一职责+独立部署。抽取为单一的微服务 2. 【完成】秒杀链接加密。使用随机码,防止脚本抢秒杀 3. 【完成】库存预热+快速扣减。将库存等信息放入redis中,使用信号量作为库存值,无需查询数据库 4. 动静分离。nginx做动静分离;使用CDN网络,分担集群压力 5. 【完成】拦截恶意请求。恶意脚本,在网关层可以拦截。采用登录用户才能进行秒杀的策略 6. 流量错峰。引入验证码机制或者将商品加入购物车 7. 限流+熔断+降级。前端+后段限流; 8. 【已完成】队列消峰。对于秒杀成功的订单,将其放入进入队列,慢慢处理这些订单 ## 启动程序 1. 启动Alibaba-Nacos 1. `cd /Applications/nacos/bin` 2. `sh startup.sh -m standalone` 3. 访问浏览器中的nacos界面[http://127.0.0.1:8848/nacos] 2. 【根据需求】启动分布式事务SEATA 1. `cd /Applications/seata-server-0.7.1/bin` 2. `bash seata-server.sh` 3. 【根据需求】启动sentinel 1. `cd /Applications/sentinel-dashboard-1.6.3` 2. `java -jar sentinel-dashboard-1.6.3.jar --server.port=8333` 4. 启动各个微服务 ## 性能优化 ### 优化思路 需要考虑应用属于CPU密集型还是IO密集型? 1. 数据库 2. 应用程序 3. 中间件【tomcat、nginx等】 4. 网络 5. 操作系统 ### 压测 压测表格 | 压测内容 | 压测线程数 | 吞吐量/s | 90%响应时间 | 99%响应时间 | | ------------------------------------------------------------ | ---------- | ---------------------------- | ----------- | ----------- | | nginx | 50 | 2335 | 11 | 944 | | gateway | 50 | 10367 | 8 | 31 | | 简单服务 | 50 | 11341 | 8 | 17 | | gateway+简单服务 | 50 | 3126 | 30 | 125 | | 全链路【nginx+gateway+简单服务】 | 50 | 800 | 88 | 310 | | 首页一级分类渲染 | 50 | 270【db、thymeleaf】 | 265 | 365 | | 首页一级分类渲染【打开缓存】 | 50 | 290 | 251 | 365 | | 首页一级分类渲染【打开缓存,加索引,关数据库log】 | 50 | 700 | 105 | 183 | | 首页三级分类渲染 | 50 | 2【db】 | - | - | | 首页三级分类渲染【打开缓存,加索引,关数据库log】 | 50 | 8【db】 | - | - | | 首页三级分类渲染【打开缓存,加索引,关数据库log,优化与数据库的交互】 | 50 | 111 | 571 | 896 | | 首页三级分类渲染【打开缓存,加索引,关数据库log,优化与数据库的交互,redis缓存】 | 50 | 411 | 153 | 217 | | | | | | | | 首页全量数据获取 | 50 | 7【db、thymeleaf、静态资源】 | - | - | | 首页全量数据获取【动静分离】 | 50 | 13【db、thymeleaf】 | - | - | 结论 1. 中间件越多,性能损失越大 2. 业务影响也较大 1. DB【mysql优化】 2. 模版引擎渲染速度【可以开启缓存】 3. 静态资源 ### 本项目的重要性能优化 1. 中间件 1. nginx 1. 动静分离。吞吐量提升约2倍 1. 将项目的静态资源放置在nginx中 2. 将/static/* 的所有请求直接由nginx返回,而不是转发给网关 2. GateWay 2. 打开thymeleaf缓存。吞吐量提升约7% 3. 调大新生代eden区,以免老生代经常进行full gc,而占用大量资源。 4. 业务逻辑 1. db加索引,关db的log。吞吐量提升约4倍 2. 优化与数据库交互次数。将【首页三级分类渲染】函数中,多次前往数据库查询的逻辑改为:一次性查到所有数据,再通过程序组装返回结果。吞吐量提升约15倍 5. 缓存 1. 将【首页三级分类渲染】的数据存入redis。吞吐量提升约4倍 2. 缓存null结果,防止缓存穿透 3. 在缓存key原有失效时间上加上随机值,防止缓存雪崩 4. 热点key加锁,防止缓存击穿。注意,不能使用synchronized(this){}或者在方法前加上synchronized:因为本地锁在分布式下将只能锁住本地的进程。因此如果要实现分布式系统的锁,需要使用分布式锁【lua脚本实现的redis分布式锁】 ## 遇到的问题 1. 使用Redis,进行高并发压测时出现netty的io.netty.util.internal.OutOfDirectMemory异常。 1. 原因:springboot2.0 之后默认使用lettuce作为操作Redis的客户端,它使用netty进行网络通信。netty有bug,导致堆外内存溢出。原因时设置了-Xmx300m,netty默认使用-Xmx300m作为堆外内存,高并发时超过内存!根本原因是没有进行内存释放。 2. 解决方案:不能仅仅使用-Dio.netty.maxDirectMemory调大堆外内存;方案1:升级lettuce客户端;方案2:使用jedis。 2. feign接口进行远程调用时,请求头丢失 1. 原因:feign远程调用会新创建一个request,会丢失请求头,也不携带cookie,认为没有登录; 2. 解决方法:需要加入feign远程调用的请求拦截器,在拦截器中拷贝请求头 3. feign接口进行远程【异步】调用时,请求头丢失 1. 原因:之前获取数据采用ThreadLocal,只能在同一个线程共享数据;开启异步调用后,不是同一个线程了! 2. 解决方法:在每个异步任务开启之前,重新设置RequestAttributes,共享给子线程 4. feign接口远程调用order接口时,报错说返回到了login.html 1. 原因:order接口中的拦截器要求登录之后才能访问接口。而其实服务之间调用是不需要登录的 2. 解决方法:对拦截器进行修改,匹配接口的调用是否为服务之间的调用 ## 数据库介绍 1. 采用mysql 2. 表与表之间没有外键:由于电商平台的数据量一般都较大,数据进行增删改的时候,都会对外键进行扫描,以保证数据的一致性、完整性 3. 五个数据库 4. bbmall_oms:订单 5. bbmall_pms:商品 6. bbmall_sms:销售 7. bbmall_ums:用户 8. bbmall_wms:库存 9. 采用renren-generator生成基本的增删改查代码