慕课网,乐字节 Java电商秒杀项目

本文详细介绍了基于Java的电商秒杀系统实现,涵盖技术点如Spring Boot、Redis、RabbitMQ等。从项目搭建、分布式会话到秒杀功能实现,再到系统优化和安全措施,探讨了并发读写、库存超卖等问题的解决方案,提出了使用Redis分布式锁和信号量优化秒杀流程的建议。
摘要由CSDN通过智能技术生成

慕课网、乐字节Java电商秒杀项目

技术点介绍:

  • 前端:Thymeleaf,Bootstrap,Jquerry
  • 后端:SpringBoot,MybatisPlus,Lombok
  • 中间件:RabbitMQ,Redis

秒杀方案:

  • 分布式会话:用户登录,共享session
  • 功能开发:商品列表,商品详情,秒杀,订单详情
  • 系统压测:JMeter入门,yace
  • 页面优化:缓存,静态化分离
  • 服务优化:RabbitMQ消息队列,接口优化,分布式锁
  • 安全优化:隐藏秒杀地址,算术验证码,接口限流。

秒杀介绍:

秒杀,对我们来说,都不是一个陌生的东西。每年的双11,618以及时下流行的直播等等。秒杀对于我们系统而言是一个巨大的考验。秒杀主要解决两个问题,一个是并发读,一个是并发写。并发读的核心优化理念是尽量减少用户到服务端来“读”数据,或者让他们读更少的数据;并发写的处理原则也一样,它要求我们在数据库层面独立出来一个库,做特殊的处理。

秒杀的整体架构可以概括为“稳、准、快”几个关键字。稳要保证秒杀活动顺利完成,即秒杀商品顺利地卖出去,这个是最基本的前提;“准”就是要求保证数据的一致性,秒杀几台东西就只能卖几台东西;“快”就是要求系统的性能要足够高。

从技术角度上看“稳、准、快”,就对应了我们架构上的高可用、一致性和高性能的要求。

  • 高性能。 秒杀涉及大量的并发读和并发写,因此支持高并发访问这点非常关键。对应的方案比如动静分离方案、热点的发现与隔离、请求的削峰与分层过滤、服务端的极致优化.
  • 一致性。 秒杀中商品减库存的实现方式同样关键。可想而知,有限数量的商品在同一时刻被很多倍的请求同时来减库存,减库存又分为“拍下减库存”“付款减库存”以及预扣等几种,在大并发更新的过程中都要保证数据的准确性,其难度可想而知
  • 高可用。 现实中总难免出现一些我们考虑不到的情况,所以要保证系统的高可用和正确性,我们还要设计一个 PlanB 来兜底,以便在最坏情况发生时仍然能够从容应对。

项目搭建

选择Spring Initializr, sdk选择1.8,java版本选择8,添加Lombok,Spring Web,Thymeleaf依赖。

添加依赖:pom.xml

<?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>
	<!--SpringBoot依赖-->
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.3.4.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>

	<groupId>com.xxxx</groupId>
	<artifactId>seckill-demo</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>seckill-demo</name>
	<description>Demo project for Spring Boot</description>

	<properties>
		<java.version>8</java.version>
	</properties>

	<dependencies>
		<!--thymeleaf 组件-->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-thymeleaf</artifactId>
		</dependency>
		<!--web 组件-->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<!--mybatisplus依赖-->
		<dependency>
			<groupId>com.baomidou</groupId>
			<artifactId>mybatis-plus-boot-starter</artifactId>
			<version>3.4.0</version>
		</dependency>
		<!--mysql-->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<scope>runtime</scope>
		</dependency>
		<!--lombok-->
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>
		<!--test组件-->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
			<exclusions>
				<exclusion>
					<groupId>org.junit.vintage</groupId>
					<artifactId>junit-vintage-engine</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
		<!-- md5 依赖 -->
		<dependency>
			<groupId>commons-codec</groupId>
			<artifactId>commons-codec</artifactId>
		</dependency>
		<!-- validation组件 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-validation</artifactId>
		</dependency>
		<!--spring data redis 依赖-->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-redis</artifactId>
		</dependency>
		<!--commons-pool2 对象池依赖-->
		<dependency>
			<groupId>org.apache.commons</groupId>
			<artifactId>commons-pool2</artifactId>
		</dependency>
		<!-- AMQP依赖 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-amqp</artifactId>
		</dependency>
		<!-- 验证码依赖 -->
		<dependency>
			<groupId>com.github.whvcse</groupId>
			<artifactId>easy-captcha</artifactId>
			<version>1.6.2</version>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

配置文件:application.yml

spring:
  #静态资源处理
  resources:
    #启动默认静态资源处理,默认启用
    add-mappings: true
    cache:
      cachecontrol:
        #缓存相应时间,单位秒
        max-age: 3600
    chain:
      #资源链启动缓存,默认启动
      cache: true
      #启用资源链,默认禁用
      enabled: true
      #启用压缩资源(gzip,brotli)解析,默认禁用
      compressed: true
      #启用h5应用缓存,默认禁用
      html-application-cache: true
    static-locations: classpath:/static/
  # thymelaef配置
  thymeleaf:
    # 关闭缓存
    cache: false
  # 数据源配置
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://192.168.56.10:3306/seckill?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
    username: root
    password: root
    hikari:
      #连接池名
      pool-name: DateHikariCP
      # 最小空闲连接出
      minimum-idle: 5
      # 空闲连接存活最大时间,默认600000(10分钟)
      idle-timeout: 600000
      #最大连接数,默认10
      maximum-pool-size: 10
      # 从连接池返回的连接自动提交
      auto-commit: true
      # 连接最大存活时间,0表示永久存活,默认1800000(30分钟)
      max-lifetime: 1800000
      # 连接超时时间,默认30000(30秒)
      connection-timeout: 30000
      # 测试连接是否可用的查询语句
      connection-test-query: SELECT 1

  # redis配置
  redis:
    #服务器地址
    host: 192.168.56.10
    #端口
    port: 6379
    #数据库
    database: 0
    #超时时间
    timeout: 10000ms
    #密码
    lettuce:
      pool:
        #最大连接数,默认8
        max-active: 8
        #最大连接阻塞等待时间,默认-1
        max-wait: 10000ms
        #最大空闲连接,默认8
        max-idle: 200
        #最小空闲连接,默认0
        min-idle: 5
  # RabbitMQ
  rabbitmq:
    # 服务器
    host: 192.168.56.10
    #用户名
    username: root
    #密码
    password: root
    # 虚拟主机
    virtual-host: /
    #端口
    port: 5672
    listener:
      simple:
        #消费者最小数量
        concurrency: 10
        #消费者最大数量
        max-concurrency: 10
        #限制消费者每次只处理一条消息,处理完再继续下一条消息
        prefetch: 1
        #启动时是否默认启动容器,默认true
        auto-startup: true
        #被拒绝时重新进入队列
        default-requeue-rejected: true
    template:
      retry:
        #发布重试,默认false
        enabled: true
        #重试时间,默认1000ms
        initial-interval: 1000ms
        #重试最大次数,默认3次
        max-attempts: 3
        #重试最大间隔时间,默认10000ms
        max-interval: 10000ms
        #重试的间隔乘数。比如配2.0,第一次就等10s,第二次就等20s,第三次就等40s
        multiplier: 1

#Mybatis-plus配置
mybatis-plus:
  # 配置Mapper.xml映射文件
  mapper-locations: classpath*:/mapper/*Mapper.xml
  # 配置MyBatis数据返回类型别名(默认别名是类名)
  type-aliases-package: com.xxxx.seckill.pojo

# MyBatis SQL打印(方法接口所在的包,不是Mapper.xml所在的包)
#logging:
#  level:
#    com.xxxx.seckill.mapper: debug

添加公共结果返回对象:

//公共返回对象枚举
public enum RespBeanEnum {
   
	//通用
	SUCCESS(200, "SUCCESS"),
	ERROR(500, "服务端异常"),
	//登录模块5002xx
	LOGIN_ERROR(500210, "用户名或密码不正确"),
	MOBILE_ERROR(500211, "手机号码格式不正确"),
	BIND_ERROR(500212, "参数校验异常"),
	MOBILE_NOT_EXIST(500213, "手机号码不存在"),
	PASSWORD_UPDATE_FAIL(500214, "密码更新失败"),
	SESSION_ERROR(500215, "用户不存在"),
	//秒杀模块5005xx
	EMPTY_STOCK(500500, "库存不足"),
	REPEATE_ERROR(500501, "该商品每人限购一件"),
	REQUEST_ILLEGAL(500502, "请求非法,请重新尝试"),
	ERROR_CAPTCHA(500503, "验证码错误,请重新输入"),
	ACCESS_LIMIT_REAHCED(500504, "访问过于频繁,请稍后再试"),
	//订单模块5003xx
	ORDER_NOT_EXIST(500300, "订单信息不存在"),
	;
	private final Integer code;
	private final String message;
}
//公共返回对象
public class RespBean {
   
	private long code;
	private String message;
	private Object obj;
	/**
	 * 功能描述: 成功返回结果
	 */
	public static RespBean success(){
   
		return new RespBean(RespBeanEnum.SUCCESS.getCode(),RespBeanEnum.SUCCESS.getMessage(),null);
	}
	/**
	 * 功能描述: 成功返回结果
	 */
	public static RespBean success(Object obj){
   
		return new RespBean(RespBeanEnum.SUCCESS.getCode(),RespBean.success().getMessage(),obj);
	}
	/**
	 * 功能描述: 失败返回结果
	 */
	public static RespBean error(RespBeanEnum respBeanEnum){
   
		return new RespBean(respBeanEnum.getCode(),respBeanEnum.getMessage(),null);
	}
	
	/**
	 * 功能描述: 失败返回结果
	 */
	public static RespBean error(RespBeanEnum respBeanEnum,Object obj){
   
		return new RespBean(respBeanEnum.getCode(),respBeanEnum.getMessage(),obj);
	}

}

分布式会话

登录功能:两次MD5加密

  • 客户端 → 服务端:MD5(用户输入 + salt)
  • 服务端 → 数据库:MD5(服务端结果 + salt)
public class MD5Util {
   
	public static String md5(String src){
   
		return DigestUtils.md5Hex(src);
	}
	private static final String salt="1a2b3c4d";
	public static String inputPassToFromPass(String inputPass)
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值