上一篇文章介绍了Redisson的分布式锁原理,这篇文章来验证一下Redisson分布式锁的作用。
我这里使用Redis的主从模式。
搭建Redis主从,一主两从:
1、修改config文件
把redis.confg 复制多两份。
一共三份配置文件,分别是 redis6379.conf、redis6380.conf、redis6381.conf。
1、修改master
redis6379.conf 不需要修改,默认端口是 6379
这里我设置了密码:
requirepass redispid修改:
pidfile /var/run/redis_6379.pid可以另外修改一下允许远程连接,把bind注释。
2、修改 slave
修改 redis6380.conf
端口:
port 6380pid修改:
pidfile /var/run/redis_6380.pid指明master :
slaveof 127.0.0.1 6379因为我的redis配置了 密码 ,需要加上
masterauth redisredis6381.conf 修改同上。
2、启动
[root@VM-8-8-centos src]# ./redis-server /var/www/web/redis-5.0.8/redis6380.conf 16237:C 16 Oct 2020 09:26:22.275 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo 16237:C 16 Oct 2020 09:26:22.275 # Redis version=5.0.8, bits=64, commit=00000000, modified=0, pid=16237, just started 16237:C 16 Oct 2020 09:26:22.275 # Configuration loaded [root@VM-8-8-centos src]# ./redis-server /var/www/web/redis-5.0.8/redis6381.conf 16248:C 16 Oct 2020 09:26:27.793 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo 16248:C 16 Oct 2020 09:26:27.793 # Redis version=5.0.8, bits=64, commit=00000000, modified=0, pid=16248, just started 16248:C 16 Oct 2020 09:26:27.793 # Configuration loaded成功启动:
[root@VM-8-8-centos src]# ps -aux|grep redis root 6570 0.0 0.7 167336 13784 ? Ssl Oct14 2:15 ./redis-server *:6379 root 16238 0.0 0.3 153900 6400 ? Ssl 09:26 0:00 ./redis-server *:6380 root 16249 0.0 0.4 153900 7700 ? Rsl 09:26 0:00 ./redis-server *:6381 root 16264 0.0 0.0 112712 956 pts/0 R+ 09:26 0:00 grep --color=auto redis查看一下配置:
slave 6381:
[root@VM-8-8-centos src]# ./redis-cli -p 6381 -a redis Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe. 127.0.0.1:6381> info replication # Replication role:slave master_host:127.0.0.1 master_port:6379 master_link_status:down master_last_io_seconds_ago:-1 master_sync_in_progress:0 slave_repl_offset:1 master_link_down_since_seconds:1602812509 slave_priority:100 slave_read_only:1 connected_slaves:0 master_replid:9c3ed5d61281a184e63c10483a8aeb31c3c57402 master_replid2:0000000000000000000000000000000000000000 master_repl_offset:0 second_repl_offset:-1 repl_backlog_active:0 repl_backlog_size:1048576 repl_backlog_first_byte_offset:0 repl_backlog_histlen:0master 6379 :
[root@VM-8-8-centos src]# ./redis-cli -p 6379 -a redis Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe. 127.0.0.1:6379> info replication # Replication role:master connected_slaves:0 master_replid:d26097e5e79e7475e91e8d0f04b0b756047d2a75 master_replid2:0000000000000000000000000000000000000000 master_repl_offset:0 second_repl_offset:-1 repl_backlog_active:1 repl_backlog_size:1048576 repl_backlog_first_byte_offset:1 repl_backlog_histlen:0连接你的redis:
./redis-cli.exe -h 82.71.16.139 -p 6379 -a redis使用redis-desktop-manager连接:
配置Nginx,分流进入两个服务。
修改nginx.conf
upstream mysite { server 127.0.0.1:8090 weight=1; server 127.0.0.1:8091 weight=1; } server { listen 80; server_name hellocoder.com www.hellocoder.com; error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } location / { proxy_pass http://mysite; } }最后改一下hosts。
127.0.0.1 www.hellocoder.com 127.0.0.1 hellocoder.com启动nginx。
配置redisson:
引入依赖:
<dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.7.3</version> </dependency>配置Redis:
新建 RedissonConfig.java:
@Configuration public class RedissonConfig { @Bean public RedissonClient redissonClient() { Config config = new Config(); // config.useSingleServer();//单机 // config.useMasterSlaveServers();//集群 // config.useSentinelServers();//哨兵 // config.useClusterServers();//集群 // config.setLockWatchdogTimeout(30000); //使用的Redis主从模式 config.useMasterSlaveServers() .setPassword("redis") .setMasterAddress("redis://82.71.16.139:6379") .addSlaveAddress("redis://82.71.16.139:6380","redis://82.71.16.139:6381"); return Redisson.create(config); }新建两个实体:
Book.java:
/** * @author 公众号:HelloCoder,每天分享Java技术和面试题 * @date 2020/10/16 * @Description */ @Builder @Data @TableName("t_book") @AllArgsConstructor @NoArgsConstructor public class Book { @TableId(value = "book_id", type = IdType.AUTO) private long bookId; private String name; private int count; }Order.java
@Builder @Data @TableName("t_book_order") @AllArgsConstructor @NoArgsConstructor public class Order { @TableId(value = "id", type = IdType.AUTO) private int id; private String orderId; private long bookId; private int status; private long userId; private int count; private String billTime; }OrderController.java:
@RestController @Slf4j @RequestMapping("Order/") public class OrderController { @Autowired BookOrderService bookOrderService; @RequestMapping("/seckill") public RetResult seckill(@RequestParam(value = "bookId") Long bookId, @RequestParam(value = "userId", required = false) Long userId) { if (userId == null) { //模拟userId,随机生成,这里应该有前端传入 userId = (long) (Math.random() * 1000); } String result = bookOrderService.seckill(bookId, userId); return RetResponse.makeOKRsp(result); } }这里模拟了两种情况:
一种是不加锁,第二种是加redis锁
BookOrderService.java
@Slf4j @Service public class BookOrderService { @Autowired BookMapper bookMapper; @Autowired OrderMapper orderMapper; @Autowired RedissonClient redissonClient; public String seckill(Long bookId, Long userId) { return notLockDemo(bookId, userId); // return lockDemo(bookId, userId); } String lockDemo(Long bookId, Long userId) { final String lockKey = bookId + ":" + "seckill" + ":RedissonLock"; RLock rLock = redissonClient.getLock(lockKey); try { // 尝试加锁,最多等待20秒,上锁以后10秒自动解锁 Boolean flag = rLock.tryLock(20, 10, TimeUnit.SECONDS); if (flag) { //1、判断这个用户id 是否已经秒杀过 List<Order> list = orderMapper.selectList(new QueryWrapper<Order>().lambda().eq(Order::getUserId, userId).eq(Order::getStatus, 1).eq(Order::getBookId, bookId)); if (list.size() >= 1) { log.info("你已经抢过了"); return "你已经抢过了,一人只能抢一次"; } //2、查库存 Book book = bookMapper.selectOne(new QueryWrapper<Book>().lambda().eq(Book::getBookId, bookId)); if (book != null && book.getCount() > 0) { //生成订单 String orderId = UUID.randomUUID().toString(); Order newOrder = Order.builder(). orderId(orderId). status(1). bookId(bookId). userId(userId). count(1). billTime(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())).build(); orderMapper.insert(newOrder); //更新库存 Book newBook = Book.builder().count(book.getCount() - 1).build(); bookMapper.update(newBook, new QueryWrapper<Book>().lambda().eq(Book::getBookId, bookId)); log.info("userId:{} 秒杀成功", userId); return "秒杀成功" + ""; } else { log.info("秒杀失败,被抢完了"); } } else { log.info("请勿重复点击,userid:{} ", userId); return "你已经抢过了"; } } catch (Exception e) { e.printStackTrace(); } finally { if (rLock.isLocked()) { if (rLock.isHeldByCurrentThread()) { rLock.unlock(); } } } return "很遗憾,没货了..."; } String notLockDemo(Long bookId, Long userId) { //1、判断这个用户id 是否已经秒杀过 List<Order> list = orderMapper.selectList(new QueryWrapper<Order>().lambda().eq(Order::getUserId, userId).eq(Order::getStatus, 1).eq(Order::getBookId, bookId)); if (list.size() >= 1) { log.info("你已经抢过了"); return "你已经抢过了,一人只能抢一次"; } //2、查库存 Book book = bookMapper.selectOne(new QueryWrapper<Book>().lambda().eq(Book::getBookId, bookId)); if (book != null && book.getCount() > 0) { //生成订单 String orderId = UUID.randomUUID().toString(); Order newOrder = Order.builder(). orderId(orderId). status(1). bookId(bookId). userId(userId). count(1). billTime(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())).build(); orderMapper.insert(newOrder); //更新库存 Book newBook = Book.builder().count(book.getCount() - 1).build(); bookMapper.update(newBook, new QueryWrapper<Book>().lambda().eq(Book::getBookId, bookId)); log.info("userId:{} 秒杀成功", userId); return "秒杀成功" + ""; } else { log.info("秒杀失败,被抢完了"); return "很遗憾,没货了..."; } } }新建两个表。
t_book、t_book_order
DROP TABLE IF EXISTS `t_book` ; CREATE TABLE `t_book` ( `book_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '编号', `name` varchar(400) DEFAULT NULL COMMENT '名称', `count` int DEFAULT 0 COMMENT '数量', PRIMARY KEY (`book_id`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='商品表'; DROP TABLE IF EXISTS `t_book_order` ; CREATE TABLE `t_book_order` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '编号', `order_id` varchar(100) NOT NULL COMMENT '订单号', `book_id` bigint(20) NOT NULL COMMENT '商品id', `user_id` bigint(20) DEFAULT NULL COMMENT '用户id', `status` int DEFAULT 1 COMMENT '状态', `count` int DEFAULT 0 COMMENT '购买数量', `bill_time` datetime DEFAULT NULL COMMENT '下单时间', PRIMARY KEY (`id`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='订单表'; INSERT INTO `seckill`.`t_book`(`book_id`, `name`, `count`) VALUES (1, '《HaC的自传》', 5);启动服务,启动两个端口的服务,模拟分布式部署。
1、不加锁情况:
使用jmeter 模拟并发。不加锁的情况模拟10个请求在1s发出 共2次,方便查看:
点击start
查看一下日志:
8090这台服务器:
8091这台服务器:
同一时间进入请求。
查询一下订单:
库存为0之后,但是初始化只有 5 本书,最后竟然出现了18个订单,显然是有问题的。
这就是不加锁的结果。
2、加锁情况:
清空表:
TRUNCATE TABLE t_book_order; UPDATE t_book SET count = 5 WHERE book_id =1;放开BookOrderService.java注释,重启两个服务
public String seckill(Long bookId, Long userId) { // return notLockDemo(bookId, userId); return lockDemo(bookId, userId); }jmeter设置 1000个请求,共2次
再看一下日志:
8090服务器:
8091服务器:
看一下数据库:
刚好生成 5 个订单,没有超卖的现象。
以上就是redisson分布式锁的简单使用。