spring redis Sentinel 哨兵 原理

news2024/11/18 2:29:45

客户端选择

redis 常用的连接客户端 有三个

  1. Jedis:是老牌的Redis的Java实现客户端,提供了比较全面的Redis命令的支持,
  2. Redisson:实现了分布式和可扩展的Java数据结构。
  3. Lettuce:高级Redis客户端,用于线程安全同步,异步和响应使用,支持集群,Sentinel,管道和编码器。

spring data redis

  1. 如果未指定 redis client 则 spring-boot-autoconfigure.jar 默认取 LettuceConnectionConfiguration中的LettuceConnectionFactory
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration

	@Bean
	@ConditionalOnMissingBean(name = "redisTemplate")
	public RedisTemplate<Object, Object> redisTemplate(
			RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
		RedisTemplate<Object, Object> template = new RedisTemplate<>();
		template.setConnectionFactory(redisConnectionFactory);
		return template;
	}

org.springframework.boot.autoconfigure.data.redis.LettuceConnectionConfiguration

	@Bean
	@ConditionalOnMissingBean(RedisConnectionFactory.class)
	public LettuceConnectionFactory redisConnectionFactory(
			ClientResources clientResources) throws UnknownHostException {
		LettuceClientConfiguration clientConfig = getLettuceClientConfiguration(
				clientResources, this.properties.getLettuce().getPool());
		return createLettuceConnectionFactory(clientConfig);
	}


spring redis Sentinel Lettuce 实现

先看 io.lettuce.core.RedisClient

 private <K, V> StatefulRedisSentinelConnection<K, V> connectSentinel(RedisCodec<K, V> codec, RedisURI redisURI,
            Duration timeout) {
        assertNotNull(codec);
        checkValidRedisURI(redisURI);

        ConnectionBuilder connectionBuilder = ConnectionBuilder.connectionBuilder();
        connectionBuilder.clientOptions(ClientOptions.copyOf(getOptions()));
        connectionBuilder.clientResources(clientResources);

        DefaultEndpoint endpoint = new DefaultEndpoint(clientOptions);

        StatefulRedisSentinelConnectionImpl<K, V> connection = newStatefulRedisSentinelConnection(endpoint, codec, timeout);

        logger.debug("Trying to get a Redis Sentinel connection for one of: " + redisURI.getSentinels());

        connectionBuilder.endpoint(endpoint).commandHandler(() -> new CommandHandler(clientOptions, clientResources, endpoint))
                .connection(connection);
        connectionBuilder(getSocketAddressSupplier(redisURI), connectionBuilder, redisURI);

        if (clientOptions.isPingBeforeActivateConnection()) {
            connectionBuilder.enablePingBeforeConnect();
        }

        if (redisURI.getSentinels().isEmpty() && (isNotEmpty(redisURI.getHost()) || !isEmpty(redisURI.getSocket()))) {
            channelType(connectionBuilder, redisURI);
            try {
                getConnection(initializeChannelAsync(connectionBuilder));
            } catch (RuntimeException e) {
                connection.close();
                throw e;
            }
        } else {

            boolean connected = false;
            boolean first = true;
            Exception causingException = null;
            validateUrisAreOfSameConnectionType(redisURI.getSentinels());

            for (RedisURI uri : redisURI.getSentinels()) {
                if (first) {
                    channelType(connectionBuilder, uri);
                    first = false;
                }
                connectionBuilder.socketAddressSupplier(getSocketAddressSupplier(uri));

                if (logger.isDebugEnabled()) {
                    SocketAddress socketAddress = SocketAddressResolver.resolve(uri, clientResources.dnsResolver());
                    logger.debug("Connecting to Redis Sentinel, address: " + socketAddress);
                }
                try {
                    getConnection(initializeChannelAsync(connectionBuilder));
                    connected = true;
                    break;
                } catch (Exception e) {
                    logger.warn("Cannot connect Redis Sentinel at " + uri + ": " + e.toString());
                    causingException = e;
                }
            }

            if (!connected) {
                connection.close();
                throw new RedisConnectionException("Cannot connect to a Redis Sentinel: " + redisURI.getSentinels(),
                        causingException);
            }
        }

        if (LettuceStrings.isNotEmpty(redisURI.getClientName())) {
            connection.setClientName(redisURI.getClientName());
        }

        return connection;
    }

io.lettuce.core.protocol.ConnectionWatchdog

 @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {

        logger.debug("{} channelInactive()", logPrefix());
        if (!armed) {
            logger.debug("{} ConnectionWatchdog not armed", logPrefix());
            return;
        }

        channel = null;

        if (listenOnChannelInactive && !reconnectionHandler.isReconnectSuspended()) {
            scheduleReconnect();
        } else {
            logger.debug("{} Reconnect scheduling disabled", logPrefix(), ctx);
        }

        super.channelInactive(ctx);
    }

    /**
     * Enable {@link ConnectionWatchdog} to listen for disconnected events.
     */
    void arm() {
        this.armed = true;
        setListenOnChannelInactive(true);
    }

    /**
     * Schedule reconnect if channel is not available/not active.
     */
    public void scheduleReconnect() {

        logger.debug("{} scheduleReconnect()", logPrefix());

        if (!isEventLoopGroupActive()) {
            logger.debug("isEventLoopGroupActive() == false");
            return;
        }

        if (!isListenOnChannelInactive()) {
            logger.debug("Skip reconnect scheduling, listener disabled");
            return;
        }

        if ((channel == null || !channel.isActive()) && reconnectSchedulerSync.compareAndSet(false, true)) {

            attempts++;
            final int attempt = attempts;
            int timeout = (int) reconnectDelay.createDelay(attempt).toMillis();
            logger.debug("{} Reconnect attempt {}, delay {}ms", logPrefix(), attempt, timeout);

            this.reconnectScheduleTimeout = timer.newTimeout(it -> {

                reconnectScheduleTimeout = null;

                if (!isEventLoopGroupActive()) {
                    logger.warn("Cannot execute scheduled reconnect timer, reconnect workers are terminated");
                    return;
                }

                reconnectWorkers.submit(() -> {
                    ConnectionWatchdog.this.run(attempt);
                    return null;
                });
            }, timeout, TimeUnit.MILLISECONDS);

            // Set back to null when ConnectionWatchdog#run runs earlier than reconnectScheduleTimeout's assignment.
            if (!reconnectSchedulerSync.get()) {
                reconnectScheduleTimeout = null;
            }
        } else {
            logger.debug("{} Skipping scheduleReconnect() because I have an active channel", logPrefix());
        }
    }

通过以上源码可以得出如下初始化流程:

  1. 遍历Sentinel节点集合,找到一个可用的Sentinel节点,如果找不到就从Sentinel节点集合中去找下一个;如果都找不到直接抛出异常给客户端:
  2. 找到一个可用的Sentinel节点, 执行sentinelGetMasterAddrByName( masterName),通过主机名称找到对应主节点信息
  3. 根据ConnectionWatchdog重连的机制,进行故障转移 当发现 netty handler 中 channelInactive 触发 (与远程主机的连接意外断开时。当尝试连接远程主机但连接失败时。当关闭当前通道时。) 会重新去 拉去 Sentinel中对应的主节点信息

spring redis Sentinel Lettuce 实现

redis.clients.jedis.JedisSentinelPool

public JedisSentinelPool(String masterName, Set<String> sentinels, GenericObjectPoolConfig poolConfig, int connectionTimeout, int soTimeout, String password, int database, String clientName) {
    HostAndPort master = this.initSentinels(sentinels, masterName);
    this.initPool(master);
}



private HostAndPort initSentinels(Set<String> sentinels, final String masterName) {

    HostAndPort master = null;
    boolean sentinelAvailable = false;

    log.info("Trying to find master from available Sentinels...");

    for (String sentinel : sentinels) {
      final HostAndPort hap = HostAndPort.parseString(sentinel);

      log.fine("Connecting to Sentinel " + hap);

      Jedis jedis = null;
      try {
        jedis = new Jedis(hap.getHost(), hap.getPort());

        List<String> masterAddr = jedis.sentinelGetMasterAddrByName(masterName);

        // connected to sentinel...
        sentinelAvailable = true;

        if (masterAddr == null || masterAddr.size() != 2) {
          log.warning("Can not get master addr, master name: " + masterName + ". Sentinel: " + hap
              + ".");
          continue;
        }

        master = toHostAndPort(masterAddr);
        log.fine("Found Redis master at " + master);
        break;
      } catch (JedisException e) {
        // resolves #1036, it should handle JedisException there's another chance
        // of raising JedisDataException
        log.warning("Cannot get master address from sentinel running @ " + hap + ". Reason: " + e
            + ". Trying next one.");
      } finally {
        if (jedis != null) {
          jedis.close();
        }
      }
    }

    if (master == null) {
      if (sentinelAvailable) {
        // can connect to sentinel, but master name seems to not
        // monitored
        throw new JedisException("Can connect to sentinel, but " + masterName
            + " seems to be not monitored...");
      } else {
        throw new JedisConnectionException("All sentinels down, cannot determine where is "
            + masterName + " master is running...");
      }
    }

    log.info("Redis master running at " + master + ", starting Sentinel listeners...");

    for (String sentinel : sentinels) {
      final HostAndPort hap = HostAndPort.parseString(sentinel);
      MasterListener masterListener = new MasterListener(masterName, hap.getHost(), hap.getPort());
      // whether MasterListener threads are alive or not, process can be stopped
      masterListener.setDaemon(true);
      masterListeners.add(masterListener);
      masterListener.start();
    }

    return master;
  }

通过以上源码可以得出如下初始化流程:

  1. initSentinels用sentinels和masterName,也就是Sentinel节点的集合和Redis数据节点的名字来初始化,获取到Redis的主节点,同时呢,为每个Sentinel创建了一个监听的进程。
  2. MasterListener是一个线程,为每个Sentinel节点创建一个线程,来订阅channel:+switch-master,从而当主节点发生切换的时候,可以及时感知,修改Sentinel的客户端缓冲池JedisSentinelPool。

spring redis Sentinel redisson 实现


/**  
* Redisson构造方法  
* @param config for Redisson  
* @return Redisson instance  
*/  
protected Redisson(Config config) {  
    //赋值变量config  
    this.config = config;  
    //产生一份对于传入config的备份  
    Config configCopy = new Config(config);  
 
    //根据配置config的类型(主从模式、单机模式、哨兵模式、集群模式、亚马逊云模式、微软云模式)而进行不同的初始化  
    connectionManager = ConfigSupport.createConnectionManager(configCopy);  
    //连接池对象回收调度器  
    evictionScheduler = new EvictionScheduler(connectionManager.getCommandExecutor());  
    //Redisson的对象编码类  
    codecProvider = configCopy.getCodecProvider();  
    //Redisson的ResolverProvider,默认为org.redisson.liveobject.provider.DefaultResolverProvider  
    resolverProvider = configCopy.getResolverProvider();  

在这里插入图片描述
跟进去看哨兵模式下的 ConnectionManager
org.redisson.connection.SentinelConnectionManager

public SentinelConnectionManager(SentinelServersConfig cfg, Config config, UUID id) {
        super(config, id);
        
        if (cfg.getMasterName() == null) {
            throw new IllegalArgumentException("masterName parameter is not defined!");
        }
        if (cfg.getSentinelAddresses().isEmpty()) {
            throw new IllegalArgumentException("At least one sentinel node should be defined!");
        }

        this.config = create(cfg);
        this.sentinelPassword = cfg.getSentinelPassword();
        initTimer(this.config);

        this.natMapper = cfg.getNatMapper();

        this.sentinelResolver = resolverGroup.getResolver(getGroup().next());

        for (String address : cfg.getSentinelAddresses()) {
            RedisURI addr = new RedisURI(address);
            scheme = addr.getScheme();
            addr = applyNatMap(addr);
            if (NetUtil.createByteArrayFromIpAddressString(addr.getHost()) == null && !addr.getHost().equals("localhost")) {
                sentinelHosts.add(addr);
            }
        }

        checkAuth(cfg);

        Throwable lastException = null;
        for (String address : cfg.getSentinelAddresses()) {
            RedisURI addr = new RedisURI(address);
            addr = applyNatMap(addr);

            RedisClient client = createClient(NodeType.SENTINEL, addr, this.config.getConnectTimeout(), this.config.getTimeout(), null);
            try {
                RedisConnection connection = null;
                try {
                    connection = client.connect();
                    if (!connection.isActive()) {
                        continue;
                    }
                } catch (RedisConnectionException e) {
                    continue;
                }
                
                InetSocketAddress master = connection.sync(RedisCommands.SENTINEL_GET_MASTER_ADDR_BY_NAME, cfg.getMasterName());
                if (master == null) {
                    throw new RedisConnectionException("Master node is undefined! SENTINEL GET-MASTER-ADDR-BY-NAME command returns empty result!");
                }

                RedisURI masterHost = toURI(master.getHostString(), String.valueOf(master.getPort()));
                this.config.setMasterAddress(masterHost.toString());
                currentMaster.set(masterHost);
                log.info("master: {} added", masterHost);

                List<Map<String, String>> sentinelSlaves = connection.sync(StringCodec.INSTANCE, RedisCommands.SENTINEL_SLAVES, cfg.getMasterName());
                for (Map<String, String> map : sentinelSlaves) {
                    if (map.isEmpty()) {
                        continue;
                    }

                    String ip = map.get("ip");
                    String port = map.get("port");
                    String flags = map.getOrDefault("flags", "");

                    RedisURI host = toURI(ip, port);

                    this.config.addSlaveAddress(host.toString());
                    log.debug("slave {} state: {}", host, map);
                    log.info("slave: {} added", host);

                    if (flags.contains("s_down") || flags.contains("disconnected")) {
                        disconnectedSlaves.add(host);
                        log.warn("slave: {} is down", host);
                    }
                }
                
                List<Map<String, String>> sentinelSentinels = connection.sync(StringCodec.INSTANCE, RedisCommands.SENTINEL_SENTINELS, cfg.getMasterName());
                List<RFuture<Void>> connectionFutures = new ArrayList<>(sentinelSentinels.size());
                for (Map<String, String> map : sentinelSentinels) {
                    if (map.isEmpty()) {
                        continue;
                    }

                    String ip = map.get("ip");
                    String port = map.get("port");

                    RedisURI sentinelAddr = toURI(ip, port);
                    RFuture<Void> future = registerSentinel(sentinelAddr, this.config, null);
                    connectionFutures.add(future);
                }

                RFuture<Void> f = registerSentinel(addr, this.config, null);
                connectionFutures.add(f);

                for (RFuture<Void> future : connectionFutures) {
                    future.awaitUninterruptibly(this.config.getConnectTimeout());
                }

                break;
            } catch (RedisConnectionException e) {
                stopThreads();
                throw e;
            } catch (Exception e) {
                lastException = e;
                log.warn(e.getMessage());
            } finally {
                client.shutdownAsync();
            }
        }

        if (cfg.isCheckSentinelsList()) {
            if (sentinels.isEmpty()) {
                stopThreads();
                throw new RedisConnectionException("SENTINEL SENTINELS command returns empty result! Set checkSentinelsList = false to avoid this check.", lastException);
            } else if (sentinels.size() < 2) {
                stopThreads();
                throw new RedisConnectionException("SENTINEL SENTINELS command returns less than 2 nodes! At least two sentinels should be defined in Redis configuration. Set checkSentinelsList = false to avoid this check.", lastException);
            }
        }
        
        if (currentMaster.get() == null) {
            stopThreads();
            throw new RedisConnectionException("Can't connect to servers!", lastException);
        }
        if (this.config.getReadMode() != ReadMode.MASTER && this.config.getSlaveAddresses().isEmpty()) {
            log.warn("ReadMode = " + this.config.getReadMode() + ", but slave nodes are not found!");
        }
        
        initSingleEntry();
        
        scheduleChangeCheck(cfg, null);
    }

启一个 schedule 每一秒调度一次检查 是否发生过 故障转移
scheduleChangeCheck

    private void scheduleChangeCheck(SentinelServersConfig cfg, Iterator<RedisClient> iterator) {
        monitorFuture = group.schedule(new Runnable() {
            @Override
            public void run() {
                AtomicReference<Throwable> lastException = new AtomicReference<Throwable>();
                Iterator<RedisClient> iter = iterator;
                if (iter == null) {
                    // Shuffle the list so all clients don't prefer the same sentinel
                    List<RedisClient> clients = new ArrayList<>(sentinels.values());
                    Collections.shuffle(clients);
                    iter = clients.iterator();
                }
                checkState(cfg, iter, lastException);
            }
        }, cfg.getScanInterval(), TimeUnit.MILLISECONDS);
    }
private void checkState(SentinelServersConfig cfg, Iterator<RedisClient> iterator, AtomicReference<Throwable> lastException) {
        if (!iterator.hasNext()) {
            if (lastException.get() != null) {
                log.error("Can't update cluster state", lastException.get());
            }
            performSentinelDNSCheck(null);
            scheduleChangeCheck(cfg, null);
            return;
        }
        if (!getShutdownLatch().acquire()) {
            return;
        }

        RedisClient client = iterator.next();
        RedisURI addr = getIpAddr(client.getAddr());
        RFuture<RedisConnection> connectionFuture = connectToNode(cfg, addr, null);
        connectionFuture.onComplete((connection, e) -> {
            if (e != null) {
                lastException.set(e);
                getShutdownLatch().release();
                checkState(cfg, iterator, lastException);
                return;
            }

            updateState(cfg, connection, iterator);
        });

    }

    private void updateState(SentinelServersConfig cfg, RedisConnection connection, Iterator<RedisClient> iterator) {
        AtomicInteger commands = new AtomicInteger(2);
        BiConsumer<Object, Throwable> commonListener = new BiConsumer<Object, Throwable>() {
            
            private final AtomicBoolean failed = new AtomicBoolean();
            
            @Override
            public void accept(Object t, Throwable u) {
                if (commands.decrementAndGet() == 0) {
                    getShutdownLatch().release();
                    if (failed.get()) {
                        scheduleChangeCheck(cfg, iterator);
                    } else {
                        scheduleChangeCheck(cfg, null);
                    }
                }
                if (u != null && failed.compareAndSet(false, true)) {
                    log.error("Can't execute SENTINEL commands on " + connection.getRedisClient().getAddr(), u);
                    closeNodeConnection(connection);
                }
            }
        };
        
        RFuture<InetSocketAddress> masterFuture = connection.async(StringCodec.INSTANCE, RedisCommands.SENTINEL_GET_MASTER_ADDR_BY_NAME, cfg.getMasterName());
        masterFuture.onComplete((master, e) -> {
            if (e != null) {
                return;
            }

            RedisURI current = currentMaster.get();
            RedisURI newMaster = toURI(master.getHostString(), String.valueOf(master.getPort()));
            if (!newMaster.equals(current)
                    && currentMaster.compareAndSet(current, newMaster)) {
                RFuture<RedisClient> changeFuture = changeMaster(singleSlotRange.getStartSlot(), newMaster);
                changeFuture.onComplete((res, ex) -> {
                    if (ex != null) {
                        currentMaster.compareAndSet(newMaster, current);
                    }
                });
            }
        });
        masterFuture.onComplete(commonListener);
        
        if (!config.checkSkipSlavesInit()) {
            RFuture<List<Map<String, String>>> slavesFuture = connection.async(StringCodec.INSTANCE, RedisCommands.SENTINEL_SLAVES, cfg.getMasterName());
            commands.incrementAndGet();
            slavesFuture.onComplete((slavesMap, e) -> {
                if (e != null) {
                    return;
                }
                
                Set<RedisURI> currentSlaves = new HashSet<>(slavesMap.size());
                List<RFuture<Void>> futures = new ArrayList<>();
                for (Map<String, String> map : slavesMap) {
                    if (map.isEmpty()) {
                        continue;
                    }
                    
                    String ip = map.get("ip");
                    String port = map.get("port");
                    String flags = map.getOrDefault("flags", "");
                    String masterHost = map.get("master-host");
                    String masterPort = map.get("master-port");

                    RedisURI slaveAddr = toURI(ip, port);
                    if (flags.contains("s_down") || flags.contains("disconnected")) {
                        slaveDown(slaveAddr);
                        continue;
                    }
                    if ("?".equals(masterHost) || !isUseSameMaster(slaveAddr, masterHost, masterPort)) {
                        continue;
                    }

                    currentSlaves.add(slaveAddr);
                    RFuture<Void> slaveFuture = addSlave(slaveAddr);
                    futures.add(slaveFuture);
                }
                
                CountableListener<Void> listener = new CountableListener<Void>() {
                    @Override
                    protected void onSuccess(Void value) {
                        MasterSlaveEntry entry = getEntry(singleSlotRange.getStartSlot());
                        entry.getAllEntries().stream()
                                .map(e -> e.getClient().getAddr())
                                .map(a -> toURI(a.getAddress().getHostAddress(), String.valueOf(a.getPort())))
                                .filter(a -> !currentSlaves.contains(a) && !a.equals(currentMaster.get()))
                                .forEach(a -> slaveDown(a));
                    };
                };
                
                listener.setCounter(futures.size());
                for (RFuture<Void> f : futures) {
                    f.onComplete(listener);
                }
            });
            slavesFuture.onComplete(commonListener);
        }
                
        RFuture<List<Map<String, String>>> sentinelsFuture = connection.async(StringCodec.INSTANCE, RedisCommands.SENTINEL_SENTINELS, cfg.getMasterName());
        sentinelsFuture.onComplete((list, e) -> {
            if (e != null || list.isEmpty()) {
                return;
            }
            
            Set<RedisURI> newUris = list.stream().filter(m -> {
                String flags = m.getOrDefault("flags", "");
                if (!m.isEmpty() && !flags.contains("disconnected") && !flags.contains("s_down")) {
                    return true;
                }
                return false;
            }).map(m -> {
                String ip = m.get("ip");
                String port = m.get("port");
                return toURI(ip, port);
            }).collect(Collectors.toSet());
            
            InetSocketAddress addr = connection.getRedisClient().getAddr();
            RedisURI currentAddr = getIpAddr(addr);
            newUris.add(currentAddr);
            
            updateSentinels(newUris);
        });
        sentinelsFuture.onComplete(commonListener);
    }

通过以上源码可以得出如下初始化流程:

scheduleClusterChangeCheck定时检测集群节点状态
checkMasterNodesChange检测主节点状态

sentinel 查询 Master 的地址,如果发现 Master 变更了,则删除旧的 masterEntry,重建一个新的 masterEntry;

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

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

相关文章

el-table合计功能滚动条层级问题

问题描述 版本&#xff1a;2.15.13 el-table在使用合计功能及固定列功能的同时&#xff0c;由于固定列的结构是固定区域增加了div加上定位&#xff0c;用来盖住下面的内容。当使用了合计功能的时候滚动条的区域在el-table__body-wrapper会导致固定列区域下的滚动条被挡住&…

计算机网络三:运输层

一、运输层 1. 概述 运输层协议为运行在不同主机上的应用进程之间提供了逻辑通信功能&#xff0c;使得不同主机好像直接连在一起一样 运输层协议是在端系统中而不是在路由器中实现的。网络路由器仅作用于网络层分组(即数据报)的网络层字段&#xff0c;而不检查封装在该数据报…

微搭低代码学习之数据展示

低代码平台是一个快速发展的领域&#xff0c;未来有着广阔的发展前景。以下是一些低代码平台未来的发展方向&#xff1a; 1.人工智能和机器学习 随着人工智能和机器学习技术的不断发展&#xff0c;低代码平台将能够更好地利用这些技术来提高应用程序的智能化和自动化水平。例如…

APP渗透—绕过反代理、反证书检测

APP渗透—绕过反代理、反证书检测 1. 前言1.1. 无法获取数据包情况 2. 反代理2.1. 反代理情况2.1.1. 某牛牛反代理2.1.2. 某探反代理 2.2. 绕过反代理2.2.1. Proxifier设置2.2.1.1. 设置代理服务器2.2.1.2. 配置代理规则2.2.1.3. 检测状态 2.2.2. 抓包测试 2.3. 总结 3. 反证书…

用HTML+CSS简单的画一个支付宝页面(超详细)

&#x1f648;作者简介&#xff1a;练习时长两年半的Java up主 &#x1f649;个人主页&#xff1a;老茶icon &#x1f64a; ps:点赞&#x1f44d;是免费的&#xff0c;却可以让写博客的作者开兴好久好久&#x1f60e; &#x1f4da;系列专栏&#xff1a;Java全栈&#xff0c;计…

Pyqt实现合并PDF遇到的问题集合

1.exit code -1073740791报错 pycharm遇到如下报错&#xff1a; “Process finished with exit code -1073740791 (0xC0000409)” 就是遇到这种问题就不知道问题在哪里&#xff0c;有点摸不着头脑 解决措施&#xff1a; 1.点击导航栏“Run”后选择“Edit Configurations” …

Python机器学习、深度学习技术提升气象、海洋、水文领域实践应用

Python是功能强大、免费、开源&#xff0c;实现面向对象的编程语言&#xff0c;在数据处理、科学计算、数学建模、数据挖掘和数据可视化方面具备优异的性能&#xff0c;这些优势使得Python在气象、海洋、地理、气候、水文和生态等地学领域的科研和工程项目中得到广泛应用。可以…

Keil使用 fromelf 生成bin文件

一、语法格式如下&#xff1a; fromelf [options] input_file [options] 选项如下&#xff0c;可以组合使用&#xff0c;两者之间用空格隔开&#xff1a; 二、在Keil中的使用过程 1、配置Output&#xff0c;生成可执行文件 aa即为可执行文件名&#xff0c;后缀为axf。…

十三、51单片机之EEPROM(I2C)

1、EEPROM简介 (1)存储设备类型&#xff1a;ROM、RAM、PROM(可编程ROM)、EPROM(可擦除ROM)、EEPROM(电可擦除ROM)。 (2)为什么需要EEPROM&#xff1f; 某些数据内容我们需要掉电不丢失且在程序运行中可以修改这些数据内容&#xff0c;这就需要用到EEPROM。 (3)EEPROM和flas…

家庭智能开关通断—Homekit智能

智能通断器&#xff0c;也叫开关模块&#xff0c;可以非常方便地接入家中原有开关、插座、灯具、电器的线路中&#xff0c;通过手机App或者语音即可控制电路通断&#xff0c;轻松实现原有家居设备的智能化改造。 随着智能家居概念的普及&#xff0c;越来越多的人想将自己的家改…

Rust之泛型、特性和生命期(一):基本概念

开发环境 Windows 10Rust 1.69.0 VS Code 1.77.3 项目工程 这里继续沿用上次工程rust-demo 泛型、特性和生命期 每种编程语言都有有效处理概念重复的工具。在Rust中&#xff0c;一个这样的工具就是泛型&#xff1a;具体类型或其他属性的抽象替身。我们可以表达泛型的行为或…

IMX6ULL_Pro网卡配置

(15条消息) 【IMX6ULL_Pro板卡学习】配置 VMware 使用双网卡_vmware配置双网卡_爱学习的小莲的博客-CSDN博客https://blog.csdn.net/qq_52201194/article/details/127474352?ops_request_misc%257B%2522request%255Fid%2522%253A%2522168223524716800182730924%2522%252C%2522…

Windows逆向安全(一)之基础知识(十一)

二维数组 二维数组初始化 int arr[3][4]{{1,2,3,4},{5,6,7,8},{9,10,11,12} };查看反汇编 7: int arr[3][4]{ 8: {1,2,3,4}, 0040D498 mov dword ptr [ebp-30h],1 0040D49F mov dword ptr [ebp-2Ch],2 0040D4A6 mov dword …

CentOS(linux)使用Docker安装nacos

1. 拉取nacos镜像 docker pull nacos/nacos-server:2.0.3 2. 创建所需文件夹(以安装在home目录下为例) 1) 创建conf文件夹 mkdir -p /home/nacos/conf a. 新增文件application.properties(或者不增加该文件,会使用默认的) 文件内容如下: # spring server.servlet.contextP…

不知道玩什么游戏的你看过来

推荐一&#xff1a;原神 《原神》游戏设定在一个名为“蒂瓦特”的奇幻世界。 被神选中的人&#xff0c;将被赐予“神之眼”&#xff0c;引导“元素之力”&#xff0c;成为“旅者”。 在旅途中&#xff0c;结识性格各异、才华横溢、能力各异的小伙伴。 我们将一起战胜强大的敌…

PyTorch实战2:彩色图片识别(CIFAR10)

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f366; 参考文章&#xff1a;365天深度学习训练营-第P2周&#xff1a;彩色图片识别&#x1f356; 原作者&#xff1a;K同学啊|接辅导、项目定制 目录 一、数据准备二、构建简单CNN网络⭐1. torch…

图扑软件 | 数字孪生智慧水泥工厂

前言 近年来&#xff0c;随着我国经济的发展和人民生活水平的提高&#xff0c;我国对于水泥行业的关注程度也越来越高&#xff0c;为了保证水泥行业的健康稳定发展&#xff0c;许多地方都在大力推动水泥生产技术创新工作。当前水泥行业的发展正处于新旧动能更迭的关键阶段&…

JavaWeb开发 —— SpringBootWeb综合案例

通过综合案例&#xff0c;我们来了解和学习在开发Web程序时&#xff0c;前端程序和后端程序以及数据库三者之间是如何交互、如何协作的&#xff0c;而通过综合案例也来掌握通过接口文档开发服务端接口的能力。 目录 一、准备工作 1. 需求说明 2. 环境搭建 3. 开发规范 二…

Postgis导出shp和gdb数据库(Postgre入门九)

背景 有时候我们需要将postgis数据库中的空间数据表导出shp格式,而PG自带的PostGIS Shapefile Import/Export Manager 导出shp大部分时候是可以用的,但是它有个缺点是,当shp字段名称超过10个字节时,字段会被切掉,如字段“afdskskkfkfjdj”被切掉后是“afdskskkfk”,所以…

文案自动修改软件-文案自动改写的免费软件下载

文章生成器ai写作机器人 随着人工智能技术的飞速发展&#xff0c;越来越多的新型产品被推向市场。其中&#xff0c;文章生成器AI写作机器人是一个备受关注的新兴行业。它使用机器学习和自然语言处理等技术&#xff0c;为用户自动生成高质量的文章和内容&#xff0c;帮助用户在…