在介绍微服务时,首先得先理解什么是微服务,顾名思义,微服务得从两个方面去理解,什么是"微"、什么是"服务", 微 狭义来讲就是体积小、单个服务的设计。 而所谓服务,一定要区别于系统,服务一个或者一组相对较小且独立的功能单元,是用户可以感知最小功能集。
微服务,关键其实不仅仅是微服务本身,而是系统要提供一套基础的架构,这种架构使得微服务可以独立的部署、运行、升级,不仅如此,这个系统架构还让微服务与微服务之间在结构上“松耦合”,而在功能上则表现为一个统一的整体。这种所谓的“统一的整体”表现出来的是统一风格的界面,统一的权限管理,统一的安全策略,统一的上线过程,统一的日志和审计方法,统一的调度方式,统一的访问入口等等。
微服务最早由Martin Fowler与James Lewis于2014年共同提出,微服务架构风格是一种使用一套小服务来开发单个应用的方式途径,每个服务运行在自己的进程中,并使用轻量级机制通信,通常是HTTP API,这些服务基于业务能力构建,并能够通过自动化部署机制来独立部署,这些服务使用不同的编程语言实现,以及不同数据存储技术,并保持最低限度的集中式管理。
IT架构一直从all in one到近两年热门的微服务架构,技术不断进步,微服务架构模式(Microservice Architect Pattern)开始被越来越多的企业所接受,那么究竟什么是微服务架构?微服务架构模式有什么优点呢?
从整个IT技术发展趋势来看,我们可以看到无论是硬件、还是软件、还是基础架构都在朝着轻量化的方向发展。云计算的发展更让资源的调控灵活性和部署速度都有所提高,微服务就是一项在云中部署应用和服务的技术。采用化整为零的概念,将复杂的IT部署,通过功能化、原子化分解,形成一种松散耦合的组件,让其更容易升级和扩展。
ThoughtWorks的首席科学家,马丁·福勒先生对微服务做出了这样的定义:“微服务架构是一种架构模式,它提倡将单一应用程序划分成一组小的服务,服务之间互相协调、互相配合,为用户提供最终价值。每个服务运行在其独立的进程中,服务与服务间采用轻量级的通信机制互相沟通(通常是基于HTTP协议的RESTful API)。每个服务都围绕着具体业务进行构建,并且能够被独立的部署到生产环境、类生产环境等。另外,应当尽量避免统一的、集中式的服务管理机制,对具体的一个服务而言,应根据业务上下文,选择合适的语言、工具对其进行构建。”
微服务架构是一项在云中部署应用和服务的技术
总的来说,可以将微服务架构的优势归结为以下几点:
1、复杂度可控
在all in one的状态下,容易造成盲人摸象的状态,造成不必要的数据孤岛。而微服务架构通过分解单体式应用为多个服务方法,让复杂性可控。为了实现同一功能,应用被分解为多个可管理的分支或服务,通过微服务架构模式,让复杂的功能,通过模块化的方式呈现出来,让单个服务更容易开发和维护。
避免“盲人摸象”
2、灵活可扩展
灵活性是基于微服务架构模式使得每个服务独立扩展。微服务架构下,技术选型是去中心化的。在这种模式下,每个团队都可以根据自身服务的需求和行业发展状况做出自己的判断,选择适合的技术栈。
3、独立部署
由于微服务具备独立的运行进程,所以每个微服务也可以独立部署。这样,当某个微服务发生变更时无需编译、部署整个应用,让发布更高效,右下缩短应用交付周期。UI团队可以采用AB测试,快速的部署变化。微服务架构模式使得持续化部署成为可能。
4、开发针对性更强
众所周知,在单块架构系统下,新人的培养周期很长,需要花费大量时间了解本地开发环境。而微服务架构模式使得每个服务独立扩展,开发运维人员也不需要在花费一个月的时间去熟悉本地环境,而只需要了解自己所处的模块状态即可。
5、降低TCO
在传统IT架构中,即单块架构系统中,是以技术分层,譬如逻辑层、数据层等。但随着市场需求的不断变化,用户需求住家个性化,开发周期需要越来越短,产品的生命周期也开始变短,单块架构系统开始面临挑战。无论是开发还是维护成本太高。
相较而言,微服务架构模式下,当某一组件发生故障时,不会发现单块架构系统的进程内扩散等弊端,故障会被隔离在单个服务中。
Docker 是一个容器工具,提供虚拟环境。docker改变了我们对软件的认识。
站在 Docker 的角度,软件就是容器的组合:业务逻辑容器、数据库容器、储存容器、队列容器......Docker 使得软件可以拆分成若干个标准化容器,然后像搭积木一样组合起来。
这正是微服务(microservices)的思想:软件把任务外包出去,让各种外部服务完成这些任务,软件本身只是底层服务的调度中心和组装层。
微服务很适合用 Docker 容器实现,每个容器承载一个服务。一台计算机同时运行多个容器,从而就能很轻松地模拟出复杂的微服务架构。
#第一行必须指令基于的基础镜像
From ubutu
#维护者信息
MAINTAINER docker\_user docker\_user@mail.com
#镜像的操作指令
RUN apt-get update && apt-get install -y ngnix
RUN echo "\ndaemon off;">>/etc/ngnix/nignix.conf
#容器启动时执行指令
CMD /usr/sbin/ngnix
介绍一下一些常用的命令:
From 或者From :
DockerFile第一条必须为From指令。如果同一个DockerFile创建多个镜像时,可使用多个From指令(每个镜像一次)
格式为maintainer ,指定维护者的信息
格式为Run 或者Run [“executable” ,”Param1”, “param2”]
前者在shell终端上运行,即/bin/sh -C,后者使用exec运行。例如:RUN [“/bin/bash”, “-c”,”echo hello”]
每条run指令在当前基础镜像执行,并且提交新镜像。当命令比较长时,可以使用“/”换行。
支持三种格式:
CMD [“executable” ,”Param1”, “param2”]使用exec执行,推荐
CMD command param1 param2,在/bin/sh上执行
CMD [“Param1”, “param2”] 提供给ENTRYPOINT做默认参数。
每个容器只能执行一条CMD命令,多个CMD命令时,只最后一条被执行。
格式为 EXPOSE […] 。
告诉Docker服务端容器暴露的端口号,供互联系统使用。在启动Docker时,可以通过-P,主机会自动分配一个端口号转发到指定的端口。使用-P,则可以具体指定哪个本地端口映射过来
例如:
EXPOSE 22 80 8443
格式为 ENV 。 指定一个环境变量,会被后续 RUN 指令使用,并在容器运行时保持。
ENV PG\_MAJOR 9.3
ENV PG\_VERSION 9.3.4
RUN curl -SL http://example.com/postgres-$PG\_VERSION.tar.xz | tar -xJC /usr/src/postgress && …
ENV PATH /usr/local/postgres-$PG\_MAJOR/bin:$PATH1234
格式为 ADD 。
该命令将复制指定的 到容器中的 。 其中 可以是Dockerfile所在目录的一个相对路径;也可以是一个URL;还可以是一个tar文件(自动解压为目录)。则。
格式为 COPY 。
复制本地主机的 (为Dockerfile所在目录的相对路径)到容器中的 。
当使用本地目录为源目录时,推荐使用 COPY 。
两种格式:
ENTRYPOINT [“executable”, “param1”, “param2”]
ENTRYPOINT command param1 param2 (shell中执行)。
配置容器启动后执行的命令,并且不可被 docker run 提供的参数覆盖。
每个Dockerfile中只能有一个 ENTRYPOINT ,当指定多个时,只有最后一个起效。
格式为 VOLUME [“/data”] 。
创建一个可以从本地主机或其他容器挂载的挂载点,一般用来存放数据库和需要保持的数据等。
格式为 USER daemon 。
指定运行容器时的用户名或UID,后续的 RUN 也会使用指定用户。
当服务不需要管理员权限时,可以通过该命令指定运行用户。并且可以在之前创建所需要的用户,例如: RUN groupadd -r postgres && useradd -r -g postgres postgres 。要临时获取管理员权限可以使用 gosu ,而不推荐 sudo 。
格式为 WORKDIR /path/to/workdir 。
为后续的 RUN 、 CMD 、 ENTRYPOINT 指令配置工作目录。
可以使用多个 WORKDIR 指令,后续命令如果参数是相对路径,则会基于之前命令指定的路径。例如
WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd
则最终路径为 /a/b/c 。
格式为 ONBUILD [INSTRUCTION] 。
配置当所创建的镜像作为其它新创建镜像的基础镜像时,所执行的操作指令。
例如,Dockerfile使用如下的内容创建了镜像 image-A 。
[…]
ONBUILD ADD . /app/src
ONBUILD RUN /usr/local/bin/python-build –dir /app/src
[…]
如果基于A创建新的镜像时,新的Dockerfile中使用 FROM image-A 指定基础镜像时,会自动执行 ONBUILD 指令内容,等价于在后面添加了两条指令。
FROM image-A
#Automatically run the following
ADD . /app/src
RUN /usr/local/bin/python-build --dir /app/src
使用 ONBUILD 指令的镜像,推荐在标签中注明,例如 ruby:1.9-onbuild 。
1234567
编排(orchestration),指自动配置、协作和管理服务的过程,在 Docker 中,编排用来描述一组实践过程,这个过程会管理运行在多个 Docker 里的应用,这些 Docker 容器也可能运行在不同的宿主机上。
Docker 编排工具 Docker Compose ,由 Python 编写。使用 Docker Compose ,可以用一个 YAML 文件定义一组要启动的容器,以及容器运行时的属性。Docker Compose 称这些容器为“服务”:
容器通过某些方法并制定一些运行时的属性来和其他容器产生交互。
默认的模板文件是 docker-compose.yml,其中定义的每个服务都必须通过 image 指令指定镜像或 build 指令(需要 Dockerfile)来自动构建。
其它大部分指令都跟 docker run 中的类似。
如果使用 build 指令,在 Dockerfile 中设置的选项(例如:CMD, EXPOSE, VOLUME, ENV 等) 将会自动被获取,无需在 docker-compose.yml 中再次设置。
image
指定为镜像名称或镜像 ID。如果镜像在本地不存在,Compose 将会尝试拉去这个镜像。
先看例子:
version: '2'
services:
web:
image: dockercloud/hello-world
ports:
- 8080
networks:
- front-tie
- back-tie
redis:
image: redis
links:
- web
networks:
- back-tie
lb:
image: dockercloud/haproxy
ports:
- 80:80
links:
- web
networks:
- front-tie
- back-tie
volumes:
- /var/run/docker.sock:/var/run/docker.sock
networks:
front-tier:
driver: bridge
back-tier:
driver: bridge
versionn, services、networks 三大部分,其中最关键的就是 services 和 networks 两个部分,下面先来看 services 的书写规则。
services:
web:
image: hello-world
在 services 标签下的第二级标签是 web,这个名字是用户自己自定义,它就是服务名称。
image 则是指定服务的镜像名称或镜像 ID。如果镜像在本地不存在,Compose 将会尝试拉取这个镜像。
例如下面这些格式都是可以的:
image: redis
image: ubuntu:14.04
image: tutum/influxdb
image: example-registry.com:4000/postgresql
image: a4bc65fd
服务除了可以基于指定的镜像,还可以基于一份 Dockerfile,在使用 up 启动之时执行构建任务,这个构建标签就是 build,它可以指定 Dockerfile 所在文件夹的路径。Compose 将会利用它自动构建这个镜像,然后使用这个镜像启动服务容器。
build: /path/to/build/di
也可以是相对路径,只要上下文确定就可以读取到 Dockerfile。
build: ./di
设定上下文根目录,然后以该目录为准指定 Dockerfile。
build:
context: ../
dockerfile: path/of/Dockerfile
注意 build 都是一个目录,如果你要指定 Dockerfile 文件需要在 build 标签的子级标签中使用 dockerfile 标签指定,如上面的例子。
如果你同时指定了 image 和 build 两个标签,那么 Compose 会构建镜像并且把镜像命名为 image 后面的那个名字。
build: ./di
image: webapp:tag
既然可以在 docker-compose.yml 中定义构建任务,那么一定少不了 arg 这个标签,就像 Dockerfile 中的 ARG 指令,它可以在构建过程中指定环境变量,但是在构建成功后取消,在 docker-compose.yml 文件中也支持这样的写法:
build:
context: .
args:
buildno: 1
password: secret
下面这种写法也是支持的,一般来说下面的写法更适合阅读。
build:
context: .
args:
- buildno=1
- password=secret
与 ENV 不同的是,ARG 是允许空值的。例如:
args:
- buildno
- password
这样构建过程可以向它们赋值。
注意:YAML 的布尔值(true, false, yes, no, on, off)必须要使用引号引起来(单引号、双引号均可),否则会当成字符串解析。
使用 command 可以覆盖容器启动后默认执行的命令。
command: bundle exec thin -p 3000
也可以写成类似 Dockerfile 中的格式:
command: [bundle, exec, thin, -p, 3000]
前面说过 Compose 的容器名称格式是:<项目名称>*<服务名称>*<序号>
虽然可以自定义项目名称、服务名称,但是如果你想完全控制容器的命名,可以使用这个标签指定:
container\_name: app
这样容器的名字就指定为 app 了。
在使用 Compose 时,最大的好处就是少打启动命令,但是一般项目容器启动的顺序是有要求的,如果直接从上到下启动容器,必然会因为容器依赖问题而启动失败。
例如在没启动数据库容器的时候启动了应用容器,这时候应用容器会因为找不到数据库而退出,为了避免这种情况我们需要加入一个标签,就是 depends_on,这个标签解决了容器的依赖、启动先后的问题。
例如下面容器会先启动 redis 和 db 两个服务,最后才启动 web 服务:
version: '2'
services:
web:
build: .
depends\_on:
- db
- redis
redis:
image: redis
db:
image: postgres
注意的是,默认情况下使用 docker-compose up web 这样的方式启动 web 服务时,也会启动 redis 和 db 两个服务,因为在配置文件中定义了依赖关系。
和 --dns 参数一样用途,格式如下:
dns: 8.8.8.8
也可以是一个列表:
dns:
- 8.8.8.8
- 9.9.9.9
此外 dns_search 的配置也类似:
dns\_search: example.com
dns\_search:
- dc1.example.com
- dc2.example.com
挂载临时目录到容器内部,与 run 的参数一样效果:
tmpfs: /run
tmpfs:
- /run
- /tmp
在 Dockerfile 中有一个指令叫做 ENTRYPOINT 指令,用于指定接入点,第四章有对比过与 CMD 的区别。
在 docker-compose.yml 中可以定义接入点,覆盖 Dockerfile 中的定义:
entrypoint: /code/entrypoint.sh
格式和 Docker 类似,不过还可以写成这样:
entrypoint:
- php
- -d
- zend\_extension=/usr/local/lib/php/extensions/no-debug-non-zts-20100525/xdebug.so
- -d
- memory\_limit=-1
- vendor/bin/phpunit
还记得前面提到的 .env 文件吧,这个文件可以设置 Compose 的变量。而在 docker-compose.yml 中可以定义一个专门存放变量的文件。
如果通过 docker-compose -f FILE 指定了配置文件,则 env_file 中路径会使用配置文件路径。
如果有变量名称与 environment 指令冲突,则以后者为准。格式如下:
env\_file: .env
或者根据 docker-compose.yml 设置多个:
env\_file:
- ./common.env
- ./apps/web.env
- /opt/secrets.env
注意的是这里所说的环境变量是对宿主机的 Compose 而言的,如果在配置文件中有 build 操作,这些变量并不会进入构建过程中,如果要在构建中使用变量还是首选前面刚讲的 arg 标签。
与上面的 env_file 标签完全不同,反而和 arg 有几分类似,这个标签的作用是设置镜像变量,它可以保存变量到镜像里面,也就是说启动的容器也会包含这些变量设置,这是与 arg 最大的不同。
一般 arg 标签的变量仅用在构建过程中。而 environment 和 Dockerfile 中的 ENV 指令一样会把变量一直保存在镜像、容器中,类似 docker run -e 的效果。
environment:
RACK\_ENV: development
SHOW: 'true'
SESSION\_SECRET:
environment:
- RACK\_ENV=development
- SHOW=true
- SESSION\_SECRET
这个标签与Dockerfile中的EXPOSE指令一样,用于指定暴露的端口,但是只是作为一种参考,实际上docker-compose.yml的端口映射还得ports这样的标签。
expose:
- "3000"
- "8000"
在使用Docker过程中,我们会有许多单独使用docker run启动的容器,为了使Compose能够连接这些不在docker-compose.yml中定义的容器,我们需要一个特殊的标签,就是external_links,它可以让Compose项目里面的容器连接到那些项目配置外部的容器(前提是外部容器中必须至少有一个容器是连接到与项目内的服务的同一个网络里面)。
格式如下:
external\_links:
- redis\_1
- project\_db\_1:mysql
- project\_db\_1:postgresql
添加主机名的标签,就是往/etc/hosts文件中添加一些记录,与Docker client的--add-host类似:
extra\_hosts:
- "somehost:162.242.195.82"
- "otherhost:50.31.209.229"
启动之后查看容器内部hosts:
162.242.195.82 somehost
50.31.209.229 otherhost
向容器添加元数据,和Dockerfile的LABEL指令一个意思,格式如下:
labels:
com.example.description: "Accounting webapp"
com.example.department: "Finance"
com.example.label-with-empty-value: ""
labels:
- "com.example.description=Accounting webapp"
- "com.example.department=Finance"
- "com.example.label-with-empty-value"
还记得上面的depends_on吧,那个标签解决的是启动顺序问题,这个标签解决的是容器连接问题,与Docker client的--link一样效果,会连接到其它服务中的容器。
格式如下:
links:
- db
- db:database
- redis
使用的别名将会自动在服务容器中的/etc/hosts里创建。例如:
172.12.2.186 db
172.12.2.186 database
172.12.2.187 redis
相应的环境变量也将被创建。
这个标签用于配置日志服务。格式如下:
logging:
driver: syslog
options:
syslog-address: "tcp://192.168.0.42:123"
默认的driver是json-file。只有json-file和journald可以通过docker-compose logs显示日志,其他方式有其他日志查看方式,但目前Compose不支持。对于可选值可以使用options指定。
有关更多这方面的信息可以阅读官方文档:
https://docs.docker.com/engine/admin/logging/overview/
pid: "host"
将PID模式设置为主机PID模式,跟主机系统共享进程命名空间。容器使用这个标签将能够访问和操纵其他容器和宿主机的名称空间。
映射端口的标签。
使用HOST:CONTAINER格式或者只是指定容器的端口,宿主机会随机映射端口。
ports:
- "3000"
- "8000:8000"
- "49100:22"
- "127.0.0.1:8001:8001"
注意:当使用HOST:CONTAINER格式来映射端口时,如果你使用的容器端口小于60你可能会得到错误得结果,因为YAML将会解析xx:yy这种数字格式为60进制。所以建议采用字符串格式。
为每个容器覆盖默认的标签。简单说来就是管理全部服务的标签。比如设置全部服务的user标签值为USER。
security\_opt:
- label:user:USER
- label:role:ROLE
设置另一个信号来停止容器。在默认情况下使用的是SIGTERM停止容器。设置另一个信号可以使用stop_signal标签。
stop\_signal: SIGUSR1
挂载一个目录或者一个已存在的数据卷容器,可以直接使用 HOST:CONTAINER 这样的格式,或者使用 HOST:CONTAINER:ro 这样的格式,后者对于容器来说,数据卷是只读的,这样可以有效保护宿主机的文件系统。
Compose的数据卷指定路径可以是相对路径,使用 . 或者 .. 来指定相对目录。
数据卷的格式可以是下面多种形式:
volumes:
// 只是指定一个路径,Docker 会自动在创建一个数据卷(这个路径是容器内部的)。
- /var/lib/mysql
// 使用绝对路径挂载数据卷
- /opt/data:/var/lib/mysql
// 以 Compose 配置文件为中心的相对路径作为数据卷挂载到容器。
- ./cache:/tmp/cache
// 使用用户的相对路径(~/ 表示的目录是 /home/<用户目录>/ 或者 /root/)。
- ~/configs:/etc/configs/:ro
// 已经存在的命名的数据卷。
- datavolume:/var/lib/mysql
如果你不使用宿主机的路径,你可以指定一个volume_driver。
volume\_driver: mydrive
从其它容器或者服务挂载数据卷,可选的参数是 :ro或者 :rw,前者表示容器只读,后者表示容器对数据卷是可读可写的。默认情况下是可读可写的。
volumes\_from:
- service\_name
- service\_name:ro
- container:container\_name
- container:container\_name:rw
添加或删除容器的内核功能。详细信息在前面容器章节有讲解,此处不再赘述。
cap\_add:
- ALL
cap\_drop:
- NET\_ADMIN
- SYS\_ADMIN
指定一个容器的父级cgroup。
cgroup\_parent: m-executor-abcd
设备映射列表。与Docker client的--device参数类似。
devices:
- "/dev/ttyUSB0:/dev/ttyUSB0"
这个标签可以扩展另一个服务,扩展内容可以是来自在当前文件,也可以是来自其他文件,相同服务的情况下,后来者会有选择地覆盖原有配置。
extends:
file: common.yml
service: webapp
用户可以在任何地方使用这个标签,只要标签内容包含file和service两个值就可以了。file的值可以是相对或者绝对路径,如果不指定file的值,那么Compose会读取当前YML文件的信息。
更多的操作细节在后面的12.3.4小节有介绍。
网络模式,与Docker client的--net参数类似,只是相对多了一个service:service name 的格式。
例如:
network\_mode: "bridge"
network\_mode: "host"
network\_mode: "none"
network\_mode: "service:[service name]"
network\_mode: "container:[container name/id]"
可以指定使用服务或者容器的网络。
加入指定网络,格式如下:
services:
some-service:
networks:
- some-network
- other-network
关于这个标签还有一个特别的子标签aliases,这是一个用来设置服务别名的标签,例如:
services:
some-service:
networks:
some-network:
aliases:
- alias1
- alias3
other-network:
aliases:
- alias2
相同的服务可以在不同的网络有不同的别名。
还有这些标签:cpu_shares, cpu_quota, cpuset, domainname, hostname, ipc, mac_address, mem_limit, memswap_limit, privileged, read_only, restart, shm_size, stdin_open, tty, user, working_di
上面这些都是一个单值的标签,类似于使用docker run的效果。
cpu\_shares: 73
cpu\_quota: 50000
cpuset: 0,1
user: postgresql
working\_dir: /code
domainname: foo.com
hostname: foo
ipc: host
mac\_address: 02:42:ac:11:65:43
mem\_limit: 1000000000
memswap\_limit: 2000000000
privileged: true
restart: always
read\_only: true
shm\_size: 64M
stdin\_open: true
tty: true
Define and run multi-container applications with Docker.
Usage:
docker-compose [-f <arg>...] [options] [COMMAND] [ARGS...]
docker-compose -h|--help
Options:
-f, --file FILE Specify an alternate compose file
(default: docker-compose.yml)
-p, --project-name NAME Specify an alternate project name
(default: directory name)
--verbose Show more output
--log-level LEVEL Set log level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
--no-ansi Do not print ANSI control characters
-v, --version Print version and exit
-H, --host HOST Daemon socket to connect to
--tls Use TLS; implied by --tlsverify
--tlscacert CA\_PATH Trust certs signed only by this CA
--tlscert CLIENT\_CERT\_PATH Path to TLS certificate file
--tlskey TLS\_KEY\_PATH Path to TLS key file
--tlsverify Use TLS and verify the remote
--skip-hostname-check Don't check the daemon's hostname against the
name specified in the client certificate
--project-directory PATH Specify an alternate working directory
(default: the path of the Compose file)
--compatibility If set, Compose will attempt to convert deploy
keys in v3 files to their non-Swarm equivalent
Commands:
build Build or rebuild services
bundle Generate a Docker bundle from the Compose file
config Validate and view the Compose file
create Create services
down Stop and remove containers, networks, images, and volumes
events Receive real time events from containers
exec Execute a command in a running containe
help Get help on a command
images List images
kill Kill containers
logs View output from containers
pause Pause services
port Print the public port for a port binding
ps List containers
pull Pull service images
push Push service images
restart Restart services
rm Remove stopped containers
run Run a one-off command
scale Set number of containers for a service
start Start services
stop Stop services
top Display the running processes
unpause Unpause services
up Create and start containers
version Show the Docker-Compose version information
列出本地 docker-compose.yml 文件定义的正在运行的所有服务,查看服务运行状态
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。