• 【云原生】Service服务暴露详细


    Service服务

    一、Service介绍

    1.1、介绍

    • Kubernetes中Service是将运行在一个或一组Pod上的网络用用程序公开为网络服务的方法。
    • Kubernetes中Service的一个关键目标是让你无需修改现有应用以使用某种不熟悉的服务发现机制。你可以在Pod集合中运行代码,无论该代码是为云原生环境设计的,还是被容器化的老应用。你可以使用Service让一个组Pod可以在网络上被访问,这样客户端就能与之交互。
    • 如果你使用Deployment来运行你的应用,Deployment可以动态地创建和销毁Pod。在任何时刻,你都不需要知道有多少个这样的Pod正在工作以及它们是否健康;你可能甚至不知道如何辨别健康的Pod。Kubernetes Pod的创建和销毁是为了匹配集群的预期状态。Pod是临时资源(你不应该期待单个Pod即可靠又耐用)。
    • 每个Pod会获得属于自己的IP地址(Kubernetes期待网络插件来保证这一点)。对于集群中给定的某个Deployment,这一刻运行的Pod集合可能不同于下一刻运行该应用的Pod集合。
    • 这就带来了一个问题:如果某组Pod(成为后端)为集群内的其他Pod(成为前端)集合提供功能,前端要如何发现并跟踪要连接的IP地址,以便使用负载的后端组件呢?

    1.2、Kubernetes中的Service

    • Service API是kubernetes的组成部分,它是一种抽象,邦族你将Pod集合在网络上公开出去。每个Service对象定义端点的一个逻辑集合(通常这些端点就是Pod)以及如何访问到这些Pod的策略。

    • 例如,开了一个无状态的图像处理端,其中运行3个副本(Replicas)。这些副本是可互换的——前端不需要关心它们调用的是哪个后端,即便构成后端集合的实际Pod可能会发生变化,前端客户端不应该也没有必要知道这些,而且它们也不必亲自跟踪后端的状态变化。

    • Service抽象使这种解耦成为可能。

    • Service对应的Pod集合通常由你定义的标签来确定。

    • 如果你的工作负载使用HTTP通信,你可能会选择使用Ingress来控制Web流量如何到达该工作负载。Ingress不是一种Service,但它可用作集群的入口点。Ingress能让你的路由规则整合到同一个资源内,这样你就能工作负载的多个组件公开出去,这些组件使用同一个侦听器,但各自独立运行在集群中。

    二、Service服务类型

    • 对一些应用的某些部分(如前端),你可能希望将其公开于某外部IP地址,也就是可以从集群外部访问的某个地址。
    • Kubernetes Service类型允许指定你所需要的Service类型。

    2.1、ClusterIP

    • 通过集群的内部IP公开Service,选择该值时Service只能够在集群内部访问。这也是你没有为Service显式指定type时使用的默认值。你可以使用Ingress或者Gateway API向公共互联网公开服务。
      • 此默认Service类型从你的集群中为此预留的IP地址池中分配一个IP地址。
      • 其他几种Service类型在ClusterIP类型的基础上进行构建。
      • 如果你定义的Service将.spec.clusterIP设置为"None"则Kubernetes不会为其分配IP地址。

    2.2、NodePort

    • 通过每个节点上的IP和静态端口(NodePort)公开Service。为了让Service可通过节点端口访问,Kubernetes会为Service配置集群IP地址,相当于你请求了type: ClusterIP的Service。
      • 如果你将type字段设置为NodePort,则Kubernetes控制平面将在--service-node-port-range标志所指定的范围内分配端口(默认值:30000-32767)每个节点将该端口(每个节点上的相同端口号)上的流量代理到你的Service。你的Service在其.spec.ports[*].nodePort字段中指定分配的对外访问的端口。
      • 使用NodePort可以让你自由设置自己的负载均衡解决方案,配置Kubernetes不完全支持你的环境,甚至直接公开一个或多个节点的IP地址。
      • 对于NodePort类型Service,Kubernetes额外分配一个端口(TCP、UDP或者其他匹配Service的协议)。集群中的每个节点都将自己配置为监听所分配的端口,并将流量转发到与该Service关联的某个就绪端点。通过使用合适的协议(例如TCP)和适当的端口(分配给该Service)连接到任何一个接待你,你就能够从集群外部访问type: NodePort服务

    2.3、LadBalancer

    • 使用云平台的负载均衡器向外公开Service。kubernetes不直接提供负载均衡组件;你必须提供一个,或者将你的Kubernetes集群与某个云平台集成。
      • 在使用支持外部负载均衡器的云平台时,如果将type设置为"LadBanlancer",则平台会为Service提供负载均衡器。负载均衡器的实际创建过程是异步进行的,关于所制备的负载均衡器的信息将会通过Service的status.loadBalancer字段公开出来

    2.4、ExternalName

    • 将服务映射到externalName字段的内容(例如,映射到主机名api.foo.bar.example)。该映射将集群的DNS服务器配置为返回具有该外部主机名值得CNAME记录。集群不会为之创建任何类型代理。
      • 类型为ExternalName的Service将Service映射到DNS名称,而不是典型的选择算符。

    三、Service玩法

    • Kubernetes中的Service是一个对象(与Pod或ConfigMap类似)。你可以使用Kubernetes API创建,查看你或修改Service定义。通常你会使用kubectl这类工具来替你发起这些API调用。

    3.1、定义Service

    • 例如,假定有一组Pod,每个Pod都在侦听TCP端口9376,并且它们还被打上app.kubernetes.io/name=MyApp标签。你可以定义一个Service来发布该TCP侦听器。
    [root@master ~]# vim service.yaml
    apiVersion: "v1"
    kind: Service
    metadata:
      name: my-service
    spec:
      # 标签选择器,意味着将会把流量路由到带有此标签的Pod上
      selector:
        app.kubernetes.io/name: MyApp
      ports:  # ports定义Service暴露的端口信息
        - name: http  # 为端口起个名字,可以清晰标识每个端口的作用
          protocol: TCP  # 指定了端口使用的协议,这里指定的是TCP
          port: 80   # 定义Service对外暴露的端口
          targetPort: 9376  # 定义如果访问到Service对外暴露的80端口,流量将会被路由到9376容器侦听端口上
          
          
          
          
    # 应用上述清单时,系统将创建一个名为“my-service”的、服务类型默认为ClusterIP的Service。该Service指向带有标签"app.kubernetes.io/name: MyApp"的所有Pod的TCP端口9376
    # Kubernetes为该Service分配一个IP地址(称为集群IP),供虚拟IP地址机制使用
    # 此Service的控制器会不断操作与其选择算符匹配的Pod集合,然后对Service的EndpointSlice集合执行必要的更新。
    # Service对象的名称必须是有效的RFC 1035标签名称
    # 说明:Service能够任意入站,port映射到某个targetPort。默认情况下,出于方便考虑,targetPort会被设置为与port字段相同的值
    
    # 加载资源
    [root@master ~]# kubectl apply -f service.yaml
    
    # 你可以使用此命令查看刚刚创建的Service对象
    [root@master ~]# kubectl get svc my-service
    NAME         TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
    my-service   ClusterIP   10.96.239.56   <none>        80/TCP    30m
    

    3.2、端口定义别名

    • Pod中的端口定义是有名字的,你可以在Service的targetPort属性中引用这些名字。例如,我们可以通过以下方式将Service的targetPort绑定到Pod端口:
    [root@master ~]# vim service.yaml
    apiVersion: "v1"
    kind: Pod
    metadata:
      name: nginx
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:latest
        imagePullPolicy: IfNotPresent
        ports:  # 定义容器要暴露的端口
          - containerPort: 80  # 指定容器内部监听的端口
            name: http-web-svc # 为这个端口指定一个名称,通过端口名称可以更灵活引用端口
    
    ---
    
    apiVersion: "v1"
    kind: Service
    metadata:
      name: nginx-service
    spec:
      selector:  # 标签选择器
        app: nginx # Service会把流量路由到带有app=nginx标签的Pod上
      ports:
      - name: name-of-service-port # 定义service端口名称
        protocol: TCP  # 指定网络协议
        port: 80   # 定义service的端口号(对外暴露的端口)
        targetPort: http-web-svc  # 指定Pod中监听端口名称
        
        
        
    # 即使在Service中混合使用配置名称相同的多个Pod,各Pod通过不同的端口号支持相同的网络协议,此机制也可以工作。这一机制为Service的部署和演化提供了较高的灵活性。例如,你可以在后端软件新版本中更改Pod的公开端口号,但不会影响到客户端。
    # Service的默认协议是TCP;你还可以使用其他受支持的任何协议
    # 由于许多Service需要公开多个端口,所以Kubernetes为同一Service定义多个端口。每个端口定义可以具有相同的protocol,也可以具有不同协议。
    
    # 创建资源
    [root@master ~]# kubectl apply -f service.yaml
    
    # 查看pod和service资源
    [root@master ~]# kubectl get pod,svc
    NAME        READY   STATUS    RESTARTS   AGE
    pod/nginx   1/1     Running   0          26m
    
    NAME                    TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
    service/kubernetes      ClusterIP   10.96.0.1      <none>        443/TCP   5d12h
    service/nginx-service   ClusterIP   10.109.37.65   <none>        80/TCP    26m
    
    # 你可以通过刚刚查看到service的集群IP地址访问后端Pod
    [root@master ~]# curl 10.109.37.65
    <!DOCTYPE html>
    <html>
    <head>
    <title>Welcome to nginx!</title>
    <style>
    html { color-scheme: light dark; }
    body { width: 35em; margin: 0 auto;
    font-family: Tahoma, Verdana, Arial, sans-serif; }
    </style>
    </head>
    <body>
    <h1>Welcome to nginx!</h1>
    <p>If you see this page, the nginx web server is successfully installed and
    working. Further configuration is required.</p>
    
    <p>For online documentation and support please refer to
    <a href="http://nginx.org/">nginx.org</a>.<br/>
    Commercial support is available at
    <a href="http://nginx.com/">nginx.com</a>.</p>
    
    <p><em>Thank you for using nginx.</em></p>
    </body>
    </html>
    

    3.3、多端口Service

    • 对于某些Service,你需要公开多个端口。Kubernetes允许你为Service对象配置多个端口定义。为Service使用多个端口时,必须为所有端口提供名称,以使它们无歧义。
    [root@master ~]# vim service.yaml
    apiVersion: "v1"
    kind: Pod
    metadata:
      name: nginx
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:latest
        imagePullPolicy: IfNotPresent
        ports:  # 定义容器要暴露的端口
          - containerPort: 80  # 指定容器内部监听的端口
            name: http-web-svc # 为这个端口指定一个名称,通过端口名称可以更灵活引用端口
    
    ---
    
    apiVersion: "v1"
    kind: Service
    metadata:
      name: nginx-service
    spec:
      selector:  # 标签选择器
        app: nginx # Service会把流量路由到带有app=nginx标签的Pod上
      ports:
      - name: http # 定义service端口名称
        protocol: TCP  # 指定网络协议
        port: 80   # 定义service的端口号(对外暴露的端口)
        targetPort: http-web-svc  # 指定Pod中监听端口名称
      - name: https 
        protocol: TCP
        port: 443  # Service对外暴露的端口
        targetPort: 80 # 此处直接指定了Pod内部暴露的端口
    
    # 创建资源
    [root@master ~]# kubectl apply -f service.yaml
    
    # 查看Pod和Service资源
    [root@master ~]# kubectl get pod,svc
    NAME        READY   STATUS    RESTARTS   AGE
    pod/nginx   1/1     Running   0          36s
    
    NAME                    TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE
    service/kubernetes      ClusterIP   10.96.0.1        <none>        443/TCP          5d12h
    service/nginx-service   ClusterIP   10.105.238.225   <none>        80/TCP,443/TCP   36s
    
    # 你可以通过刚刚查看到service的集群IP地址访问后端Pod
    
    # 访问http
    [root@master ~]# curl 10.105.238.225:80
    <!DOCTYPE html>
    <html>
    <head>
    <title>Welcome to nginx!</title>
    <style>
    html { color-scheme: light dark; }
    body { width: 35em; margin: 0 auto;
    font-family: Tahoma, Verdana, Arial, sans-serif; }
    </style>
    </head>
    <body>
    <h1>Welcome to nginx!</h1>
    <p>If you see this page, the nginx web server is successfully installed and
    working. Further configuration is required.</p>
    
    <p>For online documentation and support please refer to
    <a href="http://nginx.org/">nginx.org</a>.<br/>
    Commercial support is available at
    <a href="http://nginx.com/">nginx.com</a>.</p>
    
    <p><em>Thank you for using nginx.</em></p>
    </body>
    </html>
    
    
    # 访问https
    [root@master ~]# curl 10.105.238.225:443
    <!DOCTYPE html>
    <html>
    <head>
    <title>Welcome to nginx!</title>
    <style>
    html { color-scheme: light dark; }
    body { width: 35em; margin: 0 auto;
    font-family: Tahoma, Verdana, Arial, sans-serif; }
    </style>
    </head>
    <body>
    <h1>Welcome to nginx!</h1>
    <p>If you see this page, the nginx web server is successfully installed and
    working. Further configuration is required.</p>
    
    <p>For online documentation and support please refer to
    <a href="http://nginx.org/">nginx.org</a>.<br/>
    Commercial support is available at
    <a href="http://nginx.com/">nginx.com</a>.</p>
    
    <p><em>Thank you for using nginx.</em></p>
    </body>
    </html>
    

    四、Service类型

    • 常用的Service类型有三种

    4.1、Cluster IP类型

    • 这种类型的Service只会得到虚拟IP和端口,只能在Kubernetes集群内部被访问,此模型为默认类型。
    [root@master ~]# vim service_clusterip.yaml
    apiVersion: "apps/v1"
    kind: Deployment
    metadata:
      name: nginx
      labels:
        app: nginx
    spec:
      replicas: 2
      selector:
        matchLabels:
          app: nginx
      template:
        metadata:
          labels:
            app: nginx
        spec:
          containers:
          - name: test-nginx
            image: nginx:latest
            imagePullPolicy: IfNotPresent
            ports:
            - containerPort: 80
    
    ---
    
    apiVersion: "v1"
    kind: Service
    metadata:
      name: nginx
    spec:
      selector:  # 标签选择器
        app: nginx   # 指定后端服务Pod,把流量路由到带有app=nginx的后端Pod上
      type: ClusterIP
      ports:
      - port: 80  # 对外暴露80端口
        targetPort: 80  # 此处填写容器真实暴露出来的端口,流量将会被路由到这个端口上
    
    # 创建资源
    [root@master ~]# kubectl apply -f service_clusterip.yaml
    
    # 查看Pod和Service资源
    [root@master ~]# kubectl get pod,svc
    NAME                         READY   STATUS    RESTARTS   AGE
    pod/nginx-585dbbb7c7-hq98j   1/1     Running   0          115s
    pod/nginx-585dbbb7c7-t6dh7   1/1     Running   0          115s
    
    NAME                 TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
    service/kubernetes   ClusterIP   10.96.0.1      <none>        443/TCP   5d13h
    service/nginx        ClusterIP   10.107.19.45   <none>        80/TCP    115s
    
    # 你可以通过刚刚查看到service的集群IP地址访问后端Pod
    # ClusterIP类型只能在集群内部访问
    [root@master ~]# curl 10.107.19.45
    <!DOCTYPE html>
    <html>
    <head>
    <title>Welcome to nginx!</title>
    <style>
    html { color-scheme: light dark; }
    body { width: 35em; margin: 0 auto;
    font-family: Tahoma, Verdana, Arial, sans-serif; }
    </style>
    </head>
    <body>
    <h1>Welcome to nginx!</h1>
    <p>If you see this page, the nginx web server is successfully installed and
    working. Further configuration is required.</p>
    
    <p>For online documentation and support please refer to
    <a href="http://nginx.org/">nginx.org</a>.<br/>
    Commercial support is available at
    <a href="http://nginx.com/">nginx.com</a>.</p>
    
    <p><em>Thank you for using nginx.</em></p>
    </body>
    </html>
    [root@master ~]# curl 10.107.19.45
    <!DOCTYPE html>
    <html>
    <head>
    <title>Welcome to nginx!</title>
    <style>
    html { color-scheme: light dark; }
    body { width: 35em; margin: 0 auto;
    font-family: Tahoma, Verdana, Arial, sans-serif; }
    </style>
    </head>
    <body>
    <h1>Welcome to nginx!</h1>
    <p>If you see this page, the nginx web server is successfully installed and
    working. Further configuration is required.</p>
    
    <p>For online documentation and support please refer to
    <a href="http://nginx.org/">nginx.org</a>.<br/>
    Commercial support is available at
    <a href="http://nginx.com/">nginx.com</a>.</p>
    
    <p><em>Thank you for using nginx.</em></p>
    </body>
    </html>
    

    4.2、NodePort类型

    • 这种类型的Service除了会得到虚拟IP地址和端口,Kubernetes还会再所有Node节点上为其分配端口,分配端口的值可以通过.spec.ports.nodePort指定,或由Kubernetes再配置好的池里面分配(默认为30000-32767)即可从Kubernetes集群通过虚拟IP:端口访问也可以从集群外部通过Node节点的IP:nodePort访问
    # 以下是type: NodePort服务的一个清单示例,其中指定了NodePort值(本次案例为30007)
    [root@master ~]# vim service_nodePort.yaml
    apiVersion: "apps/v1"
    kind: Deployment
    metadata:
      name: my-nginx
      labels:
        run: my-nginx
    spec:
      selector:
        matchLabels:
          run: my-nginx
      replicas: 2
      template:
        metadata:
          labels:
            run: my-nginx
        spec:
          containers:
          - name: my-nginx
            image: nginx:latest
            imagePullPolicy: IfNotPresent
            ports:
            - containerPort: 80
    
    ---
    
    apiVersion: "v1"
    kind: Service
    metadata:
      name: my-nginx
      labels:
        run: my-nginx
    spec:
      selector:
        run: my-nginx # 定义标签选择器,将会把流量路由到带有run=my-nginx标签的pod上的端口上 
      type: NodePort
      ports:
      # 默认情况下,为了方便起见,'targetPort'被设置为与'port'字段相同的值
      - port: 80
        protocol: TCP
        targetPort: 80
        # nodePort可选字段
        # 默认情况下,为了方便起见,kubernetes控制平面会从某个范围内分配一个端口号
        # (默认情况:30000-32767)
        nodePort: 30007
    
    # 创建资源
    [root@master ~]# kubectl apply -f service_nodePort.yaml 
    
    # 查看Pod和Service资源
    # nodePor类型的Service可以从集群外部访问
    [root@master ~]# kubectl get pod,svc
    NAME                            READY   STATUS    RESTARTS   AGE
    pod/my-nginx-597fd96b76-hqvcv   1/1     Running   0          85s
    pod/my-nginx-597fd96b76-vv8p9   1/1     Running   0          85s
    
    NAME                 TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
    service/kubernetes   ClusterIP   10.96.0.1       <none>        443/TCP        5d13h
    service/my-nginx     NodePort    10.107.85.136   <none>        80:30007/TCP   85s
    
    # 你可以通过刚刚查看到service的集群IP地址访问后端Pod
    # 访问的时候集群任意节点IP地址加上暴露出去的NodePort端口
    C:\Users\Lenovo>curl http://192.168.93.145:30007
    <!DOCTYPE html>
    <html>
    <head>
    <title>Welcome to nginx!</title>
    <style>
    html { color-scheme: light dark; }
    body { width: 35em; margin: 0 auto;
    font-family: Tahoma, Verdana, Arial, sans-serif; }
    </style>
    </head>
    <body>
    <h1>Welcome to nginx!</h1>
    <p>If you see this page, the nginx web server is successfully installed and
    working. Further configuration is required.</p>
    
    <p>For online documentation and support please refer to
    <a href="http://nginx.org/">nginx.org</a>.<br/>
    Commercial support is available at
    <a href="http://nginx.com/">nginx.com</a>.</p>
    
    <p><em>Thank you for using nginx.</em></p>
    </body>
    </html>
    

    4.3、ExternalName类型Service简介

    • externalName Service是K8S中一个特俗的service类型,它不需要指定selector去选择哪些Pod实例提供服务,而是使用DNS CNAME机制把自己CNAME到你指定的另外一个域名上,你可以提供集群内的名字,比如mysql.db.svc这样的简历在db名称空间内的mysql服务,也可以指定http:///mysql.example.com这样的外部真实域名。
    # 定义ExternalName类型的Service
    
    apiVersion: "v1"
    kind: Namespace
    metadata:
      name: dev
    
    ---
    
    apiVersion: "v1"
    kind: Pod
    metadata:
      name: nginx
      namespace: dev
    spec:
      containers:
      - name: nginx
        image: nginx:latest
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 80
    
    ---
    
    apiVersion: "v1"
    kind: Service
    metadata:
      name: search
      namespace: dev
    spec:
      type: ExternalName
      externalName: www.baidu.com   # 访问这个特定的service路径将会访问到www.baidu.com
    
    # 创建资源
    [root@master ~]# kubectl apply -f externalname-nginx.yaml
    
    # 查看Pod和Service资源
    [root@master ~]# kubectl get pod,svc -n dev
    NAME        READY   STATUS    RESTARTS   AGE
    pod/nginx   1/1     Running   0          6s
    
    NAME             TYPE           CLUSTER-IP   EXTERNAL-IP     PORT(S)   AGE
    service/search   ExternalName   <none>       www.baidu.com   <none>    4m26s
    
    # 此时,在集群内部的Pod就可以通过search.dev.svc.cluster.local访问www.baidu.comle
    # 下面验证一下,首先查看K8s内部使用的DNS地址
    [root@master ~]# kubectl exec -it -n dev nginx -- cat /etc/resolv.conf
    nameserver 10.96.0.10   # 这个就是k8s内部的DNS地址
    search dev.svc.cluster.local svc.cluster.local cluster.local
    options ndots:5
    
    # 然后使用dig命令验证
    [root@master ~]# yum -y install bind-utils-9.11.4-26.P2.el7_9.16.x86_64
    [root@master ~]# dig @10.96.0.10 search.dev.svc.cluster.local
    
    ; <<>> DiG 9.11.4-P2-RedHat-9.11.4-26.P2.el7_9.16 <<>> @10.96.0.10 search.dev.svc.cluster.local
    ; (1 server found)
    ;; global options: +cmd
    ;; Got answer:
    ;; WARNING: .local is reserved for Multicast DNS
    ;; You are currently testing what happens when an mDNS query is leaked to DNS
    ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 44519
    ;; flags: qr aa rd; QUERY: 1, ANSWER: 4, AUTHORITY: 0, ADDITIONAL: 1
    ;; WARNING: recursion requested but not available
    
    ;; OPT PSEUDOSECTION:
    ; EDNS: version: 0, flags:; udp: 4096
    ;; QUESTION SECTION:
    ;search.dev.svc.cluster.local.	IN	A
    
    ;; ANSWER SECTION:
    search.dev.svc.cluster.local. 30 IN	CNAME	www.baidu.com.
    www.baidu.com.		30	IN	CNAME	www.a.shifen.com.
    www.a.shifen.com.	30	IN	A	220.181.38.150
    www.a.shifen.com.	30	IN	A	220.181.38.149
    
    ;; Query time: 57 msec
    ;; SERVER: 10.96.0.10#53(10.96.0.10)
    ;; WHEN: 一 8月 05 22:51:59 CST 2024
    ;; MSG SIZE  rcvd: 219
    
  • 相关阅读:
    C++--二叉搜索树初阶
    (min,max)=>Math.floor(Math.random()*(max-min+1)+min
    GEE python——将GEE ASSETS中存储的影像或者矢量转化为数据格式XEE()
    软考 网工 每日学习打卡 2024/3/19
    实现单行/多行文本溢出
    Redis-使用java代码操作Redis
    服装公司事务/办理流程是如何办理的?
    ArcGIS制作规划图卫星影像地图虚化效果
    最新AI智能写作系统ChatGPT源码/支持GPT4.0+GPT联网提问/支持ai绘画Midjourney+Prompt+MJ以图生图+思维导图生成
    linux-ARM下的数据库管理工具的安装使用(dbeaver)
  • 原文地址:https://blog.csdn.net/weixin_73059729/article/details/140988296