redis 樂觀鎖實踐秒殺

p4cf 9年前發布 | 58K 次閱讀 Redis NoSQL數據庫

需求:有一個標(理解成搶紅包也行,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>


 本文由用戶 p4cf 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!