Loading [MathJax]/jax/output/CommonHTML/config.js
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >在Go中如何正确重试请求

在Go中如何正确重试请求

作者头像
luozhiyun
发布于 2022-09-21 06:41:47
发布于 2022-09-21 06:41:47
2.4K00
代码可运行
举报
运行总次数:0
代码可运行

转载请声明出处哦~,本篇文章发布于luozhiyun的博客:https://www.luozhiyun.com/archives/677

我们平时在开发中肯定避不开的一个问题是如何在不可靠的网络服务中实现可靠的网络通信,其中 http 请求重试是经常用的技术。但是 Go 标准库 net/http 实际上是没有重试这个功能的,所以本篇文章主要讲解如何在 Go 中实现请求重试。

概述

一般而言,对于网络通信失败的处理分为以下几步:

  1. 感知错误。通过不同的错误码来识别不同的错误,在HTTP中status code可以用来识别不同类型的错误;
  2. 重试决策。这一步主要用来减少不必要的重试,比如HTTP的4xx的错误,通常4xx表示的是客户端的错误,这时候客户端不应该进行重试操作,或者在业务中自定义的一些错误也不应该被重试。根据这些规则的判断可以有效的减少不必要的重试次数,提升响应速度;
  3. 重试策略。重试策略就包含了重试间隔时间,重试次数等。如果次数不够,可能并不能有效的覆盖这个短时间故障的时间段,如果重试次数过多,或者重试间隔太小,又可能造成大量的资源(CPU、内存、线程、网络)浪费。这个我们下面再说;
  4. 对冲策略。对冲是指在不等待响应的情况主动发送单次调用的多个请求,然后取首个返回的回包。这个概念是 grpc 中的概念,我把它也借用过来;
  5. 熔断降级;如果重试之后还是不行,说明这个故障不是短时间的故障,而是长时间的故障。那么可以对服务进行熔断降级,后面的请求不再重试,这段时间做降级处理,减少没必要的请求,等服务端恢复了之后再进行请求,这方面的实现很多 go-zerosentinelhystrix-go,也蛮有意思的;

重试策略

重试策略可以分为很多种,一方面要考虑到本次请求时长过长而影响到的业务忍受度,另一方面要考虑到重试会对下游服务产生过多的请求而带来的影响,总之就是一个trade-off的问题。

所以对于重试算法,一般是在重试之间加一个 gap 时间,感兴趣的朋友也可以去看看这篇文章。结合我们自己平时的实践加上这篇文章的算法一般可以总结出以下几条规则:

  • 线性间隔(Linear Backoff):每次重试间隔时间是固定的进行重试,如每1s重试一次;
  • 线性间隔+随机时间(Linear Jitter Backoff):有时候每次重试间隔时间一致可能会导致多个请求在同一时间请求,那么我们可以加入一个随机时间,在线性间隔时间的基础上波动一个百分比的时间;
  • 指数间隔(Exponential Backoff):每次间隔时间是2指数型的递增,如等 3s 9s 27s后重试;
  • 指数间隔+随机时间(Exponential Jitter Backoff):这个就和第二个类似了,在指数递增的基础上添加一个波动时间;

上面有两种策略都加入了扰动(jitter),目的是防止惊群问题 (Thundering Herd Problem)的发生。

In computer science, the thundering herd problem occurs when a large number of processes or threads waiting for an event are awoken when that event occurs, but only one process is able to handle the event. When the processes wake up, they will each try to handle the event, but only one will win. All processes will compete for resources, possibly freezing the computer, until the herd is calmed down again

所谓惊群问题当许多进程都在等待被同一事件唤醒的时候,当事件发生后最后只有一个进程能获得处理。其余进程又造成阻塞,这会造成上下文切换的浪费。所以加入一个随机时间来避免同一时间同时请求服务端还是很有必要的。

使用 net/http 重试所带来的问题

重试这个操作其实对于 Go 来说其实还不能直接加一个 for 循环根据次数来进行,对于 Get 请求重试的时候没有请求体,可以直接进行重试,但是对于 Post 请求来说需要把请求体放到Reader 里面,如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
req, _ := http.NewRequest("POST", "localhost", strings.NewReader("hello"))

服务端收到请求之后就会从这个Reader中调用Read()函数去读取数据,通常情况当服务端去读取数据的时候,offset会随之改变,下一次再读的时候会从offset位置继续向后读取。所以如果直接重试,会出现读不到 Reader的情况。

我们可以先弄一个例子:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
func main() {
	go func() {
		http.HandleFunc("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			time.Sleep(time.Millisecond * 20)
			body, _ := ioutil.ReadAll(r.Body)  
			fmt.Printf("received body with length %v containing: %v\n", len(body), string(body))
			w.WriteHeader(http.StatusOK)
		}))
		http.ListenAndServe(":8090", nil)
	}()
	fmt.Print("Try with bare strings.Reader\n") 
	retryDo(req)
}

func retryDo() {
	originalBody := []byte("abcdefghigklmnopqrst")
	reader := strings.NewReader(string(originalBody))
	req, _ := http.NewRequest("POST", "http://localhost:8090/", reader)
	client := http.Client{
		Timeout: time.Millisecond * 10,
	}

	for {
		_, err := client.Do(req)
		if err != nil {
			fmt.Printf("error sending the first time: %v\n", err)
		} 
		time.Sleep(1000)
	}
}

// output:
error sending the first time: Post "http://localhost:8090/": context deadline exceeded (Client.Timeout exceeded while awaiting headers)
error sending the first time: Post "http://localhost:8090/": http: ContentLength=20 with Body length 0
error sending the first time: Post "http://localhost:8090/": http: ContentLength=20 with Body length 0
received body with length 20 containing: abcdefghigklmnopqrst
error sending the first time: Post "http://localhost:8090/": http: ContentLength=20 with Body length 0
....

在上面这个例子中,在客户端设值了 10ms 的超时时间。在服务端模拟请求处理超时情况,先sleep 20ms,然后再读请求数据,这样必然会超时。

当再次请求的时候,发现 client 请求的 Body 数据并不是我们预期的20个长度,而是 0,导致了 err。因此需要将Body这个Reader 进行重置,如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
func resetBody(request *http.Request, originalBody []byte) {
	request.Body = io.NopCloser(bytes.NewBuffer(originalBody))
	request.GetBody = func() (io.ReadCloser, error) {
		return io.NopCloser(bytes.NewBuffer(originalBody)), nil
	}
}

上面这段代码中,我们使用 io.NopCloser 对请求的 Body 数据进行了重置,避免下次请求的时候出现非预期的异常。

那么相对于上面简陋的例子,还可以完善一下,加上我们上面说的 StatusCode 重试判断、重试策略、重试次数等等,可以写成这样:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
func retryDo(req *http.Request, maxRetries int, timeout time.Duration,
	backoffStrategy BackoffStrategy) (*http.Response, error) {
	var (
		originalBody []byte
		err          error
	)
	if req != nil && req.Body != nil {
		originalBody, err = copyBody(req.Body)
		resetBody(req, originalBody)
	}
	if err != nil {
		return nil, err
	}
	AttemptLimit := maxRetries
	if AttemptLimit <= 0 {
		AttemptLimit = 1
	}

	client := http.Client{
		Timeout: timeout,
	}
	var resp *http.Response
  //重试次数
	for i := 1; i <= AttemptLimit; i++ {
		resp, err = client.Do(req)
		if err != nil {
			fmt.Printf("error sending the first time: %v\n", err)
		} 
		// 重试 500 以上的错误码
		if err == nil && resp.StatusCode < 500 {
			return resp, err
		}
		// 如果正在重试,那么释放fd
		if resp != nil {
			resp.Body.Close()
		}
		// 重置body
		if req.Body != nil {
			resetBody(req, originalBody)
		}
		time.Sleep(backoffStrategy(i) + 1*time.Microsecond)
	}
	// 到这里,说明重试也没用
	return resp, req.Context().Err()
}

func copyBody(src io.ReadCloser) ([]byte, error) {
	b, err := ioutil.ReadAll(src)
	if err != nil {
		return nil, ErrReadingRequestBody
	}
	src.Close()
	return b, nil
}

func resetBody(request *http.Request, originalBody []byte) {
	request.Body = io.NopCloser(bytes.NewBuffer(originalBody))
	request.GetBody = func() (io.ReadCloser, error) {
		return io.NopCloser(bytes.NewBuffer(originalBody)), nil
	}
}

对冲策略

上面讲的是重试的概念,那么有时候我们接口只是偶然会出问题,并且我们的下游服务并不在乎多请求几次,那么我们可以借用 grpc 里面的概念:对冲策略(Hedged requests)

对冲是指在不等待响应的情况主动发送单次调用的多个请求,然后取首个返回的回包。对冲和重试的区别点主要在:对冲在超过指定时间没有响应就会直接发起请求,而重试则必须要服务端响应后才会发起请求。所以对冲更像是比较激进的重试策略。

使用对冲的时候需要注意一点是,因为下游服务可能会做负载均衡策略,所以要求请求的下游服务一般是要求幂等的,能够在多次并发请求中是安全的,并且是符合预期的。

对冲请求一般是用来处理“长尾”请求的,关于”长尾“请求的概念可以看这篇文章:https://segmentfault.com/a/1190000039978117

并发模式的处理

因为对冲重试加上了并发的概念,要用到 goroutine 来并发请求,所以我们可以把数据封装到 channel 里面来进行消息的异步处理。

并且由于是多个goroutine处理消息,我们需要在每个goroutine处理完毕,但是都失败的情况下返回err,不能直接由于channel等待卡住主流程,这一点十分重要。

但是由于在 Go 中是无法获取每个 goroutine 的执行结果的,我们又只关注正确处理结果,需要忽略错误,所以需要配合 WaitGroup 来实现流程控制,示例如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
func main() {
	totalSentRequests := &sync.WaitGroup{}
	allRequestsBackCh := make(chan struct{})
	multiplexCh := make(chan struct {
		result string
		retry  int
	})
	go func() {
    //所有请求完成之后会close掉allRequestsBackCh
		totalSentRequests.Wait()
		close(allRequestsBackCh)
	}()
	for i := 1; i <= 10; i++ {
		totalSentRequests.Add(1)
		go func() {
			// 标记已经执行完
			defer totalSentRequests.Done()
			// 模拟耗时操作
			time.Sleep(500 * time.Microsecond)
			// 模拟处理成功
			if random.Intn(500)%2 == 0 {
				multiplexCh <- struct {
					result string
					retry  int
				}{"finsh success", i}
			}
			// 处理失败不关心,当然,也可以加入一个错误的channel中进一步处理
		}()
	}
	select {
	case <-multiplexCh:
		fmt.Println("finish success")
	case <-allRequestsBackCh:
		// 到这里,说明全部的 goroutine 都执行完毕,但是都请求失败了
		fmt.Println("all req finish,but all fail")
	}
}

从上面这段代码看为了进行流程控制,多用了两个 channel :totalSentRequests 、allRequestsBackCh,多用了一个 goroutine 异步关停 allRequestsBackCh,才实现的流程控制,实在太过于麻烦,有新的实现方案的同学不妨和我探讨一下。

除了上面的并发请求控制的问题,对于对冲重试来说,还需要注意的是,由于请求不是串行的,所以 http.Request 的上下文会变,所以每次请求前需要 clone 一次 context,保证每个不同请求的 context 是独立的。但是每次 clone 之后 Reader offset位置又变了,所以我们还需要进行重新 reset:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
func main() {
	req, _ := http.NewRequest("POST", "localhost", strings.NewReader("hello"))
	req2 := req.Clone(req.Context())
	contents, _ := io.ReadAll(req.Body)
	contents2, _ := io.ReadAll(req2.Body)
	fmt.Printf("First read: %v\n", string(contents))
	fmt.Printf("Second read: %v\n", string(contents2))
}

//output:
First read: hello
Second read: 

所以结合一下上面的例子,我们可以将对冲重试的代码变为:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
func retryHedged(req *http.Request, maxRetries int, timeout time.Duration,
	backoffStrategy BackoffStrategy) (*http.Response, error) {
	var (
		originalBody []byte
		err          error
	)
	if req != nil && req.Body != nil {
		originalBody, err = copyBody(req.Body)
	}
	if err != nil {
		return nil, err
	}

	AttemptLimit := maxRetries
	if AttemptLimit <= 0 {
		AttemptLimit = 1
	}

	client := http.Client{
		Timeout: timeout,
	}

	// 每次请求copy新的request
	copyRequest := func() (request *http.Request) {
		request = req.Clone(req.Context())
		if request.Body != nil {
			resetBody(request, originalBody)
		}
		return
	}

	multiplexCh := make(chan struct {
		resp  *http.Response
		err   error
		retry int
	})

	totalSentRequests := &sync.WaitGroup{}
	allRequestsBackCh := make(chan struct{})
	go func() {
		totalSentRequests.Wait()
		close(allRequestsBackCh)
	}()
	var resp *http.Response
	for i := 1; i <= AttemptLimit; i++ {
		totalSentRequests.Add(1)
		go func() {
			// 标记已经执行完
			defer totalSentRequests.Done()
			req = copyRequest()
			resp, err = client.Do(req)
			if err != nil {
				fmt.Printf("error sending the first time: %v\n", err)
			}
			// 重试 500 以上的错误码
			if err == nil && resp.StatusCode < 500 {
				multiplexCh <- struct {
					resp  *http.Response
					err   error
					retry int
				}{resp: resp, err: err, retry: i}
				return
			}
			// 如果正在重试,那么释放fd
			if resp != nil {
				resp.Body.Close()
			}
			// 重置body
			if req.Body != nil {
				resetBody(req, originalBody)
			}
			time.Sleep(backoffStrategy(i) + 1*time.Microsecond)
		}()
	}

	select {
	case res := <-multiplexCh:
		return res.resp, res.err
	case <-allRequestsBackCh:
		// 到这里,说明全部的 goroutine 都执行完毕,但是都请求失败了
		return nil, errors.New("all req finish,but all fail")
	}
}

熔断 & 降级

因为在我们使用 http 调用的时候,调用的外部服务很多时候其实并不可靠,很有可能因为外部的服务问题导致自身服务接口调用等待,从而调用时间过长,产生大量的调用积压,慢慢耗尽服务资源,最终导致服务调用雪崩的发生,所以在服务中使用熔断降级是非常有必要的一件事。

其实熔断降级的概念总体上来说,实现都差不多。核心思想就是通过全局的计数器,用来统计调用次数、成功/失败次数。通过统计的计数器来判断熔断器的开关,熔断器的状态由三种状态表示:closed、open、half open,下面借用了 sentinel 的图来表示三者的关系:

首先初始状态是closed,每次调用都会经过计数器统计总次数和成功/失败次数,然后在达到一定阈值或条件之后熔断器会切换到 open状态,发起的请求会被拒绝。

熔断器规则中会配置一个熔断超时重试的时间,经过熔断超时重试时长后熔断器会将状态置为 half-open 状态。这个状态对于 sentinel 来说会发起定时探测,对于 go-zero 来说会允许通过一定比例的请求,不管是主动定时探测,还是被动通过的请求调用,只要请求的结果返回正常,那么就需要重置计数器恢复到 closed 状态。

一般而言会支持两种熔断策略:

  • 错误比率:熔断时间窗口内的请求数阈值错误率大于错误率阈值,从而触发熔断。
  • 平均 RT(响应时间):熔断时间窗口内的请求数阈值大于平均 RT 阈值,从而触发熔断。

比如我们使用 hystrix-go 来处理我们的服务接口的熔断,可以结合我们上面说的重试从而进一步保障我们的服务。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
hystrix.ConfigureCommand("my_service", hystrix.CommandConfig{ 
        ErrorPercentThreshold:  30,
    })
    _ = hystrix.Do("my_service", func() error { 
      	req, _ := http.NewRequest("POST", "http://localhost:8090/", strings.NewReader("test"))
        _, err := retryDo(req, 5, 20*time.Millisecond, ExponentialBackoff)
        if err != nil {
            fmt.Println("get error:%v",err)
            return err
        }
        return nil
    }, func(err error) error {
        fmt.Printf("handle  error:%v\n", err)
        return nil
    }) 

上面这个例子中就利用 hystrix-go 设置了最大错误百分比等于30,超过这个阈值就会进行熔断。

总结

这篇文章从接口调用出发,探究了重试的几个要点,讲解了重试的几种策略;然后在实践环节中讲解了直接使用 net/http重试会有什么问题,对于对冲策略使用 channel 加上 waitgroup 来实现并发请求控制;最后使用 hystrix-go 来对故障服务进行熔断,防止请求堆积引起资源耗尽的问题。

Reference

https://github.com/sethgrid/pester

https://juejin.cn/post/6844904105354199047

https://github.com/ma6174/blog/issues/11

https://aws.amazon.com/cn/blogs/architecture/exponential-backoff-and-jitter/

https://medium.com/@trongdan_tran/circuit-breaker-and-retry-64830e71d0f6

https://www.lixueduan.com/post/grpc/09-retry/

https://en.wikipedia.org/wiki/Thundering_herd_problem

https://go-zero.dev/cn/docs/blog/governance/breaker-algorithms/

https://sre.google/sre-book/handling-overload/#eq2101

https://sentinelguard.io/zh-cn/docs/golang/circuit-breaking.html

https://github.com/afex/hystrix-go

https://segmentfault.com/a/1190000039978117

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-08-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
七、连Pycharm都不知道怎么用,学什么Python
工欲善其事必先利其器,Pycharm 是最受欢迎的Python开发工具,它提供的功能非常强大,我尽量把自己用的都写写吧
润森
2020/09/27
7320
七、连Pycharm都不知道怎么用,学什么Python
教你使用PyCharm实现远程调试
最近手头被交接了几个测试脚本,都需要进行二次开发或者持续维护,这几个测试脚本分别被部署在不同的服务器中,使用的Python环境也各不相同,因此如果在本地进行二次开发再部署到服务器中,会很麻烦,所以在本地PyCharm上搭建一个远程调试功能,对脚本进行远程调试和运行,就会特别方便啦。
用户5521279
2019/06/02
1.4K0
pycharm本地远程连接服务器,并在本地调试服务器代码
以pycharm professional 2019.1版本为例(使用学校邮箱注册,可以走教育通道) 本地系统:Ubuntu16.04
烤粽子
2021/07/07
8.4K0
pycharm本地远程连接服务器,并在本地调试服务器代码
Pycharm专业版配置远程服务器并自动同步代码
如果每次都在本机上面写代码,然后传到服务器上面,在服务器上面运行就太麻烦了。这样的方式十分繁琐,效率很低。
py3study
2020/02/27
4.1K0
Pycharm专业版配置远程服务器并自动同步代码
如何配置Pycharm实现本地编写代码远程到服务器编译并同步代码
本文主要介绍如何使用Pycharm进行远程开发,使用内网穿透工具实现异地连接服务器编译代码与项目同步。
YY的秘密代码小屋
2024/06/14
2.8K0
如何配置Pycharm实现本地编写代码远程到服务器编译并同步代码
AutoDL算力租用++Pycharm中SSH、SFTP连接远程服务器[通俗易懂]
创建一个新的Pycharm项目(如果你的代码在服务器上,你需要用一个新的纯Python项目同步服务器上的项目,那么进行这一步)
全栈程序员站长
2022/09/07
8.9K0
AutoDL算力租用++Pycharm中SSH、SFTP连接远程服务器[通俗易懂]
Pycharm远程连接服务器(windows下远程修改服务器代码)[通俗易懂]
http://blog.csdn.net/duankaifei/article/details/41898641
全栈程序员站长
2022/09/27
10.2K0
Pycharm远程连接服务器(windows下远程修改服务器代码)[通俗易懂]
02-pycharm详细安装教程(大妈看了都会)
PyCharm是一个流行的集成开发环境(IDE),专门为Python语言开发而设计。它提供了许多功能来帮助开发者更高效地编写、测试和调试Python代码。以下是PyCharm的一些主要用途:
正在走向自律
2024/12/17
6070
02-pycharm详细安装教程(大妈看了都会)
Python深耕之Pycharm实现远程服务器连接
Pycharm作为python的一个重要开发工具深得大家的喜爱,并且专业版本更是开发了替代xshell等的ssh远程连接插件。首先我们来看下如何获得免费使用专业版本的权限。JetBrains为学生老师用户开通了免费许可。链接如下:
一粒沙
2022/11/21
1.6K0
Python深耕之Pycharm实现远程服务器连接
pycharm安装,svn使用,远程开发调试,接口测试,连接服务器
磨刀不误砍柴工,配置完美的编辑器,在开发时,能帮助我们节约大量的时间成本,从而是我们的精力放在业务逻辑实现上面!
用户1558882
2019/01/30
1.5K0
Pycharm 实现远程部署和调试,原来这么简单
一般代码本地调试完成后,需要运行到服务器上,比如自动化测试脚本、爬虫脚本等,所以第一步需要将项目上传到服务器,然后在服务器上进行调试和运行。
吾非同
2020/12/07
1.8K0
Pycharm 实现远程部署和调试,原来这么简单
pycharm帮助文档_pycharm pytorch
大家在利用python进行机器学习时,pycharm是一个很不错的IDE。通过这段时间的使用,自己总结了一些使用心得,故试着写下来共勉,不当之处,希望正在阅读的你批评指正。
全栈程序员站长
2022/09/27
2.2K0
pycharm帮助文档_pycharm pytorch
Pycharm远程开发_pycharm远程linux开发
需要使用远程的linux服务器,但是因为pycharm很方便、希望在自己电脑上开发。
全栈程序员站长
2022/09/25
2.8K0
Pycharm远程开发_pycharm远程linux开发
Pycharm配置远程开发环境
代码的运行依赖一整套的运行环境,如微服务之间依赖haproxy的调用,每次调试时,都要将代码部署到远程开发环境,每次修改后都要手动上传代码,并在远程服务器上调试。 代码修改后,只能在本地开发,如果想要切换设备,需要将代码提交到git,然后并不是每次修改的,都会提交到git,或者手动拷贝代码。 本地开发使用的Windows或者Mac o,而服务运行环境则是Linux,每次在本地开发完成的代码,上传到服务器后有各种各样的问题,开发环境跟运行环境无法保证一致给部署和调试带来困难。
用户1392128
2024/01/08
1.9K0
Pycharm配置远程开发环境
pycharm如何远程连接服务器_py服务端软件
一般连接服务器需要服务器的ip地址,IP地址分为内外IP和外网IP,一般高校实验室的服务器使用内网IP,例如192.168.1.X等。但是做深度学习的小伙伴都知道,我们可能会租用网络服务器或者将学校的服务器IP地址映射成外网,以便在任何其他地方进行访问。接下来,将带领大家一步步实现连接服务器并将代码托管到服务器上进行跑代码。
全栈程序员站长
2022/09/27
1.7K0
pycharm如何远程连接服务器_py服务端软件
Linux上跑深度学习实验
之前一直使用Google Colab跑实验,因为实验的规模不大,配合Google Drive用起来就很舒服,但是最近要系统地进行实验,规模一下子上来了,Colab经常在代码没跑完就达到额度上限,于是自己租了个GPU服务器,Ubuntu子系统,没有图形化界面,所以用起来还不太熟练,这里简单记录一下一些关键点。
Marigold
2023/08/23
7470
Linux上跑深度学习实验
手把手教你用Pycharm连接远程Python环境
这个要从我的一次经历说起,有一次我帮朋友爬取一些东西,由于类别不同,分了几次爬取,这一次我写好规则之后,依然正常爬取,由于我本人比较善良,加上数据量目测并不是太多,并没有使用代理ip,并且将scpay的速度控制的比较慢,一般爬取时,一般也就几分钟而已,泡一杯咖啡喝几口就完了。
Python进阶者
2021/02/05
4.7K0
手把手教你用Pycharm连接远程Python环境
使用 VSCODE 连接远程服务器上的容器
自从 VSCODE 出现以来,我就立马从 pycharm 转入了。厌倦了 pycharm 的笨重,用了 vscode 之后只能说是真香,编辑器界的 flask。但是和 flask 一样,虽然轻便,但是自然基本上一切都需要你自己去配置,各种插件和扩展。有些功能 pycharm 可能自带,但是 VSCODE 就需要自己各种折腾,比如说本文的主题:本地连接远程服务器上的容器。
Alan Lee
2020/03/18
9.8K1
使用 VSCODE 连接远程服务器上的容器
云服务器上运行python程序(PyCharm本地编辑同步服务器+Anaconda)挂载跑实验详细教程[通俗易懂]
背景:前段时间帮学长跑实验,在电脑上挂着得跑15个小时左右。白天跑,半夜跑,跑了5、6次,一次因为电脑死机,一次因为PyCharm闪退。跑了那么久全白费,想想就气。而且在本地跑实验十分占用CPU等资源,耗电又有风险。想着自己还有个服务器,这2天就捣鼓了下怎么在服务器上跑实验。总结下步骤,避免大家采坑。
全栈程序员站长
2022/09/12
9.6K0
云服务器上运行python程序(PyCharm本地编辑同步服务器+Anaconda)挂载跑实验详细教程[通俗易懂]
linux服务器安装pycharm_服务器
如果你想使用 pycharm 的远程 SSH 功能在服务器上跑代码,记得一定要下载 专业版(社区版不支持 SSH )。
全栈程序员站长
2022/09/27
1.9K0
linux服务器安装pycharm_服务器
推荐阅读
相关推荐
七、连Pycharm都不知道怎么用,学什么Python
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验