Redis学习笔记3 Java + Redis模拟秒杀场景
来源:互联网 发布:淘宝拆车变速箱 编辑:程序博客网 时间:2024/05/18 02:18
秒杀场景中,客户端对服务器的访问可以抽象为两个:访问静态页面(列出静态商品页面),访问后台接口(抢购)
- 静态页面可以使用DNS实现,压力不大;
- 后台接口是重点要解决的问题。
- 一定要快
- 不要直接访问传统数据库,太慢。建议使用内存数据库技术,本例使用Redis进行示例
- 防止同一账号短时间内的多次请求
- 防止超发(即本来只有100件商品,却最终成交了101件)
- 悲观锁:即实际对某个商品的购买api,同时只允许一个用户访问,“查询该商品数量”、“商品数量减1”是在同一个事务中,保证数据的完整性。缺点是性能,通常无法满足抢购的场景。
- FIFO: 客户端的抢购指令,只是插入一个交易表,由另外一个统一的线程来处理交易表,标记交易的成功或失败(如商品已售完)。缺点是客户端无法立即得到反馈,需要等待统一的线程处理完自己的交易后才知道抢购是否成功。不知道是否有公司采用此种方案实现抢购场景,个人感觉还是可行的。
- 乐观锁:即每个抢购指令前:step 1. 首先做个特殊标记; step 2. 然后正常执行指令; step 3. 在指令提交时,根据标记判断step 1至step 3之间商品数据是否有变化,如果有,则失败;否则,则抢购成功。
以下采用Java + Redis模拟乐观锁的实现。
- Redis服务器需要事先搭建好,作者将具体ip mask掉
- 本例采用异步方式记录交易log表,之所以要插入此log表,是为了方便统计最终商品交易的成功数、失败数。不是必须的。可以注释掉这些代码。(当然实际业务中应该会记录类似的表)
1. MyJedisPool.java // Redis客户端pool的实现
package com.cloudboy.redis;import redis.clients.jedis.Jedis;import redis.clients.jedis.JedisPool;import redis.clients.jedis.JedisPoolConfig;public class MyJedisPool {private static JedisPool pool;static {JedisPoolConfig config = new JedisPoolConfig();// 设置的逐出策略类名, 默认DefaultEvictionPolicy(当连接超过最大空闲时间,或连接数超过最大空闲连接数)config.setEvictionPolicyClassName("org.apache.commons.pool2.impl.DefaultEvictionPolicy");// 最大连接数config.setMaxTotal(8);// 最大空闲连接数config.setMaxIdle(8);// 获取连接时的最大等待毫秒数(如果设置为阻塞时BlockWhenExhausted),如果超时就抛异常, 小于零:阻塞不确定的时间,// 默认-1config.setMaxWaitMillis(-1);// 是否启用后进先出,默认trueconfig.setLifo(true);// 最小空闲连接数, 默认0config.setMinIdle(0);// 每次逐出检查时 逐出的最大数目 如果为负数就是 : 1/abs(n), 默认3config.setNumTestsPerEvictionRun(3);// 对象空闲多久后逐出, 当空闲时间>该值 且 空闲连接>最大空闲数// 时直接逐出,不再根据MinEvictableIdleTimeMillis判断 (默认逐出策略)config.setSoftMinEvictableIdleTimeMillis(1800000);// 在获取连接的时候检查有效性, 默认falseconfig.setTestOnBorrow(false);// 在空闲时检查有效性, 默认falseconfig.setTestWhileIdle(false);// 逐出扫描的时间间隔(毫秒) 如果为负数,则不运行逐出线程, 默认-1config.setTimeBetweenEvictionRunsMillis(-1);pool = new JedisPool(config, "????????");}public static Jedis getJedis() {return pool.getResource();}/** 归还jedis对象 */public static void recycleJedisOjbect(Jedis jedis) {jedis.close();}}
2. FlashSaleTest.java // 抢购模拟
package com.cloudboy.redis.flashSale;import java.util.List;import com.cloudboy.redis.MyJedisPool;import redis.clients.jedis.Jedis;import redis.clients.jedis.Transaction;public class FlashSaleTest {private static String KEY = "COUNT";private int userCount;private int interval;/** * @param totalItemCount 商品总数 * @param userCount 模拟用户数 * @param interval 用户采购间隔(毫秒) */public FlashSaleTest(int totalItemCount, int userCount, int interval) {this.userCount = userCount;this.interval = interval;Jedis jedis = MyJedisPool.getJedis();jedis.set(KEY, "" + totalItemCount);MyJedisPool.recycleJedisOjbect(jedis);}public void start() {for(int i=0; i<userCount; i++) {Thread tt = new UserThread("Thread" + i); tt.start(); try {Thread.sleep(interval);} catch (InterruptedException e) {e.printStackTrace();}}}private static int buy() {Jedis jedis = MyJedisPool.getJedis();jedis.watch(KEY);int value = Integer.valueOf(jedis.get(KEY)).intValue();int result;if(value > 0) {Transaction tx = jedis.multi();tx.decr(KEY);List<Object> res = tx.exec();if(res.size() == 0) {result = 1; // 失败} else {result = 0; // 成功}} else {result = 2; // 已售完}MyJedisPool.recycleJedisOjbect(jedis);return result;}static class UserThread extends Thread {private String user = null; public UserThread(String user) { this.user = user; } public void run() { int result = buy(); Trade trade = new Trade(); trade.setUser(this.user); trade.setResult(result); LogManager.addLog(trade); System.out.println("user(" + user + ") result(" + result + ")"); } }public static void main(String[] args) {FlashSaleTest test = new FlashSaleTest(100, 200, 100);test.start();}}
3. Trade.java 交易记录数据模型
package com.cloudboy.redis.flashSale;public class Trade {private String user;private int result;public int getResult() {return result;}public void setResult(int result) {this.result = result;}public String getUser() {return user;}public void setUser(String user) {this.user = user;}}
4. LogManager.java 异步记录交易Log的服务
package com.cloudboy.redis.flashSale;import java.sql.Connection;import java.sql.DriverManager;import java.sql.PreparedStatement;import java.sql.SQLException;import java.util.concurrent.LinkedBlockingQueue;public class LogManager implements Runnable {private static LinkedBlockingQueue<Trade> list = new LinkedBlockingQueue<Trade>();private static String url="jdbc:mysql://......../feitu?useUnicode=true&characterEncoding=UTF-8";private static Connection conn;static {try {Class.forName("com.mysql.cj.jdbc.Driver");conn = DriverManager.getConnection(url, "user","password");} catch (ClassNotFoundException | SQLException e) {e.printStackTrace();}new Thread(new LogManager()).start();}public static void addLog(Trade log) {list.add(log);}@Overridepublic void run() {while(true) {Trade trade = null;try {trade = list.take();log(trade);} catch (InterruptedException e) {e.printStackTrace();}}}private void log(Trade trade) {String sql = "insert into t_buy (user, result) values(?, ?)";try {PreparedStatement pst = conn.prepareStatement(sql);pst.setString(1, trade.getUser());pst.setInt(2, trade.getResult());pst.execute();} catch(SQLException e) {e.printStackTrace();}}}
5. 本文用到的库
compile group:'redis.clients', name:'jedis', version:'2.9.0'compile group:'com.thoughtworks.xstream', name:'xstream', version:'1.4.7'compile group:'xmlpull', name:'xmlpull', version:'1.1.3.4d_b4_min'compile group: 'mysql', name: 'mysql-connector-java', version: '6.0.5'compile group: 'commons-pool', name: 'commons-pool', version: '1.6'
6. 运行结果
select result, count(*) from t_buy group by result;
resultcount(*)010019291可以看到:
- 最终成功100件,和商品总数一致。所有商品被抢购完了,且没有发生“超发”
- 抢购中失败9次,即抢购提交中,数据已经被其它线程更改,因此失败
- 其它91次失败,是商品已经售罄
阅读全文
1 0
- Redis学习笔记3 Java + Redis模拟秒杀场景
- Redis学习笔记(一)应用场景
- Redis学习笔记-Java连接Redis
- [Redis学习笔记]-Java 使用 Redis
- redis--3--Redis应用场景
- redis秒杀
- Redis 秒杀
- php redis秒杀
- redis秒杀
- redis秒杀案例
- 2 秒杀系统模拟基础实现,使用Redis实现
- redis spring 事务控制(模拟秒杀)
- .NetCore+Jexus代理+Redis模拟秒杀商品活动
- Redis学习笔记3--Redis键值设计
- Redis学习笔记3--Redis键值设计
- redis学习笔记3(redis.conf)
- Redis学习笔记3--Redis键值设计
- SB集成Redis学习笔记之实际应用场景-java干货
- Linux下TFTP服务的安装、配置和操作
- 豪杰信息杯I 湘潭oj1268-Strange Optimization
- 博客系统开发推送第四季----网站框架的搭建及博客模块功能的完善
- SonicOperator之傅里叶变换3
- webpack之loader
- Redis学习笔记3 Java + Redis模拟秒杀场景
- 如何去掉SugarCRM详细页的InsideView模块
- OpenGL蓝宝书源码学习(一)第二章——Triangle.cpp
- php 微信红包 发送方法【干货】
- 必知的 15 个jQuery小技巧(干货)
- CAS_SHIRO_SESSION使用Redis共享的方式与结果
- 用户实现简单的printf函数,itoa,itox,并且用S5PV210进行实验
- SonicOperator之傅里叶变换4
- Python的chardet