Opentelemetry SDK的简单用法

概述

Opentelemetry trace的简单架构图如下,客户端和服务端都需要启动一个traceProvider,主要用于将trace数据传输到registry(如jaeger、opencensus等)。client和server通过context将整个链路串起来。

traceProvider会周期性的将数据推送到Registry,默认是5s

  1. func NewBatchSpanProcessor(exporter SpanExporter, options ...BatchSpanProcessorOption) SpanProcessor {
  2. ...
  3. o := BatchSpanProcessorOptions{
  4. BatchTimeout: time.Duration(env.BatchSpanProcessorScheduleDelay(DefaultScheduleDelay)) * time.Millisecond,
  5. ExportTimeout: time.Duration(env.BatchSpanProcessorExportTimeout(DefaultExportTimeout)) * time.Millisecond,
  6. MaxQueueSize: maxQueueSize,
  7. MaxExportBatchSize: maxExportBatchSize,
  8. }
  9. ...
  10. }

下面是官方提供的SDK,它实现了opentelemetry的API,也是操作opentelemetry所使用的基本库:

  1. tracesdk "go.opentelemetry.io/otel/sdk/trace"

创建TracerProvider

要使用trace,首先要创建一个TracerProvider,定义exporter以及相关属性。

使用全局TracerProvider

参数表示应用名称或代码库名称

  1. var tracer = otel.Tracer("app_or_package_name")

创建TracerProvider

下面展示了使用Jaeger作为exporter的tracerProvider,其中包含两个概念:exporter和resource。前者为发送遥测数据的目的地,如jaeger、zepkin、opencensus等;后者通常用于添加非临时的底层元数据信息,如主机名,实例ID等。

  1. // tracerProvider returns an OpenTelemetry TracerProvider configured to use
  2. // the Jaeger exporter that will send spans to the provided url. The returned
  3. // TracerProvider will also use a Resource configured with all the information
  4. // about the application.
  5. func tracerProvider(url string) (*tracesdk.TracerProvider, error) {
  6. // Create the Jaeger exporter
  7. exp, err := jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint(url)))
  8. if err != nil {
  9. return nil, err
  10. }
  11. tp := tracesdk.NewTracerProvider(
  12. // Always be sure to batch in production.
  13. tracesdk.WithBatcher(exp),
  14. // Record information about this application in a Resource.
  15. tracesdk.WithResource(resource.NewWithAttributes(
  16. semconv.SchemaURL,
  17. semconv.ServiceNameKey.String(service),
  18. attribute.String("environment", environment),
  19. attribute.Int64("ID", id),
  20. )),
  21. )
  22. return tp, nil
  23. }

可以使用如下方式创建resource,semconv包可以为资源属性提供规范化的名称。

  1. // newResource returns a resource describing this application.
  2. func newResource() *resource.Resource {
  3. r, _ := resource.Merge(
  4. resource.Default(),
  5. resource.NewWithAttributes(
  6. semconv.SchemaURL,
  7. semconv.ServiceNameKey.String("fib"),
  8. semconv.ServiceVersionKey.String("v0.1.0"),
  9. attribute.String("environment", "demo"),
  10. ),
  11. )
  12. return r
  13. }
注册tracerProvider

如果使用自定义的tracerProvider,需要将其注册为全局tracerProvider:

  1. tp, err := tracerProvider("http://localhost:14268/api/traces")
  2. if err != nil {
  3. log.Fatal(err)
  4. }
  5. // Register our TracerProvider as the global so any imported
  6. // instrumentation in the future will default to using it.
  7. otel.SetTracerProvider(tp)

启动tracerProvider

  1. tr := tp.Tracer("component-main")
  2. ctx, span := tr.Start(ctx, "foo")
  3. defer span.End()

关闭tracerProvider

当程序退出前,需要关闭tracerProvider,执行数据清理工作:

  1. ctx, cancel := context.WithCancel(context.Background())
  2. defer cancel()
  3. // Cleanly shutdown and flush telemetry when the application exits.
  4. defer func(ctx context.Context) {
  5. // Do not make the application hang when it is shutdown.
  6. ctx, cancel = context.WithTimeout(ctx, time.Second*5)
  7. defer cancel()
  8. if err := tp.Shutdown(ctx); err != nil {
  9. log.Fatal(err)
  10. }
  11. }(ctx)

span的简单用法

tracer会创建span,为了创建span,需要一个context.Context实例。该context通常来自于请求对象,或已经存在的父span。Go的context用于保存活动的span,当span启用后,就可以操作创建好的span以及其包含的已修改的上下文。当span结束后,其将成为不可变状态。

下面为从请求中获取span:

  1. func httpHandler(w http.ResponseWriter, r *http.Request) {
  2. ctx, span := tracer.Start(r.Context(), "hello-span")
  3. defer span.End()
  4. // do some work to track with hello-span
  5. }
获取当前span
  1. // This context needs contain the active span you plan to extract.
  2. ctx := context.TODO()
  3. span := trace.SpanFromContext(ctx)
  4. // Do something with the current span, optionally calling `span.End()` if you want it to en
创建嵌套的span

下面将childSpan嵌套在了parentSpan中,表示串行执行:

  1. func parentFunction(ctx context.Context) {
  2. ctx, parentSpan := tracer.Start(ctx, "parent")
  3. defer parentSpan.End()
  4. // call the child function and start a nested span in there
  5. childFunction(ctx)
  6. // do more work - when this function ends, parentSpan will complete.
  7. }
  8. func childFunction(ctx context.Context) {
  9. // Create a span to track `childFunction()` - this is a nested span whose parent is `parentSpan`
  10. ctx, childSpan := tracer.Start(ctx, "child")
  11. defer childSpan.End()
  12. // do work here, when this function returns, childSpan will complete.
  13. }
设置span相关的信息
添加属性

属性是一组key/value元数据,用于聚合、过滤以及对traces进行分组。

  1. // setting attributes at creation...
  2. ctx, span = tracer.Start(ctx, "attributesAtCreation", trace.WithAttributes(attribute.String("hello", "world")))
  3. // ... and after creation
  4. span.SetAttributes(attribute.Bool("isTrue", true), attribute.String("stringAttr", "hi!"))

可以使用如下方式预设置属性,然后再添加到span中:

  1. var myKey = attribute.Key("myCoolAttribute")
  2. span.SetAttributes(myKey.String("a value"))

注:trace的属性并不是随便定义的,它有一些特定的约束,参见官方约定以及uptrace总结的约束

添加事件

事件为可读的消息,表示在span的生命周期中"发生了某些事情"。例如,假设某个函数需要获取锁来访问互斥的资源时,可以在两个节点创建事件,一个是尝试访问资源时,另一个是获取到锁时。如:

  1. span.AddEvent("Acquiring lock")
  2. mutex.Lock()
  3. span.AddEvent("Got lock, doing work...")
  4. // do stuff
  5. span.AddEvent("Unlocking")
  6. mutex.Unlock()

事件的一个有用的特点是,它们的时间戳显示为从span开始的偏移量(即事件发生的真实时间)。

事件也可以配置属性:

  1. span.AddEvent("Cancelled wait due to external signal", trace.WithAttributes(attribute.Int("pid", 4328), attribute.String("signal", "SIGHUP")))
设置span状态

通常用于表示操作是否有异常。默认状态为Unset,可以手动将其设置为Ok,但通常没必要这么做。

  1. result, err := operationThatCouldFail()
  2. if err != nil {
  3. span.SetStatus(codes.Error, "operationThatCouldFail failed")
  4. }
记录错误

用于记录错误日志或调用栈等信息。强烈建议在使用RecordError的同时,通过SetStatus将span状态设置为Error

  1. result, err := operationThatCouldFail()
  2. if err != nil {
  3. span.SetStatus(codes.Error, "operationThatCouldFail failed")
  4. span.RecordError(err)
  5. }

完整代码

下面是对本地的一个函数bar生成trace信息:

  1. func tracerProvider(url string) (*tracesdk.TracerProvider, error) {
  2. // Create the Jaeger exporter
  3. exp, err := jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint(url)))
  4. if err != nil {
  5. return nil, err
  6. }
  7. tp := tracesdk.NewTracerProvider(
  8. // Always be sure to batch in production.
  9. tracesdk.WithBatcher(exp),
  10. // Record information about this application in a Resource.
  11. tracesdk.WithResource(resource.NewWithAttributes(
  12. semconv.SchemaURL,
  13. semconv.ServiceNameKey.String(service),
  14. attribute.String("environment", environment),
  15. attribute.Int64("ID", id),
  16. )),
  17. )
  18. return tp, nil
  19. }
  20. func main() {
  21. tp, err := tracerProvider("http://localhost:14268/api/traces")
  22. if err != nil {
  23. log.Fatal(err)
  24. }
  25. // Register our TracerProvider as the global so any imported
  26. // instrumentation in the future will default to using it.
  27. otel.SetTracerProvider(tp)
  28. ctx, cancel := context.WithCancel(context.Background())
  29. defer cancel()
  30. // Cleanly shutdown and flush telemetry when the application exits.
  31. defer func(ctx context.Context) {
  32. // Do not make the application hang when it is shutdown.
  33. ctx, cancel = context.WithTimeout(ctx, time.Second*5)
  34. defer cancel()
  35. if err := tp.Shutdown(ctx); err != nil {
  36. log.Fatal(err)
  37. }
  38. }(ctx)
  39. tr := tp.Tracer("component-main")
  40. ctx, span := tr.Start(ctx, "foo")
  41. defer span.End()
  42. bar(ctx)
  43. }
  44. func bar(ctx context.Context) {
  45. // Use the global TracerProvider.
  46. tr := otel.Tracer("component-bar")
  47. _, span := tr.Start(ctx, "bar")
  48. span.SetAttributes(attribute.Key("testset").String("value"))
  49. defer span.End()
  50. // Do bar...
  51. }

Trace context的跨服务传播

为了跨服务传播Trace context需要注册一个propagator ,通常在创建注册TracerProvider之后执行。

  1. func initTracer() (*sdktrace.TracerProvider, error) {
  2. // Create stdout exporter to be able to retrieve
  3. // the collected spans.
  4. exporter, err := stdout.New(stdout.WithPrettyPrint())
  5. if err != nil {
  6. return nil, err
  7. }
  8. // For the demonstration, use sdktrace.AlwaysSample sampler to sample all traces.
  9. // In a production application, use sdktrace.ProbabilitySampler with a desired probability.
  10. tp := sdktrace.NewTracerProvider(
  11. sdktrace.WithSampler(sdktrace.AlwaysSample()),
  12. sdktrace.WithBatcher(exporter),
  13. )
  14. otel.SetTracerProvider(tp)
  15. otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}))
  16. return tp, err
  17. }

如上注册了两种propagator :TraceContext和Baggage,因此可以使用这两种数据结构传播上下文。

TraceContext

下面是gorilla/mux的服务端代码,通过 trace.SpanFromContext(r.Context())从请求的context构建span,当然也可以通过tracer.Start(c.Context(), "getUser", oteltrace.WithAttributes(attribute.String("id", id)))这种方式启动一个新的span:

  1. func TestPropagationWithCustomPropagators(t *testing.T) {
  2. prop := propagation.TraceContext{}
  3. r := httptest.NewRequest("GET", "/user/123", nil)
  4. w := httptest.NewRecorder()
  5. ctx := trace.ContextWithRemoteSpanContext(context.Background(), sc)
  6. prop.Inject(ctx, propagation.HeaderCarrier(r.Header))
  7. var called bool
  8. router := mux.NewRouter()
  9. router.Use(Middleware("foobar", WithPropagators(prop)))
  10. router.HandleFunc("/user/{id}", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  11. called = true
  12. span := trace.SpanFromContext(r.Context())
  13. defer span.End()
  14. assert.Equal(t, sc, span.SpanContext())
  15. w.WriteHeader(http.StatusOK)
  16. }))
  17. router.ServeHTTP(w, r)
  18. assert.True(t, called, "failed to run test")
  19. }

baggage

下面是使用baggage的客户端和服务端代码,需要注意的是,客户端需要使用otelhttp

客户端代码:

  1. package main
  2. import (
  3. "context"
  4. "flag"
  5. "fmt"
  6. "io/ioutil"
  7. "log"
  8. "net/http"
  9. "time"
  10. "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
  11. "go.opentelemetry.io/otel"
  12. "go.opentelemetry.io/otel/baggage"
  13. stdout "go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
  14. "go.opentelemetry.io/otel/propagation"
  15. sdktrace "go.opentelemetry.io/otel/sdk/trace"
  16. semconv "go.opentelemetry.io/otel/semconv/v1.10.0"
  17. "go.opentelemetry.io/otel/trace"
  18. )
  19. func initTracer() (*sdktrace.TracerProvider, error) {
  20. // Create stdout exporter to be able to retrieve
  21. // the collected spans.
  22. exporter, err := stdout.New(stdout.WithPrettyPrint())
  23. if err != nil {
  24. return nil, err
  25. }
  26. // For the demonstration, use sdktrace.AlwaysSample sampler to sample all traces.
  27. // In a production application, use sdktrace.ProbabilitySampler with a desired probability.
  28. tp := sdktrace.NewTracerProvider(
  29. sdktrace.WithSampler(sdktrace.AlwaysSample()),
  30. sdktrace.WithBatcher(exporter),
  31. )
  32. otel.SetTracerProvider(tp)
  33. otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}))
  34. return tp, err
  35. }
  36. func main() {
  37. tp, err := initTracer()
  38. if err != nil {
  39. log.Fatal(err)
  40. }
  41. defer func() {
  42. if err := tp.Shutdown(context.Background()); err != nil {
  43. log.Printf("Error shutting down tracer provider: %v", err)
  44. }
  45. }()
  46. url := flag.String("server", "http://localhost:7777/hello", "server url")
  47. flag.Parse()
  48. client := http.Client{Transport: otelhttp.NewTransport(http.DefaultTransport)}
  49. bag, _ := baggage.Parse("username=donuts")
  50. ctx := baggage.ContextWithBaggage(context.Background(), bag)
  51. var body []byte
  52. tr := otel.Tracer("example/client")
  53. err = func(ctx context.Context) error {
  54. ctx, span := tr.Start(ctx, "say hello", trace.WithAttributes(semconv.PeerServiceKey.String("ExampleService")))
  55. defer span.End()
  56. req, _ := http.NewRequestWithContext(ctx, "GET", *url, nil)
  57. fmt.Printf("Sending request...\n")
  58. res, err := client.Do(req)
  59. if err != nil {
  60. panic(err)
  61. }
  62. body, err = ioutil.ReadAll(res.Body)
  63. _ = res.Body.Close()
  64. return err
  65. }(ctx)
  66. if err != nil {
  67. log.Fatal(err)
  68. }
  69. fmt.Printf("Response Received: %s\n\n\n", body)
  70. fmt.Printf("Waiting for few seconds to export spans ...\n\n")
  71. time.Sleep(10 * time.Second)
  72. fmt.Printf("Inspect traces on stdout\n")
  73. }

服务端代码:

  1. package main
  2. import (
  3. "context"
  4. "io"
  5. "log"
  6. "net/http"
  7. "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
  8. "go.opentelemetry.io/otel"
  9. "go.opentelemetry.io/otel/attribute"
  10. "go.opentelemetry.io/otel/baggage"
  11. stdout "go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
  12. "go.opentelemetry.io/otel/propagation"
  13. "go.opentelemetry.io/otel/sdk/resource"
  14. sdktrace "go.opentelemetry.io/otel/sdk/trace"
  15. semconv "go.opentelemetry.io/otel/semconv/v1.10.0"
  16. "go.opentelemetry.io/otel/trace"
  17. )
  18. func initTracer() (*sdktrace.TracerProvider, error) {
  19. // Create stdout exporter to be able to retrieve
  20. // the collected spans.
  21. exporter, err := stdout.New(stdout.WithPrettyPrint())
  22. if err != nil {
  23. return nil, err
  24. }
  25. // For the demonstration, use sdktrace.AlwaysSample sampler to sample all traces.
  26. // In a production application, use sdktrace.ProbabilitySampler with a desired probability.
  27. tp := sdktrace.NewTracerProvider(
  28. sdktrace.WithSampler(sdktrace.AlwaysSample()),
  29. sdktrace.WithBatcher(exporter),
  30. sdktrace.WithResource(resource.NewWithAttributes(semconv.SchemaURL, semconv.ServiceNameKey.String("ExampleService"))),
  31. )
  32. otel.SetTracerProvider(tp)
  33. otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}))
  34. return tp, err
  35. }
  36. func main() {
  37. tp, err := initTracer()
  38. if err != nil {
  39. log.Fatal(err)
  40. }
  41. defer func() {
  42. if err := tp.Shutdown(context.Background()); err != nil {
  43. log.Printf("Error shutting down tracer provider: %v", err)
  44. }
  45. }()
  46. uk := attribute.Key("username")
  47. helloHandler := func(w http.ResponseWriter, req *http.Request) {
  48. ctx := req.Context()
  49. span := trace.SpanFromContext(ctx) // span为Hello
  50. defer span.End()
  51. bag := baggage.FromContext(ctx)
  52. span.AddEvent("handling this...", trace.WithAttributes(uk.String(bag.Member("username").Value())))
  53. _, _ = io.WriteString(w, "Hello, world!\n")
  54. }
  55. // otelhttp.NewHandler会在处理请求的同时创建一个名为Hello的span
  56. otelHandler := otelhttp.NewHandler(http.HandlerFunc(helloHandler), "Hello")
  57. http.Handle("/hello", otelHandler)
  58. err = http.ListenAndServe(":7777", nil)
  59. if err != nil {
  60. log.Fatal(err)
  61. }
  62. }

上述代码生成的链路跟踪如下,client的HTTP GET会调用server端的Hello。Server的Hello span是在处理请求时生成的,上述用的是otelhttp,其他registry也是类似的处理方式。

使用如下代码则可以启动两个独立的span,可以表示两个并行的任务:

  1. helloHandler := func(w http.ResponseWriter, req *http.Request) {
  2. ctx := req.Context()
  3. ctx, span1 := tracer.Start(ctx, "span1 proecss", trace.WithLinks())
  4. defer span1.End()
  5. bag := baggage.FromContext(req.Context())
  6. span1.SetAttributes(attribute.String("span1", "test1"))
  7. span1.AddEvent("span1 handling this...", trace.WithAttributes(uk.String(bag.Member("username").Value())))
  8. ctx, span2 := tracer.Start(req.Context(), "span2 proecss", trace.WithLinks())
  9. defer span2.End()
  10. span2.SetAttributes(attribute.String("span2", "test2"))
  11. span2.AddEvent("span2 handling this...", trace.WithAttributes(uk.String(bag.Member("username").Value())))
  12. _, _ = io.WriteString(w, "Hello, world!\n")
  13. }

此外还可以通过baggage.NewKeyValueProperty("key", "value")等方式创建baggage。

注:baggage要遵循W3C Baggage 规范

支持otel的工具

官方给出了很多Registry,如Gorilla MuxGORMGin-gonicgRPC等。更多可以参见官方代码库

采样

  1. provider := sdktrace.NewTracerProvider(
  2. sdktrace.WithSampler(sdktrace.AlwaysSample()),
  3. )
  • AlwaysSample:采集每条链路信息
  • NeverSample :不采集
  • TraceIDRatioBased:按比例采集,即如果将其设置.5,则表示采集一半链路信息
  • ParentBased:根据传入的采样决策表现不同。通常会父span已采样的span进行采样,而不会对父span未采样的span进行采样。

生产中可以考虑使用TraceIDRatioBasedParentBased

参考

Opentelemetry SDK的简单用法的更多相关文章

  1. Android—— ListView 的简单用法及定制ListView界面

    一.ListView的简单用法 2. 训练目标 1) 掌握 ListView 控件的使用 2) 掌握 Adapter 桥梁的作用 实现步骤: 1)首先新建一个项目, 并让ADT 自动帮我们创建好活动. ...

  2. CATransition(os开发之画面切换) 的简单用法

    CATransition 的简单用法 //引进CATransition 时要添加包“QuartzCore.framework”,然后引进“#import <QuartzCore/QuartzCo ...

  3. jquery.validate.js 表单验证简单用法

    引入jquery.validate.js插件以及Jquery,在最后加上这个插件的方法名来引用.$('form').validate(); <!DOCTYPE html PUBLIC " ...

  4. NSCharacterSet 简单用法

    NSCharacterSet 简单用法 NSCharacterSet其实是许多字符或者数字或者符号的组合,在网络处理的时候会用到 NSMutableCharacterSet *base = [NSMu ...

  5. [转]Valgrind简单用法

    [转]Valgrind简单用法 http://www.cnblogs.com/sunyubo/archive/2010/05/05/2282170.html Valgrind的主要作者Julian S ...

  6. Oracle的substr函数简单用法

    substr(字符串,截取开始位置,截取长度) //返回截取的字 substr('Hello World',0,1) //返回结果为 'H'  *从字符串第一个字符开始截取长度为1的字符串 subst ...

  7. Ext.Net学习笔记19:Ext.Net FormPanel 简单用法

    Ext.Net学习笔记19:Ext.Net FormPanel 简单用法 FormPanel是一个常用的控件,Ext.Net中的FormPanel控件同样具有非常丰富的功能,在接下来的笔记中我们将一起 ...

  8. TransactionScope简单用法

    记录TransactionScope简单用法,示例如下: void Test() { using (TransactionScope scope = new TransactionScope()) { ...

  9. WPF之Treeview控件简单用法

    TreeView:表示显示在树结构中分层数据具有项目可展开和折叠的控件 TreeView 的内容是可以包含丰富内容的 TreeViewItem 控件,如 Button 和 Image 控件.TreeV ...

随机推荐

  1. 如何规避容器内做Java堆dump导致容器崩溃的问题

    写在前边 最近公司生产环境的容器云上出了个性能问题,为了做性能分析,使用 JDK 自带的 jmap 收集堆dump,出现了内存溢出导致了容器崩溃. 本篇文章将带你探究,如何规避容器内做堆 dump 导 ...

  2. python中一些列表知识

    列表 序列是 Python 中最基本的数据结构. 序列中的每个值都有对应的位置值,称之为索引,第一个索引是 0,第二个索引是 1,依此类推. Python 有 6 个序列的内置类型,但最常见的是列表和 ...

  3. Attention Mechanism in Computer Vision

    ​  前言 本文系统全面地介绍了Attention机制的不同类别,介绍了每个类别的原理.优缺点. 欢迎关注公众号CV技术指南,专注于计算机视觉的技术总结.最新技术跟踪.经典论文解读.CV招聘信息. 概 ...

  4. 将python脚本打包为exe可执行文件

    技术背景 在很多情况下,编程人员是在Linux环境下完成的编程任务,但是更多的使用人员是在Windows环境下的,比方说,在参考链接1的文章中提到: 那么我们就不得不考虑一个环境转化的问题.pytho ...

  5. WFP资源

    资源基础 WPF程序在代码中以及在标记中的各个位置定义资源,具有高效性.可维护性.适应性的优点. 资源的层次 <Windows.Resource> <ImageBrush x:key ...

  6. mybatis添加代码出现是第几条数据

  7. 2.1 安装Linux系统对硬件有什么要求?

    很多初学者在安装 Linux 系统时,都对自己的电脑配置存在质疑,担心其是否能够满足安装 Linux 的要求.本节就从 CPU.内存.硬盘.显卡等这些方面,详细介绍一下安装 Linux 系统的最低配置 ...

  8. 【LINT】cpplint修改版:自定义编码风格检查工具lint

    github:https://github.com/skullboyer/code-check Code Check 本仓介绍的内容涉及代码静态检查和编码风格检查 但主要放在编码风格检查,lint是基 ...

  9. JavaScript 任务池

    JavaScript 任务池 本文写于 2022 年 5 月 13 日 线程池 在多线程语言中,我们通常不会随意的在需要启动线程的时候去启动,而是会选择创建一个线程池. 所谓线程池,本意其实就是(不止 ...

  10. Ubuntu中安装redis

    第一种方式在线安装首要前提安装c语言编译环境,命令如下:$sudo apt-get install gcc 安装完成后可以输入$gcc --version查看版本 1.获取源码:$wget https ...