go-zero开发入门之gateway深入研究1
创建一个 gateway 示例:
// main.go
package mainimport ("flag""fmt""gateway/middleware""github.com/zeromicro/go-zero/core/conf""github.com/zeromicro/go-zero/gateway"
)var configFile = flag.String("f", "etc/gateway.yaml", "the config file")func main() {var c gateway.GatewayConfflag.Parse()// 加载 gateway 配置,如果配置有问题记录 FATAL 日志后即退出conf.MustLoad(*configFile, &c)// 实例化 gateway,如果出错记录 FATAL 日志后即退出// 可能的出错包括:// 1)初始化日志 logx 失败(创建日志文件失败),日志文件含以下五种:// 信息级别的日志:infoLog// 错误级别的日志:errorLog// 严重级别的日志:severeLog// 慢查询日志:slowLog// 统计日志:statLog// 而堆栈日志 stackLog 同 errorLog 一起,访问日志 access 同 infoLog 。server := gateway.MustNewServer(c)defer server.Stop()fmt.Printf("Starting gateway at %s:%d...\n", c.Host, c.Port)server.Start()
}
// gateway/server.go
// MustNewServer creates a new gateway server.
func MustNewServer(c GatewayConf, opts ...Option) *Server {svr := &Server{upstreams: c.Upstreams,Server: rest.MustNewServer(c.RestConf),}for _, opt := range opts {opt(svr)}return svr
}// rest/server.go
// MustNewServer returns a server with given config of c and options defined in opts.
// Be aware that later RunOption might overwrite previous one that write the same option.
// The process will exit if error occurs.
func MustNewServer(c RestConf, opts ...RunOption) *Server {server, err := NewServer(c, opts...)if err != nil {logx.Must(err)}return server
}
gateway.MustNewServer 调用了 rest.MustNewServer,但在 rest.MustNewServer 增加了 upstreams 的初始化。upstreams 源自于 gateway.GatewayConf,对应的配置如下:。
Upstreams: # 网关上游的配置列表- Grpc: # 网关上游只能为 grpc 服务,不支持 http 等服务其它服务Etcd: # 服务发现用的 Etcd 配置Hosts: # Etcd 的服务地址列表- 127.0.0.1:2379Key: login.rpc # 服务注册在 Etcd 的 keyProtoSets: # 服务的 pb 文件列表(使用工具 protoc 根据 proto 生成 pb 文件:protoc --descriptor_set_out=login.pb login.proto)- proto/login.pbMappings: # Mappings can also be written in proto options 定义 http 路径到 rpc 路径的映射列表- Method: getPath: /v1/loginRpcPath: login.Login/login // 格式:包名.服务名/方法名
从上述内容可以看出,go-zero 的 gateway 在 rest 基础上增加了 upstreams 。当然不仅这一些,在 gateway 启动时也增加了特有的东西:
// Start starts the gateway server.
func (s *Server) Start() {logx.Must(s.build()) // 这也是 gateway 在 rest 基础上新增的s.Server.Start()
}
上述 s.build() 的源代码如下:
// gateway/server.go
func (s *Server) build() error {// 调用 s.ensureUpstreamNames() 确保所有上游服务(gRPC 服务)的名称都是唯一的,// 如果有重复的名称,函数返回错误。if err := s.ensureUpstreamNames(); err != nil {return err}// 使用 mr.MapReduceVoid 函数进行 MapReduce 操作,这个函数接收三个参数:// 1)一个用于生成数据源的函数// 2)一个 Map 函数// 3)一个 Reduce 函数return mr.MapReduceVoid(func(source chan<- Upstream) {// 生成数据源的函数:// 遍历 s.upstreams(上游服务列表),将每个上游服务发送到 Map 函数for _, up := range s.upstreams {source <- up}}, func(up Upstream, writer mr.Writer[rest.Route], cancel func(error)) { // Map 函数,对于每个上游服务,执行以下操作:var cli zrpc.Client// 创建一个 gRPC 客户端 cli,用于与上游服务通信if s.dialer != nil {cli = s.dialer(up.Grpc)} else {cli = zrpc.MustNewClient(up.Grpc)}// 调用 s.createDescriptorSource(cli, up) 创建一个描述符源 grpcurl.DescriptorSource),// 用于获取 gRPC 服务的元数据。source, err := s.createDescriptorSource(cli, up)if err != nil {cancel(fmt.Errorf("%s: %w", up.Name, err))return}// 使用 internal.GetMethods(source) 获取 gRPC 服务的所有方法methods, err := internal.GetMethods(source)if err != nil {cancel(fmt.Errorf("%s: %w", up.Name, err))return}// 创建一个 gRPCurl 解析器,用于解析 gRPC 方法的元数据resolver := grpcurl.AnyResolverFromDescriptorSource(source)// 遍历这些方法,为每个具有 HTTP 方法和路径的方法生成一个 HTTP 处理器(s.buildHandler(...)),// 并将它们映射到 RESTful API 的路由上。for _, m := range methods {if len(m.HttpMethod) > 0 && len(m.HttpPath) > 0 {writer.Write(rest.Route{Method: m.HttpMethod,Path: m.HttpPath,// http 调用转为 rpc 调用Handler: s.buildHandler(source, resolver, cli, m.RpcPath),})}}methodSet := make(map[string]struct{})for _, m := range methods {methodSet[m.RpcPath] = struct{}{}}// 遍历 up.Mappings(自定义的 RESTful API 映射),// 为每个映射生成一个 HTTP 处理器,并将生成的路由写入到 Reduce 函数。// 如果映射中指定的 gRPC 方法不存在,则返回错误。for _, m := range up.Mappings {// 在将方法映射到路由之前,函数会检查映射是否存在,如果不存在则返回错误if _, ok := methodSet[m.RpcPath]; !ok {cancel(fmt.Errorf("%s: rpc method %s not found", up.Name, m.RpcPath))return}writer.Write(rest.Route{Method: strings.ToUpper(m.Method),Path: m.Path,// 调用 buildHandler 函数来构建一个处理器,用于处理 RESTful API 请求Handler: s.buildHandler(source, resolver, cli, m.RpcPath),})}}, func(pipe <-chan rest.Route, cancel func(error)) {// Reduce 函数:// 从管道中读取生成的路由,并将它们添加到 HTTP 服务器(s.Server)中for route := range pipe {s.Server.AddRoute(route)}})
}
这个函数的主要目的是将 gRPC 服务的方法映射到 HTTP RESTful API,并将生成的 API 添加到 HTTP 服务器中。通过这种方式,可以在 gRPC 服务的基础上提供一个 RESTful API,使得客户端可以使用 HTTP 调用 gRPC 服务。
下为 mr.MapReduceVoid 的源代码:
// core/mr/mapreduce.go
// MapReduceVoid maps all elements generated from given generate,
// and reduce the output elements with given reducer.
func MapReduceVoid[T, U any](generate GenerateFunc[T], mapper MapperFunc[T, U], reducer VoidReducerFunc[U], opts ...Option) error {_, err := MapReduce(generate, mapper, func(input <-chan U, writer Writer[any], cancel func(error)) {reducer(input, cancel)}, opts...)if errors.Is(err, ErrReduceNoOutput) {return nil}return err
}// MapReduce maps all elements generated from given generate func,
// and reduces the output elements with given reducer.
func MapReduce[T, U, V any](generate GenerateFunc[T], mapper MapperFunc[T, U], reducer ReducerFunc[U, V], opts ...Option) (V, error) {panicChan := &onceChan{channel: make(chan any)}source := buildSource(generate, panicChan)return mapReduceWithPanicChan(source, panicChan, mapper, reducer, opts...)
}
// gateway/server.go
func (s *Server) buildHandler(source grpcurl.DescriptorSource, resolver jsonpb.AnyResolver,cli zrpc.Client, rpcPath string) func(http.ResponseWriter, *http.Request) {return func(w http.ResponseWriter, r *http.Request) {parser, err := internal.NewRequestParser(r, resolver)if err != nil {httpx.ErrorCtx(r.Context(), w, err)return}w.Header().Set(httpx.ContentType, httpx.JsonContentType)handler := internal.NewEventHandler(w, resolver)// http 调用转成了 grpc 调用if err := grpcurl.InvokeRPC(r.Context(), source, cli.Conn(), rpcPath, s.prepareMetadata(r.Header),handler, parser.Next); err != nil {httpx.ErrorCtx(r.Context(), w, err)}st := handler.Statusif st.Code() != codes.OK {httpx.ErrorCtx(r.Context(), w, st.Err())}}
}
http 调用转 grpc 调用过程复杂,最终调用了 grpc-go 的 Invoke:
// https://github.com/grpc/grpc-go/blob/master/clientconn.go
// ClientConnInterface defines the functions clients need to perform unary and
// streaming RPCs. It is implemented by *ClientConn, and is only intended to
// be referenced by generated code.
type ClientConnInterface interface { // ClientConn 实现了该接口,实现落在两个文件中:clientconn.go 和 call.go// Invoke performs a unary RPC and returns after the response is received// into reply.Invoke(ctx context.Context, method string, args any, reply any, opts ...CallOption) error// NewStream begins a streaming RPC.NewStream(ctx context.Context, desc *StreamDesc, method string, opts ...CallOption) (ClientStream, error)
}
中间还用到了 grpcdynamic 的 Stub.InvokeRpc:
// https://github.com/jhump/protoreflect/blob/main/dynamic/grpcdynamic/stub.go
// InvokeRpc sends a unary RPC and returns the response. Use this for unary methods.
func (s Stub) InvokeRpc(ctx context.Context, method *desc.MethodDescriptor, request proto.Message, opts ...grpc.CallOption) (proto.Message, error) {if method.IsClientStreaming() || method.IsServerStreaming() {return nil, fmt.Errorf("InvokeRpc is for unary methods; %q is %s", method.GetFullyQualifiedName(), methodType(method))}if err := checkMessageType(method.GetInputType(), request); err != nil {return nil, err}resp := s.mf.NewMessage(method.GetOutputType())if err := s.channel.Invoke(ctx, requestMethod(method), request, resp, opts...); err != nil {return nil, err}return resp, nil
}
// https://github.com/grpc/grpc-go/blob/master/call.go
package grpcimport ("context"
)// Invoke sends the RPC request on the wire and returns after response is
// received. This is typically called by generated code.
//
// All errors returned by Invoke are compatible with the status package.
func (cc *ClientConn) Invoke(ctx context.Context, method string, args, reply any, opts ...CallOption) error {// allow interceptor to see all applicable call options, which means those// configured as defaults from dial option as well as per-call optionsopts = combine(cc.dopts.callOptions, opts)if cc.dopts.unaryInt != nil {return cc.dopts.unaryInt(ctx, method, args, reply, cc, invoke, opts...)}return invoke(ctx, method, args, reply, cc, opts...)
}func invoke(ctx context.Context, method string, req, reply any, cc *ClientConn, opts ...CallOption) error {// cs 类型为,// 结构体 clientStream 实现了接口 ClientStreamcs, err := newClientStream(ctx, unaryStreamDesc, cc, method, opts...)if err != nil {return err}if err := cs.SendMsg(req); err != nil { // 发送请求return err}return cs.RecvMsg(reply) // 接收响应
}// ClientStream defines the client-side behavior of a streaming RPC.
//
// All errors returned from ClientStream methods are compatible with the
// status package.
type ClientStream interface {// Header returns the header metadata received from the server if there// is any. It blocks if the metadata is not ready to read. If the metadata// is nil and the error is also nil, then the stream was terminated without// headers, and the status can be discovered by calling RecvMsg.Header() (metadata.MD, error)// Trailer returns the trailer metadata from the server, if there is any.// It must only be called after stream.CloseAndRecv has returned, or// stream.Recv has returned a non-nil error (including io.EOF).Trailer() metadata.MD// CloseSend closes the send direction of the stream. It closes the stream// when non-nil error is met. It is also not safe to call CloseSend// concurrently with SendMsg.CloseSend() error// Context returns the context for this stream.//// It should not be called until after Header or RecvMsg has returned. Once// called, subsequent client-side retries are disabled.Context() context.Context// SendMsg is generally called by generated code. On error, SendMsg aborts// the stream. If the error was generated by the client, the status is// returned directly; otherwise, io.EOF is returned and the status of// the stream may be discovered using RecvMsg.//// SendMsg blocks until:// - There is sufficient flow control to schedule m with the transport, or// - The stream is done, or// - The stream breaks.//// SendMsg does not wait until the message is received by the server. An// untimely stream closure may result in lost messages. To ensure delivery,// users should ensure the RPC completed successfully using RecvMsg.//// It is safe to have a goroutine calling SendMsg and another goroutine// calling RecvMsg on the same stream at the same time, but it is not safe// to call SendMsg on the same stream in different goroutines. It is also// not safe to call CloseSend concurrently with SendMsg.//// It is not safe to modify the message after calling SendMsg. Tracing// libraries and stats handlers may use the message lazily.SendMsg(m any) error// RecvMsg blocks until it receives a message into m or the stream is// done. It returns io.EOF when the stream completes successfully. On// any other error, the stream is aborted and the error contains the RPC// status.//// It is safe to have a goroutine calling SendMsg and another goroutine// calling RecvMsg on the same stream at the same time, but it is not// safe to call RecvMsg on the same stream in different goroutines.RecvMsg(m any) error
}
调用路径归纳总结:
grpcurl/grpcurl.InvokeRPC()/invoke.go
-> grpcdynamic/Stub.InvokeRpc()/stub.go
-> grpc-go/grpc.ClientConn.Invoke()/clientconn.go|call.go // ClientConn 是一个 struct,实现了接口 ClientConnInterface
-> grpc-go/grpc.invoke()/call.go // invoke 是 grpc 下的全局私有函数
-> grpc-go/grpc.clientStream::SendMsg()/stream.go // clientStream 是一个 struct,实现了接口 ClientStream
-> grpc-go/grpc.csAttempt::SendMsg()/stream.go // csAttempt 是一个 struct,实现了接口 ClientTransport
-> grpc-go/grpc.ClientTransport::write()/internal/transport/transport.go // ClientTransport 是一个接口,结构体 http2Client 实现了 ClientTransport
-> grpc-go/grpc.http2Client::Write()/internal/transport/http2_client.go // 结构体 http2Client 实现了 ClientTransport,将数据写入 http2Client.controlBuf 中
http2Client::Write 将数据写入 http2Client.controlBuf 后返回,数据的发送由另外的协程 loopyWriter.run() 负责:
// https://github.com/grpc/grpc-go/blob/master/internal/transport/controlbuf.go
//
// run should be run in a separate goroutine.
// It reads control frames from controlBuf and processes them by:
// 1. Updating loopy's internal state, or/and
// 2. Writing out HTTP2 frames on the wire.
//
// Loopy keeps all active streams with data to send in a linked-list.
// All streams in the activeStreams linked-list must have both:
// 1. Data to send, and
// 2. Stream level flow control quota available.
//
// In each iteration of run loop, other than processing the incoming control
// frame, loopy calls processData, which processes one node from the
// activeStreams linked-list. This results in writing of HTTP2 frames into an
// underlying write buffer. When there's no more control frames to read from
// controlBuf, loopy flushes the write buffer. As an optimization, to increase
// the batch size for each flush, loopy yields the processor, once if the batch
// size is too low to give stream goroutines a chance to fill it up.
//
// Upon exiting, if the error causing the exit is not an I/O error, run()
// flushes and closes the underlying connection. Otherwise, the connection is
// left open to allow the I/O error to be encountered by the reader instead.
func (l *loopyWriter) run() (err error) {defer func() {if l.logger.V(logLevel) {l.logger.Infof("loopyWriter exiting with error: %v", err)}if !isIOError(err) {l.framer.writer.Flush()l.conn.Close()}l.cbuf.finish()}()for {it, err := l.cbuf.get(true)if err != nil {return err}if err = l.handle(it); err != nil {return err}if _, err = l.processData(); err != nil {return err}gosched := truehasdata:for {it, err := l.cbuf.get(false)if err != nil {return err}if it != nil {if err = l.handle(it); err != nil {return err}if _, err = l.processData(); err != nil {return err}continue hasdata}isEmpty, err := l.processData() // 最底层调用了 Go 标准库的 io.Writer::Write(),Writer 是一个接口if err != nil {return err}if !isEmpty {continue hasdata}if gosched {gosched = falseif l.framer.writer.offset < minBatchSize {runtime.Gosched()continue hasdata}}l.framer.writer.Flush()break hasdata}}
}
相关文章:

go-zero开发入门之gateway深入研究1
创建一个 gateway 示例: // main.go package mainimport ("flag""fmt""gateway/middleware""github.com/zeromicro/go-zero/core/conf""github.com/zeromicro/go-zero/gateway" )var configFile flag.String(&…...

【每日一题】反转二叉树的奇数层
文章目录 Tag题目来源题目解读解题思路方法一:广度优先搜索方法二:深度优先搜索 写在最后 Tag 【深度优先搜索】【广度优先搜索】【二叉树】【2023-12-15】 题目来源 2415. 反转二叉树的奇数层 题目解读 反转二叉树奇数层的节点。 解题思路 对于二叉…...

vue 项目配置反向代理导致项目白屏
问题:vue 项目配置反向代理导致项目白屏 一、现象描述 添加反向代理代码后,前端运行白屏 // 设置baseURL,8888是后端端口号,前端请求默认发送到baseURL的地址 var axios require(axios) axios.defaults.baseURL http://local…...

全国县级行政区点位数据,Shp+excel格式
基本信息. 数据名称: 县级行政区点位 数据格式: Shpexcel 数据时间: 2021年 数据几何类型: 点 数据坐标系: WGS84坐标系 数据来源:网络公开数据 数据字段: 序号字段名称字段说明1xzqhdm_1省代码2xzqhmc_1省名称3xzqhdm_2市代码4xzqhmc_2市代…...

文件包含的提升刷题
上一篇文章:一篇文章带你入门文件包含-CSDN博客 已经开始入门了文件包含,那现在开始拔高提升刷题! 1. 拿到题目后啥也没有,所以也不知道要读取啥文件,那就查看源代码。 直接看if的条件就可以知道一定要设置cookie&a…...

入门级银行测试岗位招聘,只需具备这些基本条件!
2023年应该说是超乎意外的寒冷,几乎算是百业凋零。充斥在各个地方各个行业的,更多的是裁员的消息,很少有以往的风风火火的招聘了。无论是金九银十还是在以往的淡季。 谁也不知道这样一个特殊的寒冬还有多久才能过去。但是无论面对什么样的局…...

组里新来了个00后,真卷不过....
📢专注于分享软件测试干货内容,欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正!📢交流讨论:欢迎加入我们一起学习!📢资源分享:耗时200小时精选的「软件测试」资…...

python 命令添加参数
官网 argparse模块可以很容易地编写用户友好的命令行界面。程序定义它需要什么参数,argparse将找出如何从sys.argv中解析这些参数。argparse模块还会自动生成帮助和用法消息。当用户为程序提供无效参数时,该模块也会发出错误。 核心功能 argparse模块对…...

LVS负载均衡器(DR模式)+nginx七层代理+tomcat多实例+php+mysql 实现负载均衡以及动静分离、数据库的调用!!!
目录 前言 一、nfs共享存储,为两个节点服务器提供静态网页共享 二、nginx作为lvs的后端节点服务器,完成lo:0网卡配置,以及内核参数设置,还有设置路由表 步骤一:先完成nfs共享存储挂载 步骤二:完成lo:0网…...

jmx_exporter安装
下载 wget https://repo1.maven.org/maven2/io/prometheus/jmx/jmx_prometheus_javaagent/0.13.0/jmx_prometheus_javaagent-0.13.0.jar 创建jmx_exporter.yml文件 文件内容为: rules: - pattern: ".*" 配置tomcatpinter/apache-tomcat-8.5.38/bin/ca…...

怎么给自己的微信公众号留言?
为什么公众号没有留言功能?根据要求,自2018年2月12日起,新申请的微信公众号默认无留言功能。有些人听过一个说法:公众号粉丝累计到一定程度或者原创文章数量累计到一定程度就可以开通留言功能。其实这个方法是2018年之前才可以&am…...

Unity中 URP 下的棋盘格Shader
文章目录 前言一、制作思路法1:使用纹理采样后,修改重铺效果法2:计算实现 二、粗略计算实现棋盘格效果1、使 uv.x < 0.5 区域 0 。反之, 0.52、使 uv.y < 0.5 区域 0 。反之, 0.53、使两个颜色相加4、取小数…...

杰发科技AC7840——SPM电源管理之低功耗模式
0、SPM简介 很早以前就听过低功耗模式,一直没有怎么深入了解,最近遇到几个项目都是跟低功耗有关。正好AutoChips的芯片都有电源管理的功能,在此借用AC7840的SPM对低功耗进行测试。 1、AC7840的5种功耗模式 2、AC7840的模式转换 3、唤醒 在…...

PCL 点云匹配 之NICP(Normal ICP)
一、概述 上面一篇中我们已经得出了一个结论,就是ICP虽然简单,但是也有明显的缺点 1、计算速度慢,收敛慢,迭代次数多 2、对内存的开销比较大 3、很容易陷入局部最优的困局 因此我们在经典ICP的基础上添加一两个约束: 第…...

华脉智联融合通信一张图
随着通信技术、信息技术以及互联网的发展,融合通信技术也日益发展成熟。融合通信系统作为常见的通信指挥调度系统,其发挥的功能也越来越强大,在不同行业中的应用也越来越丰富。 华脉智联深耕融合通信行业多年,自主研发的融合通信…...

Flink系列之:窗口Top-N
Flink系列之:窗口Top-N 一、窗口Top-N二、示例:在窗口聚合后进行窗口 Top-N三、在窗口表值函数后进行窗口 Top-N四、限制 一、窗口Top-N 适用于流、批一体窗口 Top-N 是特殊的 Top-N,它返回每个分区键的每个窗口的N个最小或最大值。与普通To…...

【k8s】--insecure-registry详解 ( 访问仓库、https、http)
文章目录 一、--insecure-registry是什么二、如何使用--insecure-registry三、--insecure-registry的安全风险四、--insecure-registry的替代方案五、总结参考 一、–insecure-registry是什么 --insecure-registry是docker中用来设置与docker registry通信的安全限制的一个参数…...

ElementUI,修改el-cascader的默认样式
Element UI 中的下拉弹窗是通过在整个body标签末尾动态添加div实现的,所以修改样式时,必须要定义全局样式才能实现样式覆盖,那怎样才能避免全局的样式污染呢? 解决办法:通过给组件添加自定义的 popper-class 属性来避…...

外卖系统海外版:代码与美食的完美交融
在数字化时代,外卖系统海外版正引领着全球美食点餐的新潮流。不仅为用户提供了便捷的用餐服务,更通过技术创新为美食与代码之间搭建了一座桥梁。本文将探讨其中的一些技术应用,并呈现与美食完美交融的全新体验。 多语言支持代码示例 def m…...

Java代码解析:初学者的编程入门指南
💂 个人网站:【 海拥】【神级代码资源网站】【办公神器】🤟 基于Web端打造的:👉轻量化工具创作平台💅 想寻找共同学习交流的小伙伴,请点击【全栈技术交流群】 Java作为一门强大而广泛应用的编程语言&#x…...

数据结构--图
树具有灵活性,并且存在许多不同的树的应用,但是就树本身而言有一定的局限性,树只能表示层次关系,比如父子关系。而其他的比如兄弟关系只能够间接表示。 推广--- 图 图形结构中,数据元素之间的关系是任意的。 一、图…...

AXure的情景交互
目录 导语: 1.erp多样性登录界面 2.主页跳转 3.省级联动编辑 4. 下拉加载 导语: Axure是一种流行的原型设计工具,可以用来创建网站和应用程序的交互原型。通过Axure,设计师可以创建情景交互,以展示用户与系统的交…...

数据库操作习题12.12
考虑如下的人员数据,其中加下划线的是主码,数据库模式由四个关系组成: employee (empname, street, city) works (empname, compname, salary) company(id, compname, city) managers (empname, mgrname) 其中 关系 employee 给出人员的基本信息,包括人员…...

Redis之INCR命令,通常用于统计网站访问量,文章访问量,分布式锁
前言 Redis的INCR命令用于将键的值增加1。如果键不存在,则会先将键的值设置为0,然后再执行INCR操作。INCR命令的作用是对计数器进行自增操作,可以用于实现多种场景,比如统计网站访问量、文章访问量、分布式锁等。 一、Redis键之…...

window运行celery报错
报错信息 Traceback (most recent call last): File "c:\program files\python36\lib\site-packages\billiard\pool.py", line 359, in workloop result (True, prepare_result(fun(*args, **kwargs))) File "c:\program files\python36\lib\site-packages\ce…...

玩转Docker(五):网络
文章目录 〇、关于linux系统网络一、none网络二、host网络三、bridge网络一个问题:为什么在主机上仍可以通过localhost:port访问到容器中的服务? 四、user-defined网络 Docker安装时会自动在host上创建三个网络,我们可用docker network ls命令…...

选择合适教育管理软件:必须考虑的10个关键问题
随着教育行业的迅速数字化,学校要能够提供最新的管理和教育方法。大家逐渐意识到技术让运营变得更容易、更有效率。 不过首先我们需要找到一个能满足需求的应用程序。面对众多的选择,你该如何选择一个合适的平台呢?当然,没有人想…...

前端不同架构的分层设计
1. 架构设计分层: (1). 系统架构: ①. 应用场景:a. 应用在整个系统内,如与后台服务如何通信,与第三方系统如何集成.②. 前提条件:a. 了解前端系统与其它系统间的关系,包括业务关系和协作机制.b. 了解后端系统,需要规定与后台数据传递的机制,包括:(1). api设计规范(2). 访问授…...

android系统镜像文件
boot.img:这是包含内核和设备树(Device Tree)的镜像文件。它被引导加载程序(bootloader)加载以启动系统,并负责将控制权转交给内核。 dtbo.img:这是设备树增量编译(Device Tree Ove…...

相位的重要性
在过去的几年中,相干信号和图像处理尖端技术的开发和应用有了显著的增长。相干处理的特点是使用一个称为相位的单一量[1]。相比之下,非相干处理只利用信号幅度或强度。需要进行相干处理的例子包括合成孔径雷达(SAR)、合成孔径声纳…...