当前位置: 首页 > news >正文

【go语言grpc之client端源码分析三】

go语言grpc之server端源码分析三

  • newClientStream
    • newAttemptLocked

上一篇在介绍了grpc.Dial之后,然后再介绍一下后面的

    //创建RPC客户端client := pb.NewGreetsClient(conn)//设置超时时间_, cancel := context.WithTimeout(context.Background(), time.Second)defer cancel()reply, err := client.SayHello(context.Background(), &pb.HelloRequest{Name: "小超", Message: "回来吃饭吗"})if err != nil {log.Fatalf("couldn not greet: %v", err)return}log.Println(reply.Name, reply.Message)

然后看一下pb.NewGreetsClient还有SayHello的方法。

func NewGreetsClient(cc grpc.ClientConnInterface) GreetsClient {return &greetsClient{cc}
}func (c *greetsClient) SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) {out := new(HelloReply)err := c.cc.Invoke(ctx, "/proto.Greets/SayHello", in, out, opts...)if err != nil {return nil, err}return out, nil
}

可以看出来核心就是调用ClientConn的Invoke方法。
来看一下具体的实现

func Invoke(ctx context.Context, method string, args, reply interface{}, cc *ClientConn, opts ...CallOption) error {return cc.Invoke(ctx, method, args, reply, opts...)
}var unaryStreamDesc = &StreamDesc{ServerStreams: false, ClientStreams: false}func invoke(ctx context.Context, method string, req, reply interface{}, cc *ClientConn, opts ...CallOption) error {cs, 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)
}

所以这里就是上面的三个方法,

newClientStream


func newClientStream(ctx context.Context, desc *StreamDesc, cc *ClientConn, method string, opts ...CallOption) (_ ClientStream, err error) {if channelz.IsOn() {cc.incrCallsStarted()defer func() {if err != nil {cc.incrCallsFailed()}}()}// Provide an opportunity for the first RPC to see the first service config// provided by the resolver.if err := cc.waitForResolvedAddrs(ctx); err != nil {return nil, err}var mc serviceconfig.MethodConfigvar onCommit func()var newStream = func(ctx context.Context, done func()) (iresolver.ClientStream, error) {return newClientStreamWithParams(ctx, desc, cc, method, mc, onCommit, done, opts...)}rpcInfo := iresolver.RPCInfo{Context: ctx, Method: method}rpcConfig, err := cc.safeConfigSelector.SelectConfig(rpcInfo)if err != nil {return nil, toRPCErr(err)}return newStream(ctx, func() {})
}

可以看出来这个方法就是newClientStreamWithParams。

func newClientStreamWithParams(ctx context.Context, desc *StreamDesc, cc *ClientConn, method string, mc serviceconfig.MethodConfig, onCommit, doneFunc func(), opts ...CallOption) (_ iresolver.ClientStream, err error) {c := defaultCallInfo()if mc.WaitForReady != nil {c.failFast = !*mc.WaitForReady}// Possible context leak:// The cancel function for the child context we create will only be called// when RecvMsg returns a non-nil error, if the ClientConn is closed, or if// an error is generated by SendMsg.// https://github.com/grpc/grpc-go/issues/1818.var cancel context.CancelFuncif mc.Timeout != nil && *mc.Timeout >= 0 {ctx, cancel = context.WithTimeout(ctx, *mc.Timeout)} else {ctx, cancel = context.WithCancel(ctx)}defer func() {if err != nil {cancel()}}()for _, o := range opts {if err := o.before(c); err != nil {return nil, toRPCErr(err)}}c.maxSendMessageSize = getMaxSize(mc.MaxReqSize, c.maxSendMessageSize, defaultClientMaxSendMessageSize)c.maxReceiveMessageSize = getMaxSize(mc.MaxRespSize, c.maxReceiveMessageSize, defaultClientMaxReceiveMessageSize)if err := setCallInfoCodec(c); err != nil {return nil, err}callHdr := &transport.CallHdr{Host:           cc.authority,Method:         method,ContentSubtype: c.contentSubtype,DoneFunc:       doneFunc,}// Set our outgoing compression according to the UseCompressor CallOption, if// set.  In that case, also find the compressor from the encoding package.// Otherwise, use the compressor configured by the WithCompressor DialOption,// if set.var cp Compressorvar comp encoding.Compressorif ct := c.compressorType; ct != "" {callHdr.SendCompress = ctif ct != encoding.Identity {comp = encoding.GetCompressor(ct)if comp == nil {return nil, status.Errorf(codes.Internal, "grpc: Compressor is not installed for requested grpc-encoding %q", ct)}}} else if cc.dopts.cp != nil {callHdr.SendCompress = cc.dopts.cp.Type()cp = cc.dopts.cp}if c.creds != nil {callHdr.Creds = c.creds}cs := &clientStream{callHdr:      callHdr,ctx:          ctx,methodConfig: &mc,opts:         opts,callInfo:     c,cc:           cc,desc:         desc,codec:        c.codec,cp:           cp,comp:         comp,cancel:       cancel,firstAttempt: true,onCommit:     onCommit,}if !cc.dopts.disableRetry {cs.retryThrottler = cc.retryThrottler.Load().(*retryThrottler)}cs.binlog = binarylog.GetMethodLogger(method)if err := cs.newAttemptLocked(false /* isTransparent */); err != nil {cs.finish(err)return nil, err}op := func(a *csAttempt) error { return a.newStream() }if err := cs.withRetry(op, func() { cs.bufferForRetryLocked(0, op) }); err != nil {cs.finish(err)return nil, err}if cs.binlog != nil {md, _ := metadata.FromOutgoingContext(ctx)logEntry := &binarylog.ClientHeader{OnClientSide: true,Header:       md,MethodName:   method,Authority:    cs.cc.authority,}if deadline, ok := ctx.Deadline(); ok {logEntry.Timeout = time.Until(deadline)if logEntry.Timeout < 0 {logEntry.Timeout = 0}}cs.binlog.Log(logEntry)}if desc != unaryStreamDesc {// Listen on cc and stream contexts to cleanup when the user closes the// ClientConn or cancels the stream context.  In all other cases, an error// should already be injected into the recv buffer by the transport, which// the client will eventually receive, and then we will cancel the stream's// context in clientStream.finish.go func() {select {case <-cc.ctx.Done():cs.finish(ErrClientConnClosing)case <-ctx.Done():cs.finish(toRPCErr(ctx.Err()))}}()}return cs, nil
}

可以看出来这里是初始化了clientStream这个结构体,然后是调用了
newAttemptLocked方法

newAttemptLocked

// newAttemptLocked creates a new attempt with a transport.
// If it succeeds, then it replaces clientStream's attempt with this new attempt.
func (cs *clientStream) newAttemptLocked(isTransparent bool) (retErr error) {ctx := newContextWithRPCInfo(cs.ctx, cs.callInfo.failFast, cs.callInfo.codec, cs.cp, cs.comp)method := cs.callHdr.Methodsh := cs.cc.dopts.copts.StatsHandlervar beginTime time.Timeif sh != nil {ctx = sh.TagRPC(ctx, &stats.RPCTagInfo{FullMethodName: method, FailFast: cs.callInfo.failFast})beginTime = time.Now()begin := &stats.Begin{Client:                    true,BeginTime:                 beginTime,FailFast:                  cs.callInfo.failFast,IsClientStream:            cs.desc.ClientStreams,IsServerStream:            cs.desc.ServerStreams,IsTransparentRetryAttempt: isTransparent,}sh.HandleRPC(ctx, begin)}var trInfo *traceInfoif EnableTracing {trInfo = &traceInfo{tr: trace.New("grpc.Sent."+methodFamily(method), method),firstLine: firstLine{client: true,},}if deadline, ok := ctx.Deadline(); ok {trInfo.firstLine.deadline = time.Until(deadline)}trInfo.tr.LazyLog(&trInfo.firstLine, false)ctx = trace.NewContext(ctx, trInfo.tr)}newAttempt := &csAttempt{ctx:          ctx,beginTime:    beginTime,cs:           cs,dc:           cs.cc.dopts.dc,statsHandler: sh,trInfo:       trInfo,}defer func() {if retErr != nil {// This attempt is not set in the clientStream, so it's finish won't// be called. Call it here for stats and trace in case they are not// nil.newAttempt.finish(retErr)}}()if err := ctx.Err(); err != nil {return toRPCErr(err)}if cs.cc.parsedTarget.Scheme == "xds" {// Add extra metadata (metadata that will be added by transport) to context// so the balancer can see them.ctx = grpcutil.WithExtraMetadata(ctx, metadata.Pairs("content-type", grpcutil.ContentType(cs.callHdr.ContentSubtype),))}t, done, err := cs.cc.getTransport(ctx, cs.callInfo.failFast, cs.callHdr.Method)if err != nil {return err}if trInfo != nil {trInfo.firstLine.SetRemoteAddr(t.RemoteAddr())}newAttempt.t = tnewAttempt.done = donecs.attempt = newAttemptreturn nil
}

看一下这里的getTransport方法。

func (cc *ClientConn) getTransport(ctx context.Context, failfast bool, method string) (transport.ClientTransport, func(balancer.DoneInfo), error) {t, done, err := cc.blockingpicker.pick(ctx, failfast, balancer.PickInfo{Ctx:            ctx,FullMethodName: method,})if err != nil {return nil, nil, toRPCErr(err)}return t, done, nil
}

注意一下这里的cc.blockingpicker.pick。是不是很熟悉,其实就是前面的。
然后看一下pick这个方法

// pick returns the transport that will be used for the RPC.
// It may block in the following cases:
// - there's no picker
// - the current picker returns ErrNoSubConnAvailable
// - the current picker returns other errors and failfast is false.
// - the subConn returned by the current picker is not READY
// When one of these situations happens, pick blocks until the picker gets updated.
func (pw *pickerWrapper) pick(ctx context.Context, failfast bool, info balancer.PickInfo) (transport.ClientTransport, func(balancer.DoneInfo), error) {var ch chan struct{}var lastPickErr errorfor {pw.mu.Lock()if pw.done {pw.mu.Unlock()return nil, nil, ErrClientConnClosing}if pw.picker == nil {ch = pw.blockingCh}//fmt.Printf(" pw.picker:%v nil:%v ch == pw.blockingCh:%v\n", pw.picker, pw.picker == nil, ch == pw.blockingCh)if ch == pw.blockingCh {// This could happen when either:// - pw.picker is nil (the previous if condition), or// - has called pick on the current picker.pw.mu.Unlock()select {case <-ctx.Done():var errStr stringif lastPickErr != nil {errStr = "latest balancer error: " + lastPickErr.Error()} else {errStr = ctx.Err().Error()}switch ctx.Err() {case context.DeadlineExceeded:return nil, nil, status.Error(codes.DeadlineExceeded, errStr)case context.Canceled:return nil, nil, status.Error(codes.Canceled, errStr)}case <-ch:}continue}ch = pw.blockingChp := pw.pickerpw.mu.Unlock()pickResult, err := p.Pick(info)if err != nil {if err == balancer.ErrNoSubConnAvailable {continue}if _, ok := status.FromError(err); ok {// Status error: end the RPC unconditionally with this status.return nil, nil, err}// For all other errors, wait for ready RPCs should block and other// RPCs should fail with unavailable.if !failfast {lastPickErr = errcontinue}return nil, nil, status.Error(codes.Unavailable, err.Error())}acw, ok := pickResult.SubConn.(*acBalancerWrapper)if !ok {logger.Errorf("subconn returned from pick is type %T, not *acBalancerWrapper", pickResult.SubConn)continue}if t := acw.getAddrConn().getReadyTransport(); t != nil {if channelz.IsOn() {return t, doneChannelzWrapper(acw, pickResult.Done), nil}return t, pickResult.Done, nil}if pickResult.Done != nil {// Calling done with nil error, no bytes sent and no bytes received.// DoneInfo with default value works.pickResult.Done(balancer.DoneInfo{})}logger.Infof("blockingPicker: the picked transport is not ready, loop back to repick")// If ok == false, ac.state is not READY.// A valid picker always returns READY subConn. This means the state of ac// just changed, and picker will be updated shortly.// continue back to the beginning of the for loop to repick.}
}

注意这里的p.Pick,这个就是在pickfirst中进行更新后调用的,如下

func (b *pickfirstBalancer) UpdateSubConnState(sc balancer.SubConn, s balancer.SubConnState) {if logger.V(2) {logger.Infof("pickfirstBalancer: UpdateSubConnState: %p, %v", sc, s)}if b.sc != sc {if logger.V(2) {logger.Infof("pickfirstBalancer: ignored state change because sc is not recognized")}return}b.state = s.ConnectivityStateif s.ConnectivityState == connectivity.Shutdown {b.sc = nilreturn}switch s.ConnectivityState {case connectivity.Ready:b.cc.UpdateState(balancer.State{ConnectivityState: s.ConnectivityState, Picker: &picker{result: balancer.PickResult{SubConn: sc}}})case connectivity.Connecting:b.cc.UpdateState(balancer.State{ConnectivityState: s.ConnectivityState, Picker: &picker{err: balancer.ErrNoSubConnAvailable}})case connectivity.Idle:b.cc.UpdateState(balancer.State{ConnectivityState: s.ConnectivityState, Picker: &idlePicker{sc: sc}})case connectivity.TransientFailure:b.cc.UpdateState(balancer.State{ConnectivityState: s.ConnectivityState,Picker:            &picker{err: s.ConnectionError},})}
}

如果成功了就是返回ready下面的balancer.PickResult。返回了SubConn,然后失败了就是返回了balancer.PickResult但是其中的err是错误的,这样在cc.blockingpicker.pick的时候,就可以返回具体的成功或者失败。
这样完成了整个逻辑的闭环。
下面的sendMsg和ReckMsg和之前的没有什么区别,就是在http2的基础上加上hpack头部压缩和proto的序列化,就不进行赘述了。

相关文章:

【go语言grpc之client端源码分析三】

go语言grpc之server端源码分析三newClientStreamnewAttemptLocked上一篇在介绍了grpc.Dial之后&#xff0c;然后再介绍一下后面的 //创建RPC客户端client : pb.NewGreetsClient(conn)//设置超时时间_, cancel : context.WithTimeout(context.Background(), time.Second)defer c…...

Android 基础知识4-2.6LinearLayout(线性布局)

一、LinearLayout的概述 线性布局&#xff08;LinearLayout&#xff09;主要以水平或垂直方式来排列界面中的控件。并将控件排列到一条直线上。在线性布局中&#xff0c;如果水平排列&#xff0c;垂直方向上只能放一个控件&#xff0c;如果垂直排列&#xff0c;水平方向上也只能…...

补充前端面试题(三)

图片懒加载<!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content"widthdevice-width, in…...

.net开发安卓入门-自动升级(配合.net6 webapi 作为服务端)

文章目录思路客户端权限清单&#xff08;AndroidManifest.xml&#xff09;权限列表(完整内容看 权限清单&#xff08;AndroidManifest.xml&#xff09;&#xff09;打开外部应用的权限(完整内容看 权限清单&#xff08;AndroidManifest.xml&#xff09;&#xff09;添加文件如下…...

分享111个HTML艺术时尚模板,总有一款适合您

分享111个HTML艺术时尚模板&#xff0c;总有一款适合您 111个HTML艺术时尚模板下载链接&#xff1a;https://pan.baidu.com/s/1sYo2IPma4rzeku3yCG7jGw?pwdk8dx 提取码&#xff1a;k8dx Python采集代码下载链接&#xff1a;采集代码.zip - 蓝奏云 时尚理发沙龙服务网站模…...

spring之Spring AOP基于注解

文章目录前言一、Spring AOP基于注解的所有通知类型1、前置通知2、后置通知3、环绕通知4、最终通知5、异常通知二、Spring AOP基于注解之切面顺序三、Spring AOP基于注解之通用切点三、Spring AOP基于注解之连接点四、Spring AOP基于注解之全注解开发前言 通知类型包括&#x…...

LeetCode题目笔记——6362. 合并两个二维数组 - 求和法

文章目录题目描述题目链接题目难度——简单方法一&#xff1a;常规双指针遍历代码/Python方法二&#xff1a;字典\哈希表代码/Python总结题目描述 给你两个 二维 整数数组 nums1 和 nums2. nums1[i] [idi, vali] 表示编号为 idi 的数字对应的值等于 vali 。nums2[i] [idi, …...

【C#基础】C# 常用语句讲解

序号系列文章3【C#基础】C# 数据类型总结4【C#基础】C# 变量和常量的使用5【C#基础】C# 运算符总结文章目录前言语句概念1&#xff0c;迭代语句1.1 for 语句1.2 foreach 语句1.3 while 语句1.4 do 语句2&#xff0c;选择语句2.1&#xff0c;if 语句2.2&#xff0c;else 语句2.3…...

腾讯云——负载均衡CLB

负载均衡 CLB 提供四层&#xff08;TCP 协议/UDP 协议/TCP SSL 协议&#xff09;和七层&#xff08;HTTP 协议/HTTPS 协议&#xff09;负载均衡。您可以通过 CLB 将业务流量分发到多个后端服务器上&#xff0c;消除单点故障并保障业务可用性。CLB 自身采用集群部署&#xff0c;…...

6.关于系统服务的思考—— native vs java

文章目录native服务 以sensor service为例Native 服务java 服务&#xff0c; 以vibrate为例java 服务 以一个demo为例native服务 以sensor service为例 service启动 SystemServer.startBootstrapServices---->>>mSystemServiceManager.startService—>>>Sen…...

SQL语句创建视图:

前言 &#x1f388;个人主页:&#x1f388; :✨✨✨初阶牛✨✨✨ &#x1f43b;推荐专栏: &#x1f354;&#x1f35f;&#x1f32f; c语言初阶 &#x1f511;个人信条: &#x1f335;知行合一 &#x1f349;本篇简介:>:介绍数据库中有关视图的知识,参考学校作业. 金句分享:…...

使用BP神经网络和Elman Net预测航班价格(Matlab代码实现)

&#x1f468;‍&#x1f393;个人主页&#xff1a;研学社的博客&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5;&#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密…...

JavaWeb9-volatile解决内存可见性和指令重排序问题

目录 1.解决内存可见性问题 2.解决指令重排序问题 3.volatile缺点 4.特使使用场景 volatile&#xff08;易变的&#xff0c;易挥发的&#xff0c;不稳定的&#xff09;可以解决内存可见性和指令重排序的问题。 1.解决内存可见性问题 代码在写入 volatile 修饰的变量时&am…...

Docker - 镜像操作命令

镜像名称一般分为两部分组成:[repository]:[tag]在没有指定tag时&#xff0c;默认是latest&#xff0c;代表最新版本的镜像1.下载docker镜像 docker pull repository:tag2.查看本地所有镜像 docker images3.创建镜像别名 docker tag repository:tag repository111:tag4.查看镜像…...

全栈之路-前端篇 | 第三讲.基础前置知识【前端标准与研发工具】学习笔记

欢迎关注「全栈工程师修炼指南」公众号 点击 &#x1f447; 下方卡片 即可关注我哟! 设为「星标⭐」每天带你 基础入门 到 进阶实践 再到 放弃学习&#xff01; 涉及 企业运维、网络安全、应用开发、物联网、人工智能、大数据 学习知识 “ 花开堪折直须折&#xff0c;莫待无花…...

Tomcat 线上调优记录

原始Tomcat配置 启动参数Plaintext-Xms256m -Xmx512m -XX:MaxPermSize128m Tomcat 参数配置XML<Executor name"tomcatThreadPool" namePrefix"catalina-exec-" maxThreads"1500" minSpareThreads"50" maxIdleTime"600000&q…...

学习 Python 之 Pygame 开发坦克大战(四)

学习 Python 之 Pygame 开发坦克大战&#xff08;四&#xff09;坦克大战添加音效1. 初始化音效2. 加入游戏开始音效和坦克移动音效3. 添加坦克开火音效4. 添加装甲削减音效5. 添加坦克爆炸音效6. 添加子弹击中边界音效坦克大战添加音效 我的素材放到了百度网盘里&#xff0c;…...

New和Malloc的使用及其差异

1&#xff0c;new的使用关于new的定义&#xff1a;new其实就是告诉计算机开辟一段新的空间&#xff0c;但是和一般的声明不同的是&#xff0c;new开辟的空间在堆上&#xff0c;而一般声明的变量存放在栈上。通常来说&#xff0c;当在局部函数中new出一段新的空间&#xff0c;该…...

2023年细胞生物学复习汇总

细胞分化 1.什么是细胞分化&#xff1f;细胞分化的特点是什么&#xff1f; 答&#xff1a;&#xff08;1&#xff09;细胞分化&#xff08;cell differentiation&#xff09;是指同一来源的细胞逐渐产生出形态结构、功能特征各不相同的细胞类群的过程&#xff0c;其结果是在空间…...

光伏VSG-基于虚拟同步发电机的光伏并网逆变器系统MATLAB仿真

采用MATLAB2021b仿真&#xff01;&#xff01;&#xff01;仿真模型1光伏电池模块&#xff08;采用MATLAB自带光伏模块&#xff09;、MPPT控制模块、升压模块、VSG控制模块、电流滞环控制模块。2s时改变光照强度 &#xff01;&#xff01;&#xff01;VSG输出有功功率、无功功率…...

高可用 - 02 Keepalived_VRRP工作原理

文章目录Keepalived VS HeartbeatKeepalived的用途VRRP与工作原理物理路由器和虚拟路由器Keepalived VS Heartbeat Keepalived是Linux下一个轻量级的高可用解决方案&#xff0c;它与Heartbeat、RoseHA实现的功能类似&#xff0c;都可以实现服务或者网络的高可用&#xff0c;但…...

vue实现xml在线编辑功能

先看效果 避免误会 这是一个在线编辑器 我们可以在这上面随意的编写xml代码格式 我们修改上面的内容之后 就可以在控制台输出内容 如果这正是您想要的东西 那就可以先创建一个vue项目 我们先引入依赖 npm install brace -S npm install element-ui -S npm install vue-cli…...

GitHub Workflow

GitHub Workflow 基本流程 把远程仓库克隆到本地 git clone xxxx.git在本地切换至新的分支 git checkout -b new_branch修改本地仓库的文件 项目修改完成后&#xff0c;查看修改的内容 git diff上传修改之后的内容到本地暂存区 git add modified_files将本地暂存区的代码更新…...

vue学习

vue 其实你只要安装一个vue-cli 就可以了 vue-cli 你可以用比较高的版本 这 当然是 可以滴...

Windows使用ssh协议远程连接ubuntu linux系统

Windows使用ssh协议远程连接ubuntu linux系统一、Windows远程连接ubuntu linux系统二、开启ubuntu ssh服务三、获取ubuntu子系统的ip地址四、从windows上通过ssh连接到ubuntu子系统五、设置ubuntu系统ssh自启动&#xff08;18.04&#xff09;一、Windows远程连接ubuntu linux系…...

大数据处理 - Overview

本文主要介绍大数据处理的一些思路。何谓海量数据处理?所谓海量数据处理&#xff0c;无非就是基于海量数据上的存储、处理、操作。何谓海量&#xff0c;就是数据量太大&#xff0c;所以导致要么是无法在较短时间内迅速解决&#xff0c;要么是数据太大&#xff0c;导致无法一次…...

12-Composer的配置与使用详解

1、自定义类与非类的自动加载与测试 # composer> php 包管理工具 &#xff0c;类似npm1.自己写的类&#xff0c;函数&#xff0c;接口&#xff0c;常量等全局成员&#xff0c;通过自动加载来实现按需加载 2.自己写的代码&#xff0c;有哪些依赖&#xff0c;用到了哪些外部成…...

RK3566开启wifi自适应

系统:linux(buildroot) 一、修改Makefile,使能RTW_ADAPTIVITY 文件路径:..\x3566_linux_v1.2.0\kernel\drivers\net\wireless\rockchip_wlan\rtl8821cs\Makefile 第74行&#xff1a; CONFIG_RTW_ADAPTIVITY_EN disable 改为&#xff1a; CONFIG_RTW_ADAPTIVITY_EN enab…...

shell编程之变量定义

typora-copy-images-to: pictures typora-root-url: …\pictures 文章目录typora-copy-images-to: pictures typora-root-url: ..\..\pictures一、SHELL介绍㈠ 什么是shell脚本&#xff1f;㈡ 什么时候用到脚本?㈢ shell脚本能干啥?㈣ 如何学习shell脚本&#xff1f;㈤ 学习s…...

Spring Cloud Alibaba 微服务简介

微服务简介 1 什么是微服务 2014年&#xff0c;Martin Fowler&#xff08;马丁福勒 &#xff09; 提出了微服务的概念&#xff0c;定义了微服务是由以单一应用程序构成的小服务&#xff0c;自己拥有自己的进程与轻量化处理&#xff0c;服务依业务功能设计&#xff0c;以全自动…...

wordpress颜色/如何注册一个域名

2019独角兽企业重金招聘Python工程师标准>>> MySQL主从复制(Master-Slave)与读写分离(MySQL-Proxy)实践 Mysql作为目前世界上使用最广泛的免费数据库&#xff0c;相信所有从事系统运维的工程师都一定接触过。但在实际的生产环境中&#xff0c;由单台Mysql作为独立的…...

下载用的网站怎么做/网站开发是做什么的

一、 4.2.5版本下载 链接: https://pan.baidu.com/s/1qKQ9Kzb7qtjfwu8msBNRHA 密码: seun 二、安装 先将下载好的压缩包解压&#xff0c;然后打开 shell 终端&#xff0c;切换到你已经解压好的文件的bin目录下&#xff0c;然后执行下面的命令&#xff1a; install_compass …...

排名前50名免费的网站/搜索引擎推广案例

文章目录收集表单数据v-model的三个修饰符trimnumberlazy收集表单数据 我们前面在数据绑定的时候就知道可以用v-model的数据双向流动性完成对数据的收集。不过当时我们只是简单的对输入框进行了数据收集。在表单中还有很多其他类型的组件&#xff0c;我们怎么对他们进行数据收…...

做外贸的人如何上国外网站/西安发布最新通知

原文首发于同名微信公号「Allen5G」&#xff0c;欢迎大家搜索关注&#xff01;C语言的变量属性1.C语言中的变量可以有自己的属性2.在定义变量的时候可以加上“属性”关键字3.“属性”关键字指明变量的特有意义auto关键字1.auto即C语言中局部变量的默认属性2.auto表明将被修饰的…...

网站开发人员必备技能/附近电脑培训班零基础

简介 Typora是一款轻便简洁的Markdown编辑器&#xff0c;支持即时渲染技术&#xff0c;这也是与其他Markdown编辑器最显著的区别。即时渲染使得你写Markdown就想是写Word文档一样流畅自如&#xff0c;不像其他编辑器的有编辑栏和显示栏。 Typora删除了预览窗口&#xff0c;以…...

网站导航如何做半透明渐变/百度应用商店app

select date_format(date(current_timestamp()),yyyymmdd)...