redis 乐观锁实践秒杀
p4cf
9年前
需求:有一个标(理解成抢红包也行,accountBalance预赋值1000元),大家可以抢购,每个用户抢购成功后,更新最后标的总数,在并发情况下,使用redis的乐观锁,保证更新标总值正确性,先往redis放一个标的金额:
set accountBalance "1000"
实现方式如下:
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 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>mybatisPage</groupId> <artifactId>page</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <name>PageHelperSample</name> <url>http://git.oschina.net/free/Mybatis-Sample</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <!-- jstl --> <dependency> <groupId>jstl</groupId> <artifactId>jstl</artifactId> <version>1.1.2</version> </dependency> <dependency> <groupId>taglibs</groupId> <artifactId>standard</artifactId> <version>1.1.2</version> </dependency> <!-- jstl --> <dependency> <groupId>commons-pool</groupId> <artifactId>commons-pool</artifactId> <version>1.6</version> </dependency> <!-- log mybatis sql --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.5</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.5</version> </dependency> <!-- log mybatis sql --> <!-- fastjson --> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.5</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.4</version> </dependency> <!-- web --> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.5</version> <scope>provided</scope> </dependency> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>jsp-api</artifactId> <version>2.1</version> <scope>provided</scope> </dependency> <dependency> <groupId>taglibs</groupId> <artifactId>standard</artifactId> <version>1.1.2</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>3.7.4</version> </dependency> <dependency> <groupId>com.github.jsqlparser</groupId> <artifactId>jsqlparser</artifactId> <version>0.9.1</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.2.5</version> </dependency> <!-- util --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.1</version> </dependency> <!-- mysql --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.35</version> </dependency> <!-- redis --> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.1.0</version> <type>jar</type> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.6</source> <target>1.6</target> <encoding>utf-8</encoding> </configuration> </plugin> <plugin> <groupId>org.mortbay.jetty</groupId> <artifactId>jetty-maven-plugin</artifactId> <version>8.0.0.M3</version> </plugin> </plugins> </build> </project>
web.xml
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0"> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> <servlet> <servlet-name>bid</servlet-name> <servlet-class>com.heli.mybatis.page.servlet.ReidsMatchServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>bid</servlet-name> <url-pattern>/bid</url-pattern> </servlet-mapping> <servlet> <servlet-name>list</servlet-name> <servlet-class>com.heli.mybatis.page.servlet.ReidsMatchListServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>list</servlet-name> <url-pattern>/list</url-pattern> </servlet-mapping> </web-app>
servlet
package com.heli.mybatis.page.servlet; import java.io.IOException; import java.util.List; import java.util.Random; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang3.StringUtils; import com.commnon.RedisAPI; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import redis.clients.jedis.Transaction; public class ReidsMatchServlet extends HttpServlet { public static JedisPool pool = RedisAPI.getPool(); // RedisAPI.set("accountBalance", "999999999");// 标还剩999999999块钱 private static final long serialVersionUID = 1L; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { Jedis jedis = pool.getResource(); long start = System.currentTimeMillis(); int flag = 0; try { flag = bid(request, response, jedis); } catch (Exception e) { e.printStackTrace(); response.getWriter().write("fail buy"); } finally { pool.returnBrokenResource(jedis); RedisAPI.returnResource(pool, jedis); } if (flag == 1) { response.getWriter().write("success buy"); } else if (flag == 2) { response.getWriter().write("have buy"); } else if (flag == 0) { response.getWriter().write("bid is zero ,you can not buy"); }else{ response.getWriter().write("fail buy"); } long end = System.currentTimeMillis(); System.out.println("--------------------------------------------请求耗时:" + (end - start) + "毫秒"); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } private int bid(HttpServletRequest request, HttpServletResponse response, Jedis jedis) throws Exception { int flag = 0;// 1,成功,2已经购买,3已经没钱了,其他異常 // 每个请求对应一个userId int userId = new Random().nextInt(999999); // 判断是否购买过 Boolean isBuy = RedisAPI.sismember("userIdSet", userId + ""); if (isBuy) { flag = 2; return flag; } // 观察 总标值,每人抢购一元 while ("OK".equals(jedis.watch("accountBalance"))) { int r = 1;// new Random().nextInt(2); int lastAccount = 0; String balance = RedisAPI.get("accountBalance"); if (StringUtils.isNotBlank(balance)) { lastAccount = Integer.valueOf(balance) - r; } if (lastAccount < 0) { flag = 3; break; } Transaction tx = jedis.multi(); tx.set("accountBalance", lastAccount + ""); List<Object> result = tx.exec(); if (result == null || result.isEmpty()) { jedis.unwatch(); } else { System.out.println("恭喜您," + userId + "已经中标" + r + "元,标余额" + lastAccount + "元"); RedisAPI.set(Thread.currentThread().getName(), r + ""); RedisAPI.sadd("userIdSet", userId + ""); flag = 1; break; } } return flag; } }
package com.heli.mybatis.page.servlet; import java.io.IOException; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Set; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.commnon.RedisAPI; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; public class ReidsMatchListServlet extends HttpServlet { public static JedisPool pool= RedisAPI.getPool();; public static Jedis jedis; static { jedis = pool.getResource(); } private static final long serialVersionUID = 1L; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { list(request, response); try { response.sendRedirect("list.jsp"); } catch (IOException e) { e.printStackTrace(); } } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } private void list(HttpServletRequest request, HttpServletResponse response) { Set set = jedis.smembers("userIdSet"); Iterator ite = set.iterator(); System.out.println("中标名单-------------------------"); int i = 0; Map<String, String> map = new HashMap<String, String>(); while (ite.hasNext()) { i++; Object obj1 = ite.next(); System.out.println("第" + i + "名:" + obj1); map.put("第" + i + "名:", obj1 + ""); } request.getSession().setAttribute("user", map); System.out.println("中标名单-------------------------"); } }
工具类
package com.commnon; import java.util.ResourceBundle; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPoolConfig; /** * Redis操作接口 * * @author 林计钦 * @version 1.0 2013-6-14 上午08:54:14 */ public class RedisAPI { private static JedisPool pool = null; private static ThreadLocal<JedisPool> poolThreadLocal = new ThreadLocal<JedisPool>(); /** * 构建redis连接池 * * @param ip * @param port * @return JedisPool */ public static JedisPool getPool() { if (pool == null) { ResourceBundle bundle = ResourceBundle.getBundle("redis"); if (bundle == null) { throw new IllegalArgumentException( "[redis.properties] is not found!"); } JedisPoolConfig config = new JedisPoolConfig(); config.setMaxActive(Integer.valueOf(bundle .getString("redis.pool.maxActive"))); config.setMaxIdle(Integer.valueOf(bundle .getString("redis.pool.maxIdle"))); config.setMaxWait(Long.valueOf(bundle.getString("redis.pool.maxWait"))); config.setTestOnBorrow(Boolean.valueOf(bundle .getString("redis.pool.testOnBorrow"))); config.setTestOnReturn(Boolean.valueOf(bundle .getString("redis.pool.testOnReturn"))); pool = new JedisPool(config, bundle.getString("redis.ip"), Integer.valueOf(bundle.getString("redis.port"))); } return pool; } public static JedisPool getConnection() { // ②如果poolThreadLocal没有本线程对应的JedisPool创建一个新的JedisPool,将其保存到线程本地变量中。 if (poolThreadLocal.get() == null) { pool = RedisAPI.getPool(); poolThreadLocal.set(pool); return pool; } else { return poolThreadLocal.get();// ③直接返回线程本地变量 } } /** * 返还到连接池 * * @param pool * @param redis */ public static void returnResource(JedisPool pool, Jedis redis) { if (redis != null) { pool.returnResource(redis); } } /** * 获取数据 * * @param key * @return */ public static String get(String key) { String value = null; JedisPool pool = null; Jedis jedis = null; try { pool = getPool(); jedis = pool.getResource(); value = jedis.get(key); } catch (Exception e) { e.printStackTrace(); } finally { // 释放redis对象 pool.returnBrokenResource(jedis); // 返还到连接池 returnResource(pool, jedis); } return value; } /** * 赋值数据 * * @param key * @return */ public static String set(String key, String value) { String result = null; JedisPool pool = null; Jedis jedis = null; try { pool = getPool(); jedis = pool.getResource(); result = jedis.set(key, value); } catch (Exception e) { e.printStackTrace(); } finally { // 释放redis对象 pool.returnBrokenResource(jedis); // 返还到连接池 returnResource(pool, jedis); } return result; } /** * 赋值数据 * * @param key * @return */ public static Long sadd(String key, String value) { Long result = null; JedisPool pool = null; Jedis jedis = null; try { pool = getPool(); jedis = pool.getResource(); result = jedis.sadd(key, value); } catch (Exception e) { e.printStackTrace(); } finally { // 释放redis对象 pool.returnBrokenResource(jedis); // 返还到连接池 returnResource(pool, jedis); } return result; } /** * 判断set中是否有值 * * @param key * @return */ public static Boolean sismember(String key, String member) { Boolean result = null; JedisPool pool = null; Jedis jedis = null; try { pool = getPool(); jedis = pool.getResource(); result = jedis.sismember(key, member); } catch (Exception e) { e.printStackTrace(); } finally { // 释放redis对象 pool.returnBrokenResource(jedis); // 返还到连接池 returnResource(pool, jedis); } return result; } }
redis.properties
#\u6700\u5927\u5206\u914d\u7684\u5bf9\u8c61\u6570 redis.pool.maxActive=1024 #\u6700\u5927\u80fd\u591f\u4fdd\u6301idel\u72b6\u6001\u7684\u5bf9\u8c61\u6570 redis.pool.maxIdle=200 #\u5f53\u6c60\u5185\u6ca1\u6709\u8fd4\u56de\u5bf9\u8c61\u65f6\uff0c\u6700\u5927\u7b49\u5f85\u65f6\u95f4 redis.pool.maxWait=1000 #\u5f53\u8c03\u7528borrow Object\u65b9\u6cd5\u65f6\uff0c\u662f\u5426\u8fdb\u884c\u6709\u6548\u6027\u68c0\u67e5 redis.pool.testOnBorrow=true #\u5f53\u8c03\u7528return Object\u65b9\u6cd5\u65f6\uff0c\u662f\u5426\u8fdb\u884c\u6709\u6548\u6027\u68c0\u67e5 redis.pool.testOnReturn=true #IP redis.ip=127.0.0.1 #Port redis.port=6379
bid.jsp
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> <title>抢标秒杀</title> <style type="text/css"> * { margin: 0; } html, body { height: 100%; } .wrapper { min-height: 100%; height: auto !important; height: 100%; margin: 0 auto -155px; } .footer, .push { height: 155px; } .middle { text-align: center; margin: 0 auto; width: 600px; height: auto; } </style> </head> <body> <form name="formBid" action="bid" method="get"> <div class="wrapper"> <div class="middle"> <h1 style="padding: 0px 0 10px;">秒标</h1> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <input type="submit" style="width: 600px; height: 200px" value="秒杀" /> <br> <br> <br> <br> <br> </div> </div> </form> <form name="formList" action="list" method="post"> <div class="wrapper"> <div class="middle"> <br> <br> <br> <br> <br> <input type="submit" style="width: 200px; height: 50px" value="查看秒杀结果" /> </div> </div> </form> </body> </html>
list.jsp
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> <title>抢标秒杀</title> <style type="text/css"> * { margin: 0; } html, body { height: 100%; } .wrapper { min-height: 100%; height: auto !important; height: 100%; margin: 0 auto -155px; } .footer, .push { height: 155px; } .middle { text-align: center; margin: 0 auto; width: 600px; height: auto; } </style> </head> <body> <% java.util.Map<String, String> mapBean = (java.util.Map<String, String>) request.getSession() .getAttribute("user"); %> <form name="formList" action="list" method="post"> <div class="wrapper"> <div class="middle"> <br> <br> <br> <br> <br> <br> <br> <br> <h1 style="padding: 0px 0 10px;"> 中奖名单<% if (mapBean != null) { %> <%=mapBean.size()%></h1> <% } %> <br> <% if (mapBean != null) { for (String key : mapBean.keySet()) { %> <%=key%>--<%=mapBean.get(key)%><br> <% } } %> </div> </div> </form> </body> </html>