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

慕课网、乐字节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


<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.0modelVersion>
	
	<parent>
		<groupId>org.springframework.bootgroupId>
		<artifactId>spring-boot-starter-parentartifactId>
		<version>2.3.4.RELEASEversion>
		<relativePath/> 
	parent>

	<groupId>com.xxxxgroupId>
	<artifactId>seckill-demoartifactId>
	<version>0.0.1-SNAPSHOTversion>
	<name>seckill-demoname>
	<description>Demo project for Spring Bootdescription>

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

	<dependencies>
		
		<dependency>
			<groupId>org.springframework.bootgroupId>
			<artifactId>spring-boot-starter-thymeleafartifactId>
		dependency>
		
		<dependency>
			<groupId>org.springframework.bootgroupId>
			<artifactId>spring-boot-starter-webartifactId>
		dependency>
		
		<dependency>
			<groupId>com.baomidougroupId>
			<artifactId>mybatis-plus-boot-starterartifactId>
			<version>3.4.0version>
		dependency>
		
		<dependency>
			<groupId>mysqlgroupId>
			<artifactId>mysql-connector-javaartifactId>
			<scope>runtimescope>
		dependency>
		
		<dependency>
			<groupId>org.projectlombokgroupId>
			<artifactId>lombokartifactId>
			<optional>trueoptional>
		dependency>
		
		<dependency>
			<groupId>org.springframework.bootgroupId>
			<artifactId>spring-boot-starter-testartifactId>
			<scope>testscope>
			<exclusions>
				<exclusion>
					<groupId>org.junit.vintagegroupId>
					<artifactId>junit-vintage-engineartifactId>
				exclusion>
			exclusions>
		dependency>
		
		<dependency>
			<groupId>commons-codecgroupId>
			<artifactId>commons-codecartifactId>
		dependency>
		
		<dependency>
			<groupId>org.springframework.bootgroupId>
			<artifactId>spring-boot-starter-validationartifactId>
		dependency>
		
		<dependency>
			<groupId>org.springframework.bootgroupId>
			<artifactId>spring-boot-starter-data-redisartifactId>
		dependency>
		
		<dependency>
			<groupId>org.apache.commonsgroupId>
			<artifactId>commons-pool2artifactId>
		dependency>
		
		<dependency>
			<groupId>org.springframework.bootgroupId>
			<artifactId>spring-boot-starter-amqpartifactId>
		dependency>
		
		<dependency>
			<groupId>com.github.whvcsegroupId>
			<artifactId>easy-captchaartifactId>
			<version>1.6.2version>
		dependency>
	dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.bootgroupId>
				<artifactId>spring-boot-maven-pluginartifactId>
			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){<

你可能感兴趣的:(java,redis,数据库,mysql,spring,boot)