Service Mesh服务网格:是什么和为什么
Service Mesh服务网格:是什么和为什么 - 好雨云帮 CSDN 博客 - CSDN博客 https://blog.csdn.net/zyqduron/article/details/80433995
Service Mesh(服务网格)会是今年微服务生态的主角吗?从趋势来看,众多企业正在将这项理微服务复杂性的技术/工具,搬进他们的IT“火药库”之中。
什么是Service Mesh?
根据Linkerd CEO William Morgan定义,Service Mesh是用于处理服务间通信的基础设施层,用于在云原生应用复杂的服务拓扑中实现可靠的请求传递。在实践中,Service Mesh通常是一组与应用一起部署,但对应用透明的轻量级网络代理。
Service Mesh与传统基础设施层不同之处在于,它形成了一个分布式的互连代理网络,以sidecar形式部署在服务两侧,服务对于代理无感知,且服务间所有通信都由代理进行路由。
为什么需要Service Mesh?
“Smart endpoint and dumb pipes”是微服务架构在集成服务时采用的一个核心理念,这一理念改变了过去臃肿集中的ESB(企业服务总线),无疑是正确方向上的一大进步,但同时也给我们出了一些难题——多智能才不会过于智能,而服务轻重大小的程度如何拿捏?我们应该如何处理微服务系统中服务间交互的复杂性?放在服务内部还是外部?如果是内部,如何处理业务逻辑关系,或者应该与基础设施更为相关?如果是外部,如何避免重蹈ESB的覆辙?
皮的不谈,先来看看处理服务间通信时需要关注的点:
- 服务发现
- 负载均衡
- 路由
- 流量控制
- 通信可靠性
- 弹性
- 安全
- 监控/日志
似乎都是老生常谈,存在于任何需要处理网络的分布式系统之中,区别在于,当所涉及微服务数量呈指数级增加,这些问题也会被相应放大。
一个已经被广泛应用的解决方案是利用api网关来处理服务外部和服务之间的请求,提供例如服务发现、路由、监控、流量控制等。
然而,api网关有一个比较致命的缺陷,它容易出现单点故障并且实践不当很有可能会变得异常臃肿。另一方面,api网关核心是面向用户,也就是说它可以解决从用户到微服务的流量问题,但不能解决所有问题,而我们需要的是一个完整的方案,或者至少是一些能够与api网关互补的方案和工具。
另一种选择是在网络堆栈的较低层级(4/3)进行可靠性、监控、流量控制等方面处理。这种选择的问题是,在较低较低的操作难易满足应用层的问题。联想end-to-end(端到端)的理论,我们前面提到的那几个关注点实际上还是集中在应用层,也只能在应用层成功实现。
像Netflix、Twitter等SOA/微服务的早期采用者,他们通过建立内部库的方式处理这些问题,然后提供给所有服务使用。这种方法的问题在于,把库扩展到成百上千个微服务中难度极高,而且这些库相对来说是比较”脆弱“的,我们很难保证他们可以适应所有的技术堆栈选择。
程度上来说,Service Mesh与这些库很类似,但Service Mesh是与服务相邻的独立进程。服务连接到代理,代理反过来又与其他代理(HTTP1.1/2、GRPC)进行通信。它们是相对独立的进程,在应用层或应用层之下分布和运行,进而解决了上述两个方案存在的缺陷。
Service Mesh架构
Service Mesh由data plane构成,其中所有服务通过sidecar代理进行服务通信。(所有代理相互连接形成一个Mesh,Service Mesh由此得名)网格同时包含一个control plane——可以将所有独立的sidecar代理连接到一个分布式网络中,并设置网格还包括一个控制平面——它将所有独立的sidecar代理连接到一个分布式网络中,并设置由data plane指定的策略。
Control plane定义服务发现、路由、流量控制等策略。这些策略可以是全局的,也可以是限定的。Data plane负责在通信时应用和执行这些策略。
最后
总结来说,Service Mesh是“时间的产物”,Docker、Kubernetes等容器技术直接推进了对于Service Mesh的需求,让复杂的系统可以被轻松部署和管理。
未来Service Mesh将如何发展还未可知,或许会像TCP/IP一样形成标准,或许不同工具和平台会独具一格……但有一点是确认的,Service Mesh对于微服务生态的价值令人难以忽视,能够将繁重的服务治理工作变得简单高效,何乐而不为?
Kubernetes,微服务以及 Service Mesh_Kubernetes中文社区 https://www.kubernetes.org.cn/2737.html
您目前处于:
- 社区首页
- istio
- Kubernetes实践分享/开发实战
- Kubernetes,微服务以及 Service Mesh
>
>
>
Kubernetes,微服务以及 Service Mesh
作者:Jolestar ,2015年初开始创业,作为技术合伙人,专注于打造一款团队通讯协作工具-Grouk
这是前一段时间在一个微服务的 meetup 上的分享,整理成文章发布出来。
谈微服务之前,先澄清一下概念。微服务这个词的准确定义很难,不同的人有不同的人的看法。比如一个朋友是『微服务原教旨主义者』,坚持微服务一定是无状态的 http API 服务,其他的都是『邪魔歪道』,它和 SOA,RPC,分布式系统之间有明显的分界。而另外也有人认为微服务本身就要求把整体系统当做一个完整的分布式应用来对待,而不是原来那种把各种组件堆积在一起,『拼接』系统的做法。两种说法都有道理,因为如果微服务没有个明确的边界的话,你可能会发现微服务囊括了一切,但如果只是坚持无状态,那微服务相关的一些领域又无法涵盖。我个人对这个问题持开放式的看法,微服务本身代表了一种软件交付以及复用模式的变化,从依赖库到依赖服务,和 SOA 有相通之处,同时它也带来了新的挑战,对研发运维都有影响,所有因为和微服务相关而产生的变化,都可以囊括在大微服务主题下。
微服务带来的变化
微服务主要给我们带来的变化有三点,
- 部署单元 越来越小的粒度,加快交付效率,同时增加运维的复杂度。
- 依赖方式 从依赖库到依赖服务,增加了开发者选择的自由(语言,框架,库),提高了复用效率,同时增加了治理的复杂度。
- 架构模式 从单体应用到微服务架构,架构设计的关注点从分层转向了服务拆分。
##微服务涉及的技术点
- 服务注册与发现 服务目录 服务列表 配置中心
- 进程间通讯 负载均衡
- 服务生命周期管理 部署, 变更,升级,自动化运维
- 服务依赖关系
- 链路跟踪,限流,降级,熔断
- 访问控制
- 日志与监控
- 微服务应用框架
这个是我浏览了众多微服务的话题之后摘要出来的一些技术点,不全面也不权威,不过可以看出,微服务主题涉及的面非常广,那这些问题在微服务之前就不存在么?为什么大家谈到微服务的时候才把这些东西拿出来说。这个需要从软件开发的历史说来。软件开发行业从十多年前开始,出现了一个分流,一部分是企业应用开发,软件要安装到企业客户自己的资源上,客户负责运维(或者通过技术支持),一部分是互联网软件,自己开发运维一套软件通过网络给最终用户使用。这两个领域使用的技术栈也逐渐分化,前者主要关注标准化,框架化(复用),易安装,易运维,后者主要关注高可用,高性能,纵向伸缩。而这两个领域到微服务时代,形成了一个合流,都在搞微服务化。主要原因我认为有两点:
- SaaS 的兴起,使得一些企业应用厂商也开始采用互联网模式,遇到用户规模的问题。同时企业应用很难纯 SaaS 化,面对大客户的时候,势必面临私有部署的问题,所以必须探索一种既能支撑用户规模,同时要能方便私有化部署的架构。
- 随着互联网技术领域为了应对新的业务变化,需要将原来的技术经验沉淀继承过来,开始关注标准化和框架化。
也就是说,这些技术点并不是新问题,但如何将这些技术点从业务逻辑中抽取出来,作为独立的,可复用的框架或者服务,这个是大家探寻的新问题。
为微服务而生的 Kubernetes
- Kubernetes Pod – Sidecar 模式
- Kubernetes 支持微服务的一些特性
- Service Mesh 微服务的中间件层
我这样说,不仅仅是因为这是一个微服务的 meetup,微服务和 Kubernetes 确实是相辅相成的。一方面 Kubernetes 帮助微服务落地,另外一方面微服务促进了对容器和 Kubernetes 的需求。
Kubernetes 的 Pod – Sidecar 模式
我们先从 Kubernetes 的 Pod 机制带来的一种架构模式 — Sidecar 来说。下面这段配置文件是我从 Kubernetes 内置的 dns 的 deployment 中抽取出来的 Pod 描述文件,简化掉了资源限制,端口设置以及健康检查等内容。
apiVersion: v1
kind: Pod
metadata:
name: dnspod
spec:
containers:
- name: kubedns
image: gcr.io/google_containers/k8s-dns-kube-dns-amd64:1.14.4
args:
- --domain=cluster.local.
- --dns-port=10053
- --config-dir=/kube-dns-config
- --v=2
ports:
- containerPort: 10053
name: dns-local
protocol: UDP
- containerPort: 10053
name: dns-tcp-local
protocol: TCP
- name: dnsmasq
image: gcr.io/google_containers/k8s-dns-dnsmasq-nanny-amd64:1.14.4
args:
- -v=2
- -logtostderr
- -configDir=/etc/k8s/dns/dnsmasq-nanny
- -restartDnsmasq=true
- --
- -k
- --cache-size=1000
- --log-facility=-
- --server=/cluster.local/127.0.0.1#10053
- --server=/in-addr.arpa/127.0.0.1#10053
- --server=/ip6.arpa/127.0.0.1#10053
ports:
- containerPort: 53
name: dns
protocol: UDP
- containerPort: 53
name: dns-tcp
protocol: TCP
- name: sidecar
image: gcr.io/google_containers/k8s-dns-sidecar-amd64:1.14.4
args:
- --v=2
- --logtostderr
- --probe=kubedns,127.0.0.1:10053,kubernetes.default.svc.cluster.local,5,A
- --probe=dnsmasq,127.0.0.1:53,kubernetes.default.svc.cluster.local,5,A
从这个配置文件可以看出,Kubernetes 的 Pod 里可以包含多个容器,同一个 Pod 的多个容器之间共享网络,Volume,IPC(需要设置),生命周期和 Pod 保持一致。上例中的 kube-dns 的作用是通过 Kubernetes 的 API 监听集群中的 Service 和 Endpoint 的变化,生成 dns 记录,通过 dns 协议暴露出来。dnsmasq 的作用是作为 dns 缓存,cluster 内部域名解析代理到 kube-dns,其他域名通过上游 dns server 解析然后缓存,供集群中的应用使用。sidecar 容器的作用是进行 dns 解析探测,然后输出监控数据。
通过这个例子可以看出来,Kubernetes 的 Pod 机制给我们提供了一种能力,就是将一个本来要捆绑在一起的服务,拆成多个,分为主容器和副容器(sidecar),是一种更细粒度的服务拆分能力。当然,没有 Kubernetes 的时候你也可以这么干,但如果你的程序需要几个进程捆绑在一起,要一起部署迁移,运维肯定想来打你。有了这种能力,我们就可以用一种非侵入的方式来扩展我们的服务能力,并且几乎没有增加运维复杂度。这个在我们后面的例子中也可以看到。
Kubernetes 上的服务发现
服务发现其实包含了两个方面的内容,一种是要发现应用依赖的服务,这个 Kubernetes 提供了内置的 dns 机制和 ClusterIP 机制,每个 Service 都自动注册域名,分配 ClusterIP,这样服务间的依赖可以从 IP 变为 name,这样可以实现不同环境下的配置的一致性。
apiVersion: v1
kind: Service
metadata:
name: myservice
spec:
ports:
- port: 80
protocol: TCP
targetPort: 9376
selector:
app: my-app
clusterIP: 10.96.0.11
curl http://myservice
如上面的 Service 的例子,依赖方只需要通过 myservice 这个名字调用,具体这个 Service 后面的后端实例在哪,有多少个,用户不用关心,交由 Kubernetes 接管,相当于一种基于虚 IP(通过 iptables 实现) 的内部负载均衡器。(具体的 clusterIP 实现这里不再详述,可查阅相关资料)。
另外一种服务发现是需要知道同一个服务的其他容器实例,节点之间需要互相连接,比如一些分布式应用。这种需求可以通过 Kubernetes 的 API 来实现,比如以下实例代码:
endpoints, _ = client.Core().Endpoints(namespace).Get("myservice", metav1.GetOptions{})
addrs := []string{}
for _, ss := range endpoints {
for _, addr := range ss.Addresses {
ips = append(addrs, fmt.Sprintf(`"%s"`, addr.IP))
}
}
glog.Infof("Endpoints = %s", addrs)
Kubernetes 提供了 ServiceAccount 的机制,自动在容器中注入调用 Kubernetes API 需要的 token,应用代码中无需关心认证问题,只需要部署的时候在 yaml 中配置好合适的 ServiceAccount 即可。
关于服务发现再多说两句,没有 Kubernetes 这样的统一平台之前,大家做服务发现还主要依赖一些服务发现开源工具,比如 etcd,zookeeper,consul 等,进行自定义开发注册规范,在应用中通过 sdk 自己注册。但当应用部署到 Kubernetes 这样的平台上你会发现,应用完全不需要自己注册,Kubernetes 本身最清楚应用的节点状态,有需要直接通过 Kubernetes 进行查询即可,这样可以降低应用的开发运维成本。
通过 Kubernetes 进行 Leader 选举
有些分布式需要 Leader 选举,在之前,大家一般可能依赖 etcd 或者 zookeeper 这样的服务来实现。如果应用部署在 Kubernetes 中,也可以利用 Kubernetes 来实现选举,这样可以减少依赖服务。
Kubernetes 中实现 Leader 选举主要依赖以下特性:
- Kubernetes 中的所有 API 对象类型,都有一个 ResourceVersion,每次变更,版本号都会增加。
- Kubernetes 中的所有对象,都支持 Annotation,支持通过 API 修改,这样可以附加一些自定义的 key-value 数据保存到 Kubernetes 中。
- Kubernetes 的所有 API 对象中,Endpoint/ConfigMap 属于『无副作用』对象,也就是说,创建后不会带来额外的影响,所以一般用这两种对象来保存选主信息。
- Kubernetes 的 Update/Replace 接口,支持 CAS 机制的更新,也就是说,更新时可以带上客户端缓存中的 ResourceVersion,如果服务器端该对象的 ResourceVersion 已经大于客户端传递的 ResourceVersion,则会更新失败。
这样,所有的节点都一起竞争更新同一个 Endpoint/ConfigMap,更新成功的,作为 Leader,然后把 Leader 信息写到 Annotation 中,其他节点也能获取到。为了避免竞争过于激烈,会有一个过期机制,过期时间也写入到 Annotation,Leader 定时 renew 过期时间,其他节点定时查询,发现过期就发起新一轮的竞争。这样相当于 Kubernetes 提供了一种以 client 和 API 一起配合实现的 Leader 选举机制,并且在 client sdk 中提供。当前 Kubernetes 的一些内部组件,比如 controller-manager,也是通过这种方式来实现选举的。
当然,如果觉得调用 client 麻烦,或者该语言的 sdk 尚未支持这个特性(应该只有 go 支持了),可以也可以通过 sidercar 的方式实现,参看 https://github.com/kubernetes/contrib/tree/master/election 。也就是说,通过一个 sidecar 程序去做选主,主程序只需要调用 sidecar 的一个 http api 去查询即可。
$ kubectl exec elector-sidecar -c nodejs -- wget -qO- http://localhost:8080
Master is elector-sidecar
Kubernetes 支持微服务的运维特性
Kubernetes 对微服务的运维特性上的支持,主要体现以下两方面:
- 滚动升级以及自动化伸缩
kubectl set image deployment <deployment> <container>=<image>
kubectl rollout status deployment <deployment>
kubectl rollout pause deployment <deployment>
kubectl rollout resume deployment <deployment>
kubectl rollout undo deployment <deployment>kubectl autoscale deployment php-apache --cpu-percent=50 --min=1 --max=10
滚动升级支持最大不可用节点设置,支持暂停,恢复,一键回滚。自动伸缩支持根据监控数据在一个范围内自动伸缩,很大程度上降低了微服务的运维成本。
- 日志与监控的标准化通过 Kubernetes 可以实现日志收集以及应用监控的标准化,自动化,这样应用不用关心日志和监控数据的收集展示,只需要按照系统标准输出日志和监控接口即可。
Service Mesh 微服务的中间件层
微服务这个话题虽然火了有很长时间了,关于微服务的各种框架也有一些。但这些框架大多是编程语言层面来解决的,需要用户的业务代码中集成框架的类库,语言的选择也受限。这种方案很难作为单独的产品或者服务给用户使用,升级更新也受限于应用本身的更新与迭代。直到 Service Mesh 的概念的提出。Service Mesh 貌似也没有比较契合的翻译(有的译做服务齿合层,有的翻译做服务网格),这个概念就是试图在网络层抽象出一层,来统一接管一些微服务治理的功能。这样就可以做到跨语言,无侵入,独立升级。其中前一段时间 Google,IBM,Lyft 联合开源的 istio 就是这样一个工具,先看下它的功能简介:
- 智能路由以及负载均衡
- 跨语言以及平台
- 全范围(Fleet-wide)策略执行
- 深度监控和报告
是不是听上去就很厉害?有的还搞不明白是啥意思?我们看 istio 之前先看看 Service Mesh 能在网络层做些什么。
- 可视化 其实本质上微服务治理的许多技术点都包含可视化要求,比如监控和链路追踪,比如服务依赖
- 弹性(Resiliency 或者应该叫柔性,因为弹性很容易想到 scale) 就是网络层可以不那么生硬,比如超时控制,重试策略,错误注入,熔断,延迟注入都属于这个范围。
- 效率(Efficiency) 网络层可以帮应用层多做一些事情,提升效率。比如卸载 TLS,协议转换兼容
- 流量控制 比如根据一定规则分发流量到不同的 Service 后端,但对调用方来说是透明的。
- 安全保护 在网络层对流量加密/解密,增加安全认证机制,而对应用透明。
可以看出,如果接管了应用的进出流量,将网络功能可编程化,实际上可做的事情很多。那我们简单看下 istio 是如何做的。
大致看一下这个架构图,istio 在业务 Pod 里部署了一个 sidecar — Envoy,这是一个代理服务器,前面说的网络层功能基本靠它来实现,然后 Envoy 和上面的控制层组件(Mixer,Pilot,Istio-Auth)交互,实现动态配置,策略执行,安全证书获取等,对用户业务透明。实际部署的时候,并不需要开发者在自己的 Pod 声明文件里配置 Envoy 这个 sidecar,istio 提供了一个命令行工具,在部署前注入(解析声明配置文件然后自动修改)即可,这样 istio 的组件就可以和业务应用完全解耦,进行独立升级。
看到这里,Java 服务器端的研发人员可能会感觉到,这个思路和 Java 当初的 AOP(aspect-oriented programming) 有点像,确实很多 Java 微服务框架也是利用 AOP 的能力来尽量减少对应用代码的侵入。但程序语言的 AOP 能力受语言限制,有的语言里就非常难实现非侵入的 AOP,而如果直接在网络层面寻找切面就可以做到跨语言了。
当前其实已经有许多在网络层实现的中间件,比如有提供数据库安全审计功能的,有提供 APM 的,有提供数据库自动缓存的,但这些中间件遇到的最大问题是增加了用户应用的部署复杂度,实施成本比较高,很难做到对用户应用透明。可以预见,随着 Kubernetes 的普及,这类中间件也会涌现出来。
结语
本质上,微服务的目的是想以一种架构模式,应对软件所服务的用户的规模增长。没有微服务架构之前,大多数应用是以单体模式出现的,只有当规模增长到一定程度,单体架构满足不了伸缩的需求的时候,才考虑拆分。而微服务的目标是在一开始的时候就按照这种架构实现,是一种面向未来的架构,也就是说用开始的选择成本降低以后的重构成本。用经济学的观点来说,微服务是技术投资,单体应用是技术债务,技术有余力那可以投资以期待未来收益,没余力那就只能借债支持当前业务,等待未来还债。而随着微服务基础设施的越来越完善,用很小的投资就可以获得未来很大的收益,就没有理由拒绝微服务了。
相关链接
- kubernetes election
- Kubernetes on QingCloud
- istio 官方文档
- istio.doczh.cn istio 中文文档
Service Mesh服务网格:是什么和为什么的更多相关文章
- Service Mesh服务网格新生代--Istio(转)
万字解读:Service Mesh服务网格新生代--Istio 官网地址:https://preliminary.istio.io/zh/docs/concepts/security/ Servic ...
- Service Mesh服务网格新生代--Istio
原文: 数人云|万字解读:Service Mesh服务网格新生代--Istio 参考: istio 简介 Istio是啥?一文带你彻底了解! 使用Istio治理微服务入门 Istio 流量管理 ist ...
- Service Mesh服务网格清单
Service Mesh服务网格清单 Istio Istio官网 Istio中文官网 Istio开源 无需太多介绍Service Mesh明日之星,扛把子,截止2019.11还有太多问题没解决 复杂性 ...
- Service Mesh服务网格新生代——Istio
Istio 是什么?使用云平台可以为组织提供丰富的好处.然而,不可否认的是,采用云可能会给 DevOps 团队带来压力.开发人员必须使用微服务已满足应用的可移植性,同时运营商管理了极其庞大的混合和多云 ...
- Service Mesh服务网格之Linkerd架构
今天详细介绍一下Linkerd的架构. 控制平面 Linkerd控制平面是一组在专用Kubernetes命名空间中运行的服务(在Linked默认情况下).这些服务完成各种事情——聚合遥测数据.提供面向 ...
- Linkerd Service Mesh 服务配置文件规范
服务配置文件 为 Linkerd 提供有关服务的附加信息. 以下是可以使用服务配置文件完成的所有操作的参考. 系列 中文手册(https://linkerd.hacker-linner.com) Sp ...
- Qcon2017实录|Service Mesh:下一代微服务
https://zhuanlan.zhihu.com/p/30292372 数人云11月Meetup报名开启,看中西方大神如何论道云原生与微服务!本文作者敖小剑老师将在本次Meetup上继续分享Ser ...
- 第八章 跨语言服务治理方案 Service Mesh
8.1 Service Mesh 概述 新兴的下一代微服务架构,被称为下一代微服务,同时也是云原生技术栈的代表技术之一. 8.1.1 Service Mesh的由来 从2016年到2018年,serv ...
- CDRAF之Service mesh
最近翻看一些网上的文章,偶然发现我们的CDRAF其实就是Service mesh的C++版本.不管从架构的理念上,或者功能的支持上面,基本完全符合.发几个简单的文章链接,等有时间的时候,再来详细描述. ...
随机推荐
- 一款基于jQuery带事件记录的日历插件
之前我们也已经分享过不少jQuery日历插件,有些应用了CSS3的特性,外观就特别漂亮.今天要分享的这款jQuery日历插件不仅有着绚丽的外观,而且带有日期事件记录功能,点击日期即可展开事件记录窗口, ...
- Odoo 8.0 new API 概述
相对于7来说,8的api改进了不少,用官方的话来说就是更加面向对象了. 下面探究一下具体的改动. 准备知识:python装饰器的使用 http://blog.csdn.net/thy38/articl ...
- C语言 · 三角形面积
算法提高 三角形面积 时间限制:1.0s 内存限制:256.0MB 问题描述 由三角形的三边长,求其面积. 提示:由三角形的三边a,b,c求面积可以用如下的公式: s=(a+b+c ...
- UCOS2系统内核讲述(四)_创建任务
Ⅰ.写在前面 学习本文之前可以参看我前面的文章: UCOS2_STM32移植详细过程(汇总文章) UCOS2系统内核讲述(一)_总体描述 UCOS2系统内核讲述(二)_初始化调用函数 UCOS2系统内 ...
- JavaScript概述.pdf
第1章 JavaScript概述 第2章 使用JavaScript 第3章 语法.关键保留字及变量 第4章 数据类型 第5章 运算符 第6章 流程控制语句 第7章 函数 //没有参数的函数 funct ...
- Redis学习笔记——简介及配置
1.Redis简介 Redis概述 Redis是一个开源,先进的key-value存储,并用于构建高性能,可扩展的应用程序的完美解决方案.Redis从它的许多竞争继承来的三个主要特点:Redis数据库 ...
- 如何创建Cookie? (选择1项)
如何创建Cookie? (选择1项) A. 使用new Cookie语句 B. 调用response.addCookie方法 C. 使用Cookie的setMaxAge方法 D. setCookie方 ...
- (转)directx中丢失的设备(lost device)
directx中丢失的设备(lost device) 丢失的设备 一个Microsoft? Direct3D?可以处于操作状态或丢失状态.操作状态是设备的正常状态,设备按预期运行并present所 ...
- 怎么隐藏MathType标尺
因为MathType公式编辑能力非常的好用,所以非常的受大家的欢迎.MathType用现有的模板可以直接输入输出各种公式,而且MathType中有着各式各样的数学符号满足了大家日常公式的需求,为大家的 ...
- print多重打印
遇见有趣的问题必须记录下来,当时的想法思路也要记下来 以下两行代码打印出来的结果会是什么 print('2 * 3 = %d' % (2 * 3)) print('2 * 3 = %d' % 2 * ...