分布式锁实现方案-基于Redis实现的分布式锁

news2024/11/19 7:43:28

目录

一、基于Lua+看门狗实现

1.1 缓存实体

1.2 延迟队列存储实体

1.3 分布式锁RedisDistributedLockWithDog

1.4 看门狗线程续期

1.5 测试类

1.6 测试结果

1.7 总结

二、RedLock分布式锁

2.1 Redlock分布式锁简介

2.2 RedLock测试例子

2.3 RedLock 加锁核心源码分析

2.4 RedLock的问题与解决方案

2.4.1 问题

2.4.1.1 时钟偏移

2.4.1.2 单点故障

2.4.1.3 性能瓶颈

2.4.2 解决方案

2.4.2.1 引入重试机制

2.4.2.2 时钟校准

2.4.2.3 引入冗余节点

2.5 总结


一、基于Lua+看门狗实现

1.1 缓存实体

package com.ningzhaosheng.distributelock.redis;

/**
 * @author ningzhaosheng
 * @date 2024/4/18 15:37:16
 * @description 类说明:Redis的key-value结构
 */
public class LockItem {
    private final String key;
    private final String value;

    public LockItem(String key, String value) {
        this.key = key;
        this.value = value;
    }

    public String getKey() {
        return key;
    }

    public String getValue() {
        return value;
    }
}

1.2 延迟队列存储实体

package com.ningzhaosheng.distributelock.redis;

import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;

/**
 * @author ningzhaosheng
 * @date 2024/4/18 15:35:43
 * @description 说明:存放到延迟队列的元素,比标准的delay的实现要提前一点时间
 */
public class ItemVo<T> implements Delayed {
    /*到期时刻  20:00:35,234*/
    private long activeTime;
    /*业务数据,泛型*/
    private T data;

    /*传入的数值代表过期的时长,单位毫秒,需要乘1000转换为毫秒和到期时间
     * 同时提前100毫秒续期,具体的时间可以自己决定*/
    public ItemVo(long expirationTime, T data) {
        super();
        this.activeTime = expirationTime + System.currentTimeMillis() - 100;
        this.data = data;
    }

    public long getActiveTime() {
        return activeTime;
    }

    public T getData() {
        return data;
    }

    /**
     * 返回元素到激活时刻的剩余时长
     */
    public long getDelay(TimeUnit unit) {
        long d = unit.convert(this.activeTime
                - System.currentTimeMillis(), unit);
        return d;
    }

    /**
     * 按剩余时长排序
     */
    public int compareTo(Delayed o) {
        long d = (getDelay(TimeUnit.MILLISECONDS)
                - o.getDelay(TimeUnit.MILLISECONDS));
        if (d == 0) {
            return 0;
        } else {
            if (d < 0) {
                return -1;
            } else {
                return 1;
            }
        }
    }
}

1.3 分布式锁RedisDistributedLockWithDog

package com.ningzhaosheng.distributelock.redis;

import redis.clients.jedis.Jedis;
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;

/**
 * @author ningzhaosheng
 * @date 2024/4/18 15:38:07
 * @description Redis分布式锁,附带看门狗线程的实现:加锁,保持锁1秒
 */
public class RedisDistributedLockWithDog implements Lock {
    private final static int LOCK_TIME = 1 * 1000;
    private final static String RS_DISTLOCK_NS = "tdln2:";
    /*
     if redis.call('get',KEYS[1])==ARGV[1] then
        return 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";


    private Jedis jedis = null;

    // 看门狗线程
    private Thread expireThread;
    private WatchDogThead watchDogThead;

    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;
    }

    public RedisDistributedLockWithDog(Jedis jedis) {
        this.jedis = jedis;
        watchDogThead = new WatchDogThead(jedis);
        closeExpireThread();
    }

    @Override
    public void lock() {
        while (!tryLock()) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {
        throw new UnsupportedOperationException("不支持可中断获取锁!");
    }

    @Override
    public boolean tryLock() {
        Thread t = Thread.currentThread();
        /*说明本线程正在持有锁*/
        if (ownerThread == t) {
            return true;
        } else if (ownerThread != null) {/*说明本进程中有别的线程正在持有分布式锁*/
            return false;
        }
        try {
            /*每一个锁的持有人都分配一个唯一的id,也可采用snowflake算法*/
            String id = UUID.randomUUID().toString();

            SetParams params = new SetParams();
            params.px(LOCK_TIME); //加锁时间1s
            params.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(watchDogThead, "expireThread");
                        expireThread.setDaemon(true);
                        expireThread.start();
                    }
                    //往延迟阻塞队列中加入元素(让看门口可以在过期之前一点点的时间去做锁的续期)
                    watchDogThead.addDelayDog(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);
        }
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        throw new UnsupportedOperationException("不支持等待尝试获取锁!");
    }

    @Override
    public void unlock() {
        Thread t = Thread.currentThread();
        if (ownerThread != t) {
            throw new RuntimeException("试图释放无所有权的锁!");
        }
        try {
            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(t.getName()+" Redis上的锁已释放!");
            } else {
                System.out.println(t.getName()+" Redis上的锁释放失败!");
            }
        } catch (Exception e) {
            throw new RuntimeException("释放锁失败!", e);
        } finally {
            lockerId.remove();
            setOwnerThread(null);
            // 关闭看门狗
            closeExpireThread();
            // 关闭jedis连接
            jedis.close();
        }
    }

    @Override
    public Condition newCondition() {
        throw new UnsupportedOperationException("不支持等待通知操作!");
    }

    /**
     * 中断看门狗线程
     */
    public void closeExpireThread() {
        if (null != expireThread) {
            expireThread.interrupt();
        }
    }
}

1.4 看门狗线程续期

package com.ningzhaosheng.distributelock.redis;

import redis.clients.jedis.Jedis;

import java.util.Arrays;
import java.util.concurrent.DelayQueue;

/**
 * @author ningzhaosheng
 * @date 2024/4/18 16:18:10
 * @description 看门狗线程
 */
public class WatchDogThead implements Runnable {
    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:";
    /*看门狗线程*/
    private Thread expireThread;
    //通过delayDog 避免无谓的轮询,减少看门狗线程的轮序次数   阻塞延迟队列   刷1  没有刷2
    private 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";
    // 客户端
    Jedis jedis = null;

    public WatchDogThead(Jedis jedis) {
        this.jedis = jedis;
    }

    public void addDelayDog(ItemVo<LockItem> itemItemVo) {
        delayDog.add(itemItemVo);
    }

    @Override
    public void run() {
        System.out.println("看门狗线程已启动......");
        while (!Thread.currentThread().isInterrupted()) {
            try {
                LockItem lockItem = delayDog.take().getData();//只有元素快到期了才能take到  0.9s
                try {
                    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);
                }
            } catch (InterruptedException e) {
                System.out.println("看门狗线程被中断");
                break;
            }
        }
        System.out.println("看门狗线程准备关闭......");
    }

}

1.5 测试类

package com.ningzhaosheng.distributelock.redis;

import com.ningzhaosheng.distributelock.zookeeper.OrderServiceHandle;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisSentinelPool;

import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.locks.Lock;

/**
 * @author ningzhaosheng
 * @date 2024/4/19 8:07:38
 * @description redis看门狗分布式锁测试类
 */
public class TestRedisDistributedLockWithDog {
    private static final String REDIS_ADDRESS1 = "192.168.31.167:26379";
    private static final String REDIS_ADDRESS2 = "192.168.31.215:26379";
    private static final String REDIS_ADDRESS3 = "192.168.31.154:26379";
    public static void main(String[] args) {
        Set<String> sentinels = new HashSet<String>();
        sentinels.add(REDIS_ADDRESS1);
        sentinels.add(REDIS_ADDRESS2);
        sentinels.add(REDIS_ADDRESS3);
        // Redis主服务器的名字
        String masterName = "mymaster";

        // Redis服务器的密码
        String password = "xiaoning";

        try {

            int NUM = 10;
            CountDownLatch cdl = new CountDownLatch(NUM);
            for (int i = 1; i <= NUM; i++) {
                // 创建JedisSentinelPool
                JedisSentinelPool pool = new JedisSentinelPool(masterName, sentinels, password);
                // 从池中获取Jedis实例
                Jedis jedis  = pool.getResource();
                // 按照线程数迭代实例化线程
                Lock lock = new RedisDistributedLockWithDog(jedis);
                new Thread(new OrderServiceHandle(cdl, lock)).start();
                // 创建一个线程,倒计数器减1
                cdl.countDown();
                pool.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

1.6 测试结果

1.7 总结

以上基于看门狗机制实现的Redis锁在对高并发要求不高的场景下可以使用,但是其实它还是有问题的,在主从架构模式下,可能会导致两个线程同时获取到锁得问题出现:

  •  线程A从主redis中请求一个分布式锁,获取锁成功;
  • 从redis准备从主redis同步锁相关信息时,主redis突然发生宕机,锁丢失了;
  • 触发从redis升级为新的主redis;线程B从继任主redis的从redis上申请一个分布式锁,此时也能获取锁成功;
  • 导致,同一个分布式锁,被两个客户端同时获取,没有保证独占使用特性;

当对高可用有要求的场景,这种方式就不合适了,那么在高并发、高可靠场景下,我们该如何基于Redis 实现分布式锁呢?得接着往下看!

二、RedLock分布式锁

2.1 Redlock分布式锁简介

Redlock是由Redis的作者Salvatore Sanfilippo提出的一种分布式锁算法,它利用Redis的特性来实现分布式锁。Redlock算法的核心思想是通过在多个Redis实例上创建相同的分布式锁,以保证锁的可靠性。Redlock算法通过在Redis中设置一个唯一的键值对来表示锁的存在,其他线程或进程需要通过竞争来获取这个锁。

官网wiki:8. 分布式锁和同步器 · redisson/redisson Wiki · GitHub

2.2 RedLock测试例子

package com.ningzhaosheng.distributelock.redis.redisson;

import org.redisson.Redisson;
import org.redisson.RedissonRedLock;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;

import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

/**
 * @author ningzhaosheng
 * @date 2024/6/22 15:57:17
 * @description
 */
public class TestRedLockByRedisson {
    public static void main(String[] args) {
         String RS_DISTLOCK_NS = "tdln2:";
         String lockName = "lock";
        Config config = new Config();

        config.useSentinelServers()
                .addSentinelAddress("redis://192.168.31.215:26379","redis://192.168.31.167:26379","redis://192.168.31.154:26379")
                .setMasterName("mymaster")
                .setPassword("xiaoning");
        RedissonClient redisson = Redisson.create(config);

        try {
    
            for (int i = 1; i <= 10; i++) {
                // 生成8位随机数
                Random random = new Random();
                // 创建RedLock
                RLock rLock = redisson.getLock(RS_DISTLOCK_NS+lockName+ random.nextInt(99999999));
                RedissonRedLock rdLock = new RedissonRedLock(rLock);
                boolean islock = rdLock.tryLock(500,3000,TimeUnit.MILLISECONDS);
                if(islock){
                    System.out.println("执行业务啦!======================");
                }else{
                    System.out.println("获取锁失败!======================");
                }
                rdLock.unlock();

            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        redisson.shutdown();
    }
}

测试结果:

2.3 RedLock 加锁核心源码分析

这里我就以tryLock()为例,分析下RedLock 的源码:

 public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {
        long newLeaseTime = -1L;
        if (leaseTime != -1L) {
            if (waitTime == -1L) {
                newLeaseTime = unit.toMillis(leaseTime);
            } else {
                newLeaseTime = unit.toMillis(waitTime) * 2L;
            }
        }

        long time = System.currentTimeMillis();
        long remainTime = -1L;
        if (waitTime != -1L) {
            remainTime = unit.toMillis(waitTime);
        }

        long lockWaitTime = this.calcLockWaitTime(remainTime);
        // 允许加锁失败的节点数量(N-(N/2+1))
        int failedLocksLimit = this.failedLocksLimit();
        List<RLock> acquiredLocks = new ArrayList(this.locks.size());
        ListIterator iterator = this.locks.listIterator();
        // 遍历所有节点,通过EVAL命令执行lua脚本加锁
        while(iterator.hasNext()) {
            // 迭代获取Redis实例
            RLock lock = (RLock)iterator.next();

            boolean lockAcquired;
            // 尝试加锁,调用lock.tryLock()方法
            try {
                if (waitTime == -1L && leaseTime == -1L) {
                    lockAcquired = lock.tryLock();
                } else {
                    long awaitTime = Math.min(lockWaitTime, remainTime);
                    // 尝试加锁
                    lockAcquired = lock.tryLock(awaitTime, newLeaseTime, TimeUnit.MILLISECONDS);
                }
            } catch (RedisResponseTimeoutException var21) {
                // 如果抛出异常,为了防止加锁成功,但是响应失败,需要解锁所有节点,同时返回加锁失败状态
                this.unlockInner(Arrays.asList(lock));
                lockAcquired = false;
            } catch (Exception var22) {
                lockAcquired = false;
            }

            if (lockAcquired) {
                // 如果加锁成功,把锁加到以获得锁集合中
                acquiredLocks.add(lock);
            } else {
                //计算已经申请锁失败的节点是否已经到达允许加锁失败节点个数限制 (即:N-(N/2+1));如果已经到达,就认定最终申请锁失败,则没有必要继续从后面的节点申请了因为 Redlock 算法要求至少N/2+1 个节点都加锁成功,才算最终的锁申请成功
                if (this.locks.size() - acquiredLocks.size() == this.failedLocksLimit()) {
                    break;
                }

                if (failedLocksLimit == 0) {
                    this.unlockInner(acquiredLocks);
                    if (waitTime == -1L) {
                        return false;
                    }

                    failedLocksLimit = this.failedLocksLimit();
                    acquiredLocks.clear();

                    while(iterator.hasPrevious()) {
                        iterator.previous();
                    }
                } else {
                    --failedLocksLimit;
                }
            }
            // 计算 目前从各个节点获取锁已经消耗的总时间,如果已经等于最大等待时间,则认定最终申请锁失败,返回false
            if (remainTime != -1L) {
                remainTime -= System.currentTimeMillis() - time;
                time = System.currentTimeMillis();
                if (remainTime <= 0L) {
                    this.unlockInner(acquiredLocks);
                    return false;
                }
            }
        }
        // 重置锁过期时间
        if (leaseTime != -1L) {
            acquiredLocks.stream().map((l) -> {
                return (RedissonLock)l;
            }).map((l) -> {
                return l.expireAsync(unit.toMillis(leaseTime), TimeUnit.MILLISECONDS);
            }).forEach((f) -> {
                f.syncUninterruptibly();
            });
        }
        // 如果逻辑正常执行完则认为最终申请锁成功,返回true
        return true;
    }

2.4 RedLock的问题与解决方案

2.4.1 问题

2.4.1.1 时钟偏移

Redlock算法中需要使用到各个Redis实例的系统时钟来实现锁的过期时间控制。然而,不同Redis实例之间的时钟可能存在偏移,导致锁的过期时间计算错误,从而导致锁的误释放或者死锁的发生。

2.4.1.2 单点故障

Redlock算法的可靠性依赖于多个Redis实例之间的协作。如果其中一个Redis实例发生故障,可能会导致整个系统的分布式锁服务不可用。

2.4.1.3 性能瓶颈

RedLock需要访问多个实例,网络延迟可能导致某些线程获取锁的时间较长,会增加网络带宽的压力。此外,每个实例都需要对锁进行检查和定时删除操作,也会影响Redis的性能。

2.4.2 解决方案

针对上述问题,我们可以采取以下解决方案来提高Redlock分布式锁在高并发环境下的性能和可靠性:

2.4.2.1 引入重试机制

为了应对网络延迟带来的竞争问题,我们可以在获取锁失败后进行重试。通过设定适当的重试次数和重试间隔,可以减少因网络延迟导致的锁竞争失败的情况。但是该方案还是解决不了性能瓶颈问题,性能瓶颈问题是架构方案决定的。

2.4.2.2 时钟校准

为了解决时钟偏移问题,我们可以通过定期校准各个Redis实例的系统时钟来保证它们之间的一致性。可以使用NTP协议或者其他时间同步机制来实现时钟校准。

2.4.2.3 引入冗余节点

为了避免单点故障带来的问题,我们可以引入冗余节点来提高系统的可用性。通过在系统中增加多个Redis实例作为备份节点,可以在主节点故障时快速切换到备份节点,保证分布式锁服务的可用性。

2.5 总结

基于Redis实现分布式锁的方案,他的实现方式有两种,一直是基于setnx指令+lua脚本+看门狗进程,实现在Redis单实例下的分布式锁方案,这种方式在主从模式下,当主节点宕机,主从切换的时候,会有获取锁失败的情况或者会有多个客户端同时获取到锁的情况,为了提高分布式锁的可用性,则出现了改进后的RedLock锁,RedLock锁是基于多实例加锁(即:N/2+1),同时给N/2+1个实例加锁成功,才算获取锁成功。但是他的问题也很明显,具体如前文所述。所以在进行技术方案选型的时候,你要结合你的场景,明确选用分布式锁时需要用途:

  1. 如果你只考虑提升加锁效率问题,那么选用一台高性能的服务器,部署单实例,采用基于setnx指令+lua脚本+看门狗进程的方案就够了。
  2. 如果你考虑高可用,想避免单点问题,并且你的部署架构是主从的,或者集群模式的,那么你可以选择使用RedLock,但是你就不得不考虑因此带来的复杂性和各种问题。
  3. 如果你要保证绝对的数据一致性,那么不好意思,Redis实现分布式锁方案可能不适合你,因为从架构的角度来说,Redis的架构属于AP架构,它保证的是可用性;这时,你可以考虑使用Zookeeper实现分布式锁方案,因为它的架构属于CP架构,保证的是一致性;但是Zookeeper也有问题,就是它的性能没有Redis高。

那么在并发量大,可靠性高的场景下,我们最终应该怎么技术选型呢,我的建议是避免使用分布式锁,可以通过串行化来解决,比如同步串行化,异步串行化等方案。这里暂时不对串行化设计展开论述,后续有时间我会在另外的主题文章进行分享。

好了,本次内容就分享到这,欢迎关注本博主。如果有帮助到大家,欢迎大家点赞+关注+收藏,有疑问也欢迎大家评论留言!

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

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

相关文章

OpenAI CTO谈GPT-5将达博士生智力水平;斯坦福评估排名前十两款来自中国

&#x1f989; AI新闻 &#x1f680; OpenAI CTO谈GPT-5将达博士生智力水平 摘要&#xff1a;美国达特茅斯工程学院采访了OpenAI首席技术官米拉・穆拉蒂&#xff0c;她表示GPT-4的智力相当于高中生&#xff0c;而GPT-5将在一年半后发布&#xff0c;预计达到博士生水平。穆拉蒂…

【Unity Shader】Alpha Blend(Alpha混合)的概念及其使用示例

在Unity和图形编程中&#xff0c;Alpha Blend&#xff08;也称为Alpha混合&#xff09;是一种用于处理像素透明度的技术。它允许像素与背景像素融合&#xff0c;从而实现透明或半透明的效果。Alpha Blend在渲染具有透明度的物体&#xff08;如窗户、玻璃、水、雾等&#xff09;…

闷热烦躁,精神倦怠?3个食疗方,5款好物,助您清凉度夏~

夏日&#xff0c;阳气旺盛&#xff0c;万物繁荣秀丽&#xff0c;但赤日炎炎似火烧&#xff0c;人们容易闷热、食欲不佳、四肢倦怠、萎靡不振等&#xff0c;再加上蚊虫多&#xff0c;更是让人烦躁不安&#xff01; 5款清凉好物 赶走蚊虫 天气炎热&#xff0c;蚊子、小虫都来了…

webpack【实用教程】

基础配置 配置的拆分和合并 通常 webpack 的配置文件会有3个 webpack.common.js 公共配置&#xff08;会被另外两个配置文件导入并合并&#xff09;webpack.dev.js 开发环境的配置webpack.prod.js 生产环境的配置 开发环境的本地服务 在 webpack.dev.js 中配置 devServer:…

springboot加载bean的方式

在SpringBoot的大环境下&#xff0c;基本上很少使用之前的xml配置Bean&#xff0c;主要是因为这种方式不好维护而且也不够方便。 springboto注入bean主要采用下图几种方式&#xff0c; 1、注解装配Bean 1、使用Component等派生注解 只要在类上加类上加 Component 注解即可,该…

Kotlin 运行代码片段多种方式

目录 场景描述 一、Scratch files and worksheets in the IDE 1、Scratch files(草稿文件) 特点&#xff1a; Scratch files文件创建步骤&#xff1a; 功能解释&#xff1a; Scratch Buffer笔记文件&#xff1a; 2、Worksheets(工单) 1&#xff09;、创建方式不同。 …

基于堆叠长短期记忆网络 Stacked LSTM 预测A股股票价格走势

前言 系列专栏:【深度学习&#xff1a;算法项目实战】✨︎ 涉及医疗健康、财经金融、商业零售、食品饮料、运动健身、交通运输、环境科学、社交媒体以及文本和图像处理等诸多领域&#xff0c;讨论了各种复杂的深度神经网络思想&#xff0c;如卷积神经网络、循环神经网络、生成对…

思看科技冲刺上市疑云:募资用途遭强烈质疑,IPO前突击分红

近日&#xff0c;思看科技&#xff08;杭州&#xff09;股份有限公司&#xff08;下称“思看科技”&#xff09;已更新提交2023年最新财务资料&#xff0c;重启科创板IPO进程。贝多财经了解到&#xff0c;思看科技的上市申请于2023年6月获上交所受理&#xff0c;目前已进入问询…

简鹿文件批量重命名:一款文件批量改名高手都在用的工具

作为 IT 行业的搬砖民工&#xff0c;互联网的数据量爆炸性增长&#xff0c;文件管理成为了一项日益重要的任务。"简鹿文件批量重命名"应运而生&#xff0c;旨在为用户提供一个高效、灵活的解决方案&#xff0c;以应对繁琐的文件命名、排序、创建及属性修改等挑战。 这…

足底筋膜炎吃什么药最管用

足底筋膜炎在使用药物后未见明显改善&#xff0c;但通过使用“古顺、敷堂、筋膜”贴后病情得到了缓解。按疗程使用自愈了&#xff0c;这款筋膜贴通过其药物成分渗透到组织中&#xff0c;从根本上消除炎症&#xff0c;从而快速缓解症状&#xff0c;足底筋膜炎得到了明显的改善。…

【思科】IPv6 过渡技术 - 6to4隧道

【思科】IPv6 过渡技术 - 6to4隧道 实验要求实现思路6 to 4 特点注意点IPv4 转 IPv6 格式小技巧 配置R1基础配置OSPFv3 局域网可达 R2基础配置局域网环境(OSPFv3)&#xff1a;IPv6 网络6 to 4 隧道 R3R4基础配置局域网环境(OSPFv3)&#xff1a;IPv6 网络6 to 4 隧道 R5基础配置…

Apifox 快速入门教程

访问示例项目​ 可访问Apifox官网&#xff0c;下载并打开 Apifox 后&#xff0c;你将会看到由系统自动创建的“示例团队”&#xff0c;其中内含一个“示例项目”。 项目中自动生成了与宠物商店有关的数条接口。 手动新建接口​ 新建接口是开发者们最常用的功能之一。Apifox 能…

Java研学-RBAC权限控制(八)

九 登录登出 1 登录作用 判断员工是否有权限访问&#xff0c;首先得知道现在操作的人是谁&#xff0c;所以必须先实现登录功能 2 登录流程 ① 提供登录页面&#xff0c;可输入用户名与密码信息&#xff0c;并添加执行登录的按钮。&#xff08;登录页面不能被拦截&#xff09;…

微服务、多租户、单点登录、国产化形成的开源Java框架!

一、项目简介 JVS是软开企服构建的一站式数字化的开源框架&#xff0c;支持对接多种账户体系&#xff0c;支持多租户、支持Auth2、统一登录、单点登录等&#xff0c;支持原生开发、低代码/零代码开发应用。 二、框架核心功能 控制台(首页)&#xff1a;采用配置化的方式 用户…

C语言从入门到进阶(15万字总结)

前言&#xff1a; 《C语言从入门到进阶》这本书可是作者呕心沥血之作&#xff0c;建议零售价1元&#xff0c;当然这里开个玩笑。 本篇博客可是作者之前写的所有C语言笔记博客的集结&#xff0c;本篇博客不止有知识点&#xff0c;还有一部分代码练习。 有人可能会问&#xff…

HarmonyOS Next 系列之可移动悬浮按钮实现(六)

系列文章目录 HarmonyOS Next 系列之省市区弹窗选择器实现&#xff08;一&#xff09; HarmonyOS Next 系列之验证码输入组件实现&#xff08;二&#xff09; HarmonyOS Next 系列之底部标签栏TabBar实现&#xff08;三&#xff09; HarmonyOS Next 系列之HTTP请求封装和Token…

Huffman树——AcWing 148. 合并果子

目录 Huffman树 定义 运用情况 注意事项 解题思路 AcWing 148. 合并果子 题目描述 运行代码 代码思路 其它代码 代码思路 Huffman树 定义 它是一种最优二叉树。通过构建带权路径长度最小的二叉树&#xff0c;经常用于数据压缩等领域。 运用情况 在数据压缩中&a…

C语言 while循环1

在C语言里有3种循环&#xff1a;while循环 do while 循环 for循环 while语句 //while语法结构 while&#xff08;表达式&#xff09;循环语句; 比如在屏幕上打印1-10 在while循环中 break用于永久的终止循环 在while循环中&#xff0c;continue的作用是跳过本次循环 …

MySQL的综合运用

MySQL版的葵花宝典&#xff0c;欲练此功&#xff0c;挥刀自。。。呃&#xff0c;&#xff0c;&#xff0c;说错了&#xff0c;是先创建两个表&#xff0c;分别是location表和store_info表 示例表为location表和store_info表&#xff0c;如下图所示&#xff1a; 操作一&#xf…

关于小蛋の编程和小蛋编程为同一作者的说明

小蛋の编程和小蛋编程的作品为同一人制作&#xff0c;因前者为父母的手机号进行注册&#xff0c;现用本人手机号注册了新账号小蛋编程&#xff0c;后续文章将在新账号小蛋编程上进行刊登&#xff0c;同时在小蛋编程上对原账号文章进行转载。此账号不再发布帖子&#xff0c;请大…