Etcd注册中心基本实现

Etcd入门

什么是Etcd

GitHub:https://github.com/etcd-io/etcd

image-20241215131646916

Etcd数据结构与特性

键值对格式,类似文件层次结构。

image-20241215131754070

image-20241215131841194

image-20241215131921418

image-20241215132016035

Etcd如何保证数据一致性?

表面来看,Etcd支持事务操作,能够保证数据一致性。

底层来看,Etcd使用Raft一致性算法保证数据一致性。

image-20241215132528385

官方可视化地址:http://play.etcd.io/play

可以深度了解,raft算法运行机制。

现在是一主两从两stop。

image-20241215132926476

停止主节点

此时主节点挂了,并没有选择新的主节点上线,因为还剩两个节点,一人一票,都没有胜出无法选择出新的Leader,这种现象也成为“脑裂”。

image-20241215133230080

启动node2,发现node3成为了Leader,此时不会有平票的情况。

image-20241215133747216

Etcd基本操作

增删改查。

写数据

image-20241215134412969

读数据

image-20241215134529732

前缀搜索

image-20241215134645914

Etcd安装

安装:https://github.com/etcd-io/etcd/releases

有不同系统安装启动脚本。

image-20241222185118997

安装完成会有三个脚本

  • etcd: etcd服务本身
  • etcdctl:客户端,用户操作etcd,如读写数据
  • etcdutl:备份恢复工具

执行etcd脚本,会启动etcd服务,服务默认占用2379和2380两个端口

2379:提供HTTP API服务,和etcdctl交互

2380:集群中节点通讯

Etcd可视化工具

etcdkeeper:https://github.com/evildecay/etcdkeeper

下载安装启动完毕,访问http://127.0.0.1:8080/etcdkeeper

Etcd Java客户端

jtecd:https://github.com/etcd-io/jetcd

1)引入依赖

<dependency>
    <groupId>io.etcd</groupId>
    <artifactId>jetcd-core</artifactId>
    <version>0.7.7</version>
</dependency>

2)demo

public class EtcdRegistry {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // create client using endpoints
        Client client = Client.builder().endpoints("http://localhost:2379")
                .build();

        KV kvClient = client.getKVClient();
        ByteSequence key = ByteSequence.from("likelong".getBytes());
        ByteSequence value = ByteSequence.from("666".getBytes());

        // put the key-value
        kvClient.put(key, value).get();

        // get the CompletableFuture
        CompletableFuture<GetResponse> getFuture = kvClient.get(key);

        // get the value from CompletableFuture
        GetResponse response = getFuture.get();
        System.out.println("value = " + response);

        // delete the key
        kvClient.delete(key).get();
    }
}

上述代码使用KVClient操作Etcd读写数据,除了KVClient客户端外,Etcd还提供了其他客户端。

image-20241215180012838

3)常用客户端

image-20241215180046293

绝大多数情况,前三个就够用。

Java Etcd数据结构

image-20241215223051370

除了有基本的KV,还有版本、创建版本、修改版本等字段。Etcd中每个键都有一个与之关联的版本号,用于跟踪键的修改历史。当键值发生变化,版本号也会随之增加。

image-20241215180536633

存储结构设计

存储结构设计几个要点:

  1. key如何设计?
  2. value如何设计?
  3. key什么时候过期?

结合Etcd数据存储结构特点(支持层级查询),以及一个服务会有多个服务提供者实例(负载均衡),可以设计为层级结构。

层级结构:将服务理解为文件夹、将服务对应的一个节点理解为文件夹下的文件,可以通过服务名称,用前缀查询的方式查询到某个服务的所有节点。

如下:键名规则可以为:业务前缀/服务名/服务节点地址

image-20241215183321023

如果是Redis作为注册中心,可以设计为列表结构(Redis本身支持列表数据结构)。

列表结构:将所有服务节点以列表的形式整体作为value。

image-20241215184137752

设置key过期超时时间,如30s,当服务宕机时,超时自动移除。

etcd选择层级结构。

开发实现

1. 注册中心开发

1)注册信息定义

ServiceMetaInfo类,封装服务注册信息,包括服务名称、服务版本号、服务地址(域名和端口)、服务分组等。

/**
 * 服务元信息(注册信息)
 */
public class ServiceMetaInfo {

    /**
     * 服务名称
     */
    private String serviceName;

    /**
     * 服务版本号
     */
    private String serviceVersion = "1.0";

    /**
     * 服务域名
     */
    private String serviceHost;

    /**
     * 服务端口号
     */
    private Integer servicePort;

    /**
     * 服务分组(暂未实现)
     */
    private String serviceGroup = "default";

}

添加方法,获取服务键名、获取服务注册节点键名以及获取服务访问地址。

/**
 * 获取服务键名
 */
public String getServiceKey() {
    // 后续可扩展服务分组
    // return String.format("%s:%s:%s", serviceName, serviceVersion, serviceGroup);
    return String.format("%s:%s", serviceName, serviceVersion);
}

/**
 * 获取服务注册节点键名
 */
public String getServiceNodeKey() {
    return String.format("%s/%s:%s", getServiceKey(), serviceHost, servicePor	t);
}

/**
 * 获取完整服务地址(服务调用会用到)
 */
public String getServiceAddress() {
    if (!StrUtil.contains(serviceHost, "http")) {
        return String.format("http://%s:%s", serviceHost, servicePort);
    }
    return String.format("%s:%s", serviceHost, servicePort);
}

2)注册中心配置

/**
 * RPC 框架注册中心配置
 */
public class RegistryConfig {

    /**
     * 注册中心类别
     */
    private String registry = "etcd";

    /**
     * 注册中心地址
     */
    private String address = "http://localhost:2380";

    /**
     * 用户名
     */
    private String username;

    /**
     * 密码
     */
    private String password;

    /**
     * 超时时间(单位毫秒)
     */
    private Long timeout = 10000L;
}

3)注册中心接口

定义注册中心接口,后续可以实现多种不同的注册中心。可以使用SPI机制,动态加载

提供注册中心初始化、注册服务、注销服务、服务发现(获取服务节点列表)、服务销毁等方法。

/**
 * 注册中心接口
 */
public interface Registry {

    /**
     * 初始化
     *
     * @param registryConfig
     */
    void init(RegistryConfig registryConfig);

    /**
     * 注册服务(服务端)
     *
     * @param serviceMetaInfo
     */
    void register(ServiceMetaInfo serviceMetaInfo) throws Exception;

    /**
     * 注销服务(服务端)
     *
     * @param serviceMetaInfo
     */
    void unRegister(ServiceMetaInfo serviceMetaInfo);

    /**
     * 服务发现(获取某服务的所有节点,消费端)
     *
     * @param serviceKey 服务键名
     * @return
     */
    List<ServiceMetaInfo> serviceDiscovery(String serviceKey);

    /**
     * 服务销毁
     */
    void destroy();
}

4)Etcd注册中心实现

public class EtcdRegistry implements Registry {

    private static final Logger logger = LoggerFactory.getLogger(EtcdRegistry.class);

    private Client client;

    private KV kvClient;

    /**
     * 根节点
     */
    private static final String ETCD_ROOT_PATH = "/rpc/";

    @Override
    public void init(RegistryConfig registryConfig) {
        logger.info("etcd注册中心初始化...");
        client = Client.builder().endpoints(registryConfig.getAddress()).connectTimeout(Duration.ofMillis(registryConfig.getTimeout())).build();
        kvClient = client.getKVClient();
    }

    /**
     * 服务注册(默认30s自动剔除)
     *
     * @param serviceMetaInfo 服务元信息
     */
    @Override
    public void register(ServiceMetaInfo serviceMetaInfo) throws Exception {
        // 创建 Lease 和 KV 客户端
        Lease leaseClient = client.getLeaseClient();

        // 创建一个 30 秒的租约
        long leaseId = leaseClient.grant(30).get().getID();

        // 设置要存储的键值对
        String registerKey = ETCD_ROOT_PATH + serviceMetaInfo.getServiceNodeKey();
        ByteSequence key = ByteSequence.from(registerKey, StandardCharsets.UTF_8);
        ByteSequence value = ByteSequence.from(JSONUtil.toJsonStr(serviceMetaInfo), StandardCharsets.UTF_8);

        // 将键值对与租约关联起来,并设置过期时间
        PutOption putOption = PutOption.builder().withLeaseId(leaseId).build();
        kvClient.put(key, value, putOption).get();
    }

    public void unRegister(ServiceMetaInfo serviceMetaInfo) {
        kvClient.delete(ByteSequence.from(ETCD_ROOT_PATH + serviceMetaInfo.getServiceNodeKey(), StandardCharsets.UTF_8));
    }

    public List<ServiceMetaInfo> serviceDiscovery(String serviceKey) {
        // 前缀搜索,结尾一定要加 '/'
        String searchPrefix = ETCD_ROOT_PATH + serviceKey + "/";

        try {
            // 前缀查询
            GetOption getOption = GetOption.builder().isPrefix(true).build();
            List<KeyValue> keyValues = kvClient.get(
                            ByteSequence.from(searchPrefix, StandardCharsets.UTF_8),
                            getOption)
                    .get()
                    .getKvs();
            // 解析服务信息
            return keyValues.stream()
                    .map(keyValue -> {
                        String value = keyValue.getValue().toString(StandardCharsets.UTF_8);
                        return JSONUtil.toBean(value, ServiceMetaInfo.class);
                    })
                    .collect(Collectors.toList());
        } catch (Exception e) {
            throw new RuntimeException(String.format("serviceKey=%s, 获取服务列表失败", serviceKey), e);
        }
    }

    public void destroy() {
        logger.info("etcd注册中心下线...");
        // 释放资源
        if (kvClient != null) {
            kvClient.close();
        }
        if (client != null) {
            client.close();
        }
    }

}

2.SPI机制配置和扩展注册中心

使用SPI机制,读取配置文件,初始化对应的注册中心。

对应类属性位置:RpcConfig#registryConfig#registry

可以自己实现接口,自行扩展注册中心,也可以使用默认etcd注册中心。

SPI机制:添加对应SPI配置文件。

image-20241221181629344

注册中心工厂

类似序列化器,创建注册中心工厂,可以通过配置文件配置注册中心类型,指定对应注册中心

/**
 * 注册中心工厂(用于获取注册中心对象)
 */
public class RegistryFactory {

    static {
        SpiLoader.load(Registry.class);
    }

    /**
     * 默认注册中心
     */
    private static final Registry DEFAULT_REGISTRY = new EtcdRegistry();

    /**
     * 获取实例
     *
     * @param key 注册中心键值
     * @return 注册中心实例
     */
    public static Registry getInstance(String key) {
        return SpiLoader.getInstance(Registry.class, key);
    }

}

3.RPC调用

1)服务代理类

服务代理类,使用jdk动态代理(实现InvocationHandler类),用于生成代理对象实现远程调用

/**
 * 服务代理(JDK 动态代理)
 */
public class ServiceProxy implements InvocationHandler {

    /**
     * 调用代理
     *
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        RpcConfig rpcConfig = RpcApplication.getRpcConfig();
        // 指定序列化器
        final Serializer serializer = SerializerFactory.getInstance(rpcConfig.getSerializer());

        // 构造请求
        String serviceName = method.getDeclaringClass().getName();
        RpcRequest rpcRequest = new RpcRequest();
        rpcRequest.setServiceName(serviceName);
        rpcRequest.setMethodName(method.getName());
        rpcRequest.setParameterTypes(method.getParameterTypes());
        rpcRequest.setArgs(args);

        try {
            // 序列化
            byte[] bodyBytes = serializer.serialize(rpcRequest);

            // 从注册中心获取服务提供者请求地址
            Registry registry = RegistryFactory.getInstance(rpcConfig.getRegistryConfig().getRegistry());
            ServiceMetaInfo serviceMetaInfo = new ServiceMetaInfo();
            serviceMetaInfo.setServiceName(serviceName);
            serviceMetaInfo.setServiceVersion(RpcConstants.DEFAULT_SERVICE_VERSION);
            List<ServiceMetaInfo> serviceMetaInfoList = registry.serviceDiscovery(serviceMetaInfo.getServiceKey());
            if (CollUtil.isEmpty(serviceMetaInfoList)) {
                throw new RuntimeException("暂无服务地址");
            }
            // 先默认取第一个,后续优化
            ServiceMetaInfo selectedServiceMetaInfo = serviceMetaInfoList.get(0);

            // 发送请求
            try (HttpResponse httpResponse = HttpRequest.post(selectedServiceMetaInfo.getServiceAddress())
                    .body(bodyBytes)
                    .execute()) {
                byte[] result = httpResponse.bodyBytes();
                // 反序列化
                RpcResponse rpcResponse = serializer.deserialize(result, RpcResponse.class);
                return rpcResponse.getData();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        return null;
    }
}

2)服务代理工厂

/**
 * 服务代理工厂(工厂模式,用于创建代理对象)
 */
public class ServiceProxyFactory {

    /**
     * 根据服务类获取代理对象
     *
     * @param serviceClass
     * @param <T>
     * @return
     */
    public static <T> T getProxy(Class<T> serviceClass) {
        return (T) Proxy.newProxyInstance(
                serviceClass.getClassLoader(),
                new Class[]{serviceClass},
                new ServiceProxy());
    }

}

基于服务代理类,生成代理对象,实现服务远程调用

3)本地注册中心

用于存放接口名与具体实现类映射,便于获取

/**
 * 本地注册中心
 */
public class LocalRegistry {

    /**
     * 注册信息存储
     */
    private static final Map<String, Class<?>> map = new ConcurrentHashMap<>();

    /**
     * 注册服务
     *
     * @param serviceName 接口
     * @param implClass   实现类
     */
    public static void register(String serviceName, Class<?> implClass) {
        map.put(serviceName, implClass);
    }

    /**
     * 获取服务
     *
     * @param serviceName
     * @return
     */
    public static Class<?> get(String serviceName) {
        return map.get(serviceName);
    }

    /**
     * 删除服务
     *
     * @param serviceName
     */
    public static void remove(String serviceName) {
        map.remove(serviceName);
    }
}

4)web服务器

Vertx官方文档:https://vertx.io/

使用Vertx实现web服务器

添加依赖

        <dependency>
            <groupId>io.vertx</groupId>
            <artifactId>vertx-core</artifactId>
            <version>4.5.1</version>
        </dependency>
/**
 * HTTP 服务器接口
 */
public interface HttpServer {

    /**
     * 启动服务器
     *
     * @param port
     */
    void doStart(int port);
}
/**
 * Vertx HTTP 服务器
 */
public class VertxHttpServer implements HttpServer {

    private static final Logger logger = LoggerFactory.getLogger(VertxHttpServer.class);

    /**
     * 启动服务器
     *
     * @param port 端口
     */
    public void doStart(int port) {
        // 创建 Vert.x 实例
        Vertx vertx = Vertx.vertx();

        // 创建 HTTP 服务器
        io.vertx.core.http.HttpServer server = vertx.createHttpServer();

        // 处理请求
        server.requestHandler(new HttpServerHandler());

        // 启动 HTTP 服务器并监听指定端口
        server.listen(port, result -> {
            if (result.succeeded()) {
                logger.info("Server is now listening on port " + port);
            } else {
                logger.error("Failed to start server: " + result.cause());
            }
        });
    }
}

具体业务处理逻辑

/**
 * HTTP 请求处理器
 */
public class HttpServerHandler implements Handler<HttpServerRequest> {

    private static final Logger logger = LoggerFactory.getLogger(HttpServerHandler.class);

    @Override
    public void handle(HttpServerRequest request) {
        // 指定序列化器
        final Serializer serializer = SerializerFactory.getInstance(RpcApplication.getRpcConfig().getSerializer());

        // 记录日志
        logger.info("Received request: " + request.method() + " " + request.uri());

        // 异步处理 HTTP 请求
        request.bodyHandler(body -> {
            byte[] bytes = body.getBytes();
            RpcRequest rpcRequest = null;
            try {
                rpcRequest = serializer.deserialize(bytes, RpcRequest.class);
            } catch (Exception e) {
                e.printStackTrace();
            }

            // 构造响应结果对象
            RpcResponse rpcResponse = new RpcResponse();
            // 如果请求为 null,直接返回
            if (rpcRequest == null) {
                rpcResponse.setMessage("rpcRequest is null");
                doResponse(request, rpcResponse, serializer);
                return;
            }

            try {
                // 获取要调用的服务实现类,通过反射调用
                Class<?> implClass = LocalRegistry.get(rpcRequest.getServiceName());
                Method method = implClass.getMethod(rpcRequest.getMethodName(), rpcRequest.getParameterTypes());
                Object result = method.invoke(implClass.newInstance(), rpcRequest.getArgs());
                // 封装返回结果
                rpcResponse.setData(result);
                rpcResponse.setDataType(method.getReturnType());
                rpcResponse.setMessage("ok");
            } catch (Exception e) {
                e.printStackTrace();
                rpcResponse.setMessage(e.getMessage());
                rpcResponse.setException(e);
            }
            // 响应
            doResponse(request, rpcResponse, serializer);
        });
    }

    /**
     * 响应
     *
     * @param request
     * @param rpcResponse
     * @param serializer
     */
    void doResponse(HttpServerRequest request, RpcResponse rpcResponse, Serializer serializer) {
        HttpServerResponse httpServerResponse = request.response()
                .putHeader("content-type", "application/json");
        try {
            // 序列化
            byte[] serialized = serializer.serialize(rpcResponse);
            httpServerResponse.end(Buffer.buffer(serialized));
        } catch (IOException e) {
            e.printStackTrace();
            httpServerResponse.end(Buffer.buffer());
        }
    }
}

5)服务提供者服务注册

创建服务提供者module,引入starlink-rpc-core模块

image-20241221175357225

RPC相关配置application.properties

rpc.name=starlink
rpc.version=1.0
rpc.serverPort=8081
rpc.serializer=hessian

服务接口及实现类

public class HelloServiceImpl implements HelloService {
    @Override
    public String hello(String name) {
        return "hello, " + name;
    }
}

服务注册

public class ServiceStarter {
    public static void main(String[] args) {
        RpcApplication.init();

        RpcConfig rpcConfig = RpcApplication.getRpcConfig();
        Registry registry = RegistryFactory.getInstance(rpcConfig.getRegistryConfig().getRegistry());


        // 注册服务
        String serviceName = HelloService.class.getName();
        LocalRegistry.register(serviceName, HelloServiceImpl.class);

        ServiceMetaInfo serviceMetaInfo = new ServiceMetaInfo();
        serviceMetaInfo.setServiceHost(rpcConfig.getServerHost());
        serviceMetaInfo.setServicePort(rpcConfig.getServerPort());
        serviceMetaInfo.setServiceName(serviceName);
        serviceMetaInfo.setServiceVersion(rpcConfig.getVersion());
        try {
            registry.register(serviceMetaInfo);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

        // 启动Http服务
        HttpServer httpServer = new VertxHttpServer();
        httpServer.doStart(rpcConfig.getServerPort());
    }
}

客户端查看,注册成功,类似文件夹结构。

image-20241221175319016

6)服务调用者远程调用

依旧创建module,引入starlink-rpc-core及服务提供者example-consumer依赖

image-20241221180145283

先启动服务提供者,再远程调用

public class TestService {
    public static void main(String[] args) {
        // 创建代理对象
        HelloService helloService = ServiceProxyFactory.getProxy(HelloService.class);
        System.out.println(helloService.hello("Jack"));
    }
}

远程调用成功。

image-20241221180523273

核心:动态代理【ServiceProxy】 + 反射调用【HttpServerHandler】

基本可用,后续优化。

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

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

相关文章

sqlite 自定以脚本解释器

应用程序使用 libfdt 解析设备树,获取兼容性配置 内核源码支持libfdt 标准设备树语法,不用自己再创造 非常的爽,因为设备树支持预编译 一些可以跑类 BSD 系统的设备也可以使用这样的方法,不仅仅是在linux 系统上跑 有pylibfdt 支持解析设备树&#xff0c;校验设备树是否是正确的…

[python]pymc3-3.11.0安装后测试代码

测试通过环境&#xff1a; pymc33.11.0 python3.8 测试代码&#xff1a; import arviz as az import matplotlib.pyplot as plt import numpy as np import pymc3 as pm RANDOM_SEED 8927 np.random.seed(RANDOM_SEED) az.style.use("arviz-darkgrid") # True p…

Chrome 关闭自动添加https

Open Chrome and go to “chrome://net-internals/#hsts”

重温设计模式--适配器模式

文章目录 适配器模式&#xff08;Adapter Pattern&#xff09;概述适配器模式UML图适配器模式的结构目标接口&#xff08;Target&#xff09;&#xff1a;适配器&#xff08;Adapter&#xff09;&#xff1a;被适配者&#xff08;Adaptee&#xff09;&#xff1a; 作用&#xf…

Flamingo:少样本多模态大模型

Flamingo&#xff1a;少样本多模态大模型 论文大纲理解1. 确认目标2. 分析过程&#xff08;目标-手段分析&#xff09;3. 实现步骤4. 效果展示5. 金手指 解法拆解全流程核心模式提问Flamingo为什么选择使用"固定数量的64个视觉tokens"这个特定数字?这个数字的选择背…

[spring]处理器

我们可以通过spring来管理我们的类&#xff0c;之后我们可以通过spring的容器来获取我们所需要的Bean类对象。Spring的处理器是Spring对外开发的重要扩展点&#xff0c;它允许我们介入到Bean的整个实例化流程中来&#xff0c;可以动态添加、修改BeanDefinition、动态修改Bean 首…

git企业开发的相关理论(二)

目录 git企业开发的相关理论&#xff08;一&#xff09; 八.修改文件 九.版本回退 十.撤销修改 情况一(还没有add) 情况二(add后还没有commit) 情况三(commit后还没有push) 十一.删除本地仓库中的文件 方法一 方法二 十二.理解分支 1.常见的分支工作流程 2.合并冲…

计算机网络压缩版

计算机网络到现在零零散散也算过了三遍&#xff0c;一些协议大概了解&#xff0c;但总是模模糊糊的印象&#xff0c;现在把自己的整体认识总结一下&#xff0c;&#xff08;本来想去起名叫《看这一篇就够了》&#xff0c;但是发现网上好的文章太多了&#xff0c;还是看这篇吧&a…

重温设计模式--状态模式

文章目录 状态模式&#xff08;State Pattern&#xff09;概述状态模式UML图作用&#xff1a;状态模式的结构环境&#xff08;Context&#xff09;类&#xff1a;抽象状态&#xff08;State&#xff09;类&#xff1a;具体状态&#xff08;Concrete State&#xff09;类&#x…

JVM执行引擎JIT深度剖析

前端编译与后端编译 Java 程序的编译过程是分两个部分的。一个部分是从java文件编译成为class文件&#xff0c;这一部分也称为前端编译。另一个部分则是这些class文件&#xff0c;需要进入到 JVM 虚拟机&#xff0c;将这些字节码指令编译成操作系统识别的具体机器指令。这一部…

五分钟学会如何在GitHub上自动化部署个人博客(hugo框架 + stack主题)

上一篇文章&#xff1a; 10分钟学会免费搭建个人博客&#xff08;Hugo框架 stack主题&#xff09; 前言 首先&#xff0c;想要实现这个功能的小伙伴需要完成几个前置条件&#xff1a; 有一个GitHub账号安装了git&#xff0c;并可以通过git推送commit到GitHub上完成第一篇文章…

OpenHarmony的分布式服务框架介绍与实现解析

OpenHarmony的分布式服务框架是一个用于实现设备间高效协作与资源共享的重要架构&#xff0c;以下是其详细介绍&#xff1a; 框架概述 OpenHarmony的分布式服务框架基于分布式软总线、分布式数据管理、分布式Profile等技术特性&#xff0c;构建了统一的分布式服务管理机制&am…

360pika—弹性 KV 数据存储系统入门安装使用

一、简介 github官网说Pika 是一个高性能、大容量、多租户、数据持久化的弹性 KV 数据存储系统,使用 RocksDB 作为存储引擎。它完全兼容 Redis 协议,并支持其常用的数据结构,如字符串/哈希/列表/有序集合/集合/地理位置/HyperLogLog/发布-订阅/位图/数据流等。 二、对标啥能干…

springboot中使用gdal将表中的空间数据转shapefile文件

springboot中使用gdal将表中的空间数据转shapefile文件 代码&#xff1a; // 样本导出-将样本表导出为shapefile&#xff0c;复制样本shp文件到临时目录下 sampleDir是文件夹pathpublic void setYbShapeFile(Yb yb, File sampleDir) {// 创建 前时项 和 后时项 文件夹File y…

【学习笔记】蒙特卡洛与强化学习

视频链接&#xff1a;https://www.bilibili.com/video/BV1SV4y1i7bW 文章目录 [蒙特卡洛方法] 02 重要性采样&#xff08;importance sampling&#xff09;及 python 实现Basics实现重要性采样 [蒙特卡洛方法] 03 接受/拒绝采样&#xff08;accept/reject samping&#xff09;初…

查看MySQL存储引擎方法,表操作

修改数据库表存储引擎 show create table dept; show table status from itpux where name s2\G; select * from information_schema.TABLES where table_schemaitpux and table_names3; 查询整个mysql里面存储引擎是innodb/myisam的表 建表时候要写好存储引擎 -- 创建表 -- 表…

项目亮点案例

其实对我来说是日常操作&#xff0c;但是如果在面试的时候面试者能把日常的事情总结好发出来&#xff0c;其实足矣。 想让别人认同项目&#xff0c;选取的示例需要包含以下要素&#xff1a; 亮点项目四要素&#xff1a;明确的目标&#xff0c;问题点&#xff0c;解决方法和结果…

MyBatis通过注解配置执行SQL语句原理源码分析

文章目录 前置准备流程简要分析配置文件解析加载 Mapper 接口MapperAnnotationBuilder解析接口方法注解parseStatement 方法详解MapperBuilderAssistant 前置准备 创建一个mybatis-config.xml文件&#xff0c;配置mapper接口 <mappers><!--注解配置--><mapper…

蓝桥杯物联网开发板硬件组成

第一节 开发板简介 物联网设计与开发竞赛实训平台由蓝桥杯大赛技术支持单位北京四梯科技有限公司设计和生产&#xff0c;该产品可用于参加蓝桥杯物联网设计与开发赛道的竞赛实训或院校相关课程的 实践教学环节。 开发板基于STM32WLE5无线微控制器设计&#xff0c;芯片提供了25…

常用矢量图标库

常用矢量图标库 1. iconfont 阿里巴巴旗下的矢量图标素材库&#xff1b;很强大且图标内容很丰富的矢量图标库,提供矢量图标下载&#xff08;AI / SVG / PNG / 代码格式&#xff09;、在线存储等功能&#xff0c;支持按路径改变 icon 颜色。 iconfont 网址 设备图标 2. IconP…