【微服务即时通讯系统】——etcd一致性键值存储系统、etcd的介绍、etcd的安装、etcd使用和功能测试

news2024/12/23 16:54:24

文章目录

  • etcd
    • 1. etcd的介绍
      • 1.1 etcd的概念
    • 2. etcd的安装
      • 2.1 安装etcd
      • 2.2 安装etcd客户端C/C++开发库
    • 3. etcd使用
      • 3.1 etcd接口介绍
    • 4. etcd使用测试
      • 4.1 原生接口使用测试
      • 4.2 封装etcd使用测试

etcd

在这里插入图片描述

  

1. etcd的介绍

1.1 etcd的概念

   Etcd 是一个基于GO实现的 分布式、高可用、一致 的 一致性键值存储系统 用于配置共享和服务发现等。它使用 Raft 一致性算法来保持集群数据的一致性,且客户端通过长连接watch 功能,能够及时收到数据变化通知,相较于 Zookeeper 框架更加轻量化。

  

  为什么需要 etcd?

  管理共享配置信息:etcd 可以将配置信息集中存储,服务端将配置信息存储于 etcd 中,客户端可以通过 etcd 方便地获取这些配置信息。

  服务发现:etcd 可以作为服务注册中心,将服务信息注册到 etcd 中,其他服务可以通过查询 etcd 来获取这些信息。且当服务发生变化时,etcd 可以及时通知,实现服务的自动发现和动态调整。

  防止单点故障:为了防止单点故障,可以启动多个 etcd 组成集群。etcd 集群使用 raft 一致性算法来处理日志复制,保证多节点数据的强一致性。

  

  etcd 使用 raft 算法来实现数据的一致性:Raft 算法用于分布式系统,包含主节点选举和数据更新两部分。主节点选举中,从节点在未收到主节点心跳包时可成为候选主节点发起投票,获超半数响应则成为新主节点。数据更新分两阶段,先主节点记录并复制日志,超半数响应后通知客户端,再主节点提交修改并通知从节点提交。

  

2. etcd的安装

2.1 安装etcd

  这是在 Linux 系统(ubuntu) 上安装 Etcd 的基本步骤:
  
  安装 Etcd:

sudo apt-get install etcd

  启动 Etcd 服务:

sudo systemctl start etcd

  设置 Etcd 开机自启:

sudo systemctl enable etcd

  运行验证:

etcdctl put mykey "this is awesome"

  
  如果出现报错:No help topic for 'put'

  则 sudo vi /etc/profile 在末尾声明环境变量 ETCDCTL_API=3 以确定 etcd 版本:

export ETCDCTL_API=3

  完毕后,加载配置文件,并重新执行测试指令:

dev@Crocodile:~/workspace$ source /etc/profile
dev@Crocodile:~/workspace$ etcdctl put mykey "this is awesome"
OK
dev@Crocodile:~/workspace$ etcdctl get mykey
mykey
this is awesome
dev@Crocodile:~/workspace$ etcdctl del mykey

  

2.2 安装etcd客户端C/C++开发库

  etcd 由 golang 编写,v3 版本使用 grpc API(HTTP2+protobuf)通信,官方仅维护了 go 语言的 client 库。若要使用 C/C++ 语言,需寻找非官方的 etcd client 开发库。

  etcd-cpp-apiv3 是一个 etcd 的 C++版本客户端 API。它依赖于 mipsasm, boost, protobuf, gRPC, cpprestsdk 等库。
  
  依赖安装:

sudo apt-get install libboost-all-dev libssl-dev
sudo apt-get install libprotobuf-dev protobuf-compiler-grpc
sudo apt-get install libgrpc-dev libgrpc++-dev 
sudo apt-get install libcpprest-dev

  api 框架安装:

git clone https://github.com/etcd-cpp-apiv3/etcd-cpp-apiv3.git

cd etcd-cpp-apiv3

mkdir build && cd build

cmake .. -DCMAKE_INSTALL_PREFIX=/usr

make -j$(nproc) && sudo make install

  

3. etcd使用

  etcd工作原理:
  
在这里插入图片描述
  

  etcd 是分布式高可用的 一致性键值存储系统 用于配置共享和服务发现(Raft 一致性算法来保持集群数据的一致性,且客户端通过长连接 watch 功能,能够及时收到数据变化通知,相较于 Zookeeper 框架更加轻量化)

  先创建一个etcd服务器存储键值对数据, 主机1可以向服务器进行服务注册,该注册是以一个键值对存储,保存了服务和主机的地址端口,主机2可以获取又主机1向服务器中添加的服务数据, 同时主机1对这个服务有一个长连接保活(服务器每个3s进行保活确认),如果服务下线了,etcd服务器就会通知主机2。

  
在这里插入图片描述

  etcd 服务器有设置租约的功能 在上面介绍了 client和etcd服务器直接有一个长连接保活(KeepAlive) 这个长连接可以Lease()获取一个租约, 同时这个租约和client提供服务设置的租约id一致, etcd 服务器和client直接通过KeepALive长连接保活 ,如果长连接断开,那么client的键值对数据就无法获取到租约, etcd就会将没有租约的数据进行删除。

  租约机制类似房租,没有租约就是房子属于自己,有租约就要定时通过房东续租,否则无法租房。

  

3.1 etcd接口介绍

//pplx::task 并行库异步结果对象
//阻塞方式 get(): 阻塞直到任务执行完成,并获取任务结果
//非阻塞方式 wait(): 等待任务到达终止状态,然后返回任务状态
namespace etcd {
class Value {
 bool is_dir()//判断是否是一个目录
 std::string const& key() //键值对的 key 值
 std::string const& as_string()//键值对的 val 值
 
 int64_t lease() //用于创建租约的响应中,返回租约 ID
}

//etcd 会监控所管理的数据的变化,一旦数据产生变化会通知客户端
//在通知客户端的时候,会返回改变前的数据和改变后的数据
class Event {
 enum class EventType {
 PUT, //键值对新增或数据发生改变
 DELETE_,//键值对被删除
 INVALID,
 };
 enum EventType event_type() 
 const Value& kv()
 const Value& prev_kv()
} 

class Response {
 bool is_ok()
 std::string const& error_message()
 Value const& value()//当前的数值 或者 一个请求的处理结果
 Value const& prev_value()//之前的数值
 Value const& value(int index)//
 std::vector<Event> const& events();//触发的事件
} 
class KeepAlive {
 KeepAlive(Client const& client, int ttl, int64_t lease_id = 
0);
 //返回租约 ID
 int64_t Lease();
 //停止保活动作
 void Cancel();
} 

class Client {
 // etcd_url: "http://127.0.0.1:2379"
 Client(std::string const& etcd_url,
 std::string const& load_balancer = "round_robin");
 //Put a new key-value pair 新增一个键值对
 pplx::task<Response> put(std::string const& key, 
 std::string const& value);
 //新增带有租约的键值对 (一定时间后,如果没有续租,数据自动删除)
 pplx::task<Response> put(std::string const& key, 
 std::string const& value,
 const int64_t leaseId);
 //获取一个指定 key 目录下的数据列表
 pplx::task<Response> ls(std::string const& key);
  //创建并获取一个存活 ttl 时间的租约
 pplx::task<Response> leasegrant(int ttl);
 //获取一个租约保活对象,其参数 ttl 表示租约有效时间
 pplx::task<std::shared_ptr<KeepAlive>> leasekeepalive(int 
ttl);
 //撤销一个指定的租约
 pplx::task<Response> leaserevoke(int64_t lease_id);
 //数据锁
 pplx::task<Response> lock(std::string const& key);
} 

class Watcher {
 Watcher(Client const& client, 
 std::string const& key, //要监控的键值对 key
 std::function<void(Response)> callback, //发生改变后的回调
 bool recursive = false); //是否递归监控目录下的所有数据改变
 Watcher(std::string const& address, 
 std::string const& key,
 std::function<void(Response)> callback, 
 bool recursive = false);
 //阻塞等待,直到监控任务被停止
 bool Wait();
 bool Cancel();
}

  

4. etcd使用测试

4.1 原生接口使用测试

  put.cc

#include <etcd/Client.hpp>
#include <etcd/KeepAlive.hpp>
#include <etcd/Response.hpp>
#include <thread>

int main(int argc, char *argv[])
{
    std::string etcd_host="http://127.0.0.1:2379";
    // 实例化客户端对象
    etcd::Client client(etcd_host);

    // 获取租约保活对象,伴随着创建一个指定有效时长的租约
    auto keep_alive=client.leasekeepalive(3).get();
    // 获取租约id
    auto lease_id=keep_alive->Lease();
  
    // 向ectd新增数据
    auto resp1=client.put("/service/user","127.0.0.1:8080",lease_id).get();
    if(resp1.is_ok()==false)
    {
        std::cout<<"新增数据失败: "<<resp1.error_message()<<std::endl;
        return -1;
    }
    auto resp2=client.put("/service/friend","127.0.0.1:9090").get();
    if(resp2.is_ok()==false)
    {
        std::cout<<"新增数据失败: "<<resp2.error_message()<<std::endl;
        return -1;
    }

    std::this_thread::sleep_for(std::chrono::seconds(10));

    return 0;
}

  get.cc

#include <etcd/Client.hpp>
#include <etcd/KeepAlive.hpp>
#include <etcd/Response.hpp>
#include <etcd/Watcher.hpp>
#include <etcd/Value.hpp>
#include <thread>

// 监控事件发生的回调函数
void callback(const etcd::Response &resp)
{
    if(resp.is_ok()==false)
    {
        std::cout<<"收到一个错误的事件通知:"<<resp.error_message()<<std::endl;
        return;
    }
    for(auto const& ev:resp.events())
    {
        if(ev.event_type()==etcd::Event::EventType::PUT)
        {
            std::cout<<"数据发生了改变:\n";
            std::cout<<"当前的值:"<<ev.kv().key()<<"-"<<ev.kv().as_string()<<std::endl;
            std::cout<<"原来的值:"<<ev.prev_kv().key()<<"-"<<ev.prev_kv().as_string()<<std::endl;
        }
        else if(ev.event_type()==etcd::Event::EventType::DELETE_)
        {
            std::cout<<"服务信息下线被删除:\n";
            std::cout<<"当前的值:"<<ev.kv().key()<<"-"<<ev.kv().as_string()<<std::endl;
            std::cout<<"原来的值:"<<ev.prev_kv().key()<<"-"<<ev.prev_kv().as_string()<<std::endl;
        }
    }
}

int main(int argc, char* argv[])
{
    std::string etch_host="http://127.0.0.1:2379";
    
    // 实例化客户端对象 
    etcd::Client client(etch_host);

    // 获取指定的键值对信息
    // 初次先用 ls 获取所有能够提供指定服务/service的实例信息
    auto resp=client.ls("/service").get(); 
    if(resp.is_ok()==false)
    {
        std::cout<<"获取键值对数据失败:"<<resp.error_message()<<std::endl;
        return -1;
    }
    int sz=resp.keys().size();
    for(int i=0;i<sz;++i) // 遍历所有可以提供的服务
    {
        std::cout<<resp.value(i).as_string()<<"可以提供"<<resp.key(i)<<"服务\n";
    }

    // 实例化一个键值对事件监控对象
    auto watcher=etcd::Watcher(client,"/service",callback,true); // true递归监控路径下所有函数
    watcher.Wait(); // 开始事件监控

    return 0;
}

  makefile

all:put get
put:put.cc
	g++ -std=c++17 $^ -o $@ -letcd-cpp-api -lcpprest
get:get.cc
	g++ -std=c++17 $^ -o $@ -letcd-cpp-api -lcpprest

  
在这里插入图片描述
  
在这里插入图片描述

  

4.2 封装etcd使用测试

  etcd.hpp

#pragma once
#include <etcd/Client.hpp>
#include <etcd/KeepAlive.hpp>
#include <etcd/Response.hpp>
#include <etcd/Watcher.hpp>
#include <etcd/Value.hpp>
#include <functional>
#include "logger.hpp"

// 封装etcd-client-api 实现两种类型的客户端
// 1. 服务注册客户端:向服务器新增服务信息数据,并进行保活
// 2. 服务发现客户端:从服务器查找服务信息数据,并进行改变世界监控
// etcd本质是一个键值存储系统,并不是专门用于作为注册中心进行服务注册和发现的

// 实现:
// 1. 封装服务注册客户端类,提供接口向服务器新增数据进行保活
//     参数:注册中心地址(etcd服务器地址) 新增的服务信息(服务名-主机地址键值对)
// 2. 封装服务发现客户端类,提供服务上线事件接口,服务下线事件接口,一个设置根目录接口

namespace wh_im{

//服务注册客户端类
class Registry 
{
    public:
        using ptr = std::shared_ptr<Registry>;

        // 服务注册客户端类初始化
        Registry(const std::string &host):
            _client(std::make_shared<etcd::Client>(host)) ,
            _keep_alive(_client->leasekeepalive(3).get()),
            _lease_id(_keep_alive->Lease()){}
        
        ~Registry() 
        {
            _keep_alive->Cancel(); 
        }
        
        // 注册服务键值对,val路径可以提供的服务key
        bool registry(const std::string &key, const std::string &val) 
        {
            // 进行服务键值对注册
            auto resp = _client->put(key, val, _lease_id).get();
            if (resp.is_ok() == false) 
            {
                LOG_ERROR("注册数据失败:{}", resp.error_message());
                return false;
            }
            return true;
        }

    private:
        std::shared_ptr<etcd::Client> _client;        // 客户端
        std::shared_ptr<etcd::KeepAlive> _keep_alive; // 长连接
        uint64_t _lease_id;                           // 租约id
};

//服务发现客户端类
class Discovery 
{
    public:
        using ptr = std::shared_ptr<Discovery>;
        using NotifyCallback = std::function<void(std::string, std::string)>;

        // 服务发现客户端类初始化
        Discovery(const std::string &host,   // 注册中心地址
            const std::string &basedir,      // 基础根目录
            const NotifyCallback &put_cb,    // 上线通知处理回调函数
            const NotifyCallback &del_cb):   // 下线通知处理回调函数
            _client(std::make_shared<etcd::Client>(host)) ,  // 构造客户端对象
            _put_cb(put_cb), _del_cb(del_cb){

            //先进行服务发现,先获取到当前已有的数据
            auto resp = _client->ls(basedir).get();
            if (resp.is_ok() == false) 
            {
                LOG_ERROR("获取服务信息数据失败:{}", resp.error_message());
            }
            int sz = resp.keys().size();
            for (int i = 0; i < sz; ++i) 
            {
                if (_put_cb) _put_cb(resp.key(i), resp.value(i).as_string());
            }

            // 然后进行事件监控,监控数据发生的改变并调用回调进行处理
            _watcher = std::make_shared<etcd::Watcher>(*_client.get(), basedir,
                std::bind(&Discovery::callback, this, std::placeholders::_1), true);
        }

        ~Discovery() 
        {
            _watcher->Cancel();
        }

    private:
        // 设置回调函数
        void callback(const etcd::Response &resp) 
        {
            if (resp.is_ok() == false) 
            {
                LOG_ERROR("收到一个错误的事件通知: {}", resp.error_message());
                return;
            }

            for (auto const& ev : resp.events()) 
            {
                if (ev.event_type() == etcd::Event::EventType::PUT) 
                {
                    if (_put_cb) _put_cb(ev.kv().key(), ev.kv().as_string());
                    LOG_DEBUG("新增服务:{}-{}", ev.kv().key(), ev.kv().as_string());
                }
                else if (ev.event_type() == etcd::Event::EventType::DELETE_) 
                {
                    if (_del_cb) _del_cb(ev.prev_kv().key(), ev.prev_kv().as_string());
                    LOG_DEBUG("下线服务:{}-{}", ev.prev_kv().key(), ev.prev_kv().as_string());
                }
            }
        }

    private:
        NotifyCallback _put_cb;                   // 上线通知回调函数
        NotifyCallback _del_cb;                   // 下线通知回调函数
        std::shared_ptr<etcd::Client> _client;    // 客户端
        std::shared_ptr<etcd::Watcher> _watcher;  // Watcher对象
};

} // end namespace

  registry.cc

#include "../../common/logger.hpp"
#include "../../common/etcd.hpp"
#include <gflags/gflags.h>
#include <thread>

DEFINE_bool(run_mode, false, "程序的运行模式, false-调试; true-发布;");
DEFINE_string(log_file,"","发布模式下,用于指定日志的输出文件");
DEFINE_int32(log_level,0,"发布模式下,用于指定日志输出等级");

DEFINE_string(etch_host,"http://127.0.0.1:2379","服务注册中心地址");
DEFINE_string(base_service,"/service","服务监控根目录");
DEFINE_string(instance_name,"/user/instance1","当前实例名称");
DEFINE_string(access_host,"127.0.0.1:8880","当前实例的外部访问地址");

int main(int argc, char *argv[])
{
    google::ParseCommandLineFlags(&argc, &argv, true);
    wh_im::init_logger(FLAGS_run_mode,FLAGS_log_file,FLAGS_log_level);

    wh_im::Registry::ptr rclient=std::make_shared<wh_im::Registry>(FLAGS_etch_host);
    LOG_DEBUG("服务名称:{}",FLAGS_base_service+FLAGS_instance_name);
    rclient->registry(FLAGS_base_service+FLAGS_instance_name,FLAGS_access_host);

    std::this_thread::sleep_for(std::chrono::seconds(600));
    return 0;
}

  discovery.cc

#include "../../common/logger.hpp"
#include "../../common/etcd.hpp"
#include <gflags/gflags.h>
#include <thread>

DEFINE_bool(run_mode, false, "程序的运行模式, false-调试; true-发布;");
DEFINE_string(log_file,"","发布模式下,用于指定日志的输出文件");
DEFINE_int32(log_level,0,"发布模式下,用于指定日志输出等级");

DEFINE_string(etch_host,"http://127.0.0.1:2379","服务注册中心地址");
DEFINE_string(base_service,"/service","服务监控根目录");

void online(const std::string &service_name, const std::string &service_host)
{
    LOG_DEBUG("上线服务:{}-{}",service_name,service_host);
}

void offline(const std::string &service_name, const std::string &service_host)
{
    LOG_DEBUG("下线服务:{}-{}",service_name,service_host);
}

int main(int argc, char *argv[])
{
    google::ParseCommandLineFlags(&argc, &argv, true);
    wh_im::init_logger(FLAGS_run_mode,FLAGS_log_file,FLAGS_log_level);

    wh_im::Discovery::ptr rclient=std::make_shared<wh_im::Discovery>(FLAGS_etch_host,
        FLAGS_base_service,online,offline);

    std::this_thread::sleep_for(std::chrono::seconds(600));

    return 0;
}

  makefile

all:discovery registry
discovery:discovery.cc
	g++ -std=c++17 $^ -o $@ -lspdlog -lfmt -lgflags -letcd-cpp-api -lcpprest
registry:registry.cc
	g++ -std=c++17 $^ -o $@ -lspdlog -lfmt -lgflags -letcd-cpp-api -lcpprest

  
在这里插入图片描述
  
在这里插入图片描述

            

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

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

相关文章

通过OpenScada在ARMxy边缘计算网关上实现数字化转型

随着工业4.0概念的普及&#xff0c;数字化转型已成为制造业升级的关键路径之一。在此背景下&#xff0c;边缘计算技术因其能够有效处理大量数据、减少延迟并提高系统响应速度而受到广泛关注。ARMxy边缘计算网关&#xff0c;特别是BL340系列&#xff0c;凭借其强大的性能和灵活的…

--杂项2--

将之前实现的顺序表、栈、队列都更改成模板类 #include <iostream> #include <string.h> using namespace std;template <typename T> class Stack { private:T* a;int top;int size1;public:Stack(int c) : a(new T[c]), top(-1), size1(c) {}~Stack() { de…

IDEA 系列产品 下载

准备工作 下载 下载链接&#xff1a;https://www.123865.com/ps/EF7OTd-mbHnH 仅供参考 环境 演示环境&#xff1a; 操作系统&#xff1a;windows10 产品&#xff1a;IntelliJ IDEA 版本&#xff1a;2024.1.2 注意&#xff1a;如果需要其他产品或者版本可以自行下载&#xff0…

ArcEngine C#二次开发图层处理:根据属性分割图层(Split)

需求&#xff1a;仅根据某一属性&#xff0c;分割图层&#xff0c;并以属性值命名图层名称保存。 众所周知&#xff0c;ArcGIS ArcToolbox中通过Split可以实现图形分割一个图层&#xff0c;以属性值命名图层&#xff0c;如下图所示。 本文仅仅依据属性值&#xff0c;将一个shp…

Android界面控件概述

节选自《Android应用开发项目式教程》&#xff0c;机械工业出版社&#xff0c;2024年7月出版 做最简单的安卓入门教程&#xff0c;手把手视频、代码、答疑全配齐 控件是Android界面的重要组成单元&#xff0c;Android应用主要通过控件与用户交互&#xff0c;Android提供了非常…

YUView:YUV查看工具

文章目录 引言安装步骤使用YUView查看YUV数据播放与分析功能亮点注意事项 YUView的架构设计 引言 本文将介绍如何在Ubuntu 20.04上安装YUView&#xff0c;并分享其基本使用方法。 安装步骤 安装依赖项 在开始安装YUView前&#xff0c;先确保安装了所有必需的依赖包。打开终…

VGA/HDMI/DP接口和USB、串口通信协议

1、视频接口 开始之前我们先聊一聊数字信号和模拟信号&#xff0c;模拟信号和数字信号的不同之处在于它们所传输的信息的形式。模拟信号是一个连续的信号&#xff0c;可以以在无限小的时间内进行测量。数字信号则是以离散的形式进行传输&#xff0c;它的数值只能是离散的、有限…

每日一题学习笔记

给你单链表的头结点 head &#xff0c;请你找出并返回链表的中间结点。 如果有两个中间结点&#xff0c;则返回第二个中间结点。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5] 输出&#xff1a;[3,4,5] 解释&#xff1a;链表只有一个中间结点&#xff0c;提示&…

Unity中的GUIStyle错误:SerializedObject of SerializedProperty has been Disposed.

一运行就循环打印这个报错&#xff0c; 解决办法&#xff0c;每次改参数之后在HIerarchy中手动保存&#xff0c;就会停止循环打印&#xff0c;style中的字体也显示出来了&#xff0c; 或者 直接换个低版本的

如何使用C语言接入Doris数据库

如何使用C语言接入Doris数据库 一、环境准备1. 安装MySQL C API2. Doris数据库环境二、编写C语言接入代码1. 包含必要的头文件2. 编写连接和查询函数3. 编译和运行程序三、注意事项1. 安全性2. 错误处理3. 性能优化4. 兼容性5. 调试和日志记录四、结论Doris(之前称为Palo或Apa…

SQL高可用优化-优化SQL中distinct和Where条件对索引字段进行非空检查语句

最近做一个需求&#xff0c;关于SQL高可用优化&#xff0c;需要优化项目中的SQL&#xff0c;提升查询效率。 SQL高可用优化 一、优化SQL包含distinct场景二、优化SQL中Where条件中索引字段是否为NULL三、代码验证1. NodeMapper2. NodeService3. NodeController4.数据库数据5.项…

《西安交通大学学报》

投稿须知 感谢你对本刊的信任和支持。为了更好地为你服务&#xff0c;保证你的稿件能够顺利通过专家审稿&#xff0c;乃至及时录用发表&#xff0c;现将向本刊投稿时需要注意的事项罗列如下。   &#xff08;1&#xff09;本刊面向国内外公开征稿&#xff0c;校外作者的稿件要…

Linux系统文件的基础IO

目录 一、C语言的文件IO操作 二、系统调用的文件IO操作 1.open打开文件 2.close关闭文件 3.write写入文件 4.read读取文件 三、文件描述符 四、文件描述符的分配规则 五、终端文件 六、C语言中的文件IO对比系统调用文件IO 文件I/O&#xff08;Input/Output&#xff0…

信安 实验2,3 使用Gpg4win进行数字签名、发送加密电子邮件

我发现了有些人喜欢静静看博客不聊天呐&#xff0c; 但是ta会点赞。 这样的人呢帅气低调有内涵&#xff0c; 美丽大方很优雅。 说的就是你&#xff0c; 不用再怀疑哦 实验2 使用Gpg4win进行数字签名 实验目的 通过实验&#xff0c;让学生掌握使用RSA算法实施数字签名的…

【1米C-SAR卫星】

1米C-SAR卫星 1米C-SAR卫星是我国自主研发的重要遥感卫星&#xff0c;主要用于海洋、陆地等观测任务&#xff0c;具备高分辨率、宽覆盖、多极化、多模式等特点。以下是对1米C-SAR卫星的详细介绍&#xff1a; 一、基本概况 发射时间&#xff1a;首颗1米C-SAR卫星于2021年11月2…

谁能给我一个ai现在无法替代画师的理由?

小白可做&#xff01;全自动AI影视解说一键成片剪辑工具https://docs.qq.com/doc/DYnl6d0FLdHp0V2ll 如何看待现如今的AI绘画 哎呀玫瑰花来了&#xff0c;所有花式都要玩完了。 我相信大家在网上已经看过了太多惊为天人的AI绘画作品&#xff0c;有人抵制&#xff0c;有人支持&a…

PMP--二模--解题--141-150

文章目录 14.敏捷--创建敏捷环境--团队构成--混合项目环境&#xff0c;通常是自组织团队&#xff0c;即团队成员自己决定谁做什么&#xff0c;而不是项目经理决定。易混--常见场景--一个新人加入141、 [单选] 在一个混合项目的执行过程中&#xff0c;不得不更换一个开发人员。新…

【爱给网:登录_注册安全分析报告-无验证方式导致安全隐患】

前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 1. 暴力破解密码&#xff0c;造成用户信息泄露 2. 短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉 3. 带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨大&#xff0c;造…

JS对不同浏览器的检测问题

Navigator对象也称浏览器对象&#xff0c;该对象包含了浏览器的整体信息&#xff0c;如浏览器名称&#xff0c;版本号等。Navigator对象由Navigator浏览器率先使用&#xff0c;后来各方浏览器都开始支持Navigator对象&#xff0c;逐步成为一种标准。 一、Navigator对象的属性 …

检查索引对象中是否存在缺失值pandas.Index.hasnans

【小白从小学Python、C、Java】 【考研初试复试毕业设计】 【Python基础AI数据分析】 检查索引对象中是否存在缺失值 pandas.Index.hasnans [太阳]选择题 题目代码中执行结果是&#xff1f; import pandas as pd import numpy as np idx1 pd.Index([1, 2, np.nan, 4]) print…