使用Spring Cloud配置服务器控制你的配置
使用Spring Cloud配置服务器控制你的配置
摘要 本文是《Spring Microservices In Action》第三章关于配置管理的中文翻译,在微服务实践中将所有微服务的配置集中外置到配置中心统一管理,通过将配置管理抽象成独立的服务来简化在不同的环境中的微服务配置管理,帮助微服务无状态化和轻量部署以及调度,已经成为业内默认的最佳实践,Spring Cloud开放的子项目ConfigServer,阿里云免费开放的应用配置中心产品ACM都是这种最佳实践的具体体现。
Spring Microservices In Action
-- John Carnell
本章涵盖
将服务的配置从代码中分离
配置Spring Cloud Config Server
集成Spring Boot微服务
加密敏感的配置属性
无论如何,开发者被迫将代码中的配置信息从代码中分离出来。毕竟,自从上学以来,不应该将值硬编码到代码中就已经烙印在开发者的脑海中。许多开发人员在其应用程序中使用常量类文件,以便将所有配置集中在一个地方。将应用程序配置数据直接写在代码里的通常是有问题的,因为每次必须更改配置时,都必须重新编译和/或重新部署应用程序。为了避免这种情况,开发人员将从应用程序代码中完全分离出配置信息。这使得在不经过重新编译过程的情况下对配置进行变更变得很容易,但这也会带来复杂性,因为你现在有另一个需要与应用程序一起管理和部署的工件。
许多开发人员转向低级属性文件(或YAML,JSON或XML)来存储其配置信息。这个属性文件放在一个服务器上,通常包含数据库和中间件连接信息以及那些将驱动应用程序行为的应用程序的元数据。将应用程序配置分离到一个属性文件是很容易的,大多数开发者不再对他们的应用程序配置进行更多的操作,仅只是把他们作为应用程序的一部分放在源代码版本控制之下并作为应用程序的一部分来部署。
这种方法可能适用于少数应用程序,但在处理可能包含数百个基于云的微服务的应用程序,而每个微服务又可能有多个服务实例在运行时,这种方式会很快崩溃。
突然之间,配置管理成为了一个大问题,因为基于云的环境中的应用程序开发和运维团队必须与那些配置文件所在的鼠窝搏斗。基于云的微服务开发强调
将应用程序的配置与正在部署的实际代码完全分开
构建服务器和应用程序,以及在你的环境中不变的镜像(image)
通过环境变量或通过读取集中式存储库中的配置,在服务器启动时注入任何应用程序的配置信息
本章将向你介绍在基于云的微服务应用程序中管理应用程序配置数据所需的核心原则和模式。
3.1 管理配置(和复杂性)
管理应用程序配置对于在云中运行的微服务至关重要,因为微服务实例需要在最少的人为干预下快速启动,以应对应用程序的可扩展性挑战。而每当部署时需要人手动配置或介入时,都会出现配置漂移,意外停机和滞后时间。
让我们开始讨论应用程序配置管理,建立我们想要遵循的四个原则:
隔离 - 我们希望将服务配置信息与实际的部署完全分开。应用程序配置不应与物理服务实例一起部署。相反,应将配置信息作为环境变量传递给启动服务,或者在服务启动时从集中式配置存储库中读取。
抽象 - 将配置数据的访问抽象并封装在一个服务后面而不是直接访问配置数据的存储(从文件或者通过JDBC访问存储配置的数据库),让应用程序使用基于REST的JSON服务来检索配置数据。
集中 - 由于基于云的应用程序可能实际上具有数百个服务,因此减少用于保存配置信息的不同配置存储库的数量至关重要。将你的应用程序配置集中到尽可能少的配置存储库中。
硬化(Harden) - 因为你的应用程序配置信息与你部署的服务完全隔离并集中,因此无论你使用何种解决方案都可能实现高度可用和冗余,这一点至关重要。
要记住的关键之一是,当你将配置信息分离到实际代码之外时,你将创建一个需要进行管理和版本控制的外部依赖关系。我必须强调应用程序配置数据需要版本控制和跟踪,因为管理不善的应用程序配置是难以察觉的错误和意外中断的肥沃滋生地。
意外的复杂性
我亲身体验过没有管理应用程序配置数据策略的危险性。在财富500强金融服务公司工作期间,我被要求帮助将一个大的WebSphere升级项目带回到正轨。该公司在WebSphere上拥有超过120个应用程序,并且需要在整个应用程序环境达到供应商维护期限结束之前将其基础结构从WebSphere 6升级到WebSphere 7。
该项目已经进行了一年,仅部署了120个应用程序中的一个。这个项目花费了一百万美元的人力和硬件成本,而目前的进度轨迹显示还需要再过两年才能完成升级。当我开始与应用程序团队合作时,我发现的一个(也是仅仅一个)主要问题是应用程序团队用在属性文件内管理其所有数据库的配置以及服务的端点信息。这些属性文件是手动管理的,不受源代码控制。随着120个应用程序分布在四个环境和每个应用程序的多个WebSphere节点上,这个配置文件的老鼠窝导致团队需要尝试迁移分布在运行的数百台服务器和应用程序的12,000个配置文件。 (是的,你正在读取这个数字是:12,000。)这些文件还只是用于应用程序的配置,甚至不包括应用程序服务器的配置。我说服了项目发起人要花费两个月的时间将所有的应用程序信息整合到一个由20个配置文件组成的集中版本控制的配置库中。当我向框架团队询问有关12000个配置文件的情况时,团队的首席工程师说,他们最初是围绕一小组应用程序设计配置策略的。然而,构建和部署的Web应用程序的数量在五年内爆炸性增长,尽管他们乞求花钱和时间重新修改其配置管理方法,但他们的业务合作伙伴和IT领导者从未将其视为优先事项。不花时间来弄清楚如何进行配置管理会产生真实(而且是代价昂贵的)的负面影响。
3.1.1你的配置管理体系结构
正如你在第二章中所记得的那样,加载微服务的配置管理发生在微服务的启动(Bootstrapping)阶段。提醒一下,图3.1显示了微服务的生命周期。
让我们来看看前面3.1节(隔离,抽象,集中和硬化)中我们所阐述的四个原则,看看这四个原则在启动时如何应用。图3.2更详细地介绍了启动过程,并展示了配置服务在这一步中如何扮演了一个至关重要的角色。
在图3.2中,你会看到几个活动正在发生:
当一个微服务实例启动时,它将调用一个(配置)服务端点来读取与它正在运行的环境匹配的特定的配置信息。配置管理的连接信息(连接凭证,服务端点等等)将在启动时传入微服务。
实际的配置将驻存在一个存储库中。根据配置库的实现,你可以选择使用不同的实现来保存配置数据。实现选项可以包括源代码管理下的文件,关系数据库或kv数据库。
应用程序配置数据的实际管理独立于应用程序的部署方式。通常通过构建和部署流(pipeline)来处理对配置的变更管理,其中可以使用版本信息对配置的变更进行标记,并通过不同的环境进行部署。
当进行配置管理变更时,必须通知使用该配置的那些应用服务,同时刷新应用程序配置数据的副本。
现在我们已经完成了概念架构,它阐明了配置管理模式的不同部分以及这些部分如何组合在一起。我们现在要继续研究不同的解决方案,然后来看一个具体的实现方案。
3.1.2 实现的选择
幸运的是,你可以选择大量经过实战检验的开源项目来实现配置管理解决方案。让我们来看几个可用的不同选择并进行比较。表3.1列出了这些选择。
项目名
描述
特征
Etcd
用Go编写的开源项目用于服务发现和键值管理。其分布式计算模型使用raft协议(https:// raft.github.io/)
非常快速和可扩展。分布式。命令行驱动。易于使用和设置。
Eureka
由Netflix研发。经过严苛的实战检验。用于服务发现和键值管理。
分布式键值存储。 灵活的。学习成本较高。开箱即用的动态刷新客户端。
Consul
由Hashicorp研发。类似于Etcd和Eureka的特性,但是为其分布式计算模型使用了不同的算法(SWIM 协议 https:// www.cs.cornell.edu/~asdas/research/ dsn02-swim.pdf))
快。 提供原生的服务发现功能,可直接与DNS集成。不提供客户端动态刷新能力。
ZooKeeper
一个提供分布式锁功能的Apache项目。常常用作访问键值数据的配置管理解决方案。
最古老,最经过实战检验的解决方案。用起来最复杂。可以用于配置管理,但只有在你的架构中已经使用了ZooKeeper的情况下才应该考虑。
Spring Cloud configuration server
开源项目,提供了一个通用的配置管理解决方案,不同的后端。它可以将Git,Eureka和Consul作为后端整合
非分布式密钥/值存储。为Spring和非Spring服务提供紧密集成可以使用多个后端进行故事配置数据,包括共享文件系统Eureka,Consul和Git
ACM
阿里云开放的免费配置中心产品,专注于应用配置管理的一站式解决方案。
简单易用,特性丰富。经过阿里巴巴大规模生产验证。推送性能高。高可靠的SLA。
表3.1中的所有解决方案均可轻松用于构建配置管理解决方案。对于本章以及本书其余部分的示例,将使用Spring Cloud config server。我选择这个解决方案的原因有很多,其中包括:
Spring Cloud配置服务器易于设置和使用。
Spring Cloud配置与Spring Boot紧密集成。你可以使用几个简单易用的注解(annotation)来逐个使用你应用程序的所有配置数据。
Spring Cloud配置服务器提供多个后端用于存储配置数据。如果你已经在使用Eureka和Consul等工具,则可以将其直接插入Spring Cloud配置服务器。
表3.1中的所有解决方案中,Spring Cloud配置服务器可以直接与Git源代码管理平台集成。 Spring Cloud配置与Git的集成消除了你的解决方案中的额外依赖性,并使你的应用程序配置数据的版本控制变得轻而易举。
其他工具(Etcd,Consul,Eureka)不提供任何种类的本地版本,如果你想要的话,你必须自己构建它。如果你已经使用Git,则使用Spring Cloud配置服务器是一个有吸引力的选择。
对于本章的其余部分,我们将:
建立一个Spring Cloud配置服务器,并演示两种不同的机制来提供应用程序配置数据 - 一个使用filesystem,另一个使用Git仓库
继续构建许可服务,从数据库中检索数据
将Spring Cloud配置服务勾连到你的许可服务中来提供应用程序配置数据
3.2 构建我们的Spring Cloud 配置服务器
Spring Cloud配置服务器是基于REST的应用程序,它建立在Spring Boot之上。它不是一个独立的服务器。相反,你可以选择将其嵌入到已有的Spring Boot应用程序中,或者启动一个新的Spring Boot项目。
你需要做的第一件事就是建立一个叫做confsvr的新项目目录。在consvr目录中,创建一个新的Maven文件,该文件将用于在你的Spring Cloud配置服务器上启动所需的JAR文件。我不会列全整个Maven文件,而是列出以下列表中的关键部分。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://
www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://
maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.thoughtmechanix</groupId>
<artifactId>configurationserver</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>Config Server</name>
<description>Config Server demo project</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.4.RELEASE</version>
</parent>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Camden.SR5</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<start-class>com.thoughtmechanix.confsvr. ConfigServerApplication </start-class>
<java.version>1.8</java.version>
<docker.image.name>johncarnell/tmx-confsvr</docker.image.name>
<docker.image.tag>chapter3</docker.image.tag>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
</dependencies>
<!--Docker build Config Not Displayed -->
</project>
在上面的Maven文件中,首先声明要用于你的微服务的Spring Boot的版本(版本1.4.4)。Maven定义的下一个重要部分是你要使用的Spring Cloud配置父BOM(物料清单)。 Spring Cloud是一个大量独立项目的集合,都随着他们自己的版本而滚动的。此父级BOM包含云项目中使用的所有第三方库和依赖项以及组成该版本的各个项目的版本号。在本例中,你使用的是Spring Cloud的Camden.SR5版本。通过使用BOM定义,你可以保证在Spring Cloud中使用子项目的兼容版本。这也意味着你不必为你的子依赖声明版本号。清单3.1中的其余部分处理声明你将在服务中使用的特定Spring Cloud依赖关系。第一个依赖项是所有Spring Cloud项目都使用的spring-cloud-starter-config依赖项。第二个依赖项是spring-cloud-config-server启动项目。这包含spring-cloud-config-server的核心库。
Come on, ride the train, the release train
Spring Cloud使用非传统机制来标记Maven项目。 Spring Cloud是独立子项目的集合。Spring Cloud团队通过他们所谓的“发布列车”来发布他们的版本。构成Spring Cloud的所有子项目都被打包在一个Maven物料清单(BOM)中,并作为一个整体发布。Spring Cloud团队一直使用伦敦地铁站名作为发布的名称,每增加一个主要版本,伦敦地铁站就有了第二高的字母。已经有三个版本:Angel,Brixton,and Camden。Camden是迄今为止最新的版本,但其中的子项目仍有多个候选版本分支。
有一件事要注意,Spring Boot是独立于Spring Cloud发行版本发布的。因此,不同版本的Spring Boot与Spring Cloud的不同版本不兼容。通过参考Spring Cloud网站(http://projects.spring.io/spring-cloud/),你可以看到Spring Boot和Spring Cloud之间的版本依赖关系,以及版本系列中包含的不同子项目版本。
你仍然需要设置一个文件来启动并运行配置服务器。这个文件是你的application.yml文件,位于confsvr/src/main/resources目录下。application.yml文件将告诉你的Spring Cloud配置服务监听哪个端口,以及定位提供配置数据的后端在哪里。
你几乎可以开始使用Spring Cloud配置服务了。你需要将服务器指向将保存配置数据的后端存储库。在本章中,你将使用第2章中开始构建的许可服务,作为如何使用Spring Cloud Config的示例。为了简单起见,你将为三种环境设置应用程序配置数据:在本地运行服务时的默认环境,开发环境和生产环境。
在Spring Cloud配置中,一切都是按照层次结构进行的。你的应用程序配置由应用程序的名称表示,然后为每个你希望具有配置信息的环境指定一个属性文件。在每个这样的环境中,你将设置两个配置属性:
将由你的许可服务直接使用的示例属性
用于存储许可证应用数据的Postgres数据库的数据库配置
图3.3展示了如何设置和使用Spring Cloud配置服务。有一件事要注意,当你构建你的配置服务时,它将成为你的环境中运行的另一个微服务。一旦建立,服务的内容就可以通过基于http的REST端点访问。
应用程序配置文件的命名约定是appname-env.yml。从图3.3中的图中可以看到,环境名称直接转换为将被访问的URL以浏览配置信息。稍后,在启动许可微服务示例时,你要运行服务的环境由你在命令行服务启动时传入的Spring Boot profile指定。如果profile没有通过命令行传入,Spring Boot将始终默认使用包含在应用程序包中的application.yml文件中的配置数据。
以下是你将为许可服务提供的一些应用程序配置数据的示例。这是将被包含在图3.3中提到的confsvr/src/main/resources/config/licensingservice/licensingservice.yml文件中的数据。这里是这个文件的部分内容:
tracer.property: "I AM THE DEFAULT"
spring.jpa.database: "POSTGRESQL"
spring.datasource.platform: "postgres"
spring.jpa.show-sql: "true"
spring.database.driverClassName: "org.postgresql.Driver" spring.datasource.url: "jdbc:postgresql://database:5432/eagle_eye_local" spring.datasource.username: "postgres"
spring.datasource.password: "p0stgr@s"
spring.datasource.testWhileIdle: "true"
spring.datasource.validationQuery: "SELECT 1"
spring.jpa.properties.hibernate.dialect:
"org.hibernate.dialect.PostgreSQLDialect"
Think before you implement
我建议不要使用基于文件系统的解决方案来处理大中型云应用程序。使用文件系统方法意味着你需要为想要访问应用程序配置数据的所有云配置服务器实现共享文件挂载点。在云中设置共享文件系统服务器是可行的,但是这导致把维护这个环境的责任放到了你身上。我将文件系统方法展示为使用Spring Cloud配置服务器是因为这是最容易使用的示例。在后面的章节中,我将介绍如何配置Spring Cloud配置服务器以使用基于云的Git提供程序(如Bitbucket或GitHub)来存储应用程序配置。
3.2.1 设置Spring Cloud Config Bootstrap类
本书涵盖的每个Spring Cloud服务总是需要一个启动类来启动服务。这个启动类将包含两件事情:一个Java main() 方法作为服务启动的入口点,一套Spring Cloud注解告诉启动服务将启用哪些Spring Cloud行为。
下面的清单显示了用作配置服务启动类的confsvr/src/main/java/com/thoughtmechanix/confsvr/Application.java。
package com.thoughtmechanix.confsvr;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;
@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class, args)
}
}
接下来,将使用最简单的示例来设置你的Spring Cloud配置服务器:文件系统。
3.2.2 使用Spring Cloud配置服务器和文件系统
Spring Cloud配置服务器使用confsvr/src/main/resources/application.yml文件中的条目来指向将存放应用程序配置数据的存储库。建立一个基于文件系统的存储库是完成这个任务的最简单的方法。
为此,请将以下信息添加到配置服务器的application.yml文件中。以下清单显示了你的Spring Cloud配置服务器的application.yml文件的内容。
server:
port: 8888
spring:
profiles:
active: native
cloud:
config:
server:
native:
searchLocations: file:///Users/johncarnell1/book/native_cloud_apps/ch4-config-managment/confsvr/src/main/resources/config/licensingservice
在这个清单中的配置文件中,你首先告诉配置服务器它应该监听所有配置请求的端口号:
server:
port: 8888
由于你使用文件系统来存储应用程序配置信息,因此你需要告知Spring Cloud配置服务器使用“native” profile:
profiles:
active: native
application.yml文件中的最后一部分提供了Spring Cloud配置以及应用程序配置数据所在的目录:
server:
native:
searchLocations: file:///Users/johncarnell1/book/native_cloud_apps/ch4-config-managment/confsvr/src/main/resources/config/licensingservice
配置条目中的重要参数是searchLocations属性。此属性为每个将要由配置服务器管理配置属性的应用程序提供逗号分隔的目录列表。在前面的例子中,你只配置了许可服务。
注意 如果你使用Spring Cloud Config的本地文件系统版本,那么在本地运行你的代码时,你需要修改spring.cloud.config.server.native.searchLocations属性以反映你的本地文件路径。
现在已经完成了足够的工作来启动配置服务器。使用mvn spring-boot:run命令启动配置服务器。服务器现在应该在命令行上显示Spring Boot启动画面。如果你将浏览器指向http://localhost:8888/licensingservice/default,你将看到JSON有效负载将与licensesservice.yml文件中包含的所有属性一起返回。图3.4显示了访问这个端点的结果。
如果要查看开发环境的许可服务的配置信息,请点击GET http://localhost:8888/licensingservice/dev端点。图3.5显示了访问这个端点的结果:
如果仔细观察,你会看到当你访问开发端点时,将同时返回许可服务的默认配置属性和开发环境配置属性。Spring Cloud Config返回两组配置信息的原因是,Spring框架实现了解析属性的分层机制。当Spring框架执行属性解析时,它将始终首先在默认属性中查找属性,然后使用特定于环境的值(如果存在)覆盖默认属性。
具体而言,如果你在licensingservice.yml文件中定义一个属性,并且不在任何其他环境配置文件(例如licensingservice-dev.yml)中定义它,则Spring框架将使用默认值。
注意 这不是你直接访问Spring Cloud Config REST端点所看到的行为。 REST端点将返回被访问的默认配置值和环境特定的所有配置值。
让我们看看如何将Spring Cloud配置服务器勾联到你的许可微服务。
3.3 将Spring Cloud Config与Spring Boot客户端集成
在前一章中,你构建了一个许可服务的简单框架,只不过是从数据库中返回代表单个许可记录的硬编码Java对象。在下一个示例中,你将构建许可服务并与存有许可数据的Postgres数据库进行交互。
你将使用Spring Data与数据库进行通信,并将数据从许可表映射到持有数据的POJO。你的数据库连接和一个简单的属性将从Spring Cloud配置服务器被读出。图3.6显示了许可服务与Spring Cloud配置服务之间发生了什么事情。
当许可服务首次启动时,你将通过命令行传递两条信息:Spring profile以及许可服务与Spring Cloud配置服务进行通信的端点。Spring profile值映射到为Spring服务检索属性的环境上。当许可服务首次启动时,它将通过传入Spring Profile的端点联系Spring Cloud Config Service。然后,Spring Cloud Config Service将使用配置的后端配置库(filesystem,Git,Consul,Eureka)来检索于URI的Spring传入的profile值匹配的特定的配置信息。然后将适当的属性值传回给许可服务。 Spring Boot框架然后将这些值注入到应用程序的相应部分。
3.3.1 设置许可服务Spring Cloud Config 服务器依赖
让我们把关注点从配置服务器移到许可服务。你需要做的第一件事是在许可服务中向Maven文件添加更多条目。以下列表中显示了需要添加的条目。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>9.1-901.jdbc4</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-client</artifactId>
</dependency>
第一个和第二个依赖项spring-boot-starter-data-jpa和PostgreSQL导入了Spring Data Java Persistence API(JPA)和Postgres JDBC驱动程序。最后一个依赖项spring-cloud-config-client包含与Spring Cloud配置服务器进行交互所需的所有类。
配置许可服务以使用Spring Cloud Config
在定义了Maven依赖关系后,你需要告知许可服务如何连接Spring Cloud配置服务器。在使用Spring Cloud Config的Spring Boot服务中,可以在两个配置文件(bootstrap.yml和application.yml)之一中设置配置信息。
bootstrap.yml文件在使用任何其他配置信息之前读取应用程序属性。通常,bootstrap.yml文件包含服务的应用名,应用程序profile以及连接到Spring Cloud Config服务器的URI。你希望保留在服务(而不是存储在Spring Cloud Config中)的任何其他本地配置信息都可以在application.yml文件中进行设置。通常,你存储在application.yml文件中的信息是即使Spring Cloud Config服务不可用,也可能希望自身服务可用的配置数据。 bootstrap.yml和application.yml文件都存储在项目的src/main/resources目录中。
要让许可服务与Spring Cloud Config服务进行通信,你需要添加一个licensing-service/src/main/resources/bootstrap.yml文件并在其中设置三个属性:spring.application.name、spring.profiles.active、spring.cloud.config.uri
以下列表中显示了许可服务的bootstrap.yml文件。
spring:
application:
name: licensingservice
profiles:
active:
default
cloud:
config:
uri: http://localhost:8888
注意 Spring Boot应用程序支持两种机制来定义属性:YAML(另一种标记语言)和一个"."分隔的属性名称。我们选择了YAML(另一种标记语言)作为配置我们的应用程序的手段。 YAML属性值的分层格式直接映射到spring.application.name、spring.profiles.active、spring.cloud.config.uri
名称。
spring.application.name是你的应用程序名(例如licensingservice),并且必须直接映射到你的Spring Cloud Config Server中的目录名称。对于许可服务,你需要Spring Cloud Config Server上建立名为licensingservice的目录。第二个属性spring.profiles.active用于告诉Spring Boot应该运行为哪个profile。profile是区分Spring Boot应用程序所使用的配置数据的机制。对于许可服务的profile,支持将直接映射到你的云配置环境中的环境。例如,通过传入dev作为我们的profile,Spring Cloud配置服务器将使用dev配置属性。如果你没有设置profile,则许可服务将使用默认profile。第三个也是最后一个属性spring.cloud.config.uri是许可服务应该查找Spring Cloud配置服务器端点的位置。默认情况下,许可服务将在http://localhost:8888中查找配置服务器。本章稍后将介绍如何在应用程序启动时覆盖boostrap.yml和application.yml文件中定义的不同属性。这将允许你告诉许可微服务应该运行在哪个环境。现在,如果你启动Spring Cloud配置服务,并在你的本地计算机上运行相应的Postgres数据库,则可以使用其默认profile启动许可服务。这是通过改变许可服务目录并发出以下命令来完成:
mvn spring-boot:run
通过运行此命令并且不设置任何属性集合,许可服务器将自动使用默认的连接端点(http://localhost:8888)和在许可服务的bootstrap.yml文件中定义的active profile(默认)来连接到Spring Cloud配置服务器。
如果要覆盖这些默认值并指向另一个环境,可以通过将licensingservice项目编译为JAR,然后使用覆盖的-D系统属性运行JAR来完成此操作。以下命令行演示了如何使用非默认配置文件启动许可服务:
java -Dspring.cloud.config.uri=http://localhost:8888 \
-Dspring.profiles.active=dev \
-jar target/licensing-service-0.0.1-SNAPSHOT.jar
使用上面的命令行,你将覆盖两个参数:spring.cloud.config.uri和spring.profiles.active。使用-Dspring.cloud.config.uri=http://localhost:8888系统属性,可以指向非本地的远程配置服务器。
注意 如果你尝试使用先前的Java命令运行从GitHub存储库(https://github.com/carnellj/spmia-chapter3)下载的许可服务,会失败,因为你没有运行Postgres服务器,GitHub仓库中的源代码在配置服务器上使用了加密。本章稍后会介绍使用加密技术。上面的例子演示了如何通过命令行覆盖Spring的属性。
使用-Dspring.profiles.active=dev系统属性,你告诉授权服务使用dev profile(从配置服务器读取)连接到数据库的dev实例。
使用环境变量传递启动信息
在这个例子中,你正在用硬编码的值传递给-D参数值。在云中,你需要的大部分应用程序配置数据都将位于你的配置服务器中。但是,对于启动服务需要的信息(例如配置服务器的信息),将通过启动VM实例或Docker容器时传入一个环境变量。所有的章节的代码示例都可以从Docker容器中完全运行。通过Docker,你可以通过环境特定的Docker-compose文件来模拟不同的环境,这些文件可以协调所有服务的启动。容器所需的环境特定值作为环境变量传递到容器。例如,要在开发环境中启动许可服务,docker/dev/docker-compose.yml文件包含以下用于许可服务的条目:
licensingservice:
image: ch3-thoughtmechanix/licensing-service
ports:
- "8080:8080"
environment:
PROFILE: "dev"
CONFIGSERVER_URI: http://configserver:8888
CONFIGSERVER_PORT: "8888"
DATABASESERVER_PORT: "5432"
文件中的环境条目包含两个变量PROFILE的值,这是许可服务将要运行的Spring Boot profile。 CONFIGSERVER_URI传递并定义许可服务将从其读取配置数据的Spring Cloud配置服务器实例。
在由容器运行的启动脚本中,然后将这些环境变量作为-D参数传递给启动应用程序的JVMS。在每个项目中,你将烘焙Docker容器,并且在该Docker容器使用一个启动脚本来启动容器中的软件。对于许可服务,可以在licensing-service/src/main/docker/run.sh中找到容器中的启动脚本。在run.sh脚本中,以下条目将启动你的许可服务JVM:
echo "********************************************************"
echo "Starting License Server with Configuration Service :
$CONFIGSERVER_URI";
echo "********************************************************" java -Dspring.cloud.config.uri=$CONFIGSERVER_URI -Dspring.profiles.active=$PROFILE -jar /usr/local/licensingservice/
licensing-service-0.0.1-SNAPSHOT.jar
因为通过Spring Boot Actuator可以通过自检你的所有服务,可以通过访问http://localhost:8080/env来确认所运行的环境。/env端点将提供服务配置信息的完整列表,包括服务启动的属性和所在的端点,如图3.7所示。从图3.7中要注意的是,许可服务的活动profile是dev。通过检查返回的JSON,你还可以看到正在返回的Postgres数据库是jdbc:postgresql://database:5432/eagle_eye_dev的开发URI。
暴露太多的信息
如何在服务周围实现安全,每个组织都有不同的规则。许多组织认为,服务不应该广播任何关于自己的信息,也不会允许诸如/env端点之类的东西在服务上处于活动状态,因为他们认为这样做会为潜在的黑客提供太多的信息。Spring Boot提供了丰富的功能,用于配置Spring Actuators端点所返回的信息,这些信息不在本书的范围之内。在克雷格·沃尔斯(Craig Walls)出色的书籍“Spring Boot in Action”中详细介绍了这一主题,我强烈建议你阅读公司的安全策略和Walls的书籍,以提供你希望通过Spring Actuator公开的正确安全级别的细节。
3.3.3 使用Spring Cloud配置服务器接线数据源
到此,你将数据库配置信息直接注入到你的微服务中。通过设置数据库配置,配置你的许可服务变成了使用标准Spring组件来从Postgres数据库构建和检索数据的练习。许可服务已经被重构为几个不同的类,每个类都有不同的职责。这些类如表3.2所示。
License类是持有从许可数据库中检索的数据的模型类。以下清单展示了License类的代码。
package com.thoughtmechanix.licenses.model;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name = "licenses")
public class License{
@Id
@Column(name = "license_id", nullable = false)
private String licenseId;
@Column(name = "organization_id", nullable = false)
private String organizationId;
@Column(name = "product_name", nullable = false)
private String productName;
/*The rest of the code has been removed for conciseness*/
}
这个类使用了几个Java Persistence Annotation(JPA),它们可以帮助Spring Data框架将Postgres数据库中licenses表中的数据映射到Java对象。 @Entity注解让Spring知道这个Java POJO将会映射到保存数据的对象。 @Table注解告诉Spring/JPA应该映射哪个数据库表。@Id注解标识数据库的主键。最后,将要映射到单个属性的数据库中的每一列标记为@Column属性。
Spring Data和JPA框架提供了访问数据库的基本CRUD方法。如果你想构建超越这个方法的方法,你可以使用Spring Data Repository接口和基本的命名约定来构建这些方法。Spring将在启动时从Repository接口解析方法的名称,并将它们转换为基于名称的SQL语句,然后生成一个动态代理类来进行工作。以下列表中展示了许可服务的repository。
package com.thoughtmechanix.licenses.repository;
import com.thoughtmechanix.licenses.model.License;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface LicenseRepository
extends CrudRepository<License,String> {
public List<License> findByOrganizationId (String organizationId);
public License findByOrganizationIdAndLicenseId (String organizationId,String licenseId);
}
repository接口LicenseRepository标有@Repository注解,告诉Spring它应该把这个接口当作一个repository,并为它生成一个动态代理。Spring为数据访问提供了不同类型的repository。你已经选择使用Spring CrudRepository基类来扩展你的LicenseRepository类。CrudRepository基类包含基本的CRUD方法。除了从CrudRepository扩展的CRUD方法之外,你还添加了两个自定义查询方法来从许可表中检索数据。Spring Data框架将拆开方法的名字来构建一个查询,来访问底层的数据。
注意 Spring Data框架在各种数据库平台上提供了一个抽象层,并不仅限于关系数据库。也支持如MongoDB和Cassandra这样的NoSQL数据库。
与第2章中许可服务的先前版本不同,现在你已将许可服务的业务和数据访问逻辑从LicenseController中分离出来,并分离到LicenseService的独立服务类中。
package com.thoughtmechanix.licenses.services;
import com.thoughtmechanix.licenses.config.ServiceConfig;
import com.thoughtmechanix.licenses.model.License;
import com.thoughtmechanix.licenses.repository.LicenseRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.UUID;
@Service
public class LicenseService {
@Autowired
private LicenseRepository licenseRepository;
@Autowired
ServiceConfig config;
public License getLicense(String organizationId,String licenseId) {
License license = licenseRepository.findByOrganizationIdAndLicenseId(
organizationId, licenseId);
return license.withComment(config.getExampleProperty());
}
public List<License> getLicensesByOrg(String organizationId){
return licenseRepository.findByOrganizationId( organizationId );
}
public void saveLicense(License license){
license.withId( UUID.randomUUID().toString());
licenseRepository.save(license);
}
/*Rest of the code removed for conciseness*/
}
controller,service,和repository类使用标准的Spring @Autowired 注解连接在一起。
3.3.4 直接使用@Value注解读取属性
在上一节的LicenseService类中,你可能已经注意到你正在使用config.getExampleProperty()中获取的值在getLicense()代码中设置license.withComment()。代码如下所示:
public License getLicense(String organizationId,String licenseId) {
License license = licenseRepository.findByOrganizationIdAndLicenseId(
organizationId, licenseId);
return license.withComment(config.getExampleProperty());
}
如果你查看licensing-service/src/main/java/com/ thinkmechanix/licenses/config/ServiceConfig.java类,你将看到使用@Value注解的属性。以下列表显示正在使用的@Value注解。
package com.thoughtmechanix.licenses.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
public class ServiceConfig {
@Value("${example.property}")
private String exampleProperty;
public String getExampleProperty(){
return exampleProperty;
}
}
虽然Spring Data“自动奇迹地”将数据库的配置数据注入到数据库连接对象中,但所有其他属性必须使用@Value注解注入。在前面的示例中,@Value批注从Spring Cloud配置服务器中提取example.property,并将其注入到ServiceConfig类的example.property属性中。
提示 虽然可以直接将配置值注入到各个类中的属性中,但是我发现将所有配置信息集中到一个配置类中,然后将该配置类注入到需要的地方是很有用的。
3.3.5 使用Spring Cloud配置服务器和Git
如前所述,使用文件系统作为Spring Cloud配置服务器的后端存储库对于基于云的应用程序来说可能是不切实际的,因为开发团队必须设置和管理在Cloud配置服务器的所有实例上挂载的共享文件系统。Spring Cloud配置服务器与不同的后端存储库集成,可用于托管应用程序配置属性。我成功使用的一个方法是将Spring Cloud配置服务器与Git源代码控制库一起使用。通过使用Git,你可以获得将配置管理属性置于源代码控制之下的所有好处,并提供一种简单的机制,将你的属性配置文件的部署集成到构建和部署管道中。要使用Git,你需要在配置服务的bootstrap.yml文件中使用下面的配置替换掉文件系统的配置。
server:
port: 8888
spring:
cloud:
config:
server:
git:
uri: https://github.com/carnellj/config-repo/
searchPaths:licensingservice,organizationservice
username: native-cloud-apps
password: 0ffended
上例中的三个关键配置是spring.cloud.config.server,spring.cloud.config.server.git.uri和spring.cloud.config.server.git.searchPaths属性。 spring.cloud.config.server属性告诉Spring Cloud配置服务器使用一个基于非文件系统的后端库。在前面的示例中,你将连接到基于云的Git存储库GitHub。spring.cloud.config.server.git.uri属性提供了你要连接到的存储库的URL。最后,spring.cloud.config.server.git.searchPaths属性告诉Spring Cloud Config服务器,当Cloud配置服务器启动时,应该在Git存储库上搜索的相对路径。像配置的文件系统版本一样,spring.cloud.config.server.git.seachPaths属性中的值将配置服务托管的每个服务的逗号分隔的列表。
3.3.6 使用Spring Cloud配置服务器刷新你的属性
开发团队希望使用Spring Cloud配置服务器时遇到的第一个问题是,如何在属性变更时动态刷新应用程序。Spring Cloud配置服务器将始终提供最新版本的属性。通过其底层存储库对属性所做的更改将是最新的。但是,Spring Boot应用程序只会在启动时读取它们的属性,所以Spring 配置服务器中所做的属性变更将不会被Spring Boot应用程序自动获取。Spring Boot Actuator确实提供了一个@RefreshScope注解,允许开发团队访问一个/refresh端点,以强制Spring Boot应用程序重新读取其应用程序配置。下面的清单显示了@RefreshScope注解的作用。
package com.thoughtmechanix.licenses;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.context.config.annotation.RefreshScope;
@SpringBootApplication
@RefreshScope
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
请注意关于@RefreshScope注解的一些事情。首先,注解只会重载你在应用程序配置中客制的Spring属性。 Spring Data使用的数据库配置等项目不会被@RefreshScope注解重新加载。要执行刷新,可以访问http://:8080/refresh端点。
刷新微服务
将Spring Cloud配置服务与微服务一起使用时,在动态变更属性之前需要考虑的一件事情是,你可能会运行同一微服务的多个实例,并且需要使用新的应用程序配置来刷新所有这些微服务。有几种方法可以解决这个问题:
Spring Cloud配置服务确实提供了一种名为“Spring Cloud Bus”的“推送”机制,允许Spring Cloud配置服务器向所有使用配置服务的客户端发布已发生变更的情况。 Spring Cloud配置需要运行一个额外的中间件(RabbitMQ)。这是检测变化的非常有用的手段,但并不是所有的Spring云配置后端都支持“推送”机制(即Consul服务器)。在下一章中,你将使用Spring Service Discovery和Eureka来注册服务的所有实例。我用来处理应用程序配置刷新事件的一种技术是刷新Spring Cloud配置中的应用程序属性,然后编写一个简单的脚本来查询服务发现引擎,以查找服务的所有实例并直接调用/refresh端点。最后,你可以重新启动所有的服务器或容器来获取新的属性。这是一个微不足道的练习,特别是如果你在Docker等容器服务中运行服务时。重新启动Docker容器需要几秒钟的时间,并会强制重新读取应用程序配置。请记住,基于云的服务器是临时的。不要害怕使用新配置启动服务的新实例,将流量引导至新服务,然后拆除旧服务。
3.4 保护敏感的配置信息
默认情况下,Spring云配置服务器以纯文本格式将所有属性存储在应用程序的配置文件中。这包括敏感信息,如数据库凭证。将敏感凭证作为纯文本存储在你的源代码库中是非常糟糕的做法。不幸的是,这发生得要比你想象的多得多。Spring Cloud Config可以让你轻松加密敏感属性。Spring Cloud Config支持使用对称(共享密钥)和不对称加密(公钥/私钥)。我们将看看如何设置Spring Cloud配置服务器以使用对称密钥加密。要做到这一点,你需要
下载并安装加密所需的 Oracle JCE jars
设置一个加密密钥
加密和解密一个属性
配置微服务在客户端使用加密
3.4.1 下载并安装加密所需的 Oracle JCE jar
首先,你需要下载并安装Oracle的无限强度Java密码扩展(JCE)。这不是通过Maven提供的,必须从Oracle公司下载。一旦下载了包含JCE jar的zip文件,你必须执行以下操作:
找到你的$JAVA_HOME/jre/lib/security目录。
备份$JAVA_HOME/jre/lib/security目录中的local_policy.jar和US_export_policy.jar到不同的位置。
解压从Oracle下载的JCE zip文件。
将local_policy.jar和US_export_policy.jar复制到你的$JAVA_HOME/jre/lib/security。
配置Spring Cloud Config以使用加密。
自动化安装Oracle JCE文件的过程
我已经描述了在你的笔记本电脑上安装JCE所需的手动步骤。因为我们将所有服务构建为Docker容器,所以我已经在Spring Cloud Config Docker容器中脚本化了这些JAR文件的下载和安装。下面的OS X shell脚本片段显示了如何使用curl(https://curl.haxx.se/)命令行工具自动执行此操作:
cd /tmp/
curl -k-LO "http://download.oracle.com/otn-pub/java/jce/8/jce_policy-
8.zip"
-H 'Cookie:oraclelicense = accept-securebackup-cookie' && unzip
jce_policy-8.zip
rm jce_policy-8.zip
yes | cp -v /tmp/UnlimitedJCEPolicyJDK8/*.jar /usr/lib/jvm/java-1.8-openjdk/jre/lib/security
我不打算详细介绍所有的细节,但基本上我使用CURL来下载JCE zip文件(注意curl命令中通过-H属性传递的Cookie头参数),然后解压缩文件并将其复制到我的Docker容器中的/usr/lib/jvm/java-1.8-openjdk/jre/lib/security目录。
如果你查看本章源代码中的src/main/docker/ Dockerfile文件,你可以看到这个脚本实例。
3.4.2 设置加密密钥
一旦JAR文件就位,你需要设置一个对称加密密钥。对称加密密钥只不过是加密器用来加密值和解密器解密值的共享密钥。使用Spring Cloud配置服务器,对称加密密钥是你选择的字符串,通过名为ENCRYPT_KEY的操作系统环境变量传递给服务。在本书中,你总是将ENCRYPT_KEY环境变量设置为
export ENCRYPT_KEY=IMSYMMETRIC
注意关于对称密钥的两件事:
对称密钥的长度应该是12个或更多字符,最好是随机的一组字符。
不要丢失你的对称密钥。否则一旦你用加密密钥加密了一些东西,你就不能解密它了。
管理加密密钥
在本书中,我做了两件通常我不推荐在生产环境中使用事:
我将加密密钥设置为一个短语。我想把密钥保持简单,这样我就可以记住它,它很适合使用可阅读的文本。在真实世界的部署中,我将为每个部署的环境使用一个单独的加密密钥,并使用随机字符作为我的密钥。我直接在书中使用的Docker文件中硬编码了ENCRYPT_KEY环境变量。我这样做是为了让读者可以下载文件并启动它们,而不必记住设置环境变量。在真正的运行时环境中,我将在我的Dockerfile中引用ENCRYPT_KEY作为操作系统环境变量。注意这一点,不要在你的Dockerfiles里面硬编码你的加密密钥。同时记住,你的Dockerfiles应该在源代码版本控制之下。
3.4.3 加密和解密一个属性
你现在已经准备好开始加密Spring Cloud Config中使用的属性了。你将加密你用来访问EagleEye数据的许可服务Postgres数据库密码。这个名为spring.datasource.password的属性当前被设置为纯文本p0stgr@s。当你启动Spring Cloud Config实例时,Spring Cloud Config将检测到已设置ENCRYPT_KEY环境变量,并自动为Spring Cloud Config服务添加两个新端点(/ encrypt和/decrypt)。你将使用/encrypt端点来加密p0stgr@s值。
图3.8显示了如何使用/encrypt端点和POSTMAN加密p0stgr@s的值。请注意无论何时使用/encrypt 或者/decrypt端点,你要确保对这些端点进行POST访问。如果你想解密这个值,你可以向/decrypt端点传入加密了的字符串。你现在可以使用以下语法将加密过的属性添加到你的GitHub或基于文件系统的授权服务的配置文件中:
spring.datasource.password:"{cipher}858201e10fe3c9513e1d28b33ff417a66e8c8411dcff3077c53cf53d8a1be360"
Spring Cloud配置服务器要求所有加密的属性前缀为{cipher}。 {cipher}值告诉Spring Cloud配置服务器正在处理的是个加密值。启动你的Spring Cloud配置服务器并点击GET http://localhost:8888/licensingservice/default端点。
图3.9显示了这个访问的结果。
你已经通过加密属性使spring.datasource.password更安全,但是仍然有问题。当你点击http://localhost:8888/licensingservice/default端点时,数据库密码将以纯文本形式公开。
默认情况下,Spring Cloud Config将在服务器上执行所有的属性解密,并将结果传回给消费属性的应用程序,是未加密的明文文本。但是,你可以告诉Spring Cloud Config在服务器上不要解密,并使其成为应用程序检索配置数据以解密加密属性的责任。
配置微服务在客户端使用加密属性
要启用客户端解密属性,你需要做三件事情:
将Spring Cloud Config配置为在服务器端不解密属性
在许可服务器上设置对称密钥
将 spring-security-rsa JAR添加到许可服务的pom.xml文件中。
你需要做的第一件事是在Spring Cloud Config中禁用服务器端属性解密。这可以通过设置Spring Cloud Config的src/main/resources/application.yml文件来设置属性spring.cloud.config.server.encrypt.enabled:false。这就是你在Spring Cloud Config服务器上必须做的所有事情。
由于许可服务现在负责解密已加密的属性,因此你需要首先在许可服务上设置对称密钥,方法是确保ENCRYPT_KEY环境变量设置了与你的Spring Cloud Config服务器使用的相同的对称密钥(例如,IMSYMMETRIC)。接下来,你需要在许可服务中包含spring-security-rsa JAR依赖项:
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-rsa</artifactId>
</dependency>
这些JAR文件包含解密从Spring Cloud Config检索的加密属性所需的Spring代码。通过这些更改,你可以启动Spring Cloud Config和许可服务。如果你点击http://localhost:8888/licensingservice/default端点,你将看到在其中返回的spring.datasource.password是加密的形式。图3.10 展示了访问的输出:
3.5 闭幕之思
应用程序配置管理看起来可能像一个平凡的话题,但在基于云的环境中,这是至关重要的。正如我们将在后面的章节中更详细地讨论的那样,关键是你的应用程序和运行的服务器是不变的,整个服务器不会在环境之间手动配置。这在传统的部署模型中可以正常工作,在这些模型中,将应用程序工件(例如JAR或WAR文件)及其属性文件部署到“固定”环境。使用基于云的模型,应用程序配置数据应该与应用程序完全分离,并且需要在运行时注入相应的配置数据,以便在所有环境中始终如一地提供相同的服务器/应用程序工件。
3.6 本章概要
Spring Cloud配置服务器允许你使用特定于环境的值设置应用程序属性。
Spring使用Spring配置文件启动服务,以确定从Spring Cloud Config服务中检索哪些环境属性
SpringCloud配置服务可以使用基于文件或基于Git的应用程序配置存储库来存储应用程序属性。
SpringCloud配置服务允许你使用对称和非对称加密来加密敏感属性文件。
3.7 关联阅读
微服务与配置中心:别让您的微服务被配置管理“绊”了一跤
QCon演讲视频
AliExpress基于配置中心ACM的微服务配置管理实践
使用阿里云ACM简化你的Spring Cloud微服务环境配置管理
文章
存储 · 数据库 · 数据安全/隐私保护 · 容器 · Spring
2018-01-04
systemd简介
systemd是Linux的一个工具,是CentOS7.x系统启动的第一个进程
1.systemd的一些相关命令
(1)systemctl 系统管理
#检查系统是否充分的运行
[root@localhost ~]# systemctl is-system-running
running
#进入系统默认模式
[root@localhost ~]# systemctl default
PolicyKit daemon disconnected from the bus.
We are no longer a registered authentication agent.
#进入营救模式
[root@localhost ~]# systemctl rescue
Broadcast message from root@localhost.localdomain on pts/0 (Mon 2018-03-05 23:25:23 CST):
The system is going down to rescue mode NOW!
[root@localhost ~]# systemctl is-system-running
maintenance
#进入紧急情况模式
[root@localhost ~]# systemctl emergency
#进入冬眠模式
[root@localhost ~]# systemctl hibernate
[root@localhost ~]# systemctl half
[root@localhost ~]# systemctl poweroff
[root@localhost ~]# systemctl reboot
(2)systemd-analyze查看启动耗时
[root@localhost ~]# systemd-analyze
Startup finished in 849ms (kernel) + 2.347s (initrd) + 20.205s (userspace) = 23.402s
#查看每个服务启动耗时
[root@localhost ~]# systemd-analyze blame
#查看指定服务的启动流
[root@localhost ~]# systemd-analyze critical-chain httpd.service
The time after the unit is active or started is printed after the "@" character.
The time the unit takes to start is printed after the "+" character.
httpd.service +115ms
└─system.slice
└─-.slice
[root@localhost ~]# systemd-analyze critical-chain sshd.service
The time after the unit is active or started is printed after the "@" character.
The time the unit takes to start is printed after the "+" character.
sshd.service @9min 39.109s
└─system.slice
└─-.slice
(3)hostnamectl
#查看主机名等信息
[root@localhost ~]# hostnamectl
#修改主机名,exit会话或者reboot生效
[root@localhost ~]# hostnamectl set-hostname
(4)localectl
#查看本地化设置
[root@darren ~]# localectl
System Locale:.UTF-8
VC Keymap: us
X11 Layout: us
[root@darren ~]# localectl set-locale.UTF-8
[root@darren ~]# localectl list-locales|grep 'zh_CN'
(5)timedatectl
[root@darren ~]# timedatectl
Local time: Tue 2018-03-06 19:05:28 CST
Universal time: Tue 2018-03-06 11:05:28 UTC
RTC time: Tue 2018-03-06 12:22:53
Time zone: Asia/Shanghai (CST, +0800)
NTP enabled: n/a
NTP synchronized: no
RTC in local TZ: no
DST active: n/a
[root@darren ~]# timedatectl list-timezones
#设置时间
[root@darren ~]# timedatectl set-time 2018-03-06
[root@darren ~]# timedatectl set-time 20:31
(6)loginctl用户连接相关信息
Tue Mar 6 20:31:53 CST 2018
[root@darren ~]# loginctl
SESSION UID USER SEAT
19 0 root
24 0 root
23 0 root
[root@darren ~]# loginctl list-users
UID USER
0 root
#查看root用户的详细信息
[root@darren ~]# loginctl show-user root
2.服务管理systemctl
(1)自启动原理
对于支持systemd的软件,安装后,会自动在/usr/lib/systemd/system目录下生成一个xxx.service配置文件.
系统启动的时候会读取/etc/systemd/system下的配置文件,所以会开启自启动。
例:
开启httpd自启动的实质是在/etc/systemd/system/multi-user.target.wants下创建一个软连接指向/usr/lib/systemd/system/httpd.service文件
[root@localhost system]# systemctl is-enabled httpd.service
disabled
[root@localhost system]# systemctl enable httpd.service
Created symlink from /etc/systemd/system/multi-user.target.wants/httpd.service to /usr/lib/systemd/system/httpd.service.
[root@localhost ~]# ll /etc/systemd/system/multi-user.target.wants/httpd.service
lrwxrwxrwx. 1 root root 37 Feb 24 04:03 /etc/systemd/system/multi-user.target.wants/httpd.service -> /usr/lib/systemd/system/httpd.service
启动一个服务:systemctl start postfix.service
关闭一个服务:systemctl stop postfix.service
重启一个服务:systemctl restart postfix.service
显示一个服务的状态:systemctl status postfix.service
开启开机自启动:systemctl enable postfix.service
关闭开机自启动:systemctl disable postfix.service
查看服务是否开机启动:systemctl is-enabled postfix.service
查看服务是否活动:systemctl is-active httpd.service
查看已启动的服务列表:systemctl list-unit-files|grep enabled
删除httpd进程: systemctl kill httpd.service
修改配置文件后,需要重新加载配置文件: systemctl daemon-reload
查看服务的依赖关系:systemctl list-dependencies httpd.service
(2)列出所有unit及配置文件的状态
配置文件状态种类:
enabled:已建立自启动链接
disabled:没有建立自启动链接
static:该配置文件没有[Install]部分(无法执行),只能作为其他配置文件的依赖
masked:该配置文件被禁止建立自启动链接
[root@darren system]# systemctl list-unit-files
autovt@.service disabled
brandbot.service static
httpd.service enabled
(3)配置文件的格式说明
[root@darren system]# cat httpd.service
[Unit]
Description=The Apache HTTP Server
After=network.target remote-fs.target nss-lookup.target
Documentation=man:httpd(8)
Documentation=man:apachectl(8)
[Service]
Type=notify
EnvironmentFile=/etc/sysconfig/httpd
#ExecStart=/usr/sbin/httpd $OPTIONS -DFOREGROUND
ExecReload=/usr/sbin/httpd $OPTIONS -k graceful
ExecStop=/bin/kill -WINCH ${MAINPID}
# We want systemd to give httpd some time to finish gracefully, but still want
# it to kill httpd after TimeoutStopSec if something went wrong during the
# graceful stop. Normally, Systemd sends SIGTERM signal right after the
# ExecStop, which would kill httpd. We are sending useless SIGCONT here to give
# httpd time to finish.
KillSignal=SIGCONT
PrivateTmp=true
[Install]
WantedBy=multi-user.target
[Unit]区块通常是配置文件的第一个区块,用来定义Unit的元数据,以及该服务与其他Unit的关系
Description:描述信息
Documentation:文档地址
After:在httpd之前需要启动的unit
Before:在httpd之后需要启动的unit
Requires:依赖的unit启动,该unit才能启动,如After模块的unit没有运行,当前Unit则不会启动
Wants:和Requires相似,但是没有它严格,如After模块的unit没有运行,当前Unit也可成功启动个(推荐使用)
[Service]服务的配置
Type选项:
Type=simple :(默认值) systemd认为该服务将立即启动。服务进程不会 fork 。如果该服务要启动其他服务,不要使用此类型启动,除非该服务是socket 激活型。
Type=forking :systemd认为当该服务进程fork,且父进程退出后服务启动成功。对于常规的守护进程(daemon),除非你确定此启动方式无法满足需求,使用此类型启动即可。使用此启动类型应同时指定 PIDFile=,以便 systemd 能够跟踪服务的主进程。
Type=oneshot :这一选项适用于只执行一项任务、随后立即退出的服务。可能需要同时设置 RemainAfterExit=yes 使得 systemd 在服务进程退出之后仍然认为服务处于激活状态。
Type=notify :与 Type=simple 相同,但约定服务会在就绪后向 systemd 发送一个信号。这一通知的实现由 libsystemd-daemon.so 提供。
Type=dbus :若以此方式启动,当指定的 BusName 出现在DBus系统总线上时,systemd认为服务就绪。
Type=idle :systemd会等待所有任务处理完成后,才开始执行 idle 类型的单元。其他行为与 Type=simple 类似。
[Install]通常是配置文件的最后一个区块,用来定义如何启动,以及是否开机启动。
WantedBy=multi-user.target
3.日志管理journalctl
配置文件:/etc/systemd/journald.conf
journalctl -b -0 显示本次启动的信息
journalctl -b -1 显示上次启动的信息
journalctl -b -2 显示上上次启动的信息 journalctl -b -2
只显示错误、冲突和重要告警信息
journalctl -p err..alert 等价于: journalctl -p 3..1
显示从某个日期 ( 或时间 ) 开始的消息:
journalctl --since="2012-10-30 18:17:16"
显示从某个时间 ( 例如 20分钟前 ) 的消息:
journalctl --since "20 min ago"
显示最新信息
journalctl -f
显示特定程序的所有消息:
# journalctl /usr/lib/systemd/systemd
显示特定进程的所有消息:
journalctl _PID=1
参考链接:https://wiki.archlinux.org/index.php/systemd_(%E7%AE%80%E4%BD%93%E4%B8%AD%E6%96%87)#.E5.A4.84.E7.90.86.E4.BE.9D.E8.B5.96.E5.85.B3.E7.B3.BB
Qt Mode/View 学习笔记 —— 概述和Model
介绍
Qt 4推出了一组新的item view类,它们使用model/view结构来管理数据与表示层的关系。这种结构带来的功能上的分离给了开发人员更大的弹性来定制数据项的表示,它也提供一个标准的model接口,使得更多的数据源可以被这些item view使用。这里对model/view的结构进行了描述,结构中的每个组件都进行了解释,给出了一些例子说明了提供的这些类如何使用。
Model/View 结构
Model-View-Controller(MVC), 是从Smalltalk发展而来的一种设计模式,常被用于构建用户界面。经典设计模式的著作中有这样的描述:
MVC由三种对象组成。Model是应用程序对象,View是它的屏幕表示,Controller定义了用户界面如何对用户输入进行响应。在MVC之前,用户界面设计倾向于三者结合在一起,MVC对它们进行了解耦,提高了灵活性与重用性。
假如把view与controller结合在一起,结果就是model/view结构。这个结构依然是把数据存储与数据表示进行了分离,它与MVC都基于一样的思想,但它更简单一些。这种分离使得在几个不同的view上显示同一个数据成为可能,也可以重新实现新的view,而不必改变底层的数据结构。为了更灵活的对用户输入进行处理,引入了delegate这个概念。它的好处是,数据项的渲染与编程可以进行定制。
如上图所示,model与数据源通讯,并提供接口给结构中的其他组件使用。通信的性质依赖于数据源的种类与model实现的方式。view从model获取model indexes,后者是数据项的引用。通过把model indexes提供给model,view可以从数据源中获取数据。在标准的views中,delegate会对数据项进行渲染,当某个数据项被选中时,delegate通过model indexes与model直接进行交流。
总的来说,model/view相关类可以被分成上面所提到的三组:models、views、delegates。这些组件通过抽象类来定义,它们提供了共同的接口,在某些情况下,还提供了缺省的实现。抽象类意味着需要子类化以提供完整的其他组件希望的功能。这也允许实现定制的组件。
Models、views、delegates之间通过信号—槽机制来进行通信:
从model发出的信号通知view数据源中的数据发生了改变。
从view发出的信号提供了有关被显示的数据项与用户交互的信息。
从delegate发生的信号被用于在编辑时通知model和view关于当前编辑器的状态信息。
Models(模型)
所有的item models都基于QAbstractItemModel类,这个类定义了用于views和delegates访问数据的接口。数据本身不必存储在model,数据可被置于一个数据结构或另外的类、文件、数据库、或其它的程序组件中。
QAbstractItemModel提供给数据一个接口,它非常灵活,基本满足views的需要,无论数据用以下任何形式表现,如table、list、trees。然而,当你重新实现一个model时,如果它基于table或list形式的数据结构,最好从QAbstractListModel、QAbstractTableModel开始做起,因为它们提供了适当的常规功能的缺省实现。这些类可以被子类化以支持特殊的定制需求。
Qt提供了一些现成的models用于处理数据项:
QStringListModel 用于存储简单的QString项目列表。
QStandardItemModel管理更复杂的树型结构数据项,每项都可以包含任意数据。
QFileSystemModel提供本地文件系统中的文件与目录信息。
QSqlQueryModel、QSqlTableModel、QSqlRelationTableModel用来访问数据库。
如果这些标准models不能满足需要,可以使用子类化QAbstractItemModel、QAbstractListModel或是QAbstractTableModel来定制models。
Views(视图)
不同的view都完整实现了各自的功能:
QListView把数据显示为一个列表,QTableView把Model 中的数据以table的形式表现,QTreeView用具有层次结构列表来显示model中的数据。这些类都基于QAbstractItemView抽象基类。尽管这些类都已经完整地得到了实现,但它们仍然可以用于子类化以满足定制需求。
Delegates(委托)
QAbstractItemDelegate是model/view架构中的用于delegate的抽象基类。缺省的delegate实现在QStyledItemDelegate类中提供,它可以用于Qt标准views的缺省 delegate。但是,QStyledItemDelegate和QItemDelegate是相互独立的用于实现视图(views)中items的描绘和编辑功能的方法。它们两者的不同在于,QStyledItemDelegate使用当前的样式(style)来描绘items。因此,当我们实现定制委托(delegate)或者使用Qt Style Sheets时,我们建议使用QStyledItemDelegate作为基类。
Sorting(排序)
在model/view架构中,有两种方法进行排序,选择哪种方法依赖于你的底层Model。
假如你的model是可排序的,也就是它重新实现了QAbstractItemModel::sort()函数,QTableView与QTreeView都提供了API,允许你以编程的方式对model数据进行排序。另外,你也可以进行交互方式下的排序(例如,允许用户通过点击view表头的方式对数据进行排序),具体方法是:把QHeaderView::sectionClicked()信号与QTableView::sortByColum()槽或QTreeView::sortByColumn()槽进行关联。
另一种方法是,假如你的model没有提供需要的接口或是你想用list view表示数据,可以用一个代理model在用view表示数据之前对你的model数据结构进行转换。
Convenience classes(便利类)
许多便利类都源于标准的view类,它们方便了那些使用Qt中基于项的view与table类,它们不应该被子类化,它们只是为Qt 3的等价类提供一个熟悉的接口。这些类有QListWidget、QTreeWidget、QTableWidget,它们提供了如Qt 3中的QListBox、 QlistView、QTable相似的行为。这些类比View类缺少灵活性,不能用于任意的models,推荐使用model/view的方法处理数据。
Using models and views(使用模型和视图)
Qt提供了两个标准的models:QStandardItemModel和QFileSystemModel。
QStandardItemModel是一个多用途的model,可用于表示list,table,tree views所需要的各种不同的数据结构。这个model也持有数据。
QFileSystemModel维护相关的目录内容的信息,它本身不持有数据,仅是对本地文件系统中的文件与目录的描述。QFileSystemModel是一个现成的model,很容易进行配置以利用现有的数据。使用这个model,可以很好地展示如何给一个现成的view设定model,研究如何用model indexes来操纵数据。
model与views的搭配使用
QListView与QTreeView很适合与QFileSystemModel进行搭配。下面的例子在tree view与list view显示了相同的信息,QFileSystemModel提供了目录内容数据。这两个Views共享用户选择,因此每个被选择的项在每个view中都会被高亮。
/*先创建QF<span style="font-family:Times New Roman;">i</span>leSystemModel以供使用,再创建views去显示目录的内容。这里展示了使用model的最简单的方式。model的创建与使用都在main()函数中完成:*/
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QSplitter *splitter = new QSplitter;
/*此模型model的创建是为了使用特定文件系统的数据。setRootPath()调用告诉驱动此文件系统的model将数据呈现给哪些视图(Views)*/
QFileSystemModel *model = new QFileSystemModel;
model->setRootPath(QDir::currentPath());
/*创建两个视图(Views),分别是TreeView和ListView*/
/*配置一个View以显示模型model中的数据,只需简单调用setModel()函数,并将目录model作为参数。setRootIndex()告诉views显示哪个目录的信息,这需要提供一个model index,然后用这个model index去model中去获取数据。index()函数是QFileSystemModel特有的,通过把一个目录做为参数,得到了需要的model index。*/
QTreeView *tree = new QTreeView(splitter);
tree->setModel(model);
tree->setRootIndex(model->index(QDir::currentPath()));
QListView *list = new QListView(splitter);
list->setModel(model);
list->setRootIndex(model->index(QDir::currentPath()));
/*将所有的widgets置于splitter中*/
splitter->setWindowTitle("Two views onto the same file system model");
splitter->show();
return app.exec();
}上面的例子并没有展示如何处理数据项的选择,这包括很多细节,以后会提到。
Model类
基本概念
在model/view构架中,model为view和delegates使用数据提供了标准接口。在Qt中,标准接口QAbstractItemModel类中被定义。不管数据在底层以何种数据结构存储,QAabstractItemModel的子类会以层次结构的形式来表示数据,结构中包含了数据项表。我们按这种约定来访问model中的数据项,但这个约定不会对如何显示这些数据有任何限制。数据发生改变时,model通过信号槽机制来通知关联的views。
Model Indexes
为了使数据存储与数据访问分开,引入了model indexes的概念。通过model indexes,可以引用model中的数据项,views和delegates都使用indexes来访问数据项,然后再显示出来。因此,只有model需要了解如何获取数据,被model管理的数据类型可以非常广泛地被定义。Model indexes包含一个指向创建它们的model的指针,这会在配合多个model工作时避免混乱。
QAbstractItemModel *model = index.model();Model indexes提供了对一项数据信息的临时引用,通过它可以访问或是修改model中的数据。既然model可能不时重新组织内部的数据结构,model indexes可能会失效,因此不应该保存临时的model indexes。假如需要一个对数据信息的长期的引用,那么应该创建一个persistent model index。这个引用会保持更新。临时的model indexes由QModelIndex提供,而具有持久能力的model indexes则由QPersistentModelIndex提供。
在获取对应一个数据项的model index时,需要考虑有关于model的三个属性:行数,列数,父项的model index。
行与列
在最基本的形式中,一个model可作为一个简单的表来访问,每个数据项由行数,列数来定位。这并不意味着底层的数据用数组结构来存储。行和列的使用仅仅是一种约定,它允许组件之间相互通信。可以通过指定model中的行列数来获取任一项数据,可以得到与数据项一一对应的那个index。
QModelIndex index = model->index(row, column, ...);Model为简单的、单级的数据结构如list与tables提供了接口,它们如上面代码所显示的那样,不再需要别的信息被提供。当我们在获取一个model index时,我们需要提供另外的信息。
上图代表一个基本的table model,它的每一项用一对行列数来定位。通过行数和列数,可以获取代表一个数据项的model index :
QModelIndex indexA = model->index(0, 0,QModelIndex());
QModelIndex indexB = model->index(1, 1,QModelIndex());
QModelIndex indexC = model->index(2, 1,QModelIndex());一个model的顶层项由QModelIndex()取得,它们被用作父项。
父项
类似于表的接口在搭配使用table或list view时是理想的,这种行列系统与view显示的方式是确切匹配的。然而,像tree views这种结构需要model提供更为灵活的接口来访问数据项。每个数据项可能是其它的项的父项,上级的项可以获取下级项的列表。当获取model中数据项的index时,我们必须指定关于数据项的父项的信息。在model外部,引用一个数据项的唯一方法就是通过model index,因此需要在求取model index时指定父项的信息。
QModelIndex index = model->index(row, column, parent);
上图中,A项和C项作为model中顶层的兄弟项:
QModelIndex indexA = model->index(0, 0, QModelIndex());
QModelIndex indexC = model->index(2, 1, QModelIndex());A有许多孩子,它的一个孩子B用以下代码获取:
QModelIndex indexB = model->index(1, 0, indexA);
项角色
model中的项可以作为各种角色来使用,这允许为不同的环境提供不同的数据。举例来说,Qt::DisplayRole被用于访问一个字符串,它作为文本会在view中显示。典型地,每个数据项都可以为许多不同的角色提供数据,标准的角色在Qt::ItemDataRole中定义。
我们可以通过指定model index与角色来获取我们需要的数据:
QVariant value = model->data(index, role);
角色指出了从model中引用哪种类型的数据。views可以用不同的形式显示角色,因此为每个角色提供正确的信息是非常重要的。通过为每个角色提供适当数据,model也为views和delegates提供了暗示,以指示如何正确地把这些数据项显给用户。不同的views可以自由地解析或忽略这些数据信息,对于特殊的应用要求,也可以定义一些附加的角色。
基本概念总结:
1、Model indexes为views与delegates提供model中数据项定位的信息,它与底层的数据结构无关。
2、通过指定行数、列数以及父项的model index来引用数据项。
3、依照其它的组件的要求,model indexes被model构建,如views和delegates。
4、使用index()时,如果指定了有效的父项的model index,那么返回得到的model index对应于父项的某个孩子。
5、使用index()时,如果指定了无效的父项的model index,那么返回得到的model index对应于顶层项的某个孩子。
6、角色对一个数据项包含的不同类型的数据给出了区分。
使用Model Indexes
为了演示数据是如何通过model indexes来从model中获取数据,我们创建了一个没有views的QFileSystemModel ,这个Model用于在一个widget中显示文件名和目录路径。尽管这个例子并没有展现出一个使用Model的正常方法,但是我们可以通过此例来了解处理Model indexes的一些约定和规范。
首先,我们创建一个文件系统Model。
QFileSystemModel *model = new QFileSystemModel;
QModelIndex parentIndex = model->index(QDir::currentPath());
/*计算Model中的行数*/
int numRows = model->rowCount(parentIndex);
/*这里,为了简约,我们仅仅只关注Model每一列的第一项。我们依次检查每一行,获取该行第一项的Model index。然后在读取该项的内容。*/
for (int row = 0; row < numRows; ++row) {
QModelIndex index = model->index(row, 0, parentIndex);
/*在这里,理解data()函数的用法,data()函数用于获取指定index处的数据,同时,通过Qt::DisplayRole来以String的方式获取该项内容。*/
QString text = model->data(index, Qt::DisplayRole).toString();
// Display the text in a widget.
}以上的例子说明了从model中获取数据的基本原则:
1、Model的尺寸可以从rowCount()与columnCount()中得出。这些函数通常都需要一个表示父项的model index。
2、Model indexes用来从model中访问数据项,数据项用行数,列数以及父项来实现model index定位。
3、为了访问model顶层项,可以使用QModelIndex()指定一个Null Model Index。
4、数据项为不同的角色提供不同的数据。为了获取数据,除了model index之外,还要指定角色。
创建新的Models
model/view组件之间功能的分离,允许创建model利用现成的views。这也可以使用标准的功能图形用户接口组件像QListView、QTableView和QTreeView来显示来自各种数据源的数据。
QAbstractListModel类提供了非常灵活的接口,允许数据源以层次结构的形式来管理信息,也允许以某种方式对数据进行插入、删除、修改和排序。它也提供了对拖拽操作的支持。
QAbstractListModel与QAbstractTableModel为简单的非层次结构的数据提供了接口,对于比较简单的list和table models来说,这是不错的一个开始点。
设计一个Model
当我们为存在的数据结构新建一个model时,首先要考虑的问题是应该选用哪种model来为这些数据提供接口。
假如数据结构可以用数据项的列表或表格来表示,那么可以考虑子类化QAbstractListModel或QAbstractTableModel,因为这些类已经合理地对许多功能提供缺省实现。
然而,假如底层的数据结构只能表示成具有层次结构的树型结构,那么必须得子类化QAbstractItemModel。
无论底层的数据结构采取何种形式,在特定的model中实现标准的QAbstractItemModel API总是一个不错的主意,这使得可以使用更自然的方式对底层的数据结构进行访问。这也使得用数据构建model更为容易,其他的model/view组件也可以使用标准的API与之进行交互。
一个只读model示例
这个示例实现了一个简单的、非层次结构的、只读的数据model,它基于QStringListModel类。它有一个QStringList作为它内部的数据源,只实现了一些必要的接口。为了简单化,它子类化了QAbstractListModel,这个基类提供了合理的缺省行为,对外提供了比QAbstractItemModel更为简单的接口。当我们实现一个model时,不要忘了QAbstractItemModel本身不存储任何数据,它仅仅提供了给views访问数据的接口。
class StringListModel : public QAbstractListModel
{
Q_OBJECT
public:
StringListModel(const QStringList &strings, QObject *parent = 0)
: QAbstractListModel(parent), stringList(strings) {}
int rowCount(const QModelIndex &parent = QModelIndex()) const;
QVariant data(const QModelIndex &index, int role) const;
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
private:
QStringList stringList;
};
除了构造函数,我们仅需要实现两个函数:rowCount()返回model中的行数,data()返回与特定model index对应的数据项。
具有良好行为的model也会实现headerData(),它返回tree和table views需要的在标题中显示的数据(标题栏)。
因为这是一个非层次结构的model,我们不必考虑父子关系。假如model具有层次结构,我们也应该实现index()与parent()函数。
Model的尺寸
我们认为model中的行数与string list中的string数目一致:
int StringListModel::rowCount(const QModelIndex &parent) const
{
return stringList.count();
}在缺省情况下,从QAbstractListModel派生的model只具有一列,因此不需要实现columnCount()。
Model 标题与数据
QVariant StringListModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();// QVariant is invalid data.
if (index.row() >= stringList.size())
return QVariant();
if (role == Qt::DisplayRole)
return stringList.at(index.row());
else
return QVariant();
}
对于一些视图(Views),如QTreeView和QTabeleView,我们需要设置对应的数据项的标题。这里,我们使用headerData()函数来实现添加行列标题的目的:
QVariant StringListModel::headerData(int section, Qt::Orientation orientation,
int role) const
{
if (role != Qt::DisplayRole)
return QVariant();
if (orientation == Qt::Horizontal)
return QString("Column %1").arg(section);
else
return QString("Row %1").arg(section);
}
一个数据项可能有多个角色,根据角色的不同输出不同的数据。上例中,model中的数据项只有一个角色DisplayRole,然而我们也可以重用提供给DisplayRole的数据,作为别的角色使用,如我们可以作为ToolTipRole来用。
可编辑的model
上面我们演示了一个只读的model,它只用于向用户显示,对于许多程序来说,可编辑的list model可能更有用。我们只需要给只读的model提供另外两个函数flags()与setData()的实现。下列函数声明被添加到类定义中:
Qt::ItemFlags flags(const QModelIndex &index) const;
bool setData(const QModelIndex &index, const QVariant &value,
int role = Qt::EditRole);
使model可编辑
delegate会在创建编辑器之前检查数据项是否是可编辑的。model必须得让delegate知道它的数据项是可编辑的。这可以通过为每一个数据项返回一个正确的标记得到,在本例中,我们假设所有的数据项都是可编辑可选择的:
Qt::ItemFlags StringListModel::flags(const QModelIndex &index) const
{
if (!index.isValid())
return Qt::ItemIsEnabled;
return QAbstractItemModel::flags(index) | Qt::ItemIsEditable;
}
我们不必知道delegate执行怎样实际的编辑处理过程,我们只需提供给delegate一个方法,delegate会使用它对model中的数据进行设置。这个特殊的函数就是setData():
bool StringListModel::setData(const QModelIndex &index,
const QVariant &value, int role)
{
if (index.isValid() && role == Qt::EditRole) {
stringList.replace(index.row(), value.toString());
emit dataChanged(index, index);
return true;
}
return false;
}
当数据被设置后,model必须得让views知道一些数据发生了变化,这可通过发射一个dataChanged()信号实现。因为只有一个数据项发生了变化,因此在信号中说明的变化范围只限于一个model index。
同时,data()函数也需要进行更改来添加Qt::Editable测试:
QVariant StringListModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
if (index.row() >= stringList.size())
return QVariant();
if (role == Qt::DisplayRole || role == Qt::EditRole)
return stringList.at(index.row());
else
return QVariant();
}
插入,删除行
在model中改变行数与列数是可能的。当然在本列中,只考虑行的情况,我们只需要重新实现插入、删除的函数就可以了,下面应在类定义中声明:
bool insertRows(int position, int rows, const QModelIndex &index = QModelIndex());
bool removeRows(int position, int rows, const QModelIndex &index = QModelIndex());
既然model中的每行对应于列表中的一个string,因此,insertRows()函数在string list 中指定位置插入一个空的string。
Parent和index通常用于决定model中行列的位置。本例中只有一个单独的顶级项,因此只需要在list中插入空string。
bool StringListModel::insertRows(int position, int rows, const QModelIndex &parent)
{
beginInsertRows(QModelIndex(), position, position+rows-1);
for (int row = 0; row < rows; ++row) {
stringList.insert(position, "");
}
endInsertRows();
return true;
}
函数首先调用beginInsertRows()通知其他组件行数将会改变。然后确定插入的第一行和最后一行所对应的行号,以及父项的Model Index。在插入所有字符串表项后,调用endInseRows()来完成所有操作同时通知其他组件model尺寸的改变,返回TRUE来表示插入操作的成功。
删除操作与插入操作类似:
bool StringListModel::removeRows(int position, int rows, const QModelIndex &parent){
beginRemoveRows(QModelIndex(), position, position+rows-1);
for (int row = 0; row < rows; ++row) {
stringList.removeAt(position);
}
endRemoveRows();
return true;
}
文章
存储 · 前端开发 · API · 数据库 · 设计模式
2014-08-11
四大顶级开源网络管理工具详解
随着网络方案的不断扩展与多元化走势,大量有线及无线设备开始成为网络体系不可或缺的组成部分,用户对网络监控工具的需求也随之持续走高。虽然功能丰富的商业产品比比皆是,但来自开源社区的强大方案仍然对监控工具市场的发展起到巨大的推动作用。
在本系列文章中,我们将一同剖析四款高人气开源产品--Nagios Core 3.5、NetXMS 1.2.7、OpenNMS 1.10.9以及Zenoss Core 4.2.四款产品都已经相当成熟,足以提供与其它企业级同类方案相媲美的综合性监控能力,同时拥有良好的社区支持体系。
四款产品都提供管理、性能监控以及网络设备警示与报告功能,其作用范围涵盖服务器、路由器、交换机、打印机、UPS、网络站点、业务应用程序以及移动设备等等。监控功能可以简单起效,用户可以像使用PING请求那样利用SNMP(即简单网络管理协议)或者本地代理对单一设备进行全面管理。
此次参与评述的四款产品经实测全部支持SNMP及其它通用型协议,包括WMI、HTTP、SMTP、SSH以及XML。尽管其中两款产品只能用于特定操作系统,但四者全部可以实现跨平台IT基础设施监控(本质上说可作用于任何拥有IP地址的设备)。它们支持的功能包括自动识别、SNMP、可阅读系统日志、触发器/警报设置、Web应用程序、分布式监控(即负载平衡)、映射、IPv6(只有NetXMS不支持IPv6)以及库存追踪。
四款产品还提供基本而全面的基础设施监控功能,而且很少甚至完全不需要额外硬件、内存以及存储机制作为监控环境的支持基础。在本次评述中,我们将从安装便捷性、配置便捷性、管理能力、多平台支持能力、报告机制及总体可用性等几个角度做出横向比较。
第一位竞争者:Zenoss
Zenoss堪称我们的首选方案,这主要是由于其直观且专业的管理界面对于用户极具吸引力。另外,其环境配置与报告运作机制也非常简便;当需要帮助时,我们找到的用户指导文档非常翔实清晰--出色的说明资料在开源领域极为难得。但完美永远只是目标却无法真正达成,我们发现Zenoss对资源要求较高,即使只管理少数几台设备、它也需要消耗大量硬件及内存等附加资源。
Zenoss Core是一款开源网络管理工具,可以运行在红帽企业Linux(简称RHEL)或者社区企业操作系统(简称CentOS)两大Linux版本当中。另有一套虚拟设备版本,能够利用VMware Player运行在最小安装配置下的CentOS当中.zenoss Core基于Gnu GPL2许可。除了开源版本之外,Zenoss还提供多套商业监控解决方案。
Zenoss不需要搭配代理,且使用SNMP、SSH、Telnet以及WMI等现有协议。在识别过程中,Zenoss会收集个别设备的组件信息并归纳其潜在运行依赖关系,通过这种方式创建出的基础架构清单被称为“配置模型”、这也正是Zenoss网络管理方案的核心所在。
除了使用自动识别功能之外,管理员还可以手动输入信息并将监控的ZenPack数量调整至200以上,从而拓展单一或多台设备所搭配的配置模型.zenPack从使用层面看类似于开源免费社区所开发的功能性插件.core ZenPack被打包在Zenoss Core版本当中,而商业版ZenPack则只会出现在商业版本中。
在解决了一系列硬件需求与依赖关系问题后,我们最终成功在运行着CentOS 6.4最小化版本(无图形用户界面)的虚拟机中安装了Zenoss 4.2.我们发现Zenoss在设计思路上并没有考虑轻量级基础设施的配置现状--事实上最少要6GB内存才能让一台仅安装了操作系统和Zenoss的设备进入正常运行状态.zenoss资源密集型方案的头衔可谓实至名归,它能够监控多达一千台设备,且建议配置为最少16GB内存与八个CPU核心,存储空间和传输速度也是越强越好。
Zenoss能够监控多种操作系统平台,其中包括网络设备(例如路由器与交换机)、服务器以及特定应用程序。它采用分层式架构:用户层、数据层、处理层与采集层。用户层基本负责提供用户界面,数据层则负责从三个不同的数据库中收集数据,处理层管理采集层与数据层之间的通信工作,采集层利用一系列服务向数据层提供收集到的数据。
可用性监控的实现依赖于一系列手段,例如利用ICMP或者SNMP响应对TCP/IP端口及服务进行测试及查询.zenoss还能够对Windows及Linux流程及服务进行可用性检测。
Zenoss通过收集SNMP OID值的方式实现性能监控。整个过程需要借助预加载MIB(即管理信息基础)、新的本地MIB以及对定制性能插件的内置支持等机制。
安装完成之后,Zenoss就能像物理设备那样接受来自不同设备(包括台式机、笔记本及移动设备)浏览器的访问。其用户界面高效而直观,在默认情况下会显示警示信息以及基础设施地理位置。用户还可以通过配置在浏览器中查看任何URL、内部或外部页面。仪表板中显示的信息可以通过开发者提供的所谓“门户组件”进行调整,各项目的对应显示窗口也可随时添加、删除并在仪表板内部任意拖拽及调整窗口尺寸。这样的开放性让我们能够随心所欲定制出符合需要的选项。“守护进程关闭”、“消息”与“生产状态”等功能也拥有对应的门户组件。
在向监控工具中添加设备时,大家可以使用自动识别功能或者手动输入设备的IP地址或主机名称。在设备添加完成之后,具体项目会显示在“基础设施”选项卡中、并附带状态基础信息与彩色编码提示。
除了“基础设施”之外、“事件”选项卡中显示的基本属于同类信息,但只按事件进行排序。事件与基础设施两种标签使管理员能够通过输入数列标题对大部分数列加以分类。对于每个事件,大家可以执行不同的处理操作,包括“只应答事件但不实际执行”及向设备发出“手动ping”或者“snmpwalk”指令以检索更多信息等。事件一旦处理完成,Zenoss会将其关闭并进行归档。
Zenoss Core提供强大的报告功能,从基础的准备就绪报告到定制报告功能应有尽有。除了标准的事件、设备及性能报告之外,Zenoss还还提供完善的图形化报告,包括在同一份报告中包含多份汇总自多台设备的图形化报告。这些报告可在屏幕中显示、打印在纸面上或者被保存为PDF文件。
目前已经有大量第三方ZenPack可供下载,其中一些甚至允许用户实现Zenoss与移动Android设备间的交互需求,我们也可以利用由Zenoss提供的API说明文档创建属于自己的ZenPack。
虽然Zenoss Core对于系统配置的要求颇为严苛,但这也使得整套监控环境在性能上极为强大、足以处理规模化设备与数据流量.zenoss的用户界面灵活而富于现代气息,丰富的选项与定制化仪表板令人印象深刻。无论是显示在屏幕上还是打印成PDF形式,其统计报告都极具综合性且信息排布井井有条。另外,我们还要再次掌握Zenoss那超过两百页的PDF用户手册,面面俱到的内容与清晰明了的指导在开源领域极为难得。
第二位竞争者:OpenNMS
OpenNMS是一款强大的网络管理工具,能够运行在大多数平台之上且足以应对大规模数据的管理工作,其定制仪表板功能尤其令人赞叹。由于已经得到广泛普及,目前已经有超过一万五千款插件可供用户选择。
作为一款基于Java的跨平台产品,OpenNMS能够运行在任何支持Java SDK 1.6及更新版本的平台上.openNMS基于GNU GPL许可,开发团队还提供商业许可及相关技术支持.openNMS的核心功能包括二层及三层连接自动识别、网络自动识别、自动配置以及服务识别等。在最新版本中,这款工具还添加了对IPv6的支持能力.openNMS拥有一套内置网络服务器系统,用户通过内置用户列表、LDAP或者RADIUS协议实现身份验证。
除了创建自有事件,OpenNMS还可以从SNMP、HTTP、WMI、XML、JMX以及系统日志等外部协议中接收性能数据。从服务监控方面来看,它能够与一系列常用协议及服务进行通信,包括DNS、Windows服务状态、BGP会话、思杰、邮件协议等等,这还只是其通信对象中的一小部分。目前已经有一百多家厂商为其开发出超过一万五千款trap,其中SNMP trap接收器能够在设备启动后为其提供大量实用功能。
我们可以通过多种方式实现通知推送目的,包括利用标准页面调度程序、邮件通知机制以及更具定制特色的微博(例如Twitter)与命令行呼叫等方式。在整合性方面,它能够与DNS交互、接收并上传节点信息,还可与Hyperic HQ、RANCID以及Asterisk电话平台等第三方产品进行交互。
最后,OpenNMS还提供多种报告选择,用户可以通过预先配置调整报告机制或添加以日期范围为基础的筛选方案。报告可以在指定时间运行,其结果会被保存到文件系统中或通过电子邮件发送至特定收件人。生成的文件格式分为PDF与CSV两种。
OpenNMS的最低系统要求非常亲民,只需要512MB内存。但如果要让整套系统有能力应对任何数据采集任务,那么用户仍然需要在处理器、内存以及存储配置方面进行大幅强化。
由于该工具图形用户界面所采用的JSP页面属于动态编译,因此除JRE之外、OpenNMS还必须配合完整的Java SDK才能正常运行。再有,PostgreSQL也必须提前安装就绪。
我们将OpenNMS作为独立服务器安装在Windows系统当中,并利用Web图形用户界面在浏览器中进行管理操作。在登录之后,主页面会显示关于基础设施的一些基本信息,例如各节点在过去24小时中的停机或者可用状态,并额外提供专门的区域用于访问与报告功能。大家在屏幕上方的水平链接栏中还能找到其它导航机制。
OpenNMS主要利用两种特定配置方式向监控体系中添加新功能,即自动识别与定向识别。我们先通过一系列参数设定让OpenNMS了解如何根据需求识别基础设施,大家可以在“识别配置”中输入URL、特定的IP及IP地址范围并设定例外情况的方式调整其识别机制。值得一提的是,大部分OpenNMS配置信息都被以XML格式保存在文件中,熟悉XML的用户可以直接在对应文件中进行修改、而不必使用图形用户界面。
在配置过程中,我们发现用户需要经常查阅在线说明文档(好在指导材料还算全面),否则很多任务根本无法完成。之所以面临诸多挑战,是因为我们对于OpenNMS所使用的基础设施技术术语不够熟悉。从理论角度讲,节点应该会被自动识别并添加到监控体系当中,但由于我们的测试实验室拥有许多故意隔离出来的节点,因此这部分配置工作只能通过手动方式完成。
全局导航对于我们来说不太符合逻辑,不过我们认为各个环节与屏幕信息在整合之后会使其得到改善。也许这只是由配置引起的小问题,但我们发现在打开警报、事件或者通知选项卡后,屏幕上确实不会显示任何内容--除非大家进行搜索或者点击查看全局链接。
不过我们清楚,对于大型基础设施来说除非与筛选工具配合使用、否则屏幕上直接显示出的基础信息几乎没什么实际价值。我们在登录后只能看到采用仪表板风格的屏幕, 而且需要点击OpenNMS以返回管理面板。我们还可以点击链接激活另一个仪表板界面,其中提供一部分关于基础设施的简要信息,但通过横向比较、我们发现这套方案效果平平。
我们对于搜索功能非常赞赏,而OpenNMS也确实在搜索功能的可用性方面下足了功夫。我们尤其喜爱以节点为单位搜索DNS或者POP3等特定服务这一功能。另一项实用功能在于对与资产(包括位置、操作系统及运行状态)相关的数据字段进行搜索。
OpenNMS的报告功能非常全面,其中包含大量预创建模板并能够运行临时报告。报告可被输入到屏幕中或者以PDF及/或CSV格式汇总成文件。
我们发现OpenNMS的用户界面需要经过一定调整才能满足大部分使用者对直观性的需求,但随着对界面的愈发熟悉、我们的注意力更多集中在了这款产品出色的功能身上。管理员界面提供大量实用信息,相信能很快得到大家的肯定。我们也喜欢它带来的创建只读仪表板的功能。搜索功能非常强大,彩色编码警报也效果显着。总体来看,OpenNMS确实是一款能够支持多种平台的出色管理工具。
第三位竞争者:NetXMS
这次我们一起来看NetXMS。这是一款用户界面有些凌乱、但却拥有丰富工具集的产品,不仅能对基础设施进行细化管理、而且在移动方面倾注了大量心血。
NetXMS可以运行在Windows 2003及更新版本、Linux、Solaris、AIX、HP-UX以及FreeBSD等多套系统平台之上,并遵循GPL许可机制。
NetXMS的最大特色就是能在不借助外部插件的前提下以原生方式支持大量主流平台.netXMS能够使用SNMP以及本地“高性能”代理、从而收集数据并将结果提交并保存至SQL数据库中(包括甲骨文、微软SQL Server、PostgreSQL、MySQL以及SQLLite)。我们可以通过自动或者手动方式实现二层与三层网络基础设施识别。收集到的数据会被保存在监控服务器中,并由管理员通过多种方式加以访问。
数据收集由对象/节点负责,而且每个对象/节点都拥有与自身相关联的阈值限制,例如服务器内存利用率或者路由器流量限值等。一旦超出阈值,NetXMS会直接触发某些事件;而随着阈值的回落,系统则继续触发正常业务事件.netXMS还支持Android,既可以对Android设备进行管理、又可以作为设备监控的代理机制。
除了在屏幕上显示通知及警报信息,NetXMS还允许管理员通过配置发送警报短信及电子邮件。具体事件也可被转发至另一台NetXMS服务器或其它外部系统.netXMS还具备本地脚本引擎,旨在实现高级自动化与管理功能。
NetXMS的系统配置要求简直可谓没有要求--只需要512KB内存与主频低于1GHz的处理器。不过对于大部分生产安装环境来说,主机操作系统还需要额外的CPU处理核心/强度/速率以及更大的内存容量。要安装在Windows系统中,我们还需要为其准备一套单独的数据库服务器,用于保存配置设定以及数据收集(支持PostgreSQL、MySQL、甲骨文或者SQL Server)。某些PDF说明文档已经有些过时(官方网站也已经明确提示了这一点),但我们发现维基百科能够起到很好的指导作用。
在测试环境中,我们将NetXMS安装在运行着PostgreSQL管理方案的Windows Server 2008 R2系统中,同时配备一套基于Java的桌面界面、Web界面或者移动管理控制台.netXMS还会同时安装自己的轻量级web服务器,这样大家就不必再安装IIS、Apache或者其它web服务器了。不过各位千万不要把Web界面安装在NetXMS所在的目录中,这样会导致工具安装失败、相当于搬起石头砸自己的脚。不过安装导航会默认将二者装在同一个目录中,这可能是供应商的失误。
NetXMS管理控制台的既定目标之一在于提供一套统一化跨平台图形用户界面(包括Web界面),从这个角度来看NetXMS无疑获得了极大成功。
虽然我们也在独立的台式机上测试过Web用户界面,但本次测试的大部分工作都利用了运行在服务器上的桌面应用程序。登录之后,工作台就成了我们的主要视图界面,用户可以通过不同的自定义方式对其进行调整,并将保存结果用于未来的会话过程。初一上手,用户界面上的大量标签与链接可能会让新手们感到不太适应。
要实现完整的自动识别功能,供应商建议我们让工具对大型网络进行几个小时乃至一整夜的全局扫描。由于我们的测试环境规模不大,因此整个识别过程耗时并不太长(不到30分钟)。大家也可以通过一系列简单步骤手动添加节点。节点添加完成后,NetXMS将在“事件状态”窗口中显示节点的基本信息,例如某个进程无法正常工作或者某个节点处于不可用状态。此外,工具还提供多个数据连接项(简称DCI),允许用户针对每个节点进行自定义配置或者将配置方案保存在模板并推广到整个网络当中。这样如果需要对配置进行整体修改,我们就不必在每个节点中重复折腾。
屏幕报告功能给我们留下了深刻印象,该功能允许用户运行一系列临时报告;报告结果所产生的数据集可被导出为CSV文件.netXMS还集成了Jaspersoft报告引擎,从而实现了创建自定义报告并在必要时加以重复使用。
总体而言,NetXMS是一套功能强大的管理工具,但在配置方面颇具挑战,因为设定与控制布局过多使整个调整逻辑显得有些混乱。用户手册也还不错,但具体内容应该再重新整理一遍。另一项不足之处在于,管理控制台应该提供与当前情况相关的帮助机制。
从积极的角度看,我们对NetXMS的跨平台能力、移动设备支持能力以及在移动设备中运行管理控制台的能力深表赞赏。便捷的自定义仪表板创建功能只需几次鼠标点击即可完成,能够帮助用户以可视化方式掌握网络运行状态。
第四位竞争者:Nagios
Nagios对于基础设施规模不大的小型企业而言非常理想,在它的帮助下技术团队无需像大型企业那样花费大量时间通过命令行对节点加以配置。
早在上世纪九十年代末Nagios就已经诞生,尽管最初是针对Linux所设计、但它在Unix平台上的运行状况同样出色.nagios Core是一款遵循GNU通用公共许可的开源软件,另有Nagios企业版为用户提供商业技术支持。
Nagios能够监控包括SMTP、POP3、HTTP、NNTP以及PING在内的多种网络服务。它还会追踪主机资源的使用情况,例如处理器负载、内存及磁盘使用量等。利用自定义插件,用户能够开发出自己的服务检查方案.nagios Core与之前提到的几款工具有所不同,其Core版本与市场上的其它商业产品相比缩水比较严重。
如果想使用SNMP trap或者移动应用等功能,大家必须采购其“专业版”或者“商务版”,而后端数据库选项则只在“商务版”中提供。“学生版”虽然仅仅售价50美元,但也只提供了快速入门指南、一套预配置虚拟机外加Web配置图形用户界面等基本功能。免费版本更是相当寒酸,只包含开源监控引擎。
HTTP、FTP以及SMTP等公共服务能够在不部署主机代理或者依赖于SNMP的前提下受到,但任何“私有”服务--例如CPU与内存使用率、用户信息、服务状态及运行流程等--都需要通过在主机上安装代理方可实现。针对不同类型的主机,供应商也提供不同种类的代理机制,例如Windows服务器、Linux/Unix服务器、打印机、路由器与交换机等。除了安装代理之外,大家还需要更新一系列配置文件以顺利启动监控工具.nagios Exchange网站提供大量可选插件,足以满足用户在实际监控/管理工作中的不同需求。
在通知功能方面,Nagios能够通过电子邮件或者手机短信发送警示信息,并有专门的系统根据问题的严重程度向不同个人及小组传达情况。
Nagios Core必须运行在Linux或者Unix的衍生系统环境下,如果要使用Web界面(也就是CGI,即计算机生成界面)、我们还需要准备一台web服务器(最好是Nagios与Apache一一对应)。
准备好前续工作后,我们需要从快速入门指南开始着手Nagios Core的安装工作。在我们的实际测试中,指南计划很快搁浅--因为我们选择Linux最新版本根本不在供应商的支持范畴之内。为了完成测试,我们不得不依靠第三方在线资源的指引将Nagios Core安装在CentOS 6.4版本中。必须抱怨的是,Nagios官方网站上根本就没有提供这样的备选说明。
Web用户界面中包含的CGI素材在外观与操作感受上都与Web页面非常相似。管理员启动页面左侧设有基本导航与公告/访问软件更新、技术支持与其它实用资源等选项。进入左侧的导航界面,我们可以访问各类指标,包括主机、问题以及报告的总体概述与细节信息。
“战术总结”屏幕显示出包括主机及服务在内的整套体系的当前运行状态。状态摘要则通过“关键”、“警告”、“未知”、“待定”以及“良好”等词汇对情况加以概括。网络图中显示了所有主机、各自运行状态以及彼此之间的连接方式。通过将光标移向不同主机,我们看到更为详细的概要信息。如果我们的基础设施由数百乃至上千台主机构成,那么这项功能恐怕没什么实用价值;但在小规模基础设施中,它倒确实能够提供实时的网络体系说明。
Nagios应用的一大优势在于能够在节点之间快速起效。略显平庸的用户界面使得资源没有被浪费在花哨的描述与表现形式上(但这也使界面较难上手)。虽然简单的设计不能算坏事,但我们仍然希望供应商能将界面风格加以进一步统一,同时对显示数据进行整理以提高可读性。当然,也许他们是故意通过这种笨拙的效果敦促用户尽快购买商业版本。
在报告功能方面,Nagios Core提供多种预置屏幕报告方式,用户可以在通过选项参数设置在下拉菜单中使用临时报告。但我们无法将报告内容以PDF或者其它任何格式加以导出。
单从免费版本来看,Nagios Core可谓名符其实--只提供“核心”功能组件,即最基本的基础设施监控引擎。虽然这样的方案在某些特定环境中也能顺利起效,但我们仍然希望花点时间进行任务管理配置。尽管Nagios Core的图形用户界面有些过时,但其性能表现非常出色、而且比其它全功能竞争方案更易于设置及管理。
备注:
NetXMS与OpenNMS具备很强的跨平台能力,而Zenoss与Nagios则基于Linux环境。为了在Linux与Windows之间找到测试环境平衡点,我们选择将NetXMS与OpenNMS安装在Windows服务器当中。
原文发布时间为:2013-07-11
本文来自云栖社区合作伙伴“Linux中国”
文章
监控 · Java · 关系型数据库 · Linux · Windows · 网络协议 · Unix · PostgreSQL · 数据格式 · XML
2017-05-02
使用 Sonar 进行代码质量管理
Sonar 概述
Sonar 是一个用于代码质量管理的开放平台。通过插件机制,Sonar可以集成不同的测试工具,代码分析工具,以及持续集成工具。
与持续集成工具(例如 Hudson/Jenkins等)不同,Sonar并不是简单地把不同的代码检查工具结果(例如
FindBugs,PMD等)直接显示在
Web页面上,而是通过不同的插件对这些结果进行再加工处理,通过量化的方式度量代码质量的变化,从而可以方便地对不同规模和种类的工程进行代码质量管理。
在对其他工具的支持方面,Sonar不仅提供了对
IDE的支持,可以在 Eclipse和
IntelliJ IDEA这些工具里联机查看结果;同时 Sonar还对大量的持续集成工具提供了接口支持,可以很方便地在持续集成中使用
Sonar。
此外,Sonar的插件还可以对
Java以外的其他编程语言提供支持,对国际化以及报告文档化也有良好的支持。
Sonar 的安装
Sonar 是 Codehaus上面的一个开源项目,使用的是 LGPL V3软件许可。我们可以在其官方网站上下载其源代码及安装包。
其源代码需要使用分布式版本控制软件 Git进行检出(Check Out),命令行方式如下:
git clone git://github.com/SonarSource/sonar.git
本文主要介绍 Sonar的使用方法,只需要到Sonar网站下载最近的发行包即可,本文写作时最新的版本为
2.11。
下载 zip包后,直接解压到任意目录,由于 Sonar自带了
Jetty 6的应用服务器环境,所以不需要额外的安装就可以使用,值得一提的是 Sonar也支持部署在 Apache Tomcat应用服务器中。
在 windows
环境中,直接启动 Soanr 的 bin
目录下 windows-x86-64\StartSonar.bat
即可。
然后在浏览器中访问:http://localhost:9000/
图 1. Sonar 访问界面
这样就成功安装并启动了 Sonar,但其中没有安装插件,需要用户下载并安装自己所需要的插件。本节以 Quality Index Plugin为例,介绍如何下载及安装
Sonar插件。
首先访问 Sonar
主页中 Dashboard > Sonar > Documentation > Sonar Plugin Library
路径
图 2. Sonar 插件的下载
进入 Quality Index
插件,点击下载路径
图 3. Quality Index Plugin
下载
然后将下载的 sonar-quality-index-plugin-1.1.3.jar文件放到 sonar-2.11\extensions\plugins路径下。重启 Sonar,该插件就在
Sonar的平台上运行并开始工作。
数据库设置
Sonar 默认使用的是 Derby数据库,但这个数据库一般用于评估版本或者测试用途。商用及对数据库要求较高时,建议使用其他数据库。Sonar可以支持大多数主流关系型数据库(例如
Microsoft SQL Server, MySQL, Oracle, PostgreSQL等)
本文以 MySQL为例说明如何更改 Sonar的数据库设置:
在 MySQL
中创建 sonar 用户
CREATE USER sonar IDENTIFIED BY 'sonar';
GRANT ALL PRIVILEGES ON *.* TO 'sonar'@'localhost' \
IDENTIFIED BY 'sonar' WITH GRANT OPTION;
将 MySQL
的驱动文件(如 mysql-connector-java-5.1.13.jar)拷贝到 sonar-2.11\extensions\jdbc-driver\mysql目录
修改 sonar-2.11\conf\sonar.properties文件,用 #
注释原来 Derby
的配置项,并打开 MySQL 数据库的配置项:
# Comment the following lines to deactivate the default embedded database.
#sonar.jdbc.url: jdbc:derby://localhost:1527/sonar;create=true
#sonar.jdbc.driverClassName: org.apache.derby.jdbc.ClientDriver
#sonar.jdbc.validationQuery: values(1)
〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜省略部分〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
#----- MySQL 5.x/6.x
# Comment the embedded database and uncomment the following
#properties to use MySQL. The validation query is optional.
sonar.jdbc.url: \
jdbc:mysql://localhost:3306/sonar?useUnicode=true&characterEncoding=utf8
sonar.jdbc.driverClassName: com.mysql.jdbc.Driver
#sonar.jdbc.validationQuery: select 1
重启 Sonar。
使用 Sonar进行代码质量管理
为了不涉及代码的版权问题及方便读者试验,本节以开源项目 Mojo为例,说明如何使用 Sonar对源代码进行质量管理,在不涉及编译的情况下,本文尽量使用
Sonar的 Nemo演示功能。
首先,需要从Mojo
首页上下载源代码(本文使用 TortoiseSVN
工具),如图所示,Mojo项目包括了许多工程。
图 4. Mojo 工程文件夹
其中的每个工程都是一个 Maven项目,如图所示:
图 5. Maven 工程文件
编译命令如下:
mvn clean install
编译成功后,再使用如下命令:
mvn sonar:sonar
Maven 插件会自动把所需数据(如单元测试结果、静态检测结果等)上传到 Sonar服务器上,需要说明的是,关于
Sonar的配置并不在每个工程的 pom.xml文件里,而是在 Maven的配置文件
settings.xml文件里,具体配置如下:
<profile>
<id>sonar</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<sonar.jdbc.url>
jdbc:mysql://localhost:3306/sonar?useUnicode=true&characterEncoding=utf8
</sonar.jdbc.url>
<sonar.jdbc.driver>com.mysql.jdbc.Driver</sonar.jdbc.driver>
<sonar.jdbc.username>sonar</sonar.jdbc.username>
<sonar.jdbc.password>sonar</sonar.jdbc.password>
<sonar.host.url>http://localhost:9000</sonar.host.url>
</properties>
</profile>
将 Soanr所需要的数据上传到 Sonar服务器上之后,Sonar安装的插件会对这些数据进行分析和处理,并以各种方式显示给用户,从而使用户方便地对代码质量的监测和管理。
例如 Radiator插件可以根据项目的规模进行排序,并用不同演示显示代码质量:
图 6. Radiator 插件的显示
Sonar 插件的配置
前面已经提到,Sonar的主要特色是对不同工具产生的检查结果进行再加工处理,Sonar还向用户提供了对数据进行个性化处理的方法。
本节以 Technical Debt插件为例说明如何通过设置参数影响最后的报告结果。首先了解一下这个插件中的“技术债务”的概念,这个概念最早是在
1992 年由 Ward Cunningham在他的论文“The WyCash Portfolio Management System”中提出的,之后被软件工程界接受并推广,《重构》的作者
Martin Fowler也在其网站上对技术债务有所介绍。其实原理可以理解为“出来混早晚要还的”,当前不规范的代码,会对以后产品修改的成本造成影响。
Soanr 的 Technical Debt插件提供了默认的计算公式,通过对其中的权重参数进行配置,可以适应不同公司和项目对技术债务的计算。
图 7. Technical Debt
计算公式
以上的各项数据指标,可以根据自己公司和项目的不同情况进行设置,如图所示:
图 8. Sonar 配置界面
例如默认参数下同一个项目的技术债务指标如下:
图 9. 默认参数下 Technical Debt结果
修改了参数后的结果为:
图 10. 配置参数后 Technical Debt结果
可见将 Average time to cover complexity of one (in hours)从 0.2修改为
0.01后,Coverage的权重变小了,从而达到忽略单元测试覆盖率的作用。不同的公司和项目可以根据需要调整各自的参数,参数的调优和策略不在本文的讨论范围之内。
通过以上的示例可以看出,Sonar使用不同类型的图表显示给用户代码质量的结果,并且这些图表不是简单地对单元测试覆盖率或者静态检测工具的结果进行显示,而是根据软件工程理论进行了二次加工后的结果,更加科学和直观。
结束语
Sonar 为代码的质量管理提供了一个平台,对传统的代码静态检测如 PMD、FindBugs等工具进行整合,可以说是目前最强大的代码质量管理工具之一。
文章出处: IBM
developerWorks
参考:
开发工作中使用的软件列表
懒人可以用Automator提高工作效率
使用脚本简化工作
程序员要学会偷懒---正确运用自动化技术
如何使用搜索技巧来成为一名高效的程序员
[《人件》摘录]: 生产力:赢得战役和输掉战争
文章
关系型数据库 · MySQL · Java · 持续交付 · 数据库 · Maven · 开发工具 · 测试技术 · 数据库连接 · git
2012-07-24
带你读《Kubernetes进阶实战》之三:资源管理基础
点击这里查看第一章:Kubernetes快速入门点击这里查看第二章:资源管理基础
第3章:资源管理基础
Kubernetes系统的API Server基于HTTP/HTTPS接收并响应客户端的操作请求,它提供了一种“基于资源”(resource-based)的RESTful风格的编程接口,将集群的各种组件都抽象成为标准的REST资源,如Node、Namespace和Pod等,并支持通过标准的HTTP方法以JSON为数据序列化方案进行资源管理操作。本章将着重描述Kubernetes的资源管理方式。
3.1 资源对象及API群组
REST是Representational State Transfer的缩写,意为“表征状态转移”,它是一种程序架构风格,基本元素为资源(resource)、表征(representation)和行为(action)。资源即对象,一个资源通常意味着一个附带类型和关联数据、支持的操作方法以及与其他对象的关系的对象,它们是持有状态的事物,即REST中的S(State)。REST组件通过使用“表征”来捕获资源的当前或预期状态并在组件之间传输该表征从而对资源执行操作。表征是一个字节序列,由数据、描述数据的元数据以及偶尔描述元数据的元数据组成(通常用于验证消息的完整性),表征还有一些其他常用但不太精确的名称,如文档、文件和HTTP消息实体等。表征的数据格式称为媒体类型(media type),常用的有JSON或XML。API客户端不能直接访问资源,它们需要执行“动作”(action)来改变资源的状态,于是资源的状态从一种形式“转移”(Transfer)为另一种形式。资源可以分组为集合(collection),每个集合只包含单一类型的资源,并且各资源间是无序的。当然,资源也可以不属于任何集合,它们称为单体资源。事实上,集合本身也是资源,它可以部署于全局级别,位于API的顶层,也可以包含于某个资源中,表现为“子集合”。集合、资源、子集合及子资源间的关系如图3-1所示。
图3-1 集合、资源和子资源
Kubernetes系统将一切事物都抽象为API资源,其遵循REST架构风格组织并管理这些资源及其对象,同时还支持通过标准的HTTP方法(POST、PUT、PATCH、DELETE和GET)对资源进行增、删、改、查等管理操作。不过,在Kubernetes系统的语境中,“资源”用于表示“对象”的集合,例如,Pod资源可用于描述所有Pod类型的对象,但本书将不加区别地使用资源、对象和资源对象,并将它们统统理解为资源类型生成的实例—对象。3.1.1 Kubernetes的资源对象
依据资源的主要功能作为分类标准,Kubernetes的API对象大体可分为工作负载(Workload)、发现和负载均衡(Discovery & LB)、配置和存储(Conf?ig & Storage)、集群(Cluster)以及元数据(Metadata)五个类别。它们基本上都是围绕一个核心目的而设计:如何更好地运行和丰富Pod资源,从而为容器化应用提供更灵活、更完善的操作与管理组件,如图3-2所示。
图3-2 Kubernetes常用资源对象
工作负载型资源用于确保Pod资源对象能够更好地运行容器化应用,具有同一种负载的各Pod对象需要以负载均衡的方式服务于各请求,而各种容器化应用彼此之间需要彼此“发现”以完成工作协同。Pod资源具有生命周期,存储型资源能够为重构的Pod对象提供持久化的数据存储机制,共享同一配置的Pod资源可从配置型资源中统一获取配置改动信息,这些资源作为配置中心为管理容器化应用的配置文件提供了极为便捷的管理机制。集群型资源为管理集群本身的工作特性提供了配置接口,而元数据型资源则用于配置集群内部的其他资源的行为。(1)工作负载型资源Pod是工作负载型资源中的基础资源,它负责运行容器,并为其解决环境性的依赖,例如,向容器注入共享的或持久化的存储卷、配置信息或密钥数据等。但Pod可能会因为资源超限或节点故障等原因而终止,这些非正常终止的Pod资源需要被重建,不过,这类工作将由工作负载型的控制器来完成,它们通常也称为pod控制器。应用程序分为无状态和有状态两种类型,它们对环境的依赖及工作特性有很大的不同,因此分属两种不同类型的Pod控制器来管理,ReplicationController、ReplicaSet和Deployment负责管理无状态应用,StatefulSet用于管控有状态类应用。ReplicationController是上一代的控制器,其功能由ReplicaSet和Deployment负责实现,因此几近于废弃。还有些应用较为独特,它们需要在集群中的每个节点上运行单个Pod资源,负责收集日志或运行系统服务等任务,这些Pod资源的管理则属于DaemonSet控制器的分内之事。另外,有些容器化应用需要继续运行以为守护进程不间断地提供服务,而有些则应该在正常完成后退出,这些在正常完成后就应该退出的容器化应用则由Job控制器负责管控。下面是各Pod控制器更为详细的说明。ReplicationController:用于确保每个Pod副本在任一时刻均能满足目标数量,换言之,它用于保证每个容器或容器组总是运行并且可访问;它是上一代的无状态Pod应用控制器,建议读者使用新型控制器Deployment和ReplicaSet来取代它。ReplicaSet:新一代ReplicationController,它与ReplicationController的唯一不同之处仅在于支持的标签选择器不同,ReplicationController只支持等值选择器,而ReplicaSet还额外支持基于集合的选择器。Deployment:用于管理无状态的持久化应用,例如HTTP服务器;它用于为Pod和ReplicaSet提供声明式更新,是建构在ReplicaSet之上的更为高级的控制器。StatefulSet:用于管理有状态的持久化应用,如database服务程序;其与Deployment的不同之处在于StatefulSet会为每个Pod创建一个独有的持久性标识符,并会确保各Pod之间的顺序性。DaemonSet:用于确保每个节点都运行了某Pod的一个副本,新增的节点一样会被添加此类Pod;在节点移除时,此类Pod会被回收;DaemonSet常用于运行集群存储守护进程—如glusterd和ceph,还有日志收集进程—如f?luentd和logstash,以及监控进程—如Prometheus的Node Exporter、collectd、Datadog agent和Ganglia的gmond等。Job:用于管理运行完成后即可终止的应用,例如批处理作业任务;换句话讲,Job创建一个或多个Pod,并确保其符合目标数量,直到Pod正常结束而终止。(2)发现和负载均衡Pod资源可能会因为任何意外故障而被重建,于是它需要固定的可被“发现”的方式。另外,Pod资源仅在集群内可见,它的客户端也可能是集群内的其他Pod资源,若要开放给外部网络中的用户访问,则需要事先将其暴露到集群外部,并且要为同一种工作负载的访问流量进行负载均衡。Kubernetes使用标准的资源对象来解决此类问题,它们是用于为工作负载添加发现机制及负载均衡功能的Service资源和Endpoint资源,以及通过七层代理实现请求流量负载均衡的Ingress资源。(3)配置与存储Docker容器分层联合挂载的方式决定了不宜在容器内部存储需要持久化的数据,于是它通过引入挂载外部存储卷的方式来解决此类问题,而Kubernetes则为此设计了Volume资源,它支持众多类型的存储设备或存储系统,如GlusterFS、CEPH RBD和Flocker等。另外,新版本的Kubernetes还支持通过标准的CSI(Container Storage Interface)统一存储接口以及扩展支持更多类型的存储系统。另外,基于镜像构建容器应用时,其配置信息于镜像制作时焙入,从而为不同的环境定制配置就变得较为困难。Docker使用环境变量等作为解决方案,但这么一来就得于容器启动时将值传入,且无法在运行时修改。ConfigMap资源能够以环境变量或存储卷的方式接入到Pod资源的容器中,并且可被多个同类的Pod共享引用,从而实现“一次修改,多处生效”。不过,这种方式不适于存储敏感数据,如私钥、密码等,那是另一个资源类型Secret的功能。(4)集群级资源Pod、Deployment、Service和Conf?igMap等资源属于名称空间级别,可由相应的项目管理员所管理。然而,Kubernetes还存在一些集群级别的资源,用于定义集群自身配置信息的对象,它们仅应该由集群管理员进行操作。集群级资源主要包含以下几种类型。Namespace:资源对象名称的作用范围,绝大多数对象都隶属于某个名称空间,默认时隶属于“default”。Node:Kubernetes集群的工作节点,其标识符在当前集群中必须是唯一的。Role:名称空间级别的由规则组成的权限集合,可被RoleBinding引用。ClusterRole:Cluster级别的由规则组成的权限集合,可被RoleBinding和ClusterRoleBinding引用。RoleBinding:将Role中的许可权限绑定在一个或一组用户之上,它隶属于且仅能作用于一个名称空间;绑定时,可以引用同一名称空间中的Role,也可以引用全局名称空间中的ClusterRole。ClusterRoleBinding:将ClusterRole中定义的许可权限绑定在一个或一组用户之上;它能够引用全局名称空间中的ClusterRole,并能通过Subject添加相关信息。(5)元数据型资源此类资源对象用于为集群内部的其他资源配置其行为或特性,如HorizontalPodAutoscaler资源可用于自动伸缩工作负载类型的资源对象的规模,Pod模板资源可用于为pod资源的创建预制模板,而LimitRange则可为名称空间的资源设置其CPU和内存等系统级资源的数量限制等。一个应用通常需要多个资源的支撑,例如,使用Deployment资源管理应用实例(Pod)、使用Conf?igMap资源保存应用配置、使用Service或Ingress资源暴露服务、使用Volume资源提供外部存储等。本书后面篇幅的主体部分就展开介绍这些资源类型,它们是将容器化应用托管运行于Kubernetes集群的重要工具组件。3.1.2 资源及其在API中的组织形式
在Kubernetes上,资源对象代表了系统上的持久类实体,Kubernetes用这些持久类实体来表达集群的状态,包括容器化的应用程序正运行于哪些节点,每个应用程序有哪些资源可用,以及每个应用程序各自的行为策略,如重启、升级及容错策略等。一个对象可能会包含多个资源,用户可对这些资源执行增、删、改、查等管理操作。Kubernetes通常利用标准的RESTful术语来描述API概念。资源类型(resource type)是指在URL中使用的名称,如Pod、Namespace和Service等,其URL格式为“GROUP/VERSION/RESOURCE”,如apps/v1/deployment。所有资源类型都有一个对应的JSON表示格式,称为“种类”(kind);客户端创建对象必须以JSON提交对象的配置信息。隶属于同一种资源类型的对象组成的列表称为“集合”(collection),如PodList。某种类型的单个实例称为“资源”(resource)或“对象”(object),如名为pod-demo的Pod对象。kind代表着资源对象所属的类型,如Namespace、Deployment、Service及Pod等,而这些资源类型大体又可以分为三个类别,具体如下。对象(Object)类:对象表示Kubernetes系统上的持久化实体,一个对象可能包含多个资源,客户端可用它执行多种操作。Namespace、Deployment、Service及Pod等都属于这个类别。列表(List)类:列表通常是指同一类型资源的集合,如PodLists、NodeLists等。简单(Simple)类:常用于在对象上执行某种特殊操作,或者管理非持久化的实体,如/binding或/status等。Kubernetes绝大多数的API资源类型都是“对象”,它们代表着集群中某个概念的实例。有一小部分的API资源类型为“虚拟”(virtual)类型,它们用于表达一类“操作”(operation)。所有的对象型资源都拥有一个独有的名称标识以实现其幂等的创建及获取操作,不过,虚拟型资源无须获取或不依赖于幂等性时也可以不使用专用标识符。有些资源类型隶属于集群范畴,如Namespace和PersistentVolume,而多数资源类型则受限于名称空间,如Pod、Deployment和Service等。名称空间级别的资源的URL路径中含有其所属空间的名称,这些资源对象在名称空间被删除时会被一并删除,并且这些资源对象的访问也将受控于其所属的名称空间级别的授权审查。
Kubernetes将API分割为多个逻辑组合,称为API群组,它们支持单独启用或禁用,并能够再次分解。API Server支持在不同的群组中使用不同的版本,允许各组以不同的速度演进,而且也支持同一群组同时存在不同的版本,如apps/v1、apps/v1beta2和apps/v1beta1,也因此能够在不同的群组中使用同名的资源类型,从而能在稳定版本的群组及新的实验群组中以不同的特性同时使用同一个资源类型。群组化管理的API使得其可以更轻松地进行扩展。当前系统的API Server上的相关信息可通过“kubectl api-versions”命令获取。命令结果中显示的不少API群组在后续的章节中配置资源清单时会多次用到:[root@master ~]# kubectl api-versionsadmissionregistration.k8s.io/v1beta1apiextensions.k8s.io/v1beta1apiregistration.k8s.io/v1apiregistration.k8s.io/v1beta1apps/v1apps/v1beta1apps/v1beta2authentication.k8s.io/v1authentication.k8s.io/v1beta1authorization.k8s.io/v1authorization.k8s.io/v1beta1autoscaling/v1autoscaling/v2beta1batch/v1batch/v1beta1certificates.k8s.io/v1beta1events.k8s.io/v1beta1extensions/v1beta1networking.k8s.io/v1policy/v1beta1rbac.authorization.k8s.io/v1rbac.authorization.k8s.io/v1beta1scheduling.k8s.io/v1beta1storage.k8s.io/v1storage.k8s.io/v1beta1v1
Kubernetes的API以层级结构组织在一起,每个API群组表现为一个以“/apis”为根路径的REST路径,不过核心群组core有一个专用的简化路径“/api/v1”。目前,常用的API群组可归为如下两类。核心群组(core group):REST路径为/api/v1,在资源的配置信息apiVersion字段中引用时可以不指定路径,而仅给出版本,如“apiVersion: v1”。命名的群组(named group):REST路径为/apis/$GROUP_NAME/$VERSION,例如/apis/apps/v1,它在apiVersion字段中引用的格式为“apiVersion: $GROUP_NAME/$VERSION”,如“apiVersion: apps/v1”。总结起来,名称空间级别的每一个资源类型在API中的URL路径表示都可简单抽象为形如“/apis///namespaces//”的路径,如default名称空间中Deployment类型的路径为/apis/apps/v1/namespaces/default/deployments,通过此路径可获取到default名称空间中所有Deployment对象的列表:~]$ kubectl get --raw /apis/apps/v1/namespaces/default/deployments | jq .{ "kind": "DeploymentList", "apiVersion": "apps/v1", …… "items": [……]}
另外,Kubernetes还支持用户自定义资源类型,目前常用的方式有三种:一是修改Kubernetes源代码自定义类型;二是创建一个自定义的API Server,并将其聚合至集群中;三是使用自定义资源(Custom Resource Def?inition,CRD)。3.1.3 访问Kubernetes REST API
以编程的方式访问Kubernetes REST API有助于了解其流式化的集群管理机制,一种常用的方式是使用curl作为HTTP客户端直接通过API Server在集群上操作资源对象模拟请求和响应的过程。不过,由kubeadm部署的Kubernetes集群默认仅支持HTTPS的访问接口,它需进行一系列的认证检查,好在用户也可以借助kubectl proxy命令在本地主机上为API Server启动一个代理网关,由它支持使用HTTP进行通信,其工作逻辑如图3-3所示。例如,于本地127.0.0.1的8080端口上启动API Server的一个代理网关:
~]$ kubectl proxy --port=8080Starting to serve on 127.0.0.1:8080
而后即可于另一终端使用curl一类的客户端工具对此套接字地址发起访问请求,例如,请求Kubernetes集群上的NamespaceList资源对象,即列出集群上所有的Namespace对象:~]$ curl localhost:8080/api/v1/namespaces/{ "kind": "NamespaceList", "apiVersion": "v1", ……}
或者使用JSON的命令行处理器jq命令对响应的JSON数据流进行内容过滤,例如,下面的命令仅用于显示相关的NamespaceList对象中的各成员对象:~]$ curl -s localhost:8080/api/v1/namespaces/ | jq .items[].metadata.name"default""kube-public""kube-system"
给出特定的Namespace资源对象的名称则能够直接获取相应的资源信息,以kube-system名称空间为例:~]$ curl -s localhost:8080/api/v1/namespaces/kube-system{ "kind": "Namespace", "apiVersion": "v1", "metadata": {
"name": "kube-system",
"selfLink": "/api/v1/namespaces/kube-system",
"uid": "eb6bf659-9d0e-11e8-bf0d-000c29ab0f5b",
"resourceVersion": "33",
"creationTimestamp": "2018-08-11T02:33:23Z"
}, "spec": {
"finalizers": [
"kubernetes"
]
}, "status": {
"phase": "Active"
}
上述命令响应的结果中展现了Kubernetes大多数资源对象的资源配置格式,它是一个JSON序列化的数据结构,具有kind、apiVersion、metadata、spec和status五个一级字段,各字段的意义和功能将在3.2节中重点介绍。3.2 对象类资源格式
Kubernetes API仅接受及响应JSON格式的数据(JSON对象),同时,为了便于使用,它也允许用户提供YAML格式的POST对象,但API Server需要事先自行将其转换为JSON格式后方能提交。API Server接受和返回的所有JSON对象都遵循同一个模式,它们都具有kind和apiVersion字段,用于标识对象所属的资源类型、API群组及相关的版本。进一步地,大多数的对象或列表类型的资源还需要具有三个嵌套型的字段metadata、spec和status。其中metadata字段为资源提供元数据信息,如名称、隶属的名称空间和标签等;spec则用于定义用户期望的状态,不同的资源类型,其状态的意义也各有不同,例如Pod资源最为核心的功能在于运行容器;而status则记录着活动对象的当前状态信息,它由Kubernetes系统自行维护,对用户来说为只读字段。每个资源通常仅接受并返回单一类型的数据,而一种类型可以被多个反映特定用例的资源所接受或返回。例如对于Pod类型的资源来说,用户可创建、更新或删除Pod对象,然而,每个Pod对象的metadata、spec和status字段的值却又是各自独立的对象型数据,它们可被单独操作,尤其是status对象,是由Kubernetes系统单独进行自动更新,而不能由用户手动操作它。
3.2.1 资源配置清单
3.1节中曾使用curl命令通过代理的方式于API Server上请求到了kube-system这个Namespace活动对象的状态信息,事实上,用户也可以直接使用“kubectl get TYPE/NAME -o yaml”命令获取任何一个对象的YAML格式的配置清单,或者使用“kubectl get TYPE/NAME -o json”命令获取JSON格式的配置清单。例如,可使用下面的命令获取kube-system的状态:
~]$ kubectl get namespace kube-system -o yamlapiVersion: v1kind: Namespacemetadata: creationTimestamp: 2018-08-11T02:33:23Z name: kube-system resourceVersion: "33" selfLink: /api/v1/namespaces/kube-system uid: eb6bf659-9d0e-11e8-bf0d-000c29ab0f5bspec: finalizers:
kubernetesstatus:
phase: Active除了极少数的资源之外,Kubernetes系统上的绝大多数资源都是由其使用者所创建的。创建时,需要以与上述输出结果中类似的方式以YAML或JSON序列化方案定义资源的相关配置数据,即用户期望的目标状态,而后再由Kubernetes的底层组件确保活动对象的运行时状态与用户提供的配置清单中定义的状态无限接近。因此,资源的创建要通过用户提供的资源配置清单来进行,其格式类似于kubectl get命令获取到的YAML或JSON形式的输出结果。不过,status字段对用户来说为只读字段,它由Kubernetes集群自动维护。例如,下面就是一个创建Namespace资源时提供的资源配置清单示例,它仅提供了几个必要的字段:
apiVersion: v1kind: Namespacemetadata: name: devspec: finalizers:
kubernetes将如上所述配置清单中的内容保存于文件中,使用“kubectl create -f /PATH/TO/FILE”命令即可将其创建到集群中。创建完成后查看其YAML或JSON格式的输出结果,可以看到Kubernetes会补全其大部分的字段,并提供相应的数据。事实上,Kubernetes的大多数资源都能够以类似的方式进行创建和查看,而且它们几乎都遵循类似的组织结构,下面的命令显示了第2章中使用kubectl run命令创建的Deployment资源对象myapp的状态信息:
~]$ kubectl get deployment myapp -o yamlapiVersion: extensions/v1beta1kind: Deploymentmetadata:…… name: myappspec: replicas: 3 selector:
matchLabels:
run: myapp
……status: ……
为了节约篇幅,上面的输出结果省去了大部分内容,仅保留了其主体结构。从命令结果可以看出,它也遵循Kubernetes API标准的资源组织格式,由apiVersion、kind、metadata、spec和status五个核心字段组成,只是spec字段中嵌套的内容与Namespace资源几乎完全不同。事实上,对几乎所有的资源来说,apiVersion、kind和metadata字段的功能基本上都是相同的,但spec则用于资源的期望状态,而资源之所以存在类型上的不同,也在于它们的嵌套属性存在显著差别,它由用户定义和维护。而status字段则用于记录活动对象的当前状态,它要与用户在spec中定义的期望状态相同,或者正处于转换为与其相同的过程中。3.2.2 metadata嵌套字段
metadata字段用于描述对象的属性信息,其内嵌多个字段用于定义资源的元数据,例如name和labels等,这些字段大体可分为必要字段和可选字段两大类。名称空间级别的资源的必选字段包括如下三项。namespace:指定当前对象隶属的名称空间,默认值为default。name:设定当前对象的名称,在其所属的名称空间的同一类型中必须唯一。uid:当前对象的唯一标识符,其唯一性仅发生在特定的时间段和名称空间中;此标识符主要是用于区别拥有同样名字的“已删除”和“重新创建”的同一个名称的对象。可选字段通常是指由Kubernetes系统自行维护和设置,或者存在默认,或者本身允许使用空值等类型的字段,常用的有如下几个:labels:设定用于标识当前对象的标签,键值数据,常被用作挑选条件。annotations:非标识型键值数据,用来作为挑选条件,用于labels的补充。resourceVersion:当前对象的内部版本标识符,用于让客户端确定对象变动与否。generation:用于标识当前对象目标状态的代别。creationTimestamp:当前对象创建日期的时间戳。deletionTimestamp:当前对象删除日期的时间戳。此外,用户通过配置清单创建资源时,通常仅需要给出必选字段,可选字段可按需指定,对于用户未明确定义的嵌套字段,则需要由一系列的f?inalizer组件自动予以填充。而用户需要对资源创建的目标资源对象进行强制校验,或者在修改时需要用到initializer组件完成,例如,为每个待创建的Pod对象添加一个Sidecar容器等。不同的资源类型也会存在一些专有的嵌套字段,例如,Conf?igMap资源还支持使用clusterName等。
3.2.3 spec和status字段
Kubernetes用spec来描述所期望的对象应该具有的状态,而用status字段来记录对象在系统上的当前状态,因此status字段仅对活动对象才有意义。这两个字段都属于嵌套类型的字段。在定义资源配置清单时,spec是必须定义的字段,用于描述对象的目标状态,即用户期望对象需要表现出来的特征。status字段则记录了对象的当前状态(或实际状态),此字段值由Kubernetes系统负责填充或更新,用户不能手动进行定义。Master的controller-manager通过相应的控制器组件动态管理并确保对象的实际状态匹配用户所期望的状态,它是一种调和(reconciliation)配置系统。例如,Deployment是一种用于描述集群中运行的应用的对象,因此,创建Deployment类型的对象时,需要为目标Deployment对象设定spec,指定期望需要运行的Pod副本数量、使用的标签选择器以及Pod模板等。Kubernetes系统读取待创建的Deployment对象的spec以及系统上相应的活动对象的当前状态,必要时进行对象更新以确保status字段吻合spec字段中期望的状态。如果这其中任一实例出现问题(status字段值发生了变化),那么Kubernetes系统则需要及时对spec和status字段的差异做出响应,例如,补足缺失的Pod副本数目等。spec字段嵌套的字段对于不同的对象类型来说各不相同,具体需要参照Kubernetes API参考手册中的说明分别进行获取,核心资源对象的常用配置字段将会在本书后面的章节中进行讲解。
3.2.4 资源配置清单格式文档
定义资源配置清单时,尽管apiVersion、kind和metadata有章可循,但spec字段对不同的资源来说却是千差万别的,因此用户需要参考Kubernetes API的参考文档来了解各种可用属性字段。好在,Kubernetes在系统上内建了相关的文档,用户可以使用“kubectl explain”命令直接获取相关的使用帮助,它将根据给出的对象类型或相应的嵌套字段来显示相关的下一级文档。例如,要了解Pod资源的一级字段,可以使用类似如下的命令,命令结果会输出支持使用的各一组字段及其说明:
~]$ kubectl explain pods
需要了解某一级字段表示的对象之下的二级对象字段时,只需要指定其一级字段的对象名称即可,三级和四级字段对象等的查看方式依此类推。例如查看Pod资源的Spec对象支持嵌套使用的二级字段,可使用类似如下的命令:~]$ kubectl explain pods.specRESOURCE: spec
对象的spec字段的文档通常包含RESOURCE、DESCRIPTION和FIELDS几节,其中FIELDS节中给出了可嵌套使用的字段、数据类型及功能描述。例如,上面命令的结果显示在FIELDS中的containers字段的数据类型是一个对象列表([]Object),而且是一个必选字段。任何值为对象类型数据的字段都会嵌套一到多个下一级字段,例如,Pod对象中的每个容器也是对象类型数据,它同样包含嵌套字段,但容器不支持单独创建,而是要包含于Pod对象的上下文中,其详细信息可通过三级字段来获取,命令及其结果示例如下:~]$ kubectl explain pods.spec.containersRESOURCE: containers <[]Object>
DESCRIPTION:
List of containers belonging to the pod. Containers cannot currently be added
or removed. There must be at least one container in a Pod. Cannot be updated.
A single application container that you want to run within a pod.
FIELDS: args <[]string>
Arguments to the entrypoint. The docker image's CMD is used if this is not
provided. ……
command <[]string>
Entrypoint array. Not executed within a shell. The docker image's
ENTRYPOINT is used if this is not provided. ……
env <[]Object>
List of environment variables to set in the container. Cannot be updated.
……
内建文档大大降低了用户手动创建资源配置清单的难度,尝试使用某个资源类型时,explain也的确是用户的常用命令之一。熟悉各常用字段的功用之后,以同类型的现有活动对象的清单为模板可以更快地生成目标资源的配置文件,命令格式为“kubectl get TYPE NAME -o yaml --export”,其中--export选项用于省略输出由系统生成的信息。例如,基于现在的Deployment资源对象myapp生成配置模板deploy-demo.yaml文件,可以使用如下命令:~]$ kubectl get deployment myapp -o yaml --export > deploy-demo.yaml
通过资源清单文件管理资源对象较之直接通过命令行进行操作有着诸多优势,具体包括命令行的操作方式仅支持部分资源对象的部分属性,而资源清单支持配置资源的所有属性字段,而且使用配置清单文件还能够进行版本追踪、复审等高级功能的操作。本书后续章节中的大部分资源管理操作都会借助于资源配置文件进行。3.2.5 资源对象管理方式
Kubernetes的API Server遵循声明式编程(declarative programming)范式而设计,侧重于构建程序程序逻辑而无须用户描述其实现流程,用户只需要设定期望的状态,系统即能自行确定需要执行的操作以确保达到用户期望的状态。例如,期望某Deployment控制器管理三个Pod资源对象时,而系统观察到的当前数量却是两个,于是系统就会知道需要创建一个新的Pod资源来满足此期望。Kubernetes的自愈、自治等功能都依赖于其声明式机制。与此对应的另一种范式称为陈述式编程(imperative programming),代码侧重于通过创建一种告诉计算机如何执行操作的算法来更改程序状态的语句来完成,它与硬件的工作方式密切相关,通常,代码将使用条件语句、循环和类继承等控制结构。为了便于用户使用,Kubernetes的API Server也支持陈述式范式,它直接通过命令及其选项完成对象的管理操作,前面用到的run、expose、delete和get等命令都属于此类,执行时用户需要告诉系统要做什么,例如,使用run命令创建一个有着3个Pod对象副本的Deployment对象,或者通过delete命令删除一个名为myapp的Service对象。Kubernetes系统的大部分API对象都有着spec和status两个字段,其中,spec用于让用户定义所期望的状态,系统从中读出相关的定义;而status则是系统观察并负责写入的当前状态,用户可以从中获取相关的信息。Kubernetes系统通过控制器监控着系统对象,由其负责让系统当前的状态无限接近用户所期望的状态。kubectl的命令由此可以分为三类:陈述式命令(imperative command)、陈述式对象配置(imperative object conf?iguration)和声明式对象配置(declarative object conf?iguration)。第一种方式即此前用到的run、expose、delete和get等命令,它们直接作用于Kubernetes系统上的活动对象,简单易用,但不支持代码复用、修改复审及审计日志等功能,这些功能的使用通常要依赖于资源配置文件,这些文件也称为资源清单。在这种模式下,用户可以访问每个对象的完整模式,但用户还需要深入学习Kubernetes API。如3.2.4节所述,资源清单本质上是一个JSON或YAML格式的文本文件,由资源对象的配置信息组成,支持使用Git等进行版本控制。而用户可以资源清单为基础,在Kubernetes系统上以陈述式或声明式进行资源对象管理,如图3-4所示。
图3-4 基于资源配置清单管理对象
陈述式管理方式包括create、delete、get和replace等命令,与陈述式命令的不同之处在于,它通过资源配置清单读取需要管理的目标资源对象。陈述式对象配置的管理操作直接作用于活动对象,即便仅修改配置清单中的极小一部分内容,使用replace命令进行的对象更新也将会导致整个对象被替换。进一步地,混合使用陈述式命令进行清单文件带外修改时,必然会导致用户丢失活动对象的当前状态。声明式对象配置并不直接指明要进行的对象管理操作,而是提供配置清单文件给Kubernetes系统,并委托系统跟踪活动对象的状态变动。资源对象的创建、删除及修改操作全部通过唯一的命令apply来完成,并且每次操作时,提供给命令的配置信息都将保存于对象的注解信息(kubectl.kubernetes.io/last-applied-conf?iguration)中,并通过对比检查活动对象的当前状态、注解中的配置信息及资源清单中的配置信息三方进行变更合并,从而实现仅修改变动字段的高级补丁机制。陈述式对象配置相较于声明式对象配置来说,其缺点在于同一目录下的配置文件必须同时进行同一种操作,例如,要么都创建,要么都更新等,而且其他用户的更新也必须反映在配置文件中,不然其更新在一下次的更新中将会被覆盖。因此,声明式对象配置是优先推荐给用户使用的管理机制。然而,对于新手来说,陈述式命令的配置方式最易于上手,对系统有所了解后易于切换为使用陈述式对象配置管理方式。因此,若推荐给高级用户则推荐使用声明式配置,并建议同时使用版本控制系统存储所期望的状态,以及跨对象的历史信息,并启用变更复审机制。另外,推荐使用借助于kube-applier等一类的项目实现自动化声明式配置,用户将配置推送到Git仓库中,然后借助此类工具即能将其自动同步于Kubernetes集群上。3.3 kubectl命令与资源管理
API Server是Kubernetes集群的网关,用户和管理员以及其他客户端仅能通过此网关接口与集群进行交互。API是面向程序员的访问接口,目前可较好地支持Golang和Python编程语言,当然,终端用户更为常用的是通用命令行工具kubectl。访问集群之前,各类客户端需要了解集群的位置并拥有访问集群的凭据才能获取访问许可。使用kubeadm进行集群初始化时,kubeadm init自动生成的/etc/kubernetes/admin.conf文件是客户端接入当前集群时使用的kubeconf?ig文件,它内建了用于访问Kubernetes集群的最高管理权限的用户账号及相关的认证凭据,可由kubectl直接使用。
3.3.1 资源管理操作概述
资源的管理操作可简单归结为增、删、改、查四种,kubectl提供了一系列的子命令用于执行此类任务,如create、delete、patch、apply、replace、edit、get等,其中有些命令必须基于资源清单来进行,如apply和replace命令,也有些命令既可基于清单文件进行,也可实时作用于活动资源之上,如create、get、patch和delete等。kubectl命令能够读取任何以.yaml、.yml或.json为后缀的文件(可称为配置清单或配置文件,后文将不加区别地使用这两个术语)。实践中,用户既可以为每个资源使用专用的清单文件,也可以将多个相关的资源(例如,属于同一个应用或微服务)组织在同一个清单文件中。不过,如果是YAML格式的清单文件,多个资源彼此之间要使用“---”符号作为单独的一行进行资源分割。这样,多个资源就将以清单文件中定义的次序被create、apply等子命令调用。kubectl的多数子命令支持使用“-f”选项指定使用的清单文件路径或URL,也可以是存储有清单文件的目录,另外,此选项在同一命令中也可重复使用多次。如果指定的目录路径存在子目录中时,那么可按需同时使用“-R”选项以递归获取子目录中的配置清单。再者,支持使用标签和注解是Kubernetes系统的一大特色,它为资源管理机制增色不少,而且delete和get等命令能够基于标签挑选目标对象,有些资源甚至必须依赖于标签才能正常使用和工作,例如Service和Pod控制器Deployment等资源对象。子命令label用于管理资源标签,而管理资源注解的子命令则是annotate。就地更新(修改)现有的资源也是一种常见的操作。apply命令通过比较资源在清单文件中的版本及前一次的版本执行更新操作,它不会对未定义的属性产生额外的作用。edit命令相当于先使用get命令获取资源配置,通过交互式编辑器修改后再自动使用apply命令将其应用。patch命令基于JSON补丁、JSON合并补丁及策略合并补丁对资源进行就地更新操作。为了利用apply命令的优势,用户应该总是使用apply命令或create --save-conf?ig命令创建资源。
3.3.2 kubectl的基本用法
kubectl是最常用的客户端工具之一,它提供了基于命令行访问Kubernetes API的简洁方式,能够满足对Kubernetes的绝大部分的操作需求。例如,需要创建资源对象时,kubectl会将JSON格式的清单内容以POST方式提交至API Server。本节主要描述kubectl的基本功能。如果要单独部署kubectl,Kubernetes也提供了相应的单独发行包,或者适配于各平台的程序管理器的相关程序包,如rpm包或deb包等,用户根据平台类型的不同获取相匹配的版本安装完成即可,操作步骤类似于前面的安装方法,因此这里不再给出其具体过程。kubectl是Kubernetes系统的命令行客户端工具,特性丰富且功能强大,是Kubernetes管理员最常用的集群管理工具。其最基本的语法格式为“kubectl [command] [TYPE] [NAME] [f?lags]”,其中各部分的简要说明如下。1)command:对资源执行相应操作的子命令,如get、create、delete、run等;常用的核心子命令如表3-1所示。2)TYPE:要操作的资源对象的类型,如pods、services等;类型名称区分字符大小写,但支持使用简写格式。3)NAME:对象名称,区分字符大小写;省略时,则表示指定TYPE的所有资源对象;另外,也可以直接使用“TYPE/NAME”的格式来表示资源对象。4)f?lags:命令行选项,如“-s”或“--server”;另外,get等命令在输出时还有一个常用的标志“-o ”用于指定输出格式,如表3-1所示。
表3-1 kubectl的子命令列表
命令 命令类别 功能说明create 基础命令(初级) 通过文件或标准输入创建资源expose 基于rc、service、deployment或pod创建Service资源run 通过创建Deployment在集群中运行指定的镜像set 设置指定资源的特定属性get 基础命令(中级) 显示一个或多个资源explain 打印资源文档edit 编辑资源delete 基于文件名、stdin、资源或名字,以及资源和选择器删除资源rollout 部署命令 管理资源的滚动更新rolling-update 对ReplicationController执行滚动升级scale 伸缩Deployment、ReplicaSet、RC或Job的规模autoscale 对Deployment、ReplicaSet或RC进行自动伸缩certif?icate 集群管理命令 配置数字证书资源cluster-info 打印集群信息top 打印资源(CPU/Memory/Storage)使用率cordon 将指定node设定为“不可用”(unschedulable)状态uncordon 将指定node设定为“可用”(schedulable)状态drain “排干”指定的node的负载以进入“维护”模式taint 为node声明污点及标准行为describe 排错及调试命令 显示指定的资源或资源组的详细信息logs 显示一个Pod内某容器的日志attach 附加终端至一个运行中的容器exec 在容器中执行指定命令port-forward 将本地的一个或多个端口转发至指定的Podproxy 创建能够访问Kubernetes API Server的代理cp 在容器间复制文件或目录auth 打印授权信息apply 高级命令 基于文件或stdin将配置应用于资源patch 使用策略合并补丁更新资源字段replace 基于文件或stdin替换一个资源convert 为不同的API版本转换配置文件label 设置命令 更新指定资源的labelannotate 更新资源的annotationcompletion 输出指定的shell(如bash)的补全码version 其他命令 打印Kubernetes服务端和客户端的版本信息api-versions 以“group/version”格式打印服务器支持的API版本信息conf?ig 配置kubeconf?ig文件的内容plugin 运行命令行插件help 打印任一命令的帮助信息kubectl命令还包含了多种不同的输出格式(如表3-2所示),它们为用户提供了非常灵活的自定义输出机制,如输出为YAML或JSON格式等。
表3-2 kubectl get命令的常用输出格式
输出格式 格式说明-o wide 显示资源的额外信息-o name 仅打印资源的名称-o yaml YAML格式化输出API对象信息-o json JSON格式化输出API对象信息-o go-template 以自定义的go模板格式化输出API对象信息-o custom-columns 自定义要输出的字段此外,kubectl命令还有许多通用的选项,这个可以使用“kubectl options”命令来获取。下面列举几个比较常用命令。-s或--server:指定API Server的地址和端口。--kubeconf?ig:使用的kubeconf?ig文件路径,默认为~/.kube/conf?ig。--namespace:命令执行的目标名称空间。kubectl的部分子命令在第2章已经多次提到,余下的大多数命令在后续的章节中还会用到,对于它们的使用说明也将在首次用到时进行展开说明。
3.4 管理名称空间资源
名称空间(Namespace)是Kubernetes集群级别的资源,用于将集群分隔为多个隔离的逻辑分区以配置给不同的用户、租户、环境或项目使用,例如,可以为development、qa和production应用环境分别创建各自的名称空间。Kubernetes的绝大多数资源都隶属于名称空间级别(另一个是全局级别或集群级别),名称空间资源为这类的资源名称提供了隔离的作用域,同一名称空间内的同一类型资源名必须是唯一的,但跨名称空间时并无此限制。不过,Kubernetes还是有一些资源隶属于集群级别的,如Node、Namespace和PersistentVolume等资源,它们不属于任何名称空间,因此资源对象的名称必须全局唯一。Kubernetes的名称空间资源不同于Linux系统的名称空间,它们是各自独立的概念。另外,Kubernetes名称空间并不能实现Pod间的通信隔离,它仅用于限制资源对象名称的作用域。
3.4.1 查看名称空间及其资源对象
Kubernetes集群默认提供了几个名称空间用于特定的目的,例如,kube-system主要用于运行系统级资源,而default则为那些未指定名称空间的资源操作提供一个默认值,前面章节中的绝大多数资源管理操作都在default名称空间中进行。“kubectl get namespaces”命令则可以查看namespaces资源:
~]$ kubectl get namespacesNAME STATUS AGEdefault Active 6dkube-public Active 6dkube-system Active 6d
也可以使用“kubectl describe namespaces”命令查看特定名称空间的详细信息,例如:~]$ kubectl describe namespaces default
kubectl的资源查看命令在多数情况下应该针对特定的名称空间来进行,为其使用“-n”或“--namespace”选项即可,例如,查看kube-system下的所有Pod资源:~]$ kubectl get pods -n kube-systemNAME READY STATUS RESTARTS AGEetcd-master.ikubernetes.io 1/1 Running 1 6dkube-apiserver-master.ikubernetes.io 1/1 Running 1 6d……
命令结果显示出kube-system与default名称空间的Pod资源对象并不相同,这正是Namespace资源的名称隔离功能的体现。有了Namespace对象,用户再也不必精心安排资源名称,也不用担心误操作了其他用户的资源。3.4.2 管理Namespace资源
Namespace是Kubernetes API的标准资源类型之一,如3.2.1节中所述,它的配置主要有kind、apiVersion、metadata和spec等一级字段组成。将3.2.1节中的名称空间配置清单保存于配置文件中,使用陈述式对象配置命令create或声明式对象配置命令apply便能完成创建:
~]$ kubectl apply -f namespace-example.yamlnamespace/dev created
名称空间资源属性较少(通常只需要指定名称即可),简单起见,kubectl为其提供了一个封装的专用陈述式命令“kubectl create namespace”。Namespace资源的名称仅能由字母、数字、连接线、下划线等字符组成。例如,下面的命令可用于创建名为qa的Namespace对象:~]$ kubectl create namespace qanamespace/qa created
namespace资源的名称仅能由字母、数字、连接线、下划线等字符组成。实践中,不建议混用不同类别的管理方式。考虑到声明式对象配置管理机制的强大功能,强烈推荐用户使用apply和patch等命令进行资源创建及修改一类的管理操作。使用kubectl管理资源时,如果一并提供了名称空间选项,就表示此管理操作仅针对指定名称空间进行,而删除Namespace资源会级联删除其包含的所有其他资源对象。表3-3给出了几个常用的命令格式。表3-3 结合名称空间使用的删除命令
命令格式 功能kubectl delete TYPE RESOURCE -n NS 删除指定名称空间内的指定资源kubectl delete TYPE --all -n NS 删除指定名称空间内的指定类型的所有资源kubectl delete all -n NS 删除指定名称空间内的所有资源kubectl delete all --all 删除所有名称空间中的所有资源需要再次指出的是,Namespace对象仅用于资源对象名称的隔离,它自身并不能隔绝跨名称空间的Pod间通信,那是网络策略(network policy)资源的功能。3.5 Pod资源的基础管理操作Pod是Kubernetes API中的核心资源类型,它可以定义在JSON或YAML格式的资源清单中,由资源管理命令进行陈述式或声明式管理。创建时,用户通过create或apply命令将请求提交到API Server并将其保存至集群状态存储系统etcd中,而后由调度器将其调度至最佳目标节点,并被相应节点的kubelet借助于容器引擎创建并启动。这种由用户直接通过API创建的Pod对象也称为自主式Pod。
3.5.1 陈述式对象配置管理方式
陈述式对象配置管理机制,是由用户通过配置文件指定要管理的目标资源对象,而后再由用户借助于命令直接指定Kubernetes系统要执行的管理操作的管理方式,常用的命令有create、delete、replace、get和describe等。1.创建Pod资源Pod是标准的Kubernetes API资源,在配置清单中使用kind、apiVersion、metadata和spec字段进行定义,status字段在对象创建后由系统自行维护。Pod对象的核心功用在于运行容器化应用,在其spec字段中嵌套的必选字段是containers,它的值是一个容器对象列表,支持嵌套创建一到多个容器。下面是一个Pod资源清单示例文件,在spec中定义的期望的状态是在Pod对象中基于ikubernetes/myapp: v1镜像运行一个名为myapp的容器:
apiVersion: v1kind: Podmetadata: name: pod-examplespec: containers:
name: myapp image: ikubernetes/myapp:v1把上面的内容保存于配置文件中,使用“kubectl [COMMAND] -f /PATH/TO/YAML_FILE”命令以陈述式对象配置进行资源对象的创建,下面是相应的命令及响应结果:
~]$ kubectl create -f pod-example.yamlpod/pod-example created
如果读者熟悉JSON,也可以直接将清单文件定义为JSON格式;YAML格式的清单文件本身也是由API Server事先将其转换为JSON格式而后才进行应用的。命令的返回信息表示目标Pod对象pod-example得以成功创建。事实上,create命令中的-f选项也支持使用目录路径或URL,而且目标路径为目录时,还支持使用-R选项进行子目录递归。另外,--record选项可以将命令本身记录为目标对象的注解信息kubernetes.io/change-cause,而--save-conf?ig则能够将提供给命令的资源对象配置信息保存于对象的注解信息kubectl.kubernetes.io/last-applied-conf?iguration中,后一个命令的功用与声明式对象配置命令apply的功能相近。2.查看Pod状态get命令默认显示资源对象最为关键的状态信息,而describe等命令则能够打印出Kubernetes资源对象的详细状态。不过,虽然创建时给出的资源清单文件较为简洁,但“kubectl get”命令既可以使用“-o yaml”或“-o json”选项输出资源对象的配置数据及状态,也能够借助于“--custom-columns”选项自定义要显示的字段:~]$ kubectl get -f pod-example.yamlNAME READY STATUS RESTARTS AGEpod-example 1/1 Running 0 1m~]$ kubectl get -f pod-example.yaml -o custom-columns=NAME:metadata.name,STATUS:status.phaseNAME STATUSpod-example Running
使用“-o yaml”或“-o json”选项时,get命令能够返回资源对象的元数据、期望的状态及当前状态数据信息,而要打印活动对象的详细信息,则需要describe命令,它可根据资源清单、资源名或卷标等方式过滤输出符合条件的资源对象的信息。命令格式为“kubectl describe (-f FILENAME | TYPE [NAME_PREFIX | -l label] | TYPE/NAME)”。例如,显示pod-example的详细信息,可使用类似如下的命令:~]$ kubectl describe -f pod-example.yaml对于Pod资源对象来说,它能够返回活动对象的元数据、当前状态、容器列表及各容器的详情、存储卷对象列表、QoS类别、事件及相关信息,这些详情对于了解目标资源对象的状态或进行错误排查等操作来说至关重要。3.更新Pod资源对于活动对象,并非其每个属性值都支持修改,例如,Pod资源对象的metadata.name字段就不支持修改,除非删除并重建它。对于那些支持修改的属性,比如,容器的image字段,可将其完整的配置清单导出于配置文件中并更新相应的配置数据,而后使用replace命令基于陈述式对象配置的管理机制进行资源对象的更新。例如,将前面创建pod-example时使用的资源清单中的image值修改为“ikubernetes/myapp: v2”,而后执行更新操作:~]$ kubectl get pods pod-example -o yaml > pod-example-update.yaml~]$ sed -i 's@(image:).*@ikubernetes/myapp:v2@' pod-example-update.yaml~]$ kubectl replace -f pod-example-update.yamlpod/pod-example replaced
更新活动对象的配置时,replace命令要重构整个资源对象,故此它必须基于完整格式的配置信息才能进行活动对象的完全替换。若要基于此前的配置文件进行替换,就必须使用--force选项删除此前的活动对象,而后再进行新建操作,否则命令会返回错误信息。例如,将前面第一步“创建Pod资源”内的配置清单中的镜像修改为“ikubernetes/myapp: v2”后再进行强制替换,命令如下:~]$ kubectl replace -f pod-example.yaml --forcepod "pod-example" deletedpod/pod-example replaced
4.删除Pod资源陈述式对象配置管理方式下的删除操作与创建、查看及更新操作类似,为delete命令使用-f选项指定配置清单即可,例如,删除pod-example.yaml文件中定义的Pod资源对象:~]$ kubectl delete -f pod-example.yamlpod "pod-example" deleted
之后再次打印相关配置清单中定义的资源对象即可验正其删除的结果,例如:~]$ kubectl get -f pod-example.yamlNo resources found.Error from server (NotFound): pods "pod-example" not found
3.5.2 声明式对象配置管理方式
陈述式对象配置管理机制中,同时指定的多个资源必须进行同一种操作,而且其replace命令是通过完全替换现有的活动对象来进行资源的更新操作,对于生产环境来说,这并非理想的选择。声明式对象配置操作在管理资源对象时将配置信息保存于目标对象的注解中,并通过比较活动对象的当前配置、前一次管理操作时保存于注解中的配置,以及当前命令提供的配置生成更新补丁从而完成活动对象的补丁式更新操作。此类管理操作的常用命令有apply和patch等。例如,创建3.5.1节中定义的主容器使用“ikubernetes/myapp: v1”镜像的Pod资源对象,还可以使用如下命令进行:
~]$ kubectl apply -f pod-example.yamlpod/pod-example created
而更新对象的操作,可在直接修改原有资源清单文件后再次对其执行apply命令来完成,例如,修改Pod资源配置清单中的镜像文件为“ikubernetes/myapp: v2”后再次执行如上的apply命令:~]$ kubectl apply -f pod-example.yamlpod/pod-example configured
命令结果显示资源重新配置完成并且已经生效。事实上,此类操作也完全能够使用patch命令直接进行补丁操作。而资源对象的删除操作依然可以使用apply命令,但要同时使用--prune选项,命令的格式为“kubectl apply -f --prune -l ”。需要注意的是,此命令异常凶险,因为它将基于标签选择器过滤出所有符合条件的对象,并检查由-f指定的目录中是否存在某配置文件已经定义了相应的资源对象,那些不存在相应定义的资源对象将被删除。因此,删除资源对象的操作依然建议使用陈述式对象配置方式的命令“kubectl delete”进行,这样的命令格式操作目标明确且不易出现偏差。3.6 本章小结
本章介绍了Kubernetes系统上常用的资源对象类型及其管理方式,在说明kubectl命令行工具的基础用法后借助于Namespace资源对象简单说明了其使用方式,具体如下。Kubernetes提供了RESTful风格的API,它将各类组件均抽象为“资源”,并通过属性赋值完成实例化。Kubernetes API支持的资源类型众多,包括Node、Namespace、Pod、Service、Deployment、Conf?igMap等上百种。标准格式的资源配置大多都是由kind、apiVersion、metadata、spec和status等一级属性字段组成,其中spec是由用户定义的期望状态,而status则是由系统维护的当前状态。Kubernetes API主要提供的是声明式对象配置接口,但它也支持陈述式命令及陈述式对象配置的管理方式。kubectl命令功能众多,它将通过子命令完成不同的任务,如create、delete、edit、replace、apply等。Namespace和Pod是Kubernetes系统的基础资源类型。
文章
容器 · Kubernetes · Perl · API · 数据格式
2019-10-21
MongoDB优化浅析
一、MongoDB优化整体思路
MongoDB的查询语句优化与关系型数据库类似,简单来说就是通过慢查询日志找出慢查询语句,然后通过执行计划进行分析,最后根据实际情况进行优化。
二、慢查询日志分析
2.1 简介
在MongoDB中,慢查询日志被叫做Profiler,我们可以通过设置Profiler来记录慢查询语句;然后就可以根据慢查询日志中的内容进行优化分析了。 MongoDB的慢查询日志记录在system.profile这个集合中,默认情况下慢查询日志是关闭的,可以在数据库级别上或者是节点级别上进行配置。
2.2 慢查询日志的使用
MongoDB有两种方式可以对慢查询日志进行配置:1、直接在启动参数或者配置文件里进行设置:
#传统配置文件格式
profile = 1
slowms = 100 #默认为100ms
#YAML配置文件格式
operationProfiling:
mode: <string> # 默认为 off,可选值 off、slowOp、all
slowOpThresholdMs: <int> # 阈值,默认值为100ms
slowOpSampleRate: <double> #随机采集慢查询的百分比值,sampleRate值默认为1,表示都采集,0.5 表示采集50%的内容
2、通过db.setProfilingLevel(level,slowms) 语句设置慢查询级别和阈值;
级别
含义
0
关闭慢查询
1
只记录超过阈值的查询
2
记录所有查询
MongoDB可以通过db.getProfilingStatus()获取慢查询日志的相关配置信息
# 指定数据库,并指定阈值慢查询 ,超过10毫秒的查询被记录
use test
db.setProfilingLevel(1, { slowms: 10 })
# 随机采集慢查询的百分比值,sampleRate 值默认为1,表示都采集,0.5 表示采集50%的内容。
db.setProfilingLevel(1, { sampleRate: 0.5 })
#查询大于等于5ms的时间
db.system.profile.find( { millis : { $gt : 5 } } )
# 查询最近的5个慢查询日志
db.system.profile.find().limit(5).sort( { ts : -1 } )
# 查询除命令类型为'command'的日志
db.system.profile.find( { op: { $ne : 'command' } } )
# 查询数据库为test集合为t1的日志
db.system.profile.find( { ns : 'test.t1' } )
# 查询时间从 2019-09-01 0点到 2012-10-01 0点之间的日志
db.system.profile.find({
ts : {
$gt: new ISODate("2019-09-01T00:00:00Z"),
$lt: new ISODate("2019-10-01T00:00:00Z")
}
})
2.3 慢查询日志内容详解
{
"op" : "query",
"ns" : "test.data",
"command" : { "find" : "data", "filter" : { "id" : { "$gt" : 2, "$lt" : 10 }, "name" : "ZJ" }, "lsid" : { "id" : UUID("2fc9dd75-6731-4426-b0ba-38e1a5d7317e") }, "$db" : "test" },
"keysExamined" : 0,
"docsExamined" : 1000000,
"cursorExhausted" : true,
"numYield" : 7812,
"nreturned" : 7,
"queryHash" : "17295960",
"planCacheKey" : "17295960",
"locks" : { "ParallelBatchWriterMode" : { "acquireCount" : { "r" : NumberLong(1) } }, "ReplicationStateTransition" : { "acquireCount" : { "w" : NumberLong(7814) } }, "Global" : { "acquireCount" : { "r" : NumberLong(7814) } }, "Database" : { "acquireCount" : { "r" : NumberLong(7813) } }, "Collection" : { "acquireCount" : { "r" : NumberLong(7813) } }, "Mutex" : { "acquireCount" : { "r" : NumberLong(1) } } },
"flowControl" : { },
"storage" : { },
"responseLength" : 546,
"protocol" : "op_msg",
"millis" : 623,
"planSummary" : "COLLSCAN",
"execStats" : { "stage" : "COLLSCAN", "filter" : { "$and" : [ { "name" : { "$eq" : "ZJ" } }, { "id" : { "$lt" : 10 } }, { "id" : { "$gt" : 2 } } ] }, "nReturned" : 7, "executionTimeMillisEstimate" : 2, "works" : 1000002, "advanced" : 7, "needTime" : 999994, "needYield" : 0, "saveState" : 7812, "restoreState" : 7812, "isEOF" : 1, "direction" : "forward", "docsExamined" : 1000000 },
"ts" : ISODate("2019-10-24T04:00:54.221Z"),
"client" : "127.0.0.1",
"appName" : "MongoDB Shell",
"allUsers" : [ ], "user" : ""
}
上面是我截取慢查询日志中的一个查询语句,其中可以把它拆解为几个部分来理解:
"op"
该项表明该慢日志的种类,主要包含如下几类:
insert
query
update
remove
getmore
command
"ns"
该项表明该慢日志是哪个库下的哪个集合所对应的慢日志
"command"
该项详细输出了慢日志的具体语句和行为
"keysExamined"
该项表明为了找出最终结果MongoDB搜索了多少个key
"docsExamined"
该项表明为了找出最终结果MongoDB搜索了多少个文档
"numYield"
为了让别的操作完成而屈服的次数,一般发生在需要访问的数据尚未被完全读取到内存中,MongoDB会优先完成在内存中的操作
"nreturned"
该项表明返回的记录数
"locks"
该项表明在操作中产生的锁的相关信息
锁的种类:
级别
含义
Global
全局锁
MMAPV1Journal
MMAPV1同步日志时加的一种锁
Database
数据库锁
Collection
集合锁
Metadata
元数据锁
oplog
oplog锁
锁的模式:
级别
含义
R
共享S锁
W
排他X锁
r
意向共享IS锁
w
意向排他IX
"responseLength"
结果返回的大小,单位为bytes,该值如果过大,则需考虑limit()等方式减少输出结果
"millis"
该操作从开始到结束耗时多少,单位为ms
"execStats"
包含了该操作执行的详细信息
"ts"
该操作执行时的时间
"client"
哪个客户端发起的该操作,并显示出该客户端的ip或hostname
"allUsers"
哪个认证用户执行的该操作
"user"
是否认证用户执行该操作,如认证后使用其他用户操作,该项为空
三、执行计划分析
3.1 简介
MongoDB查看执行计划的语法:
db.collection.find().explain()
MongoDB执行计划三种模式:
queryPlanner Mode:只会显示 winning plan 的 queryPlanner,自建MongoDB默认模式
executionStats Mode:只会显示 winning plan 的 queryPlanner + executionStats
allPlansExecution Mode:会显示所有执行计划的 queryPlanner + executionStats,阿里云MongoDB默认模式
3.2 执行计划内容详解
{
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "test.data",
"indexFilterSet" : false,
"parsedQuery" : {
"$and" : [
{
"name" : {
"$eq" : "ZJ"
}
},
{
"id" : {
"$lt" : 10
}
},
{
"id" : {
"$gt" : 2
}
}
]
},
"queryHash" : "17295960",
"planCacheKey" : "17295960",
"winningPlan" : {
"stage" : "COLLSCAN",
"filter" : {
"$and" : [
{
"name" : {
"$eq" : "ZJ"
}
},
{
"id" : {
"$lt" : 10
}
},
{
"id" : {
"$gt" : 2
}
}
]
},
"direction" : "forward"
},
"rejectedPlans" : [ ]
},
"executionStats" : {
"executionSuccess" : true,
"nReturned" : 7,
"executionTimeMillis" : 650,
"totalKeysExamined" : 0,
"totalDocsExamined" : 1000000,
"executionStages" : {
"stage" : "COLLSCAN",
"filter" : {
"$and" : [
{
"name" : {
"$eq" : "ZJ"
}
},
{
"id" : {
"$lt" : 10
}
},
{
"id" : {
"$gt" : 2
}
}
]
},
"nReturned" : 7,
"executionTimeMillisEstimate" : 4,
"works" : 1000002,
"advanced" : 7,
"needTime" : 999994,
"needYield" : 0,
"saveState" : 7812,
"restoreState" : 7812,
"isEOF" : 1,
"direction" : "forward",
"docsExamined" : 1000000
},
"allPlansExecution" : [ ]
},
"serverInfo" : {
"host" : "xsj",
"port" : 27017,
"version" : "4.2.0",
"gitVersion" : "a4b751dcf51dd249c5865812b390cfd1c0129c30"
},
"ok" : 1
}
上面这条语句的执行计划可以拆解为几个部分来理解:
queryPlanner
parsedQuery:该部分解析了SQL的所有过滤条件这个部分很好理解,不在过多说明; winningPlan:该部分是SQL最终选择的执行计划该部分主要关注stage,stage是执行计划的类型,有以下分类:
COLLSCAN:全表扫描
IXSCAN:索引扫描
FETCH:根据索引去检索指定document
SHARD_MERGE:将各个分片返回数据进行merge
SORT:表明在内存中进行了排序
LIMIT:使用limit限制返回数
SKIP:使用skip进行跳过
IDHACK:针对_id进行查询
executionStats这个部分最好的情况是:nReturned = totalKeysExamined = totalDocsExamined,此部分主要是文档扫描数以及消耗信息。
四、索引分析
4.1 基本命令
查看索引MongoDB通过getIndexes()查看集合的所有索引
db.getCollection('data').getIndexes()
[
{
"v" : 2,
"key" : {
"_id" : 1
},
"name" : "_id_",
"ns" : "test.data"
},
{
"v" : 2,
"key" : {
"name" : 1
},
"name" : "name_1",
"ns" : "test.data"
}
]
查看索引大小MongoDB通过totalIndexSize()查看集合索引的总大小
db.getCollection('data').totalIndexSize()
14389248 //单位字节
创建索引
db.collection.createIndex(keys,options)
keys,要建立索引的参数列表。如:{KEY:1/-1},其中key表示字段名,1/-1表示升降序。
options:
background,在后台建立索引,以便建立索引时不阻止其他数据库活动。默认值 false。
unique,创建唯一索引。默认值 false。
name,指定索引的名称。如果未指定,MongoDB会生成一个索引字段的名称和排序顺序串联。
dropDups,创建唯一索引时,如果出现重复删除后续出现的相同索引,只保留第一个。
sparse,对文档中不存在的字段数据不启用索引。默认值是 false。
v,索引的版本号。
weights,索引权重值,数值在1到99999 之间,表示该索引相对于其他索引字段的得分权重。
删除索引
db.data.dropIndex("user_1")方法用于删除指定的索引
db.data.dropIndexes()方法用于删除全部的索引
4.2 索引分类
单字段索引
单字段索引是针对单个字段进行设置索引的操作
#创建索引语法
db.data.createIndex({name:1})
name:1代表按照升序进行排序,降序排序的索引为-1
db.data.createIndex({name:-1})
联合索引
联合索引在单字段索引上进行了多个字段操作,将多个字段合并为一个索引的联合索引,联合索引需要遵守最左前缀原则。
#创建索引语法
db.data.createIndex({name:1,time:1})
多key索引
当内容是数组或者list集合创建的一种索引。该索引会为数组中的每个字段创建索引。
子文档索引
该索引用来嵌入子文档中的字段进行创建索引。操作也可以有复合索引,单字段索引。
db.data.createIndex({"user.name":1})
4.2 索引的属性
Mongodb支持多类型的索引,还能对索引增加一些额外的属性。
唯一索引:在Mongodb中_id就是利用单字段索引加唯一索引的属性构成的
部分索引(3.2版本新增):仅索引符合指定过滤器表达式集合中的文档。部分索引有较低的存储要求,降低索引的创建与维护
稀疏索引:确保索引仅包含具有索引字段的文档的条目,会跳过没有索引字段的文档
TTL索引:在一定时间后自动从集合中删除文档的一种索引
五、总结
本文介绍了MongoDB优化的基本思路以及如何查看慢日志、执行计划和索引的基本使用,如果有兴趣可以针对性的深入研究。
文章
NoSQL · MongoDB · 数据库 · 索引
2019-10-24
实战静态拆分视图
该部分主要介绍一些基本概念和创建拆分视图的一般过程。
MFC支持两种类型的拆分窗口:静态的和动态的。这里只探讨静态拆分,不过首先还是要熟悉一下这些概念。
静态拆分窗口的行列数在拆分窗口被创建时就设置好了,用户不能更改。但是用户可以缩放各行各列。一个静态拆分窗口最多可以包含16行16列。要找一个使用了静态拆分窗口的应用程序,只要看一下windows管理器即可。
动态拆分窗口最多可以有两行两列,但它们可以相互拆分和合并。Vc就使用了动态拆分窗口使得可以同时编辑源程序文件的两个以上不同的部分。
选择静态或动态拆分的一个准则是是否希望用户能够交互地修改拆分窗口的行列配置。另一个决定因素是计划在拆分窗口中使用的视图种类。在静态拆分窗口中很容易使用两个以上不同种类的视图,因为您可以在每个窗格中指定所用的视图类型。但是在动态拆分窗口中,MFC管理着视图,除非从CsplitterWnd派生一个新类并修改拆分窗口的默认操作性能,否则拆分窗口中的所有视图使用的都是相同的视图类。
静态拆分窗口是用CsplitterWnd::CreateStatic而不是CsplitterWnd::Create创建,并且由于MFC不会自动创建静态拆分窗口中显示的视图,所以您要亲自在CreateStatic返回之后创建视图。CsplitterWnd为此提供了名为CreateView的函数。给框架窗口添加静态拆分视图的过程如下:
<!--[if !supportLists]-->1. <!--[endif]-->给框架窗口类添加一个CsplitterWnd数据成员。
<!--[if !supportLists]-->2. <!--[endif]-->覆盖框架窗口的OnCreateClient函数,并调用CsplitterWnd::CreateStatic来创建静态拆分视图。
<!--[if !supportLists]-->3. <!--[endif]-->使用CsplitterWnd:: CreateView在每个静态拆分窗口的窗格中创建视图。
使用静态拆分窗口的一个优点是由于您自己给窗格添加视图,所以可以控制放入视图的种类。
下列中创建的静态拆分窗口包含了两种不同的视图:
BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT lpcs, CcreateContext* pContext)
{
if(!m_wndSplitter.CreateStatic(this, 1, 2) ||
!m_wndSplitter.CreateView(0, 0, RUNTIME_CLASS(CtextView), Csize(128, 0), pContext) ||
!m_wndSplitter.CreateView(0, 1, RUNTIME_CLASS(CpictureView), Csize(0, 0),pContext) )
return FALSE;
<!--[if !supportEmptyParas]--> <!--[endif]-->
return TRUE;
}
传递给CreateStatic的参数指定了拆分窗口的父亲以及拆分窗口包含的行列数。对每个窗格调用一次CreateView。用从0开始的行列编号来标示窗格。在上面的代码中,第一次调用CreateView在左窗格(0行0列)中加入类型为CtextView的视图,第二次调用在右窗格(0行1列)加入类型为CpictureView的视图。传递给CreateView的Csize对象指定了窗格的初始尺寸。在上面的代码中,CtextView窗格的初始宽度为128象素,CpictureView窗格将占据剩余的窗口宽度。指定右窗格宽度的值和指定两个窗格高度的值都是0,这是因为主结构会忽略它们。
下面以一个单文档程序为例,说明静态拆分视图的实现过程:
<!--[if !supportLists]-->1. <!--[endif]-->首先建立一个单文档应用程序SplitWnd,视图CSplitWndView类型为列表视图。利用CSplitWndView ::OnInitialUpdate初始化列表视图。
<!--[if !supportLists]-->2. <!--[endif]-->为该工程新增一个树型视图类CMyTreeView,并在该类中添加HTREEITEM类型的成员变量m_hSubTree[2],该成员变量用来保存树型视图的子树句柄。利用CMyTreeView ::OnInitialUpdate初始化树型视图,为该树型视图添加一个树根,两个子树,参考代码如下:
HTREEITEM hRoot = GetTreeCtrl().InsertItem(_T("树根"), 。。。, 。。。, TVI_ROOT);
m_hSubTree[0] = GetTreeCtrl().InsertItem(_T("子树1"), 。。。, 。。。, hRoot);
m_hSubTree [1] = GetTreeCtrl().InsertItem(_T("子树2"), 。。。, 。。。, hRoot);
<!--[if !supportLists]-->3. <!--[endif]-->在框架类中添加一个CSplitterWnd 类型的成员变量m_wndSplitter1,并重载OnCreateClient函数来拆分视图,代码如下:
BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pContext)
{
if(!(m_wndSplitter1.CreateStatic(this, 1, 2) ) ||
!(m_wndSplitter1.CreateView(0, 1, RUNTIME_CLASS(CSplitWndView), CSize(0,0), pContext) ) ||
!(m_wndSplitter1.CreateView(0, 0, RUNTIME_CLASS(CMyTreeView), CSize(180 ,0), pContext) ) )
{
return FALSE;
}
return TRUE;
}
如果你设计的程序需要更多的拆分视图,可以再在框架类中添加CSplitterWnd 类型的成员变量m_wndSplitter2,再次利用CreateStatic拆分视图,代码如下:
BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pContext)
{if(!(m_wndSplitter1.CreateStatic(this, 2, 1) ) ||
!(m_wndSplitter1.CreateView(1, 0, RUNTIME_CLASS(CMyListView), CSize(0,0), pContext) ) ||
!(m_wndSplitter2.CreateStatic(&m_wndSplitter1, 1, 2, WS_CHILD|WS_VISIBLE, m_wndSplitter1.IdFromRowCol(0,0)) ) ||
!(m_wndSplitter2.CreateView(0, 0, RUNTIME_CLASS(CMyTreeView), CSize(180 ,0), pContext) ) ||
!(m_wndSplitter2.CreateView(0, 1, RUNTIME_CLASS(CSplitWndView), CSize(0,0), pContext) )
)
{
return FALSE;
}
m_wndSplitter1.SetRowInfo(0, 350, 0); //重新设置行宽
m_wndSplitter1.RecalcLayout();
<!--[if !supportEmptyParas]--> <!--[endif]-->
return TRUE;
}
现在基本的界面就建立好了,有关初始化列表视图的代码要根据具体情况来添加。另外,别忘记在框架.cpp中包含视图类的头文件。
为了视类之间的通讯,我们让这些视类共用一个文档类,现在来编写视图之间通讯的代码:
1.在文档类中添加int m_nViewType,表示要显示的类型。当用户单击“子树1”或“子树2”时改变其值。
<!--[if !supportLists]-->2. <!--[endif]-->建立树型视图通知TVN_SELCHANGED响应函数OnSelchanged,添加如下代码:
CSplitWndDoc* m_pDoc = (CSplitWndDoc*)GetDocument();
HTREEITEM hTmp = GetTreeCtrl().GetSelectedItem(); //得到当前选中的子树句柄
<!--[if !supportEmptyParas]--> <!--[endif]-->
//将m_pDoc->m_nViewType复位
m_pDoc->m_nViewType = 1000;
<!--[if !supportEmptyParas]--> <!--[endif]-->
if(hTmp == m_hSubTree [0] && m_pDoc)
{
m_pDoc->m_nViewType = 0; //将显示类型设置为子树1
}
if(hTmp == m_hSubTree [1] && m_pDoc)
{
m_pDoc->m_nViewType = 1; //将显示类型设置为子树2
}
//在改变子树的情况下才刷新CSplitWndView视图
if(hTmp == m_hSubTree [0] || hTmp == m_hSubTree [1] && m_pDoc)
{
//通知视图更新CSplitWndView
m_pDoc->UpdateAllViews(this);
}
3.在CSplitWndView中重载OnUpdate函数,响应UpdateAllViews。
CSplitWndDoc* pDoc = GetDocument();
GetListCtrl().DeleteAllItems(); //删除列表视图中的项
switch(pDoc->m_nViewType) //查看视图类型
{
case 0:
//将与子树1相关的项添加到列表视图中
break;
case 1:
//将与子树2相关的项添加到列表视图中
break;
}
本文转自 张宇 51CTO博客,原文链接:http://blog.51cto.com/zhangyu/33861,如需转载请自行联系原作者
实战静态拆分视图
该部分主要介绍一些基本概念和创建拆分视图的一般过程。
MFC支持两种类型的拆分窗口:静态的和动态的。这里只探讨静态拆分,不过首先还是要熟悉一下这些概念。
静态拆分窗口的行列数在拆分窗口被创建时就设置好了,用户不能更改。但是用户可以缩放各行各列。一个静态拆分窗口最多可以包含16行16列。要找一个使用了静态拆分窗口的应用程序,只要看一下windows管理器即可。
动态拆分窗口最多可以有两行两列,但它们可以相互拆分和合并。Vc就使用了动态拆分窗口使得可以同时编辑源程序文件的两个以上不同的部分。
选择静态或动态拆分的一个准则是是否希望用户能够交互地修改拆分窗口的行列配置。另一个决定因素是计划在拆分窗口中使用的视图种类。在静态拆分窗口中很容易使用两个以上不同种类的视图,因为您可以在每个窗格中指定所用的视图类型。但是在动态拆分窗口中,MFC管理着视图,除非从CsplitterWnd派生一个新类并修改拆分窗口的默认操作性能,否则拆分窗口中的所有视图使用的都是相同的视图类。
静态拆分窗口是用CsplitterWnd::CreateStatic而不是CsplitterWnd::Create创建,并且由于MFC不会自动创建静态拆分窗口中显示的视图,所以您要亲自在CreateStatic返回之后创建视图。CsplitterWnd为此提供了名为CreateView的函数。给框架窗口添加静态拆分视图的过程如下:
<!--[if !supportLists]-->1. <!--[endif]-->给框架窗口类添加一个CsplitterWnd数据成员。
<!--[if !supportLists]-->2. <!--[endif]-->覆盖框架窗口的OnCreateClient函数,并调用CsplitterWnd::CreateStatic来创建静态拆分视图。
<!--[if !supportLists]-->3. <!--[endif]-->使用CsplitterWnd:: CreateView在每个静态拆分窗口的窗格中创建视图。
使用静态拆分窗口的一个优点是由于您自己给窗格添加视图,所以可以控制放入视图的种类。
下列中创建的静态拆分窗口包含了两种不同的视图:
BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT lpcs, CcreateContext* pContext)
{
if(!m_wndSplitter.CreateStatic(this, 1, 2) ||
!m_wndSplitter.CreateView(0, 0, RUNTIME_CLASS(CtextView), Csize(128, 0), pContext) ||
!m_wndSplitter.CreateView(0, 1, RUNTIME_CLASS(CpictureView), Csize(0, 0),pContext) )
return FALSE;
<!--[if !supportEmptyParas]--> <!--[endif]-->
return TRUE;
}
传递给CreateStatic的参数指定了拆分窗口的父亲以及拆分窗口包含的行列数。对每个窗格调用一次CreateView。用从0开始的行列编号来标示窗格。在上面的代码中,第一次调用CreateView在左窗格(0行0列)中加入类型为CtextView的视图,第二次调用在右窗格(0行1列)加入类型为CpictureView的视图。传递给CreateView的Csize对象指定了窗格的初始尺寸。在上面的代码中,CtextView窗格的初始宽度为128象素,CpictureView窗格将占据剩余的窗口宽度。指定右窗格宽度的值和指定两个窗格高度的值都是0,这是因为主结构会忽略它们。
下面以一个单文档程序为例,说明静态拆分视图的实现过程:
<!--[if !supportLists]-->1. <!--[endif]-->首先建立一个单文档应用程序SplitWnd,视图CSplitWndView类型为列表视图。利用CSplitWndView ::OnInitialUpdate初始化列表视图。
<!--[if !supportLists]-->2. <!--[endif]-->为该工程新增一个树型视图类CMyTreeView,并在该类中添加HTREEITEM类型的成员变量m_hSubTree[2],该成员变量用来保存树型视图的子树句柄。利用CMyTreeView ::OnInitialUpdate初始化树型视图,为该树型视图添加一个树根,两个子树,参考代码如下:
HTREEITEM hRoot = GetTreeCtrl().InsertItem(_T("树根"), 。。。, 。。。, TVI_ROOT);
m_hSubTree[0] = GetTreeCtrl().InsertItem(_T("子树1"), 。。。, 。。。, hRoot);
m_hSubTree [1] = GetTreeCtrl().InsertItem(_T("子树2"), 。。。, 。。。, hRoot);
<!--[if !supportLists]-->3. <!--[endif]-->在框架类中添加一个CSplitterWnd 类型的成员变量m_wndSplitter1,并重载OnCreateClient函数来拆分视图,代码如下:
BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pContext)
{
if(!(m_wndSplitter1.CreateStatic(this, 1, 2) ) ||
!(m_wndSplitter1.CreateView(0, 1, RUNTIME_CLASS(CSplitWndView), CSize(0,0), pContext) ) ||
!(m_wndSplitter1.CreateView(0, 0, RUNTIME_CLASS(CMyTreeView), CSize(180 ,0), pContext) ) )
{
return FALSE;
}
return TRUE;
}
如果你设计的程序需要更多的拆分视图,可以再在框架类中添加CSplitterWnd 类型的成员变量m_wndSplitter2,再次利用CreateStatic拆分视图,代码如下:
BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pContext)
{if(!(m_wndSplitter1.CreateStatic(this, 2, 1) ) ||
!(m_wndSplitter1.CreateView(1, 0, RUNTIME_CLASS(CMyListView), CSize(0,0), pContext) ) ||
!(m_wndSplitter2.CreateStatic(&m_wndSplitter1, 1, 2, WS_CHILD|WS_VISIBLE, m_wndSplitter1.IdFromRowCol(0,0)) ) ||
!(m_wndSplitter2.CreateView(0, 0, RUNTIME_CLASS(CMyTreeView), CSize(180 ,0), pContext) ) ||
!(m_wndSplitter2.CreateView(0, 1, RUNTIME_CLASS(CSplitWndView), CSize(0,0), pContext) )
)
{
return FALSE;
}
m_wndSplitter1.SetRowInfo(0, 350, 0); //重新设置行宽
m_wndSplitter1.RecalcLayout();
<!--[if !supportEmptyParas]--> <!--[endif]-->
return TRUE;
}
现在基本的界面就建立好了,有关初始化列表视图的代码要根据具体情况来添加。另外,别忘记在框架.cpp中包含视图类的头文件。
为了视类之间的通讯,我们让这些视类共用一个文档类,现在来编写视图之间通讯的代码:
1.在文档类中添加int m_nViewType,表示要显示的类型。当用户单击“子树1”或“子树2”时改变其值。
<!--[if !supportLists]-->2. <!--[endif]-->建立树型视图通知TVN_SELCHANGED响应函数OnSelchanged,添加如下代码:
CSplitWndDoc* m_pDoc = (CSplitWndDoc*)GetDocument();
HTREEITEM hTmp = GetTreeCtrl().GetSelectedItem(); //得到当前选中的子树句柄
<!--[if !supportEmptyParas]--> <!--[endif]-->
//将m_pDoc->m_nViewType复位
m_pDoc->m_nViewType = 1000;
<!--[if !supportEmptyParas]--> <!--[endif]-->
if(hTmp == m_hSubTree [0] && m_pDoc)
{
m_pDoc->m_nViewType = 0; //将显示类型设置为子树1
}
if(hTmp == m_hSubTree [1] && m_pDoc)
{
m_pDoc->m_nViewType = 1; //将显示类型设置为子树2
}
//在改变子树的情况下才刷新CSplitWndView视图
if(hTmp == m_hSubTree [0] || hTmp == m_hSubTree [1] && m_pDoc)
{
//通知视图更新CSplitWndView
m_pDoc->UpdateAllViews(this);
}
3.在CSplitWndView中重载OnUpdate函数,响应UpdateAllViews。
CSplitWndDoc* pDoc = GetDocument();
GetListCtrl().DeleteAllItems(); //删除列表视图中的项
switch(pDoc->m_nViewType) //查看视图类型
{
case 0:
//将与子树1相关的项添加到列表视图中
break;
case 1:
//将与子树2相关的项添加到列表视图中
break;
}
本文转自 张宇 51CTO博客,原文链接:http://blog.51cto.com/zhangyu/33861,如需转载请自行联系原作者
《数据库基础及实践技术——SQL Server 2008》一2.5 SQL Server Management Studio工具
2.5 SQL Server Management Studio工具
SQL Server Management Studio是SQL Server 2008中最重要的管理工具之一,使用这个工具既可以用图形化的方法,也可以通过编写SQL语句来实现对数据库的操作。SQL Server Management Studio是一个集成环境,用于访问、配置和管理所有的SQL Server组件,它组合了大量的图形工具和丰富的脚本编辑器,使技术水平不同的开发和管理人员都可以通过这个工具访问和管理SQL Server。
2.5.1 连接到数据库服务器
单击“开始”→“程序”→“Microsoft SQL Server 2008”→“SQL Server Management Studio”命令,打开SQL Server Management Studio(SSMS)工具,首先弹出的是“连接到服务器”对话框,如图2-26所示。
在图2-26所示的对话框中,各选项含义如下。1)服务器类型:列出了SQL Server 2008数据库服务器所包含的服务,其下拉列表框中列出的内容就是安装时选择安装的服务,用户可以根据使用需要,连接到不同的服务。我们这里选择“数据库引擎”,即SQL Server服务。2)服务器名称:指定要连接的数据库服务器的实例名。SSMS能够自动扫描当前网络中的SQL Server实例。如果“服务器名称”中列出的不是希望连接的SQL Server实例,则可单击右侧图标按钮,然后在列表框中选择“浏览更多”命令,弹出如图2-27所示的“查找服务器”对话框。在此对话框中选中要连接的服务器实例,然后单击“确定”按钮。也可以在“服务器名称”文本框中手动输入要连接的服务器实例名。
3)身份验证:选择使用哪种身份的用户连接到数据库服务器实例,这里有两种选择,即“Windows身份验证”和“SQL Server身份验证”。如果选择的是“Windows身份验证”,则表示用当前登录到Windows的用户连接到数据库服务器实例;如果选择的是“SQL Server身份验证”,则对话框形式如图2-28所示,这时需要输入SQL Server身份验证的登录名和相应的密码。具体身份验证模式以及登录账户的种类在第12章的安全管理部分介绍。连接成功后,进入SSMS操作界面,如图2-29所示。在图2-29所示的窗口中: 单击“新建查询”图标按钮,可以打开查询编辑器窗格,如图2-30所示。 单击“新建查询”按钮右侧的“数据库引擎查询”图标按钮,将打开如图2-28所示的对话框,在此对话框中,用户可以指定在查询编辑器窗格中执行操作的用户。 单击“对象资源管理器”中的“连接”图标按钮,在出现的下拉列表框中选择“数据库引擎”(如图2-31所示)命令,也将弹出如图2-28所示的对话框。利用此对话框,用户可以再建立一个到数据库服务器的连接。
2.5.2 查询编辑器
SSMS工具提供了图形化的操作界面来创建和维护对象,同时也提供了用户编写T-SQL语句,并通过执行SQL语句创建和管理对象的工具,这就是查询编辑器。查询编辑器以选项卡窗口的形式存在于SSMS界面右侧的文档窗格中,可以通过如下方式之一打开查询编辑器: 单击标准工具栏上的图标按钮。 单击标准工具栏上的“数据库引擎查询”图标按钮。 选择“文件”菜单中“新建”命令下的“数据库引擎查询”命令。查询编辑器位于SSMS工具右部的窗格,如图2-30所示。查询编辑器的工具栏如图2-32所示。
在图2-32中,最左边的两个图标按钮用于处理到服务器的连接。其中,第一个图标按钮是“连接”图标按钮,用于请求一个到服务器的连接(如果当前没有建立任何连接的话),单击此图标按钮将弹出如图2-28所示的对话框。如果当前已经建立有到服务器的连接,则此图标按钮为不可用状态。第二个图标按钮是“更改连接”图标按钮单击此图标按钮表示要更改当前的连接。“更改连接”图标按钮的右侧是一个下拉列表框,该列表框列出了当前查询编辑器所连接的数据库服务器上的所有数据库,列表框上显示的数据库是当前连接正在操作的数据库。如果要在不同的数据库上执行操作,可以在列表框中选择不同的数据库,选择一个数据库就代表要执行的SQL代码都是在此数据库上进行的。随后的4个图标按钮与查询编辑器中所键入代码的执行有关。“执行”图标按钮用于执行在编辑区所选中的代码(如果没有选中任何代码,则表示执行全部代码)。“调试”图标按钮用于对代码进行调试。“分析”图标按钮用于对编辑区中选中的代码(如果没有选中任何代码,则表示对全部代码)进行语法分析。该组的最后一个图标按钮在图2-32上是灰色的(取消执行查询),在执行代码时它将成为红色。如果在执行代码过程中,希望取消代码的执行,则可单击此图标按钮。图标按钮组用于改变查询结果的显示形式。图标按钮设置查询结果按文本格式显示,图标按钮设置查询结果按网格形式显示,图标按钮设置将查询结果直接保存到一个文件中。图标按钮用于为选中的代码行添加注释,图标按钮用于取消对选中行代码的注释。图标按钮用于减少缩进,图标按钮用于增加缩进。不管是增加缩进还是减少缩进,都是针对选中的代码。
2.5.3 SSMS的常用选项
通过“工具”菜单中的“选项”命令,可以定制SSMS的部分设置。单击“工具”菜单中的“选项”命令,弹出如图2-33所示的对话框,本节介绍其中常用选项的设置。
“环境”节点“环境”节点中的选项涉及SSMS的环境和外观。该节点包含“常规”、“字体和颜色”、“键盘”以及“帮助”几个子节点,下面分别介绍这几个子节点的作用。
(1)常规从图2-33可以看出,在“常规”子节点的界面中包括如下内容。 启动时:设置SSMS启动时的操作,在其下拉列表框中包括5项操作(如图3-34所示),分别表示在启动后利用指定的连接信息—打开对象资源管理器、打开新查询窗口、打开对象资源管理器和新查询窗口、打开对象资源管理器和活动监视器以及打开空环境,打开空环境表示不打开查询编辑器窗口,也不将对象资源管理器连接到服务器上。 在对象资源管理器中隐藏系统对象:选中此复选框将在SSMS中隐藏所有的系统数据库和系统对象。 环境布局:■ 选项卡式文档:环境布局为类似于Excel的形式。■ MDI环境:环境布局为类似于Word的形式。 停靠工具窗口行为:■ “关闭”按钮只影响活动选项卡:若选中此复选框,则当单击“关闭”按钮时,只关闭当前活动的窗口;若未选中此复选框,则单击“关闭”按钮时,将关闭所有窗口。■ “自动隐藏”按钮只影响活动选项卡:可以保持工具箱打开或者通过“自动隐藏”按钮来隐藏。当选中此复选框时,“自动隐藏”只影响当前活动的窗口。 显示N个文件(在最近使用的列表中):定义“文件”菜单显示的最近使用的文件数量。(2)字体和颜色“字体和颜色”选项将影响用户界面元素的字体和颜色,其设置界面形式如图2-35所示。
用户可以先在“显示其设置”下拉列表框中选中要设置字体和颜色的SSMS部分,然后在“显示项”列表框中选中要设置字体和颜色的用户界面元素,可以定义其前景色、背景色、字体和字体大小。(3)键盘“键盘”节点的界面如图2-36所示,通过此界面可以为经常使用的命令定义快捷键。可以为存储过程定义快捷键,存储过程是存储在数据库服务器端的代码块,将在第10章介绍。(4)帮助SQL Server 2008不仅可以使用安装在本机上的帮助,而且还可以使用在线帮助,从而能访问最新的消息。“帮助”节点的设置界面形式如图2-37所示。可以通过“帮助”节点中的选项配置帮助系统。
“文本编辑器”节点“文本编辑器”节点包含的选项将影响如何用文本进行工作。
(1)文件扩展名特定产品的文件有其自身特有的文件扩展名,这样用户就能立即识别出文件种类,并把文件与相关软件联系起来。当用“文件”菜单中的“打开”命令打开文件时,由于系统对文件种类进行了过滤,因此,只列出有相应扩展名的文件。SQL Server也不例外,但用户可在“文件扩展名”选项中更改这项扩展名,如图2-38所示。不过,建议不要这么做,因为更改了文件扩展名,可能会引起不必要的麻烦。
(2)Transact-SQL“Transact-SQL”选项用于设置用Transact-SQL编程时的默认行为。单击“Transact-SQL”左侧的加号,展开“Transact-SQL”节点,其中包括“常规”、“制表符”和“IntelliSense”3个子项。1)常规。“常规”选项的设置界面如图3-39所示,该界面中的各选项含义如下。
① 语句结束。 自动列出成员:选中此复选框后,在编辑器中键入时会显示可用数据库和架构对象、列、表值函数或函数的列表。从弹出列表中任选一项,即可将其插入到代码中。 隐藏高级成员:此复选框不可用。 参数信息:在该复选框处于选中状态时,将显示紧挨在插入点(光标)左侧的存储过程或函数的参数信息。这些信息包括可用参数及其名称和数据类型的列表。② 设置。 启用虚空格:选中此复选框后,可以单击代码行结束处后面的任意位置,从而定位字符位置。选中此复选框将禁用“自动换行”复选框。 自动换行:如果选中此复选框,则水平延伸到编辑器可见区域以外的部分将自动显示在下一行。选中此复选框将启用“显示可视的自动换行标志符号”复选框,并禁用“启用虚空格”复选框。 显示可视的自动换行标志符号:如果选中此复选框,则在长行换行的位置显示返回箭头指示符。 没有选定内容时对空行应用剪切或复制命令:此复选框用于设置将插入点放在空白行上,不选择任何内容,单击“复制”或“剪切”时的编辑器行为。■ 如果选中此复选框,将复制或剪切空白行。如果随后单击“粘贴”,将插入一个新空白行。 ■ 如果未选中此复选框,则不复制或剪切任何内容。如果随后单击“粘贴”,将粘贴最近一次复制的内容。如果以前尚未复制任何内容,则不会粘贴任何内容。■ 如果不是空白行,则此设置对“复制”或“剪切”无效。如果没有选定任何内容,将复制或剪切整个行。如果随后单击“粘贴”,将粘贴整个行的文本及其行终止符。③ 显示。 行号:如果选中此复选框,则将在每个代码行的旁边显示行号。说明:这些行号不会添加到代码中,也不会打印出来,它们仅供参考。 启用单击URL定位:选中此复选框后,则当光标在编辑器中的URL上经过时,光标会变为手形指针。可以单击URL以在Web浏览器中显示所指示的页。2)制表符。“制表符”选项的设置界面如图2-40所示,该界面中的各选项含义如下。
① 缩进。 无:选择此选项后,按键时所创建的新行不会缩进。光标置于新行的第一列。 块:选择此选项后,按键时创建的新行的自动缩进距离与上一行的缩进距离相同。② 制表符。 制表符大小:设置制表位之间的距离(以空格为单位)。默认为4个空格。 缩进大小:设置自动缩进的大小(以空格为单位)。默认为4个空格。 插入空格:选择此选项后,缩进操作仅插入空格字符,而不会插入制表符。例如,如果“缩进大小”设置为5,则每次按键或单击SQL Server Management Studio 窗口工具栏上的“增加缩进”按钮时会插入5个空格。 保留制表符:选择此选项后,缩进操作会插入尽可能多的制表符。每个制表符都会填充“制表符大小”中指定的空格数。如果“缩进大小”不是“制表符大小”的偶数倍,则会添加空格字符补齐。3)IntelliSense。“IntelliSense”选项的设置界面如图2-41所示,该选项用于设置是否启用智能敏感,该界面中各选项的含义如下。
启用IntelliSense:启用智能敏感功能。默认情况下,此复选框处于选中状态。 用下划线标出错误:在查询窗口中标识Transact-SQL语句中的语法和语义错误。所有错误和警告将以红色显示。只有已为其实现了IntelliSense的Transact-SQL语句时才支持错误和警告。默认情况下,此复选框处于选中状态。 概括语句:打开文件时启用大纲显示功能。这将创建可折叠的Transact-SQL代码块。 最大脚本大小:指定脚本的大小,达到此大小时将禁用IntelliSense功能。如果脚本超过指定大小,则发出一条警告消息。该编辑器窗口的所有IntelliSense功能(颜色编码功能除外)将被禁用。对大型脚本启用IntelliSense会降低速度较慢的计算机的性能。允许的大小为 100KB、500KB、1MB、2MB 和无限制。默认值为1MB。 内置函数名称的大小写:指定完成列表中内置Transact-SQL函数的名称是采用大写形式(例如DATEADD)还是小写形式(例如dateadd)。(3)所有语言“文本编辑器”中的“所有语言”节点有两个选项:常规和制表符,如图2-42所示。该界面的形式和功能与图3-39类似,这里不再介绍。
“查询执行”节点“查询执行”节点包含影响Transact-SQL代码的选项,通过这些选项,可以改变Transact-SQL环境,以及在编写Transact-SQL时SSMS如何与SQL Server交互。“查询执行”中有两个子节点:“SQL Server”和“分析服务器”。如果没安装分析服务,则将只有“SQL Server”一个节点。
“SQL Server”子节点中的“常规”选项如图2-43所示。
当在查询中执行T-SQL代码时,有许多选项将影响Transact-SQL的执行。“常规”选项界面中的设置只对SSMS有效。 SET ROWCOUNT:指定在服务器停止处理查询前返回的最大结果行数。设置为0表示返回所有的查询结果行。 SET TEXTSIZE:指定从查询语句返回的text、ntext类型数据的最大长度。 执行超时值:指定在强迫结束某个查询语句的执行前,允许执行查询语句的最长时间。在生产环境中,当不希望查询占用大量的处理时间时,这个选项尤其有用。设置为0表示没有执行超时。 批处理分隔符:用来分隔代码的词或字符。目前的批处理分隔符是GO。尽管可以改变批处理分隔符,但最好不要修改,因为GO是众所周知的批处理分隔符。 默认情况下,在SQLCMD模式下打开新查询:选中此复选框,将打开命令提示符工具,创建基于SQLCMD代码而不是Transact-SQL代码的查询。注意,启用SQLCMD模式将关闭IntelliSense 和数据库引擎查询编辑器中的Transact-SQL调试器。
“查询结果”节点数据库管理系统在运行完Transact-SQL代码后,会把执行结果返回给SSMS。在“查询结果”节点中可以更改结果的显示方式。“查询结果”节点同样包含“SQL Server”和“分析服务器”两个子节点,这里我们只介绍SQL Server节点下各子节点的功能。
(1)常规“查询结果”节点中的“常规”子节点的设置界面如图2-44所示,其中各选项的含义如下。
显示结果的默认方式:指定数据查询结果的显示方式,这里有下列3个选项(如图2-45所示)。■ 以网格显示结果:将查询结果按表格的形式显示,这类似于Excel中的数据,但这里显示的数据是只读的。■ 以文本格式显示结果:将查询结果按文本格式显示,这类似于用记事本显示数据,当然,这里的数据也是只读的。■ 将结果保存到文件:如果选中此选项,表示不在SSMS中显示查询结果,而是将查询结果保存到一个文件中。选择这个选项后,再执行查询语句时,将弹出“保存结果”窗口,用户在此窗口中指定保存文件的位置和文件名。注意:更改这些选项只对新建立的查询编辑器窗口有效。 保存查询结果的默认位置:该选项可以指定保存查询结果的默认位置。 当查询批处理完成时发出Windows默认提示音:如果希望在查询结束时发出提示音,则可选中此复选框。但在执行大量查询时,过多的提示音会令人生厌,因此,除非要执行的查询需要花费很长时间,用提示音可提示用户此长时间查询已完成。否则,一般不选中此复选框。(2)以网格显示结果图2-46为“以网格形式显示结果”选项的设置界面,此界面中各选项的含义如下。 在结果集中包括查询:将所执行的Transact-SQL语句显示在结果窗格中“消息”选项卡中,并且显示在消息的前面。 在复制或保存结果时包括列标题:如果需要从查询结果集中复制信息(例如,把数据复制到其他文档中),则选中此复选框表示既复制数据也复制列的标题。 执行后放弃结果:执行SQL语句时,任何执行结果都将被立即放弃,因此,无可显示内容。 在单独选项卡中显示结果:选中该复选框时,结果将不显示在当前查询的底部窗格中,而是显示在一个单独的选项卡中,这样可给结果集更多的显示空间。 检索的最多字符数:指定结果集中每个单元显示的最大数据量。
(3)以文本格式显示结果单击“查询结果”→“以文本格式显示结果”选项后的设置界面如图2-47所示,该界面中各选项含义如下。
输出格式:其下拉列表框中列出了5种不同的格式选项供用户选择,如图2-48所示。通过这些不同的选项,可以根据需要设置输出的分隔符,从而便于将数据导入到其他系统中。 在结果集中包括列标题:如果只关心查询语句的结果,而不关心列标题,则可清除此复选框。当要将查询结果传递到其他系统中时,该选项会很有用。 在结果集中包括查询:选中此复选框时,系统会将所执行的Transact-SQL语句显示在结果窗格的“消息”选项卡中,并且显示在消息的前面。 接收到结果时滚动:若选中此复选框,则在返回记录时,如果记录超出了页面的最底端,则滚动结果集使最后一行数据得以显示。 右对齐数值:所有数值数据按右对齐形式显示。 在执行查询后放弃结果:执行SQL语句时,任何执行结果都将被立即放弃,因此,无可显示内容。 在单独选项卡中显示结果:选中该复选框时,结果将不显示在当前查询的底部窗格中,而是显示在一个单独的选项卡中,这样可给结果集以更多的显示空间。 每列中显示的最大字符数:指定结果集中每个单元显示的最大数据量。注意:这些选项的设置只对新建立的查询编辑器窗口有效。
“设计器”节点“设计器”节点的设置界面如图2-49所示,该界面中各选项的含义如下。
覆盖表设计器更新的连接字符串超时值:允许为表设计器的操作设置新的超时值。这在表设计器影响大型表,需要更多的时间完成表修改时非常有用。 自动生成更改脚本:自动创建脚本,以实现表设计器中定义的更改。 出现Null主键时警告:如果为主键选择的字段包含Null值,则提供警告对话框。 检测到差异时警告:如果选中此复选框,在保存更改时将出现一个消息框,其中列出某用户所做的更改与其他用户所做的更改之间存在的所有冲突。 表受到影响时警告:提供警告对话框,列出受操作影响的表,并提示确认。 阻止保存要求重新创建表的更改:禁止用户进行要求重新创建表的更改。下列操作可能要求重新创建表。■ 在表中添加一个新列。■ 删除列。■ 更改列为Null。■ 更改列的顺序。■ 更改列的数据类型。 默认表视图:选择要在设计器中查看表的方式,包括以下几个选项。■ 标准:显示表头、所有列名、数据类型和“允许空值”设置。■ 列名:显示列名称。■ 键:显示表头和主键列。■ 仅名称:仅显示表头及其名称。■ 自定义:允许选择要查看的列。 在新建关系图时启动“添加表”对话框:在打开设计器时自动打开“添加表”对话框。
文章
SQL · 存储 · 数据库 · Windows
2017-08-01