记录自己的redis之路-04-redis秒杀实例

乐观锁的实现

乐观锁实现中的锁就是商品的键值对。使用jediswatch方法监视商品键值对,如果事务提交exec时发现监视的键值对发生变化,事务将被取消,商品数目不会被改动。

    1. 创建MyRunnable 实现Runnable 接口
package com.qrcode.redisdemo.redis02;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;

import java.util.List;

public class MyRunnable implements Runnable {
    String watchkeys = "iphoneXS";// 监视keys
    Jedis jedis = new Jedis("192.168.253.128", 6379);
    String userinfo;

    public MyRunnable() {
    }

    public MyRunnable(String uinfo) {
        this.userinfo = uinfo;
    }

    @Override
    public void run() {
        try {

            jedis.watch(watchkeys); //当事务执行之前这个key发生了改变,事务会被打断

            String val = jedis.get(watchkeys); // 获取缓存中的数量
            int valint = Integer.valueOf(val);

            if (valint <= 100 && valint >= 1) {

                Transaction tx = jedis.multi();// 开启事务

                tx.incrBy(watchkeys, -1);  //减少一个商品数量

                List<Object> list = tx.exec();// 提交事务,如果此时watchkeys被改动了,则返回null

                if (list == null || list.size() == 0) {

                    String failinfo = "用户:" + userinfo + "商品争抢失败,抢购失败";
                      System.out.println(failinfo);
                    /* 抢购失败业务逻辑 */
//                    jedis.setnx(userinfo, failinfo);
                } else {
                    for (Object succ : list) {
                        String succinfo = "用户:" + userinfo + "抢购争抢成功,当前抢购成功人数:"
                                + (1 - (valint - 100));
                        System.out.println(succinfo);
                         /* 抢购成功业务逻辑 */
                         jedis.lpush("list1",userinfo);
//                        jedis.setnx(userinfo, succinfo);
                    }

                }

            } else {
                String failinfo1 = "用户:" + userinfo + "商品被抢购完啦!!!";
                System.out.println(failinfo1);
                return;
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            jedis.close();
        }

    }
}
  1. 创建线程启动测试
package com.qrcode.redisdemo.redis02;

import com.qrcode.redisdemo.util.GuoUtil;
import redis.clients.jedis.Jedis;

import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class MyRedistest {
    public static void main(String[] args) {
        //创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。
        //线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,
        // 那么线程池会补充一个新线程。
        ExecutorService executor = Executors.newFixedThreadPool(20);  //20个线程池并发数

        final Jedis jedis = new Jedis("192.168.253.128", 6379);
        jedis.set("iphoneXS", "100");//设置起始的抢购数
        jedis.del("list1");
        jedis.close();

        for (int i = 0; i < 1000; i++) {//设置1000个人来发起抢购
            executor.execute(new MyRunnable("user"+i));
        }
        executor.shutdown();
    }
}

 

运行结果

问题:

100人抢购,结果:

一百个 抢购 一百个 手机 ,竟然还有83个没有卖出去…

解决办法:

在抢购失败的时候让线程等待500毫秒,继续递归调用方法

此时没问题了..

但是内心总有一股不安…TODO 后期继续搞.

 

 

 

 

技术总结:

  • 线程池的使用

ExecutorService executor = Executors.newFixedThreadPool(20);

一般是和事务一起使用,当对某个key进行watch后如果其他的客户端对这个key进行了更改,那么本次事务会被取消,事务的exec会返回null

jedis.watch(watchkeys);

Transaction tx = jedis.multi();// 开启事务

tx.incrBy(watchkeys, -1)//减少一个商品数量

List<Object> list = tx.exec();// 提交事务,如果此时watchkeys被改动了,则返回null

悲观锁的实现

悲观锁中的锁是一个唯一标识的锁lockKey和该锁的过期时间。首先确定缓存中有商品,然后在拿数据(商品数目改动)之前先获取到锁,之后对商品数目进行减一操作,操作完成释放锁,一个秒杀操作完成。这个锁是基于redissetNX操作实现的阻塞式分布式锁

  1. 修改MyRunnable
package com.qrcode.redisdemo.redis02;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;

import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class MyRunnable implements Runnable {
    String watchkeys = "iphoneXS";// 监视keys
    Jedis jedis = new Jedis("192.168.253.128", 6379);
    String userinfo;
    Lock lock;
    public MyRunnable() {
    }

    public MyRunnable(String uinfo,Lock lock) {
        this.userinfo = uinfo;
        this.lock = lock;
    }

    @Override
    public void run() {
        lock.lock(); // 获得锁
        try {
            String val = jedis.get(watchkeys); // 获取缓存中的数量
            int valint = Integer.valueOf(val);
            if (valint <= 100 && valint >= 1) {
                jedis.set(watchkeys,String.valueOf((valint-1)));
                System.out.println("用户"+userinfo +"抢购成功,还剩"+(valint-1));
            }else {
                System.out.println("用户"+userinfo +"抢购失败");
            }
        }finally {
            lock.unlock(); // 释放锁
        }
}
  1. 修改线程启动测试
package com.qrcode.redisdemo.redis02;

import com.qrcode.redisdemo.util.GuoUtil;
import redis.clients.jedis.Jedis;

import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class MyRedistest {
    public static void main(String[] args) {
        //创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。
        //线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,
        // 那么线程池会补充一个新线程。
        ExecutorService executor = Executors.newFixedThreadPool(20);  //20个线程池并发数

        final Jedis jedis = new Jedis("192.168.253.128", 6379);
        jedis.set("iphoneXS", "100");//设置起始的抢购数
        jedis.del("list1");
        jedis.close();

//重入锁
        Lock lock=new ReentrantLock();
        for (int i = 0; i < 1000; i++) {//设置1000个人来发起抢购
            executor.execute(new MyRunnable("user"+i,lock));
        }
        executor.shutdown();
    }
}

结果:

100人抢购  完美!

一步一个脚印 , 独自前行的路上不要急于求成.

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 对于您的问题,我可以提供一些设计基于 Redis秒杀架构的建议。 首先,Redis 是一个快速、开源的内存键值存储系统,支持多种数据结构。在实现秒杀架构时,Redis 的高性能和支持原子性操作的特性使得其成为一个非常好的选择。 以下是一些基于 Redis秒杀架构设计建议: 1. 使用 Redis 存储商品信息和库存。可以使用 Redis 的 Hash 数据类型来存储每个商品的详细信息,如商品名称、描述、价格、剩余库存等。使用 Redis 的计数器数据类型来实现商品的库存管理。 2. 使用 Redis 的分布式锁来实现商品的售卖和库存的减少。当一个用户购买商品时,使用分布式锁来确保在同一时间只有一个用户可以购买该商品。如果锁定成功,则将商品的库存减少一个数量,并将订单信息存储在 Redis 中。 3. 使用消息队列来处理用户订单。在秒杀活动期间,订单量可能非常大。为了避免订单丢失或延迟处理,可以使用消息队列来确保所有订单都能被及时处理。 4. 可以考虑使用 Redis 集群来扩展系统的性能和容量。Redis 集群可以自动将数据分布在多个节点上,并实现高可用性和故障转移。 5. 最后,需要对系统进行充分的压力测试和性能测试,以确保其在高并发环境下能够正常运行。 这些是设计基于 Redis秒杀架构的一些建议,希望能对您有所帮助。 ### 回答2: 秒杀是一种高并发的业务场景,为了保证系统能够在短时间内处理大量的并发请求,可以设计一个基于Redis秒杀架构。 首先,为了确保系统的高可用性和水平扩展性,可以使用主从复制的方式部署Redis服务器。将读写请求分发到不同的Redis实例,提高系统的并发处理能力。 在设计秒杀业务的数据库模型时,可以使用Redis的Hash数据结构,将商品ID作为Key,将商品库存和商品信息等存储在Hash中。这样可以将商品信息保存在内存中,提高读取速度。 为了防止超卖和维护商品库存的一致性,可以使用Redis的事务机制和CAS(Compare and Set)操作。在用户发起秒杀请求时,首先判断商品库存是否大于0,如果大于0,则使用Redis事务机制将商品库存减1,并将秒杀成功的用户信息加入到一个集合(Set)中。如果库存小于等于0,则秒杀失败。通过CAS操作,可以保证商品库存的准确性,避免多个请求同时减少库存而导致超卖的问题。 为了应对高并发请求,可以使用分布式锁来控制用户的并发访问。Redis提供了分布式锁的实现方式,如使用SETNX命令来获取锁以及使用DEL命令来释放锁。当用户发起秒杀请求时,先尝试获取锁,如果获取成功,则执行秒杀逻辑,否则等待一段时间后重新尝试。 为了减轻数据库的压力,可以结合异步处理的方式。将秒杀请求放入消息队列中,通过消费者的方式异步处理秒杀逻辑,这样可以将高并发的请求分散到不同的时间段内进行处理,提高系统的并发处理能力。 最后,为了保证系统的稳定性和故障恢复能力,可以设置监控和恢复机制。通过Redis的监控工具对Redis服务器进行监控,并设置服务器宕机时的自动切换机制,将流量引导到备用节点上,确保系统的可用性。 总之,基于Redis秒杀架构需要考虑高可用性、水平扩展性、数据一致性和并发处理能力等方面,并结合分布式锁、事务机制、异步处理和监控机制等技术手段来实现。 ### 回答3: 秒杀架构是一种高并发场景下常见的设计方案,旨在解决大量用户同时请求同一商品的情况下保证系统的可用性和稳定性。基于Redis秒杀架构可以采用以下设计方案: 1. 商品库存管理:使用Redis的Hash结构来存储商品的库存信息。每个商品对应一个Hash结构,包括库存数量、已售数量、商品ID等字段。可以通过Redis的原子操作将库存数量进行减少和增加,保证库存的实时性和一致性。 2. 请求限流:为了控制系统的并发请求量,可以使用Redis的计数器功能实现请求的限流。每次用户发起秒杀请求时,利用Redis的INCR操作对计数器进行自增操作,同时设置过期时间,超过限定值的请求将被拒绝。 3. 重复请求处理:由于高并发场景下,用户可能多次提交秒杀请求,为了避免重复购买商品,引入Redis的Set数据结构记录已经购买过的用户ID。每次用户发起秒杀请求前,先判断用户ID是否存在于Set中,若存在则拒绝请求,否则可以继续进行秒杀操作。 4. 异步下单:为了提高系统的并发处理能力,并降低响应时间,可以使用消息队列来实现异步下单的操作。秒杀成功后,将下单的请求存入消息队列中,由消费者进行实际的下单操作,将订单信息写入数据库。 5. 分布式部署:为了进一步提高系统的稳定性和可扩展性,可以采用分布式部署架构。将商品的库存和用户ID等信息分片存储在不同的Redis节点上,通过分布式缓存中间件来实现数据的一致性和负载均衡。 通过以上的设计方案,基于Redis秒杀架构可以实现高并发场景下的安全、稳定和高效的秒杀操作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值