PolarisMesh源码系列——服务如何注册

前话

PolarisMesh(北极星)是腾讯开源的服务治理平台,致力于解决分布式和微服务架构中的服务管理、流量管理、配置管理、故障容错和可观测性问题,针对不同的技术栈和环境提供服务治理的标准方案和最佳实践。

PolarisMesh 官网:https://polarismesh.cn/#/
PolarisMesh Github:https://github.com/polarismesh/polaris

Polaris-server 作为 PolarisMesh 的控制面,该进程主要负责服务数据、配置数据、治理规则的管理以及下发至北极星 SDK 以及实现了 xDS 的客户端。

Polaris-server 是如何处理客户端的服务注册请求的呢?服务数据是怎么存储的呢?带着这个疑问,我们来探究看下 Polaris-server 的启动流程,看看北极星是实现的。

前期准备

  • golang 环境,需要1.17.x +
  • 准备 vscode 或者 goland
  • 从 github 中 clone 一份 polaris-server 的源码,这里推荐从 release-vx.y.z 分支中选择一个分支进行学习,以下文章将基于 release-v1.12.0 分支进行研究。
  • 从 github 中 clone 一份 polaris-java 的源码,这里推荐从 release-vx.y.z 分支中选择一个分支进行学习,以下文章将基于release-v1.10.0分支进行研究。

正题

注册数据模型

polaris-java
InstanceRegisterRequest request = new InstanceRegisterRequest();
// 设置实例所属服务信息
request.setService(service);
// 设置实例所属服务的命名空间信息
request.setNamespace(namespace);
// 设置实例的 host 信息
request.setHost(host);
// 设置实例的端口信息
request.setPort(port);
// 可选,资源访问Token,即用户/用户组访问凭据,仅当服务端开启客户端鉴权时才需配置
request.setToken(token);
// 设置实例版本
request.setVersion(version);
// 设置实例的协议
request.setProtocol(protocol);
// 设置实例权重
request.setWeight(weight);
// 设置实例的标签
request.setMetadata(metadata);
// 设置实例地理位置 zone 信息
request.setZone(zone);
// 设置实例地理位置 region 信息
request.setRegion(region);
// 设置实例地理位置 campus 信息
request.setCampus(campus);
// ttl超时时间,如果节点要调用heartbeat上报,则必须填写,否则会400141错误码,单位:秒
request.setTtl(ttl);
polaris-go
// InstanceRegisterRequest 注册服务请求
type InstanceRegisterRequest struct {// 必选,服务名Service string// 必选,命名空间Namespace string// 必选,服务监听host,支持IPv6地址Host string// 必选,服务实例监听portPort int// 可选,资源访问Token,即用户/用户组访问凭据,仅当服务端开启客户端鉴权时才需配置ServiceToken string// 以下字段可选,默认nil表示客户端不配置,使用服务端配置// 服务协议Protocol *string// 服务权重,默认100,范围0-10000Weight *int// 实例提供服务版本号Version *string// 用户自定义metadata信息Metadata map[string]string// 该服务实例是否健康,默认健康Healthy *bool// 该服务实例是否隔离,默认不隔离Isolate *bool// ttl超时时间,如果节点要调用heartbeat上报,则必须填写,否则会400141错误码,单位:秒TTL *int// Location 当前注册实例的地理位置信息,主要用于就近路由Location *Location// 可选,单次查询超时时间,默认直接获取全局的超时配置// 用户总最大超时时间为(1+RetryCount) * TimeoutTimeout *time.Duration// 可选,重试次数,默认直接获取全局的超时配置RetryCount *int
}

客户端发起注册请求

可以先通过官方的 SDK 使用手册来看看是如何使用SDK的服务注册。

  • polaris-java 服务注册功能使用

https://polarismesh.cn/docs/%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%97/java%E5%BA%94%E7%94%A8%E5%BC%80%E5%8F%91/sdk/%E6%9C%8D%E5%8A%A1%E6%B3%A8%E5%86%8C%E5%8F%91%E7%8E%B0/

  • polaris-go 服务注册功能使用

https://polarismesh.cn/docs/%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%97/go%E5%BA%94%E7%94%A8%E5%BC%80%E5%8F%91/sdk/%E6%9C%8D%E5%8A%A1%E6%B3%A8%E5%86%8C%E5%8F%91%E7%8E%B0/

这里我们已 polaris-java 为例,看看 polaris-java 如何将服务实例注册请求发送至北极星服务端。

发起注册请求

ProviderAPI providerAPI = DiscoveryAPIFactory.createProviderAPI();
InstanceRegisterRequest registerRequest = new InstanceRegisterRequest();
// 初始化服务实例注册信息
...
InstanceRegisterResponse registerResp = providerAPI.registerInstance(registerRequest);

通过这个简单示例代码可知,服务实例的注册动作是通过 polaris-java 中 ProviderAPI(负责服务实例注册相关方法的调用) 的 registerInstance 方法完成。

通过 IDEA 或者 vscode,查看这个方法的具体实现:

@Override
public InstanceRegisterResponse registerInstance(InstanceRegisterRequest req) throws PolarisException {if (req.getTtl() == null) {req.setTtl(DEFAULT_INSTANCE_TTL);}return registerFlow.registerInstance(req, this::doRegister, this::heartbeat);
}

当调用 providerAPI.registerInstance 后,SDK 内部会自动设置实例的 TTL 周期,然后交由 RegisterFlow 这个负责注册动作的流程编排者执行。因此接着看看这个 RegisterFlow 的定义。

public class RegisterFlow {// 异步注册header keyprivate static final String HEADER_KEY_ASYNC_REGIS = "async-regis";// 最大连续心跳失败阈值private static final int HEARTBEAT_FAIL_COUNT_THRESHOLD = 2;// SDK 上下文,包括SDK配置、SDK插件实例管理、内部任务流程编排等等private final SDKContext sdkContext;// 发送实例心跳private final ScheduledThreadPoolExecutor asyncRegisterExecutor;...
}

其实 RegisterFlow 就干两件事件:

  • 发起实例注册动作

  • 内部维护每个实例的心跳上报

public InstanceRegisterResponse registerInstance(InstanceRegisterRequest request, RegisterFunction registerFunction,HeartbeatFunction heartbeatFunction) {// 将注册请求发送至北极星服务端InstanceRegisterResponse instanceRegisterResponse = registerFunction.doRegister(request,createRegisterV2Header());// 当前实例的注册管理状态进行本地保存RegisterState registerState = RegisterStateManager.putRegisterState(sdkContext, request);if (registerState != null) {// 首次存入实例的注册状态时,为该实例注册创建定期心跳上报动作任务registerState.setTaskFuture(asyncRegisterExecutor.scheduleWithFixedDelay(() -> doRunHeartbeat(registerState, registerFunction, heartbeatFunction), request.getTtl(),request.getTtl(), TimeUnit.SECONDS));}return instanceRegisterResponse;
}

来看看 registerFunction.doRegister 的主要流程以及如何将请求发送到服务端。

// com.tencent.polaris.discovery.client.flow.RegisterFlow#registerInstance
private InstanceRegisterResponse doRegister(InstanceRegisterRequest req, Map<String, String> customHeader) {checkAvailable("ProviderAPI");Validator.validateInstanceRegisterRequest(req);// 填充注册实例的地理位置信息enrichInstanceLocation(req);...// 调用协议插件,发起网络调用CommonProviderResponse response = serverConnector.registerInstance(request, customHeader);...
}// com.tencent.polaris.plugins.connector.grpc.GrpcConnector#registerInstance
public CommonProviderResponse registerInstance(CommonProviderRequest req, Map<String, String> customHeader)throws PolarisException {...try {waitDiscoverReady();// 从连接池中获取一个链接connection = connectionManager.getConnection(GrpcUtil.OP_KEY_REGISTER_INSTANCE, ClusterType.SERVICE_DISCOVER_CLUSTER);req.setTargetServer(connectionToTargetNode(connection));// 根据 Connection 创建一个 gRPC StubPolarisGRPCGrpc.PolarisGRPCBlockingStub stub = PolarisGRPCGrpc.newBlockingStub(connection.getChannel());...// 向服务端发起 gRPC 请求,完成服务实例的注册ResponseProto.Response registerInstanceResponse = stub.registerInstance(buildRegisterInstanceRequest(req));...
}

服务端处理注册请求

在这里插入图片描述

当实例注册请求从北极星 SDK 发出之后,数据流在服务端主要经历这几个流程:

  • apiserver 层接受 SDK 的注册请求,将其转为对应的服务端数据结构。
  • apiserver 层将请求传递到 resource auth filter 层,进行资源鉴权。
  • resource auth filter 完成资源鉴权后,数据流转到 service 层。
  • service 层完成服务注册请求的合法性检查以及资源存在性检查比对。
    • 如果资源存在,则直接返回已存在实例的唯一 ID。
    • 如果资源不存在,则根据 namespace、service、host、port 计算出实例的唯一 ID。
    • 将注册请求扔进 BatchController 中。
  • batch controller 将一批注册请求转为 store 层的数据结构。
  • batch controller 将请求传递到 store 层,store 层选择具体的 store plugin 实现完成写操作。
    • mysql 插件:将服务实例信息写入到对应的数据库表中。
    • bolt 插件:将服务实例信息写入到本地文件中。

apiserver 层

北极星的 apiserver 层进行接收并处理,由于北极星 SDK 和服务端的数据通信走的是 gRPC 协议,因此这里请求就会在基于 gRPC 实现的 apiserver 插件中进行处理。

// RegisterInstance 注册服务实例
func (g *DiscoverServer) RegisterInstance(ctx context.Context, in *apiservice.Instance) (*apiservice.Response, error) {// 需要记录操作来源,提高效率,只针对特殊接口添加operatorrCtx := grpcserver.ConvertContext(ctx)rCtx = context.WithValue(rCtx, utils.StringContext("operator"), ParseGrpcOperator(ctx))// 客户端请求中带了 token 的,优先已请求中的为准if in.GetServiceToken().GetValue() != "" {rCtx = context.WithValue(rCtx, utils.ContextAuthTokenKey, in.GetServiceToken().GetValue())}grpcHeader := rCtx.Value(utils.ContextGrpcHeader).(metadata.MD)if _, ok := grpcHeader["async-regis"]; ok {rCtx = context.WithValue(rCtx, utils.ContextOpenAsyncRegis, true)}out := g.namingServer.RegisterInstance(rCtx, in)return out, nil
}

resource auth filter 层

在 apiserver 层处理好请求之后,就会将请求转发给 resource auth filter 层进行权限检查。但是从代码来看,调用的路径明明是 out := g.namingServer.RegisterInstance(rCtx, in),看这是 service 层的逻辑代码呀,怎么是 resource auth filter 逻辑呢?

这里就要说明下,由于北极星服务端是多协议设计模式,如果说鉴权逻辑放在协议层,则需要针对每个协议接入都需要做一次鉴权代码适配,因此这里做了一层调整,将 resource auth filter 的适配逻辑放在了 service 的接口层面,通过代理模式达到在每次调用 service 的接口都是经过 resource auth filter 层的。

  // 需要返回包装代理的 DiscoverServerorder := namingOpt.Interceptorsfor i := range order {factory, exist := serverProxyFactories[order[i]]if !exist {return fmt.Errorf("name(%s) not exist in serverProxyFactories", order[i])}proxySvr, err := factory(namingServer, server)if err != nil {return err}server = proxySvr}return nil

通过代理模式,因此在 apiserver 层调用 service 层的 RegisterInstance 方法时,会先经过 resource auth filter 进行资源权限检查。

// RegisterInstance create one instance
func (svr *ServerAuthAbility) RegisterInstance(ctx context.Context, req *apiservice.Instance) *apiservice.Response {// 收集本次客户端发起的服务实例注册请求所涉及的操作资源authCtx := svr.collectClientInstanceAuthContext(ctx, []*apiservice.Instance{req}, model.Create, "RegisterInstance")// 调用对应的鉴权插件,对本次操作进行权限检查_, err := svr.strategyMgn.GetAuthChecker().CheckClientPermission(authCtx)if err != nil {resp := api.NewResponseWithMsg(convertToErrCode(err), err.Error())return resp}ctx = authCtx.GetRequestContext()ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx)// 权限检查通过之后,将请求转发给 service 层return svr.targetServer.RegisterInstance(ctx, req)
}

service 层

在 resource auth filter 层处理好请求之后,接着就调用 g.namingServer.RegisterInstance(rCtx, in) 将服务实例数据写进北极星集群中。

// CreateInstance create a single service instance
func (s *Server) CreateInstance(ctx context.Context, req *apiservice.Instance) *apiservice.Response {...data, resp := s.createInstance(ctx, req, &ins)...// 发布实例上线事件 & 记录实例注册操作记录s.sendDiscoverEvent(*event)s.RecordHistory(ctx, instanceRecordEntry(ctx, req, svc, data, model.OCreate))...
}// createInstance store operate
func (s *Server) createInstance(ctx context.Context, req *apiservice.Instance, ins *apiservice.Instance) (*model.Instance, *apiservice.Response) {// 自动创建实例所在的服务信息svcId, errResp := s.createWrapServiceIfAbsent(ctx, req)if errResp != nil {log.Errorf("[Instance] create service if absent fail : %+v, req : %+v", errResp.String(), req)return nil, errResp}if len(svcId) == 0 {log.Errorf("[Instance] create service if absent return service id is empty : %+v", req)return nil, api.NewResponseWithMsg(apimodel.Code_BadRequest, "service id is empty")}// 根据 CMDB 插件填充实例的地域信息s.packCmdb(ins)// 如果没有开启实例批量注册,则同步调用存储层接口将实例数据进行持久化if namingServer.bc == nil || !namingServer.bc.CreateInstanceOpen() {return s.serialCreateInstance(ctx, svcId, req, ins) // 单个同步}// 如果开启了实例批量注册,则会将注册请求丢入一个异步队列进行处理return s.asyncCreateInstance(ctx, svcId, req, ins) // 批量异步
}

同步注册实例

func (s *Server) serialCreateInstance(ctx context.Context, svcId string, req *apiservice.Instance, ins *apiservice.Instance) (*model.Instance, *apiservice.Response) {...instance, err := s.storage.GetInstance(ins.GetId().GetValue())...// 如果存在,则替换实例的属性数据,但是需要保留用户设置的隔离状态,以免出现关键状态丢失if instance != nil && ins.Isolate == nil {ins.Isolate = instance.Proto.Isolate}// 直接同步创建服务实例data := instancecommon.CreateInstanceModel(svcId, ins)// 创建服务实例时,需要先锁住服务,避免在创建实例的时候把服务信息删除导致出现错误的数据_, releaseFunc, errCode := s.lockService(ctx, req.GetNamespace().GetValue(),req.GetService().GetValue())if errCode != apimodel.Code_ExecuteSuccess {return nil, api.NewInstanceResponse(errCode, req)}defer releaseFunc()// 调用存储层直接写实例信息if err := s.storage.AddInstance(data); err != nil {log.Error(err.Error(), utils.ZapRequestID(rid), utils.ZapPlatformID(pid))return nil, wrapperInstanceStoreResponse(req, err)}return data, nil
}

异步注册实例

func (s *Server) asyncCreateInstance(ctx context.Context, svcId string, req *apiservice.Instance, ins *apiservice.Instance) (*model.Instance, *apiservice.Response) {allowAsyncRegis, _ := ctx.Value(utils.ContextOpenAsyncRegis).(bool)// 将实例注册请求放入异步任务池中future := s.bc.AsyncCreateInstance(svcId, ins, !allowAsyncRegis)// 等待任务完成if err := future.Wait(); err != nil {if future.Code() == apimodel.Code_ExistedResource {req.Id = utils.NewStringValue(ins.GetId().GetValue())}return nil, api.NewInstanceResponse(future.Code(), req)}return instancecommon.CreateInstanceModel(svcId, req), nil
}

存储层处理注册数据

北极星的存储层是插件化设计,单机模式下是采用 boltdb,集群模式则是依赖 MySQL,这里只说集群模式下的存储层处理。

依赖 MySQL 的存储层实现中,针对实例信息,北极星将其拆分成了三个表。

实例主要信息
CREATE TABLE `instance`
(# 实例 ID,主键,唯一约束`id`                  varchar(128) NOT NULL,# 实例所属的服务 ID`service_id`          varchar(32)  NOT NULL,# 实例对外可访问的 IP 地址信息`host`                varchar(128) NOT NULL,# 实例对外可访问的端口地址信息`port`                int(11)      NOT NULL,# 实例的端口协议信息,用户自定义,比如 dubbo、gRPC、http 等`protocol`            varchar(32)           DEFAULT NULL,# 实例的版本信息,用户自定义,默认为空`version`             varchar(32)           DEFAULT NULL,# 实例的健康状态,1 为健康,0 为不健康`health_status`       tinyint(4)   NOT NULL DEFAULT '1',# 实例的隔离状态,1 为打开,0 为关闭`isolate`             tinyint(4)   NOT NULL DEFAULT '0',# 实例的权重信息,主要用于负载均衡`weight`              smallint(6)  NOT NULL DEFAULT '100',# 是否开启健康检查,0 为不开启,1 为开启`enable_health_check` tinyint(4)   NOT NULL DEFAULT '0',# 实例的地域信息,记录 region, zone, idc 信息`cmdb_region`         varchar(128)          DEFAULT NULL comment 'The region information of the instance is mainly used to close the route',`cmdb_zone`           varchar(128)          DEFAULT NULL comment 'The ZONE information of the instance is mainly used to close the route.',`cmdb_idc`            varchar(128)          DEFAULT NULL comment 'The IDC information of the instance is mainly used to close the route',# 实例的版本信息,仅北极星内部使用`revision`            varchar(32)  NOT NULL comment 'Instance version information',# 实例的逻辑删除标记,0 表示表示正常,1 表示已逻辑删除`flag`                tinyint(4)   NOT NULL DEFAULT '0',`ctime`               timestamp    NOT NULL DEFAULT CURRENT_TIMESTAMP comment 'Create time',`mtime`               timestamp    NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP comment 'Last updated time',...
) ENGINE = InnoDB;
实例健康检查的类型
CREATE TABLE `health_check`
(`id`   varchar(128) NOT NULL comment 'Instance ID',`type` tinyint(4)   NOT NULL DEFAULT '0' comment 'Instance health check type',`ttl`  int(11)      NOT NULL comment 'TTL time jumping',PRIMARY KEY (`id`),CONSTRAINT `health_check_ibfk_1` FOREIGN KEY (`id`) REFERENCES `instance` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE = InnoDB;
实例的元数据
CREATE TABLE `instance_metadata`
(`id`     varchar(128)  NOT NULL comment 'Instance ID',`mkey`   varchar(128)  NOT NULL comment 'instance label of Key',`mvalue` varchar(4096) NOT NULL comment 'instance label Value',`ctime`  timestamp     NOT NULL DEFAULT CURRENT_TIMESTAMP comment 'Create time',`mtime`  timestamp     NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP comment 'Last updated time',PRIMARY KEY (`id`, `mkey`),KEY `mkey` (`mkey`),CONSTRAINT `instance_metadata_ibfk_1` FOREIGN KEY (`id`) REFERENCES `instance` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE = InnoDB;

因此在操作存储层持久化服务实例信息时,需要做以下几个操作:

// 添加实例主信息
if err := batchAddMainInstances(tx, instances); err != nil {log.Errorf("[Store][database] batch add main instances err: %s", err.Error())return err
}
// 添加实例的健康检查信息
if err := batchAddInstanceCheck(tx, instances); err != nil {log.Errorf("[Store][database] batch add instance check err: %s", err.Error())return err
}
// 先清理实例原先的 metadata 信息数据,确保不会遗留脏数据
if err := batchDeleteInstanceMeta(tx, instances); err != nil {log.Errorf("[Store][database] batch delete instance metadata err: %s", err.Error())return err
}
// 添加实例的 metadata 信息
if err := batchAddInstanceMeta(tx, instances); err != nil {log.Errorf("[Store][database] batch add instance metadata err: %s", err.Error())return err
}

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

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

相关文章

280个地级市金融集聚水平数据(2006-2022年)

2006年-2022年280个地级市金融集聚水平数据整理资源-CSDN文库 金融集聚水平&#xff1a;衡量地级市金融发展的新维度 金融集聚水平是衡量一个地区金融发展程度的重要指标&#xff0c;它反映了金融机构、金融资源、金融服务在特定时间和空间的集中程度。这一指标的评估可以从多…

视语坤川大模型智能体平台亮相2024世界人工智能大会

7月4日-7月7日&#xff0c;以“以共商促共享以善治促善智”为主题的2024世界人工智能大会&#xff08;WAIC 2024&#xff09;在上海举办&#xff0c;世界顶级专家学者、知名企业代表、政界人士、高校组织等齐聚上海&#xff0c;共商发展、共话未来。 作为大会的重磅环节——昇…

【笔记】Android V 应用SDK升级适配和问题

说明 随着Google释放的Android版本,系统升级SDK到35,应用也需要升级上去,不然会报错。 Android Studio Jellyfish | 2023.3.1 | Android Developers Android Studio 预览版中的新功能 | Android Developers 当前版本的Android Studio

Elasticsearch:深度学习与机器学习:了解差异

作者&#xff1a;来自 Elastic Elastic Platform Team 近年来&#xff0c;两项突破性技术一直站在创新的最前沿 —— 机器学习 (machine learning - ML) 和深度学习 (deep learning - DL)。人工智能 (AI) 的这些子集远不止是流行语。它们是推动医疗保健、金融等各行业进步的关键…

Java面试八股之MySQL索引B+树、全文索引、哈希索引

MySQL索引B树、全文索引、哈希索引 注意&#xff1a;B树中B不是代表二叉树&#xff08;binary&#xff09;&#xff0c;而是代表平衡&#xff08;balance&#xff09;&#xff0c;因为B树是从最早的平衡二叉树演化而来&#xff0c;但是B树不是一个二叉树。 B树的高度一般在2~…

es是如何处理索引数据的变动的?

1 概述 es是如何处理索引数据的变动的&#xff1f; 或者说索引数据变动时&#xff0c;es会执行哪些操作&#xff1f; refresh、fsync、merge 和 flush 操作有何作用&#xff1f; es是如何确保即使es发生宕机数据也不丢失的&#xff1f; 在回答上述问题前&#xff0c;可以先…

文件操作和IO流

前言&#x1f440;~ 上一章我们介绍了多线程进阶的相关内容&#xff0c;今天来介绍使用java代码对文件的一些操作 文件&#xff08;file&#xff09; 文件路径&#xff08;Path&#xff09; 文件类型 文件操作 文件系统操作&#xff08;File类&#xff09; 文件内容的读…

leetcode--恢复二叉搜索树

leetcode地址&#xff1a;恢复二叉搜索树 给你二叉搜索树的根节点 root &#xff0c;该树中的 恰好 两个节点的值被错误地交换。请在不改变其结构的情况下&#xff0c;恢复这棵树 。 示例 1&#xff1a; 输入&#xff1a;root [1,3,null,null,2] 输出&#xff1a;[3,1,null…

AirPods Pro新功能前瞻:iOS 18的五大创新亮点

随着科技的不断进步&#xff0c;苹果公司一直在探索如何通过创新提升用户体验。iOS 18的推出&#xff0c;不仅仅是iPhone的一次系统更新&#xff0c;更是苹果生态链中重要一环——AirPods Pro的一次重大升级。 据悉&#xff0c;iOS 18将为AirPods Pro带来五项新功能&#xff0…

设计模式探索:观察者模式

1. 观察者模式 1.1 什么是观察者模式 观察者模式用于建立一种对象与对象之间的依赖关系&#xff0c;当一个对象发生改变时将自动通知其他对象&#xff0c;其他对象会相应地作出反应。 在观察者模式中有如下角色&#xff1a; Subject&#xff08;抽象主题/被观察者&#xf…

详细分析@FunctionalInterface的基本知识(附Demo)

目录 前言1. 基本知识2. Demo 前言 Java的基本知识推荐阅读&#xff1a; java框架 零基础从入门到精通的学习路线 附开源项目面经等&#xff08;超全&#xff09;Spring框架从入门到学精&#xff08;全&#xff09; 1. 基本知识 FunctionalInterface 是 Java 8 引入的一个注…

外卖商城平台小程序的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;用户管理&#xff0c;商家管理&#xff0c;骑手管理&#xff0c;商品类型管理&#xff0c;商品信息管理&#xff0c;订单信息管理 微信端账号功能包括&#xff1a;系统首页&#xff0c;商品信息&#…

模板初阶详解

目录 泛型编程函数模板函数模板概念函数模板格式函数模板的原理函数模板的实例化隐式实例化强制类型转换的疑惑 显式实例化 模板参数的匹配原则 类模板类模板的定义格式类模板的实例化 感谢各位大佬对我的支持,如果我的文章对你有用,欢迎点击以下链接 &#x1f412;&#x1f41…

微信小程序接口wx.getLocation违规导致封禁解决办法

1、找到站内信的这个封禁的通知&#xff08;功能封禁的通知&#xff0c;而不是处理警告的通知&#xff09; 2、点击通知会有申诉链接&#xff0c;点开申诉链接 申诉原因可参考下面的内容&#xff1a; 1.小程序哪些板块已除去收集地理位置、2.哪些板块需要收集地理位置、3.详细…

WindowsMac共享文件夹设置

共享文件夹设置 共享文件夹设置Windows系统设置步骤一&#xff1a;设置共享文件夹步骤二: 访问共享文件夹 Mac系统中设置共享文件夹步骤一&#xff1a;设置共享文件夹步骤二&#xff1a;访问共享文件夹 小贴士结论 共享文件夹设置 有时需要在多台电脑之间共享文件夹&#xff0…

4.MkDocs样式

学习 Admonitions(警告) - Material for MkDocs (wdk-docs.github.io) 提示 - Material for MkDocs 中文文档 (llango.com) Buttons(按钮) - Material for MkDocs (wdk-docs.github.io) 建议去看这些网站&#xff0c;更为详细。 常用功能 便利贴 ​​ 开启 markdown_ex…

Gemma2——Google 新开源大型语言模型完整应用指南

0.引言 Gemma 2以前代产品为基础&#xff0c;提供增强的性能和效率&#xff0c;以及一系列创新功能&#xff0c;使其在研究和实际应用中都具有特别的吸引力。Gemma 2 的与众不同之处在于&#xff0c;它能够提供与更大的专有模型相当的性能&#xff0c;但其软件包专为更广泛的可…

hdfs大规模数据存储底层原理详解(第31天)

系列文章目录 一、HDFS设计原理 二、HDFS系统架构 三、HDFS关键技术 四、HDFS应用实例 五、解决HDFS不能处理小文件详解问题 文章目录 系列文章目录前言一、设计原理二、系统架构三、关键技术四、应用实例五、解决HDFS不能处理小文件详解问题1. 合并小文件2. 优化Hive配置3. 使…

DDR3 SO-DIMM 内存条硬件总结(一)

最近在使用fpga读写DDR3&#xff0c;板子上的DDR3有两种形式与fpga相连&#xff0c;一种是直接用ddr3内存颗粒&#xff0c;另一种是通过内存条的形式与fpga相连。这里我们正好记录下和ddr3相关的知识&#xff0c;先从DDR3 SO-DIMM 内存条开始。 1.先看内存条的版本 从JEDEC下载…

Mysql练习题目【7月10日更新】

七、Mysql练习题目 https://zhuanlan.zhihu.com/p/38354000 1. 创建表 创建学生表 mysql> create table if not exists student(-> student_id varchar(255) not null,-> student_name varchar(255) not null,-> birthday date not null,-> gender varchar(…