分布式锁实现方案

分布式锁

1 什么是分布式锁

​ 就是在分布式环境下,保证某个公共资源只能在同一时间被多进程应用的某个进程的某一个线程访问时使用锁。

2 几个使用场景分析

一段代码同一时间只能被同一个不同进程的一个线程执行

  • 库存超卖 (库存被减到 负数),上面案例就是库存超卖

  • 定时任务

  • 分布式缓存中缓存同步

  • 转账(多个进程修改同一个账户)

3 需要什么样的分布式锁-特征

  • 可以保证在分布式部署的应用集群中同一个方法在同一时间只能被一台机器上的一个线程执行。(互斥性)

  • 这把锁要是一把可重入锁(避免死锁)(重入性)

  • 这把锁最好是一把阻塞锁(自旋)(根据业务需求考虑要不要这条)

  • 这把锁最好是一把公平锁(根据业务需求考虑要不要这条)

  • 获取锁和释放锁的性能要好

4 常见的分布式锁解决方案

1.4.1. 思路

  • 当在分布式模型下,数据只有一份(或有限制),此时需要利用锁的技术控制某一时刻修改数据的进程数。

  • 与单机模式下的锁不仅需要保证进程可见,还需要考虑进程与锁之间的网络问题。

  • 分布式锁还是可以将标记存在公共内存(redis),只是该内存不是某个进程分配的内存而是公共内存如 Redis、Memcache。至于利用数据库、文件(oss),zk等做锁与单机的实现是一样的,只要保证标记能互斥就行。

    在多个进程公共能够访问地方放个标识!一个进程的某个线程进去时,标识已经进去(获取到锁),其他线程就要等待!直到原来的线程释放,新的线程才能可以获取锁.

1.4.2. 分布式锁三种方式

  • 基于数据库操作

  • 基于redis缓存和过期时间

  • 基于zookeeper 临时顺序节点+watch

​ 从理解的难易程度角度(从低到高)数据库 > redis > Zookeeper

​ 从实现的复杂性角度(从低到高)数据库> redis >Zookeeper

​ 从性能角度(从高到低)redis > Zookeeper > 数据库

​ 从可靠性角度(从高到低)Zookeeper > redis > 数据库

Zookeeper >redis>数据库(基本不用)

基于数据库基本不用,zk或redis要根据项目情况来决定,如果你项目本来就用到zk,就使用zk,否则redis

分布式环境互斥实现

1 数据库锁

1.1 悲观锁 innodb行锁

  • 共享锁(S Lock):允许事务读一行数据,具有锁兼容性质,允许多个事务同时获得该锁。
  • 排它锁(X Lock):允许事务删除或更新一行数据,具有排它性,某个事务要想获得锁,必须要等待其他事务释放该对象的锁。

X锁和其他锁都不兼容,S锁值和S锁兼容,S锁和X锁都是行级别锁,兼容是指对同一条记录(row)锁的兼容性情况。

Mysql innodb锁的默认操作:

  • 我们对某一行数据进行查询是会默认使用S锁加锁,如果硬是要把查询也加X锁使用
 	@Select("select * from t_goods where id = #{id}")
    Goods laodByIdForUpdate(Long id);
  • 读的时候硬是要加x锁
  @Select("select * from t_goods where id = #{id} **for update**")
    Goods laodByIdForUpdate(Long id); 
  • 当我们对某一行数据进行增删改是会加X锁

1.2 乐观锁

​ 乐观锁是相对悲观锁而言的,乐观锁假设数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则返回给用户错误的信息,让用户决定如何去做。

​ 直接用:表中添加一个时间戳或者版本号的字段来实现,update account set version = version + 1 where id = #{id} and version = #{oldVersion} 当更新不成功,客户端重试,重新读取最新的版本号或时间戳,再次尝试更新,类似 CAS 机制,推荐使用。

2 分布式锁

疑问?既然可以使用数据库悲观锁和乐观锁保证分布式环境的互斥!那为什么还要分布式锁!

有的操作是没有数据库参与的,又想分布式环境互斥! 就必须使用分布式锁!

2.1 基于数据库的

  • 方案1 主键

    主键不能重复

      //基于数据库的分布式锁实现
    public class DbGoodsLock {
    
        private Long goodsId = null;
        public DbGoodsLock(Long goodsId) {
            this.goodsId = goodsId;
        }
    
        /**
         * 能插入就能获取获取锁
         * @return
         */
        public  boolean  trylock(){
            Connection connection = null;
            try{
               connection  = JDBCUtils.getConnection();
                Statement statement = connection.createStatement();
    
                statement.execute("insert into t_goods_lock(id) values("+this.goodsId+")");
                System.out.println(Thread.currentThread().getName()+"加锁,插入数据 goodsId="+goodsId);
                return true;
            }catch (Exception e) {
                //e.printStackTrace();
                System.out.println(Thread.currentThread().getName()+"加锁异常====================:"+e.getMessage());
                return false;
            }
            finally {
                if (connection != null) {
                    try {
                        connection.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    
        //阻塞获取锁
        public  void lock(){
            if (trylock())
                return;
    
            try {
                Thread.sleep(10);
                System.out.println("尝试获取锁...");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            lock();
        }
    
    
        //释放锁
        public  boolean  unlock(){
            Connection connection = null;
            try{
                connection  = JDBCUtils.getConnection();
                Statement statement = connection.createStatement();
    
                statement.execute("delete from t_goods_lock where id = "+goodsId);
                System.out.println(Thread.currentThread().getName()+"解锁,删除数据 goodsId="+goodsId);
                return true;
            }catch (Exception e) {
                System.out.println(Thread.currentThread().getName()+"解锁异常====================:"+e.getMessage());
                //e.printStackTrace();
                return false;
            }
            finally {
                if (connection != null) {
                    try {
                        connection.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    
    
  • 方案2

    唯一字段不能重复,和上面原来一样

  • l 数据库是单点?搞两个数据库,数据之键双向同步,一旦挂掉快速切换到备库上。 主备切换

  • l 没有失效时间?只要做一个定时任务,每隔一定时间把数据库中的超时数据清理一遍。

  • l 非阻塞的?搞一个 while 循环,直到 insert 成功再返回成功。

  • l 非重入的?在数据库表中加个字段,记录当前获得锁的机器的主机信息和线程信息,那么下次再获取锁的时候先查询数据库,如果当前机器的主机信息和线程信息在数据库可以查到的话,直接把锁分配给他就可以了。

    n 获取:再次获取锁的同时更新count(+1).

    n 释放:更新count-1,当count==0删除记录。

    l 非公平的?-mq

​ 数据库实现分布式锁,一般都很少用

2.2 redis

方案1:原生

1 setnx(如果不存在才设置成功)+del没有可以就添加  setnx goods_id = 1   del goods_id
2 expire+watchdog续约时间(不好做,我不做)
3 value是uuid,获取判断,删除
4 lua脚本
public interface IDistributedLock {
    /**
     * 自旋上锁
     */
    void lock();

    /**
     * 释放锁
     */
    void unlock();

    /**
     * 尝试获取锁
     */
    boolean tryLock();
}

public class RedisLock implements IDistributedLock {

    private String resourceName;
    private String lockVal;  //try del都有用到uuid,所以构造的时候产生一个成员变量
    private RedisTemplate redisTemplate;

    //不交给spring管理就一般用不了RedisTemplate
    public RedisLock(String resourceName) {
        this.resourceName = resourceName;
        this.lockVal = UUID.randomUUID().toString();
        ApplicationContext context = ApplicationContextHolder.getApplicationContext();
        redisTemplate = (RedisTemplate) context.getBean("redisTemplate");
    }

    @Override
    public void lock() {
        if (tryLock())
            return;
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        lock();
    }


    @Override
    public void unlock() {
       //get check del--->lua   key value
        /*if redis.call('get', KEYS[1]) == KEYS[2] then
           return redis.call('del', KEYS[1])
        else
            return 0
            end*/

        List<String> params = Arrays.asList(resourceName, lockVal); //goods_1 jfjjfjflfjof
        redisTemplate.execute(redisScript(),params);

    }

    public RedisScript<Long> redisScript(){
        DefaultRedisScript<Long> script = new DefaultRedisScript<>();
        script.setResultType(Long.class);
        //script.setScriptSource(new ResourceScriptSource(new ClassPathResource("redis.lua")));
        script.setScriptText("if redis.call('get', KEYS[1]) == KEYS[2] then return redis.call('del', KEYS[1]) else return 0 end");
        return script;
    }

    @Override
    public boolean tryLock() {
        //uuid setnx expire
        //hash K(resourceName) k(uuid) v(count)+
//        redisTemplate.opsForHash().putIfAbsent(resourceName,lockVal,1);
//        redisTemplate.expire(resourceName,3,TimeUnit.SECONDS);
        Boolean result = redisTemplate.opsForValue().setIfAbsent(resourceName, lockVal, 10, TimeUnit.SECONDS);
        System.out.println(resourceName);
        System.out.println(lockVal);
        return  result;
    }
}


@Service
public class GoodsServiceImpl_redis_Lock implements IGoodsService {

//    @Autowired
//    private IDistributedLock lock;
    @Autowired
    private GoodsMapper goodsMapper;
    @Override
    public Goods getById(Long id) {
        return goodsMapper.laodById(1L);
    }

    @Override
    @Transactional
    public void updateNum(Map<String,Object> params) {
        Long goodsId = (Long) params.get("id");
        Integer num = (Integer) params.get("num");
        String resourceName = "goods_"+goodsId;
        IDistributedLock lock = new RedisLock(resourceName);
        try{
            lock.lock();
            System.out.println(Thread.currentThread().getName()+" get lock!");
            Goods goods = goodsMapper.laodById(goodsId);
            Thread.sleep(4000);
            System.out.println(goods);
            System.out.println(Thread.currentThread().getName()+goods.getCount()+":"+num);
            if (goods.getCount()>=num){
                goodsMapper.updateNum(params);
                System.out.println(Thread.currentThread().getName()+"buy "+num+"!");

            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            if (lock != null) {
                lock.unlock();
            }
        }


    }
}

方案2:框架实现

​ 业界也提供了多个现成好用的框架予以支持分布式锁,比如Redisson、spring-integration-redis,redlock redisson,redlock 底层原理

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.2.3</version>
</dependency>
@Value("${spring.redis.host}")
    private String redisHost;

    @Value("${spring.redis.port}")
    private int redisPort;

    @Value("${spring.redis.database}")
    private int redisdatabase;

    @Value("${spring.redis.password}")
    private String redisPassword;

    @Bean
    public RedissonClient redissonClient() {
        Config config = new Config();
        config.useSingleServer().setAddress(redisHost + ":" + redisPort);
        config.useSingleServer().setDatabase(redisdatabase);
        config.useSingleServer().setPassword(redisPassword);
        RedissonClient redisson = Redisson.create(config);
        return redisson;
    }
@Service
public class GoodsServiceImpl_redission implements IGoodsService {

    @Autowired
    private GoodsMapper goodsMapper;

    @Autowired
    private RedissonClient redissonClient;

    @Override
    public void updateNum(Map<String,Object> params) {
        Long goodsId = (Long) params.get("id");
        Integer num = (Integer) params.get("num");
        System.out.println(Thread.currentThread().getName()+"enter!");
        String resourceName = "goods" + goodsId;
        RLock rLock = redissonClient.getLock(resourceName);
        try{
            rLock.lock();
            System.out.println(Thread.currentThread().getName()+" get lock!");
            Goods goods = goodsMapper.laodById(goodsId);
            System.out.println(goods);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(goods.getCount()+":"+num);
            if (goods.getCount()>=num){
                goodsMapper.updateNum(params);
                System.out.println(Thread.currentThread().getName()+"buy "+num+"!");

            }

        }catch (Exception e){
            e.printStackTrace();
        }
        finally {
            if (rLock != null) {
                rLock.unlock();
                System.out.println(Thread.currentThread().getName()+" unlock!");
            }
        }


    }

    @Override
    public Goods getById(Long id) {
        return goodsMapper.laodById(1L);
    }
}

2.3 zk

2.3.1 zk理论
2.3.1.1 是什么?

ZooKeeper是Apache下的一个Java开源项目(最初由Yahoo开发, 后捐献给了Apache)。

ZooKeeper的原始功能很简单,基于它的层次型的目录树的数据结构,并通过对树上的节点进行有效管理,可以设计出各种各样的分布式集群管理功能。此外, ZooKeeper本身 也是分布式的。

2.3.1.2 数据库模型

Zookeeper会维护一个具有层次关系的树状的数据结构,它非常类似于一个标准的文件系统,如下图所

示:同一个目录下不能有相同名称的节点
在这里插入图片描述

2.3.1.3 节点分类

ZooKeeper 节点是有生命周期的这取决于节点的类型,在 ZooKeeper 中,节点类型可以分为持久节点(PERSISTENT )、临时节点(EPHEMERAL),以及时序节点(SEQUENTIAL ),具体在节点创建过程中,一般是组合使用,可以生成以下 4 种节点类型。 是否持久化,是否有序

  • 持久节点(PERSISTENT)与临时节点(EPHEMERAL)
    所谓持久节点,是指在节点创建后,就一直存在,直到有删除操作来主动清除这个节点——不会因为创建该节点的客户端会话失效而消失。

    和持久节点不同的是,临时节点的生命周期和客户端会话绑定。也就是说,如果客户端会话失效,那么这个节点就会自动被清除掉。注意,这里提到的是会话失效,而非连接断开。另外,在临时节点下面不能创建子节点。

  • 顺序节点(SEQUENTIAL) 无序节点
    这类节点的基本特性和上面的节点类型是一致的。额外的特性是,在ZK中,每个父节点会为他的第一级子节点维护一份时序,会记录每个子节点创建的先后顺序。基于这个特性,在创建子节点的时候,可以设置这个属性,那么在创建节点过程中,ZK会自动为给定节点名加上一个数字后缀,作为新的节点名。这个数字后缀的范围是整型的最大值。 00010001 00010002 00010003

    无序节点就是没有顺序

    具体组合得到四种:持久有序 持久无序 临时有序 临时无序

2.3.2 入门
2.3.2.1 安装
  • 官方下载地址:http://mirrors.cnnic.cn/apache/zookeeper/ , 下载后获得,解压即可安装。

  • 安装配置: 把conf目录下的zoo_sample.cfg改名成zoo.cfg,这里我是先备份了zoo_sample.cfg再改的名。修改zoo.cfg的值如下:

    dataDir=D:/zookeeper-3.4.9/data/data

    dataLogDir=D:/zookeeper-3.4.9/data/log

  • 启动 :点击bin目录下的zkServer.cmd 这时候出现下面的提示就说明配置成功了。

  • 图形界面-ZooViewer:https://blog.csdn.net/u010889616/article/details/80792912

2.3.2.2 代码测试-使用代码创建各种节点
<!-- https://mvnrepository.com/artifact/com.101tec/zkclient -->
<dependency>
    <groupId>com.101tec</groupId>
    <artifactId>zkclient</artifactId>
    <version>0.10</version>
</dependency>

API总结:

  • l new ZkClient(“127.0.0.1:2181”,5000); 创建zookeeper客户端

  • l client.getChildren(“/”):获取子节点 “/”代表根节点

  • l client.createPersistent:创建持久节点

  • l client.createPersistentSequential:创建持久有顺节点,会在path后面增加序号

  • l client.createEphemeral:创建临时节点

  • l client.createEphemeralSequential:创建临时有序节点

  • l client.subscribeChildChanges:订阅子节点的改变

  • l client.subscribeDataChanges:订阅某个节点的数据改变

@Test //持久化节点
public void test1() throws Exception {
    //创建客户端
    ZkClient client = new ZkClient("127.0.0.1:2181",5000);
    //获取根节点
    List<String> children = client.getChildren("/");
    for (String child : children) {
        System.out.println(child);  //zookeeper
    }

    //创建持久节点
   client.createPersistent("/zookeeper/createPersistent");


    //创建持久顺序节点
    String persistentSequential = 
client.createPersistentSequential("/zookeeper/createPersistentSequential", "111");

    System.out.println("persistentSequential="+persistentSequential);    
//  /zookeeper/createPersistentSequential0000000003

    //创建临时节点
    client.createEphemeral("/zookeeper/createEphemeral");
    //client.createEphemeral("/zookeeper/createEphemeral"); //重复创建会报错

    //创建临时顺序节点
    String ephemeralSequential = 
client.createEphemeralSequential("/zookeeper/createEphemeralSequential", "111");
    System.out.println("ephemeralSequential="+ephemeralSequential);

    //关闭
    client.close();
}
//测试监听
    @Test
    public void test3() throws Exception {
        //创建客户端
        ZkClient client = new ZkClient("127.0.0.1:2181",5000);

        if(!client.exists("/yhptest")){
            client.createPersistent("/yhptest");
        }
        //操作节点
        client.createPersistentSequential("/yhptest/test","x1");
        client.createPersistentSequential("/yhptest/test","x2");
        client.createPersistentSequential("/yhptest/test","x3");
        client.createPersistentSequential("/yhptest/test","x4");
        client.createPersistent("/yhptest/tests","aa");
        List<String> children = client.getChildren("/yhptest");
        for (String child : children) {
            System.out.println(child);
        }
        //关闭
        client.subscribeChildChanges("/yhptest", new IZkChildListener() {
            @Override
            public void handleChildChange(String s, List<String> list) throws Exception {
                System.out.println("子节点改变:"+s);
                System.out.println("子节点改变:"+list);
            }
        });
        client.subscribeDataChanges("/yhptest/tests", new IZkDataListener() {
            @Override
            public void handleDataChange(String s, Object o) throws Exception {
                //数据改变
                System.out.println("数据改变:"+s);
                System.out.println("数据改变:"+o.toString());
            }
            @Override
            public void handleDataDeleted(String s) throws Exception {
                System.out.println("数据删除");
            }
        });

        client.delete("/yhptest/tests");
        Thread.sleep(2000);
        client.close();
    }
2.3.3 zk分布式锁-原生

在这里插入图片描述

2.3.3.1 非公平锁

​ 根据Zookeeper的临时节点的特性实现分布式锁,先执行的线程在zookeeper创建一个临时节点,代表获取到锁,后执行的线程需要等待,直到临时节点被删除说明锁被释放,第二个线程可以尝试获取锁。

T1 创建临时无序节点goods_2 执行业务逻辑 关闭

T2 创建临时无序节点goods_2 执行业务逻辑 关闭

2.3.3.2 公平锁

在这里插入图片描述

public class ZookeeperDistributedLock implements ExampleLock {


    ZkClient client = new ZkClient("127.0.0.1:2181",
            5000);
    CountDownLatch cdl = new CountDownLatch(1); //不为零阻塞住,不让他往下走

    //父节点路径
    String parent = "";
    //当前节点路径
    String currentPath = "";

    //1 goods
    // lock_goods_id 父节点(持久节点)
    // lock_goods_id_001
    // lock_goods_id_002
    @Override
    public void lock(String resourceName) {

        parent = "/"+resourceName;
        //判断父节点是否存在,如果不存在要创建一个持久节点
        if (!client.exists(parent)){
            client.createPersistent(parent,"root");
        }

        //前面的节点都处理完成,自己变成第一个节点才加锁成功。
        if (!tryLock(resourceName)){
            lock(resourceName);
        }

    }

    @Override
    public void unlock(String resourceName) {

        //自己操作完毕,删除自己,让下一个节点执行。
        System.out.println(currentPath);
        System.out.println(System.currentTimeMillis());
        System.out.println(client.delete(currentPath));
        client.close();
    }

    @Override
    public boolean tryLock(String resourceName) {
        //创建子节点-临时顺序节点
        if (StringUtils.isEmpty(currentPath)){
            currentPath = client
                    .createEphemeralSequential(parent + "/test", "test"); //test0001
        }
        //如果是第一个节点,就获取到锁了。
        List<String> children = client.getChildren(parent);
        System.out.println(currentPath+"jjj");
        for (String child : children) {
            System.out.println(child);
        }
        Collections.sort(children);

        ///goods_1/test0000000003jjj
        //test0000000003
        if (currentPath.contains(children.get(0))){
            return true;
        }else{
            //如果不是第一个节点,监听前一个节点,要再这儿等待,知道被触发,再次判断是否是第一个节点进行返回就OK
            String str = currentPath.substring(
                    currentPath.lastIndexOf("/")+1);
            System.out.println(str);
            int preIndex = children.indexOf(str)-1;
            String prePath = parent+"/"+children.get(preIndex);
//监听上一个节点,如果上一个节点被删除,把秒表设置为 0 (cdl.countDown();),那么当前节点取消等待(cdl.await();)重新获取锁

            client.subscribeDataChanges(prePath, new IZkDataListener() {
                @Override
                public void handleDataChange(String dataPath, Object data) throws Exception {

                }

                @Override
                public void handleDataDeleted(String dataPath) throws Exception {
                    //让他-1 变为零
                    cdl.countDown();
                }
            });

            //一直等待,直到自己变成第一个节点
            try {
                cdl.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            return false;

        }
    }
}
2.3.4 zk分布式锁 curator框架实现
  • 导入jar
<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-recipes</artifactId>
    <version>4.1.0</version>
</dependency>

  • 配置

    @Configuration
    public class ZkCuratorConfig {
        //初始化方法start
        @Bean(initMethod = "start",destroyMethod = "close") //bean声明周期  构造  初始化initMethod  使用  销毁destroyMethod
        public CuratorFramework curatorFramework(){
            //重试策略
            RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
            //创建客户端
            CuratorFramework client = CuratorFrameworkFactory.newClient("127.0.0.1:2181", retryPolicy);
            return client;
        }
    }
    
    
  • 代码

@Autowired
    private CuratorFramework framework;
    @Override
    public void updateNum(Map<String,Object> params) {
        Long goodsId = (Long) params.get("id");
        Integer num = (Integer) params.get("num");

        String resourceName = "/goods_"+goodsId;
        InterProcessMutex mutex = null;
        try{
            mutex = new InterProcessMutex(framework, resourceName); //1个信号量
            mutex.acquire(3, TimeUnit.SECONDS); //获取一个,  自旋(拿不到一直循环)   适应性自选(拿几秒就算乐 )
            System.out.println(Thread.currentThread().getName()+" get lock!");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(System.currentTimeMillis());
            //通过商品id获取商品  3
            Goods goods = goodsMapper.laodById(goodsId);
            System.out.println(goods);
            System.out.println(goods.getCount()+":"+num);
            if (goods.getCount()>=num){
                goodsMapper.updateNum(params);
                System.out.println(Thread.currentThread().getName()+"buy "+num+"!");

            }
        }catch (Exception e){
            e.printStackTrace();
        }
        finally {
            if (mutex != null) {
                try {
                    mutex.release();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }


    }

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【机器学习】【深度学习】MXnet神经网络图像风格迁移学习简介

使用部分 一、编程环境 编程环境使用Windows11上的Anaconda环境&#xff0c;Python版本为3.6. 关于Conda环境的建立和管理&#xff0c;可以参考我的博客&#xff1a;【Anaconda】【Windows编程技术】【Python】Anaconda的常用命令及实操 二、项目结构&#xff08;代码非原创…

CTF-pwn-虚拟化-【d3ctf-2021-d3dev】

文章目录 参考流程附件检查启动信息逆向分析漏洞查看设备配置信息exp 参考 https://x1ng.top/2021/11/26/qemu-pwn/ https://bbs.kanxue.com/thread-275216.htm#msg_header_h1_0 https://xz.aliyun.com/t/6562?time__1311n4%2BxnD0DRDBAi%3DGkDgiDlhjmYh2xuCllx7whD&alic…

Opencv学习项目2——pytesseract

上一次我们使用pytesseract.image_to_boxes来检测字符&#xff0c;今天我们使用pytesseract.image_to_data来检测文本并显示 实战教程 和上一次一样&#xff0c;添加opencv-python和pytesseract库 首先我们先来了解一下pytesseract.image_to_data pytesseract.image_to_data(…

无人值守工厂设备日志采集工具

免费试用下载: Gitee下载 最新版本 优势: A. 开箱即用. 解压直接运行.不需额外安装. B. 批管理设备. 设备配置均在后台管理. C. 无人值守 客户端自启动,自更新. D. 稳定安全. 架构简单,内存占用小,通过授权访问.

Exposure X7软件安装包下载 丨不限速下载丨亲测好用

根据使用者情况表明Exposure的设计鼓励您进行创造性的工作&#xff0c;使用涂刷和遮罩工具将效果有选择地应用于图片的特定区域&#xff0c;非破坏性图层使您能够混合预设和调整&#xff0c;以获得无尽的外观。我们都知道Exposure是用于创意照片编辑的最佳图片编辑器&#xff0…

【机器学习】使用Python实现图神经网络(GNN):图结构数据的分析与应用

&#x1f525; 个人主页&#xff1a;空白诗 文章目录 一、引言二、图神经网络的基础知识1. 图的基本概念和术语2. 传统的图分析方法3. 图神经网络的基本原理4. GNN的基本模型 三、主要的图神经网络模型1. 图卷积网络&#xff08;Graph Convolutional Network, GCN&#xff09;2…

086. 分隔链表

题目链接 一、题目描述 (一) 题目 给你一个链表的头节点 head 和一个特定值 x &#xff0c;请你对链表进行分隔&#xff0c;使得所有 小于 x 的节点都出现在 大于或等于 x 的节点之前。你应当保留两个分区中每个节点的初始相对位置。 (二) 示例 示例 1&#xff1a; 输入&a…

2024.6.16 机器学习周报

目录 引言 Abstract 文献阅读 1、题目 2、引言 3、创新点 4、匹配问题 5、SuperGlue架构 5.1、注意力图神经网络&#xff08;Attentional Graph Neural Network&#xff09; 5.2、最佳匹配层&#xff08;Optimal matching layer&#xff09; 5.3、损失 6、实验 6.…

数据分析第三讲:numpy的应用入门(二)

NumPy的应用&#xff08;二&#xff09; 数组对象的方法 获取描述统计信息 描述统计信息主要包括数据的集中趋势、离散程度和频数分析等&#xff0c;其中集中趋势主要看均值和中位数&#xff0c;离散程度可以看极值、方差、标准差等&#xff0c;详细的内容大家可以阅读《统计…

【Java】已解决java.sql.SQLException异常

文章目录 一、分析问题背景二、可能出错的原因三、错误代码示例四、正确代码示例五、注意事项 已解决java.sql.SQLException异常 在Java中&#xff0c;java.sql.SQLException是一个通用的异常类&#xff0c;用于表示在数据库操作中发生的错误。无论是类型错误、数据类型不匹配…

YOLOv10改进 | 注意力篇 | YOLOv10引入iRMB

1. iRMB介绍 1.1 摘要:本文重点关注开发现代、高效、轻量级的模型来进行密集预测,同时权衡参数、FLOP 和性能。 反向残差块(IRB)作为轻量级 CNN 的基础设施,但基于注意力的研究尚未认识到对应的部分。 这项工作从统一的角度重新思考高效IRB和Transformer有效组件的轻量级…

国际版游戏陪练源码电竞系统源码支持Android+IOS+H5

&#x1f3ae;电竞之路的得力助手 一、引言&#xff1a;电竞新纪元&#xff0c;陪练小程序助力成长 在电竞热潮席卷全球的今天&#xff0c;每一个电竞爱好者都渴望在竞技场上脱颖而出。然而&#xff0c;独自一人的游戏之路往往充满了挑战和困难。幸运的是&#xff0c;国际版游…

Flutter框架高阶——Window应用程序设置窗体窗口背景完全透明

文章目录 1.修改 main.cpp1&#xff09;C 与 Win32 API2&#xff09;EnableTransparency()3&#xff09;中文注释 2.编写 Flutter 代码1&#xff09;bitsdojo_window2&#xff09;window_manager3&#xff09;区别对比4&#xff09;同时使用&#xff08;1&#xff09;设置初始化…

全球AI视频技术竞赛加速:Runway即将推出更优更快的第三代AI视频模型|TodayAI

Runway即将在未来几天推出其更优更快的第三代AI视频模型&#xff0c;这是新一代模型中最小的一个。据公司透露&#xff0c;这款名为Gen-3的模型将带来“在真实度、一致性和动态效果上的重大提升”&#xff0c;同时在速度上也有显著的加快。 去年六月&#xff0c;Runway首次推出…

【每日刷题】Day70

【每日刷题】Day70 &#x1f955;个人主页&#xff1a;开敲&#x1f349; &#x1f525;所属专栏&#xff1a;每日刷题&#x1f34d; &#x1f33c;文章目录&#x1f33c; 1. 922. 按奇偶排序数组 II - 力扣&#xff08;LeetCode&#xff09; 2. 905. 按奇偶排序数组 - 力扣&…

数据库 | 试卷三

1.数据库的网状模型应满足的条件是&#xff08; &#xff09; A&#xff0e;允许一个以上结点无双亲&#xff0c;也允许一个结点有多个双亲 B&#xff0e;必须有两个以上的结点 C&#xff0e;有且仅有一个结点无双亲&#xff0c;其余结点都只有一个双亲 D&#xff0e;每个结…

模拟原神圣遗物系统-小森设计项目,需求分析

需求分析 我操控某个角色的圣遗物时发现&#xff0c;一开始玩啥也不懂慢慢了解&#xff0c;今天才想起要不做一个 &#xff0c;然后开始想需求 跟Ai聊技术 聊着聊着 发现圣遗物 这个东西有点意思 本来今天打算写一下数据库 的外键想起了一些高兴的事情&#xff08;美人鱼&#…

数字孪生技术及其广泛应用场景探讨

通过将实际物理世界中的物体或系统建模、模拟和分析&#xff0c;数字孪生技术可以提供更精确、更可靠、更高效的解决方案。数字孪生技术在智能制造、城市建设、智慧物流等众多领域中得到了广泛的应用。 通过将数据可视化呈现在虚拟环境中&#xff0c;我们可以更清晰地观察和理…

搜索引擎数据库介绍

搜索引擎数据库的定义 搜索引擎数据库是一类专门用于数据内容搜索的NoSQL数据库&#xff0c;是非结构化大数据处理分析领域中重要的角色。搜索引擎数据库使用索引对数据中的相似特征进行归类&#xff0c;并提高搜索能力。通过对索引和检索过程的优化&#xff0c;以处理大量文本…

安装vue时候发现npm淘宝镜像不能使用,报出:npm.taobao.org和registry.npm.taobao.or

2024.3.12 安装vue时候发现npm淘宝镜像不能使用&#xff0c;需要重新更换源&#xff0c;简单来说就是更换镜像 使用 npm config get registry 查看当前的镜像&#xff1b; npm config get registry 使用npm config set registry http://mirrors.cloud.tencent.com/npm/ &…