Redis-习-01-Redis分布式锁(单节点Lua脚本)

Redis-习-01-Redis分布式锁(Lua脚本)

SpringBoot 项目中,采用 set key value nx px 命令确保获取锁的原子性操作,使用 Lua 脚本确保释放锁的原子性操作。(仅限单节点Redis)

获取锁释放锁代码

    private static final Long RELEASE_SUCCESS = 1L;
    private static final String LOCK_SUCCESS = "OK";
    private static final String SET_IF_NOT_EXIST = "NX";
    private static final String SET_WITH_EXPIRE_TIME = "EX";
    private static final String RELEASE_LOCK_SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";


    @SuppressWarnings("rawtypes")
    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 该加锁方法仅针对单实例 Redis 可实现分布式加锁
     * 对于 Redis 集群则无法使用
     * 支持重复,线程安全
     * @param lockKey   加锁键
     * @param clientId  加锁客户端唯一标识(采用UUID)
     * @param seconds   锁过期时间
     * @return
     */
    public Boolean getLock(String lockKey, String clientId, long seconds) {
        return (Boolean) redisTemplate.execute((RedisCallback<Boolean>) redisConnection -> {
            Jedis jedis = (Jedis) redisConnection.getNativeConnection();
            String result = jedis.set(lockKey, clientId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, seconds);
            if (LOCK_SUCCESS.equals(result)) {
                return Boolean.TRUE;
            }
            return Boolean.FALSE;
        });
    }

    /**
     * 与 tryLock 相对应,用作释放锁
     * @param lockKey
     * @param clientId
     * @return
     */
    public Boolean releaseLock(String lockKey, String clientId) {
        return (Boolean) redisTemplate.execute((RedisCallback<Boolean>) redisConnection -> {
            Jedis jedis = (Jedis) redisConnection.getNativeConnection();
            Object result = jedis.eval(RELEASE_LOCK_SCRIPT, Collections.singletonList(lockKey),
                    Collections.singletonList(clientId));
            if (RELEASE_SUCCESS.equals(result)) {
                return Boolean.TRUE;
            }
            return Boolean.FALSE;
        });
    }

测试

代码

Controller代码

@ResponseBody
@RequestMapping("/system/testLock")
public R testLock(String productId) {
    String s = lockTestService.testLock(productId);
    return R.ok().put("data", s);
}

Service代码

package com.masteryee.common.service.impl;

import com.google.common.collect.Maps;
import com.masteryee.common.service.LockTestService;
import com.masteryee.msedu.util.RedisManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;


/**
 * 系统用户
 */
@Service("lockTestService")
@Transactional
public class LockTestServiceImpl implements LockTestService {

	@Autowired
	private RedisManager redisManager;

	//商品详情
	private static HashMap<String, Integer> product = new HashMap();
	//订单表
	private static HashMap<String, String> orders = new HashMap();
	//库存表
	private static HashMap<String, Integer> stock = new HashMap();

	static {
		product.put("Note3", 10000);
		stock.put("Note3", 10000);
		product.put("Nove4", 10000);
		stock.put("Nove4", 10000);
		product.put("R5", 10000);
		stock.put("R5", 10000);
	}

	public String select_info(String productId) {
		Map<String, String> stringStringMap = Maps.filterValues(orders, r -> r.equalsIgnoreCase(productId));
		return "限量抢购商品["+productId+"]共" + product.get(productId) + ",现在成功下单" + stringStringMap.size()
				+ ",剩余库存" + stock.get(productId) + "件";
	}

	/*@Override
	public synchronized String testLock(String productId) {
		if (stock.get(productId) == 0) {
			//已近买完了
			return "活动已经结束了";
		} else {
			//还没有卖完
			try {
				//模拟操作数据库
				Thread.sleep(100);
				orders.put(UUID.randomUUID().toString(), productId);
				stock.put(productId, stock.get(productId) - 1);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		return select_info(productId);
	}*/

	@Override
	public String testLock(String productId) {
		if (stock.get(productId) == 0) {
			//已近买完了
			return "活动已经结束了";
		} else {
			//还没有卖完
			String clientId = "";
			try {
			    // 作为Redis的key对应的value,释放锁时要用
				clientId = UUID.randomUUID().toString();
				// 死循环,直到获取锁为止
				while (true) {
				    // 获取锁
					Boolean lock = redisManager.getLock(productId, clientId, 2);
					if (lock) {
						//模拟操作数据库
						Thread.sleep(100);
						orders.put(UUID.randomUUID().toString(), productId);
						stock.put(productId, stock.get(productId) - 1);
						break;
					}
				}
			} catch (InterruptedException e) {
				e.printStackTrace();
			} finally {
			    // 释放锁
				redisManager.releaseLock(productId, clientId);
			}
		}
		return select_info(productId);
	}
}

测试过程

使用 Apache 提供的 ab 压测工具。用法参考

同时开三个窗口,同时请求,分别测试使用synchronized同步方法和分布式锁。
image

以 Note3 请求参数为例(另外还有 Nove4 和 R5)
同步方法synchronized

E:\develop\Apache AB\httpd-2.4.39-o102s-x64-vc14\Apache24\bin>ab -n 100 -c 50 http://localhost/system/testLock?productId=Note3
This is ApacheBench, Version 2.3 <$Revision: 1843412 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking localhost (be patient).....done


Server Software:
Server Hostname:        localhost
Server Port:            80

Document Path:          /system/testLock?productId=Note3
Document Length:        93 bytes

Concurrency Level:      50
Time taken for tests:   26.739 seconds
Complete requests:      100
Failed requests:        91
   (Connect: 0, Receive: 0, Length: 91, Exceptions: 0)
Total transferred:      21292 bytes
HTML transferred:       9392 bytes
Requests per second:    3.74 [#/sec] (mean)
Time per request:       13369.702 [ms] (mean)
Time per request:       267.394 [ms] (mean, across all concurrent requests)
Transfer rate:          0.78 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.2      0       1
Processing:   150 6632 5420.0   5257   21673
Waiting:      150 6632 5420.2   5257   21673
Total:        151 6633 5420.0   5257   21673

Percentage of the requests served within a certain time (ms)
  50%   5257
  66%   8642
  75%   9630
  80%  10436
  90%  15346
  95%  20877
  98%  21471
  99%  21673
 100%  21673 (longest request)

分布式锁

E:\develop\Apache AB\httpd-2.4.39-o102s-x64-vc14\Apache24\bin>ab -n 100 -c 50 http://localhost/system/testLock?productId=Note3
This is ApacheBench, Version 2.3 <$Revision: 1843412 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking localhost (be patient).....done


Server Software:
Server Hostname:        localhost
Server Port:            80

Document Path:          /system/testLock?productId=Note3
Document Length:        93 bytes

Concurrency Level:      50
Time taken for tests:   10.676 seconds
Complete requests:      100
Failed requests:        91
   (Connect: 0, Receive: 0, Length: 91, Exceptions: 0)
Total transferred:      21292 bytes
HTML transferred:       9392 bytes
Requests per second:    9.37 [#/sec] (mean)
Time per request:       5338.227 [ms] (mean)
Time per request:       106.765 [ms] (mean, across all concurrent requests)
Transfer rate:          1.95 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.2      0       1
Processing:   172 3888 2260.7   3963   10333
Waiting:      171 3888 2260.9   3963   10331
Total:        172 3888 2260.7   3963   10333

Percentage of the requests served within a certain time (ms)
  50%   3963
  66%   4776
  75%   5237
  80%   5931
  90%   6758
  95%   8234
  98%   9715
  99%  10333
 100%  10333 (longest request)

同步方法只允许同一个时间一个线程进入方法,不管请求参数是否一致,一律加锁。而分布式锁只针对同一钟产品或者参数加锁,对其他的参数不产生影响。如:我测试的参数分别为 Note3,Nove4,R5。分布式锁在Redis中则是以 Note3,Nove4,R5分别为key,所以互相不影响,可以同时访问,效率比同步方法更高。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值