Redis--12--1--分布式锁---java

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

  • Redis与分布式锁
  • Jedis实现
    • 1.RedisConfig
    • 2.RedisDistLock
    • 3.应用
    • 4.加上看门狗逻辑 RedisDistLockWithDog
  • redisson实现
    • 1.依赖
    • 2.代码


Redis与分布式锁

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

Jedis实现

1.RedisConfig

        <dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>3.6.3</version></dependency>

# Redis服务器地址
redis.host=127.0.0.1
# Redis服务器连接端口
redis.port=6379
# Redis服务器连接密码(默认为空)
redis.password=null
redis.timeout=30000
# 连接池最大连接数(使用负值表示没有限制)
redis.maxTotal=30
# 连接池中的最大空闲连接
redis.maxIdle=10
redis.numTestsPerEvictionRun=1024
redis.timeBetweenEvictionRunsMillis=30000
redis.minEvictableIdleTimeMillis=1800000
redis.softMinEvictableIdleTimeMillis=10000
# 连接池最大阻塞等待时间(使用负值表示没有限制)
redis.maxWaitMillis=1500
redis.testOnBorrow=true
redis.testWhileIdle=true
redis.blockWhenExhausted=false
redis.JmxEnabled=true

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;@Configuration
@PropertySource("classpath:application.properties")
public class RedisConfig {@Value("${redis.host}")private String host;@Value("${redis.port}")private int port;@Value("${redis.timeout}")private int timeout;@Value("${redis.maxIdle}")private int maxIdle;@Value("${redis.maxWaitMillis}")private int maxWaitMillis;@Value("${redis.blockWhenExhausted}")private Boolean blockWhenExhausted;@Value("${redis.JmxEnabled}")private Boolean JmxEnabled;@Beanpublic JedisPool jedisPoolFactory() {JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();jedisPoolConfig.setMaxIdle(maxIdle);jedisPoolConfig.setMaxWaitMillis(maxWaitMillis);// 连接耗尽时是否阻塞, false报异常,true阻塞直到超时, 默认truejedisPoolConfig.setBlockWhenExhausted(blockWhenExhausted);// 是否启用pool的jmx管理功能, 默认truejedisPoolConfig.setJmxEnabled(JmxEnabled);JedisPool jedisPool = new JedisPool(jedisPoolConfig, host, port, timeout);return jedisPool;}
}

2.RedisDistLock


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.params.SetParams;import java.util.Arrays;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;/*** 分布式锁的实现*/
@Component
public class RedisDistLock implements Lock {private final static int LOCK_TIME = 5*1000;//失效时间private final static String RS_DISTLOCK_NS = "tdln:"; //加锁的key的前缀/*if redis.call('get',KEYS[1])==ARGV[1] thenreturn redis.call('del', KEYS[1])else return 0 end*///释放锁的时候,确保原子。lua脚本:确保  释放锁的线程就是加锁的线程,不能被线程的线程无脑调用释放private final static String RELEASE_LOCK_LUA ="if redis.call('get',KEYS[1])==ARGV[1] then\n" +"        return redis.call('del', KEYS[1])\n" +"    else return 0 end";/*保存每个线程的独有的ID值*/private ThreadLocal<String> lockerId = new ThreadLocal<>();/*解决锁的重入*/private Thread ownerThread;private String lockName = "lock";@Autowiredprivate JedisPool jedisPool;public String getLockName() {return lockName;}public void setLockName(String lockName) {this.lockName = lockName;}public Thread getOwnerThread() {return ownerThread;}public void setOwnerThread(Thread ownerThread) {//加锁成功,就会把抢到锁的线程进行保存this.ownerThread = ownerThread;}@Overridepublic void lock() { //redis的分布式锁while(!tryLock()){try {Thread.sleep(100); //每隔100ms 都会去尝试加锁} catch (InterruptedException e) {e.printStackTrace();}}}@Overridepublic void lockInterruptibly() throws InterruptedException {throw new UnsupportedOperationException("不支持可中断获取锁!");}@Overridepublic boolean tryLock() {Thread t = Thread.currentThread();if(ownerThread==t){/*说明本线程持有锁*/return true;}else if(ownerThread!=null){/*本进程里有其他线程持有分布式锁*/return false;}Jedis jedis = jedisPool.getResource();try {String id = UUID.randomUUID().toString();SetParams params = new SetParams();params.px(LOCK_TIME);params.nx();synchronized (this){/*线程们,本地抢锁*/if((ownerThread==null)&&"OK".equals(jedis.set(RS_DISTLOCK_NS+lockName,id,params))){lockerId.set(id);setOwnerThread(t);return true;}else{return false;}}} catch (Exception e) {throw new RuntimeException("分布式锁尝试加锁失败!");} finally {jedis.close();}}@Overridepublic boolean tryLock(long time, TimeUnit unit) throws InterruptedException {throw new UnsupportedOperationException("不支持等待尝试获取锁!");}@Overridepublic void unlock() {if(ownerThread!=Thread.currentThread()) {throw new RuntimeException("试图释放无所有权的锁!");}Jedis jedis = null;try {jedis = jedisPool.getResource();Long result = (Long)jedis.eval(RELEASE_LOCK_LUA,Arrays.asList(RS_DISTLOCK_NS+lockName),Arrays.asList(lockerId.get()));if(result.longValue()!=0L){System.out.println("Redis上的锁已释放!");}else{System.out.println("Redis上的锁释放失败!");}} catch (Exception e) {throw new RuntimeException("释放锁失败!",e);} finally {if(jedis!=null) jedis.close();lockerId.remove();setOwnerThread(null);System.out.println("本地锁所有权已释放!");}}@Overridepublic Condition newCondition() {throw new UnsupportedOperationException("不支持等待通知操作!");}}

3.应用

在这里插入图片描述

4.加上看门狗逻辑 RedisDistLockWithDog

  1. 看门狗线程启动
  2. 通过delayDog 避免无谓的轮询,减少看门狗线程的轮序次数 阻塞延迟队列
    往延迟阻塞队列中加入元素(让看门口可以在过期之前一点点的时间去做锁的续期)
  3. 续锁逻辑:判断是持有锁的线程才能续锁
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.params.SetParams;import javax.annotation.PreDestroy;
import java.util.Arrays;
import java.util.UUID;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;/*** 分布式锁,附带看门狗线程的实现:加锁,保持锁1秒*/
@Component
public class RedisDistLockWithDog implements Lock {private final static int LOCK_TIME = 1*1000;private final static String LOCK_TIME_STR = String.valueOf(LOCK_TIME);private final static String RS_DISTLOCK_NS = "tdln2:";/*if redis.call('get',KEYS[1])==ARGV[1] thenreturn redis.call('del', KEYS[1])else return 0 end*/private final static String RELEASE_LOCK_LUA ="if redis.call('get',KEYS[1])==ARGV[1] then\n" +"        return redis.call('del', KEYS[1])\n" +"    else return 0 end";/*还有并发问题,考虑ThreadLocal*/private ThreadLocal<String> lockerId = new ThreadLocal<>();private Thread ownerThread;private String lockName = "lock";@Autowiredprivate JedisPool jedisPool;public String getLockName() {return lockName;}public void setLockName(String lockName) {this.lockName = lockName;}public Thread getOwnerThread() {return ownerThread;}public void setOwnerThread(Thread ownerThread) {this.ownerThread = ownerThread;}@Overridepublic void lock() {while(!tryLock()){try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}}}@Overridepublic void lockInterruptibly() throws InterruptedException {throw new UnsupportedOperationException("不支持可中断获取锁!");}@Overridepublic boolean tryLock() {Thread t=Thread.currentThread();/*说明本线程正在持有锁*/if(ownerThread==t) {return true;}else if(ownerThread!=null){/*说明本进程中有别的线程正在持有分布式锁*/return false;}Jedis jedis = null;try {jedis = jedisPool.getResource();/*每一个锁的持有人都分配一个唯一的id,也可采用snowflake算法*/String id = UUID.randomUUID().toString();SetParams params = new SetParams();params.px(LOCK_TIME); //加锁时间1sparams.nx();synchronized (this){if ((ownerThread==null)&&"OK".equals(jedis.set(RS_DISTLOCK_NS+lockName,id,params))) {lockerId.set(id);setOwnerThread(t);if(expireThread == null){//看门狗线程启动expireThread = new Thread(new ExpireTask(),"expireThread");expireThread.setDaemon(true);expireThread.start();}//往延迟阻塞队列中加入元素(让看门口可以在过期之前一点点的时间去做锁的续期)delayDog.add(new ItemVo<>((int)LOCK_TIME,new LockItem(lockName,id)));System.out.println(Thread.currentThread().getName()+"已获得锁----");return true;}else{System.out.println(Thread.currentThread().getName()+"无法获得锁----");return false;}}} catch (Exception e) {throw new RuntimeException("分布式锁尝试加锁失败!",e);} finally {jedis.close();}}@Overridepublic boolean tryLock(long time, TimeUnit unit) throws InterruptedException {throw new UnsupportedOperationException("不支持等待尝试获取锁!");}@Overridepublic void unlock() {if(ownerThread!=Thread.currentThread()) {throw new RuntimeException("试图释放无所有权的锁!");}Jedis jedis = null;try {jedis = jedisPool.getResource();Long result = (Long)jedis.eval(RELEASE_LOCK_LUA,Arrays.asList(RS_DISTLOCK_NS+lockName),Arrays.asList(lockerId.get()));System.out.println(result);if(result.longValue()!=0L){System.out.println("Redis上的锁已释放!");}else{System.out.println("Redis上的锁释放失败!");}} catch (Exception e) {throw new RuntimeException("释放锁失败!",e);} finally {if(jedis!=null) jedis.close();lockerId.remove();setOwnerThread(null);}}@Overridepublic Condition newCondition() {throw new UnsupportedOperationException("不支持等待通知操作!");}/*看门狗线程*/private Thread expireThread;//通过delayDog 避免无谓的轮询,减少看门狗线程的轮序次数   阻塞延迟队列   刷1  没有刷2private static DelayQueue<ItemVo<LockItem>> delayDog = new DelayQueue<>();//续锁逻辑:判断是持有锁的线程才能续锁private final static String DELAY_LOCK_LUA ="if redis.call('get',KEYS[1])==ARGV[1] then\n" +"        return redis.call('pexpire', KEYS[1],ARGV[2])\n" +"    else return 0 end";private class ExpireTask implements Runnable{@Overridepublic void run() {System.out.println("看门狗线程已启动......");while(!Thread.currentThread().isInterrupted()) {try {LockItem lockItem = delayDog.take().getData();//只有元素快到期了才能take到  0.9sJedis jedis = null;try {jedis = jedisPool.getResource();Long result = (Long)jedis.eval(DELAY_LOCK_LUA,Arrays.asList(RS_DISTLOCK_NS+lockItem.getKey ()),Arrays.asList(lockItem.getValue(),LOCK_TIME_STR));if(result.longValue()==0L){System.out.println("Redis上的锁已释放,无需续期!");}else{delayDog.add(new ItemVo<>((int)LOCK_TIME,new LockItem(lockItem.getKey(),lockItem.getValue())));System.out.println("Redis上的锁已续期:"+LOCK_TIME);}} catch (Exception e) {throw new RuntimeException("锁续期失败!",e);} finally {if(jedis!=null) jedis.close();}} catch (InterruptedException e) {System.out.println("看门狗线程被中断");break;}}System.out.println("看门狗线程准备关闭......");}}//    @PostConstruct
//    public void initExpireThread(){
//
//    }@PreDestroypublic void closeExpireThread(){if(null!=expireThread){expireThread.interrupt();}}
}

redisson实现

  • github项目 redisson 实现分布式锁和同步器,可以直接调用

https://github.com/redisson/redisson/
在这里插入图片描述

1.依赖

       <!--引入Redis--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-redis</artifactId><version>1.4.2.RELEASE</version></dependency><dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.12.3</version></dependency>

2.代码

import java.util.concurrent.TimeUnit;/*** Redisson分布式锁接口* <p>* RLock的实现有可重入非公平锁(RedissonLock)、可重入公平锁(RedissonFairLock)、联锁(RedissonMultiLock)、 红锁(RedissonRedLock)、 读锁(RedissonReadLock)、 写锁(RedissonWriteLock)等*/
public interface DistributedLock {void lock(String lockKey);void lock(String lockKey, long timeout);void lock(String lockKey, TimeUnit unit, long timeout);boolean tryLock(String lockKey, TimeUnit unit, long leaseTime);boolean tryLock(String lockKey, TimeUnit unit, long waitTime, long leaseTime);boolean unlock(String lockKey);}
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.annotations.Result;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;/*** Redisson分布式锁实现-使用可重入非公平锁(RedissonLock)*/
@Slf4j
@Component
public class RedisDistributedLock implements DistributedLock {@Resourceprivate RedissonClient redissonClient;public void lock(String lockKey) {RLock lock = this.redissonClient.getLock(lockKey);lock.lock();}public void lock(String lockKey, long timeout) {RLock lock = this.redissonClient.getLock(lockKey);lock.lock(timeout, TimeUnit.SECONDS);}public void lock(String lockKey, TimeUnit unit, long timeout) {RLock lock = this.redissonClient.getLock(lockKey);lock.lock(timeout, unit);}public boolean tryLock(String lockKey, TimeUnit unit, long leaseTime) {RLock lock = this.redissonClient.getLock(lockKey);try {return lock.tryLock(5L, leaseTime, unit);} catch (InterruptedException var7) {return false;}}public boolean tryLock(String lockKey, TimeUnit unit, long waitTime, long leaseTime){RLock lock = redissonClient.getLock(lockKey);try {return lock.tryLock(waitTime, leaseTime, unit);} catch (InterruptedException e) {return false;}}public boolean unlock(String lockKey){RLock lock = redissonClient.getLock(lockKey);try {if (lock.isLocked()) {lock.unlock();log.info("释放锁[{}]成功",lockKey);}return true;} catch (IllegalMonitorStateException localIllegalMonitorStateException) {log.error("释放锁[{}]失败",lockKey,localIllegalMonitorStateException);return false;}}}
  private static final String CHECK_HEART_BEAT_TASK = "checkHeartbeatTask";@Autowiredprivate DistributedLock distributedLock;try {startTime = System.currentTimeMillis();boolean lock = distributedLock.tryLock(CHECK_HEART_BEAT_TASK);//log.info("检查心跳任务获取锁状态:{}", lock);if (lock){handleCheckHeartbeatTask();} else {log.info("检查心跳任务获取分布式锁失败!");}} catch (Exception e) {log.error("检查心跳任务异常中断 {}", e.getMessage());} finally {if (distributedLock.unlock(CHECK_HEART_BEAT_TASK)) {//log.info("检查心跳任务释放分布式锁!");} else {log.warn("检查心跳任务释放分布式锁失败!");}Long endTime = System.currentTimeMillis();Long costTime = endTime - startTime;if (costTime.longValue() > heartbeatCheckTime.longValue()) {log.info("检查心跳任务耗时:{}ms", costTime);}}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/48698.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

VScode通过Graphviz插件和dot文件绘制层次图,导出svg

1、安装插件 在VScode中安装Graphviz Interactive Preview插件&#xff0c;参考。 2、创建dot文件 在本地创建一个后缀为dot的文件&#xff0c;如test.dot&#xff0c;并写入以下内容&#xff1a; digraph testGraph {label "层次图";node [shape square; widt…

一文读懂英伟达A800的性能及应用场景

随着人工智能&#xff08;AI&#xff09;和高性能计算&#xff08;HPC&#xff09;领域的快速发展&#xff0c;对处理器的性能要求日益提高。英伟达&#xff08;NVIDIA&#xff09;作为全球领先的图形处理器&#xff08;GPU&#xff09;和人工智能技术公司&#xff0c;不断推出…

全国区块链职业技能大赛国赛考题区块链产品需求分析与方案设计

任务1-1:区块链产品需求分析与方案设计 本任务需要依据项目背景完成需求分析与方案设计,具体要求如下: 依据给定区块链食品溯源系统的业务架构图,对考题进行业务分析,尽可能多的去考虑一个业务系统所需要的模块,使用Visio或思维导图工具展现本系统的基本设计概念和处理流…

python基础语法 007 文件操作-2文件支持模式文件的内置函数

1.3 文件支持的模式 模式含义ropen a file for reading(default)wopen a file for writing,creates a new file if it does not exist or truncates the file if it exists x open a file foe exclusive creation. if the file already exists, the operation fails.独创模式&…

约束

概述 概念 约束是作用于表中字段上的规则&#xff0c;用于限制存储在表中的数据。 目的 保证数据库中数据的正确、有效性和完整性。 分类 【注意】约束是作用于表中字段上的&#xff0c;可以在创建表/修改表的时候添加约束。 约束演示 根据需求&#xff0c;完成表结构的…

Docker核心技术:应用架构演进

云原生学习路线导航页&#xff08;持续更新中&#xff09; 本文是 Docker核心技术 系列文章&#xff1a;应用架构演进&#xff0c;其他文章快捷链接如下&#xff1a; 应用架构演进&#xff08;本文&#xff09;容器技术要解决哪些问题Docker的基本使用Docker是如何实现的 1.1.架…

blender使用(三)常用建模操作及修改器

1&#xff0c;挤出图形 tab编辑模式&#xff0c;选中一个点/线/面&#xff0c;按键E&#xff0c;可以挤出对应的图形。选中点会挤出一条线&#xff0c;线会挤出一个面&#xff0c;面挤出体 2&#xff0c;倒角 选中一个边后&#xff0c;ctrlB &#xff0c;拖动鼠标是倒角范围&am…

数据结构 day3

目录 思维导图&#xff1a; 学习内容&#xff1a; 1. 顺序表 1.1 概念 1.2 有关顺序表的操作 1.2.1 创建顺序表 1.2.2 顺序表判空和判断满 1.2.3 向顺序表中添加元素 1.2.4 遍历顺序表 1.2.5 顺序表按位置进行插入元素 1.2.6 顺序表任意位置删除元素 1.2.7 按值进…

智能取纸机,帮助移动公厕,无人值守降低运营成本

在快节奏的城市生活中&#xff0c;移动公厕作为临时性或应急性的公共卫生设施&#xff0c;扮演着不可或缺的角色。然而&#xff0c;传统移动公厕的管理面临着诸多挑战&#xff0c;尤其是纸巾供应与使用效率问题。近年来&#xff0c;智能取纸机的出现&#xff0c;为移动公厕的管…

好玩新游:辛特堡传说中文免费下载,Dungeons of Hinterberg 游戏分享

在游戏中&#xff0c;你将扮演Luisa&#xff0c;一个被现实生活拖得疲惫不堪的法律实习生。她决定暂时远离快节奏的公司生活&#xff0c;踏上征服辛特堡地下城的旅程…她会在第一天就被击退&#xff0c;还是能成为顶级猎魔人呢&#xff1f;只有一个办法可以找到答案... 体验刺激…

《Milvus Cloud向量数据库指南》——SPLADE:基于BERT的Learned稀疏向量技术深度解析

在自然语言处理(NLP)领域,随着深度学习技术的飞速发展,预训练语言模型如BERT(Bidirectional Encoder Representations from Transformers)已成为推动研究与应用进步的重要基石。BERT通过其强大的上下文感知能力,在多项NLP任务中取得了显著成效,尤其是在文本表示和语义理…

昇思25天学习打卡营第20天| GAN图像生成

GAN是一种特别酷的机器学习模型&#xff0c;它由两个部分组成&#xff1a;生成器和判别器。生成器的任务是制造假的图像&#xff0c;而判别器则要判断图像是真是假。这俩就像是在玩一个捉迷藏的游戏&#xff0c;生成器越做越好&#xff0c;判别器也越来越聪明。 想象一下&…

光耦合器技术的实际应用

光耦合器也称为光隔离器&#xff0c;是现代电子产品中的关键组件&#xff0c;可确保电路不同部分之间的信号完整性和隔离。它们使用光来传输电信号&#xff0c;提供电气隔离和抗噪性。 结构和功能 光耦合器通常由以下部分组成&#xff1a; 1.LED&#xff08;发光二极管&#…

yolov5进行识别安全帽

进行毕业设计 下载yolov5使用按照教程来进行就行注意事项&#xff08;有必要看看&#xff09;效果 总结 下载yolov5 地址是&#xff1a;https://github.com/ultralytics/yolov5 使用按照教程来进行就行 这里简单说一下&#xff1a; 下载需要的命令&#xff1a; pip install -…

前端vue框架的项目文件创建及常见Vue指令运用

前言 本文介绍前端Vue框架&#xff0c;先从npm工具创建的Vue项目开始&#xff0c;对项目结构的一些文件用途进行说明&#xff0c;随后对Vue文件编写所用的两种风格&#xff08;选项式API和组合式API风格&#xff09;做了区分&#xff0c;同时对编写代码中常见的生命周期钩子函…

【OpenREALM学习笔记:14】单目视觉SLAM方法在UAV影像上重建三维地形的思考

最近在学习SLAM技术与测绘三维影像重建的相关知识&#xff0c;结合自己的感受&#xff0c;撰写一下对于单目视觉SLAM利用无人机影像重建三维地形的一些看法。 1. 单目视觉SLAM系统在三维地形重建中所面临的挑战有哪些&#xff1f; 单目视觉SLAM众所周知的一个问题是&#xff…

C语言联合及枚举

一.联合体 1.联合体类型的声明 像结构体一样&#xff0c;联合体也是由一个或者多个成员构成&#xff0c;这些成员可以不同的类型,但是编译器只为最大的成员分配足够的内存空间。 联合体的特点是所有成员共用同一块内存空间。所以联合体也叫:共用体,给联合体其中一个成员赋值…

AVL树超详解上

前言 学习过了二叉树以及二叉搜索树后&#xff08;不了解二叉搜索树的朋友可以先看看这篇博客&#xff0c;二叉搜索树详解-CSDN博客&#xff09;&#xff0c;我们在一般情况下对于二叉搜索树的插入与查询时间复杂度都是O(lgN)&#xff0c;是十分快的&#xff0c;但是在一些特殊…

多维时序 | Transformer+BiLSTM多变量时间序列预测(Python)

目录 效果一览基本介绍程序设计参考资料 效果一览 基本介绍 多维时序 | TransformerBiLSTM多变量时间序列预测&#xff08;Python&#xff09; python代码&#xff0c;pytorch框架 程序设计 完整程序和数据获取方式私信博主回复多维时序 | TransformerBiLSTM多变量时间序列预…

Ubuntu 查询未更新的包 进行手动更新

lsb_release -a 查询ubuntu的版本号&#xff0c;我这边是20.04 apt list --upgradable 查询未更新的包 sudo apt --only-upgrade install php/focal 中间遇到输入选择&#xff0c;输入y即可