请选择 进入手机版 | 继续访问电脑版

[Redis] Redis锁美满 办理 高并发秒杀标题

[复制链接]
查看91 | 回复19 | 2021-9-14 03:01:15 | 显示全部楼层 |阅读模式
目次

场景:一家网上商城做商品限量秒杀。

1 单机环境下的锁

将商品的数目 存到Redis中。每个用户抢购前都必要 到Redis中查询商品数目 (代替mysql数据库。不思量 变乱 ),假如 商品数目 大于0,则证实 商品有库存。然后我们在举行 库存扣减和接下来的操作。由于 多线程并发标题 ,我们不得不在get()方法内部利用 同步代码块。如许 可以保证查询库存和减库存操作的原子性。

  1. package springbootdemo.demo.controller;
  2. /*
  3. * @auther 顶风少年
  4. * @mail dfsn19970313@foxmail.com
  5. * @date 2020-01-13 11:19
  6. * @notify
  7. * @version 1.0
  8. */
  9. import org.springframework.beans.factory.annotation.Autowired;
  10. import org.springframework.data.redis.core.RedisTemplate;
  11. import org.springframework.web.bind.annotation.GetMapping;
  12. import org.springframework.web.bind.annotation.RestController;
  13. @RestController
  14. public class RedisLock {
  15. @Autowired
  16. private RedisTemplate<String, String> redisTemplate;
  17. @GetMapping(value = "buy")
  18. public String get() {
  19. synchronized (this) {
  20. String phone = redisTemplate.opsForValue().get("phone");
  21. Integer count = Integer.valueOf(phone);
  22. if (count > 0) {
  23. redisTemplate.opsForValue().set("phone", String.valueOf(count - 1));
  24. System.out.println("抢到了" + count + "号商品");
  25. }return "";
  26. }
  27. }
  28. }
复制代码

2 分布式环境 下利用 Redis锁。

但是由于业务上升,并发数目 变大。公司不得不将原有体系 复制一份,放到新的服务器。然后利用 nginx做负载均衡 。为了模仿 高并发环境这里利用 了 Apache JMeter工具。

很显着 ,现在 的线程锁不管用了。于是我们必要 换一把锁,这把锁必须和两套体系 没有任何的耦合度。

利用 Redies的API假如 key不存在,则设置一个key。这个key就是我们现在 利用 的一把锁。每个线程到此处,先设置锁,假如 设置锁失败,则表明当前有线程获取到了锁,就返回。末了 我们为了减库存和其他业务抛出非常 ,而没有开释 锁。把开释 锁的操作放到了finally代码块中。看起来是比较完善 了。

  1. package springbootdemo.demo.controller;
  2. /*
  3. * @auther 顶风少年
  4. * @mail dfsn19970313@foxmail.com
  5. * @date 2020-01-13 11:19
  6. * @notify
  7. * @version 1.0
  8. */
  9. import org.springframework.beans.factory.annotation.Autowired;
  10. import org.springframework.data.redis.core.RedisTemplate;
  11. import org.springframework.web.bind.annotation.GetMapping;
  12. import org.springframework.web.bind.annotation.RestController;
  13. @RestController
  14. public class RedisLock {
  15. @Autowired
  16. private RedisTemplate<String, String> redisTemplate;
  17. @GetMapping(value = "buy")
  18. public String get() {
  19. Boolean phoneLock = redisTemplate.opsForValue().setIfAbsent("phoneLock", "");
  20. if (!phoneLock) {
  21. return "";
  22. }
  23. try{
  24. String phone = redisTemplate.opsForValue().get("phone");
  25. Integer count = Integer.valueOf(phone);
  26. if (count > 0) {
  27. redisTemplate.opsForValue().set("phone", String.valueOf(count - 1));
  28. System.out.println("抢到了" + count + "号商品");
  29. }
  30. }finally {
  31. redisTemplate.delete("phoneLock");
  32. }
  33. return "";
  34. }
  35. }
复制代码

3 一台服务宕机,导致无法开释 锁

假如 try中抛出了非常 ,进入finally,这把锁依然会开释 ,不会影响其他线程获取锁,那么假如 在finally也抛出了非常 ,或者在finally中服务直接关闭了,那其他的服务再也获取不到锁。终极 导致商品卖不出去。

  1. package springbootdemo.demo.controller;
  2. /*
  3. * @auther 顶风少年
  4. * @mail dfsn19970313@foxmail.com
  5. * @date 2020-01-13 11:19
  6. * @notify
  7. * @version 1.0
  8. */
  9. import org.springframework.beans.factory.annotation.Autowired;
  10. import org.springframework.data.redis.core.RedisTemplate;
  11. import org.springframework.web.bind.annotation.GetMapping;
  12. import org.springframework.web.bind.annotation.RestController;
  13. @RestController
  14. public class RedisLock {
  15. @Autowired
  16. private RedisTemplate<String, String> redisTemplate;
  17. @GetMapping(value = "buy")
  18. public String get() {
  19. int i = 0;
  20. Boolean phoneLock = redisTemplate.opsForValue().setIfAbsent("phoneLock", "");
  21. if (!phoneLock) {
  22. return "";
  23. }
  24. try {
  25. String phone = redisTemplate.opsForValue().get("phone");
  26. Integer count = Integer.valueOf(phone);
  27. if (count > 0) {
  28. i = count;
  29. redisTemplate.opsForValue().set("phone", String.valueOf(count - 1));
  30. System.out.println("抢到了" + count + "号商品");
  31. }
  32. } finally {
  33. if (i == 20) {
  34. System.exit(0);
  35. }
  36. redisTemplate.delete("phoneLock");
  37. }
  38. return "";
  39. }
  40. }
复制代码

4 给每一把锁加上过期时间

标题 就出现在 假如 出现不测 ,这把锁无法开释 。这里我们在引入Redis的API,对key举行 过期时间的设置。如许 假如 拿到锁的线程,在任何环境 下没有来得及开释 锁,当Redis的key时间到,也会自动 开释 锁。但是如许 还是存在标题

假如 在key过期后,锁开释 了,但是当火线 程没有实行 完毕。那么其他线程就会拿到锁,继续抢购商品,而这个较慢的线程则会在实行 完毕后,开释 别人的锁。导致锁失效!

  1. package springbootdemo.demo.controller;
  2. /*
  3. * @auther 顶风少年
  4. * @mail dfsn19970313@foxmail.com
  5. * @date 2020-01-13 11:19
  6. * @notify
  7. * @version 1.0
  8. */
  9. import javafx.concurrent.Task;
  10. import org.springframework.beans.factory.annotation.Autowired;
  11. import org.springframework.data.redis.core.RedisTemplate;
  12. import org.springframework.web.bind.annotation.GetMapping;
  13. import org.springframework.web.bind.annotation.RestController;
  14. import java.util.Timer;
  15. import java.util.TimerTask;
  16. import java.util.concurrent.TimeUnit;
  17. @RestController
  18. public class RedisLock {
  19. @Autowired
  20. private RedisTemplate<String, String> redisTemplate;
  21. @GetMapping(value = "buy")
  22. public String get() {
  23. Boolean phoneLock = redisTemplate.opsForValue().setIfAbsent("phoneLock", "", 3, TimeUnit.SECONDS);
  24. if (!phoneLock) {
  25. return "";
  26. }
  27. try {
  28. String phone = redisTemplate.opsForValue().get("phone");
  29. Integer count = Integer.valueOf(phone);
  30. if (count > 0) {
  31. try {
  32. Thread.sleep(99999999999L);
  33. } catch (Exception e) {
  34. }
  35. redisTemplate.opsForValue().set("phone", String.valueOf(count - 1));
  36. System.out.println("抢到了" + count + "号商品");
  37. }
  38. } finally {
  39. redisTemplate.delete("phoneLock");
  40. }
  41. return "";
  42. }
  43. }
复制代码

5延伸 锁的过期时间,办理 锁失效

标题 的出现就是,当一条线程的key已颠末 期,但是这个线程的使命 确确实实没有实行 完毕,这个交易没有竣事 。但是锁没了。现在 我们必须对锁的时间举行 延伸 。在判定 商品有库存时,第一时间创建一个线程不停的给key续命,

防止key过期。然后在交易竣事 后,制止 定时器,开释 锁。

  1. package springbootdemo.demo.controller;
  2. /*
  3. * @auther 顶风少年
  4. * @mail dfsn19970313@foxmail.com
  5. * @date 2020-01-13 11:19
  6. * @notify
  7. * @version 1.0
  8. */
  9. import org.springframework.beans.factory.annotation.Autowired;
  10. import org.springframework.data.redis.core.RedisTemplate;
  11. import org.springframework.web.bind.annotation.GetMapping;
  12. import org.springframework.web.bind.annotation.RestController;
  13. import java.util.Timer;
  14. import java.util.TimerTask;
  15. import java.util.concurrent.TimeUnit;
  16. @RestController
  17. public class RedisLock {
  18. @Autowired
  19. private RedisTemplate<String, String> redisTemplate;
  20. @GetMapping(value = "buy")
  21. public String get() {
  22. Boolean phoneLock = redisTemplate.opsForValue().setIfAbsent("phoneLock", "", 3, TimeUnit.SECONDS);
  23. if (!phoneLock) {
  24. return "";
  25. }
  26. Timer timer = null;
  27. try {
  28. String phone = redisTemplate.opsForValue().get("phone");
  29. Integer count = Integer.valueOf(phone);
  30. if (count > 0) {
  31. timer = new Timer();
  32. timer.schedule(new TimerTask() {
  33. @Override
  34. public void run() {
  35. redisTemplate.opsForValue().set("phoneLock", "", 3, TimeUnit.SECONDS);
  36. }
  37. }, 0, 1);
  38. redisTemplate.opsForValue().set("phone", String.valueOf(count - 1));
  39. System.out.println("抢到了" + count + "号商品");
  40. }
  41. } finally {
  42. if (timer != null) {
  43. timer.cancel();
  44. }
  45. redisTemplate.delete("phoneLock");
  46. }
  47. return "";
  48. }
  49. }
复制代码

6 利用 Redisson简化代码

在步骤5我们的代码已经很完满 了,不会出现高并发标题 。但是代码确过于冗余,我们为了利用 Redis锁,我们必要 设置一个定长的key,然后当购买完成后,将key删除。但为了防止key提前过期,我们不得不新增一个线程实行 定时使命 。下面我们可以利用 Redissson框架简化代码。getLock()方法代替了Redis的setIfAbsent(),lock()设置过期时间。终极 我们在交易竣事 后开释 锁。延伸 锁的操作则有Redisson框架替我们完成,它会利用 轮询去查看key是否过期,

在交易没有完成时,自动 重设Redis的key过期时间

  1. package springbootdemo.demo.controller;
  2. /*
  3. * @auther 顶风少年
  4. * @mail dfsn19970313@foxmail.com
  5. * @date 2020-01-13 11:19
  6. * @notify
  7. * @version 1.0
  8. */
  9. import org.redisson.Redisson;
  10. import org.redisson.api.RLock;
  11. import org.springframework.beans.factory.annotation.Autowired;
  12. import org.springframework.data.redis.core.RedisTemplate;
  13. import org.springframework.web.bind.annotation.GetMapping;
  14. import org.springframework.web.bind.annotation.RestController;
  15. import java.util.Timer;
  16. import java.util.TimerTask;
  17. import java.util.concurrent.TimeUnit;
  18. @RestController
  19. public class RedissonLock {
  20. @Autowired
  21. private RedisTemplate<String, String> redisTemplate;
  22. @Autowired
  23. private Redisson redisson;
  24. @GetMapping(value = "buy2")
  25. public String get() {
  26. RLock phoneLock = redisson.getLock("phoneLock");
  27. phoneLock.lock(3, TimeUnit.SECONDS);
  28. try {
  29. String phone = redisTemplate.opsForValue().get("phone");
  30. Integer count = Integer.valueOf(phone);
  31. if (count > 0) {
  32. redisTemplate.opsForValue().set("phone", String.valueOf(count - 1));
  33. System.out.println("抢到了" + count + "号商品");
  34. }
  35. } finally {
  36. phoneLock.unlock();
  37. }
  38. return "";
  39. }
  40. }
复制代码

到此这篇关于Redis锁完善 办理 高并发秒杀标题 的文章就先容 到这了,更多相干 Redis锁高并发秒杀内容请搜索 脚本之家从前 的文章或继续欣赏 下面的相干 文章渴望 大家以后多多支持脚本之家!


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
回复

使用道具 举报

avatar Guogangts | 2021-9-17 08:23:01 | 显示全部楼层
很有看点!
回复

使用道具 举报

avatar 旭日非常 | 2021-9-17 08:23:03 | 显示全部楼层
听admin楼主一席话,省我十本书!
回复

使用道具 举报

avatar 玉米人 | 2021-9-20 05:21:20 | 显示全部楼层
这个帖子好无聊啊!
回复

使用道具 举报

avatar 上帝从不眨眼 | 2021-9-26 16:42:50 | 显示全部楼层
收藏了,以后可能会用到!
回复

使用道具 举报

avatar 大头226 | 2021-9-29 10:21:10 | 显示全部楼层
admin楼主练了葵花宝典吧?
回复

使用道具 举报

avatar 八块田确 | 2021-10-4 08:52:50 | 显示全部楼层
楼上的别说的那么悲观好吧!
回复

使用道具 举报

avatar 0Zombies0 | 2021-10-4 08:52:53 | 显示全部楼层
很有品味!
回复

使用道具 举报

avatar 空城乱人心乱 | 2021-10-4 16:27:46 | 显示全部楼层
看了这么多帖子,第一次看到这么经典的!
回复

使用道具 举报

avatar 阳光469 | 2021-10-5 01:06:17 | 显示全部楼层
看了这么多帖子,第一次看到这么高质量内容!
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则