使用redis分布式锁模拟10万人抢单,【架构师必备】

34 阅读5分钟

总结

  • 对于框架原理只能说个大概,真的深入某一部分具体的代码和实现方式就只能写出一个框架,许多细节注意不到。

  • 算法方面还是很薄弱,好在面试官都很和蔼可亲,擅长发现人的美哈哈哈...(最好多刷一刷,不然影响你的工资和成功率????)

  • 在投递简历之前,最好通过各种渠道找到公司内部的人,先提前了解业务,也可以帮助后期优秀 offer 的决策。

  • 要勇于说不,对于某些 offer 待遇不满意、业务不喜欢,应该相信自己,不要因为当下没有更好的 offer 而投降,一份工作短则一年长则 N 年,为了幸福生活要慎重选择!!!

第一次跳槽十分忐忑不安,和没毕业的时候开始找工作是一样的感受,真的要相信自己,有条不紊的进行。如果有我能帮忙的地方欢迎随时找我,比如简历修改、内推、最起码,可以把烦心事说一说,人嘛都会有苦恼的~

祝大家都有美好的未来,拿下满意的 offer。 开源分享:docs.qq.com/doc/DSmRnRG…

redis.clients

jedis

org.projectlombok

lombok

创建redis工具类

=======================================================================

package com.example.lock.utils;

import org.apache.commons.pool2.impl.GenericObjectPoolConfig;

import redis.clients.jedis.Jedis;

import redis.clients.jedis.JedisPool;

public class RedisUtils {

private static final String HOST = "192.168.1.105";

private static final int PORT = 6379;

// 连接池中的最大空闲连接

private static final int maxIdle = 8;

/**

  • 初始化redis

  • @return

*/

public static Jedis getJedis() {

GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();

poolConfig.setMaxIdle(maxIdle);

JedisPool jedisPool = new JedisPool(poolConfig, HOST, PORT);

return jedisPool.getResource();

}

}

加锁

===============================================================

/**

  • 通过setnx加锁

  • @param key

  • @param val

  • @param expireTime 加锁时间,单位ms

  • @return

*/

public static boolean lock(String key, String val, int expireTime) {

try {

long re = getJedis().setnx(key, val);

if (re == 1) {

getJedis().expire(key, expireTime);

return true;

}

} catch (Exception e) {

e.printStackTrace();

} finally {

getJedis().close();

}

return false;

}

测试加锁效果

我们通过设置加锁时间为60s,在60s内对同一个key进行操作,看看是否能够操作成功

@Slf4j

@RestController

public class RedisTestController {

@GetMapping("/setnx/{key}/{val}")

public String setnx(@PathVariable String key, @PathVariable String val) {

String result = null;

if (RedisUtils.lock(key, val, 60 * 1000)) {//在此处将key进行加锁

result = "key:" + key + ",value:" + val + " 加锁成功";

log.info(result);

} else {

result = "key:" + key + ",value:" + val + " 加锁失败";

log.error(result);

}

return result;

}

}

接下来进行测试

在这里插入图片描述

通过上图可以看到第一次访问我们将key设置为lock,value设置为11成功

在这里插入图片描述

查看redis服务器的存储值,也可以看到lock对应的存活时间(TTL)为60s

ps:截图速度没那么快,所以损耗了几秒还剩59秒多一点

接下来我们尝试将lock对应的value设置为12

在这里插入图片描述

回到我们打印的日志

在这里插入图片描述

可以看到还未到1分钟,所以加锁失败

通过上面这个过程,相信大家 应该大致了解了加锁的原理,接下来我们看看获取资源后怎么样解锁

解锁

===============================================================

上面是创建锁,同样的具有有效时间,但是我们不能完全依赖这个有效时间,场景如:有效时间设置1分钟,本身用户A获取锁后,没遇到什么特殊情况正常生成了抢购订单后,此时其他用户应该能正常下单了才对,但是由于有个1分钟后锁才能自动释放,那其他用户在这1分钟无法正常下单(因为锁还是A用户的),因此我们需要A用户操作完后,主动去解锁:

/**

  • 解锁

  • @param key

  • @return

*/

public static int unlock(String key, String val) {

try {

if (!getJedis().exists(key)) return 1;

StringBuilder sbScript = new StringBuilder();

sbScript.append("if redis.call('get','").append(key).append("')").append("=='").append(val).append("'").

append(" then ").

append(" return redis.call('del','").append(key).append("')").

append(" else ").

append(" return 0").

append(" end");

return Integer.valueOf(getJedis().eval(sbScript.toString()).toString());

} catch (Exception e) {

e.printStackTrace();

} finally {

getJedis().close();

}

return 0;

}

有了加锁和解锁,我们开始进行模拟10万人抢单

模拟抢单动作

===================================================================

  • 首先我们先随机生成10万人

/**

  • 生成抢单用户

  • @param numbers 用户数量

  • @return

*/

private List generateUsers(int numbers) {

List users = new ArrayList<>();

IntStream.range(0, numbers).parallel().forEach(b -> {

users.add("用户-" + b);

});

return users;

}

  • 定义库存等基础信息

//总库存

private long nKuCuen = 0;

//商品key名字

private String shangpingKey = "computer_key";

//获取锁的超时时间 秒

private int timeout = 30 * 1000;

  • 模拟用户抢单动作

/**

  • 模拟抢单动作

  • @param b

  • @return

*/

private String qiang(String b) {

//用户开抢时间

long startTime = System.currentTimeMillis();

//未抢到的情况下,30秒内继续获取锁

while ((startTime + timeout) >= System.currentTimeMillis()) {

//商品是否剩余

if (nKuCuen <= 0) {

break;

}

//保留所资源60s

if (RedisUtils.lock(shangpingKey, b, 60 * 1000)) {

//用户b拿到锁

log.info("{}拿到锁...", b);

try {

//商品是否剩余

if (nKuCuen <= 0) {

break;

}

//模拟生成订单耗时操作,方便查看:用户-50 多次获取锁记录

try {

TimeUnit.SECONDS.sleep(1);

} catch (InterruptedException e) {

e.printStackTrace();

}

//抢购成功,商品递减,记录用户

nKuCuen -= 1;

//抢单成功跳出

log.info("{}抢单成功跳出...所剩库存:{}", b, nKuCuen);

return b + "抢单成功,所剩库存:" + nKuCuen;

} finally {

log.info("{}释放锁...", b);

//释放锁

RedisUtils.unlock(shangpingKey, b);

}

} else {

//用户b没拿到锁,在超时范围内继续请求锁,不需要处理

//log.info("{}等待获取锁...", b);

}

}

return "";

}

  • 编写接口

@GetMapping("/qiangdan")

public List qiangdan() {

//抢到商品的用户

List shopUsers = new ArrayList<>();

//构造很多用户(目前 暂定10w人)

List users = generateUsers(10*10000);

//初始化库存

nKuCuen = 10;

//模拟开抢

users.parallelStream().forEach(b -> {//并行遍历循环

String shopUser = qiang(b);

if (!StringUtils.isEmpty(shopUser)) {

shopUsers.add(shopUser);

}

});

return shopUsers;

}

所有代码如下:

import com.example.lock.utils.RedisUtils;

import lombok.extern.slf4j.Slf4j;

import org.springframework.util.StringUtils;

import org.springframework.web.bind.annotation.GetMapping;

import org.springframework.web.bind.annotation.PathVariable;

最后

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】 就答题情况而言,第一问100%都可以回答正确,第二问大概只有50%正确率,第三问能回答正确的就不多了,第四问再正确就非常非常少了。其实此题并没有太多刁钻匪夷所思的用法,都是一些可能会遇到的场景,而大多数人但凡有1年到2年的工作经验都应该完全正确才对。 只能说有一些人太急躁太轻视了,希望大家通过此文了解js一些特性。

并祝愿大家在新的一年找工作面试中胆大心细,发挥出最好的水平,找到一份理想的工作。