redis 乐观锁实践秒杀

p4cf 8年前

需求:有一个标(理解成抢红包也行,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>