走进redisson

分布式锁:Redisson源码解析——RLock(二) 一、复习 场景模拟 初始化 在获取锁的时候,会初始化一些参数,例如commandExecutor leaseTime uuid ● commandExecutor 命令执行器 ● leaseTime 持有锁的最大时间,租期 ● uuid 客户端的唯一标识 第一次加锁 设置获取到锁的主体,使用线程id作为标识 当未设置leaseTime的时候,也就是代表的锁可以无限持有时间 判断redis中是否已经存在了这个锁 第一次加锁肯定不存在,递增一个lockName为key的hash数据结构里面的一个key为uui 阅读详情

        这里作者将大家走进redisson,读完这篇相信加深你对redisson的获取锁,重入,超时,看门狗,发布订阅等原理和功能的理解。

        本文将深入原理代码,给出每行代码的意义以及最后的效果,过程有些枯燥,但探索的过程是快乐的,同时也希望大家看的过程当中去一起去查看源码。

redisson

lock

参数介绍:

  1. long leaseTime:

    • 这个参数指定锁的持有时长。在这段时间结束后,锁会自动释放,除非它被续期或手动解锁。这是一种安全措施,确保在出现问题(如应用崩溃或网络问题)时锁最终会被释放,避免死锁情况的发生。

    • 指定的时间长度通常取决于预期的操作时间和网络延迟。设置适当的租约时间可以防止资源长时间被锁定,并允许其他线程或进程在合理的时间内访问资源。

  2. TimeUnit unit:

    • 这个参数定义了leaseTime的时间单位。TimeUnit是一个枚举类型,提供了时间单位的常量,如TimeUnit.SECONDS, TimeUnit.MILLISECONDS等,使你能以不同的时间单位指定锁的租约时间。

    • 使用TimeUnit参数可以提供额外的灵活性和清晰度,允许调用者根据需要轻松地指定租约时间,而不必担心时间单位的转换。

  3. boolean interruptibly:

    • 这个布尔值指定了锁获取的行为应该如何响应中断。如果设置为true,那么当线程在等待获取锁的过程中被中断时,它会响应这个中断,并且可能会抛出一个InterruptedException。这允许线程在等待获取锁时可以被取消或中断。

    • 如果设置为false,即使线程被中断,也会继续等待锁,不响应中断。这通常用于那些必须等到获取锁后才能继续的情况,确保操作的完成。

    private void lock(long leaseTime, TimeUnit unit, boolean interruptibly) throws InterruptedException {
        //获取当前线程的id
        long threadId = Thread.currentThread().getId();
        //ttl(Time To Live)代表锁的剩余存活时间。它是尝试获取锁时返回的值,表示锁在自动释放之前还能保持多久
        Long ttl = tryAcquire(leaseTime, unit, threadId);
        // lock acquired
        //如果为为null,代表锁获取成功。
        if (ttl == null) {
            return;
        }
		//redis的发布订阅机制,用来订阅通知。
        RFuture<RedissonLockEntry> future = subscribe(threadId);
        //负责发送命令和处理响应
        commandExecutor.syncSubscription(future);

        try {
            //这段代码实现了一个循环,不断尝试获取锁,直到成功。在尝试获取锁的过程中,根据不同的情况采取不同的行动,比如等待一定时间后重试,或者在被中断时根据interruptibly变量的值决定是否继续等待
            while (true) {
                ttl = tryAcquire(leaseTime, unit, threadId);
                // lock acquired,锁获取成功,直接退出循环
                if (ttl == null) {
                    break;
                }

                // waiting for message
                if (ttl >= 0) {
                    try {
 //这里尝试等到锁知道锁可用或者超时。
                        getEntry(threadId).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
                    } catch (InterruptedException e) {
                        //如果等待过程当中被中断了,参数interruptibly为true,抛出异常,否则继续等待。
                        if (interruptibly) {
                            throw e;
                        }
                       //释放锁的人释放之后会发出一个信号量,
                        getEntry(threadId).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
                    }
                    //小于0,代表没有超过指定时间
                } else {
                    if (interruptibly) {
 //选择阻塞调用acquire()(响应中断)或acquireUninterruptibly()(不响应中断)。                      
                        getEntry(threadId).getLatch().acquire();
                    } else {
   //getLatch()提供的信号量成为了一个同步点,线程可以在这里等待,直到它们可以安全地继续执行
                        getEntry(threadId).getLatch().acquireUninterruptibly();
                    }
                }
            }
        } finally {
            unsubscribe(future, threadId);
        }
//        get(lockAsync(leaseTime, unit));
    }

tryAcquire

private Long tryAcquire(long leaseTime, TimeUnit unit, long threadId) {
    //返回一个获取锁的方法
    return get(tryAcquireAsync(leaseTime, unit, threadId));
}

 tryAcquireAsync

private <T> RFuture<Long> tryAcquireAsync(long leaseTime, TimeUnit unit, long threadId) {
        //如果leaseTime不等于-1,意味着用户指定了一个有效的租约时间,就不会走自带的逻辑。
        if (leaseTime != -1) {
            return tryLockInnerAsync(leaseTime, unit, threadId, RedisCommands.EVAL_LONG);
        }
        //如果没有设置leaseTime的值,方法获取默认的锁看门狗超时时间,再次调用tryLockInnerAsync。这意味着将使用默认的超时设置来尝试获取锁。
        RFuture<Long> ttlRemainingFuture = tryLockInnerAsync(commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(), TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
        
        ttlRemainingFuture.onComplete((ttlRemaining, e) -> {
            //e表示回调的异常,如果为null则说明操作完成,否则直接返回。
            if (e != null) {
                return;
            }

            // lock acquired
            //ttlRemaining是回调的结果,表示锁的剩余存活时间。
            if (ttlRemaining == null) {
                //在锁已经成功获取的情况下,看当前线程是否需要续订锁。
                scheduleExpirationRenewal(threadId);
            }
        });
        //如果锁当前被其他线程持有,这个值可能表示要等待多长时间才有可能再次尝试获取锁。
        //null:通常表示锁已成功被当前线程获取,没有剩余等待时间。
        return ttlRemainingFuture;
    }

        订阅发布机制目的是为了在当前线程不能立即获得锁时,订阅一个通知,当锁被释放或变为可用时,当前线程可以得到通知。通过这种方式,Redisson 实现了一个高效且响应迅速的分布式锁机制,允许多个线程或进程协调地访问共享资源。

ExprirationEntry

用于管理锁续约机制中线程和超时信息的数据结构

public static class ExpirationEntry {
        //一个映射,键是线程ID(Long),值是计数器(Integer),用于跟踪每个线程对应的锁请求次数。LinkedHashMap保持了插入顺序,这可能对确定哪个线程首先请求锁有用。
        private final Map<Long, Integer> threadIds = new LinkedHashMap<>();
    //一个Timeout类型的变量,用来存储与该锁条目关联的超时任务。标记为volatile以确保在多线程环境中线程安全地访问。
        private volatile Timeout timeout;
        
        public ExpirationEntry() {
            super();
        }
        //添加或增加一个线程ID的计数。如果该线程ID已存在,则增加其计数;如果不存在,则将其计数设置为1。
        public void addThreadId(long threadId) {
            Integer counter = threadIds.get(threadId);
            if (counter == null) {
                counter = 1;
            } else {
                counter++;
            }
            threadIds.put(threadId, counter);
        }
    //检查threadIds映射是否为空,即没有任何线程ID与此锁条目关联。
        public boolean hasNoThreads() {
            return threadIds.isEmpty();
        }
        public Long getFirstThreadId() {
            if (threadIds.isEmpty()) {
                return null;
            }
            return threadIds.keySet().iterator().next();
        }
        public void removeThreadId(long threadId) {
            Integer counter = threadIds.get(threadId);
            if (counter == null) {
                return;
            }
            counter--;
            if (counter == 0) {
                threadIds.remove(threadId);
            } else {
                threadIds.put(threadId, counter);
            }
        }
        
        
        public void setTimeout(Timeout timeout) {
            this.timeout = timeout;
        }
        public Timeout getTimeout() {
            return timeout;
        }
        
    }

syncSubscription

同步等待一个异步订阅操作的完成,并处理超时以及中断情况,确保资源正确管理和异常处理逻辑得到妥善执行

public void syncSubscription(RFuture<?> future) {
    //获取redis的配置信息,该配置信息存储在MasterSlaveServersConfig对象中,MasterSlaveServersConfig可能包含了与主从服务器相关的配置设置
    MasterSlaveServersConfig config = connectionManager.getConfig();
    try {
        //计算超时时间,基于配置中的单次操作超时时间(getTimeout())、重试间隔(getRetryInterval())以及重试次数(getRetryAttempts())计算得出
        int timeout = config.getTimeout() + config.getRetryInterval() * config.getRetryAttempts();
        //等待future的完成
        if (!future.await(timeout)) {
            ((RPromise<?>) future).tryFailure(new RedisTimeoutException("Subscribe timeout: (" + timeout + "ms). Increase 'subscriptionsPerConnection' and/or 'subscriptionConnectionPoolSize' parameters."));
        }
       // 如果在指定的超时时间内future没有完成(即await返回false),则尝试将future标记为失败,并抛出一个带有超时信息的
    } catch (InterruptedException e) {
        //处理线程中断
        Thread.currentThread().interrupt();
    }
    //同步完成,忽略中断,最后,调用syncUninterruptibly()确保当前线程在future完成之前不会继续执行,syncUninterruptibly()会忽略线程的中断状态,确保等待直到操作完全完成
    future.syncUninterruptibly();
}

unsubscribe

在完成所有操作后取消订阅,避免内存泄露或不必要的资源占用

 public void unsubscribe(E entry, String entryName, String channelName) {
        //这个信号量用于控制对特定操作的并发访问,确保在对订阅进行更改时的线程安全。
        AsyncSemaphore semaphore = service.getSemaphore(new ChannelName(channelName));
        semaphore.acquire(new Runnable() {
            @Override
            public void run() {
                //如果entry.release() == 0,则表示相关资源可以被释放
                if (entry.release() == 0) {
                    // just an assertion
                    boolean removed = entries.remove(entryName) == entry;
                    if (!removed) {
                        throw new IllegalStateException();
                    }
                    //如果成功释放资源,调用service的unsubscribe方法取消订阅特定的频道,同时传入信号量以维护操作的同步性。
                    service.unsubscribe(new ChannelName(channelName), semaphore);
                } else {
                    //如果entry没有被完全释放(即release()方法返回非零值),则调用semaphore.release()来释放信号量,允许其他操作继续进行
                    semaphore.release();
                }
            }
        });

    }

tryLockInnerAsync

T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
        internalLockLeaseTime = unit.toMillis(leaseTime);
//key=Keys[1] --锁的key
        //threadId--线程唯一id
        //releaseTime-锁自动释放时间
        return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command,
                                              //首先判断锁是否存在
                  "if (redis.call('exists', KEYS[1]) == 0) then " +
                                              //不存在,获取锁
                      "redis.call('hset', KEYS[1], ARGV[2], 1); " +
                                              //调用expire设置有效期
                      "redis.call('pexpire', KEYS[1], ARGV[1]); " +
                                              //返回结果
                      "return nil; " +
                  "end; " +
                                              //锁已经存在了,判断threadId是不是自己的线程
                  "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
                                              //是自己的,重入次数+1
                      "redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
                                              //设置有效期
                      "redis.call('pexpire', KEYS[1], ARGV[1]); " +
                      "return nil; " +
                  "end; " +
                                           //如果不是自己的就返回失败。
                  "return redis.call('pttl', KEYS[1]);",
                    Collections.<Object>singletonList(getName()), internalLockLeaseTime, getLockName(threadId));
    }
    

scheduleExpirationRenewal

        确保锁的过期时间被适时续约,特别是在锁被长时间持有时。它通过维护一个包含所有需要续约的锁条目的映射(EXPIRATION_RENEWAL_MAP)来实现。每个条目代表一个锁,包含一个或多个希望续约该锁的线程ID。如果条目已存在,说明已经有其他线程计划续约这个锁,当前线程ID将被添加到这个条目中;如果条目不存在,将创建一个新的条目,并触发续约操作。这种机制允许多个线程协调地续约同一个锁,确保锁在需要时保持活动状态,避免因超时而导致的锁自动释放。

private void scheduleExpirationRenewal(long threadId) {
        ExpirationEntry entry = new ExpirationEntry();
       ////RedissonLock的所有实例都可以看到EXPIRATION_RENEWAL_MAP,因为他是静态的。一个RedissonLock类会创建出很多实例,每个锁都有自己的名字,有自己唯一entry。
        ExpirationEntry oldEntry = 
 EXPIRATION_RENEWAL_MAP.putIfAbsent(getEntryName(), entry);
       //oldEntry不为null,说明映射中已经有一个条目与当前锁相关联,那么就将当前线程ID添加到这个旧条目中,表明当前线程也希望续约这个锁的过期时间。
        if (oldEntry != null) {
            oldEntry.addThreadId(threadId);
        } else {
            //如果没有旧条目(即oldEntry为null),表示这是第一次尝试续约当前锁的过期时间,那么就将当前线程ID添加到新条目中,并调用renewExpiration方法来实际执行续约操作。
            entry.addThreadId(threadId);
            renewExpiration();
        }
    }

 renewExpiration

private void renewExpiration() {
     //获取与当前锁相关联的ExpirationEntry对象。这个对象可能包含了关于锁续约所需的信息,如相关的线程ID
    
        ExpirationEntry ee = EXPIRATION_RENEWAL_MAP.get(getEntryName());
     //没有找到过期条目,方法直接返回,锁可能被释放或者没有续约必要
        if (ee == null) {
            return;
        }
        //创建一个新的Timeout任务,该任务将在指定的延迟后执行
        Timeout task = 
 //调度异步任务的方法,用于在指定的时间后执行代码。这里使用了internalLockLeaseTime / 3作为延时,表示在锁租约时间的三分之一后执行。
            commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
            @Override
            public void run(Timeout timeout) throws Exception {
                //再次从映射中获取锁的过期条目。
                ExpirationEntry ent = EXPIRATION_RENEWAL_MAP.get(getEntryName());
                if (ent == null) {
                    return;
                }
                //获取第一个等待续约的线程ID。
                Long threadId = ent.getFirstThreadId();
                if (threadId == null) {
                    return;
                }
                //如果线程ID存在,调用renewExpirationAsync(threadId)异步续约锁。
                RFuture<Boolean> future = renewExpirationAsync(threadId);
                future.onComplete((res, e) -> {
                    if (e != null) {
                        //如果出现异常,记录错误日志。
                        log.error("Can't update lock " + getName() + " expiration", e);
                        return;
                    }
                    //如果续约成功(res为真),则递归调用renewExpiration以再次调度续约,确保锁在持有期间保持活动。
                    if (res) {
                        // reschedule itself
                        //重置有效期
                        renewExpiration();
                    }
                });
            }
            //意味着在锁的租约时间过去三分之一之前,尝试对其进行续约。
        }, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);
        //更新过期条目的定时器
        ee.setTimeout(task);
    }

只有在释放锁的时候才会停止续期。

unlockAsync

 void cancelExpirationRenewal(Long threadId) {
        //根据锁的名字来取
        ExpirationEntry task = EXPIRATION_RENEWAL_MAP.get(getEntryName());
        if (task == null) {
            return;
        }
        
        if (threadId != null) {
            task.removeThreadId(threadId);
        }
        
        if (threadId == null || task.hasNoThreads()) {
            task.getTimeout().cancel();
            EXPIRATION_RENEWAL_MAP.remove(getEntryName());
        }
    }

tryLock

 public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {
        long time = unit.toMillis(waitTime);
        long current = System.currentTimeMillis();
        long threadId = Thread.currentThread().getId();
        Long ttl = tryAcquire(leaseTime, unit, threadId);
        // lock acquired
      //获取成功
        if (ttl == null) {
            return true;
        }
        //等待的时间为减掉获取锁的时间
        time -= System.currentTimeMillis() - current;
      //等待时间小于0就不等了
        if (time <= 0) {
            acquireFailed(threadId);
            return false;
        }
        //严格控制时间,但是刚获取一次,立刻获取成功率不大,这时候我们可以用到了之前的发布订阅,获取到该县城订阅的别的线程是否释放锁的信息
        current = System.currentTimeMillis();
      //订阅与锁相关的信息
        RFuture<RedissonLockEntry> subscribeFuture = subscribe(threadId);
      //等待订阅完成,或者给定的等待时间time耗尽,
        if (!await(subscribeFuture, time, TimeUnit.MILLISECONDS)) {
            //订阅未在指定时间完成
            if (!subscribeFuture.cancel(false)) {
                //尝试取消订阅操作。如果取消失败(即订阅操作已完成或无法取消),则设置一个完成时的回调来处理结果。
                subscribeFuture.onComplete((res, e) -> {
                    if (e == null) {
                        unsubscribe(subscribeFuture, threadId);
                    }
                });
            }
            //调用acquireFailed方法处理获取锁失败的逻辑
            acquireFailed(threadId);
            //返回false,表示未能成功获取锁。
            return false;
        }

        try {
            //精确控制之前耗尽的时间
            time -= System.currentTimeMillis() - current;
            if (time <= 0) {
                acquireFailed(threadId);
                return false;
            }
        //不断地尝试
            while (true) {
                long currentTime = System.currentTimeMillis();
                ttl = tryAcquire(leaseTime, unit, threadId);
                // lock acquired
                if (ttl == null) {
                    return true;
                }

                time -= System.currentTimeMillis() - currentTime;
                if (time <= 0) {
                    acquireFailed(threadId);
                    return false;
                }

                // waiting for message
                currentTime = System.currentTimeMillis();
                if (ttl >= 0 && ttl < time) {
 //如果ttl小于剩余时间且大于0,通过getEntry(threadId).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);等待直到锁可能变为可用或直至ttl时间结束。                  
                    getEntry(threadId).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
                } else {
 //等待剩余地总时间                  
                    getEntry(threadId).getLatch().tryAcquire(time, TimeUnit.MILLISECONDS);
                }

                time -= System.currentTimeMillis() - currentTime;
                if (time <= 0) {
                    acquireFailed(threadId);
                    return false;
                }
            }
        } finally {
            //无论结果如何,都取消对锁释放消息的订阅,以避免内存泄漏或其他潜在问题
            unsubscribe(subscribeFuture, threadId);
        }
//        return get(tryLockAsync(waitTime, leaseTime, unit));
    }

unlockInnerAsync

解锁逻辑

 protected RFuture<Boolean> unlockInnerAsync(long threadId) {
        return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
                                              //判断锁是不是自己持有
                "if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then " +
                                              //如果不是自己的就返回
                    "return nil;" +
                "end; " +
                                              //如果是自己的锁,冲入次数-1
                "local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); " +
                                              //判断是不是0,大于0说明不能释放,等于0就可以释放
                "if (counter > 0) then " +
                    "redis.call('pexpire', KEYS[1], ARGV[2]); " +
                    "return 0; " +
                "else " +
                                              //直接删除了
                    "redis.call('del', KEYS[1]); " +
                                              //将信息发送到指定的频道,任何订阅了该频道的客户端都可以接收到这个信息,在这个上下文中,它用于在锁被释放时通知其他可能正在等待这个锁的客户端
                    "redis.call('publish', KEYS[2], ARGV[1]); " +
                    "return 1; "+
                "end; " +
                "return nil;",
                Arrays.<Object>asList(getName(), getChannelName()), LockPubSub.UNLOCK_MESSAGE, internalLockLeaseTime, getLockName(threadId));

    }
    

图示流程

黑马的图,不重复造轮子

不过大家要记住如果设置了超时时间看门狗线程就不起作用了。也就是leasetime

数据工程师——ETL 文章摘要 ETL(数据抽取、转换、加载)是数据处理的核心流程,负责将分散数据源整合为高质量数据。ETL与ELT的主要区别在于转换步骤顺序,分别适用于不同规模数据场景。数据仓库通过ETL从多个数据源集成数据,支持分析决策。维度建模中的事实表记录业务指标,维度表提供上下文信息。缓慢变化维度(SCD)处理随时间变化的维度数据,常见类型包括直接覆盖型、新增版本型等。数据清洗解决数据缺失、重复、错误等问题,确保数据质量。数据血缘记录数据流转路径,便于问题排查和影响分析。常用ETL工具如Kettle通过转换和作业实现数 阅读详情

相关推荐

INCA基础教程

以软件自带Demo为例记录INCA学习过程,主要内容包括测量与标定。

yunqianrui的博客 6万+

Spring Boot 操作 Redis 的各种实现

/ =====================================================================使用Spring cahce 注解方式实现缓存。System.out.println("================线程已结束================================" + s);// ================== ==============读写锁测试 =============================

m0_73311735的博客 513

Agisoft Metashape相机标定笔记

使用Metashape进行自动相机标定是可能的。(可选:使用打印的棋盘格图案,但需保证它是平坦的且单元格是正方形)。相机标定步骤支持全相机标定矩阵的估计,包含非线性的畸变参数。相机模型的细节请参照附录D——相机模型板块。:在通用的工作流程中,相机标定步骤可以跳过。因为Metashape会在图像定向过程中进行相机自标定。但是,如果定向结果不稳定,例如,由于影像之间缺乏连接点,额外进行单独的相机标定步骤也许是有用的。

天桥飞熊的博客 3972

使用redisson时关于订阅数的问题

在使用redisson消息订阅时,我针对门店商品库存减扣进行订阅的操作(在这里一个商品一个监听队列),当正式投入生产时,发现一直再报Subscribe timeout: (" + timeout + "ms). Increase 'subscriptionsPerConnection' and/or 'subscriptionConnectionPoolSize' parameters.的错误,索...

weixin_34010949的博客 3361

【SLG游戏服务器开发手册】(ROK Like)沙盘地图篇一,菱形瓦片与笛卡尔坐标系的转换(附调试工具)

轴向坐标:以菱形网格对角方向为基准轴,记作(q, r)菱形瓦片宽度 w=2a,高度 h=a(宽高比2:1)

代码时光 979

不止是回退SDK:深入解读UE5.2编译时那个烦人的C4756警告与INFINITY宏的变迁

本文深入解析了UE5.2源码编译时出现的C4756警告及其背后的技术原因,探讨了Windows SDK中INFINITY宏的变迁对Unreal Engine的影响。文章详细介绍了微软修改INFINITY定义的技术考量,并提供了UE5.2应对此类问题的灵活解决方案,包括通过BuildConfiguration.xml文件精确控制工具链版本,确保构建系统的可重复性和可配置性。

weixin_27180827的博客 310

Redisson 分布锁 tryLock()锁重试机制源码分析

【代码】Redisson 分布锁锁重试机制源码分析。

weixin_51410166的博客 845

基于ROS与Gazebo的多机器人三角形编队仿真实践

本文详细介绍了基于ROS与Gazebo的多机器人三角形编队仿真实践,从环境搭建到编队控制的全过程。通过TurtleBot3机器人模型和TF坐标系系统,实现三个机器人的三角形编队控制,适用于机器人协同作业与编队算法研究。

weixin_29313817的博客 187

使用Redisson实现分布式锁发现的【订阅超时】Subscribe timeout: (7500ms)

使用 redisson 实现分布式锁,出现的 订阅超时:org.redisson.client.RedisTimeoutException: Subscribe timeout: (7500ms). Increase 'subscriptionsPerConnection' and/or 'subscriptionConnectionPoolSize' parameters

Jarvis's Blog 1628

Altair HyperWorks软件二次开发:二次开发成果展示与报告撰写

HyperMesh Python API允许用户通过Python脚本控制HyperMesh的大部分功能,包括模型创建、修改和分析。它提供了丰富的函数库,可以处理节点、元素、材料属性、载荷和边界条件等。二次开发可以用于创建自定义工具,这些工具可以集成到HyperWorks中,提供特定的功能或简化复杂的操作。

kkchenjj的博客 1639

OpenClaw实战系列06:持久记忆与RAG知识库构建——让AI真正“记住”你

本文介绍了OpenClaw AI助理的持久记忆与RAG知识库构建功能。OpenClaw采用"文件即真理"的独特设计,所有记忆都以Markdown文件形式存储,支持直接编辑和Git版本管理。记忆系统分为三层:长期精炼记忆(MEMORY.md)、日常日志(按日期存储)和归档记忆。文章详细说明了环境配置步骤,包括安装必要组件、创建目录结构、设置API密钥,并提供了OpenClaw主配置文件的完整参数说明。该系统实现了跨会话、跨平台的持久化记忆,使AI助理能真正理解用户需求。

PeterPan的博客 1171

4、Redis分布式锁原理解析

方法用于释放已经持有的锁,确保其他线程可以获取锁。Redisson 通过 Lua 脚本原子性地执行解锁操作,以保证解锁过程的安全性和一致性。方法在获取锁失败时不会一直阻塞,而是根据指定的等待时间和租约时间进行尝试,并返回是否成功获取锁。在等待锁释放期间,Redisson 会进入一个循环,不断尝试重新获取锁。在等待锁释放期间,Redisson 会进入一个循环,不断尝试重新获取锁。如果初次尝试获取锁失败,Redisson 会订阅锁的释放通知。如果初次尝试获取锁失败,Redisson 会订阅锁的释放通知。

qq_29434541的博客 1591

Linux | i.MX6ULL Mqtt 移植教程

本文介绍了MQTT协议在物联网中的应用及其实现工具Mosquitto的移植过程。使用Ubuntu 16.04环境和gcc交叉编译器,详细说明了从X86平台安装测试到ARM开发板移植的完整步骤。重点包括:Mosquitto的安装配置、服务启动测试方法、依赖库openssl和libuuid的交叉编译过程、路径参数修改要点,以及最终在开发板上的功能测试验证。文中提供了具体的命令操作和路径修改示例,强调路径一致性对编译成功的重要性。该移植方案实现了轻量级MQTT协议在嵌入式设备中的应用。

Qt历险记,专注Qt技术,三年医疗设备研发经验,1年机器视觉研发经验,半年柔性传感研发经验。 138

redisson工具类_绝对干货:利用redisson完成分布式锁功能

在单体架构中,我们使用synchronize或者Lock就能完成上锁同步的操作,但是这些在分布式,微服务的今天,失去了作用。分布式锁的实现一般有三种解决方案:基于数据库表实现基于缓存实现,比如redis基于zookeeper实现那么利用redisson就是基于redis来实现的分布式锁功能。什么是redisson官网:https://redisson.org从首页的,我们可以大致看出redisso...

weixin_31586521的博客 1819

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

redisson的锁的类型_绝对干货:利用redisson完成分布式锁功能

在单体架构中,我们使用synchronize或者Lock就能完成上锁同步的操作,但是这些在分布式,微服务的今天,失去了作用。分布式锁的实现一般有三种解决方案:基于数据库表实现基于缓存实现,比如redis基于zookeeper实现那么利用redisson就是基于redis来实现的分布式锁功能。什么是redisson从首页的,我们可以大致看出redisson可以实现很多东西,在redisson的基础上...

weixin_39658900的博客 264

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

五分钟带你走进Redis并发问题

redis中的并发问题 使用redis作为缓存已经很久了,redis是以单进程的形式运行的,命令是一个接着一个执行的,一直以为不会存在并发的问题,直到今天看到相关的资料,才恍然大悟~~ 具体问题实例 有个键,假设名称为myNum,里面保存的是阿拉伯数字,假设现在值为1,存在多个连接对myNum进行操作的情况,这个时候就会有并发的问题。假设有两个连接linkA和linkB,这两个连接都执行下面的操作,取出myNum的值,+1,然后再存回去,看看下面的交互: linkA get myNum => 1 li

Javacxyzz的博客 315

IMSL CNL 7.0.0 (x86-64)Crack

IMSL CNL 7.0.0 (x86-64)Crack intel的算法库破解文件,这是C++版本的破解文件

面试官:谢飞机,聊聊Redis分布式锁?

下一位,谢飞机!”面试官推了推眼镜,面无表情地喊道。谢飞机整理了下皱巴巴的格子衬衫,快步走进会议室,脸上堆满了自信的笑容:“面试官好!我就是那个写了10年代码,精通所有技术栈的男人!面试官瞥了一眼简历,嘴角微微抽搐:“好的,那我们开始吧。

weixin_69967800的博客 827

亿级数据下的基数统计:从 Set 到 HyperLogLog 的进阶实战

本文探讨了亿级数据基数统计的演进方案,从Set、Hash到Bitmap,最终聚焦Redis的HyperLogLog(HLL)解决方案。HLL作为概率型数据结构,仅需12KB内存即可统计近2^64个元素,标准误差0.81%,完美适用于UV、日活等海量数据场景。文章详细解析了HLL的数学原理(基于伯努利过程和分桶平均算法),并通过Java实战展示了Redisson的RHyperLogLog实现。对比传统方案,HLL在内存消耗(固定12KB)和性能方面优势显著,是分布式环境下大规模基数统计的最佳选择。

2401_83144588的博客 1192

分布式锁,到底该如何实现?

本文从单机锁的局限性切入,详解分布式锁的三种主流实现:数据库(唯一索引)、Redis(SET NX EX → Lua 脚本 → 看门狗 → Redisson → RedLock 完整演进)、ZooKeeper(临时顺序节点 + Curator),附带 Etcd 简介。最后给出选型决策树和 6 道面试高频题的答题模板,帮助读者从原理到实战全面掌握分布式锁。

专注Java后端开发,分享SpringBoot微服务、高并发优化、生产环境踩坑实录,帮你少走弯路,让我们一起从CRUD进阶到架构师。 729

Java八股文实践篇:NEURAL MASK微服务开发中的设计模式与并发编程

本文介绍了如何在星图GPU平台上自动化部署💠 NEURAL MASK | 幻镜 · 视觉重构实验室镜像,以构建高并发、高可用的AI微服务。通过结合工厂模式、观察者模式与JUC并发工具,该镜像能够高效管理AI模型客户端并处理推理结果,典型应用于图片识别、文本生成等AI推理服务的后端开发场景。

weixin_34268462的博客 27

还在为分布式多租户后台管理系统发愁?RuoYi-Vue-Plus 给你开箱即用的答案

如果你正在为企业级后台管理系统的选型发愁——既要撑起多租户的数据隔离,又要为将来可能到来的分布式集群留足余地——那么这套名为 **RuoYi-Vue-Plus** 的开源项目,值得你花十分钟认真读完。它是一套面向分布式多租户后台管理系统的完整解决方案,重写了经典 RuoYi-Vue 的全部功能,把 Sa-Token、Mybatis-Plus、WarmFlow、SpringDoc 等现代组件装配成开

gitblog_00659的博客 188

书籍 -《基于SpringBoot实战Java分布式中间层》

个人评价:综合 3(没有读完) 内容:3 (代码比较详细,但是有的地方就跳过了) 排版文字章节:4(代码量还可以,但是书中和配套的代码已经不一致) 价格:6 重读价值:2 (没有读完,太厚了) 本书主要讲的是如何java开发中间层,刚开始讲的非常好,但是后面就直接上代码了,能在细一点的话,书绝对能上个档次,不做推荐,太厚了。 文章目录: 走进分布式中间层 搭建Spring Boot微服务项目 缓存中间层 Redis Redis典型应用场景实战之抢红包系统 消息中间层 Rabbit

大道至简 241

Java面试实战:互联网大厂的技术问答场景与解析

嗯......登录嘛,应该用个 Spring Security,然后权限呢,就......哦,JWT可以用来做认证,OAuth2也能做授权对吧?毛毛,假设你参与一个电商平台的开发,现在我们需要设计一个用户登录与权限管理模块,你会选择哪些技术来实现?啊,这个嘛,呃,好像是可以写个过滤器,从请求头里解析 Token,然后...然后...(陷入沉思)好的,最后一个问题,全球化扩展中,如何设计一个高效的用户推荐系统?好的,毛毛,你今天的表现还可以,但有些问题需要加强。最后一个问题,这个模块的日志记录你会怎么设计?

yxc2017的博客 362

【面试问题第一篇】快手后端java一面

原理是用ASM字节码操作框架,在内存里构建目标类的子类,然后重写父类的方法,在里面织入增强逻辑。因为是有序的,还能做分页展示,比如取最新的N条粉丝。我打个比方,你想给某个类的某些方法进行扩展,但又不想改原来的代码,这时候就可以用动态代理。消息发到特殊的延迟交换机,交换机不立马投递,而是根据你设置的延迟时间(比如 30 分钟)把消息暂存起来,时间一到,才扔到队列里,消费者就能消费了。这些是面试比较常见的问题,在看看自己还有那些不懂,背的时候,自己总结着背,祝你们都能获取到想要的offer。

uybji的博客 630

Jedis 与 Redis之间有什么区别与联系?为什么Redis命令在java中要用Jedis实现?

摘要: Jedis是Java操作Redis的客户端库,封装了与Redis服务器通信的底层细节,提供简洁的API。Redis是基于内存的键值存储系统,独立运行于服务端。两者通过RESP协议通信,Jedis将Java方法转换为Redis命令执行。相比直接操作Redis,Jedis具备连接池、异常处理等优势,适合Java项目高效访问Redis。文中还对比了Lettuce等替代方案,并通过代码示例演示了Jedis的基础使用和连接池配置。 (字数:150)

1227的博客 1530

谢飞机面试大厂Java岗:从音视频到AI大模型,一场“水”与“火”的较量

谢飞机虽然表现了不少亮点(能说出AC自动机、Respire4j)但总体深度不足。面试官在结束时补充的那个问题其实就是一种含蓄的否决:“你简历上写的熟悉分布式事务,但我一问细节你就答不上来。” 所以,大家在校招或社招面试时,不要背干巴巴的八股文,一定要掌握技术背后的原理,并且结合项目/场景去说清楚。否则下一轮谢飞机就是你。不过没关系,谢飞机回去之后,把这些问题都整理成了文档,又买了几本《Kafka权威指南》《Redisson源码解析》,开始扎扎实实地啃源码。下次面试,他一定能挺进最后一轮!祝他好运。

2602_95996092的博客 229
上一篇: 数据结构的美之百家争鸣-redis-dict篇
下一篇: 走进jvm之垃圾回收器篇
happystudy_neu
博客等级 码龄4年 1140粉丝 43原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值