在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
开源软件名称:seckill开源软件地址:https://gitee.com/happydts/seckill开源软件介绍:秒杀系统一、学习目标
二、了解秒杀的业务及使用的技术架构2.1. 什么是秒杀【秒杀】一词在网络的最早起源,应该要追溯到日本的综合格斗技团体Pancrase在1993年9月21日发行的WEEKLY PRO-WRESTLING(每周职业摔跤)杂志中出现的自创词,在2000年发行的一款回合制网络游戏【石器时代】传入中国并被发扬光大。 【石器时代】在战斗时会出现【合击】现象,就算大家敏捷素质不同,在倒计时28秒时,同时选择攻击一个目标,也会极大概率发动攻击群殴一个目标。特别是在玩家的PK战,经常出现群体合击或者人宠合击,造成强大的杀伤,瞬间打飞或者打晕对手。 由于游戏战斗采用的是倒计时模式,强大的杀伤往往只在一秒没过就结束,所以这类瞬间或几下击败对手就被称作:【秒杀】 到后来演化渐变成通俗用语,甚至用来替代一些暴力词汇: “小心我秒你”;“昨天PK遇到高手,我被秒了”;“lj快走,不然秒你”;“终于120级,转生可以秒机暴啦”等等。 并在之后的【传奇】【MU】【CS】等各种经典游戏中广为流传。直至被商家促销活动所用。 所谓“秒杀”,是网上竞拍的一种新方式,就是网络卖家发布一些超低价格的商品,所有买家在同一时间网上抢购的一种销售方式。通俗一点讲就是网络商家为促销等目的组织的网上限时抢购活动。由于商品价格低廉,往往一上架就被抢购一空,有时只用一秒钟。2011年以来,在淘宝等大型购物网站中,“秒杀店”的发展可谓迅猛。 对于商家来说,按照商家的规模,秒杀分为三种形式: 1、平台要求准时准点做秒杀,类似于天猫双11,11月11日0点开始抢购,或者京东的整点抢购,都由平台发起。 2、商家对于自己的店铺做秒杀,一般是厂家的旗舰店,在平台首页占据有利广告位,进入店铺做秒杀。 3、微信公众号链接网页做秒杀,由公众号运营的商家发起。 按照商家的促销活动内容,秒杀分为三种方式: 1、限价秒杀:最常见的秒杀形式,秒杀价格绝对低到令人无法相信也无法抗拒而不去参与,此种秒杀一般在开始之后1-3秒之内就会秒杀完毕。 2、低价限量秒杀:此种形式也可以理解为低折扣秒杀,限量不限时,秒完即止,此种秒杀形式商家提供一定数量的商品,直至秒完即止。 3、低价限时限量秒杀:此种形式也可以理解为低折扣秒杀,限时限量,在规定的时间内,无论商品是否秒杀完毕,该场秒杀都会结束。 2.2. 秒杀的业务特点1、瞬时并发量大:大量用户会在同一时间抢购,网站流量瞬间激增。 2、库存少:一般都是低价限量,而访问的数量远远大于库存数量,只有极少数人成功。 3、业务流程简单:流程短,立即购买,下订单,减库存。 4、前期预热:对于还未开启活动的秒杀商品,以倒计时的方式显示,只能访问不能下单。 2.3. 设计思路1、限流:只能让秒杀成功的一小部分人进入到后台,和数据库进行交互,来减少数据库服务器的压力。 2、缓存:将部分业务逻辑写到缓存里,例如:商品限购数量、秒杀政策等。 3、异步:将业务逻辑拆分,减少服务器压力,例如:正常业务流程是下订单、付款、减库存同一时间完成,秒杀时可以将业务逻辑拆分。 4、预热:商家进行宣传,并提前设置好秒杀的商品、秒杀时间、限购数量,将设置的商品写入 redis 缓存。 5、展示:页面分为两层,第一层是商品列表页,第二层是商品详情页,通过商品列表页链接进入商品详情页,秒杀开始前,展示商品秒杀倒计时,不允许操作提交订单,只允许查看商品详情。秒杀开始时,展示商品秒杀到期时间。 6、提交订单:秒杀提交完订单将 redis 缓存里的数量减少,并提示支付。 7、队列操作:当支付成功之后,将秒杀成功详情写入 rabbitMQ,订单服务进行监听接收消息写入订单,库存服务进行监听接收消息减少库存。 8、时间服务器:页面服务端通过负载进行布署,各服务器时间可能会不一致,因此增加时间服务,来提供统一的时间。 2.4. 技术架构整体架构图: Eureka Client: 时间服务(leyouTimeServer,端口号8000):为页面服务提供时间统一的接口。 商品服务(leyouStock,端口号7000):对外提供的接口(商品列表、商品详情、秒杀政策)。 库存服务(leyouStorage,端口号6001):队列监听,在队列中提取消息与数据库交互减少库存。 会员服务(leyouUser,端口号5000):为页面服务提供会员数据接口,会员的添加、修改、登录。 订单服务(leyouOrder,端口号4000):队列监听,在队列中提取消息与数据库交互生成订单。 页面服务(leyouClient,端口号3000):为前端页面提供数据接口。 Eureka Server: 注册中心(leyouServer,端口号9000)各服务都在注册中心进行注册。 配置中心 (leyouConfig):提供所有服务需要的配置。 Redis的应用: 缓存商品数量、秒杀政策。 商家对秒杀政策、商品限量进行设置,设置完成写入Redis。 消费者访问商品详情,提交订单之后,从Redis中减少商品数量。 Redis里存取内容: 1、在政策新增的时候存入,key的值为:LIMIT_POLICY_{sku_id},value的值为政策内容 2、商品列表取数据时,通过key(LIMIT_POLICY_{sku_id}),取出政策内容。 3、政策到期之后,自动删除。 RabbitMQ的应用: 消费者提交订单,自动写入订单队列: 订单队列:订单服务监听订单队列,接收到消息之后将队列信息写入数据库订单表。 消费者付款之后,更新订单状态,更新成功之后写入库存队列 库存队列:库存服务监听库存队列,接收到消息之后将库存信息写入数据库减少库存。 2.5. 数据库结构三、秒杀环境搭建(了解)3.1. 安装redis及配置3.1.1. 安装redis3.1.2. 配置redis安装完毕后,需要先做一些设定工作,以便服务启动后能正常运行。使用文本编辑器,这里使用Notepad++,打开Redis服务配置文件。注意:不要找错了,通常为redis.windows-service.conf,而不是redis.windows.conf。后者是以非系统服务方式启动程序使用的配置文件。 找到含有requirepass字样的地方,追加一行,输入requirepass leyou。这是访问Redis时所需的密码,后面在项目中也需要设置。 测试一下Redis是否正常提供服务。进入Redis的目录,cd C:\Program Files\Redis。输入redis-cli并回车。(redis-cli是客户端程序)如图正常提示进入,并显示正确端口号,则表示服务已经启动。 输入 auth leyou,显示OK,则密码正确。 实际测试一下读写。输入set mykey "abd”并回车,用来保存一个键值。再输入get mykey,获取刚才保存的键值。 开启持久化,appendonly yes –默认为no。 持久化:将数据(如内存中的对象)保存到可永久保存的存储设备中。持久化的主要应用是将内存中的对象存储在数据库中,或者存储在磁盘文件中、 XML 数据文件中等等。 redis的数据都是缓存在内存中,当你重启系统或者关闭系统后,缓存在内存中的数据都会全部消失,再也找不回来了。所以为了让数据能够长期保存,就要将 Redis 放在缓存中的数据做持久化存储。 写入时机默认为everysec,每秒 也可以设置为always,实时写入,但是会有效率问题。 900秒有一个值存入,就持久化一次 300秒有10个值存入,就持久化一次 60秒有10000个值存入,就持久化一次 3.2. 安装RabbitMQ及配置3.2.1. 安装RabbitMQ客户端安装 otp_win64_20.2.exe 3.2.2. 安装RabbitMQ服务端安装 rabbitmq-server-3.7.4.exe 3.2.3. 配置RabbitMQ服务端配置RabbitMQ客户端环境变量 配置RabbitMQ服务端环境变量 在环境变量中增加RabbitMQ服务端 安装插件:rabbitmq-plugins.bat enable rabbitmq_management 重启RabbitMQ服务 启动RabbitMQ 四、秒杀系统创建首先看一下秒杀的目录结构 4.1. 创建Eureka注册中心(端口号9000)第一步:选择File-New-Module...,弹出的窗口中选择Spring initializr,选择Module SDK,选择Next 第二步:Group为com.itheima,Artifact为leyou,Name为leyouServer,Description为Server For leyou Project,选择Next 第三步:选择 Spring Cloud Discovery,选择 Eureka Server,选择Next 第四步:Module name 为 leyouServer,Content root为路径+leyouServer,选择Finish,此时在项目文件夹下会创建一个 leyouServer文件夹 4.1.1. 配置pom.xml在生成的项目中,打开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 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-parent</artifactId> <version>2.1.6.RELEASE</version> </parent> <groupId>com.itheima</groupId> <artifactId>leyou</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Greenwich.SR2</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement></project> 在右下角悬浮的窗口中点击 Import Changes,会自动到Maven远程仓库去下载所需要用到的jar包。 如果没有引入,可以根据下图,在IDEA右侧选择 Maven Projects,找到leyouServer选择 Dependencies右击,选择 Download Sources,也会去Maven远程仓库下载所需要用到的jar包,后面的所有项目都如此。 4.1.2. 配置文件application.properties在生成的项目中,打开src\man\resources\application.properties,配置端口号等 server.port=9000eureka.client.service-url.defaultZone=http://localhost:9000/eureka/eureka.instance.hostname=localhostspring.application.name=leyou-server#不从服务器拿服务信息eureka.client.fetch-registry=false#不在服务端注册eureka.client.register-with-eureka=false 注意:这里的端口号后一定不能加入空格等字符,否则会报错 4.1.3. 编写启动类在生成的项目中,打开src\main\java\com.itheima.leyou\leyouServerApplication,在已经生成的启动类中加入 @EnableEurekaServer,意思为启动Eureka服务端 @SpringBootApplication@EnableEurekaServerpublic class ServerApplication { public static void main(String[] args) { SpringApplication.run(ServerApplication.class, args); }} 测试运行注册中心,在浏览器中输入 http://localhost:9000 到这里,注册中心服务完成。 4.2. 创建时间服务(端口号8000)4.2.1. 创建时间服务第一步:选择File-New-Module...,弹出的窗口中选择Spring initializr,选择Module SDK,选择Next 第二步:Group为com.itheima,Artifact为leyou,Name为leyouTimeServer,Description为TimeServer For leyou Project,选择Next 第三步:选择 Spring Cloud Discovery,选择 Eureka Discovery Client,选择Next 第四步:Module name 为 leyouTimeServer,Content root为路径+leyouTimeServer,选择Finish,此时在项目文件夹下会创建一个 leyouTimeServer文件夹 在生成的项目中,打开pom.xml,配置依赖,其中spring-cloud-starter-netflix-eureka-client为项目引入了Eureka客户端的jar包,spring-boot-starter-web引入了web场景下,web模块开发所用到的jar包 <?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 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-parent</artifactId> <version>2.1.6.RELEASE</version> </parent> <groupId>com.itheima</groupId> <artifactId>leyou</artifactId> <version>1.0-SNAPSHOT</version> <name>leyouTimeServer</name> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Greenwich.SR2</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> </project> 在生成的项目中,打开src\man\resources\application.properties,配置端口号等 server.port=8000spring.application.name=leyou-time-servereureka.client.service-url.defaultZone=http://localhost:9000/eureka/ 在生成的项目中,打开src\main\java\com.itheima.leyou\leyouTimeServerApplication,在已经生成的启动类中加入 @EnableEurekaClient,意思为启动Eureka客户端 @SpringBootApplication@EnableEurekaClientpublic class TimeServerApplication { public static void main(String[] args) { SpringApplication.run(TimeServerApplication.class, args); }} 测试运行订单服务,在Eureka注册中心中可以看到 leyou-time-server服务,证明服务启动成功 4.2.2. 创建controller文件夹,并创建timeController.java文件给TimeController.java类增加注解 @RestControllerpublic class timeController {} 4.2.3. 创建时间查询方法(getTime)用途:给前端秒杀提供统一的时间标准,在TimeController里写入如下代码: @RequestMapping(value = "/getTime")public String getTime(){ SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); return simpleDateFormat.format(new Date());} 4.2.4. 测试时间查询直接通过页面访问 getTime 方法,然后得到当前时间,地址:http://localhost:8000/getTime 4.3. 创建商品服务(端口号7000)4.3.1. 创建商品表结构(略)4.3.2. 创建商品服务第一步:选择File-New-Module...,弹出的窗口中选择Spring initializr,选择Module SDK,选择Next 第二步:Group为com.itheima,Artifact为leyou,Name为leyouStock,Description为Stock For leyou Project,选择Next 第三步:选择 Spring Cloud Discovery,选择 Eureka Discovery Client,选择Next 第四步:Module name 为 leyouStock,Content root为路径+leyouStock,选择Finish,此时在项目文件夹下会创建一个 leyouStock文件夹 4.3.3. 配置pom.xml在生成的项目中,打开pom.xml,配置依赖,其中spring-cloud-starter-netflix-eureka-client为项目引入了Eureka客户端的jar包,spring-boot-starter-web引入了web场景下,web模块开发所用到的jar包(每个客户端必须要有这个依赖),利用alibaba的fastjson解析json数据,所以引入alibaba.fastjson用到的jar包,mysql-connector-java与spring-boot-starter-data-jpa依赖引入mysql连接使用的jar包 <?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 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-parent</artifactId> <version>2.1.6.RELEASE</version> </parent> <groupId>com.itheima</groupId> <artifactId>leyou</artifactId> <version>1.0-SNAPSHOT</version> <name>leyouStock</name> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.41</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Greenwich.SR2</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement></project> 4.3.4. 配置文件application.properties在生成的项目中,打开src\man\resources\application.properties,配置端口号等 server.port=7000spring.application.name=leyou-stockeureka.client.service-url.defaultZone=http://localhost:9000/eureka/spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driverspring.datasource.url=jdbc:mysql://localhost:3306/code1_2?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTCspring.datasource.username=rootspring.datasource.password=root#redis数据库编号,存在0~15共16个数据库spring.redis.database=0#redis服务器IPspring.redis.host=127.0.0.1#redis端口号spring.redis.port=6379#redis密码spring.redis.password=leyou#redis请求超时时间,超过此值redis自动断开连接spring.redis.timeout=10000ms#jedis最大连接数,超过此值则提示获取不到连接异常spring.redis.jedis.pool.max-active=32#jedis最大等待时间,超过此值会提示连接超时异常spring.redis.jedis.pool.max-wait=10000ms#jedis最大等待连接数spring.redis.jedis.pool.max-idle=32#jedis最小等待连接数spring.redis.jedis.pool.min-idle=0 4.3.5. 编写启动类在生成的项目中,打开src\main\java\com.itheima.leyou\leyouStockApplication,在已经生成的启动类中加入 @EnableEurekaClient,意思为启动Eureka客户端 @SpringBootApplication@EnableEurekaClientpublic class StockApplication { public static void main(String[] args) { SpringApplication.run(StockApplication.class, args); }} 注意:这里如果 @EnableEurekaClient报错,那就证明依赖文件引入有误,比如:在依赖spring-cloud-starter-netflix-eureka-client后加个1,如下图: 会导致: 注意:一般会犯错的地方是 starter 写成 start,不容易看出来,可以到maven projects里去查看是否有错误 测试运行商品服务,在Eureka注册中心中可以看到 leyou-Stock服务,证明服务启动成功 4.3.6. 创建controller\service\dao文件夹,并创建StockController.java\StockService.java\StockDao.java文件Controller:管理业务(Service)调度和管理跳转 Service:管理具体功能、分支判断 Dao:管理数据交互,完成增删改查 Controller像是服务员,顾客点什么菜,在几号桌。 Service像是厨师,前端请求过来的菜单上的菜都是他做。 Dao像是厨房小工,原材料都是他来打交道。 4.3.7. 创建商品列表查询方法(getStockList)用途:为前端页面服务提供商品列表数据,主要用于前端商品列表页展示。 先约定好和前端交互返回的数据结构: 返回的json字符串: 如果返回值错误:{"result":"false", "msg":"****"} 如果返回值正确:{"result":"true", "msg":"", "sku_list":["id":1,"sku_id":...]} 先从Dao的方法开始编写代码: 创建Dao层接口文件,IStockDao public interface IStockDao {} 给StockDao.java类增加注解,@Repository用于标注数据访问组件,即DAO组件,实现IStockDao接口 @Repositorypublic class StockDao implements IStockDao{} 声明 JDBCTemplate 方法,用于连接数据库使用 @Autowiredprivate JdbcTemplate jdbcTemplate; 增加getStockList方法,首先从数据库中将商品列表所需要的数据查询出来,装入一个ArrayList变量中,原因是商品列表有多行数据,是一个列表,然后将ArrayList返回,代码如下: //1、创建一个SQLString sql = "select id AS sku_id, title, images, stock, price, indexes, own_spec " + "from tb_sku";//2、执行这个SQLArrayList<Map<String, Object>> list = (ArrayList<Map<String, Object>>) jdbcTemplate.queryForList(sql);//3、返回数据return list; 再编写Service 创建Service层接口文件,IStockService public interface IStockService {} 给StockService.java增加注解,并实现IStockService接口 @Servicepublic class StockService implements IStockService{} 加入IStockDao接口的引用 @Autowiredprivate IStockDao iStockDao; 增加getStockList方法,调用StockDao中的getStockList,返回一个Map public Map<String, Object> getStockList(){ Map<String, Object> resultMap = new HashMap<String, Object>(); //1、取IstockDao的方法 ArrayList<Map<String, Object>> list = iStockDao.getStockList(); //2、如果没取出数据,返回错误信息 if (list==null||list.size()==0){ resultMap.put("result", false); resultMap.put("msg", "我们也不知道为啥没取出数据!"); return resultMap; } //3、从redis里取数据 resultMap = getLimitPolicy(list); //4、返回正常信息 resultMap.put("sku_list", list);// resultMap.put("result", true);//// resultMap.put("msg", ""); return resultMap; } 注意:这里的Service有业务逻辑判断,当Dao返回数据后,如果数据为空,需要加入业务逻辑判断,未找到相应的商品信息,如果不加该判断,前端页面获取的数据为空,那么页面上会空白并且不显示任何数据,会给页面操作者带来不好的体验感,疑惑:是不是网络中断?或者服务器宕机?所以这里返回“未找到相应的商品信息”的提示,有助于页面友好的显示。前端的程序员也可以通过result参数判断 通过alt+回车,选择Create method 'getStockList',在IStockDao接口文件中自动创建接口的方法。 在IStockDao接口文件中给方法加上public修饰符, 给StockController.java类增加注解 @RestControllerpublic class StockController {} 加入IStockService接口的引用 @Autowiredprivate IStockService iStockService; 增加 getStockList 方法,调用StockService中的getStockList,返回的是一个Map,对于页面来说是得到一个Json字符串 @RequestMapping(value = "/getStockList")public Map<String, Object> getStockList(){ return iStockService.getStockList();} 测试getStockList方法,在浏览器中输入 http://localhost:7000/getStockList 4.3.8. 创建商品查询方法(getStock)用途:为前端页面服务提供商品详情页数据,主要用于前端商品详情页展示。 在StockDao.java类中增加 getStock方法,带一个sku_id参数,意思是通过sku_id进行查找,返回一个商品Map,代码如下: //1、创建一个SQLString sql = "select tb_sku.spu_id, tb_sku.title, tb_sku.images, tb_sku.stock, tb_sku.price, tb_sku.indexes, " + "tb_sku.own_spec, tb_sku.enable, tb_sku.create_time, tb_sku.update_time,tb_spu_detail.description," + "tb_sku.id AS sku_id,tb_spu_detail.special_spec " + "from tb_sku " + "INNER JOIN tb_spu_detail ON tb_spu_detail.spu_id=tb_sku.spu_id " + "where tb_sku.id = ?";//2、执行这个SQL |
请发表评论