当前位置:

首页 > 编程开发 > Redis分布式锁该怎么实现续期

Redis分布式锁该怎么实现续期

Redis Assistant
Redis Assistant

Redis Assistant 是一款为Redis设计的高效可视化管理与监控工具,提供多种操作模式,高效处理服务器数据。日常使用里的持续防护和信息管理会更突出,适合把安全控制放进长期使用流程中的场景。

立即下载
¥19
Windows/macOS/Linux 2026-08-22
正版软件 Windows macOS软件 Linux 数据库管理工具

Redis分布式锁如何续期Redis分布式锁的正确姿势据肥朝了解,很多同学在用分布式锁时,都是直接百度搜索找一个Redis分布式锁工具类就直接用了.关键是该工具类中还充斥着很多System.out.println();等语句.其实Redis分布式锁比较正确的姿势是采用redisson这个客户端工具.具体介绍可以搜索最大的同性交友网站github.如何回答首先如果你之前用Redis的分布式锁的姿势正确,并且看过相应的官方文档的话,这个问题Soeasy.我们来看坦白说,如果你英文棒棒哒那么看英文文档可能更好理

本文目录

    Redis分布式锁如何续期

    Redis分布式锁的正确姿势

    据肥朝了解,很多同学在用分布式锁时,都是直接百度搜索找一个Redis分布式锁工具类就直接用了.关键是该工具类中还充斥着很多System.out.println();等语句.其实Redis分布式锁比较正确的姿势是采用redisson这个客户端工具.具体介绍可以搜索最大的同性交友网站github.

    如何回答

    首先如果你之前用Redis的分布式锁的姿势正确,并且看过相应的官方文档的话,这个问题So easy.我们来看

    Redis分布式锁该怎么实现续期

    坦白说,如果你英文棒棒哒那么看英文文档可能更好理解

    By default lock watchdog timeout is 30 seconds and can be changed through Config.lockWatchdogTimeout setting.

    但是你如果看的是中文文档

    看门狗检查锁的超时时间默认是30秒

    这句话肥朝从语文角度分析就是一个歧义句,他有两个意思

    1.看门狗默认30秒去检查一次锁的超时时间

    2.看们狗会去检查锁的超时时间,锁的时间时间默认是30秒

    看到这里,我希望大家不要黑我的小学体育老师,虽然他和语文老师是同个人.语文不行,我们可以源码来凑!

    源码分析

    我们根据官方文档给出的例子,写了一个最简单的demo,例子根据上面截图中Ctr+C和Ctr+V一波操作,如下

    public class DemoMain {
        public static void main(String[] args) throws Exception {
            Config config = new Config();
            config.useSingleServer().setAddress("redis://127.0.0.1:6379");
            RedissonClient redisson = Redisson.create(config);
            RLock lock = redisson.getLock("anyLock");
            lock.lock();
            //lock.unlock();
        }
    }

    create

    Redis分布式锁该怎么实现续期

    从这里我们知道,internalLockLeaseTime 和 lockWatchdogTimeout这两个参数是相等的.

    lockWatchdogTimeout默认值如下

    public class Config {	
    	private long lockWatchdogTimeout = 30 * 1000;		
    	public long getLockWatchdogTimeout() {
    		return lockWatchdogTimeout;
    	}	
    	//省略无关代码
    }

    从internalLockLeaseTime这个单词也可以看出,这个加的分布式锁的超时时间默认是30秒.但是还有一个问题,那就是这个看门狗,多久来延长一次有效期呢?我们往下看

    lock

    Redis分布式锁该怎么实现续期

    从我图中框起来的地方我们就知道了,获取锁成功就会开启一个定时任务,也就是watchdog,定时任务会定期检查去续期renewExpirationAsync(threadId).
    这里定时用的是netty-common包中的HashedWheelTimer,肥朝公众号已经和各大搜索引擎建立了密切的合作关系,你只需要把这个类在任何搜索引擎一搜,都能知道相关API参数的意义.
    从图中我们明白,该定时调度每次调用的时间差是internalLockLeaseTime / 3.也就10秒.

    真相大白

    通过源码分析我们知道,默认情况下,加锁的时间是30秒.如果加锁的业务没有执行完,那么到 30-10 = 20秒的时候,就会进行一次续期,把锁重置成30秒.那这个时候可能又有同学问了,那业务的机器万一宕机了呢?宕机了定时任务跑不了,就续不了期,那自然30秒之后锁就解开了呗.

    Redis分布式锁的5个坑

    一、锁未被释放

    这种情况是一种低级错误,就是我上边犯的错,由于当前线程 获取到redis 锁,处理完业务后未及时释放锁,导致其它线程会一直尝试获取锁阻塞,例如:用Jedis客户端会报如下的错误信息

    redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool

    redis线程池已经没有空闲线程来处理客户端命令。

    解决的方法也很简单,只要我们细心一点,拿到锁的线程处理完业务及时释放锁,如果是重入锁未拿到锁后,线程可以释放当前连接并且sleep一段时间。

    public void lock() {
        while (true) {
            boolean flag = this.getLock(key);
            if (flag) {
                  TODO .........
            } else {
                  // 释放当前redis连接
                  redis.close();
                  // 休眠1000毫秒
                 sleep(1000);
           }
         }
     }

    二、B的锁被A给释放了

    我们知道Redis实现锁的原理在于 SETNX命令。当 key不存在时将 key的值设为 value ,返回值为 1;若给定的 key已经存在,则 SETNX不做任何动作,返回值为 0 。

    SETNX key value

    我们来设想一下这个场景:A、B两个线程来尝试给key myLock加锁,A线程先拿到锁(假如锁3秒后过期),B线程就在等待尝试获取锁,到这一点毛病没有。

    那如果此时业务逻辑比较耗时,执行时间已经超过redis锁过期时间,这时A线程的锁自动释放(删除key),B线程检测到myLock这个key不存在,执行 SETNX命令也拿到了锁。

    但是,此时A线程执行完业务逻辑之后,还是会去释放锁(删除key),这就导致B线程的锁被A线程给释放了。

    为避免上边的情况,一般我们在每个线程加锁时要带上自己独有的value值来标识,只释放指定value的key,否则就会出现释放锁混乱的场景。

    三、数据库事务超时

    emm~ 聊redis锁咋还扯到数据库事务上来了?别着急往下看,看下边这段代码:

     @Transaction
     public void lock() {
          while (true) {
              boolean flag = this.getLock(key);
              if (flag) {
                  insert();
              }
          }
     }

    给这个方法添加一个@Transaction注解开启事务,如代码中抛出异常进行回滚,要知道数据库事务可是有超时时间限制的,并不会无条件的一直等一个耗时的数据库操作。

    比如:我们解析一个大文件,再将数据存入到数据库,如果执行时间太长,就会导致事务超时自动回滚。

    一旦你的key长时间获取不到锁,获取锁等待的时间远超过数据库事务超时时间,程序就会报异常。

    一般为解决这种问题,我们就需要将数据库事务改为手动提交、回滚事务。

      @Autowired
      DataSourceTransactionManager dataSourceTransactionManager;
      @Transaction
      public void lock() {
          //手动开启事务
          TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);
          try {
              while (true) {
                 boolean flag = this.getLock(key);
                 if (flag) {
                     insert();
                     //手动提交事务
                     dataSourceTransactionManager.commit(transactionStatus);
                 }
             }
         } catch (Exception e) {
             //手动回滚事务
             dataSourceTransactionManager.rollback(transactionStatus);
         }
     }

    四、锁过期了,业务还没执行完

    这种情况和我们上边提到的第二种比较类似,但解决思路上略有不同。

    同样是redis分布式锁过期,而业务逻辑没执行完的场景,不过,这里换一种思路想问题,把redis锁的过期时间再弄长点不就解决了吗?

    那还是有问题,我们可以在加锁的时候,手动调长redis锁的过期时间,可这个时间多长合适?业务逻辑的执行时间是不可控的,调的过长又会影响操作性能。

    要是redis锁的过期时间能够自动续期就好了。

    为了解决这个问题我们使用redis客户端redisson,redisson很好的解决了redis在分布式环境下的一些棘手问题,它的宗旨就是让使用者减少对Redis的关注,将更多精力用在处理业务逻辑上。

    redisson对分布式锁做了很好封装,只需调用API即可。

    RLock lock = redissonClient.getLock("stockLock");

    redisson在加锁成功后,会注册一个定时任务监听这个锁,每隔10秒就去查看这个锁,如果还持有锁,就对过期时间进行续期。默认过期时间30秒。这个机制也被叫做:“看门狗”,这名字。。。

    举例子:假如加锁的时间是30秒,过10秒检查一次,一旦加锁的业务没有执行完,就会进行一次续期,把锁的过期时间再次重置成30秒。

    通过分析下边redisson的源码实现可以发现,不管是加锁、解锁、续约都是客户端把一些复杂的业务逻辑,通过封装在Lua脚本中发送给redis,保证这段复杂业务逻辑执行的原子性。

    @Slf4j
    @Service
    public class RedisDistributionLockPlus {
       /**
        * 加锁超时时间,单位毫秒, 即:加锁时间内执行完操作,如果未完成会有并发现象
        */
       private static final long DEFAULT_LOCK_TIMEOUT = 30;
      private static final long TIME_SECONDS_FIVE = 5 ;
      /**
       * 每个key的过期时间 {@link LockContent}
       */
      private Map lockContentMap = new ConcurrentHashMap<>(512);
      /**
       * redis执行成功的返回
       */
      private static final Long EXEC_SUCCESS = 1L;
      /**
       * 获取锁lua脚本, k1:获锁key, k2:续约耗时key, arg1:requestId,arg2:超时时间
       */
      private static final String LOCK_SCRIPT = "if redis.call('exists', KEYS[2]) == 1 then ARGV[2] = math.floor(redis.call('get', KEYS[2]) + 10) end " +
              "if redis.call('exists', KEYS[1]) == 0 then " +
                 "local t = redis.call('set', KEYS[1], ARGV[1], 'EX', ARGV[2]) " +
                 "for k, v in pairs(t) do " +
                   "if v == 'OK' then return tonumber(ARGV[2]) end " +
                 "end " +
              "return 0 end";
      /**
       * 释放锁lua脚本, k1:获锁key, k2:续约耗时key, arg1:requestId,arg2:业务耗时 arg3: 业务开始设置的timeout
       */
      private static final String UNLOCK_SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
              "local ctime = tonumber(ARGV[2]) " +
              "local biz_timeout = tonumber(ARGV[3]) " +
              "if ctime > 0 then  " +
                 "if redis.call('exists', KEYS[2]) == 1 then " +
                     "local avg_time = redis.call('get', KEYS[2]) " +
                     "avg_time = (tonumber(avg_time) * 8 + ctime * 2)/10 " +
                     "if avg_time >= biz_timeout - 5 then redis.call('set', KEYS[2], avg_time, 'EX', 24*60*60) " +
                     "else redis.call('del', KEYS[2]) end " +
                 "elseif ctime > biz_timeout -5 then redis.call('set', KEYS[2], ARGV[2], 'EX', 24*60*60) end " +
              "end " +
              "return redis.call('del', KEYS[1]) " +
              "else return 0 end";
      /**
       * 续约lua脚本
       */
      private static final String RENEW_SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('expire', KEYS[1], ARGV[2]) else return 0 end";
      private final StringRedisTemplate redisTemplate;
      public RedisDistributionLockPlus(StringRedisTemplate redisTemplate) {
          this.redisTemplate = redisTemplate;
          ScheduleTask task = new ScheduleTask(this, lockContentMap);
          // 启动定时任务
          ScheduleExecutor.schedule(task, 1, 1, TimeUnit.SECONDS);
      }
      /**
       * 加锁
       * 取到锁加锁,取不到锁一直等待知道获得锁
       *
       * @param lockKey
       * @param requestId 全局唯一
       * @param expire   锁过期时间, 单位秒
       * @return
       */
      public boolean lock(String lockKey, String requestId, long expire) {
          log.info("开始执行加锁, lockKey ={}, requestId={}", lockKey, requestId);
          for (; ; ) {
              // 判断是否已经有线程持有锁,减少redis的压力
              LockContent lockContentOld = lockContentMap.get(lockKey);
              boolean unLocked = null == lockContentOld;
              // 如果没有被锁,就获取锁
              if (unLocked) {
                  long startTime = System.currentTimeMillis();
                  // 计算超时时间
                  long bizExpire = expire == 0L ? DEFAULT_LOCK_TIMEOUT : expire;
                  String lockKeyRenew = lockKey + "_renew";
                  RedisScript script = RedisScript.of(LOCK_SCRIPT, Long.class);
                  List keys = new ArrayList<>();
                  keys.add(lockKey);
                  keys.add(lockKeyRenew);
                  Long lockExpire = redisTemplate.execute(script, keys, requestId, Long.toString(bizExpire));
                  if (null != lockExpire && lockExpire > 0) {
                      // 将锁放入map
                      LockContent lockContent = new LockContent();
                      lockContent.setStartTime(startTime);
                      lockContent.setLockExpire(lockExpire);
                      lockContent.setExpireTime(startTime + lockExpire * 1000);
                      lockContent.setRequestId(requestId);
                      lockContent.setThread(Thread.currentThread());
                      lockContent.setBizExpire(bizExpire);
                     lockContent.setLockCount(1);
                     lockContentMap.put(lockKey, lockContent);
                     log.info("加锁成功, lockKey ={}, requestId={}", lockKey, requestId);
                     return true;
                 }
             }
             // 重复获取锁,在线程池中由于线程复用,线程相等并不能确定是该线程的锁
             if (Thread.currentThread() == lockContentOld.getThread()
                       && requestId.equals(lockContentOld.getRequestId())){
                 // 计数 +1
                 lockContentOld.setLockCount(lockContentOld.getLockCount()+1);
                 return true;
             }
             // 如果被锁或获取锁失败,则等待100毫秒
             try {
                 TimeUnit.MILLISECONDS.sleep(100);
             } catch (InterruptedException e) {
                 // 这里用lombok 有问题
                 log.error("获取redis 锁失败, lockKey ={}, requestId={}", lockKey, requestId, e);
                 return false;
             }
         }
     }
     /**
      * 解锁
      *
      * @param lockKey
      * @param lockValue
      */
     public boolean unlock(String lockKey, String lockValue) {
         String lockKeyRenew = lockKey + "_renew";
         LockContent lockContent = lockContentMap.get(lockKey);
         long consumeTime;
         if (null == lockContent) {
             consumeTime = 0L;
         } else if (lockValue.equals(lockContent.getRequestId())) {
             int lockCount = lockContent.getLockCount();
             // 每次释放锁, 计数 -1,减到0时删除redis上的key
             if (--lockCount > 0) {
                 lockContent.setLockCount(lockCount);
                 return false;
             }
             consumeTime = (System.currentTimeMillis() - lockContent.getStartTime()) / 1000;
         } else {
             log.info("释放锁失败,不是自己的锁。");
             return false;
         }
         // 删除已完成key,先删除本地缓存,减少redis压力, 分布式锁,只有一个,所以这里不加锁
         lockContentMap.remove(lockKey);
         RedisScript script = RedisScript.of(UNLOCK_SCRIPT, Long.class);
         List keys = new ArrayList<>();
         keys.add(lockKey);
         keys.add(lockKeyRenew);
         Long result = redisTemplate.execute(script, keys, lockValue, Long.toString(consumeTime),
                 Long.toString(lockContent.getBizExpire()));
         return EXEC_SUCCESS.equals(result);
     }
     /**
      * 续约
      *
      * @param lockKey
      * @param lockContent
      * @return true:续约成功,false:续约失败(1、续约期间执行完成,锁被释放 2、不是自己的锁,3、续约期间锁过期了(未解决))
      */
     public boolean renew(String lockKey, LockContent lockContent) {
         // 检测执行业务线程的状态
         Thread.State state = lockContent.getThread().getState();
         if (Thread.State.TERMINATED == state) {
             log.info("执行业务的线程已终止,不再续约 lockKey ={}, lockContent={}", lockKey, lockContent);
             return false;
         }
         String requestId = lockContent.getRequestId();
         long timeOut = (lockContent.getExpireTime() - lockContent.getStartTime()) / 1000;
         RedisScript script = RedisScript.of(RENEW_SCRIPT, Long.class);
         List keys = new ArrayList<>();
         keys.add(lockKey);
         Long result = redisTemplate.execute(script, keys, requestId, Long.toString(timeOut));
         log.info("续约结果,True成功,False失败 lockKey ={}, result={}", lockKey, EXEC_SUCCESS.equals(result));
         return EXEC_SUCCESS.equals(result);
     }
     static class ScheduleExecutor {
         public static void schedule(ScheduleTask task, long initialDelay, long period, TimeUnit unit) {
             long delay = unit.toMillis(initialDelay);
             long period_ = unit.toMillis(period);
             // 定时执行
             new Timer("Lock-Renew-Task").schedule(task, delay, period_);
         }
     }
     static class ScheduleTask extends TimerTask {
         private final RedisDistributionLockPlus redisDistributionLock;
         private final Map lockContentMap;
         public ScheduleTask(RedisDistributionLockPlus redisDistributionLock, Map lockContentMap) {
             this.redisDistributionLock = redisDistributionLock;
             this.lockContentMap = lockContentMap;
         }
         @Override
         public void run() {
             if (lockContentMap.isEmpty()) {
                 return;
             }
             Set> entries = lockContentMap.entrySet();
             for (Map.Entry entry : entries) {
                 String lockKey = entry.getKey();
                 LockContent lockContent = entry.getValue();
                 long expireTime = lockContent.getExpireTime();
                 // 减少线程池中任务数量
                 if ((expireTime - System.currentTimeMillis())/ 1000 < TIME_SECONDS_FIVE) {
                     //线程池异步续约
                     ThreadPool.submit(() -> {
                         boolean renew = redisDistributionLock.renew(lockKey, lockContent);
                         if (renew) {
                             long expireTimeNew = lockContent.getStartTime() + (expireTime - lockContent.getStartTime()) * 2 - TIME_SECONDS_FIVE * 1000;
                             lockContent.setExpireTime(expireTimeNew);
                         } else {
                             // 续约失败,说明已经执行完 OR redis 出现问题
                             lockContentMap.remove(lockKey);
             
               }
                     });
                 }
             }
         }
     }
    }

    五、redis主从复制的坑

    redis高可用最常见的方案就是主从复制(master-slave),这种模式也给redis分布式锁挖了一坑。

    redis cluster集群环境下,假如现在A客户端想要加锁,它会根据路由规则选择一台master节点写入key mylock,在加锁成功后,master节点会把key异步复制给对应的slave节点。

    如果此时redis master节点宕机,为保证集群可用性,会进行主备切换,slave变为了redis master。B客户端在新的master节点上加锁成功,而A客户端也以为自己还是成功加了锁的。

    此时就会导致同一时间内多个客户端对一个分布式锁完成了加锁,导致各种脏数据的产生。

    至于解决办法嘛,目前看还没有什么根治的方法,只能尽量保证机器的稳定性,减少发生此事件的概率。

    小结一下:上面就是我在使用Redis 分布式锁时遇到的一些坑,有点小感慨,经常用一个方法填上这个坑,没多久就发现另一个坑又出来了,其实根本没有什么十全十美的解决方案,哪有什么银弹,只不过是在权衡利弊后,选一个在接受范围内的折中方案而已。

    本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系bd@zhengruan.com
    作者最新文章
    编程开发
    相关文章 更多
    php72w redis,docker php7安装php-redis
    php72w redis,docker php7安装php-redis

    在Docker环境下为PHP安装Redis扩展:手动编译需下载源码、安装phpize、配置编译并安装,生成redis.so并在php.d目录新建redis.ini加载,重启PHP-FPM;也可直接yum安装php72w-pecl-redis,免手动编译。

    php 如何配置 redis,php 如何设置redis
    php 如何配置 redis,php 如何设置redis

    配置PHP的Redis扩展:先安装Redis服务,从GitHub下载phpredis源码,用phpize编译安装,修改php.ini添加extension=redis.so,重启php-fpm或Apache,通过phpinfo验证并连接Redis。

    c++如何将结构体序列化为Redis兼容的RESP协议格式【深度】
    c++如何将结构体序列化为Redis兼容的RESP协议格式【深度】

    C++结构体无法直接序列化为RESP协议,需手动将字段映射为*N数组嵌套$lenbulkstring形式。整数需转换为字符串并加:前缀,空字符串用$0\r\n,null用$-1\r\n。长度计算需精确到字节数,特别注意二进制安全,同时确保字段顺序与结构体定义一致。

    如何在Golang架构中部署Redict作为高能数据临时存储中转站
    如何在Golang架构中部署Redict作为高能数据临时存储中转站

    Redis作为跨服务临时状态枢纽,需按消息队列、缓存和原子状态三重角色设计。选型用List实现任务分发及延迟重试,Stream用于事件广播与位点回溯。go-redis/v8需配置MinIdleConns、MaxRetries及超时。数据写入应JSON序列化并带type标签、设合理TTL、将关键字段提至顶层。消费通过原子GET+DEL或processedkey

    TP8.0 高并发下库存超卖?Redis 原子递减结合数据库乐观锁控制
    TP8.0 高并发下库存超卖?Redis 原子递减结合数据库乐观锁控制

    在高并发秒杀场景下,利用Redis的减库存原子操作预扣减库存拦截超卖请求,再通过数据库乐观锁(版本号与库存校验)确保最终一致性,异常时回滚Redis库存并借助Lua脚本保证原子性,从而有效防止库存超卖。

    如何在 Go 中利用 go-redis 执行 Lua 脚本优化逻辑
    如何在 Go 中利用 go-redis 执行 Lua 脚本优化逻辑

    在Go中使用go-redis执行Lua脚本可借助原子性解决竞态问题,通过redis.Script封装脚本并自动处理SHA1缓存与重试。需注意KEYS必须为非空切片且所有键在Cluster模式下落在同一slot,ARGV以字符串传入并由tonumber转换。返回值需先检查错误再调用Val(),避免panic。

    TP6.0 基于 Redis SetNX 实现的简单互斥锁机制【并发】
    TP6.0 基于 Redis SetNX 实现的简单互斥锁机制【并发】

    先看一个典型场景:电商秒杀、库存扣减、定时任务重复调度——这些并发冲突在 TP6 应用里屡见不鲜。用 Redis 的 SETNX 做互斥锁,确实是轻量又直接的方案,但“能用”和“稳妥”之间,隔着三层硬功夫:原子加锁、身份校验、合理超时。这三条,差一条就翻车。 很多人第一次上手,直接写 SETNX 加

    TP6.0 如何处理高并发抢购?Redis 原子锁与队列削峰详解【秒杀】
    TP6.0 如何处理高并发抢购?Redis 原子锁与队列削峰详解【秒杀】

    高并发抢购中,Redis的DECR原子操作可安全扣库存;配合think-queue异步削峰,接口仅扣库存和推消息;分布式锁需用SETNXEX加唯一标识,释放时用Lua脚本校验;成功资格后须以数据库事务双写为准,失败时补回Redis库存。

    Golang 实现基于 Redis 的延时任务队列
    Golang 实现基于 Redis 的延时任务队列

    基于Redis的ZSET实现延时队列需解决并发重复消费、失败重试混乱、精度不足及内存爆炸等问题。通过Lua脚本保证取删原子性,使用毫秒级时间戳,对临时错误采用指数退避重试,永久错误记入独立哈希表,并限制最大延迟、定期清理过期任务、使用紧凑序列化格式。

    RedisInsight怎么修改Key的过期时间 Redis操作方法【步骤】
    RedisInsight怎么修改Key的过期时间 Redis操作方法【步骤】

    操作 Redis 的时候,改个 Key 的过期时间其实是个高频场景。特别是做运维排查或者临时调试那会儿,谁也不想反复切命令行窗口、背那些命令参数。好在 RedisInsight 这个图形工具把这事儿做得很顺手——三种方式任选,总有一种合你胃口。 在动手前,先确认一下 Key 当前的 TTL 状态是个

    查看更多
    精品专题 更多
    装机必备
    装机必备

    正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

    Windows
    Windows

    正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

    PDF教程
    PDF教程

    正软商城PDF教程频道提供PDF编辑、转换、合并、拆分、压缩及格式处理方法,同时介绍常用PDF软件和工具的使用技巧。

    Mac软件 更多
    讯飞智作 网页版
    讯飞智作 网页版
    Windows/ macOS/ Linux

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    火山引擎 网页版
    火山引擎 网页版
    Windows/ macOS/ Linux

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    Aperty macOS版
    Aperty macOS版
    macOS

    Aperty 是 Skylum 推出的 AI 人像修图软件,支持自然肤质修饰、瑕疵处理、面部与身体调整、预设及批量同步。

    WINDOWS 更多
    讯飞智作 网页版
    讯飞智作 网页版
    Windows/ macOS/ Linux

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    火山引擎 网页版
    火山引擎 网页版
    Windows/ macOS/ Linux

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    PixTV 网页版
    PixTV 网页版
    Windows

    PixTV 是一款在浏览器中使用的 AI 视觉内容创作平台,提供图片、视频、音频、文字生成、AI 分镜、局部重绘、提示词反推和无限画布等工具,适合短剧、漫剧、广告及社交媒体内容制作。

    即将离开本站
    您即将前往第三方网站,请确认是否继续?