Quantcast
Channel: IT瘾博客推荐
Viewing all 532 articles
Browse latest View live

Java程序员博客系统推荐!我调研了100来个 Java 开源博客系统,发现这 5 个最好用!

$
0
0

最近想倒腾一下博客,看了很多现成的比较成熟的开源博客系统,自己也简单从下面几个维度总结对比了一下:

  1. star数量
  2. 技术选型
  3. 社区生态

当然啦!好东西不能独享。下面简单分享一下我所做的笔记。

欢迎小伙伴们评论区补充完善。ღ( ´・ᴗ・` )比心

halo

  • Github地址 : github.com/halo-dev/ha…
  • Star : 16.2k
  • 简介 :✍ 一个优秀的开源博客发布应用。
  • 技术 :Spring Boot+JPA+Hutool
  • 推荐等级 :⭐⭐⭐⭐⭐
  • 评价 :这款博客生态非常好(可选主题也非常多),使用的人也非常多。并且!! 还提供了小程序端!另外,搭建步骤也非常简单,基本是傻瓜式的。

Halo 首页:

Halo首页-halo.run

Halo 主题仓库 :

主题仓库- Halo-halo.run

Halo 博客效果:

 halo-寒山志-baozi.fun

OneBlog

  • Github地址: gitee.com/yadong.zhan…
  • Star : 2.3k
  • 简介 :一个简洁美观、功能强大并且自适应的Java博客。使用Spring Boot开发,前端使用Bootstrap。支持移动端自适应,配有完备的前台和后台管理功能。
  • 技术 : Springboot + Shiro + MySQL + Mybatis + Redis
  • 推荐等级 :⭐⭐⭐⭐
  • 评价 :我个人比较喜欢的一款博客样式类型( 不过,需要花更多时间自定义和完善。没精力折腾的,慎入!),自带评论系统、SEO等功能。比较适合做知识沉淀类网站。

Artificial-Intelligence-Algorithm-Scientist-www.piqiandong.com

solo

  • Github地址: github.com/88250/solo
  • Star : 0.8k
  • 简介 : Solo是一款小而美的开源博客系统,专为程序员设计。 Solo是B3log 分布式社区的 Java 博客端节点系统,欢迎加入下一代社区网络。
  • 技术 :Docker+H2+Nginx+ Latke(作者自研的以 JSON 为主的 Java Web 框架)
  • 推荐等级:⭐⭐⭐⭐
  • 评价 :和 halo 一样,都是比较成熟的博客系统了,并且生态特别好。Solo 第一个版本是在 2020 年发布,到现在为止,Solo项目的作者已经维护这个项目快10年了。为你们点赞!感谢你们的付出!另外,需要格外说明一下: 项目框架不是选用的主流的 Spring Boot 而是作者自己写的一个叫做 Latke的web 框架。

solo 博客效果:

D的个人博客

蘑菇博客

  • Github地址: gitee.com/moxi159753/…
  • Star: 1.2k
  • 简介:蘑菇博客(MoguBlog),一个基于微服务架构的前后端分离博客系统。
  • 技术 :Spring Boot + Spring Cloud Alibaba + MyBatis-Plus + ElasticSearch
  • 推荐等级:⭐⭐⭐⭐
  • 评价:第一次看到基于微服务架构的个人博客系统。我觉得作者可能是为了检验自己对于微服务相关框架的掌握,正如作者说的那样:“现在挺多是SSM或者SSH的博客管理系统,想用spring boot + spring cloud + vue 的微服务架构进行尝试项目的构建,里面很多功能可能只是为了满足自己的学习需求而引入的,因此本博客也是一个非常好的SpringBoot、SpringCloud以及Vue技术的入门学习项目。”

蘑菇博客前台效果:

 蘑菇博客-专注于技术分享的博客平台-demoweb.moguit.cn

蘑菇博客后台效果:

蘑菇云后台管理系统-demoadmin.moguit.cn

plumemo

  • Github地址 : github.com/byteblogs16…
  • Star: 0.3k
  • 简介:基于 SpringBoot实现零配置让系统的配置更简单,使用了 Mybatis-Plus快速开发框架,在不是复杂的查询操作下,无需写sql就可以快速完成接口编写。 后台管理系统使用了vue中流行的 ant,另外前后交互使用了 JWT作为令牌,进行权限、登录校验。。
  • 技术 :Spring boot + MyBatis-Plus + JWT
  • 推荐等级:⭐⭐⭐⭐
  • 评价 :界面简单美观,基于 Spring Boot 开发,适合用来学习,同时适合用来作为自己的博客。

plumemo博客后台效果:

plumemo-qfdxz.top

以上就是我今天要推荐的所有博客了。花了比较长时间去搜索以及对比,希望能对JavaGuide的小可爱们的有帮助!ღ( ´・ᴗ・` )比心

如果有帮助的话,不要吝啬你们手中的在看和赞!“怼”起来!

以上 4 本优质 原创 PDF微信搜“ JavaGuide”后台回复“ 面试突击”即可免费领取。


程序员你是如何使用Nacos作为配置中心的? - 李福春 - 博客园

$
0
0

file

假如你使用的是spring-cloud-alibaba微服务技术栈

单个服务独有配置文件

即去除应用程序的状态,配置统一外部化管理,方便进行水平的伸缩。

集成步骤:

假如我有一个应用app-design;

1,引入依赖:

<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId><version>2.2.1.RELEASE</version></dependency>

2, 配置文件;

spring.cloud.nacos.config.enabled=true
spring.cloud.nacos.config.refresh-enabled=true

spring.cloud.nacos.config.server-addr=${spring.cloud.nacos.discovery.server-addr}
spring.cloud.nacos.config.namespace=${spring.cloud.nacos.discovery.namespace}

说明如下:

属性说明
spring.cloud.nacos.config.server-addr=${spring.cloud.nacos.discovery.server-addr}nacos配置中心地址
spring.cloud.nacos.config.namespace=${spring.cloud.nacos.discovery.namespace}nacos的命名空间,这里跟服务发现的配置一致;

3,使用配置的方式,同本地配置文件一样。

@Value @PropertyConfiguration 这些注解都是支持的;

4,确认方式,比如把之前的application.properties的配置放到了配置中心;

image.png

本地启动的时候,读取到了8081端口和数据库连接池的配置;

image.png

配置中心的连接原理,后面单独整理出来,知其然并知其所以然。

服务之间共享配置文件

场景:多个后端微服务,在同一个集群中共用中间件的配置信息。

比如 缓存redis, 消息队列kafka, 文件服务器, 邮件服务器;

那么对应的配置文件没有必要在所有的后端微服务中单独存在,这些配置文件应该放在公共配置文件中,但是也可以被具体的后端微服务自己的独有配置文件覆盖,使用自己的私有配置;

可结合下图理解:

问题回答
where are we?现状中间件配置分散在很多服务中,配置繁琐,不方便统一管理
where are we go?目的同一个集群的中间件只维护一份,各服务共享,也可按照需要覆盖共享的配置;
how can we go there?实现路径基于nacos已有功能实现

下面是实际的coding过程和测试用例;

服务app-file;

在服务对应的nacos的namespace中

1 引入共享配置

#共享中间件的配置
spring.cloud.nacos.config.shared-configs[0].data-id=mid.properties
spring.cloud.nacos.config.shared-configs[0].group=DEFAULT_GROUP
spring.cloud.nacos.config.shared-configs[0].refresh=true

位置: 模块start下的src/main/resources/bootstrap.properties文件中

自描述的配置信息,即引入的共享配置文件列表有哪些,可以按照需要,配置各种中间件的配置信息;

key说明
data-id_the data id of extended configuration 配置文件名称,带上后缀;翻译:扩展配置文件的数据id
group_the group of extended configuration, the default value is DEFAULT_GROUP 集群名称, 从名字来看,支持多集群的配置文件 翻译:扩展配置文件的集群,默认值是  DEFAULT_GROUP
refresh_whether to support dynamic refresh, the default does not support 是否刷新 翻译:是否支持动态刷新,默认不支持

花括号[0] ,里面的0是序号,如果有多个,按照数字自增顺序进行配置;

2 在nacos中新增配置文件

根据实际场景在nacos的test命名空间中新增配置文件mid.properties

image.png

3 获取配置用例测试

测试接口代码:

@ApiOperation("测试获取公共配置文件")
    @GetMapping("/config/test")
    public Response config(){
        String redisConfigServers = environment.getProperty("redis.config.servers","null");
        return SingleResponse.of(redisConfigServers);
    }

测试用例:

场景期望结果实际结果是否符合预期
获取共享配置文件中的配置r-wz9sp7dhxjnz16bs1jzhutj.redis.rds.aliyuncs.com:6379r-wz9sp7dhxjnz16bs1jzhutj.redis.rds.aliyuncs.com:6379
在服务独有app-file.properties配置中重写配置redis.config.servers=r-wz9sp7dhxjnz16bs1jzhutj.redis.rds.aliyuncs.com:637905r-wz9sp7dhxjnz16bs1jzhutj.redis.rds.aliyuncs.com:637905r-wz9sp7dhxjnz16bs1jzhutj.redis.rds.aliyuncs.com:637905

截图如下:

image.png

image.png

image.png

源码分析

掌握用法之后,深入分析源码,知其然而知其所以然;

starter调用封装

使用的starter封装;

https://github.com/alibaba/spring-cloud-alibaba/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config

版本: 2.2.1.RELEASE

启动的时候自动装配的配置如下:

org.springframework.cloud.bootstrap.BootstrapConfiguration=\
com.alibaba.cloud.nacos.NacosConfigBootstrapConfiguration

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.alibaba.cloud.nacos.NacosConfigAutoConfiguration,\
com.alibaba.cloud.nacos.endpoint.NacosConfigEndpointAutoConfiguration

org.springframework.boot.diagnostics.FailureAnalyzer=\
com.alibaba.cloud.nacos.diagnostics.analyzer.NacosConnectionFailureAnalyzer

分解一下key,看一下用途:

key说明
org.springframework.cloud.bootstrap.BootstrapConfigurationA marker interface used as a key in META-INF/spring.factories. Entries in* the factories file are used to create the bootstrap application context.

翻译:一个标记注解用来作为key 放在META-INF/spring.factories文件中,文件中的条目用来创建启动应用的上下文;

来源:spring-cloud-context-version.jar

value:

com.alibaba.cloud.nacos.NacosConfigBootstrapConfiguration |
| org.springframework.boot.autoconfigure.EnableAutoConfiguration | 注释太长了,不放这里.放到附录中。

来源:spring-boot-autoconfigure-version.jar

com.alibaba.cloud.nacos.NacosConfigAutoConfiguration,\

com.alibaba.cloud.nacos.endpoint.NacosConfigEndpointAutoConfiguration |
| org.springframework.boot.diagnostics.FailureAnalyzer | A {@code FailureAnalyzer} is used to analyze a failure and provide diagnostic* information that can be displayed to the user.

_

翻译: FailureAnalyzer用来分析错误并提供诊断信息展示给到用户

来源: spring-boot-version.jar

com.alibaba.cloud.nacos.diagnostics.analyzer.NacosConnectionFailureAnalyzer |

然后看看都自动装配了什么?以及自动装配的过程。

springboot的方式调用;

1 NacosConfigBootstrapConfiguration

源码:

package com.alibaba.cloud.nacos;

import com.alibaba.cloud.nacos.client.NacosPropertySourceLocator;

import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author xiaojing
 */
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(name = "spring.cloud.nacos.config.enabled", matchIfMissing = true)
public class NacosConfigBootstrapConfiguration {

	@Bean
	@ConditionalOnMissingBean
	public NacosConfigProperties nacosConfigProperties() {
		return new NacosConfigProperties();
	}

	@Bean
	@ConditionalOnMissingBean
	public NacosConfigManager nacosConfigManager(
			NacosConfigProperties nacosConfigProperties) {
		return new NacosConfigManager(nacosConfigProperties);
	}

	@Bean
	public NacosPropertySourceLocator nacosPropertySourceLocator(
			NacosConfigManager nacosConfigManager) {
		return new NacosPropertySourceLocator(nacosConfigManager);
	}

}

自动装配流程:

配置文件组装源码:

@Override
	public PropertySource<?> locate(Environment env) {
		nacosConfigProperties.setEnvironment(env);
		ConfigService configService = nacosConfigManager.getConfigService();

		if (null == configService) {
			log.warn("no instance of config service found, can't load config from nacos");
			return null;
		}
		long timeout = nacosConfigProperties.getTimeout();
		nacosPropertySourceBuilder = new NacosPropertySourceBuilder(configService,
				timeout);
		String name = nacosConfigProperties.getName();

		String dataIdPrefix = nacosConfigProperties.getPrefix();
		if (StringUtils.isEmpty(dataIdPrefix)) {
			dataIdPrefix = name;
		}

		if (StringUtils.isEmpty(dataIdPrefix)) {
			dataIdPrefix = env.getProperty("spring.application.name");
		}

		CompositePropertySource composite = new CompositePropertySource(
				NACOS_PROPERTY_SOURCE_NAME);

		loadSharedConfiguration(composite);
		loadExtConfiguration(composite);
		loadApplicationConfiguration(composite, dataIdPrefix, nacosConfigProperties, env);

		return composite;
	}

加载应用配置文件的顺序源码:

private void loadApplicationConfiguration(
			CompositePropertySource compositePropertySource, String dataIdPrefix,
			NacosConfigProperties properties, Environment environment) {
		String fileExtension = properties.getFileExtension();
		String nacosGroup = properties.getGroup();
		// load directly once by default
		loadNacosDataIfPresent(compositePropertySource, dataIdPrefix, nacosGroup,
				fileExtension, true);
		// load with suffix, which have a higher priority than the default
		loadNacosDataIfPresent(compositePropertySource,
				dataIdPrefix + DOT + fileExtension, nacosGroup, fileExtension, true);
		// Loaded with profile, which have a higher priority than the suffix
		for (String profile : environment.getActiveProfiles()) {
			String dataId = dataIdPrefix + SEP1 + profile + DOT + fileExtension;
			loadNacosDataIfPresent(compositePropertySource, dataId, nacosGroup,
					fileExtension, true);
		}

	}

顺序如下:

序号说明
1加载dataIdPrefix对应的配置文件
2加载dataIdPrefix.fileExtension对应的配置文件
3加载 dataIdPrefix-activeProfiles.fileExtension对应的配置文件

2.1 NacosConfigAutoConfiguration

序号说明
1NacosConfigProperties  nacos配置
2NacosRefreshProperties  已经不建议被使用
3NacosRefreshHistory  刷新历史
4NacosConfigManager 配置
5NacosContextRefresher 注册nacos的监听器到应用

2.2 NacosConfigEndpointAutoConfiguration

NacosConfigEndpoint

本地配置同步逻辑

@ReadOperation
	public Map<String, Object> invoke() {
		Map<String, Object> result = new HashMap<>(16);
		result.put("NacosConfigProperties", properties);

		List<NacosPropertySource> all = NacosPropertySourceRepository.getAll();

		List<Map<String, Object>> sources = new ArrayList<>();
		for (NacosPropertySource ps : all) {
			Map<String, Object> source = new HashMap<>(16);
			source.put("dataId", ps.getDataId());
			source.put("lastSynced", dateFormat.get().format(ps.getTimestamp()));
			sources.add(source);
		}
		result.put("Sources", sources);
		result.put("RefreshHistory", refreshHistory.getRecords());

		return result;
	}

NacosConfigHealthIndicator

健康检查 UP,DOWN,UNKNOWN ;

3 NacosConnectionFailureAnalyzer

连接不上nacos服务端抛出异常

@Override
	protected FailureAnalysis analyze(Throwable rootFailure,
			NacosConnectionFailureException cause) {
		return new FailureAnalysis("Application failed to connect to Nacos server: \""
						+ cause.getServerAddr() + "\"","Please check your Nacos server config", cause);
	}

小结:服务通过集成该starter,通过http请求从nacos的服务端拉取配置数据,并做了 配置刷新历史,注册监听器到spring容器中, 本地缓存,和错误报告;

服务端封装

源码位置: https://github.com/alibaba/nacos/tree/develop/config

应用启动读取配置文件整体调用链:待后续完成;

小结

如果读完本篇文章你只能记住一句话:nacos作为配置中心可为单独的服务提供外部化配置文件,也支持多应用共享配置文件。
从nacos的客户端源码分析中可看到一些配置优先级的顺序。

原创不易,关注诚可贵,转发价更高!转载请注明出处,让我们互通有无,共同进步,欢迎沟通交流。

MySQL数据库规范 (设计规范+开发规范+操作规范) - 东山絮柳仔 - 博客园

$
0
0

I 文档定义

1.1 编写目的

      为了在软件生命周期内规范数据库相关的需求分析、设计、开发、测试、运维工作,便于不同团队之间的沟通协调,以及在相关规范上达成共识,提升相关环节的工作效率和系统的可维护性。同时好的规范,在执行的时候可以培养出好的习惯,好的习惯是软件质量的保证。

1.2  适用范围 

       本文档适用于开发、测试、QA及运维团队成员。 

II . 命名设计规范

2.1 总则

(1)所有命名采用26个英文小写字母和0-9这十个自然数,加上下划线_组成。不能出现其他字符(注释除外)。

(2)对象名尽量短,长度不超过30个字符。

(3)对象名字尽量描述实体的内容,由英文单词、单词组合或单词缩写组成,不以数字和_开头。

(4)命名中禁止使用SQL保留字。

2.2 库名

库名与应用名称尽量一致,统一小写,以下划线分割。

2.3 表名

表名必须使用小写字母或数字,以下划线分割,禁止出现数字开头,禁止两个下划线中间只出现数字。如果表名仅有一个单词,那么建议不使用缩写,而是用完整的单词。同一模块的表尽可能使用相同的前缀,表名称尽可能表达含义。

数据表 <模块标识>_<表标识>  例如: order_header , order_detail

编码表 base_<模块标识>_<表标识>

日志表 log_<模块标识>_<表标识>

2.4 字段名 

(1) 能表达字段功能的英文单词或单词缩写,一般不超过三个英文单词,以下划线分割。布尔类型的字段以“is_”作为前缀。

(2) 各表之间意义相同的字段应同名。

(3) 系统中所有属于内码的字段(仅用于表示唯一性和程序内部用到的标识性字段),名称取为:<表标识>_id。

(4) 系统中属于是业务范围内的编号的字段,其代表一定的业务信息,这样的字段建议命名为<业务标识>_code,其数据类型为VARCHAR,该字段需加唯一索引。

(5) 字段名不要与表名重复。

(6) 不要在列的名称中包含数据类型。

(7) 每个字段添加字段说明。

(8) 数据库字段名的修改代价很大,所以字段名称需要慎重考虑。

(9) 统一命名字段:create_by、create_time、modify_by、modify_time、disabled

2.5 索引名 

A. 非唯一索引必须按照“idx_<构成索引的字段名>”进行命名 

例如:在age上添加索引idx_age

B. 唯一索引必须按照“uidx_<构成索引的字段名>”进行命名

例如:uidx_cardid

C. 组合索引建议包含所有字段名,过长的字段名可以采⽤缩写形式

例如:idx_age_name

2.6 视图命名 

v_<模块标识>_<视图标识> 

2.7 存储过程命名 

usp_<模块标识>_<存储过程标识> 

2.8 函数命名 

ufn_<模块标识>_<函数标识> 

III 数据库设计规范 

3.1 表设计原则

(1) 表的存储引擎建议是InnoDB存储引擎,InnoDB 支持事务,支持行级锁,更好的恢复性,高并发下性能更好

(2)同一个DB中的表,其存储引擎、字符集应保持统一

(2) 数据表创建、变更具备说明文档

   数据表创建、变更时必须提供数据表设计文档: 包含表及字段详细说明

(3) 规范化与反规范化

          规范化的优点是减少了数据冗余,节约了存储空间,相应逻辑和物理的I/O次数减少,同时加快了增、删、改的速度。但是一个完全规范化的设计并不总能生成最优的性能,因为对数据库查询通常需要更多的连接操作,从而影响到查询的速度,而且范式越高性能就会越差。出于性能和方便管理的考虑,原则上表设计应满足第三范式。有时为了提高某些查询或应用的性能而可以破坏规范规则,即反规范化。数据应当按两种类别进行组织:频繁访问的数据和频繁修改的数据。对于频繁访问但是不频繁修改的数据,内部设计应当物理不规范化。对于频繁修改但并不频繁访问的数据,内部设计应当物理规范化。比较复杂的方法是将规范化的表作为逻辑数据库设计的基础,然后再根据整个应用系统的需要,物理地非规范化数据。

(4)临时库表必须以 _tmp_ 为前缀并以日期为后缀,备份表必须以 _bak_ 为前缀并以日期 为后缀。

(5)尽量控制单表数据量的大小,建议控制在 600 万以内

         大表在查询性能和结构修改、备份、恢复等运维方面存在很多弊端。可以用历史数据归档,分库分表、选择其它类型数据库等手段来控制数据量大小。

(6)数据表分类说明

  根据应用的实际需要和特点,可以将数据表进行如下分类: 

A. 基本数据表:描述业务实体的基本信息。例如:人员基本信息、单位基本信息等。 

B. 标准编码表:描述属性的列表值。例如:职称、民族、状态等。

C. 业务数据表:记录业务发生的过程和结果。例如:人员调动登记、变更通知单等。

D. 系统信息表:存放与系统操作、业务控制有关的参数。例如:用户信息、权限、用户配置信息等。

E. 统计数据表:存放业务数据统计值。例如:通知单统计、人员类别统计等。

F. 临时处理表:存放业务处理过程中的中间结果。

G. 其他类型表:存放应用层的日志、消息记录等。

3.2 字段设计原则 

(1)完善的字段说明

         涉及数据字段新增、变更,必须提供字段说明,需要及时更新字段注释。 

(2)选择符合存储需要的最小的数据类型

          一般来说,应该使用能正确存储和表示数据的最小类型。如果不确定需要什么数据类型,则选择不会超出范围的最小类型。选择更简单的数据类型。例如,整数类型的比较其代价小于字符类型的比较,因为字符集和排序规则使字符比较更复杂。

(3)合理的字段默认值

         字段尽可能有默认值,字符型的默认值为一个空字符串,数字型的默认为数值0。 尽可能把字段定义为NOT NULL。对于字段能否NULL,应该在SQL建表脚本中明确指明,不应使用缺省。

(4)所有布尔类型字段数据类型是unsigned tinyint,数值0表示为假;数值1表示为真(根据表的字段意义:比如Disabled = 1表示 Disabled 值为真,可以表示数据被逻辑删除)

(5)避免使用 ENUM 类型

          ENUM 类型的 ORDER BY 操作效率低,需要额外操作。

(6)MySQL最大行大小不能超过64KB(65535字节),所以一个表中的字段不要太多,理论上建议不要超过30个。

(7)如果存储的字符串长度几乎相等,推荐使用CHAR定长字符串类型。

(8)VARCHAR是可变长字符串,不预先分配存储空间,长度不要超过2000,如果存储长度大于此值,定义字段类型为text或blob,独立出来一张表,用主键来对应,避免影响其他字段索引效率。TEXT 和 BLOB 的主要差别是 BLOB 能够保存 二进制数据;而 TEXT 只能保存 字符数据。在程序设计时,尽可能不使用TEXT、BLOB类型。

(9)区分使用DATETIME和TIMESTAMP,两者都可用来表示YYYY-MM-DD HH:MM:SS类型的日期。两种都保存日期和时间信息,毫秒部分最高精确度都是6位数。建议使用TIMESTAMP(3)。

A. TIMESTAMP占用4字节,DATETIME占用8字节,当保存毫秒部分时两者都使用额外的空间 (1-3 字节)。

B. TIMESTAMP的取值范围比DATETIME小得多,不适合存放比较久远的日期。TIMESTAMP只能存储从 '1970-01-01 00:00:01.000000' 到 '2038-01-19 03:14:07.999999' 之间的时间。而DATETIME允许存储从 '1000-01-01 00:00:00.000000' 到 '9999-12-31 23:59:59.999999' 之间的时间。

C. TIMESTAMP的插入和查询受时区的影响。如果记录的日期需要让不同时区的人使用,最好使用 TIMESTAMP。

(10)根据实际需要选择能够满足应用的最小存储的日期类型。如果应用只需记录“年份”,那么用1个字节的YEAR类型完全可以满足,而不需要用4个字节来存储的DATE类型。这样不仅可以节约存储,还可以提高表的操作效率。

(11)小数类型为decimal,禁止使用float和double。因为float和double在存储的时候,存在精度损失问题,这是浮点数特有的问题。因此在精度要求比较高的应用中(比如货币)要使用定点数而不是浮点数来保存数据。浮点数指的就是含有小数的值,浮点数插入到指定列中超过指定精度后,浮点数会四舍五入,MySQL 中的浮点数指的就是  float 和  double,定点数指的是  decimal,定点数能够更加精确的保存和显示数据。

(12)字段允许适当冗余,以提高性能,但是必须考虑数据完整性。冗余字段应遵循:

A. 不是频繁修改的字段。

B. 不是varchar超长字段,更不能是text字段。

C. 需要维护冗余字段的数据完整性。

3.3 主键设计原则 

(1)一定要有显式的主键。 

(2)针对InnoDB,在无特殊需求的情况下,建议使用与业务无关的自增ID作为主键。

(3)自增字段做主键时,字段类型必须是bigint 。

(4)不推荐使用联合主键。由于InnoDB索引的数据结构都是B+tree,对包含联合主键的表做大量写入,会导致InnoDB为了维持B+tree而移动大量数据,降低性能。

(5)禁止外键。对性能损耗特别大,一般的做法是,在业务层设计专门的逻辑或解决方案来保证数据的一致性,以最终一致的时差来换取即使访问的性能问题。

3.4 索引设计原则 

(1)不允许存在和主键重复的索引。主键其实就是一个非空的唯一索引,所以再在该字段上添加一个索引完全是多此一举。

(2)业务上具有唯一特性的字段,即使是组合字段,也必须建成唯一索引。唯一索引的值是唯一的,可以更快速地通过该索引确定某条记录。另外,即使在应用层做了非常完善的校验控制,只要没有唯一索引,必然有脏数据产生。

(3)考虑索引列值的分布。评估某一栏位是否值得建索引,是根据选择性(符合条件笔数/总笔数)*100%来判断,选择性越低代表越值得,惯用的百分比界线是20%。如果某个数据列用于记录性别(只有"M"和"F"两种值),并且值出现的几率几乎相等,那么无论搜索哪个值都可能得到一半的数据行,在这种情况下索引的用处就不大。因为查询优化器发现某个值出现在表的数据行中的百分比很高的时候,它一般会忽略索引,进行全表扫描。

(4)为经常需要排序、分组和关联的字段建立索引。

(5)为常作为查询条件的字段建立索引。

(6)使用短索引,不要索引大字段。如果对varchar字段进行索引,必须指定一个前缀长度,尽量使用 前缀索引,没必要对全字段建立索引,根据实际文本区分度决定索引长度即可。 使用前缀索引,对列的某几个字符进行索引,可以提高检索效率。

(7)合理创建联合索引,(a,b,c) 相当于 (a) 、(a,b) 、(a,b,c),区分度最高的列在最左边。

(8)合理使用覆盖索引减少IO,避免排序。

(9)不要过度使用索引,单个表上的索引数量建议不要超过5个 。

(10)删除不再使用或者很少使用的索引。

3.5 数据库里不建议存放业务日志

业务日志的写入量比较大,影响mysql的性能,建议存放到非关系型数据库中。  

IV  SQL设计规范

4.1 避免数据类型的隐式转换

例如:SQL中的字符串类型数据应该统一使用单引号。特别对纯数字的字符串,必须用单引号,否则会导致隐式转换而引起性能问题或索引失效问题。

4.2 避免复杂SQL 

对于非常复杂的SQL,特别是有多层嵌套,带子句或相关子查询的,应该先考虑是否设计不当引起的。对于一些复杂SQL可以考虑使用程序实现。

4.3 批量插入

使用INSERT语句一定要给出插入值的字段列表,这样即使表加了字段也不会影响现有系统的运行。对于小批量插入,可以将多条记录合并为同一个SQL,使用INSERT INTO tablename (col1,col2,...) VALUES (value1, value2,...),(value1, value2,...),...; 插入多条数据只有一次提交,效率明显提高。对于大批量插入和文件的导入导出,避免使用insert .... select和create table…select的形式,可能会阻止对源表的并发更新,如果查询比较复杂,会造成严重的性能问题。推荐使用select...into outfile和load data infile的组合来实现,采用这种方式MySQL不会给source_tab 加锁,还可以大大缩短数据的导出导入时间。但是,由于这种方式存在一定的安全隐患,所以如果需要使用这种方式,必须提交DBA审批,审批通过以后才可执行。

4.4 数据更新

推荐使用主键更新,其它维度条件的更新操作会造成页锁。对多个表进行关联update操作风险较大,尤其是当执行计划出现错误时,可导致多个表同时被锁住,应该尽量避免。不带条件的update会导致全表操作,耗时较长,如有此需求,请联系DBA评估、操作。

4.5 避免使用TRUNCATE TABLE

TRUNCATE TABLE 比 DELETE速度快,且使用的系统和事务日志资源较少,也可以直接释放磁盘空间,但TRUNCATE无事务且不触发trigger,有可能造成事故,故不建议在代码中使用此语句。

TRUNCATE TABLE在功能上与不带where子句的delete语句相同。

4.6 避免使用SELECT *

如果不必要取出所有数据,不要用 * 来代替,应给出字段列表。

4.7 使用索引做条件查询count(*)

innodb引擎在统计方面和myisam是不同的,Myisam内置了一个计数器,所以在使用 select count(*) from table 的时候,直接可以从计数器中取出数据。而innodb必须全表扫描一次方能得到总的数量。每执行一次扫描一次,代价非常高。需要进行count(*)统计表记录总数时,加上secondary index扫描条件,可以加快扫描速度。例如:SELECT COUNT(*) FROM sbtest1 WHERE id>=0;

4.8 避免IN子句

使用 IN 或 NOT IN 子句时,特别是当子句中有多个值且表数据较多时,速度会明显下降。可以采用连接查询或外连接查询来提高性能。

4.9 避免不必要的排序

不必要的数据排序大大的降低系统性能。 

比如:在使用group by col的时候,mysql会默认order by col ,在只需要分组不需要排序的情况下,可以使用GROUP BY col ORDER BY NULL提升执行效率,仅仅对col列分组,而不排序。

4.10 合理利用最左索引

组合索引的生效原则是:从前往后依次使用生效,如果中间某个索引没有使用,那么断点前面的索引部分起作用,断点后面的索引没有起作用。对于组合索引,注意索引的使用顺序,where子句中将最左索引放在第一列。

比如:(a,b,c) 三个列上加了联合索引(是联合索引,不是在每个列上单独加索引)where a=3 and b=45 and c=5 .... 这种三个索引顺序使用中间没有断点,全部发挥作用 where a=3 and c=5... 这种情况下b就是断点,a发挥了效果,c没有效果where b=3 and c=4... 这种情况下a就是断点,在a后面的索引都没有发挥作用,这种写法联合索引没有发挥任何效果where b=45 and a=3 and c=5 .... 这个跟第一个一样,全部发挥作用,abc只要用上了就行,跟写的顺序无关 。

4.11 多表连接

做多表操作时,应该给每个表取一个别名,每个表字段都应该标明其所属哪个表。 

为关联操作的字段建立索引,并使用统一数据类型,不同数据类型做关联时,MySQL会进行隐式转换,导致无法用到索引,开销较大。

多表连接个数建议不超过3个。

4.12 避免在where后的索引字段上使用函数

在where后的索引字段上使用函数会导致索引失效,严重情况下会拖慢整个数据库实例的速度。

例如:

SELECT orderid

FROM order_detail

WHERE from_unixtime(create_time)>'2017-12-04 12:00:00';

这样使用函数会导致查询条件不使用索引,使查询性能下降。应改为:

SELECT orderid

FROM order_detail

WHERE create_time>unix_timestamp('2017-12-04 12:00:00');

4.13 尽量不要做’%’前缀模糊查询

col like “abc%” 能用上索引,而col like “%abc”不能用上索引

4.14 使用UNION ALL代替UNION

UNION合并两个或多个SELECT语句的结果集,并消去表中任何重复行。而UNION ALL不会消除重复行。从效率上说,UNION ALL要比UNION快很多,所以如果可以确认合并的多个结果集中不包含重复数据时,建议使用UNION ALL。

4.15 尽量避免OR操作

通常情况下,如果条件中有or,即使其中有条件带索引也不会使用,所以除非每个列都建立了索引,否则不建议使用OR。在多列OR中,建议用UNION ALL替换。

比如:

select f_crm_id from d_dbname1.t_tbname1 where f_xxx_id = 926067

and (f_mobile ='1234567891' or f_phone ='1234567891' );

应改为:

select f_crm_id from d_dbname1.t_tbname1 where f_xxx_id = 926067

and f_mobile ='1234567891'

UNION ALL

select f_crm_id from d_dbname1.t_tbname1 where f_xxx_id = 926067

and f_phone ='1234567891'

相同字段or可改成 in,如 f_id=1 or f_id=100 --> f_id in (1,100)。

4.16 MySQL 在否定条件中不能使用索引

例如,where 条件里面有<>、not in 、not exists的时候,即便是在这些判断字段上加有索引,也不会起作用。

4.7 MySQL 在JOIN中连接字段类型如果不一致,则不能使用索引

但是例外就是char和varchar如果在定义表的时候,长度一致,就可以利用索引JOIN,反正不行。例如,char(20)和varchar(20)可以利用索引,char(20)和varchar(25)则不行,不管varchar里面实际存储的值是多长。

4.18 如果两个字段列的字符集不同,不推荐JOIN

字符集不同的列,索引失效,容易引起慢查询故障。

V 完整性设计规范

采用数据库系统实现数据的完整性,这不但包括通过标准化实现的完整性而且还包括数据的功能性。

5.1 主键约束

每个表要求有主健,主健字段或组合字段必须满足非空属性和唯一性要求。

5.2 NULL值

(1)由于NULL值在参加任何运算时,结果均为NULL,所以尽可能把字段定义为NOT NULL。对于所有声明为NOT NULL的字段,必须显式指定默认值。 

(2)不要使用count(列名)或者count(常量)来替代 count(*),count(*)是SQL92定义的标准统计行数的语法,跟数据库无关,跟null和非null无关。 

  说明:count(*)会统计值为null的行,而count(列名)不会统计此列为null的行。

(3)count(distinct col)计算该列除null之外不重复的行数

  注意:count(distinct col1, col2),如果其中一列全为null,那么即使另一列有不同的值,也返回0。 

(4)当某一列的值全为null,count(col)的返回结果为0,但sum(col)的返回结果为null,因此使用sum()时需要注意NPE问题。

 例如,可以使用ISNULL()来判断是否为NULL值,来避免sum的NPE问题: 

 SELECT IF(ISNULL(SUM(g)), 0, SUM(g)) FROM table;

(5)NULL与任何值的直接比较都为null。

NULL<>NULL的返回结果是NULL,而不是false。

NULL=NULL的返回结果是NULL,而不是true。

NULL<>1的返回结果是NULL,而不是true。

5.3 视图使用原则

为了在应用程序和数据库之间提供一层抽象,可以为应用程序建立视图而不必直接访问表。使用试图可以简化操作,不用关注表结构的定义,可以把经常使用的数据集合定义成视图;屏蔽了表结构变化对用户的影响, 表增加列对视图没有影响,具有一定的独立性。此外,用户对视图不可以随意的更改和删除,可以保证数据的安全性。视图是虚拟的数据库表,在使用时要遵循以下原则:

A. 尽可能减少使用视图。

B. 视图中如果嵌套使用视图,级数不要超过3级。

C. 由于视图中只能固定条件或没有条件,所以对于数据量较大或随时间的推移逐渐增多的表,不宜使用视图。

D. 除特殊需要,避免类似SELECT * FROM [Table Name] 而没有检索条件的视图 

E. 视图中尽量避免出现数据排序的SQL语句。

VI 安全性设计规范

6.1 数据库账号使用规范

严格管理程序的专用账号,禁止用户使用此账号进行数据操作。 请使用开发人员专用只读账号进行数据查询。

6.2 用户与权限

为不同用户设定允许的权限,管理和使用权限分离。确定每个用户对数据库表的操作权限,如查询、新增、更新等。每个用户拥有刚好能够完成任务的权限。

严格把控好管理权限,只将管理权限赋予管理员。禁止有super权限的应用程序账号存在。禁止有DDL、DCL权限的应用程序账号存在。 

6.3 用户密码管理

用户帐号的密码必须进行加密处理,确保在任何地方查询都不会出现密码的明文。

VII 开发行为规范

7.1 总则

(1) 业务部门推广活动或上线新功能,必须提前通知DBA,并留出必要时间以便DBA完成压力评估和扩容 ;

(2) 单表多次alter操作必须合并一次操作;

例如:
要给表t增加一个字段aa,同时给已有的字段bb建立索引,通常的做法分为两步:

alter table t add column aa varchar(10);

然后增加索引:

alter table t add index idx_bb(bb); 

正确的做法是:

alter table t add column aa varchar(10),add index idx_bb(bb);

(3) 怀疑有性能瓶颈的SQL及早提交DBA调优,避免上线出现性能问题;

(4) 批量更新数据,必须通知DBA进行审核,并在执行过程中观察服务及主从延迟;

(5) 重要业务库的变更,须告知DBA重要等级、是否数据备份和执行时间要求;

(6) 避免在业务高峰期批量更新、查询数据库;

(7) 提交线上建表改表需求,必须详细注明涉及到的所有SQL语句,便于DBA进行审核和优化;

(8) 所有DDL和DML语句必须要在运维平台上提交申请,禁止口头或通过聊天工具传送需求; 

(9) 不要在MySQL数据库中存放业务逻辑 如果把业务逻辑放到数据库中,将会影响横向发展和上线测试。建议把业务逻辑提前,放到前端或中间逻辑层,数据库仅作为存储层,实现逻辑与存储的分离;

(10) 出现业务部门人为误操作导致数据丢失,需要恢复数据的,必须第一时间通知DBA,并提供准确时间地点、误操作语句等重要线索;

(11) 业务部门程序出现BUG等影响数据库服务的问题,必须及时通知DBA,便于维护服务稳定; 

(12) 重要项目的数据库方案选型和设计必须提前通知DBA参与。

7.2 避免使用触发器

MySQL中触发器是行触发的,每次增加、修改或者删除记录都会触发进行处理,编写过于复杂的触发器或者增加过多的触发器对记录的插入、更新、删除操作会有比较严重的影响,因此不要将应用的处理逻辑过多地依赖于触发器来处理。触发器的功能通常可以用其他方式实现,确实需要采用触发器,请联系DBA进行确认。

7.3 避免使用存储过程和函数

在数据库服务器上进行大量的复杂运算会占用服务器的CPU,造成数据库服务器的压力,影响数据库的正常使用,所以应尽量将这些运算操作分摊到应用服务器上执行。此外,存储过程难以调试和扩展,数据库扩展能力远远不如应用。

7.4 避免使用视图

视图可能导致执行计划错乱,影响SQL运行效率。对视图的修改,数据库必须把它转化为对基本表的信息修改,不便于维护。

VIII 其他规范

8.1 编制文档

对所有的命名规范、限制、数据字典、存储过程、函数都要编制文档。数据库文档化会大大减少犯错的机会,对开发、支持和跟踪修改非常有用。

8.2 维护计划规范

(1) 数据归档设计

根据业务功能,做最小限度保留,将数据备份至归档库,系统功能兼容访问历史数据库。

(2) 数据归档删除

需要物理删除不需要归档的数据,直接由DBA排作业自动物理删除。

 

Oracle查看等待事件_yh_zeng2的博客-CSDN博客

$
0
0

--查询等待的会话ID , 阻塞的等待时间类型、事件ID 、 SQLID 等等信息

select *
  from v$active_session_history h
where sample_time > trunc(sysdate)
  and session_state = 'WAITING'
  and exists(
        select 1 from v$sql s 
          where upper(s.sql_text) like '%T_USER%'
            and s.sql_id = h.sql_id     
  )
  order by sample_time desc;


--会话阻塞的事件查询

select * from v$session_wait where sid = 148;


--会话发生过的所有等待事件查询

select s.time_waited/1000,s.* from v$session_event s where sid = 148;


--被锁了之后,查看持有该锁的会话查询

select a.sid blocker_sid,
       a.serial#, 
       a.username as blocker_username,
       b.type,
       decode(b.lmode,0,'None',1,'Null',2,'Row share',3,'Row Exclusive',4,'Share',5,'Share Row Exclusive',6,'Exclusive') lock_mode,
       b.ctime as time_held,
       c.sid as waiter_sid,
       decode(c.request,0,'None',1,'Null',2,'Row share',3,'Row Exclusive',4,'Share',5,'Share Row Exclusive',6,'Exclusive') request_mode,
       c.ctime time_waited  
from   v$lock b, v$enqueue_lock c, v$session a  
where  a.sid = b.sid  
  and  b.id1= c.id1(+)
  and  b.id2 = c.id2(+)
  and  c.type(+) = 'TX'
  and  b.type = 'TX' 
  and  b.block   = 1
  and  c.sid = 148
order by time_held, time_waited;

--查询持有锁的会话执行了的SQL

select s.sql_text,h.* from v$active_session_history h,v$sql s
 where h.sql_id = s.sql_id
   and h.session_id = 150;

 

从一个例子入手Istio - luozhiyun - 博客园

$
0
0

74617380_p0_master1200

转载请声明出处哦~,本篇文章发布于luozhiyun的博客: https://www.luozhiyun.com

本文使用的Istio源码是 release 1.5。

本篇是Istio系列的第一篇,希望大家接下来能和我一起学习进步。

封面图是 Klegs的作品,颜色有一种深邃感,我很喜欢。这篇文章是Istio系列文章的开篇,主要从一个例子入手讲一下Istio,并讲解一些基础概念,后面会基于这个例子来展开讲解istio里面的实现原理。

Istio里面有很多有趣的内容,希望大家能一起来学习,感受Istio的魅力,当然Istio是和k8s是分不开的,所以也需要掌握了一定k8s知识能力才能进行学习,还没有掌握的同学不妨看看我的系列文章来进行学习: 深入k8s系列文章

基本概念

先来说一下什么是Service Mesh(服务网格),一般来说Service Mesh是一种控制处理服务间通信的基础设施层,可以在云原生场景下帮助应用程序在复杂的服务拓扑间可靠的传递请求。在实际使用中,服务网格一般是通过一组轻量网络代理来执行治理逻辑的,并且网络代理和应用绑定在一起,但是对应用来说是无感的。

下面用一张经典的网络示意图来表示一下Service Mesh:

image-20201024215839257

那么Istio又是什么呢?Istio就是一个Service Mesh实现的形态,用于服务治理的开放平台,并且Istio是与K8s紧密结合的适用于云原生场景的平台。

下面我们看看Istio的架构图:

The overall architecture of an Istio-based application.

Istio分别由数据平面(Data plane)和控制平面(Control plane)组成。

数据平面由网格内的Proxy代理和应用组成,这些代理以sidecar的形式和应用服务一起部署。每一个 sidecar会接管进入和离开服务的流量,并配合控制平面完成流量控制等方面的功能。

控制平面用于控制和管理数据平面中的sidecar代理,完成配置的分发、服务发现、和授权鉴权等功能,可以统一的对数据平面进行管理。

在上面的组件中,Proxy代理默认使用Envoy作为sidecar代理,Envoy是由Lyft内部于2016年开发的,其性能和资源占用都有着很好的表现,能够满足服务网格中对透明代理的轻量高性能的要求。

Pilot组件主要功能是将路由规则等配置信息转换为sidecar可以识别的信息,并下发给数据平面,完成流量控制相关的功能。

Citadel是专门负责安全的组件,内置有身份和证书管理功能,可以实现较为强大的授权和认证等操作。

Galley主要负责配置的验证、提取和处理等功能。

安装 Istio

本地需要准备一台机器上面安装有K8s,可以使用我在讲k8s的时候部署的机器: 1.深入k8s:k8s部署&在k8s中运行第一个程序

因为Istio的发展太过于迅速了,我这里是使用1.5.10的版本进行举例,大家可以去这里下载好应用包: https://github.com/istio/istio/releases/tag/1.5.10。

解压好之后里面会包含如下文件目录:

目录包含内容
bin包含 istioctl 的客户端文件
install包含 Consul、GCP 和 Kubernetes 平台的 Istio安装脚本和文件
samples包含示例应用程序
tools包含用于性能测试和在本地机器上进行测试的脚本

然后我们将istioctl客户端路径加入环境变量中:

[root@localhost ~]# export PATH=$PATH:$(pwd)/istio-1.5.10/bin

istio不同的版本会有不同的差异,如下表格:

defaultdemominimalremote
使用场景生产环境展示、学习基本流控多网格共享平面
核心组件
- pilotYYY
- ingressgatewayYY
- engressgatewayY

我们这里用于学习使用,所以使用demo进行安装:

[root@localhost ~]# istioctl manifest apply --set profile=demo

运行完命令后显示:Installation compelte代表安装完成。

安装好之后会安装一个新的namespace:istio-system

我们可以指定ns来获取它下面的pod:

[root@localhost ~]# kubectl get pod -n istio-system
NAME                                   READY   STATUS    RESTARTS   AGE
grafana-764dbb499-pxs84                1/1     Running   0          17h
istio-egressgateway-775f9cd579-lsw5q   1/1     Running   0          17h
istio-ingressgateway-5d75d8897-dn8vz   1/1     Running   0          17h
istio-tracing-9dd6c4f7c-pwq22          1/1     Running   0          17h
istiod-749c4cf7f8-xgnv8                1/1     Running   0          17h
kiali-869c6894c5-l72sc                 1/1     Running   0          17h
prometheus-79757ffc4-qxccg             2/2     Running   0          17h

Bookinfo 示例

Bookinfo 是 Istio 社区官方推荐的示例应用之一。它可以用来演示多种Istio的特性,并且它是一个异构的微服务应用。应用由四个单独的微服务构成:productpage、details、reviews、ratings。

  • productpage会调用 detailsreviews两个微服务,用来生成页面由python来编写。
  • details中包含了书籍的信息由,Ruby来编写
  • reviews中包含了书籍相关的评论。它还会调用 ratings微服务,由java编写。
  • ratings中包含了由书籍评价组成的评级信息,由Node js编写。

下面这个图展示了调用关系:

image-20201025163623769

如果我们的应用要接入Istio服务,那么就需要在这些应用里面都打上sidecar,使服务所有的出入流量都被sidecar所劫持,然后就可以利用istio为应用提供服务路由、遥测数据收集以及策略实施等功能。

启动服务

要实现注入sidecar有两种方式,一个是手动注入,一个是自动注入。

手动注入可以通过使用: istioctl kube-inject -f xxx.yaml | kubectl apply -f -来实现。

自动注入的需要为应用部署的命令空间打上标签 istio-injection=enabled,如果在default空间部署应用,那么可以这么做:

[root@localhost ~]# kubectl label namespace default istio-injection=enabled

这里istio会利用k8s的webhook机制为每个创建的pod都自动注入sidecar,具体是如何做的,下一篇我们再讲。

然后我们使用istio中自带的例子部署应用:

[root@localhost ~]# kubectl apply -f samples/bookinfo/platform/kube/bookinfo.yaml

过一段时间后确认应用都已启动和部署成功:

[root@localhost ~]#  kubectl get pod
NAME                              READY   STATUS    RESTARTS   AGE
details-v1-6c9f8bcbcb-shltm       2/2     Running   0          17h
productpage-v1-7df7cb7f86-h75dd   2/2     Running   0          17h
ratings-v1-65cff55fb8-9vh2x       2/2     Running   0          17h
reviews-v1-7bccdbbf96-m2xbf       2/2     Running   0          17h
reviews-v2-7c9685df46-lljzt       2/2     Running   0          17h
reviews-v3-58fc46b64-294f4        2/2     Running   0          17h
sleep-8f795f47d-6pw96             2/2     Running   0          16h

我们可以用describe命令查看其中的pod:

[root@localhost ~]#  kubectl describe pod details-v1-6c9f8bcbcb-shltm
...
Init Containers:
  istio-init:
    Container ID:  docker://6d14ccc83bd119236bf8fda13f6799609c87891be9b2c5af7cbf7d8c913ce17e
    Image:         docker.io/istio/proxyv2:1.5.10
    Image ID:      docker-pullable://istio/proxyv2@sha256:abbe8ad6d50474814f1aa9316dafc2401fbba89175638446f01afc36b5a37919
    Port:          <none>
    Host Port:     <none>
    Command:
      istio-iptables
      -p
      15001
      -z
      15006
      -u
      1337
      -m
      REDIRECT
      -i
      *
      -x

      -b
      *
      -d
      15090,15020
...
Containers:
  details:
    Container ID:   docker://ed216429216ea1b8a1ba20960590edb7322557467c38cceff3c3e847bcff0a14
    Image:          docker.io/istio/examples-bookinfo-details-v1:1.15.1
    Image ID:       docker-pullable://istio/examples-bookinfo-details-v1@sha256:344b1c18703ab1e51aa6d698f459c95ea734f8317d779189f4638de7a00e61ae
  	...
  istio-proxy:
    Container ID:  docker://a3862cc8f53198c8f86a911089e73e00f4cc4aa02eea05aaeb0bd267a8e98482
    Image:         docker.io/istio/proxyv2:1.5.10
    Image ID:      docker-pullable://istio/proxyv2@sha256:abbe8ad6d50474814f1aa9316dafc2401fbba89175638446f01afc36b5a37919
    Port:          15090/TCP
    Host Port:     0/TCP
    Args:
    ...

可以看到里面有一个初始化的Init Containers,用于设置 iptables规则。还注入了istio-proxy,这个容器是真正的 Sidecar。

为了能让应用程序可以从外部访问 k8s 集群,需要安装gateway:

[root@localhost ~]# kubectl apply -f samples/bookinfo/networking/bookinfo-gateway.yaml

检查gateway:

[root@localhost ~]# kubectl get gateway
NAME               AGE
bookinfo-gateway   17h

因为我是单机环境,未使用外部负载均衡器,需要通过 node port 访问,然后我们查看node port:

[root@localhost ~]# kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.spec.ports[?(@.name=="http2")].nodePort}'
22412

我的k8snode地址是192.168.13.129,然后我们在浏览器输入:

http://192.168.13.129:22412/productpage

可以访到对应的页面了,多刷新几次,会发现访问到不同的Book Reviews,因为默认使用的轮询策略。

总结

这一篇讲了一下如何安装istio,以及如何部署应用,使用istio来完成一个实例,比较简单和基础,里面的很多细节我这里都一笔带过了,但是在后面的一些内容会基于这个例子来进行讲解里面的具体实现原理,所以这篇文章还是有些必要的。

Reference

What's a service mesh?

What is Istio?

https://istio.io/latest/docs/examples/bookinfo/

利用TfidfVectorizer进行中文文本分类(数据集是复旦中文语料) - 西西嘛呦 - 博客园

$
0
0

1、对语料进行分析

基本目录如下:

其中train存放的是训练集,answer存放的是测试集,具体看下train中的文件:

下面有20个文件夹,对应着20个类,我们继续看下其中的文件,以C3-Art为例:

每一篇都对应着一个txt文件,编码格式是gb18030.utf8文件夹下的是utf-8编码格式的txt文件。

其中C3-Art0001.txt的部分内容如下:

2、数据预处理

(1)将文本路径存储到相应的txt文件中

我们要使用数据,必须得获得文本以及其对应的标签,为了方便我们进行处理,首先将训练集中的txt的路径和测试集中的txt的路径分别存到相应的txt文件中,具体代码如下:

复制代码
def txt_path_to_txt():
  #将训练数据的txt和测试数据的txt保存在txt中
  train_path = "/content/drive/My Drive/NLP/dataset/Fudan/train/" #训练数据存放位置
  test_path = "/content/drive/My Drive/NLP/dataset/Fudan/answer/" #测试数据存放位置
  train_txt_path = "/content/drive/My Drive/NLP/dataset/Fudan/train.txt" 
  test_txt_path = "/content/drive/My Drive/NLP/dataset/Fudan/test.txt"

  train_list = os.listdir(train_path)
  fp1 = open(train_txt_path,"a",encoding="utf-8")
  fp2 = open(test_txt_path,"a",encoding="utf-8")
  for train_dir in train_list:
    for txt in glob.glob(train_path+train_dir+"/*.txt"):
      fp1.write(txt+"\n")
  fp1.close()
  test_list = os.listdir(test_path)
  for test_dir in test_list:
    for txt in glob.glob(test_path+test_dir+"/*.txt"):
      fp2.write(txt+"\n")
  fp2.close()
复制代码

os.listdir():用于获取目录下的所有文件夹,返回一个列表。

glob.glob():用于获取当前目录下指定的文件,返回的是一个字符串。

之后我们得到一个train.txt和test.txt。train.txt部分内容如下:

(2) 读取train.txt或test.txt中的文件路径,将里面的内容和标签存储到txt文件中

代码如下:

复制代码
def train_content_to_txt():
  train_txt_path = "/content/drive/My Drive/NLP/dataset/Fudan/train.txt" #训练数据txt
  test_txt_path = "/content/drive/My Drive/NLP/dataset/Fudan/test.txt" #测试数据txt

  train_content_path = "/content/drive/My Drive/NLP/dataset/Fudan/train_jieba.txt" #存储文本和标签txt
  train_content_txt = open(train_content_path,"a",encoding="utf-8") 
  import re
  def remove_punctuation(line, strip_all=True):
    if strip_all:
      rule = re.compile(u"[^a-zA-Z0-9\u4e00-\u9fa5]")
      line = rule.sub('',line)
    else:
      punctuation = """!?。"#$%&'()*+-/:;<=>@[\]^_`{|}~⦅⦆「」、、〃》「」『』【】〔〕〖〗〘〙〚〛〜〝〞〟〰〾〿–—‘'‛“”„‟…‧﹏"""
      re_punctuation = "[{}]+".format(punctuation)
      line = re.sub(re_punctuation, "", line)
    return line.strip()

  train_txt = open(train_txt_path,"r",encoding="utf-8")
  for txt in train_txt.readlines(): #读取每一行的txt  
    txt = txt.strip() #去除掉\n
    content_list=[] 
    label_str = txt.split("/")[-1].split("-")[-1] #先用/进行切割,获取列表中的最后一个,再利用-进行切割,获取最后一个
    label_list = []
    #以下for循环用于获取标签,遍历每个字符,如果遇到了数字,就终止
    for s in label_str:
      if s.isalpha():
        label_list.append(s)
      elif s.isalnum():
        break
      else:
        print("出错了")
    label = "".join(label_list) #将字符列表转换为字符串,得到标签
    #print(label)
    #以下用于获取所有文本
    fp1 = open(txt,"r",encoding="gb18030",errors='ignore') #以gb18030的格式打开文件,errors='ignore'用于忽略掉超过该字符编码范围的字符
    for line in fp1.readlines(): #读取每一行
      #line = remove_punctuation(line)
      line = jieba.lcut(line.strip(), cut_all=False) #进行分词,cut_all=False表明是精确分词,lcut()返回的分词后的列表
      content_list.extend(line)
    fp1.close()

    content_str = " ".join(content_list) #转成字符串
    #print(content_str)
    train_content_txt.write(content_str+"\t"+label+"\n") #将文本 标签存到txt中
    
  train_content_txt.close()
复制代码

同样的,我们也对test.txt中的进行相应的处理,这些重复的代码可以将其封装成一个函数,我们偷点懒就不改了:

复制代码
def test_content_to_txt():
  train_txt_path = "/content/drive/My Drive/NLP/dataset/Fudan/train.txt" 
  test_txt_path = "/content/drive/My Drive/NLP/dataset/Fudan/test.txt"

  test_content_path = "/content/drive/My Drive/NLP/dataset/Fudan/test_jieba.txt" 
  test_content_txt = open(test_content_path,"a",encoding="utf-8")
  import re
  def remove_punctuation(line, strip_all=True):
    if strip_all:
      rule = re.compile(u"[^a-zA-Z0-9\u4e00-\u9fa5]")
      line = rule.sub('',line)
    else:
      punctuation = """!?。"#$%&'()*+-/:;<=>@[\]^_`{|}~⦅⦆「」、、〃》「」『』【】〔〕〖〗〘〙〚〛〜〝〞〟〰〾〿–—‘'‛“”„‟…‧﹏"""
      re_punctuation = "[{}]+".format(punctuation)
      line = re.sub(re_punctuation, "", line)
    return line.strip()

  test_txt = open(test_txt_path,"r",encoding="utf-8")
  for txt in test_txt.readlines():  
    txt = txt.strip()
    content_list=[]
    label_str = txt.split("/")[-1].split("-")[-1]
    label_list = []
    #以下for循环用于获取标签
    for s in label_str:
      if s.isalpha():
        label_list.append(s)
      elif s.isalnum():
        break
      else:
        print("出错了")
    label = "".join(label_list)
    #print(label)
    #以下用于获取所有文本
    fp1 = open(txt,"r",encoding="gb18030",errors='ignore')
    for line in fp1.readlines():
      #line = remove_punctuation(line)
      line = jieba.lcut(line.strip(), cut_all=False)
      content_list.extend(line)
    fp1.close()
    content_str = " ".join(content_list)
    #print(content_str)
    test_content_txt.write(content_str+"\t"+label+"\n")
  test_content_txt.close()
复制代码

完成之后会生成train_jieba.txt和test_jieba.txt,我们简要看一下train_jieba.txt中的内容:

with open("/content/drive/My Drive/NLP/dataset/Fudan/train_jieba.txt","r",encoding="utf-8") as fp:
  lines = fp.readlines()
  for line in lines:
    print(line)
    break
【   文献号   】 3 - 525 【 原文 出处 】 《 文艺报 》 【 原刊 地名 】 京 【 原刊 期号 】 20010705 【 原刊 页 号 】 ④ 【 分   类   号 】 J7 【 分   类   名 】 造型艺术 【 复印 期号 】 200105 【   标     题   】 关于 “ 艺术 与 科学 ” — — 《 艺术 与 科学 国际 作品展 》 理论 研讨会 纪要 【   正     文   】 杭   间 ( 清华大学美术学院 艺术 史论 系主任 ) : 由 清华大学 主办 、 我院 承办 的 《 艺术 与 科学 国际 作品展 》 已 在 中国美术馆 开幕 。 艺术 与 科学 的 关系 的 探讨 , 不仅 是 我们 此次 展览 的 命题 , 而且 也 是 全人类 发展 需要 探索 的 重要 主题 之一 , 因此 我们 希望 此 论题 能够 在 学术 层面 得到 更 深入 的 探讨 , 从而 为 未来 社会 发展 积累 充分 的 思想 资源 。 鉴于 此 , 我们 举办 了 这次 由 知名 批评家 参加 的 小型 理论 研讨会 , 希望 各位 踊跃发言 。 ( 以下 以 发言 先后 为序 ) 张凤昌 ( 清华大学美术学院 党委书记 ) : 我院 主办 的 这次 《 艺术 与 科学 国际 作品展 》 得到 了 社会各界 的 大力支持 , 展览 达到 了 预期 的 效果 。 对此 , 我 代表 学院 向 各位 理论家 的 支持 表示 最 衷心 的 感谢 。 艺术 与 科学 这个 命题 的 讨论 早已有之 , 但 在 我国 举办 如此 大规模 的 作品展 与 研讨会 , 这是 第一次 。 这是 一项 长期 的 任务 , 今后 还应 继续 发展 。 因为 我们 的 出发点 是 立足于 教育 , 因此 研究 艺术 与 科学 这个 命题 是 要 重点 考虑 怎样 培养 人 , 培养 德 、 智 、 体 全面 发展 的 人才 。 我 觉得 理论 在 其 中起 着 重要 的 作用 , 它 是 旗帜 , 是 实践 的 指导 。 我们 希望 能 得到 理论家 更 多 的 支持 和 帮助 。 王明 旨 ( 清华大学 副校长 、 美术学院 院长 ) : 非常高兴 众 位 美术 理论家 来 参加 这次 研讨 , 实际上 这次 “ 艺术 与 科学 ” 的 讨论 还 刚刚 起步 , 希 望 将来 能 与 众 兄弟 院校 共同 进行 进一步 的 研究 。 我院 提出 这个 大 的 理想化 的 主题 与 多年 的 学科 设置 和 专业 基础 有关 , 并入 清华大学 后 , 我们 处处 感到 了 清华大学 对 交叉性 的 综合 学科 的 重视 。 科学 本身 同 艺术 一样 是 广泛 而 生动 的 , 这是 二者 结合 的 基础 。 这次 的 展品 偏重于 美术 、 造型艺术 和 科技 之间 , 从小 的 切入点 来 探讨 大 的 课题 。 展品 从 内容 上 可以 分为 三个 方面 : 一部分 表达 了 艺术家 内心 对 科学 的 理解 , 另 一部分 是 科学家 通过 艺 术 表达 的 科学 成果 , 第三 部分 是 与 科学技术 结合 的 艺术设计 作品 。 可以 说 , 介入 科学 是 艺术 永恒 的 命题 , 也 是 新 时代 的 艺术家 了解 生活 的 不可 回 避 的 问题 , 艺术 教育 也 必须 作出 相应 的 调整 。 这次 展览 虽然 只是 初步 探讨 , 并非 十分 完善 和 理想 , 但 毕竟 是 正式 的 起步 , 我们 希望 诸位 能 从 理论 上 提出批评 和 指导 , 以利于 这项 工作 的 深入 发展 。 范迪安 ( 中央美术学院 副 院长 ) : 刚才 王 院长 介绍 了 主办 的 宗旨 、 构想 , 使 我们 对 这次 活动 有 了 更 深入 的 了解 。 我 认为 这次 展览 是 21 世纪 初 大型 的 社会 文化 活动 , 展览 热烈 , 观众 情绪高涨 , 接受度 和 欣喜 度均 超过 以往 。 其 意义 在于 : 第一 , 它 是 清华大学美术学院 多年 教育 积累 的 必然 结果 。 第二 , 它 完成 了 20 世纪 中国 本应 完成 的 命题 , 非常 具有 价值 。 既 完成 了 公众 多年 的 夙愿 , 又 是 引导 新世纪 对 该 命题 关注 的 起点 , 体现 了 学 院 领导班子 的 前瞻性 和 战略性 。 观众 通过 展览 , 不仅 学到 科学知识 , 而且 看到 了 艺术 创造 , 这种 艺术 的 、 文化 的 营养 是 社会 大众 所 需要 的 。 第三 , 展品 多以 视觉 传达 形式 出现 , 是 最 广泛 的 视觉 传达 发射 体 , 形式 前所未有 , 体现 了 当代 文化 传播 手段 。 总之 , 作为 一个 展览 , 能够 基于 一个 最 广泛 的 文化 层面 , 并 具有 相当 的 文化 前瞻性 、 当代 性是 其它 展览 少有 的 。 但是 , 对 具体 命题 的 艺术化 创作 , 在 艺术 与 科学 原理 上 的 思考 还有 待 提 高 。 程 大利 ( 中国 美术 出版 集团 副总 编辑 ) : 上个世纪 提出 的 “ 科学 与 民主 ” 与 “ 艺术 与 科学 ” 一样 , 是 一种 文化 理想 , 具有 指向性 。 看 了 这个 展览 , 我 首先 感到 这是 一个 非常 了不起 的 展览 , 是 一个 经过 精心 准备 的 有 理性 思考 的 展览 , 清华大学 提出 这个 题目 , 是 一个 非常 宏大 的 命题 , 具 有 长远 意义 。 另外 我 想 提出 一个 问题 , 即 艺术 不 应该 仅 是 科学 的 注脚 , 科学 也 不 应仅 是 艺术 的 拐杖 。 去年 《 中国 文化 报 》 有 一个 讨论 , 讨论 科学 与 人文 的 关系 , 可惜 它 浅尝辄止 ; 科学 使人 认识 物质 世界 , 但 科学 的 高度 发展 带来 了 许多 负面 问题 , 艺术 使人 认识 美 , 它 的 最大 功能 应是 净化 人 、 抒发 人类 的 情感 , 二者 的 结合 是 人类 的 理想 , 如果 结合 得 好 了 , 人类 就 进步 了 。 今天 艺术 教育 最 失败 的 地方 是 忽略 了 情感 教育 , 艺术 教育 的 问 题 是 培养 真正 的 人 。 长期以来 , 理工科 学生 的 艺术 素质 薄弱 是 我国 教育界 的 突出 问题 之一 , 大大 限制 了 科学 的 发展 。 应该 看到 , 科学 本身 也 是 美 的 , 量子 物理 、 纳米 世界 都 是 美的 , 科学 与 艺术 是 两翼 , 缺一不可 , 没有 人文 指向 的 科学 的 发展 是 多么 可怕 。 因此 , 这次 展览 是 个 创举 , 在 对 年 轻 科学家 的 培养 方面 有 巨大 的 促进作用 。 如果 我们 真正 弄清 了 艺术 与 科学 的 关系 , 其 意义 是 不可估量 的 。 林   木 ( 四川大学 艺术系 教授 ) : 我 注意 到 这次 “ 艺术 与 科学 ” 研讨会 发言 的 构成 人 主要 是 科学家 、 画家 , 思维 导向 主要 是 自然科学 。 可以 说 , 20 世纪 人们 对 科学 的 强调 非常 突出 。 值得注意 的 是 , “ 五四 ” 的 两面 旗帜 “ 民主 ” 和 “ 科学 ” 在 当时 的 美术界 是 对立 的 。 他们 张扬 人性 、 民 主 、 生命 意识 , 反对 科学 的 写实主义 , 这种 对立 很 奇怪 。 科学主义 对 科学研究 的 方法论 、 价值观 较为 关注 , 它 利用 科学 的 权威 做 与 科学 无关 的 事 , 在 20 世纪 较为 普遍 , 这是 应该 注意 的 问题 。 我 认为 , 人文科学 应 在 科学 与 艺术 的 结合 中 产生 重要 的 作用 。 人们 对 科学 的 宏观 与 微观 的 思考 演化 为 哲学 、 宗教 、 历史 的 思考 , 反过来 形成 对 艺术 的 引导 。 这次 展览 的 作品 , 不少 很 新颖 , 但 也 有 很多 只是 对 科学 进行 了 图解 , 比较 牵强 , 因此 值 得 进一步 研究 。 岛   子 ( 四川 美术学院 美术学 系主任 、 教授 ) : 我 认为 这次 “ 艺术 与 科学 ” 大展 开启 了 21 世纪 艺术 教育 的 思路 和 框架 。 1998 年 我 到 川美 任教 以来 , 深刻 地 感到 了 美术 学科 缺少 科学性 , 即 国家教委 的 学科 目录 与 社会 需求 有 很大 的 矛盾 , 美术学 的 分类 既 高度 分化 又 高度 融合 。 在 我国 , 人 文 科学 的 范围 界定 还有 待 研究 。 我们 应 关注 学科 内部 的 科学 问题 。 21 世纪 的 素质教育 应 包括 哪些 内容 呢 ? 我 注意 到 清华大学美术学院 艺术 史论 系 开设 了 社会学 课程 , 这 一点 值得 学习 。 人文科学 跟 自然科学 、 社会科学 需要 融合 。 21 世纪 我们 学院 的 建设者 任务 将会 十分艰巨 , 因为 高科技 的 信 息 时代 对 教学 的 冲击 是 不可避免 的 。 它 所 带来 的 知识 传承 问题 , 使 人文 学者 回归 到 前 现代 的 角色 , 即 成为 世界 意义 的 守护者 和 阐释 者 。 水天 中 ( 中国艺术研究院 美术 研究所 研究员 ) : 《 艺术 与 科学 国际 作品展 》 和 “ 艺术 与 科学 理论 研讨会 ” 是 近年 国内 美术界 规模 最大 、 规格 最高 的 学术活动 。 展览 与 研讨会 都 有 许多 使人 耳目一新 的 内容 , 打破 了 艺术 活动 只 着眼于 艺术 圈 之内 的 局限 , 开辟 了 美术 教学 、 美术 实践 、 美术 理论 与 姐妹 学科 交流 借鉴 的 局面 , 为 中国 美术 引来 一股 清风 。 对于 艺术 的 发展 来说 , 它 的 意义 是 在 艺术 与 社会 、 艺术 与 政治 、 艺术 与 个人 感情 … … 之外 , 引导 人们 注视 一条 新 的 思路 , 尝试 一种 新 的 艺术 活动 方式 , 走出 新 的 艺术 通道 。 这是 一条 有 生气 、 有 活力 的 通道 , 它 的 价值 不会 在 其它 通道 之下 。 对于 清华大学美术学院 来说 , “ 艺术 与 科学 ” 还 可以 成为 一个 长期 发展 目标 , 成为 学术 和 办学 风格 上 的 一种 理想 。 当 我们 说 “ 高雅 艺术 ” 的 时候 , 往往 联想起 传统 的 、 古老 的 文化 情趣 。 实际上 艺术 与 科学 也 可以 达到 一种 高雅 的 文化 情趣 ; 当 我们 说 “ 先锋 艺术 ” 的 时候 , 往往 联想起 对 社会 生活 的 反讽 和 对 个人 处境 的 焦虑 , 实际上 艺术 与 科学 、 科学 与 人 的 复杂 关系 , 也 应该 成为 艺术创作 上 的 “ 先锋 问题 ” 。 展览会 的 作品 多 , 中国美术馆 的 场地 小 , 这使 展出 效果 不够 理想 。 除了 展出 场馆 条件 的 限制 外 , 从 艺术 水平 或者 展览会 的 主题 看 , 如果 将 一 些 可有可无 的 作品 删减 ( 如果 减去 四分之一 ) , 展览 的 整体 水准 就 会 明显 提升 。 作品 的 评奖 结果 也 值得 进一步 讨论 , 有些 获奖作品 并 不 具有 艺术 或者 科学 思想 方面 的 创造性 , 有些 作品 甚至 不能 代表 作者 原有 的 艺术 水平 。 在 题材 和 作品 的 人文精神 方面 , 宜作 周密 的 考量 , 例如 科学研究 和 科 技 运用 的 伦理 问题 , 这 已 是 全球性 的 不容 回避 的 话题 。 从 展览 作品 和 研讨会 发言 看 , 这是 一次 称颂 科学 与 艺术 的 集会 , 大家 主要 着眼于 两者 的 光明面 , 而 忽视 了 它们 目前 发展 的 问题 。 在 观察 角度 上 , 国内 艺术家 和 学者 偏向 于 称颂 历史 , 回顾 往昔 的 辉煌 , 在 思路 上 习惯于 绪论 、 概说 式 的 “ 宏大 叙事 ” , 缺少 国外 艺术家 和 学者 那种 对 具体 问 题 的 深入研究 。 这 已经 成为 中国 美术 理论界 的 流弊 。 丁   宁 ( 北京大学 艺术 学系 教授 ) : 我 想 谈 以下几点 : 第一 , 艺术 的 发展 要 进入 公众 领域 。 可以 说 , 这次 展览 引起 了 久违 的 关注 , 它 打破 了 近年 美术 领域 的 圈子 化 , 使得 艺术 进入 了 公众 的 精神 领域 , 随着 时间 的 推移 , 其 价值 会 逐渐 彰显 出来 。 第二 , 这次 展览 把 “ 艺术 与 科学 ” 作为 主 题 阐述 是 90 年代 以来 美术界 的 高峰 事件 , 可以 改变 当代 美术 的 专家 化 思路 , 具有 鲜明 的 时代感 和 重要 的 启示 作用 。 第三 , 展览 提出 了 艺术 教育 的 新 方向 , 有助于 改变 对纯 艺术 的 过分 倚重 。 艺术 要 走向 现实 的 发展 , 离不开 科学 的 成分 。 第四 , 这是 一次 美术 活动 国际性 的 探索 。 希望 今后 能继 续 进行 下去 。 第五 , 展览 的 不足 在于 对 研究 的 前沿 情况 把握 不够 , 新思路 不足 。 刘 龙庭 ( 人民美术出版社 编审 ) : 这次 展览 的 命题 是 社会 大 发展 的 产物 。 展览 既 具有 前瞻性 、 开创性 , 又 具有 总结性 、 启示 性 , 对 公众 产生 了 不可 忽视 的 影响 。 中央 工艺 美院 虽然 并入 清华大学 的 时间 并 不长 , 但 贡献 很大 。 我们 也 应该 看到 , 科学 与 艺术 毕竟 不同 , 在 强调 联系 时 , 也 有 区别 。 这是 一个 很大 的 命题 , 需要 继续 深入 讨论 下去 。 在 当前 中国 科学 比较落后 的 情况 下 , 人文科学 也 并 不 先进 , 希望 清华 美院 继续 张扬 自己 的 艺术 个性 , 取得 更大 的 发展 。 吕品 田 ( 中国艺术研究院 美术 研究所 研究员 ) : 清华大学 花 了 如此 大 的 力气 和 声势 , 用 展览 和 研讨会 的 形式 提出 “ 艺术 与 科学 ” 这样 一个 命 题 , 是 具有 历史性 的 、 前瞻性 的 。 但 对 中国 来讲 , 历史性 这 一块 确实 是 中国 的 问题 , 因为 西方 对 “ 艺术 与 科学 ” 的 关注 在 20 世纪 就 大量 地 展开 了 。 而 中国 在 20 世纪 还 并未 真正 地 意识 到 这样 一种 关系 , 并 把 它 作为 一个 命题 提出 来 。 因此 , 这个 命题 的 前瞻性 我 认为 是 国际性 的 , 全球性 的 。 不 仅 是 中国 人 , 整个 生活 在 地球 上 的 人 都 在 关注 这样 一对 关系 。 在我看来 , 不管 是 科学 还是 艺术 , 这 两个 概念 都 是 西方式 的 。 甚至 把 它 作为 一种 关 系 问题 摆出来 , 也 是 一种 西方式 的 思想 。 在 这种 思想 贯穿 着 一种 形而上学 的 传统 , 从 古希腊 到 今天 信息时代 的 二进位制 语言 , 这 中间 是 有着 深刻 联系 的 。 但 实际上 西方 这种 思想 从 一 开始 就 包含 一种 矛盾 。 一方面 , 它 把 人 解释 成 一种 自由 的 存在 , 具有 无限 发展性 、 丰富性 、 非 专门化 的 存在 , 而 另一方面 它 在 解释 自然 、 探索 自然 时 , 又 把 这个 世界 理解 为 有 一个 最终 的 终极 时代 。 在 自然科学 上要 探讨 物质 的 终极 时代 , 在 人文科学 上要 探讨 体现 、 反映 这种 终极 时代 的 真理 。 因此 , 无论 自然科学 , 还是 人文科学 , 在 西方 人 看来 是 一 回事 , 本质 上 它 有 一种 大 科学 思想 。 由此 , 矛盾 从 一 开始 便 产生 了 。 在 古典 时代 , 这样 一种 矛盾 是 不 明显 的 , 文艺复兴 后 , 随着 西方 对 人 的 强调 , 所谓 人文主义 崛起 后 , 必然 就 会 把 这种 矛盾 带 出来 。 对 世界 认识 的 问题 和 人 对 自己 本身 认识 的 问题 矛盾重重 。 这种 矛盾 导致 科学 与 艺术 从 原有 的 混沌 状态 分裂 出来 , 并 产生 科学 与 艺术 。 而 在 今天 大谈 科学 与 艺术 , 它 实际上 反映 了 我们 现在 的 一种 生存 状态 , 一种 力图 改变 我们 现在 对 世界 认识 和 把握 的 矛盾 状态 。 20 世纪 上半叶 , 人类 对 科学 、 现代文明 是持 一种 乐观 的 态度 的 , 中叶 以后 , 这样 一种 状态 发生 了 变化 , 科学 所 带来 的 一些 现实 问题 迫使 我们 去 回头 反思 , 这 是 由 生存环境 引发 的 一种 物质性 问题 。 只有 这些 物质性 的 问题 才能 在 科学时代 打动 这些 被 科学 思 想 所 占领 、 控制 的 人群 。 由此 , 科学 与 艺术 作为 一种 关系 命题 暴露 出来 。 从 这样 一种 意义 上 说 , 提出 这样 一个 前瞻性 的 命题 , 是 非常 有 价值 的 。 从 展览 来说 , 我 感觉 有 三种 类型 的 作品 : 1 . 把 科学 思想 、 科学知识 加以 阐释 、 介绍 , 实际上 带有 一定 程度 的 科学 色彩 。 而 在 当下 , 它 又 具有 意识 形态 的 色彩 。 2 . 表现 现代科技 或用 现代科技 来 辅助 制作 的 作品 , 它 在 某种程度 上 显示 着 现代科技 的 力量 。 3 . 在 科学 的 条件 下 , 强调 对 人 的 生存 状 态 的 关注 , 强调 人 的 现实 体验 的 作品 。 实际上 , 从 艺术 与 科学 的 关系 的 角度看 , 这 一部分 作品 更 有 价值 。 如果 在 以后 还 将 继续 进行 展开讨论 , 我 认为 更应 关注 人 的 生存 状态 , 因为 在 让 人 生存 得 更 幸福 、 更好 的 目标 上 , 无论 科学 还是 艺术 , 它们 都 是 统一 的 。 邵大箴 ( 中央美术学院 教授 ) : 这次 展览 社会 反响 很大 , 收到 了 很 好 的 效果 。 虽然 也 有 不少 缺点 , 当 总的来说 还是 很 不错 的 。 科学 与 艺术 的 关系 问题 谈 不 清楚 , 已经 有 很多 的 讨论 , 李政道 先生 已经 是 第三次 提出 这个 问题 , 这次 是 最 重要 的 一次 , 并且 是 最大 的 一次 国际 研讨会 。 他 主要 强调 智慧 和 情感 , 艺术 也 是 , 科学 也 是 , 它们 本质 上 是 一样 的 , 讲得 很 对 。 而且 李政道 先生 讲 的 是 科学 、 自然科学 而 不是 讲 技术 。 科学 与 艺术 的 关系 发展 到 现在 , 包括 科学技术 与 艺术 的 关系 , 这个 问题 带有 一定 片面性 , 但 又 有 合理性 , 前面 有人 说 到 的 命题 的 公众 性 , 它 带有 普遍性 , 这是 一个 国际性 的 潮流 。 听说 浙江美术学院 曾经 请 哲学家 去 讲课 , 讲得 很 好 , 但是 有 的 艺术家 发问 : 你 懂 笔墨 吗 ? 这 很 可笑 , 哲学家 讲 的 是 普遍性 的 东西 , 艺术家 问 的 问题 有 一定 的 合理性 , 但 哲学家 谈 的 是 普遍性 的 东西 , 艺术家 谈 的 是 个别性 的 东西 。 这个 普遍性 的 东西 如果 我们 不去 研究 , 就 要 落后 。 个别性 的 东西 也 要 关注 , 例如 民族化 , 但是 要 在 普遍性 的 原则 下 。 科学 与 艺术 不管 以 何种 形式 发展 , 这个 命题 的 提出 都 带有 一定 前沿性 , 它 也 一定 会 推动 科学 与 艺术 的 发展 , 它们 的 结合 一定 会 产生 新 的 东西 。 西   川 ( 中央美术学院 副教授 ) : 刚才 各位 的 发言 已经 把 我 想到 的 一些 问题 差不多 都 谈到 了 , 活动 成功 的 地方 我 就 不 重复 了 。 我 没 参加 清华 的 讨论会 , 因此 , 只 根据 展览 谈 我 当时 产生 的 几个 印象 。 一 , 这是 一个 美术学院 所 做 的 跟 科学 有 关系 的 展览 , 而 不是 科学院 做 的 跟 艺术 有 关系 的 展览 。 因此 , 艺术 很 显然 地 呈现 一种 向 科学 靠拢 的 姿态 。 通过 现代科技 手段 , 把 一些 存在 于 现代 艺术 之中 的 现代 观念 介绍 给 大家 。 另外 , 艺术 本 身 显得 不 自然 , 由于 艺术 与 科学 在 社会 发展 中 的 不 对称 比例 , 似乎 艺术 观念 落后 于 科学 的 观念 。 同时 , 通过 这样 一个 展览 , 使 艺术 本身 表现 出来 的 科学 似乎 已经 落后 当代 科学 所 达到 的 水准 。 因为 当代 科学 的 技术 层面 , 它 所 达到 的 形而上 的 层面 都 已 远远 超出 我们 的 想象 。 二 , 作品 不看 标示 牌 , 基本上 能 猜测出 是 哪 国人 所 做 , 尤其 是 中国 人 的 作品 更为 明显 。 为什么 会 如此 ? 这 里面 实际上 存在 一个 观念 置换 的 过程 , 中国 人 的 作品 不是 以 科学 精神 来 指导 , 而是 把 科学 与 艺术 的 空间概念 置换 成 古代 与 现代 的 时间 概念 。 三 , 科学 本身 在 其 现代 研究 中 已经 实现 了 科学 本身 的 转身 。 比 如 物理学 走到 核 物理学 , 最后 变成 对 社会 生活 的 破坏 , 这种 科学 转身 所 引起 的 当代 科学 对 伦理学 所 产生 的 挑战 , 事实上 在 这个 展览 中 很少 涉及 , 这个 展览 基本上 是 艺术 向 科学 靠拢 , 艺术 为 科学 唱赞歌 。 科学 本身 所 包含 的 那些 复杂 命题 、 转身 、 悖论 基本上 艺术家 没有 触及 到 。 四 , 在 科学 与 艺术 这样 一个 大题目 下 , 还 可以 分列 一些 小题目 进行 。 张晓凌 ( 中国艺术研究院 美术 研究所 研究员 ) : 我 最早 介入 “ 艺术 与 科学 ” 这个 主题 是 作为 撰稿人 为 这个 展览 搞 一个 专题片 , 一共 五集 , 我 写 第一集 , 介绍 艺术 与 科学 的 历史 关系 及 发展 脉落 , 并 由此 引发 一些 问题 。 艺术 与 科学 本身 一样 , 最早 的 艺术家 就是 科学家 , 直到 近代 科学 产生 以后 , 才 出现 一些 问题 , 艺术 与 科学 正式 分离 。 展览 有 不足 也 有 价值 的 地方 。 1 . 展品 可 减掉 三分之一 , 一些 作品 与 主题 没有 太大 的 关系 , 而且 质 量 平庸 。 另外 , 700 件 作品 使得 展厅 太 拥挤 , 展览 效果 不是太好 。 2 . 这次 展览 是 一个 开端 , 是 一个 仪式 。 但 进入 仪式 后 , 它 对 科学 、 科学 本身 的 一些 问题 , 对 当代人 的 生存 状态 反思 不够 , 艺术 与 科学 共同 作用 产生 力量 , 并 带来 震撼 的 作品 太 少 。 意义 : 尽管 有些 不足 , 但 其 成绩 、 价值 、 意 义 仍 远远 超过 不足 。 1 . 通过 这个 展览 , 它 表明 艺术 不是 点缀 、 美化 , 也 不是 装饰 , 它 是 一种 生存 方式 、 思维 方式 , 而 这种 方式 可以 通过 科学 的 方 式 展示 给 人们 。 2 . 这个 展览 完成 后 , 全国 美展 与 它 的 距离 至少 有 10 年 以上 的 差距 。 另外 , 它 对 美术馆 本身 也 是 一种 讽刺 , 装置 、 影像 艺术 等等 都 纳入 了 这次 展览 之中 。 3 . 这个 展览 另外 一个 更 重要 的 意义 在于 它 的 象征意义 。 而 在 中国 的 美术界 、 教育界 跨入 21 世纪 时 , 这样 一个 展览 至少 表现 出 一种 和 西方 各国 同步 的 文化 姿态 。 因为 这个 课题 也 是 同 时期 西方 关注 的 课题 , 在 国内 也 从未 提出 过 , 它 的 解决 程度 不管 如何 , 其 象征意义 必定 是 深远 的 。 出席 研讨会 的 还有 : 刘巨德 副 院长 、 包林 教授 、 陈瑞林 副教授 、 尚刚 副教授 等 。 ( 田君 、 海军 根据 录音 整理 , 未经 本人 审阅 。 标题 为 编者 所 加 )    Art

文本是通过空格进行了分词,最后的标签和文本之间用制表符进行了分割。

我们接下来要对标签映射成具体的数值,代码如下:

复制代码
label = []
with open("/content/drive/My Drive/NLP/dataset/Fudan/train_jieba.txt","r",encoding="utf-8") as fp:
  lines = fp.readlines()
  for line in lines:
    line = line.strip().split("\t")
    if len(line) == 2:
      label.append(line[1])   
label = set(label) label_to_idx = list(zip(label,range(len(label)))) label_to_idx_dict = {} idx_to_label_dict = {} for k,v in label_to_idx: label_to_idx_dict[k] = v idx_to_label_dict[v] = k print(label_to_idx_dict) print(idx_to_label_dict)
复制代码

结果:如果不想标签对应的值每次都变,最好将其保存在txt中

{'Energy': 0, 'Enviornment': 1, 'Computer': 2, 'Education': 3, 'Military': 4, 'Philosophy': 5, 'Law': 6, 'Electronics': 7, 'Sports': 8, 'History': 9, 'Space': 10, 'Communication': 11, 'Economy': 12, 'Mine': 13, 'Agriculture': 14, 'Medical': 15, 'Art': 16, 'Transport': 17, 'Literature': 18, 'Politics': 19}
{0: 'Energy', 1: 'Enviornment', 2: 'Computer', 3: 'Education', 4: 'Military', 5: 'Philosophy', 6: 'Law', 7: 'Electronics', 8: 'Sports', 9: 'History', 10: 'Space', 11: 'Communication', 12: 'Economy', 13: 'Mine', 14: 'Agriculture', 15: 'Medical', 16: 'Art', 17: 'Transport', 18: 'Literature', 19: 'Politics'}

接下来我们构建真正的训练集和测试集,首先是训练集:

复制代码
train_data = []
train_label = []
with open("/content/drive/My Drive/NLP/dataset/Fudan/train_jieba.txt","r",encoding="utf-8") as fp:
  lines = fp.readlines()
  for line in lines:
    line = line.strip().split("\t")
    if len(line) == 2:
      train_data.append([line[0]])
      train_label.append([label_to_idx_dict[line[1]]])
train_data = np.array(train_data)
train_label = np.array(train_label)
print(train_data.shape)
print(train_label.shape)
print(train_data[:2])
print(train_label[:2])
复制代码
复制代码
(9803, 1)
(9803, 1)
[['【   文献号   】 3 - 525 【 原文 出处 】 《 文艺报 》 【 原刊 地名 】 京 【 原刊 期号 】 20010705 【 原刊 页 号 】 ④ 【 分   类   号 】 J7 【 分   类   名 】 造型艺术 【 复印 期号 】 200105 【   标     题   】 关于 “ 艺术 与 科学 ” — — 《 艺术 与 科学 国际 作品展 》 理论 研讨会 纪要 【   正     文   】 杭 \u3000 间 ( 清华大学美术学院 艺术 史论 系主任 ) : 由 清华大学 主办 、 我院 承办 的 《 艺术 与 科学 国际 作品展 》 已 在 中国美术馆 开幕 。 艺术 与 科学 的 关系 的 探讨 , 不仅 是 我们 此次 展览 的 命题 , 而且 也 是 全人类 发展 需要 探索 的 重要 主题 之一 , 因此 我们 希望 此 论题 能够 在 学术 层面 得到 更 深入 的 探讨 , 从而 为 未来 社会 发展 积累 充分 的 思想 资源 。 鉴于 此 , 我们 举办 了 这次 由 知名 批评家 参加 的 小型 理论 研讨会 , 希望 各位 踊跃发言 。 ( 以下 以 发言 先后 为序 ) 张凤昌 ( 清华大学美术学院 党委书记 ) : 我院 主办 的 这次 《 艺术 与 科学 国际 作品展 》 得到 了 社会各界 的 大力支持 , 展览 达到 了 预期 的 效果 。 对此 , 我 代表 学院 向 各位 理论家 的 支持 表示 最 衷心 的 感谢 。 艺术 与 科学 这个 命题 的 讨论 早已有之 , 但 在 我国 举办 如此 大规模 的 作品展 与 研讨会 , 这是 第一次 。 这是 一项 长期 的 任务 , 今后 还应 继续 发展 。 因为 我们 的 出发点 是 立足于 教育 , 因此 研究 艺术 与 科学 这个 命题 是 要 重点 考虑 怎样 培养 人 , 培养 德 、 智 、 体 全面 发展 的 人才 。 我 觉得 理论 在 其 中起 着 重要 的 作用 , 它 是 旗帜 , 是 实践 的 指导 。 我们 希望 能 得到 理论家 更 多 的 支持 和 帮助 。 王明 旨 ( 清华大学 副校长 、 美术学院 院长 ) : 非常高兴 众 位 美术 理论家 来 参加 这次 研讨 , 实际上 这次 “ 艺术 与 科学 ” 的 讨论 还 刚刚 起步 , 希 望 将来 能 与 众 兄弟 院校 共同 进行 进一步 的 研究 。 我院 提出 这个 大 的 理想化 的 主题 与 多年 的 学科 设置 和 专业 基础 有关 , 并入 清华大学 后 , 我们 处处 感到 了 清华大学 对 交叉性 的 综合 学科 的 重视 。 科学 本身 同 艺术 一样 是 广泛 而 生动 的 , 这是 二者 结合 的 基础 。 这次 的 展品 偏重于 美术 、 造型艺术 和 科技 之间 , 从小 的 切入点 来 探讨 大 的 课题 。 展品 从 内容 上 可以 分为 三个 方面 : 一部分 表达 了 艺术家 内心 对 科学 的 理解 , 另 一部分 是 科学家 通过 艺 术 表达 的 科学 成果 , 第三 部分 是 与 科学技术 结合 的 艺术设计 作品 。 可以 说 , 介入 科学 是 艺术 永恒 的 命题 , 也 是 新 时代 的 艺术家 了解 生活 的 不可 回 避 的 问题 , 艺术 教育 也 必须 作出 相应 的 调整 。 这次 展览 虽然 只是 初步 探讨 , 并非 十分 完善 和 理想 , 但 毕竟 是 正式 的 起步 , 我们 希望 诸位 能 从 理论 上 提出批评 和 指导 , 以利于 这项 工作 的 深入 发展 。 范迪安 ( 中央美术学院 副 院长 ) : 刚才 王 院长 介绍 了 主办 的 宗旨 、 构想 , 使 我们 对 这次 活动 有 了 更 深入 的 了解 。 我 认为 这次 展览 是 21 世纪 初 大型 的 社会 文化 活动 , 展览 热烈 , 观众 情绪高涨 , 接受度 和 欣喜 度均 超过 以往 。 其 意义 在于 : 第一 , 它 是 清华大学美术学院 多年 教育 积累 的 必然 结果 。 第二 , 它 完成 了 20 世纪 中国 本应 完成 的 命题 , 非常 具有 价值 。 既 完成 了 公众 多年 的 夙愿 , 又 是 引导 新世纪 对 该 命题 关注 的 起点 , 体现 了 学 院 领导班子 的 前瞻性 和 战略性 。 观众 通过 展览 , 不仅 学到 科学知识 , 而且 看到 了 艺术 创造 , 这种 艺术 的 、 文化 的 营养 是 社会 大众 所 需要 的 。 第三 , 展品 多以 视觉 传达 形式 出现 , 是 最 广泛 的 视觉 传达 发射 体 , 形式 前所未有 , 体现 了 当代 文化 传播 手段 。 总之 , 作为 一个 展览 , 能够 基于 一个 最 广泛 的 文化 层面 , 并 具有 相当 的 文化 前瞻性 、 当代 性是 其它 展览 少有 的 。 但是 , 对 具体 命题 的 艺术化 创作 , 在 艺术 与 科学 原理 上 的 思考 还有 待 提 高 。 程 大利 ( 中国 美术 出版 集团 副总 编辑 ) : 上个世纪 提出 的 “ 科学 与 民主 ” 与 “ 艺术 与 科学 ” 一样 , 是 一种 文化 理想 , 具有 指向性 。 看 了 这个 展览 , 我 首先 感到 这是 一个 非常 了不起 的 展览 , 是 一个 经过 精心 准备 的 有 理性 思考 的 展览 , 清华大学 提出 这个 题目 , 是 一个 非常 宏大 的 命题 , 具 有 长远 意义 。 另外 我 想 提出 一个 问题 , 即 艺术 不 应该 仅 是 科学 的 注脚 , 科学 也 不 应仅 是 艺术 的 拐杖 。 去年 《 中国 文化 报 》 有 一个 讨论 , 讨论 科学 与 人文 的 关系 , 可惜 它 浅尝辄止 ; 科学 使人 认识 物质 世界 , 但 科学 的 高度 发展 带来 了 许多 负面 问题 , 艺术 使人 认识 美 , 它 的 最大 功能 应是 净化 人 、 抒发 人类 的 情感 , 二者 的 结合 是 人类 的 理想 , 如果 结合 得 好 了 , 人类 就 进步 了 。 今天 艺术 教育 最 失败 的 地方 是 忽略 了 情感 教育 , 艺术 教育 的 问 题 是 培养 真正 的 人 。 长期以来 , 理工科 学生 的 艺术 素质 薄弱 是 我国 教育界 的 突出 问题 之一 , 大大 限制 了 科学 的 发展 。 应该 看到 , 科学 本身 也 是 美 的 , 量子 物理 、 纳米 世界 都 是 美的 , 科学 与 艺术 是 两翼 , 缺一不可 , 没有 人文 指向 的 科学 的 发展 是 多么 可怕 。 因此 , 这次 展览 是 个 创举 , 在 对 年 轻 科学家 的 培养 方面 有 巨大 的 促进作用 。 如果 我们 真正 弄清 了 艺术 与 科学 的 关系 , 其 意义 是 不可估量 的 。 林 \u3000 木 ( 四川大学 艺术系 教授 ) : 我 注意 到 这次 “ 艺术 与 科学 ” 研讨会 发言 的 构成 人 主要 是 科学家 、 画家 , 思维 导向 主要 是 自然科学 。 可以 说 , 20 世纪 人们 对 科学 的 强调 非常 突出 。 值得注意 的 是 , “ 五四 ” 的 两面 旗帜 “ 民主 ” 和 “ 科学 ” 在 当时 的 美术界 是 对立 的 。 他们 张扬 人性 、 民 主 、 生命 意识 , 反对 科学 的 写实主义 , 这种 对立 很 奇怪 。 科学主义 对 科学研究 的 方法论 、 价值观 较为 关注 , 它 利用 科学 的 权威 做 与 科学 无关 的 事 , 在 20 世纪 较为 普遍 , 这是 应该 注意 的 问题 。 我 认为 , 人文科学 应 在 科学 与 艺术 的 结合 中 产生 重要 的 作用 。 人们 对 科学 的 宏观 与 微观 的 思考 演化 为 哲学 、 宗教 、 历史 的 思考 , 反过来 形成 对 艺术 的 引导 。 这次 展览 的 作品 , 不少 很 新颖 , 但 也 有 很多 只是 对 科学 进行 了 图解 , 比较 牵强 , 因此 值 得 进一步 研究 。 岛 \u3000 子 ( 四川 美术学院 美术学 系主任 、 教授 ) : 我 认为 这次 “ 艺术 与 科学 ” 大展 开启 了 21 世纪 艺术 教育 的 思路 和 框架 。 1998 年 我 到 川美 任教 以来 , 深刻 地 感到 了 美术 学科 缺少 科学性 , 即 国家教委 的 学科 目录 与 社会 需求 有 很大 的 矛盾 , 美术学 的 分类 既 高度 分化 又 高度 融合 。 在 我国 , 人 文 科学 的 范围 界定 还有 待 研究 。 我们 应 关注 学科 内部 的 科学 问题 。 21 世纪 的 素质教育 应 包括 哪些 内容 呢 ? 我 注意 到 清华大学美术学院 艺术 史论 系 开设 了 社会学 课程 , 这 一点 值得 学习 。 人文科学 跟 自然科学 、 社会科学 需要 融合 。 21 世纪 我们 学院 的 建设者 任务 将会 十分艰巨 , 因为 高科技 的 信 息 时代 对 教学 的 冲击 是 不可避免 的 。 它 所 带来 的 知识 传承 问题 , 使 人文 学者 回归 到 前 现代 的 角色 , 即 成为 世界 意义 的 守护者 和 阐释 者 。 水天 中 ( 中国艺术研究院 美术 研究所 研究员 ) : 《 艺术 与 科学 国际 作品展 》 和 “ 艺术 与 科学 理论 研讨会 ” 是 近年 国内 美术界 规模 最大 、 规格 最高 的 学术活动 。 展览 与 研讨会 都 有 许多 使人 耳目一新 的 内容 , 打破 了 艺术 活动 只 着眼于 艺术 圈 之内 的 局限 , 开辟 了 美术 教学 、 美术 实践 、 美术 理论 与 姐妹 学科 交流 借鉴 的 局面 , 为 中国 美术 引来 一股 清风 。 对于 艺术 的 发展 来说 , 它 的 意义 是 在 艺术 与 社会 、 艺术 与 政治 、 艺术 与 个人 感情 … … 之外 , 引导 人们 注视 一条 新 的 思路 , 尝试 一种 新 的 艺术 活动 方式 , 走出 新 的 艺术 通道 。 这是 一条 有 生气 、 有 活力 的 通道 , 它 的 价值 不会 在 其它 通道 之下 。 对于 清华大学美术学院 来说 , “ 艺术 与 科学 ” 还 可以 成为 一个 长期 发展 目标 , 成为 学术 和 办学 风格 上 的 一种 理想 。 当 我们 说 “ 高雅 艺术 ” 的 时候 , 往往 联想起 传统 的 、 古老 的 文化 情趣 。 实际上 艺术 与 科学 也 可以 达到 一种 高雅 的 文化 情趣 ; 当 我们 说 “ 先锋 艺术 ” 的 时候 , 往往 联想起 对 社会 生活 的 反讽 和 对 个人 处境 的 焦虑 , 实际上 艺术 与 科学 、 科学 与 人 的 复杂 关系 , 也 应该 成为 艺术创作 上 的 “ 先锋 问题 ” 。 展览会 的 作品 多 , 中国美术馆 的 场地 小 , 这使 展出 效果 不够 理想 。 除了 展出 场馆 条件 的 限制 外 , 从 艺术 水平 或者 展览会 的 主题 看 , 如果 将 一 些 可有可无 的 作品 删减 ( 如果 减去 四分之一 ) , 展览 的 整体 水准 就 会 明显 提升 。 作品 的 评奖 结果 也 值得 进一步 讨论 , 有些 获奖作品 并 不 具有 艺术 或者 科学 思想 方面 的 创造性 , 有些 作品 甚至 不能 代表 作者 原有 的 艺术 水平 。 在 题材 和 作品 的 人文精神 方面 , 宜作 周密 的 考量 , 例如 科学研究 和 科 技 运用 的 伦理 问题 , 这 已 是 全球性 的 不容 回避 的 话题 。 从 展览 作品 和 研讨会 发言 看 , 这是 一次 称颂 科学 与 艺术 的 集会 , 大家 主要 着眼于 两者 的 光明面 , 而 忽视 了 它们 目前 发展 的 问题 。 在 观察 角度 上 , 国内 艺术家 和 学者 偏向 于 称颂 历史 , 回顾 往昔 的 辉煌 , 在 思路 上 习惯于 绪论 、 概说 式 的 “ 宏大 叙事 ” , 缺少 国外 艺术家 和 学者 那种 对 具体 问 题 的 深入研究 。 这 已经 成为 中国 美术 理论界 的 流弊 。 丁 \u3000 宁 ( 北京大学 艺术 学系 教授 ) : 我 想 谈 以下几点 : 第一 , 艺术 的 发展 要 进入 公众 领域 。 可以 说 , 这次 展览 引起 了 久违 的 关注 , 它 打破 了 近年 美术 领域 的 圈子 化 , 使得 艺术 进入 了 公众 的 精神 领域 , 随着 时间 的 推移 , 其 价值 会 逐渐 彰显 出来 。 第二 , 这次 展览 把 “ 艺术 与 科学 ” 作为 主 题 阐述 是 90 年代 以来 美术界 的 高峰 事件 , 可以 改变 当代 美术 的 专家 化 思路 , 具有 鲜明 的 时代感 和 重要 的 启示 作用 。 第三 , 展览 提出 了 艺术 教育 的 新 方向 , 有助于 改变 对纯 艺术 的 过分 倚重 。 艺术 要 走向 现实 的 发展 , 离不开 科学 的 成分 。 第四 , 这是 一次 美术 活动 国际性 的 探索 。 希望 今后 能继 续 进行 下去 。 第五 , 展览 的 不足 在于 对 研究 的 前沿 情况 把握 不够 , 新思路 不足 。 刘 龙庭 ( 人民美术出版社 编审 ) : 这次 展览 的 命题 是 社会 大 发展 的 产物 。 展览 既 具有 前瞻性 、 开创性 , 又 具有 总结性 、 启示 性 , 对 公众 产生 了 不可 忽视 的 影响 。 中央 工艺 美院 虽然 并入 清华大学 的 时间 并 不长 , 但 贡献 很大 。 我们 也 应该 看到 , 科学 与 艺术 毕竟 不同 , 在 强调 联系 时 , 也 有 区别 。 这是 一个 很大 的 命题 , 需要 继续 深入 讨论 下去 。 在 当前 中国 科学 比较落后 的 情况 下 , 人文科学 也 并 不 先进 , 希望 清华 美院 继续 张扬 自己 的 艺术 个性 , 取得 更大 的 发展 。 吕品 田 ( 中国艺术研究院 美术 研究所 研究员 ) : 清华大学 花 了 如此 大 的 力气 和 声势 , 用 展览 和 研讨会 的 形式 提出 “ 艺术 与 科学 ” 这样 一个 命 题 , 是 具有 历史性 的 、 前瞻性 的 。 但 对 中国 来讲 , 历史性 这 一块 确实 是 中国 的 问题 , 因为 西方 对 “ 艺术 与 科学 ” 的 关注 在 20 世纪 就 大量 地 展开 了 。 而 中国 在 20 世纪 还 并未 真正 地 意识 到 这样 一种 关系 , 并 把 它 作为 一个 命题 提出 来 。 因此 , 这个 命题 的 前瞻性 我 认为 是 国际性 的 , 全球性 的 。 不 仅 是 中国 人 , 整个 生活 在 地球 上 的 人 都 在 关注 这样 一对 关系 。 在我看来 , 不管 是 科学 还是 艺术 , 这 两个 概念 都 是 西方式 的 。 甚至 把 它 作为 一种 关 系 问题 摆出来 , 也 是 一种 西方式 的 思想 。 在 这种 思想 贯穿 着 一种 形而上学 的 传统 , 从 古希腊 到 今天 信息时代 的 二进位制 语言 , 这 中间 是 有着 深刻 联系 的 。 但 实际上 西方 这种 思想 从 一 开始 就 包含 一种 矛盾 。 一方面 , 它 把 人 解释 成 一种 自由 的 存在 , 具有 无限 发展性 、 丰富性 、 非 专门化 的 存在 , 而 另一方面 它 在 解释 自然 、 探索 自然 时 , 又 把 这个 世界 理解 为 有 一个 最终 的 终极 时代 。 在 自然科学 上要 探讨 物质 的 终极 时代 , 在 人文科学 上要 探讨 体现 、 反映 这种 终极 时代 的 真理 。 因此 , 无论 自然科学 , 还是 人文科学 , 在 西方 人 看来 是 一 回事 , 本质 上 它 有 一种 大 科学 思想 。 由此 , 矛盾 从 一 开始 便 产生 了 。 在 古典 时代 , 这样 一种 矛盾 是 不 明显 的 , 文艺复兴 后 , 随着 西方 对 人 的 强调 , 所谓 人文主义 崛起 后 , 必然 就 会 把 这种 矛盾 带 出来 。 对 世界 认识 的 问题 和 人 对 自己 本身 认识 的 问题 矛盾重重 。 这种 矛盾 导致 科学 与 艺术 从 原有 的 混沌 状态 分裂 出来 , 并 产生 科学 与 艺术 。 而 在 今天 大谈 科学 与 艺术 , 它 实际上 反映 了 我们 现在 的 一种 生存 状态 , 一种 力图 改变 我们 现在 对 世界 认识 和 把握 的 矛盾 状态 。 20 世纪 上半叶 , 人类 对 科学 、 现代文明 是持 一种 乐观 的 态度 的 , 中叶 以后 , 这样 一种 状态 发生 了 变化 , 科学 所 带来 的 一些 现实 问题 迫使 我们 去 回头 反思 , 这 是 由 生存环境 引发 的 一种 物质性 问题 。 只有 这些 物质性 的 问题 才能 在 科学时代 打动 这些 被 科学 思 想 所 占领 、 控制 的 人群 。 由此 , 科学 与 艺术 作为 一种 关系 命题 暴露 出来 。 从 这样 一种 意义 上 说 , 提出 这样 一个 前瞻性 的 命题 , 是 非常 有 价值 的 。 从 展览 来说 , 我 感觉 有 三种 类型 的 作品 : 1 . 把 科学 思想 、 科学知识 加以 阐释 、 介绍 , 实际上 带有 一定 程度 的 科学 色彩 。 而 在 当下 , 它 又 具有 意识 形态 的 色彩 。 2 . 表现 现代科技 或用 现代科技 来 辅助 制作 的 作品 , 它 在 某种程度 上 显示 着 现代科技 的 力量 。 3 . 在 科学 的 条件 下 , 强调 对 人 的 生存 状 态 的 关注 , 强调 人 的 现实 体验 的 作品 。 实际上 , 从 艺术 与 科学 的 关系 的 角度看 , 这 一部分 作品 更 有 价值 。 如果 在 以后 还 将 继续 进行 展开讨论 , 我 认为 更应 关注 人 的 生存 状态 , 因为 在 让 人 生存 得 更 幸福 、 更好 的 目标 上 , 无论 科学 还是 艺术 , 它们 都 是 统一 的 。 邵大箴 ( 中央美术学院 教授 ) : 这次 展览 社会 反响 很大 , 收到 了 很 好 的 效果 。 虽然 也 有 不少 缺点 , 当 总的来说 还是 很 不错 的 。 科学 与 艺术 的 关系 问题 谈 不 清楚 , 已经 有 很多 的 讨论 , 李政道 先生 已经 是 第三次 提出 这个 问题 , 这次 是 最 重要 的 一次 , 并且 是 最大 的 一次 国际 研讨会 。 他 主要 强调 智慧 和 情感 , 艺术 也 是 , 科学 也 是 , 它们 本质 上 是 一样 的 , 讲得 很 对 。 而且 李政道 先生 讲 的 是 科学 、 自然科学 而 不是 讲 技术 。 科学 与 艺术 的 关系 发展 到 现在 , 包括 科学技术 与 艺术 的 关系 , 这个 问题 带有 一定 片面性 , 但 又 有 合理性 , 前面 有人 说 到 的 命题 的 公众 性 , 它 带有 普遍性 , 这是 一个 国际性 的 潮流 。 听说 浙江美术学院 曾经 请 哲学家 去 讲课 , 讲得 很 好 , 但是 有 的 艺术家 发问 : 你 懂 笔墨 吗 ? 这 很 可笑 , 哲学家 讲 的 是 普遍性 的 东西 , 艺术家 问 的 问题 有 一定 的 合理性 , 但 哲学家 谈 的 是 普遍性 的 东西 , 艺术家 谈 的 是 个别性 的 东西 。 这个 普遍性 的 东西 如果 我们 不去 研究 , 就 要 落后 。 个别性 的 东西 也 要 关注 , 例如 民族化 , 但是 要 在 普遍性 的 原则 下 。 科学 与 艺术 不管 以 何种 形式 发展 , 这个 命题 的 提出 都 带有 一定 前沿性 , 它 也 一定 会 推动 科学 与 艺术 的 发展 , 它们 的 结合 一定 会 产生 新 的 东西 。 西 \u3000 川 ( 中央美术学院 副教授 ) : 刚才 各位 的 发言 已经 把 我 想到 的 一些 问题 差不多 都 谈到 了 , 活动 成功 的 地方 我 就 不 重复 了 。 我 没 参加 清华 的 讨论会 , 因此 , 只 根据 展览 谈 我 当时 产生 的 几个 印象 。 一 , 这是 一个 美术学院 所 做 的 跟 科学 有 关系 的 展览 , 而 不是 科学院 做 的 跟 艺术 有 关系 的 展览 。 因此 , 艺术 很 显然 地 呈现 一种 向 科学 靠拢 的 姿态 。 通过 现代科技 手段 , 把 一些 存在 于 现代 艺术 之中 的 现代 观念 介绍 给 大家 。 另外 , 艺术 本 身 显得 不 自然 , 由于 艺术 与 科学 在 社会 发展 中 的 不 对称 比例 , 似乎 艺术 观念 落后 于 科学 的 观念 。 同时 , 通过 这样 一个 展览 , 使 艺术 本身 表现 出来 的 科学 似乎 已经 落后 当代 科学 所 达到 的 水准 。 因为 当代 科学 的 技术 层面 , 它 所 达到 的 形而上 的 层面 都 已 远远 超出 我们 的 想象 。 二 , 作品 不看 标示 牌 , 基本上 能 猜测出 是 哪 国人 所 做 , 尤其 是 中国 人 的 作品 更为 明显 。 为什么 会 如此 ? 这 里面 实际上 存在 一个 观念 置换 的 过程 , 中国 人 的 作品 不是 以 科学 精神 来 指导 , 而是 把 科学 与 艺术 的 空间概念 置换 成 古代 与 现代 的 时间 概念 。 三 , 科学 本身 在 其 现代 研究 中 已经 实现 了 科学 本身 的 转身 。 比 如 物理学 走到 核 物理学 , 最后 变成 对 社会 生活 的 破坏 , 这种 科学 转身 所 引起 的 当代 科学 对 伦理学 所 产生 的 挑战 , 事实上 在 这个 展览 中 很少 涉及 , 这个 展览 基本上 是 艺术 向 科学 靠拢 , 艺术 为 科学 唱赞歌 。 科学 本身 所 包含 的 那些 复杂 命题 、 转身 、 悖论 基本上 艺术家 没有 触及 到 。 四 , 在 科学 与 艺术 这样 一个 大题目 下 , 还 可以 分列 一些 小题目 进行 。 张晓凌 ( 中国艺术研究院 美术 研究所 研究员 ) : 我 最早 介入 “ 艺术 与 科学 ” 这个 主题 是 作为 撰稿人 为 这个 展览 搞 一个 专题片 , 一共 五集 , 我 写 第一集 , 介绍 艺术 与 科学 的 历史 关系 及 发展 脉落 , 并 由此 引发 一些 问题 。 艺术 与 科学 本身 一样 , 最早 的 艺术家 就是 科学家 , 直到 近代 科学 产生 以后 , 才 出现 一些 问题 , 艺术 与 科学 正式 分离 。 展览 有 不足 也 有 价值 的 地方 。 1 . 展品 可 减掉 三分之一 , 一些 作品 与 主题 没有 太大 的 关系 , 而且 质 量 平庸 。 另外 , 700 件 作品 使得 展厅 太 拥挤 , 展览 效果 不是太好 。 2 . 这次 展览 是 一个 开端 , 是 一个 仪式 。 但 进入 仪式 后 , 它 对 科学 、 科学 本身 的 一些 问题 , 对 当代人 的 生存 状态 反思 不够 , 艺术 与 科学 共同 作用 产生 力量 , 并 带来 震撼 的 作品 太 少 。 意义 : 尽管 有些 不足 , 但 其 成绩 、 价值 、 意 义 仍 远远 超过 不足 。 1 . 通过 这个 展览 , 它 表明 艺术 不是 点缀 、 美化 , 也 不是 装饰 , 它 是 一种 生存 方式 、 思维 方式 , 而 这种 方式 可以 通过 科学 的 方 式 展示 给 人们 。 2 . 这个 展览 完成 后 , 全国 美展 与 它 的 距离 至少 有 10 年 以上 的 差距 。 另外 , 它 对 美术馆 本身 也 是 一种 讽刺 , 装置 、 影像 艺术 等等 都 纳入 了 这次 展览 之中 。 3 . 这个 展览 另外 一个 更 重要 的 意义 在于 它 的 象征意义 。 而 在 中国 的 美术界 、 教育界 跨入 21 世纪 时 , 这样 一个 展览 至少 表现 出 一种 和 西方 各国 同步 的 文化 姿态 。 因为 这个 课题 也 是 同 时期 西方 关注 的 课题 , 在 国内 也 从未 提出 过 , 它 的 解决 程度 不管 如何 , 其 象征意义 必定 是 深远 的 。 出席 研讨会 的 还有 : 刘巨德 副 院长 、 包林 教授 、 陈瑞林 副教授 、 尚刚 副教授 等 。 ( 田君 、 海军 根据 录音 整理 , 未经 本人 审阅 。 标题 为 编者 所 加 )']
 ['【   文献号   】 1 - 1659 【 原文 出处 】 黄钟 【 原刊 地名 】 武汉 【 原刊 期号 】 199801 【 原刊 页 号 】 3 ~ 8 【 分   类   号 】 J6 【 分   类   名 】 音乐 、 舞蹈 研究 【 复印 期号 】 199804 【   标     题   】 20 世纪 的 中国 二胡 艺术 【   作     者   】 冯光钰 【 作者简介 】 冯光钰 , 男 , 1935 年生 ; 中国音乐家协会 教授 ( 北京     100026 ) 【 内容提要 】 本文 回顾 了 20 世纪 中国 现代 二胡 艺术 从 草创 到 逐渐 成熟 的 历程 。 着重 叙述 了 近百年来 中国 二胡 界 出现 的 华彦钧 、 刘天华 、 吕 文成 “ 二胡 三杰 ” 的 成就 。 并 从 演奏 人才 的 涌现 、 创作 大量 二胡曲 两个 方面 , 论述 50 年代 以来 二胡 艺术 的 全面 发展 。 还 就 力求 二胡 艺术 的 雅俗共赏 、 充 分 发挥 二胡 的 歌唱性 特点 、 体现 出 浓郁 的 民族风格 与 鲜明 的 时代精神 相结合 等 三个 问题 , 分析 了 二胡 艺术 审美 价值 的 创造 。 【 关   键   词 】 二胡     20 世纪     回顾     二胡 三杰     审美 价值 【   正     文   】       [ 分类号 ] J632.21 20 世纪 的 中国 音乐 , 走过 了 一条 既 曲折 艰辛 , 又 充满 了 希望 的 道路 。     现代 二胡 艺术 的 发展 , 也 经历 了 从 草创 到 逐渐 成熟 的 历程 。 近 百年 以 来 , 许多 二胡 艺术家 为了 二胡 演奏 、 创作 的 提高 和 繁荣 , 付出 了 很多 努力 , 做出 了 很大 贡献 。 一 在 谈到 二胡 艺术 审美 价值 时 , 不能不 提到 20 世纪 以来 中国 二胡 界 出现 的 三位 杰出人物 , 这 就是 华彦钧 、 刘天华 、 吕 文成 “ 二胡 三杰 ” 。 在 历代 统治者 的 眼里 , 二胡 是 一件 鄙俗 的 乐器 , 甚至 为 叫化子 乞讨 时所 操拉 , 不能 登 大雅之堂 。 正如 刘天华 在 《 月夜 及 除夜 小唱 说明 》 一文 中 所说 : “ 论及 胡琴 这 乐器 , 从前 国乐 盛行 时代 , 以其为 胡乐 , 都 鄙视 之 ; 今人 误以为 国乐 , 一般 贱视 国乐 者 , 亦 连累 及 之 , 故 自来 很少 有人 将 它 作为 一件 正式 乐器 讨论 过 , 这 真是 胡琴 的 不幸 ” ( 1928 年 2 月 《 音乐 杂志 》 ) 。 所以 , 在 一些 人 看来 , 二胡 只有 帮助 讨饭 的 功能 , 根本 谈不上 什么 审美 价值 。 但 20 世纪 “ 二胡 三杰 ” 的 出现 , 在 很大 程度 上 改变 了 二胡 “ 不幸 ” 的 命运 , 使 二胡 发生 了 历史性 的 变化 , 二胡 艺术 在 群众 的 音乐 生活 中 占有 了 不可或缺 的 地位 。 华彦钧 ( 阿炳 , 1893 — 1950 ) 是 一位 具有 多方面 才能 的 天才 民间 音乐家 。 他 在 二胡 上 的 造诣 使 这件 古老 的 乐器 焕发 了 新 的 艺术 生命力 。 他 的 成功 , 得益于 他 一直 浸泡 在 民间 音乐 的 海洋 里 。 他 从 宗教音乐 、 江南 丝竹 、 民歌 、 戏曲 滩簧 、 曲艺 评弹 、 说 因果 、 小 热昏 、 卖 梨膏 糖 的 叫卖 音乐 中 吸取 了 丰富 的 养料 。 早 在 华彦钧 还是 无锡 道观 雷尊殿 的 小 道童 时 , 便 能 演奏 竹笛 、 胡琴 、 琵琶 、 三弦 、 打击乐 等 乐器 , 受到 道士 们 的 赞赏 , 被 同行 褒誉 为 “ 小天 师 ” 。 在 二胡 演奏 和 创作 方面 , 他 的 功力 尤为 突出 。 原先 的 民间 二胡 , 多 是 在 原 把 位 上 演奏 , 而 阿炳 却 常 在 内外 弦 的 高 把 位 上 灵活运用 。 特别 是 充分发挥 了 内 弦 厚朴 深沉 的 音色 , 以及 空弦上 轻巧 的 弹拨 技巧 , 这 在 他 演奏 的 《 二泉映月 》 及 《 听松 》 、 《 寒 春风 曲 》 中 可以 领略到 。 他 的 作品 , 在 继承 民间 传统 技法 的 基础 上 进行 了 极大 的 突破 。 同时 , 阿炳 还是 一个 技艺 精湛 的 作曲家 。 不过 他 创作 的 许多 二胡 、 琵琶曲 不是 记录 在 乐谱 上 , 而是 凭 他 惊人 的 记忆力 保存 在 琴弦 上 。 从 一些 口碑 材料 可以 看出 , 他 的 艺术 生涯 可 分 两个 阶段 : 前期 ( 大约 1928 年 , 35 岁 双目失明 之前 ) 多 是 道教 音乐 、 江南 民间 乐曲 如 《 三 六 》 、 《 行街 》 、 《 四合 》 、 《 湘江 浪 》 等 , 还有 听 唱片 学来 的 《 小 桃红 》 、 《 昭君 怨 》 、 《 雨 打 芭蕉 》 、 《 三潭印月 》 等 ; 后期 ( 大约 是 35 岁 以后 ) 经常 演奏 的 是 许多 他 自己 经过 长期 积累 、 反复 琢磨 创作 而成 的 乐曲 。 但 当 人们 听到 这些 不曾 听过 的 曲子 问 他 是 什么 名字 时 , 他 总是 喃喃地 说 “ 是 我 瞎 拉 瞎 拉 的 ” , 或 “ 是 向 乡下人 家学来 的 ” 。 但 我们 从 1950 年 由 杨 荫 浏 、 曹安 和 先生 抢 录下来 的 三首 二胡曲 和 三首 琵琶曲 ( 《 大浪淘沙 》 、 《 龙船 》 、 《 昭君 出塞 》 ) 中 , 找不出 与 他 演奏 的 道教 音乐 和 民间 音乐 有 什么 雷同 之 处 , 足以 说明 这些 乐曲 确是 出自 阿炳之手 。 可惜 的 是 , 由于 阿炳 病体 急 剧 恶化 , 加之 抢录 工作 未 抓紧 进行 , 以致 他 的 许多 得意之作 失传 了 。 尽管如此 , 我们 从 阿炳 的 《 二泉映月 》 等 瑰宝 中 , 仍 可以 了解 到 他 不愧 是 一 位 出类拔萃 的 二胡 艺术 巨匠 。 他 对 二胡 艺术 的 发展 作出 了 卓越贡献 。 江阴 才子 刘天华 ( 1895 — 1932 ) , 也 是 发展 二胡 艺术 的 划时代 人物 。 在 中国 音乐史 上 , 他 是 把 民间 乐器 — — 二胡 引进 现代 高等 音乐 学府 教学 和 音乐会 演奏 的 第一 人 , 是 创立 专业 二胡 学派 的 奠基人 。 刘天华 对 二胡 艺术 的 提高 和 发展 , 是从 演奏 和 创作 两 方面 着手 的 。 刘天华 在 演奏 方面 , 首先 潜心 向 著名 江南 丝竹 名家 周少梅 ( 1884 — 1938 ) 学 习 二胡 , 并 经常 在 江阴 寺庙 中 与 和尚 们 一道 演奏 佛曲 , 从中 学习 佛教 音乐 的 二胡 演奏 。 1922 年 刘天华 应聘 到 北京大学 音乐 研究会 任教 后 , 为 提高 和 丰富 二胡 演奏 技法 , 向 俄籍 教授 托诺夫 学习 小提琴 长达 9 年 时间 , 有意识 地 从 西洋 音乐 中 吸取 有益 的 养料 。 这 期间 , 他 对 制作 二胡 琴筒 、 琴杆 、 琴轴 、 琴码 、 弓子 等 工艺 选料 及 定弦 、 揉 弦 、 换 把 等 演奏 技法 进行 了 革新 , 使 二胡 的 表现力 明显 地 得到 了 提高 。 与此同时 , 他 结合 二胡 演奏 技 法 的 改进 , 于 1915 年 蕴酿 创作 《 病中吟 》 开始 , 陆续 写作 了 47 首 二胡 练习曲 及 乐曲 《 月夜 》 、 《 空山鸟 语 》 、 《 苦闷 之 讴 》 、 《 悲歌 》 、 《 闲居 吟 》 、 《 良宵 》 、 《 光明行 》 、 《 独弦操 》 、 《 烛影摇红 》 。 从 这些 乐曲 中 可以 看出 , 刘天华 是 一位 富有 深厚 文化 传统 和 民间 音乐 基础 , 又 十分 善于 巧妙 地 借鉴 西洋 音乐 技巧 的 作曲家 。 他 的 创作 具有 独创性 , 其 旋律 流畅 生动 , 手法 简炼 朴实 , 音乐 结构 严谨 , 形象 鲜明 感人 。 通过 他 的 创作 和 演奏 革新 , 把 二胡 艺术 推向 了 一个 新 的 阶段 , 使 二胡 成为 专业化 的 独奏 乐器 。 吕 文成 ( 1898 — 1981 年 ) 是 一位 有 “ 二胡 博士 ” 美誉 的 音乐家 。 他 在 二胡 、 高胡 、 扬琴 演奏 及 广东音乐 创作 方面 均 有 很 深 的 造诣 , 尤 以 二胡 、 高胡 的 改革 及 演奏 的 贡献 最为 突出 。 他 创作 的 《 步步高 》 、 《 平湖 秋月 》 、 《 渔 歌唱 晚 》 等 名曲 早已 蜚声 海内外 , 然而 我们 以往 对 他 在 二胡 艺 术 上 的 成就 却 重视 得 不够 , 对 他 的 历史 地位 也 肯定 得 不够 。 吕 文成 的 二胡 艺术 成就 与 他 青少年 时代 在 上海 度过 是 分不开 的 。 他 从 1901 年 ( 3 岁 ) 至 1932 年 一直 随父 从 广东 中山 县 旅居 上海 。 他 自幼 喜爱 音乐 。 在 沪 的 30 年间 , 他 向 人 学习 二胡 演奏 , 熟练地 掌握 了 江南 丝竹 中 的 二胡 演奏 技巧 。 并于 1919 年 参加 上海 有名 的 中华 音乐会 。 20 年代 又 与 广 东 音乐家 尹 自重 、 何大 傻 等 人 组成 粤乐组 , 在 上海 、 北京 、 天津 等 地 演奏 广东音乐 , 还 与 小提琴家 司徒 梦岩 合作 同台 演出 。 这些 活动 , 使 吕 文成 积累 了 丰富 的 传统 民间 音乐 与 西洋 音乐 知识 。 在 此基础 上 , 他 对 二胡 进行 了 改革 , 突破 了 传统 二胡 的 局限性 。 首先 是 把 传统 二胡 的 琴杆 适当 缩短 , 将 二胡 外弦 的 丝线 改换 为 钢丝 弦 , 并 将 传统 二胡 的 定弦 提高 四度 , 与 小提琴 的 二弦 、 三弦 的 定弦 相同 ; 还 把 音域 扩展 到 二 、 三把位 , 自由 换 把 , 发展 了 滑 指 、 走 指 、 擞 音 等 技法 , 创制 新 的 乐器 高胡 。 最为 突出 的 是 , 他 将 琴筒 夹于 两腿间 拉奏 , 因为 两腿 可以 自如 地 控制 音量 、 音色 的 变化 , 使得 高胡 风格 华美 流丽 , 音色 明亮 清脆 。 吕 文成 1926 年 赴 广东 巡回演出 时 , 运用 他 改革 的 高胡 演奏 广东音乐 , 代替 了 原来 广东音乐 “ 五架 头 ” 中 二弦 的 地位 , 而 成为 主奏 乐器 , 使广 东 音乐 这 一乐种 得到 很大 的 发展 。 他 的 《 步步高 》 等 正是 为了 发挥 高胡 的 演奏 技能 而 创作 的 乐曲 。 30 年代 后 , 吕 文成 定居 香港 , 专事 粤乐 创作 和 演奏 。 经过 不断 的 艺术 实践 , 使 他 在 技艺 上 日趋 成熟 并 达到 了 炉火纯青 的 境地 。 在 此后 的 几十年 间 , 吕 文成 改革 创造 的 高胡 , 已 在 全国 各地 各种 编制 的 民族 乐队 中 广泛 运用 , 极大 地 丰富 了 乐队 的 表现力 。 这 是 吕 文成 对 发展 二胡 艺术 不可磨灭 的 贡献 , 从而 使 他 成为 与 华彦钧 、 刘天华 并 驾齐 驱 的 20 世纪 中国 “ 二胡 三杰 ” 之一 。 二 “ 二胡 三杰 ” 的 出现 , 乃是 20 世纪 中国 民族音乐 发展 的 必然 现象 。 20 世纪 是 中国 音乐 历史长河 中 十分 重要 的 阶段 。 持续 几千年 的 封建社会 在 本世纪初 崩溃 解体 , 引起 了 文化 的 转型 。 这 期间 音乐 产生 了 很大 的 变化 , 为 “ 二胡 三杰 ” 的 产生 提供 了 历史 机遇 。 这 三位 二胡 杰出人物 , 虽然 在 所处 的 文化 环境 、 成长 际遇 、 师承 关系 、 艺术 活动 等 方面 各不相同 , 但 也 有 许多 共同 的 因素 , 促使 他们 差不多 在 同一 时期 中 趋于 成熟 。 这些 共同点 是 : 第一 , “ 二胡 三杰 ” 都 先后 出 生于 19 世纪 90 年代 。 在 他们 悟事 之时 , 都 经历 了 20 世纪 初叶 的 两大 政治 事件 。 一是 1911 ( 辛亥 ) 年 10 月 10 日爆 发 的 震撼 世界 的 资产阶级 革命 。 辛亥革命 结束 了 中国 两千多年 的 封建 君主专制 制度 。 二是 1919 年 5 月 4 日 爆发 的 中国 人民 反对 帝国主义 和 封建主义 的 伟大 民主革命 运动 。 五四运动 成为 中国新民主主义革命 的 开端 。 华彦钧 、 刘天华 、 吕 文成 在 这 历史性 的 变革 中 , 都 不同 程度 地 接受 了 爱国主义 、 民族主义 、 民主自由 解放 的 思想 的 影响 。 这 对 他们 的 艺术 生涯 , 包括 演奏 和 创作 均 带来 了 直接 的 影响 。 特别 有意思 的 是 , 他们 的 生命 虽然 有长 有 短 , 但 纵观 他们 在 二胡 艺术 上 的 成熟 和 辉煌 时期 , 不约而同 的 都 是 本世纪 二三十 年代 。 刘天华 在 30 年代 初因 收集 民间 锣鼓 音乐 , 不幸 染上 猩红 热而 英年早逝 , 终年 只有 37 岁 。 华彦钧 和 吕 文成 虽然 分别 活 了 58 岁 和 83 岁 , 但 他俩 对 二胡 的 改革 成就 和 创作 上 的 精品 , 都 主要 产生 在 这 一时期 。 特别 是 “ 二胡 三杰 ” 创作 的 二胡 及 高胡 曲 , 都 是从 不同 角度 反映 这 一时期 人们 的 生活 和 思想感情 。 可以 说 , 经受 辛亥革命 和 五四运动 洗礼 的 华彦 钧 、 刘天华 和 吕 文成 , 在 艺术 上 的 创造 , 无不 受益 于 时代 的 给予 。 第二 , 中国 传统 音乐 经过 数千年 的 积累 和 发展 , 具有 巨大 的 艺术 能量 。 每 一个 时代 有 作为 的 音乐家 , 都 会 从 传统 音乐 中 吮吸 丰富 的 养料 。 华 彦 钧 、 刘天华 和 吕 文成 也 是 这样 。 他们 都 从 孩提 之时 起 直接 受到 传统 音乐 的 熏陶 。 特别 是 民间 音乐 、 宗教音乐 对 他们 的 成长 起到 了 十分 关键 的 作 用 。 华彦钧 自幼 就 生活 在 农村 和 道观 , 以 道教 乐手 和 民间 音乐 艺人 为师 , 学习 各种 传统 音乐 。 刘天华 在 江阴 和 常州 的 中学 教 音乐 时 , 便 抽出 大量 时间 向 各地 的 民间 音乐家 学习 二胡 、 琵琶 、 古琴 、 三弦 拉戏 及 昆曲 等 , 还 经常 到 江阴 涌塔庵 学习 佛教 音乐 。 1930 年 他 为 京剧 名家 梅兰芳 记录 唱腔 , 编成 《 梅兰芳 歌曲 谱 》 一册 。 他 的 不幸 早逝 , 也 是 因 赴 北京 天桥 收集 锣鼓 谱 , 罹病 不治 所致 。 吕 文成 也 是 浸泡 在 江南 丝竹 、 广东音乐 和 粤曲 演 奏 演唱 中 成长 起来 的 。 他 不仅 二胡 、 高胡 、 扬琴 的 技艺 娴熟 , 而且 还 擅长 演唱 粤曲 “ 子喉 ” 。 他 演唱 的 《 潇湘 琴怨 》 、 《 燕子 楼 》 、 《 离天 恨 》 等 曲目 , 情意 缠绵 、 悠扬 抒情 。 这 对 他 创作 的 由 高胡 领奏 的 许多 广东音乐 乐曲 有着 直接 的 影响 。 特别 是 一些 富于 歌唱性 的 广东音乐 乐曲 , 可以 听 出 , 有 不少 是 吸取 的 粤曲 唱腔 旋律 , 有 的 甚至 就是 模拟 唱腔 发展 而成 。 第三 , 20 世纪 初始 西洋 音乐 大量 传入 中国 。 中西 音乐 的 融合 , 是 促使 中国 音乐 在 20 世纪 转型 的 因素 之一 。 “ 二胡 三杰 ” 的 演奏 和 创作 , 都 不 同 程度 直接 或 间接 受到 西洋 音乐 的 影响 。 刘天华 16 岁 时 在 江阴 的 中学 参加 军乐队 , 吹奏 小号 , 开始 接触 西洋 音乐 。 如前所述 , 他 在 北京 的 10 年间 ( 1922 — 1932 ) 从未 间断 学习 小提琴 。 同时 , 他 还 很 重视 西洋 音乐 理论 的 学习 和 研究 , 努力 从中 借鉴 外国 乐曲 的 音乐 结构 及 表现 方法 。 在 他 创作 的 二胡 练习曲 及 乐曲 中 , 明显 地 吸收 了 西洋 音乐 的 经验 。 吕 文成 将 二胡 改革 为 高胡 , 从 将 外 弦 改为 钢丝 弦 到 把 定弦 提高 四度 , 无一不是 受到 小提琴 的 启示 。 从 他 创作 的 《 步步高 》 等 乐曲 中 , 也 可 看出 其 曲式 结构 及 旋律 发展 手法 , 均 有 借鉴 西洋 音乐 的 因素 。 华彦钧 虽未 直接 学习 过 西洋 音乐 , 但 从 他 对 新 事物 的 敏感 和 接受 能力 来看 , 从 接触 当时 的 唱片 及 广播电台 播放 的 音乐 中 , 也许 会 间接 地 受到 一些 外国 音乐 及 新 音乐 的 影响 , 并 有机 地 吸收 到 他 的 创作 及 演奏 中 去 。 “ 二胡 三杰 ” 的 这些 共同 特点 的 形成 , 归根到底 乃 时代 所 使然 。 他们 在 风起云涌 的 时代 浪潮 中 , 从 关心 国家 、 民族 的 命运 出发 , 对 中国 民族 音乐 事业 克尽 所能 , 发挥 了 自己 的 聪明才智 , 使 二胡 艺术 在 20 世纪 获得 了 空前 的 发展 。 三 华彦钧 、 刘天华 、 吕 文成 “ 二胡 三杰 ” 对 20 世纪 中国 二胡 艺术 的 贡献 , 不仅 给 我们 留下 了 珍贵 的 二胡 艺术 遗产 , 还 在 他们 直接 培育 或 积极 影 响下 , 造就 了 一批 从事 二胡 艺术 的 人才 。 成长 于 三四十 年代 的 二胡 家 , 成 了 这 一时期 传播 和 发展 二胡 艺术 的 中坚力量 。 二三十 年代 有 陈振铎 、 储 师竹 、 蒋风 之 、 刘北茂 、 陆修堂 、 刘 天一 、 朱海 等 人 ; 40 年代 有王 乙 、 丁 d ā n g @ ① 、 黎松寿 、 俞鹏 、 张锐 、 朱郁 之 、 闵季 骞 、 段启诚 、 刘明 沅 、 张韶 、 陈朝儒 等 人 。 他们 直接 或 间接 地 承袭 了 “ 二胡 三杰 ” 的 优良传统 , 不仅 在 二胡 演奏 和 创作 方面 取得 了 很大 的 成绩 , 还 在 二胡 教学 上 做 了 许多 有益 的 探索 , 培养 了 一些 后起之秀 。 特别 引人注目 的 是 , 50 年代 以来 的 近 半个世纪 , 中国 二胡 艺术 水平 得到 了 飞速 的 提高 , 可以 说 , 这是 二胡 艺术 发生 整体性 变革 与 全面 发展 的 黄金 季节 。 主要 表现 在 两个 方面 : 一是 演奏 人才 的 涌现 。 各 高等 音乐 院校 的 本科 和 附中 , 及 一些 师范院校 音乐系 , 乃至 省 、 地级 艺术 学校 , 都 开设 了 二胡 演奏 课程 。 从 练习曲 到 乐曲 的 教学 的 逐步 规 范化 , 为 学习者 打下 扎实 的 基本功 创造 了 条件 。 经过 严格 的 专业训练 , 五六十年代 涌现 了 一批 富有 才华 的 二胡 家 , 如 唐毓斌 、 牛 巨贵 、 黄海 怀 、 项祖英 、 汪炳炎 、 王宜勤 、 鲁日融 、 甘尚时 、 黄日进 、 孙 文明 、 甘柏霖 、 王国 潼 、 蒋巽风 、 陈 耀星 、 肖白庸 、 舒昭 、 周耀锟 、 闵惠芬 、 蒋才 如 等 人 。 在 很长 一段时间 里 , 他们 是 二胡 界 的 中流砥柱 式 的 人物 , 对 推动 二胡 艺术 的 进一步 发展 , 起到 了 积极 的 作用 。 80 年代 以来 , 二胡 乐坛 上 又 出 现了 一批 熠熠生辉 的 新星 , 如余 其伟 、 陈 国产 、 姜 建华 、 朱昌耀 、 周维 、 欧 景星 、 宋飞 、 李小萍 等 人 , 这 标志 着 二胡 艺术 后继有人 , 且 青出于蓝 而胜于蓝 。 二是 创作 了 大量 的 二胡曲 。 这 一时期 的 二胡曲 , 有 的 出自 二胡 演奏家 之手 , 有 的 则 是 专业 作曲家 的 创作 。 前者 如 《 田野 小曲 》 ( 王乙曲 ) 、 《 大凉山 狂想曲 》 ( 段启诚 曲 ) 、 《 苍山 抒怀 》 ( 舒昭曲 ) 、 《 梆子 风 》 ( 项祖英 编曲 ) 、 《 鱼游 春水 》 ( 刘天 一曲 ) 、 《 江河水 》 ( 黄海 怀 移植 改编 ) 、 《 赛马 》 ( 黄海 怀曲 ) 、 《 秦腔 主题 随想曲 》 ( 鲁日融 、 赵震霄曲 ) 、 《 洪湖 人民 的 心愿 》 ( 闵惠芬 编曲 ) 、 《 流波曲 》 ( 孙 文明 曲 ) 、 《 弹乐 》 ( 孙 文明 曲 ) 、 《 草原 新 牧民 》 ( 刘长 福曲 ) 、 《 战马 奔腾 》 ( 陈 耀星 曲 ) 、 《 江南 春色 》 ( 朱昌耀曲 ) 等等 ; 后者 如 《 豫北 叙事曲 》 ( 刘文金 曲 ) 、 《 三门 峡 畅想曲 》 ( 刘文金 曲 ) 、 二胡 协奏曲 《 长城 随想 》 ( 刘文金 曲 ) 、 《 一枝花 》 ( 张式业 编曲 ) 、 《 子弟兵 和 老百姓 》 ( 晨耕 、 唐诃 编曲 ) 、 《 拉 骆驼 》 ( 曾 寻曲 ) 、 《 山村 变了样 》 ( 曾 加庆曲 ) 、 《 赶集 》 ( 瞿春泉曲 ) 、 《 湘江 乐 》 ( 时乐 m é n g @ ② y í n g @ ③ 曲 ) 、 《 春诗 》 ( 钟义 良曲 ) 、 《 金珠玛米 赞 》 ( 王 竹林 曲 ) 、 《 春到 田间 》 ( 林韵曲 ) 、 《 春郊 试马 》 ( 陈德钜曲 ) 、 高胡 、 古筝 三重奏 《 春天 来 了 》 ( 雷雨 声 曲 ) 等等 。 新 的 二胡曲 的 创作 , 一方面 增添 和 积累 了 二胡 的 演奏 曲目 , 另一方面 也 推动 了 二胡 演奏 技法 的 不断改进 和 丰富 。 在 继承 “ 二胡 三态 ” 的 基础 上 , 现代 二胡 演奏 又 获得 了 较大 的 提高 和 发展 , 这 与 二胡曲 新作 的 不断 出现 是 分不开 的 。 四 近 一个 世纪 以来 , “ 二胡 三杰 ” 及 他们 的 后继者 在 发展 二胡 艺术 的 道路 上 , 历尽艰辛 所 取得 的 成就 , 从根本上 来说 是 一种 审美活动 。 而 二胡 艺术 的 这种 审美 价值 的 创造 , 无论是 演奏 , 还是 创作 , 都 是 特殊 的 生产性 审美活动 。 从 审美 价值 的 角度 来 审视 现代 二胡 艺术 , 可以 归纳 为 如下 几 个 特点 : 1 . 力求 二胡 艺术 的 雅俗共赏 传统 的 二胡 , 是 一件 在 民间 广为流传 的 乐器 。 由于 制作 简便 , 价格低廉 , 很 容易 为 一般 平民百姓 所 掌握 。 自 20 世纪 “ 二胡 三杰 ” 出现 后 , 使 二胡 艺术 发展 到 较 高 的 品位 , 从此 使 一个 民间 的 俗 乐器 登上 了 大雅之堂 。 “ 二胡 三杰 ” 以来 的 近百 年间 , 二胡 艺术 之所以 深受 广大群众 的 喜爱 , 主要 在于 它 既 保持 了 传统 的 “ 俗 ” 音乐 的 面貌 , 又 有 “ 雅 ” 音乐 的 审 美 追求 , 使雅 、 俗 音乐 在 二胡 艺术 中 走向 合流 , 两者 相互 借鉴 、 交叉 渗透 、 取长补短 , 达到 了 一定 程度 的 融合 。 这样 的 例子 很多 。 如 《 二泉映月 》 是 出自 民间 音乐家 阿炳之手 , 其 成功 之 处 , 便是 它 具有 雅俗共赏 的 艺术 魅力 。 这首 既 通俗 又 深刻 反映 广大 低层 大众 的 思想感情 的 乐曲 , 在 阿炳 经年累月 演奏 于 大街小巷 时 , 深受 寻常 老百姓 的 欢迎 。 这首 乐曲 经 录音 记录 出版 后 , 得到 更加 广泛 的 传播 。 通过 在 音乐会 上 演奏 和 广播电视 的 播放 , 在 高层次 文化圈 内 也 引起 了 强烈 共鸣 , 人们 无不 为 阿炳 的 音乐 所 倾倒 , 甚至 使 日本 著名 指挥家 小泽征尔 感动 得 不禁 潸然泪下 , 他 说 : “ 这种 音乐 只 应该 跪 着 听 。 ” 可见 《 二泉映月 》 的 艺术 感染力 强烈 。 刘天华 、 吕 文成 以及 黄海 怀等 人 的 音乐 得以 久远 的 流传 , 也 证明 二胡 艺术 发展 中 的 雅俗 彼此 相渗 、 交相 为用 , 达到 了 雅俗 相通 的 艺术境界 。 本世纪 涌现 的 许多 优秀 的 二胡 作品 , 都 有着 一个 共同 的 特点 , 这是 力求 做到 雅俗共赏 。 2 . 充分发挥 二胡 的 歌唱性 特点 二胡 千百年来 能 长久 流行 在 民间 , 本世纪 以来 又 不断 登上 大雅之堂 , 一个 重要 原因 是 它 具有 歌唱性 的 特点 。 这 就 使得 二胡 艺术 的 价值 表现 为 : 以 作品 的 丰富 内容 及 富于 歌唱性 的 艺术 感染力 赢得 人们 的 喜爱 。 “ 二胡 三杰 ” 的 作品 及 演奏 之所以 经久不衰 , 百听不厌 , 一是 乐曲 的 旋律 性 很 强 , 音乐 主题 优美 动听 , 听众 可以 “ 过耳 成诵 ” ; 二是 在 演奏 技法 上 充分发挥 了 两根 琴弦 的 歌唱性 特点 , 奏 出 人们 的 心声 , 引起共鸣 。 黄海 怀及 许多 二胡曲 作家 的 作品 都 充分体现 了 这种 特点 。 黄海 怀 移植 改编 的 《 江河水 》 , 在 同名 民间 双管 曲 的 基础 上 , 着力 发挥 二胡 的 特性 , 无论是 指法 的 揉 颤 吟咏 , 还是 各种 弓法 的 强弱 力度 对比 , 都 把 乐曲 的 音乐 形象 表现 得 淋漓尽致 , 音乐 好像 是 一位 古代 妇女 不幸 人生 的 悲泣 、 倾诉 , 震撼 着 人 们 的 心灵 。 他 的 《 赛马 》 ( 原作 ) 在 表现 内蒙古草原 骏马奔驰 的 热烈 场面 时 , 一方面 在 前后 乐段 运用 了 复杂多变 的 快 弓 、 跳弓 及顿 弓 , 同时 在 中 段 十分 注重 在 琴弦 上奏出 悠扬 舒畅 的 蒙古族 民歌 旋律 , 既 增强 了 音乐 的 紧松 快慢 对比 , 又 刻画 了 草原 牧民 的 豪迈 性格 。 另一方面 , 我们 也 看到 , 有些 二胡曲 的 创作 和 演奏 , 不同 程度 地 存在 着 脱离 内容 的 单纯 技巧 表现 , 乃至 炫耀 的 倾向 。 其 原因 在于 没有 扬 二胡 艺术 之 所长 , 反而 弄巧成拙 。 3 . 体现 出 浓郁 的 民族风格 与 鲜明 的 时代精神 相结合 “ 二胡 三杰 ” 和 他们 的 后继者 的 许多 成功 之作 , 在 这方面 做出 了 可贵 的 艺术 实践 。 他们 在 作品 中 , 采用 群众 喜闻 乐听 的 音乐 语言 表现 各个 时 代 的 现实生活 , 说明 他们 在 取之不尽 的 生活源泉 中 , 选取 最 富于 时代 意义 的 题材 , 并 认真 从 丰富多彩 的 民族民间 音乐 中 吸取 营养 , 将 其 发展 变化 成 特有 的 二胡 音乐 语言 。 这样 的 例子 不胜枚举 。 从 以上 可见 , 二胡 艺术 和 其他 姊妹 艺术 一样 , 其 价值 只有 当 它 被 人们 欣赏 、 评论 , 真正 得到 检验 和 承认 , 其 潜在 的 价值 才能 转化 成为 现实 的 价值 , 从而 形成 一种 社会 效应 。 20 世纪 的 中国 二胡 艺术 的 迅速 发展 , 正是 由于 “ 二胡 三杰 ” 和 等 众多 的 二胡 艺术家 , 以 其 孜孜不倦 的 精神 , 不断创新 的 结果 。 当然 , 二胡 在 未来 的 岁月 中要 实现 艺术 上 的 更加 完善 , 还 需要 不断 地 超越 。 二胡 艺术 的 审美 价值 , 就 会 在 这种 超越 、 升华 和 融汇 中 获得 充分 的 实现 。 【 责任编辑 】 蔡际洲 字库 未存 字 注释 : @ ① 原字 王右 加当 @ ② 原字 氵 右 加蒙 @ ③ 原字 氵 右 加莹']]
[[16]
 [16]]
复制代码

然后是构建测试集:

复制代码
test_data = []
test_label = []
with open("/content/drive/My Drive/NLP/dataset/Fudan/test_jieba.txt","r",encoding="utf-8") as fp:
  lines = fp.readlines()
  for line in lines:
    line = line.strip().split("\t")
    if len(line) == 2:
      test_data.append([line[0]])
      test_label.append([label_to_idx_dict[line[1]]])
test_data = np.array(test_data)
test_label = np.array(test_label)
print(test_data.shape)
print(test_label.shape)
print(test_data[:2])
print(test_label[:2])
复制代码

按照惯例,我们还需要将训练集打乱:要同时将数据和标签进行打乱

复制代码
shuffle_ix = np.random.permutation(np.arange(len(train_data)))
train_row = list(range(len(train_data)))
random.shuffle(train_row)
print(train_row)
train_data = train_data[train_row,:]
train_label = train_label[train_row,:]
print(train_data[:2])
print(train_label[:2])
复制代码

3、搭建模型

接下来就可以搭建模型了,这里需要将二维数组转换一维数组,同时要将numpy数组转换为列表:

from sklearn.feature_extraction.text import TfidfVectorizer
train_data = train_data.reshape(len(train_data)).tolist()
print(train_data[:2])
tfidf_model = TfidfVectorizer()
sparse_result = tfidf_model.fit_transform(train_data)     # 得到tf-idf矩阵,稀疏矩阵表示法

打印一下看看:

for k,v in tfidf_model.vocabulary_.items():
  print(k,v)
复制代码
心理负荷 215604
打破常规 224020
环境压力 283954
起新 341654
救弱 234689
抑强 225392
亚稳态 114323
醒觉 353619
能态 317052
特异功能 280313
。。。。。。
复制代码

同样的,我们也需要对测试集进行相同的转换:

test_data = test_data.reshape(len(test_data)).tolist()
print(test_data[:2])
test_sparse_result = tfidf_model.transform(test_data) 

最后是使用朴素贝叶斯进行分类:

from sklearn.naive_bayes import MultinomialNB
from sklearn.metrics import classification_report
mnb_count = MultinomialNB()
mnb_count.fit(sparse_result, train_label)   # 学习
mnb_count_y_predict = mnb_count.predict(test_sparse_result)   # 预测
classification_report(mnb_count_y_predict, test_label)

结果:

复制代码
              precision    recall  f1-score   support

           0       1.00      0.44      0.61      3662
           1       0.00      0.00      0.00         0
           2       0.81      0.90      0.85      1104
           3       0.00      0.00      0.00         0
           4       0.44      0.97      0.60       461
           5       0.96      0.81      0.88      1608
           6       0.00      0.00      0.00         0
           7       0.62      0.85      0.72       752
           8       0.00      0.00      0.00         0
           9       0.36      0.99      0.53       236
          10       0.00      0.00      0.00         0
          11       0.00      0.00      0.00         0
          12       0.00      0.00      0.00         0
          13       0.00      0.00      0.00         0
          14       0.00      0.00      0.00         0
          15       0.87      0.90      0.88      1214
          16       0.00      0.00      0.00         0
          17       0.00      0.00      0.00         0
          18       0.86      0.81      0.83       795
          19       0.00      0.00      0.00         0

    accuracy                           0.71      9832
   macro avg       0.30      0.33      0.30      9832
weighted avg       0.87      0.71      0.74      9832
复制代码

至此,将整个流程打通了。如果想提高分类的性能,则需要进一步的数据预处理以及模型的调参了。

4、进一步调优

为了避免每次再运行时标签映射发生变化,我们将标签映射存入到txt中:

复制代码
label = []
with open("/content/drive/My Drive/NLP/dataset/Fudan/train_jieba.txt","r",encoding="utf-8") as fp:
  lines = fp.readlines()
  for line in lines:
    line = line.strip().split("\t")
    if len(line) == 2:
      label.append(line[1])
label = set(label)
label_to_idx = list(zip(label,range(len(label))))
with open("/content/drive/My Drive/NLP/dataset/Fudan/label.txt","w",encoding="utf-8") as fp:
  for k,v in label_to_idx:
    fp.write(k+"\t"+str(v)+"\n")
复制代码

接下里我们要仔细研究一下sklearn自带的TfidfVectorizer()了。

(1)fit()、fit_transform()以及transform()

这里要额外提到fit()、fit_transform()以及transform():简单的说下自己的理解,具体的还是百度吧

fit():输入要拟合的数据

transform():对拟合的数据进行转换

fit_transform():拟合和转换的结合

一般是这么使用的:

from sklearn.preprocessing import StandardScaler
sc = StandardScaler()
sc.fit_tranform(X_train)
sc.tranform(X_test)

一般是对训练集进行拟合和转换,然后对测试集进行转换即可,如果我们再使用sc.fit_transform(X_test)就会报错。具体对于不同的函数,稍微有所区别,这里就不展开了。

(2)参数:token_pattern

接下来看以下代码:

复制代码
from sklearn.feature_extraction.text import TfidfVectorizer
document = ['我 是 一条 天狗 呀 !', '我 把 月 来 吞 了 ,', '我 把 日来 吞 了 ,', '我 把 一切 的 星球 来 吞 了 ,', '我 把 全宇宙 来 吞 了 。', '我 便是 我 了 !'
      ]

tfidf = TfidfVectorizer()
tfidf_model = tfidf.fit(document)
tfidf_model.vocabulary_
复制代码

输出:{'一切': 0, '一条': 1, '便是': 2, '全宇宙': 3, '天狗': 4, '日来': 5, '星球': 6}

也就是之前我们没有进行任何处理,直接使用TfidfVectorizer(),那么单词长度为1的就自动被过滤掉了,控制这个的参数是:token_pattern,其默认匹配长度>=2的单词。

其默认参数为r"(?u)\b\w\w+\b",其中的两个\w决定了其匹配长度至少为2的单词。

我们改为以下即可匹配出一个词的:

tfidf = TfidfVectorizer(token_pattern=r"(?u)\b\w+\b")

输出:{'一切': 0, '一条': 1, '了': 2, '便是': 3, '全宇宙': 4, '吞': 5, '呀': 6, '天狗': 7, '我': 8, '把': 9, '日来': 10, '星球': 11, '是': 12, '月': 13, '来': 14, '的': 15}

看一下转换之后的结果:

tfidf_model.transform(document).todense()
复制代码
matrix([[0.        , 0.48812169, 0.        , 0.        , 0.        ,
         0.        , 0.48812169, 0.48812169, 0.2166769 , 0.        ,
         0.        , 0.        , 0.48812169, 0.        , 0.        ,
         0.        ],
        [0.        , 0.        , 0.31515212, 0.        , 0.        ,
         0.36493681, 0.        , 0.        , 0.27305977, 0.36493681,
         0.        , 0.        , 0.        , 0.61513894, 0.42586833,
         0.        ],
        [0.        , 0.        , 0.34831708, 0.        , 0.        ,
         0.40334084, 0.        , 0.        , 0.30179515, 0.40334084,
         0.67987295, 0.        , 0.        , 0.        , 0.        ,
         0.        ],
        [0.4641016 , 0.        , 0.23777166, 0.        , 0.        ,
         0.27533252, 0.        , 0.        , 0.2060144 , 0.27533252,
         0.        , 0.4641016 , 0.        , 0.        , 0.32130331,
         0.4641016 ],
        [0.        , 0.        , 0.31515212, 0.        , 0.61513894,
         0.36493681, 0.        , 0.        , 0.27305977, 0.36493681,
         0.        , 0.        , 0.        , 0.        , 0.42586833,
         0.        ],
        [0.        , 0.        , 0.35776647, 0.69831701, 0.        ,
         0.        , 0.        , 0.        , 0.61996492, 0.        ,
         0.        , 0.        , 0.        , 0.        , 0.        ,
         0.        ]])
复制代码

说明:我们有6个句子,共有16个词组,因此形状是(6,16)。

(3)参数:min_df/max_df

我们可以通过min_df/max_df参数:默认是0-1的浮点数或者是一个整数,如果是浮点数,则是将低于某比例或者高于某比例的词移除,如果是整数,则是将低于一定频数或者高于一定频数的词进行移除。我们接下来试试:

tfidf = TfidfVectorizer(token_pattern=r"(?u)\b\w+\b",min_df=0.1,max_df=0.5)

输出:{'一切': 0, '一条': 1, '便是': 2, '全宇宙': 3, '呀': 4, '天狗': 5, '日来': 6, '星球': 7, '是': 8, '月': 9, '来': 10, '的': 11}

过滤掉了一些词。

(4)参数:ngram_range

tfidf = TfidfVectorizer(token_pattern=r"(?u)\b\w+\b",ngram_range=(1,2))

输出:{'一切': 0, '一切 的': 1, '一条': 2, '一条 天狗': 3, '了': 4, '便是': 5, '便是 我': 6, '全宇宙': 7, '全宇宙 来': 8, '吞': 9, '吞 了': 10, '呀': 11, '天狗': 12, '天狗 呀': 13, '我': 14, '我 了': 15, '我 便是': 16, '我 把': 17, '我 是': 18, '把': 19, '把 一切': 20, '把 全宇宙': 21, '把 日来': 22, '把 月': 23, '日来': 24, '日来 吞': 25, '星球': 26, '星球 来': 27, '是': 28, '是 一条': 29, '月': 30, '月 来': 31, '来': 32, '来 吞': 33, '的': 34, '的 星球': 35}

该参数的意思是1个到2个词组之间的组合。

(5)参数:stop_words=[]

tfidf = TfidfVectorizer(token_pattern=r"(?u)\b\w+\b",ngram_range=(1,2),stop_words=['我','来','把','的'])

输出:{'一切': 0, '一切 星球': 1, '一条': 2, '一条 天狗': 3, '了': 4, '便是': 5, '便是 了': 6, '全宇宙': 7, '全宇宙 吞': 8, '吞': 9, '吞 了': 10, '呀': 11, '天狗': 12, '天狗 呀': 13, '日来': 14, '日来 吞': 15, '星球': 16, '星球 吞': 17, '是': 18, '是 一条': 19, '月': 20, '月 吞': 21}

该参数的意思是过滤掉一些词语,传入一个列表。

(6)参数:max_features:int

tfidf = TfidfVectorizer(token_pattern=r"(?u)\b\w+\b",ngram_range=(1,2),stop_words=['我','来','把','的'],max_features=10)

输出:{'了': 0, '吞': 1, '吞 了': 2, '天狗 呀': 3, '日来': 4, '日来 吞': 5, '星球': 6, '星球 吞': 7, '是': 8, '是 一条': 9}

该参数的意思是使用前多少个词语,传入一个整型。

5、优化结果

我们重新运行一下程序,使用我们之前存储大label.txt中的映射关系,同时使用默认的TfidfVectorizer()。

,结果如下:

复制代码
              precision    recall  f1-score   support

           0       0.00      0.00      0.00         0
           1       0.44      0.97      0.60       461
          10       0.00      0.00      0.00         0
          11       0.00      0.00      0.00         0
          12       0.81      0.90      0.85      1104
          13       0.36      0.99      0.53       236
          14       0.00      0.00      0.00         0
          15       0.00      0.00      0.00         0
          16       1.00      0.44      0.61      3662
          17       0.00      0.00      0.00         0
          18       0.00      0.00      0.00         0
          19       0.00      0.00      0.00         0
           2       0.00      0.00      0.00         0
           3       0.87      0.90      0.88      1214
           4       0.00      0.00      0.00         0
           5       0.62      0.85      0.72       752
           6       0.96      0.81      0.88      1608
           7       0.00      0.00      0.00         0
           8       0.00      0.00      0.00         0
           9       0.86      0.81      0.83       795

    accuracy                           0.71      9832
   macro avg       0.30      0.33      0.30      9832
weighted avg       0.87      0.71      0.74      9832
复制代码

接下来我们先读取停止词列表:

复制代码
stopwords = []
with open("/content/drive/My Drive/NLP/dataset/Fudan/stopwords.txt","r",encoding="utf-8") as fp:
  lines = fp.readlines()
  for line in lines:
    line = line.strip()
    stopwords.append(line)
print(stopwords)
复制代码

部分结果:['$', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '?', '【', '】', '_', '“', '”', '、', '。', '《', '》', '一', '一些', '一何', '一切', '一则', '一方面', '一旦']

然后我们构建带参数的TfidfVectorizer():代码如下:

复制代码
from sklearn.feature_extraction.text import TfidfVectorizer
tfidf_model2 = TfidfVectorizer(token_pattern=r"(?u)\b\w+\b",ngram_range=(1,2),stop_words=stopwords,max_features=40000)
sparse_result2 = tfidf_model2.fit_transform(train_data)     # 得到tf-idf矩阵,稀疏矩阵表示法
test_sparse_result2 = tfidf_model2.transform(test_data) 
mnb_count2 = MultinomialNB()
mnb_count2.fit(sparse_result2, train_label)   # 学习
mnb_count_y_predict2 = mnb_count2.predict(test_sparse_result2)   # 预测
classification_report(mnb_count_y_predict2, test_label)
复制代码

结果:

复制代码
              precision    recall  f1-score   support

           0       0.00      0.00      0.00         0
           1       0.82      0.88      0.85       958
          10       0.00      0.00      0.00         0
          11       0.00      0.00      0.00         0
          12       0.89      0.91      0.90      1190
          13       0.59      0.98      0.73       384
          14       0.00      0.00      0.00         0
          15       0.00      0.00      0.00         0
          16       0.95      0.76      0.84      2019
          17       0.00      0.00      0.00         0
          18       0.00      0.00      0.00         0
          19       0.21      0.96      0.35       103
           2       0.00      0.00      0.00         0
           3       0.94      0.76      0.84      1545
           4       0.00      0.00      0.00         0
           5       0.89      0.77      0.83      1184
           6       0.97      0.85      0.91      1548
           7       0.00      0.00      0.00         0
           8       0.00      0.00      0.00         0
           9       0.92      0.75      0.83       901

    accuracy                           0.82      9832
   macro avg       0.36      0.38      0.35      9832
weighted avg       0.90      0.82      0.85      9832
复制代码

确实是有很大的提升,至此,本文就结束了,接下来准备捣鼓捣鼓词嵌入以及深度学习的一些网络啦。

 

参考:

https://blog.csdn.net/blmoistawinde/article/details/80816179

https://blog.csdn.net/yuyanyanyanyanyu/article/details/84026485

SpringCloud Gateway与k8s_zhangjunli的博客-CSDN博客

$
0
0

本文大纲

接下来的内容由以下几部分组成:

  1. 什么是SpringCloud Gateway
  2. SpringCloud Gateway实战参考
  3. kubernetes上的SpringCloud Gateway
  4. 实战环境信息
  5. 实战源码下载
  6. 开发webdemo
  7. 开发k8sgatewaydemo
  8. 解决权限问题
  9. 最后一个疑问

什么是SpringCloud Gateway

SpringCloud Gateway是SpringCloud技术栈下的网关服务框架,在基于SpringCloud的微服务环境中,外部请求会到达SpringCloud Gateway应用,该应用对请求做转发、过滤、鉴权、熔断等前置操作,一个典型的请求响应流程如下所示:

 

SpringCloud Gateway实战参考

如果您之前没有使用过SpringCloud Gateway,推荐您阅读 《速体验SpringCloud Gateway》,有时间的话动手实战效果更佳,只需编写少量代码就能快速熟悉这个SpringCloud技术栈中非常重要的功能;

kubernetes上的SpringCloud Gateway

注意以下两个知识点:

  1. SpringCloud Gateway之所以能将外部请求路由到正确的后台服务上,是因为注册中心的存在,SpringCloud Gateway可以在注册中心取得所有服务的信息,因此它可以根据路径和服务的对应关系,将请求转发到对应的服务上;
  2. 如果您看过本系列的上一篇 《spring-cloud-kubernetes的服务发现和轮询实战(含熔断)》,您就知道spring-cloud-kubernetes框架可以获取kubernetes环境内的所有服务(这里说的服务就是kubernetes的service);

将以上两个知识点结合起来,于是可以推测:运行在kubernetes环境的SpringCloud Gateway应用,如果使用了spring-cloud-kubernetes框架就能得到kubernetes的service列表,因此可以承担网关的角色,将外部请求转发至kubernetes内的service上,最终到达对应的Pod;

架构如下图所示,请注意黄色背景的对话框,里面标识了关键操作:

 

至此,理论分析已经完成,我们来实战验证这个理论,接下来我们开发两个java应用:

  1. 先开发一个普通的web服务,名为webdemo,提供一个http接口;
  2. 再开发一个SpringCloud Gateway应用,名为k8sgatewaydemo;

环境信息

本次实战的环境和版本信息如下:

  1. 操作系统:CentOS Linux release 7.6.1810
  2. minikube:1.1.1
  3. Java:1.8.0_191
  4. Maven:3.6.0
  5. fabric8-maven-plugin插件:3.5.37
  6. spring-cloud-kubernetes:1.0.1.RELEASE
  7. spring cloud:Greenwich.SR2
  8. springboot:2.1.6.RELEASE

源码下载

如果您不打算写代码,也可以从GitHub上下载本次实战的源码,地址和链接信息如下表所示:

名称

链接

备注

项目主页

https://github.com/zq2599/blog_demos

 

该项目在GitHub上的主页

git仓库地址(https)

https://github.com/zq2599/blog_demos.git

该项目源码的仓库地址,https协议

git仓库地址(ssh)

git@github.com:zq2599/blog_demos.git

该项目源码的仓库地址,ssh协议

这个git项目中有多个文件夹,本章的两个应用分别在webdemo和k8sgatewaydemo文件夹下; 下图红框中是webdemo应用的源码:

下图红框中是k8sgatewaydemo应用的源码:

下面是详细的编码过程;

开发webdemo

webdemo是个极其普通的spring boot应用,和SpringCloud没有任何关系;

  1. webdemo提供一个http接口,将请求header中名为extendtag的参数返回给请求方,controller类如下:
@RestController
@RequestMapping("/hello")
public class HelloController {

    @RequestMapping(value = "time", method = RequestMethod.GET)
    public String hello(HttpServletRequest request){

        return "hello, "
                + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())
                + ", extendtag ["
                + request.getHeader("extendtag")
                + "]";
    }
}
  1. 启动类WebdemoApplication.java:
@SpringBootApplication
public class WebdemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(WebdemoApplication.class, args);
    }
}
  1. 要注意的是pom.xml,里面通过名为fabric8-maven-plugin的maven插件,将webdemo快速部署到minikube环境:
<?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><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.1.6.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.bolingcavalry</groupId><artifactId>webdemo</artifactId><version>0.0.1-SNAPSHOT</version><name>webdemo</name><description>Demo project for Spring Boot</description><properties><java.version>1.8</java.version><spring-boot.version>2.1.6.RELEASE</spring-boot.version><fabric8.maven.plugin.version>3.5.37</fabric8.maven.plugin.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><version>${spring-boot.version}</version><executions><execution><goals><goal>repackage</goal></goals></execution></executions></plugin><plugin><!--skip deploy --><groupId>org.apache.maven.plugins</groupId><artifactId>maven-deploy-plugin</artifactId><version>${maven-deploy-plugin.version}</version><configuration><skip>true</skip></configuration></plugin><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-surefire-plugin</artifactId><version>${maven-surefire-plugin.version}</version><configuration><skipTests>true</skipTests><!-- Workaround for https://issues.apache.org/jira/browse/SUREFIRE-1588 --><useSystemClassLoader>false</useSystemClassLoader></configuration></plugin><plugin><groupId>io.fabric8</groupId><artifactId>fabric8-maven-plugin</artifactId><version>${fabric8.maven.plugin.version}</version><executions><execution><id>fmp</id><goals><goal>resource</goal></goals></execution></executions></plugin></plugins></build><profiles><profile><id>kubernetes</id><build><plugins><plugin><groupId>io.fabric8</groupId><artifactId>fabric8-maven-plugin</artifactId><version>${fabric8.maven.plugin.version}</version><executions><execution><id>fmp</id><goals><goal>resource</goal><goal>build</goal></goals></execution></executions><configuration><enricher><config><fmp-service><type>NodePort</type></fmp-service></config></enricher></configuration></plugin></plugins></build></profile></profiles></project>
  1. 以上就是webdemo应用的内容了,接下来要编译、构建、部署到minikube环境,在pom.xml执行以下命令即可:
mvn clean install fabric8:deploy -Dfabric8.generator.from=fabric8/java-jboss-openjdk8-jdk -Pkubernetes

部署完成后终端输出类似如下成功信息:

[INFO] 
[INFO] <<< fabric8-maven-plugin:3.5.37:deploy (default-cli) < install @ webdemo <<<
[INFO] 
[INFO] 
[INFO] --- fabric8-maven-plugin:3.5.37:deploy (default-cli) @ webdemo ---
[INFO] F8: Using Kubernetes at https://192.168.121.133:8443/ in namespace default with manifest /usr/local/work/k8s/webdemo/target/classes/META-INF/fabric8/kubernetes.yml 
[INFO] Using namespace: default
[INFO] Updating a Service from kubernetes.yml
[INFO] Updated Service: target/fabric8/applyJson/default/service-webdemo.json
[INFO] Using namespace: default
[INFO] Updating Deployment from kubernetes.yml
[INFO] Updated Deployment: target/fabric8/applyJson/default/deployment-webdemo.json
[INFO] F8: HINT: Use the command `kubectl get pods -w` to watch your pods start up
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  11.804 s
[INFO] Finished at: 2019-07-07T21:32:26+08:00
[INFO] ------------------------------------------------------------------------
  1. 查看service和pod,确认一切正常:
[root@minikube webdemo]# kubectl get service
NAME             TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
kubernetes       ClusterIP   10.96.0.1       <none>        443/TCP          29d
webdemo          NodePort    10.106.98.137   <none>        8080:30160/TCP   115m
[root@minikube webdemo]# kubectl get pod
NAME                              READY   STATUS    RESTARTS   AGE
webdemo-c9f774b9-gsbgx            1/1     Running   0          3m13s
  1. 使用minikube命令取得webdemo服务对外暴露的地址:
[root@minikube webdemo]# minikube service webdemo --url
http://192.168.121.133:30160

可见外部通过地址: http://192.168.121.133:30160即可访问到webdemo应用; 7. 在浏览器输入地址: http://192.168.121.133:30160/hello/time,即可验证webdemo的http接口是否正常,如下图,由于header中没有extendtag属性,因此返回的extendtag为null:

至此,webdemo在minikue上已经正常运行,该开发gateway应用了;

开发k8sgatewaydemo

  1. 基于maven创建一个名为k8sgatewaydemo的springboot应用,pom.xml内容如下:
<?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><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.1.6.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.bolingcavalry</groupId><artifactId>k8sgatewaydemo</artifactId><version>0.0.1-SNAPSHOT</version><name>k8sgatewaydemo</name><description>Demo project for Spring Boot</description><properties><java.version>1.8</java.version><spring-boot.version>2.1.6.RELEASE</spring-boot.version><maven-checkstyle-plugin.failsOnError>false</maven-checkstyle-plugin.failsOnError><maven-checkstyle-plugin.failsOnViolation>false</maven-checkstyle-plugin.failsOnViolation><maven-checkstyle-plugin.includeTestSourceDirectory>false</maven-checkstyle-plugin.includeTestSourceDirectory><maven-compiler-plugin.version>3.5</maven-compiler-plugin.version><maven-deploy-plugin.version>2.8.2</maven-deploy-plugin.version><maven-failsafe-plugin.version>2.18.1</maven-failsafe-plugin.version><maven-surefire-plugin.version>2.21.0</maven-surefire-plugin.version><fabric8.maven.plugin.version>3.5.37</fabric8.maven.plugin.version><springcloud.kubernetes.version>1.0.1.RELEASE</springcloud.kubernetes.version><spring-cloud.version>Greenwich.SR2</spring-cloud.version></properties><dependencies><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-kubernetes-core</artifactId><version>${springcloud.kubernetes.version}</version></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-kubernetes-discovery</artifactId><version>${springcloud.kubernetes.version}</version></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-kubernetes-ribbon</artifactId><version>${springcloud.kubernetes.version}</version></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-commons</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-ribbon</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-hystrix</artifactId></dependency></dependencies><dependencyManagement><dependencies><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>${spring-cloud.version}</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><version>${spring-boot.version}</version><executions><execution><goals><goal>repackage</goal></goals></execution></executions></plugin><plugin><!--skip deploy --><groupId>org.apache.maven.plugins</groupId><artifactId>maven-deploy-plugin</artifactId><version>${maven-deploy-plugin.version}</version><configuration><skip>true</skip></configuration></plugin><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-surefire-plugin</artifactId><version>${maven-surefire-plugin.version}</version><configuration><skipTests>true</skipTests><!-- Workaround for https://issues.apache.org/jira/browse/SUREFIRE-1588 --><useSystemClassLoader>false</useSystemClassLoader></configuration></plugin><plugin><groupId>io.fabric8</groupId><artifactId>fabric8-maven-plugin</artifactId><version>${fabric8.maven.plugin.version}</version><executions><execution><id>fmp</id><goals><goal>resource</goal></goals></execution></executions></plugin></plugins></build><profiles><profile><id>kubernetes</id><build><plugins><plugin><groupId>io.fabric8</groupId><artifactId>fabric8-maven-plugin</artifactId><version>${fabric8.maven.plugin.version}</version><executions><execution><id>fmp</id><goals><goal>resource</goal><goal>build</goal></goals></execution></executions><configuration><enricher><config><fmp-service><type>NodePort</type></fmp-service></config></enricher></configuration></plugin></plugins></build></profile></profiles></project>

上述pom文件中有以下几点需要注意: 第一、 依赖spring-cloud-kubernetes-core和spring-cloud-kubernetes-discovery,这样能用到spring-cloud-kubernetes提供的服务发现能力; 第二、依赖spring-cloud-starter-gateway,这样能用上SpringCloud的gateway能力; 第三、不要依赖spring-boot-starter-web,会和spring-cloud-starter-gateway冲突,启动时抛出以下异常:

2019-07-06 08:12:09.188  WARN 1 --- [           main] ConfigServletWebServerApplicationContext : 
Exception encountered during context initialization - 
cancelling refresh attempt: 
org.springframework.beans.factory.UnsatisfiedDependencyException: 
Error creating bean with name 'routeDefinitionRouteLocator' defined in class path resource [org/springframework/cloud/gateway/config/GatewayAutoConfiguration.class]: 
Unsatisfied dependency expressed through method 'routeDefinitionRouteLocator' parameter 1; 
nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: 
Error creating bean with name 'modifyRequestBodyGatewayFilterFactory' defined in class path resource [org/springframework/cloud/gateway/config/GatewayAutoConfiguration.class]: 
Unsatisfied dependency expressed through method 'modifyRequestBodyGatewayFilterFactory' parameter 0; 
nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.http.codec.ServerCodecConfigurer' available: 
expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
  1. 开发SpringCloud Gateway的启动类K8sgatewaydemoApplication.java,里面也包含了网关路由配置的实例化,除了配置路径和转发服务的关系,还在请求的header中添加了extendtag属性,请注意注释的内容:
@SpringBootApplication
@EnableDiscoveryClient
public class K8sgatewaydemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(K8sgatewaydemoApplication.class, args);
    }

    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
        return builder.routes()
                //增加一个path匹配,以"/customize/hello/"开头的请求都在此路由
                .route(r -> r.path("/customize/hello/**")
                        //表示将路径中的第一级参数删除,用剩下的路径与webdemo的路径做拼接,
                        //这里就是"lb://webdemo/hello/",能匹配到webdemo的HelloController的路径
                        .filters(f -> f.stripPrefix(1)
                                //在请求的header中添加一个key&value
                                .addRequestHeader("extendtag", "geteway-" + System.currentTimeMillis()))
                        //指定匹配服务webdemo,lb是load balance的意思
                        .uri("lb://webdemo")
                ).build();
    }
}

从上述代码可见,K8sgatewaydemoApplication与普通环境下的SpringCloud Gateway并无差别,都是通过EnableDiscoveryClient注解获取服务列表,配置RouteLocator实现路由逻辑; 3. 配置文件application.yml的内容:

spring:
  application:
    name: gateway
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true
          lowerCaseServiceId: true
  1. 以上就是k8sgatewaydemo应用的内容了,接下来要编译、构建、部署到minikube环境,在pom.xml执行以下命令即可:
mvn clean install fabric8:deploy -Dfabric8.generator.from=fabric8/java-jboss-openjdk8-jdk -Pkubernetes

部署完成后终端输出类似如下成功信息:

[INFO] 
[INFO] <<< fabric8-maven-plugin:3.5.37:deploy (default-cli) < install @ k8sgatewaydemo <<<
[INFO] 
[INFO] 
[INFO] --- fabric8-maven-plugin:3.5.37:deploy (default-cli) @ k8sgatewaydemo ---
[INFO] F8: Using Kubernetes at https://192.168.121.133:8443/ in namespace default with manifest /usr/local/work/k8s/k8sgatewaydemo/target/classes/META-INF/fabric8/kubernetes.yml 
[INFO] Using namespace: default
[INFO] Updating a Service from kubernetes.yml
[INFO] Updated Service: target/fabric8/applyJson/default/service-k8sgatewaydemo.json
[INFO] Using namespace: default
[INFO] Updating Deployment from kubernetes.yml
[INFO] Updated Deployment: target/fabric8/applyJson/default/deployment-k8sgatewaydemo.json
[INFO] F8: HINT: Use the command `kubectl get pods -w` to watch your pods start up
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  16.538 s
[INFO] Finished at: 2019-07-07T22:04:48+08:00
[INFO] ------------------------------------------------------------------------
  1. 查看service和pod,确认一切正常:
[root@minikube k8sgatewaydemo]# clear
[root@minikube k8sgatewaydemo]# kubectl get service
NAME             TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
k8sgatewaydemo   NodePort    10.97.94.238    <none>        8080:31352/TCP   129m
kubernetes       ClusterIP   10.96.0.1       <none>        443/TCP          29d
webdemo          NodePort    10.106.98.137   <none>        8080:30160/TCP   145m
[root@minikube k8sgatewaydemo]# kubectl get pod
NAME                              READY   STATUS    RESTARTS   AGE
k8sgatewaydemo-6fbb79885c-r2jfn   1/1     Running   0          33s
webdemo-c9f774b9-gsbgx            1/1     Running   0          32m
  1. 使用minikube命令取得webdemo服务对外暴露的地址:
[root@minikube k8sgatewaydemo]# minikube service k8sgatewaydemo --url
http://192.168.121.133:31352

可见外部通过地址: http://192.168.121.133:31352即可访问到k8sgatewaydemo应用; 7. 在浏览器输入地址: http://192.168.121.133:31352/customize/hello/time,即可验证k8sgatewaydemo作为网关应用,能否将路径中带有customize的请求转发到webdemo应用,并且在请求header中添加名为entendtag的属性,如下图,浏览器展示的内容是webdemo的http接口返回的,并且extendtag的内容也不为空了,而是k8sgatewaydemo在转发前写入的:

上述结果表明已可以证明我们之前的推测是正确的:SpringCloud Gateway应用在使用了spring-cloud-kubernetes提供的注册发现能力后,可以将请求转发到kubernetes环境中的服务上; 也就是说,借助spring-cloud-kubernetes框架,你在SpringCloud环境开发的SpringCloud Gateway应用,可以以很小的代价迁移到kubernetes环境,与kubernetes环境中的service可以很好的交互,而原有的eureka注册中心也可以不用了;

解决权限问题

如果您的spring-cloud-kubernetes在向webdemo转发请求时抛出以下错误,那是因为遇到了kubernetes的权限问题:

2019-07-06 04:46:40.042  WARN 1 --- [erListUpdater-1] c.n.l.PollingServerListUpdater           : Failed one update cycle
io.fabric8.kubernetes.client.KubernetesClientException: Failure executing: GET at: https://10.96.0.1/api/v1/namespaces/default/endpoints/account-service. Message: Forbidden!Configured service account doesn't have access. Service account may have been revoked. endpoints "account-service" is forbidden: User "system:serviceaccount:default:default" cannot get resource "endpoints" in API group "" in the namespace "default".
        at io.fabric8.kubernetes.client.dsl.base.OperationSupport.requestFailure(OperationSupport.java:476) ~[kubernetes-client-4.1.0.jar!/:na]
        at io.fabric8.kubernetes.client.dsl.base.OperationSupport.assertResponseCode(OperationSupport.java:413) ~[kubernetes-client-4.1.0.jar!/:na]
        at io.fabric8.kubernetes.client.dsl.base.OperationSupport.handleResponse(OperationSupport.java:381) ~[kubernetes-client-4.1.0.jar!/:na]
        at io.fabric8.kubernetes.client.dsl.base.OperationSupport.handleResponse(OperationSupport.java:344) ~[kubernetes-client-4.1.0.jar!/:na]
        at io.fabric8.kubernetes.client.dsl.base.OperationSupport.handleGet(OperationSupport.java:313) ~[kubernetes-client-4.1.0.jar!/:na]
        at io.fabric8.kubernetes.client.dsl.base.OperationSupport.handleGet(OperationSupport.java:296) ~[kubernetes-client-4.1.0.jar!/:na]
        at io.fabric8.kubernetes.client.dsl.base.BaseOperation.handleGet(BaseOperation.java:794) ~[kubernetes-client-4.1.0.jar!/:na]
        at io.fabric8.kubernetes.client.dsl.base.BaseOperation.getMandatory(BaseOperation.java:210) ~[kubernetes-client-4.1.0.jar!/:na]


        at io.fabric8.kubernetes.client.dsl.base.BaseOperation.get(BaseOperation.java:177) ~[kubernetes-client-4.1.0.jar!/:na]
        at org.springframework.cloud.kubernetes.ribbon.KubernetesServerList


.getUpdatedListOfServers(KubernetesServerList.java:75) ~[spring-cloud-kubernetes-ribbon-1.0.1.RELEASE.jar!/:1.0.1.RELEASE]
        at com.netflix.loadbalancer.DynamicServerListLoadBalancer.updateListOfServers(DynamicServerListLoadBalancer.java:240) ~[ribbon-loadbalancer-2.3.0.jar!/:2.3.0]
        at com.netflix.loadbalancer.DynamicServerListLoadBalancer$1.doUpdate(DynamicServerListLoadBalancer.java:62) ~[ribbon-loadbalancer-2.3.0.jar!/:2.3.0]
        at com.netflix.loadbalancer.PollingServerListUpdater$1.run(PollingServerListUpdater.java:116) ~[ribbon-loadbalancer-2.3.0.jar!/:2.3.0]
        at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) [na:1.8.0_191]
        at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:308) [na:1.8.0_191]
        at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$301(ScheduledThreadPoolExecutor.java:180) [na:1.8.0_191]
        at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:294) [na:1.8.0_191]
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [na:1.8.0_191]
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [na:1.8.0_191]
        at java.lang.Thread.run(Thread.java:748) [na:1.8.0_191]

处理方法是创建ServiceAccount对象,步骤如下:

  1. 创建名为fabric8-rbac.yaml的文件,内容如下:
# NOTE: The service account `default:default` already exists in k8s cluster.
# You can create a new account following like this:
#---
#apiVersion: v1
#kind: ServiceAccount
#metadata:
#  name: <new-account-name>
#  namespace: <namespace>

---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: fabric8-rbac
subjects:
  - kind: ServiceAccount
    # Reference to upper's `metadata.name`
    name: default
    # Reference to upper's `metadata.namespace`
    namespace: default
roleRef:
  kind: ClusterRole
  name: cluster-admin
  apiGroup: rbac.authorization.k8s.io
  1. 执行以下命令即可创建ServiceAccount对象:
kubectl apply -f fabric8-rbac.yaml
  1. 再在浏览器上继续刚才的验证,可以操作成功;

最后一个疑问

再回顾一下k8sgatewaydemo的开发过程,您会发现除了依赖spring-cloud-kubernetes对应的maven库,我们并没有显式调用spring-cloud-kubernetes相关的API或者做相关配置,就获取了所在kubernetes环境的原生服务,这是怎么回事呢?为何成本如此的低? 答案就在 《spring-cloud-kubernetes背后的三个关键知识点》一文中,推荐您回顾一下此文。

至此,spring-cloud-kubernetes框架下的SpringCloud Gateway开发实战就完成了,希望本文能帮助您更好的理解和使用spring-cloud-kubernetes,更加高效的将应用向容器化迁移。

spring-cloud-kubernetes的服务发现和轮询实战(含熔断)_程序员欣宸的博客-CSDN博客

$
0
0

本文是《spring-cloud-kubernetes实战系列》的第四篇,主要内容是在kubernetes上部署两个应用:Web-Service和Account-Service,通过spring-cloud-kubernetes提供的注册发现能力,实现Web-Service调用Account-Service提供的http服务;

系列文章列表

  1. 《spring-cloud-kubernetes官方demo运行实战》
  2. 《你好spring-cloud-kubernetes》
  3. 《spring-cloud-kubernetes背后的三个关键知识点》
  4. 《spring-cloud-kubernetes的服务发现和轮询实战(含熔断)》
  5. 《spring-cloud-kubernetes与SpringCloud Gateway》
  6. 《spring-cloud-kubernetes与k8s的configmap》

全文概览

本文由以下段落组成:

  1. 环境信息
  2. 常见的SpringCloud注册发现服务一览
  3. 分析kubernetes上如何实现服务注册发现
  4. 本章实战源码下载链接
  5. 实战开发Account-Service服务(服务提供方)
  6. 实战开发Web-Service服务(服务消费方)
  7. 扩容验证ribbon轮询能力
  8. 验证熔断能力

环境信息

本次实战的环境和版本信息如下:

  1. 操作系统:CentOS Linux release 7.6.1810
  2. minikube:1.1.1
  3. Java:1.8.0_191
  4. Maven:3.6.0
  5. fabric8-maven-plugin插件:3.5.37
  6. spring-cloud-kubernetes:1.0.1.RELEASE

上面的linux、minikube、java、maven,请确保已准备好,linux环境下minikube的安装和启动请参考 《Linux安装minikube指南 》

常见的SpringCloud注册发现服务一览

SpringCloud环境最重要的功能是注册发现服务,因此将SpringCloud应用迁移到kubernetes环境时,开发者最关心的问题是在kubernetes上如何将自身服务暴露出去,以及如何调用其他微服务。

先看看普通SpringCloud环境下的注册发现,下图来自spring官方博客,地址是: https://spring.io/blog/2015/07/14/microservices-with-spring,
在这里插入图片描述

由上图可见,应用Account-Service将自己注册到Eureka,这样Web-Service用"account-service"就能在Eureka找到Account-Service服务的地址,然后顺利发送RestFul请求到Account-Service,用上其提供的服务。

分析kubernetes上如何实现服务注册发现

如果将上面的Web-Service和Account-Service两个应用迁移到kubernetes上之后,注册发现机制变成了啥样呢?
第一种:沿用上图的方式,将Eureka也部署在kubernetes上,这样的架构和不用kubernetes时没有啥区别;
第二种,就是今天要实战的内容,使用spring-cloud-kubernetes框架,该框架可以调用kubernetes的原生能力来为现有SpringCloud应用提供服务,架构如下图所示:
在这里插入图片描述
上图表明,Web-Service应用在调用Account-Service应用的服务时,会用okhttp向API Server请求服务列表,API Server收到请求后会去etcd取数据返回给Web-Service应用,这样Web-Service就有了Account-Service的信息,可以向Account-Service的多个Pod轮询发起请求;

上图有个细节请注意:WebService应用并不是直接将请求发送给Account-Service在kubernetes创建的service,而是直接发送到具体的Pod上了,之所以具有这个能力,是因为spring-cloud-kubernetes框架通过service拿到了Account-Service对应的所有Pod信息(endpoint),此逻辑可以参考源码KubernetesServerList.java,如下所示:

publicList<Server>getUpdatedListOfServers(){//用namespace和serviceId做条件,得到该服务对应的所有节点(endpoints)信息Endpoints endpoints=this.namespace!=null?this.client.endpoints().inNamespace(this.namespace).withName(this.serviceId).get():this.client.endpoints().withName(this.serviceId).get();List<Server>result=newArrayList<Server>();if(endpoints!=null){if(LOG.isDebugEnabled()){LOG.debug("Found ["+endpoints.getSubsets().size()+"] endpoints in namespace ["+this.namespace+"] for name ["+this.serviceId+"] and portName ["+this.portName+"]");}//遍历所有的endpoint,取出IP地址和端口,构建成Server实例,放入result集合中for(EndpointSubset subset:endpoints.getSubsets()){if(subset.getPorts().size()==1){EndpointPort port=subset.getPorts().get(FIRST);for(EndpointAddress address:subset.getAddresses()){result.add(newServer(address.getIp(),port.getPort()));}}else{for(EndpointPort port:subset.getPorts()){if(Utils.isNullOrEmpty(this.portName)||this.portName.endsWith(port.getName())){for(EndpointAddress address:subset.getAddresses()){result.add(newServer(address.getIp(),port.getPort()));}}}}}}else{LOG.warn("Did not find any endpoints in ribbon in namespace ["+this.namespace+"] for name ["+this.serviceId+"] and portName ["+this.portName+"]");}returnresult;}

理论分析已经完成,接下来就开始实战吧

源码下载

如果您不打算写代码,也可以从GitHub上下载本次实战的源码,地址和链接信息如下表所示:

名称链接备注
项目主页https://github.com/zq2599/blog_demos该项目在GitHub上的主页
git仓库地址(https)https://github.com/zq2599/blog_demos.git该项目源码的仓库地址,https协议
git仓库地址(ssh)git@github.com:zq2599/blog_demos.git该项目源码的仓库地址,ssh协议

这个git项目中有多个文件夹,本章的Account-Service源码在spring-cloud-k8s-account-service文件夹下,Web-Service源码在spring-cloud-k8s-web-service文件夹下,如下图红框所示:
在这里插入图片描述
下面是详细的编码过程;

开发和部署Account-Service服务

Account-Service服务是个很普通的springboot应用,和spring-cloud-kubernetes没有任何关系:

  1. 通过maven创建一个springboot应用,artifactId是account-service,pom.xml内容如下:
<?xml version="1.0" encoding="UTF-8"?><projectxmlns="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><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.1.1.RELEASE</version><relativePath/><!-- lookup parent from repository --></parent><groupId>com.bolingcavalry</groupId><artifactId>account-service</artifactId><version>0.0.1-SNAPSHOT</version><name>account-service</name><description>Demo project for Spring Cloud service provider run in kubernetes</description><properties><java.version>1.8</java.version><spring-boot.version>2.1.1.RELEASE</spring-boot.version><maven-checkstyle-plugin.failsOnError>false</maven-checkstyle-plugin.failsOnError><maven-checkstyle-plugin.failsOnViolation>false</maven-checkstyle-plugin.failsOnViolation><maven-checkstyle-plugin.includeTestSourceDirectory>false</maven-checkstyle-plugin.includeTestSourceDirectory><maven-compiler-plugin.version>3.5</maven-compiler-plugin.version><maven-deploy-plugin.version>2.8.2</maven-deploy-plugin.version><maven-failsafe-plugin.version>2.18.1</maven-failsafe-plugin.version><maven-surefire-plugin.version>2.21.0</maven-surefire-plugin.version><fabric8.maven.plugin.version>3.5.37</fabric8.maven.plugin.version><springcloud.version>2.1.1.RELEASE</springcloud.version></properties><dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-dependencies</artifactId><type>pom</type><scope>import</scope><version>${spring-boot.version}</version></dependency></dependencies></dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId><version>${springcloud.version}</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><version>${springcloud.version}</version></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><version>${spring-boot.version}</version><executions><execution><goals><goal>repackage</goal></goals></execution></executions></plugin><plugin><!--skip deploy --><groupId>org.apache.maven.plugins</groupId><artifactId>maven-deploy-plugin</artifactId><version>${maven-deploy-plugin.version}</version><configuration><skip>true</skip></configuration></plugin><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-surefire-plugin</artifactId><version>${maven-surefire-plugin.version}</version><configuration><skipTests>true</skipTests><!-- Workaround for https://issues.apache.org/jira/browse/SUREFIRE-1588 --><useSystemClassLoader>false</useSystemClassLoader></configuration></plugin><plugin><groupId>io.fabric8</groupId><artifactId>fabric8-maven-plugin</artifactId><version>${fabric8.maven.plugin.version}</version><executions><execution><id>fmp</id><goals><goal>resource</goal></goals></execution></executions></plugin></plugins></build><profiles><profile><id>kubernetes</id><build><plugins><plugin><groupId>io.fabric8</groupId><artifactId>fabric8-maven-plugin</artifactId><version>${fabric8.maven.plugin.version}</version><executions><execution><id>fmp</id><goals><goal>resource</goal><goal>build</goal></goals></execution></executions><configuration><enricher><config><fmp-service><type>NodePort</type></fmp-service></config></enricher></configuration></plugin></plugins></build></profile></profiles></project>

由上面的pom.xml内容可见,account-service应用是个简单的web应用,和SpringCloud、spring-cloud-kubernetes都没有任何关系,和其他springboot唯一的不同就是用到了fabric8-maven-plugin插件,可以方便的将应用部署到kubernetes环境;

  1. application.yml内容如下,依旧很简单:
spring:application:name:account-serviceserver:port:8080
  1. 对外提供服务的是AccountController ,方法getName返回了当前容器的hostname,方法health用于响应kubernetes的两个探针,方法ribbonPing用于响应使用了ribbon服务的调用方,它们会调用这个接口来确定当前服务是否正常:
@RestControllerpublicclassAccountController{privatestaticfinalLogger LOG=LoggerFactory.getLogger(AccountController.class);privatefinalString hostName=System.getenv("HOSTNAME");/**
     * 探针检查响应类
     * @return
     */@RequestMapping("/health")publicStringhealth(){return"OK";}@RequestMapping("/")publicStringribbonPing(){LOG.info("ribbonPing of {}",hostName);returnhostName;}/**
     * 返回hostname
     * @return 当前应用所在容器的hostname.
     */@RequestMapping("/name")publicStringgetName(){returnthis.hostName+", "+newSimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(newDate());}}
  1. 将上述工程的源码放在minikube机器上,确保maven设置正常,然后在pom.xml文件所在目录执行以下命令,即可编译构建工程并部署到kubernetes上:
mvn cleaninstallfabric8:deploy -Dfabric8.generator.from=fabric8/java-jboss-openjdk8-jdk -Pkubernetes

执行成功后控制台输出如下:

...[INFO]Installing /usr/local/work/k8s/ribbon/spring-cloud-k8s-account-service/target/classes/META-INF/fabric8/kubernetes.json to /root/.m2/repository/com/bolingcavalry/account-service/0.0.1-SNAPSHOT/account-service-0.0.1-SNAPSHOT-kubernetes.json[INFO][INFO]<<<fabric8-maven-plugin:3.5.37:deploy(default-cli)<install@ account-service<<<[INFO][INFO][INFO]--- fabric8-maven-plugin:3.5.37:deploy(default-cli)@ account-service ---[INFO]F8: Using Kubernetes at https://192.168.121.133:8443/innamespace default with manifest /usr/local/work/k8s/ribbon/spring-cloud-k8s-account-service/target/classes/META-INF/fabric8/kubernetes.yml[INFO]Using namespace: default[INFO]Updating a Service from kubernetes.yml[INFO]Updated Service: target/fabric8/applyJson/default/service-account-service.json[INFO]Using namespace: default[INFO]Updating Deployment from kubernetes.yml[INFO]Updated Deployment: target/fabric8/applyJson/default/deployment-account-service.json[INFO]F8: HINT: Use thecommand`kubectl get pods -w`towatchyour pods start up[INFO]------------------------------------------------------------------------[INFO]BUILD SUCCESS[INFO]------------------------------------------------------------------------[INFO]Total time:  11.941 s[INFO]Finished at: 2019-06-16T19:00:51+08:00[INFO]------------------------------------------------------------------------
  1. 检查kubernetes上的部署和服务是否正常:
[root@minikube spring-cloud-k8s-account-service]# kubectl get deploymentsNAME              READY   UP-TO-DATE   AVAILABLE   AGE
account-service   1/1     1            1           69m[root@minikube spring-cloud-k8s-account-service]# kubectl get servicesNAME              TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)AGE
account-service   NodePort    10.105.157.201<none>8080:32596/TCP   69m
kubernetes        ClusterIP   10.96.0.1<none>443/TCP          8d
  1. minikube的service命令可以得到指定服务的访问地址:
[root@minikube spring-cloud-k8s-account-service]# minikube service account-service --urlhttp://192.168.121.133:32596

可见account-service的服务可以通过这个url访问: http://192.168.121.133:32596

  1. 用浏览器访问地址: http://192.168.121.133:32596/name,如下图所示,可以正常访问account-service提供的服务:
    在这里插入图片描述
    现在account-service服务已经就绪,接下来是开发和部署web-service应用。

开发和部署Web-Service服务

Web-Service服务是个springboot应用,用到了spring-cloud-kubernetes提供的注册发现能力,以轮询的方式访问指定服务的全部pod:

  1. 通过maven创建一个springboot应用,artifactId是web-service,pom.xml内容如下,要重点关注的是spring-cloud-starter-kubernetes-ribbon的依赖:
<?xml version="1.0" encoding="UTF-8"?><projectxmlns="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><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.1.1.RELEASE</version><relativePath/><!-- lookup parent from repository --></parent><groupId>com.bolingcavalry</groupId><artifactId>web-service</artifactId><version>0.0.1-SNAPSHOT</version><name>web-service</name><description>Demo project for Spring Cloud service consumer run in kubernetes</description><properties><java.version>1.8</java.version><spring-boot.version>2.1.1.RELEASE</spring-boot.version><maven-checkstyle-plugin.failsOnError>false</maven-checkstyle-plugin.failsOnError><maven-checkstyle-plugin.failsOnViolation>false</maven-checkstyle-plugin.failsOnViolation><maven-checkstyle-plugin.includeTestSourceDirectory>false</maven-checkstyle-plugin.includeTestSourceDirectory><maven-compiler-plugin.version>3.5</maven-compiler-plugin.version><maven-deploy-plugin.version>2.8.2</maven-deploy-plugin.version><maven-failsafe-plugin.version>2.18.1</maven-failsafe-plugin.version><maven-surefire-plugin.version>2.21.0</maven-surefire-plugin.version><fabric8.maven.plugin.version>3.5.37</fabric8.maven.plugin.version><springcloud.kubernetes.version>1.0.1.RELEASE</springcloud.kubernetes.version><springcloud.version>2.1.1.RELEASE</springcloud.version></properties><dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-dependencies</artifactId><type>pom</type><scope>import</scope><version>${spring-boot.version}</version></dependency></dependencies></dependencyManagement><dependencies><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-kubernetes-core</artifactId><version>${springcloud.kubernetes.version}</version></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-kubernetes-discovery</artifactId><version>${springcloud.kubernetes.version}</version></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-kubernetes-ribbon</artifactId><version>${springcloud.kubernetes.version}</version></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-commons</artifactId><version>${springcloud.version}</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId><version>${springcloud.version}</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><version>${springcloud.version}</version></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-ribbon</artifactId><version>${springcloud.version}</version></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-hystrix</artifactId><version>${springcloud.version}</version></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><version>${spring-boot.version}</version><executions><execution><goals><goal>repackage</goal></goals></execution></executions></plugin><plugin><!--skip deploy --><groupId>org.apache.maven.plugins</groupId><artifactId>maven-deploy-plugin</artifactId><version>${maven-deploy-plugin.version}</version><configuration><skip>true</skip></configuration></plugin><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-surefire-plugin</artifactId><version>${maven-surefire-plugin.version}</version><configuration><skipTests>true</skipTests><!-- Workaround for https://issues.apache.org/jira/browse/SUREFIRE-1588 --><useSystemClassLoader>false</useSystemClassLoader></configuration></plugin><plugin><groupId>io.fabric8</groupId><artifactId>fabric8-maven-plugin</artifactId><version>${fabric8.maven.plugin.version}</version><executions><execution><id>fmp</id><goals><goal>resource</goal></goals></execution></executions></plugin></plugins></build><profiles><profile><id>kubernetes</id><build><plugins><plugin><groupId>io.fabric8</groupId><artifactId>fabric8-maven-plugin</artifactId><version>${fabric8.maven.plugin.version}</version><executions><execution><id>fmp</id><goals><goal>resource</goal><goal>build</goal></goals></execution></executions><configuration><enricher><config><fmp-service><type>NodePort</type></fmp-service></config></enricher></configuration></plugin></plugins></build></profile></profiles></project>
  1. application.yml的内容如下,增加了熔断的配置:
spring:application:name:web-serviceserver:port:8080backend:ribbon:eureka:enabled:falseclient:enabled:trueServerListRefreshInterval:5000hystrix.command.BackendCall.execution.isolation.thread.timeoutInMilliseconds:5000hystrix.threadpool.BackendCallThread.coreSize:5
  1. 创建一个ribbon的配置类RibbonConfiguration:
packagecom.bolingcavalry.webservice;importcom.netflix.client.config.IClientConfig;importcom.netflix.loadbalancer.AvailabilityFilteringRule;importcom.netflix.loadbalancer.IPing;importcom.netflix.loadbalancer.IRule;importcom.netflix.loadbalancer.PingUrl;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.context.annotation.Bean;/**
 * @Description: ribbon配置类
 * @author: willzhao E-mail: zq2599@gmail.com
 * @date: 2019/6/16 11:52
 */publicclassRibbonConfiguration{@AutowiredIClientConfig ribbonClientConfig;/**
     * 检查服务是否可用的实例,
     * 此地址返回的响应的返回码如果是200表示服务可用
     * @param config
     * @return
     */@BeanpublicIPingribbonPing(IClientConfig config){returnnewPingUrl();}/**
     * 轮询规则
     * @param config
     * @return
     */@BeanpublicIRuleribbonRule(IClientConfig config){returnnewAvailabilityFilteringRule();}}
  1. 应用启动类如下,注意增加了服务发现、熔断、ribbon的配置,还定义了restTemplte实例,注意@LoadBalanced注解:
packagecom.bolingcavalry.webservice;importorg.springframework.boot.SpringApplication;importorg.springframework.boot.autoconfigure.SpringBootApplication;importorg.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;importorg.springframework.cloud.client.discovery.EnableDiscoveryClient;importorg.springframework.cloud.client.loadbalancer.LoadBalanced;importorg.springframework.cloud.netflix.ribbon.RibbonClient;importorg.springframework.context.annotation.Bean;importorg.springframework.web.client.RestTemplate;@SpringBootApplication@EnableDiscoveryClient@EnableCircuitBreaker@RibbonClient(name="account-service",configuration=RibbonConfiguration.class)publicclassWebServiceApplication{publicstaticvoidmain(String[]args){SpringApplication.run(WebServiceApplication.class,args);}@LoadBalanced@BeanRestTemplaterestTemplate(){returnnewRestTemplate();}}
  1. 远程调用account-service的http接口的逻辑被放进服务类AccountService中,注意URL中用的是服务名account-service:
packagecom.bolingcavalry.webservice;importcom.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;importcom.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.stereotype.Service;importorg.springframework.web.client.RestTemplate;importjava.text.SimpleDateFormat;importjava.util.Date;/**
 * @Description: 这里面封装了远程调用account-service提供服务的逻辑
 * @author: willzhao E-mail: zq2599@gmail.com
 * @date: 2019/6/16 12:21
 */@ServicepublicclassAccountService{@AutowiredprivateRestTemplate restTemplate;@HystrixCommand(fallbackMethod="getFallbackName",commandProperties={@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="1000")})publicStringgetDataFromSpringCloudK8SProvider(){returnthis.restTemplate.getForObject("http://account-service/name",String.class);}/**
     * 熔断时调用的方法
     * @return
     */privateStringgetFallbackName(){return"Fallback"+", "+newSimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(newDate());}}
  1. 最后是响应web请求的WebServiceController类,这里面调用了AccountService的服务,这样我们从web发起请求后,web-service就会远程调用account-service的服务:
packagecom.bolingcavalry.webservice;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.web.bind.annotation.RequestMapping;importorg.springframework.web.bind.annotation.RequestParam;importorg.springframework.web.bind.annotation.RestController;/**
 * @Description: 测试用的controller,会远程调用account-service的服务
 * @author: willzhao E-mail: zq2599@gmail.com
 * @date: 2019/6/16 11:46
 */@RestControllerpublicclassWebServiceController{@AutowiredprivateAccountService accountService;/**
     * 探针检查响应类
     * @return
     */@RequestMapping("/health")publicStringhealth(){return"OK";}/**
     * 远程调用account-service提供的服务
     * @return 多次远程调返回的所有结果.
     */@RequestMapping("/account")publicStringaccount(){StringBuilder sbud=newStringBuilder();for(inti=0;i<10;i++){sbud.append(accountService.getDataFromSpringCloudK8SProvider()).append("<br>");}returnsbud.toString();}}
  1. 将上述工程的源码放在minikube机器上,确保maven设置正常,然后在pom.xml文件所在目录执行以下命令,即可编译构建工程并部署到kubernetes上:
mvn cleaninstallfabric8:deploy -Dfabric8.generator.from=fabric8/java-jboss-openjdk8-jdk -Pkubernetes

执行成功后控制台输出如下:

...[INFO]Installing /usr/local/work/k8s/ribbon/spring-cloud-k8s-web-service/target/classes/META-INF/fabric8/kubernetes.json to /root/.m2/repository/com/bolingcavalry/web-service/0.0.1-SNAPSHOT/web-service-0.0.1-SNAPSHOT-kubernetes.json[INFO][INFO]<<<fabric8-maven-plugin:3.5.37:deploy(default-cli)<install@ web-service<<<[INFO][INFO][INFO]--- fabric8-maven-plugin:3.5.37:deploy(default-cli)@ web-service ---[INFO]F8: Using Kubernetes at https://192.168.121.133:8443/innamespace default with manifest /usr/local/work/k8s/ribbon/spring-cloud-k8s-web-service/target/classes/META-INF/fabric8/kubernetes.yml[INFO]Using namespace: default[INFO]Creating a Service from kubernetes.yml namespace default name web-service[INFO]Created Service: target/fabric8/applyJson/default/service-web-service.json[INFO]Using namespace: default[INFO]Creating a Deployment from kubernetes.yml namespace default name web-service[INFO]Created Deployment: target/fabric8/applyJson/default/deployment-web-service.json[INFO]F8: HINT: Use thecommand`kubectl get pods -w`towatchyour pods start up[INFO]------------------------------------------------------------------------[INFO]BUILD SUCCESS[INFO]------------------------------------------------------------------------[INFO]Total time:  12.792 s[INFO]Finished at: 2019-06-16T19:24:21+08:00[INFO]------------------------------------------------------------------------
  1. 检查kubernetes上的部署和服务是否正常:
[root@minikube spring-cloud-k8s-web-service]# kubectl get deploymentsNAME              READY   UP-TO-DATE   AVAILABLE   AGE
account-service   1/1     1            1           109m
web-service       1/1     1            1           18m[root@minikube spring-cloud-k8s-web-service]# kubectl get svcNAME              TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)AGE
account-service   NodePort    10.105.157.201<none>8080:32596/TCP   109m
kubernetes        ClusterIP   10.96.0.1<none>443/TCP          8d
web-service       NodePort    10.99.211.179<none>8080:30519/TCP   18m
  1. minikube的service命令可以得到指定服务的访问地址:
[root@minikube spring-cloud-k8s-web-service]# minikube service web-service --urlhttp://192.168.121.133:30519

可见web-service的服务可以通过这个url访问: http://192.168.121.133:30519

  1. 用浏览器访问地址: http://192.168.121.133:30519/account,如下图所示,页面上展示的内容都是web-service调用了account-service的接口返回的,证明kubernetes上的注册发现能力正常:
    在这里插入图片描述

扩容验证ribbon轮询能力

虽然web-service可以正常调用account-service的服务,但始终访问的是一个pod,接下来我们就对account-service的pod进行扩容,将数量调整为2个,看看web-service是否可以轮询调用每个account-service的pod:

  1. 执行以下命令即可将pod数量调整为2个:
kubectl scale --replicas=2 deployment account-service
  1. 检查account-service的pod,发现已经有两个了(account-service-5554576647-m29xr和account-service-5554576647-zwwml):
[root@minikube spring-cloud-k8s-web-service]# kubectl get podsNAME                               READY   STATUS    RESTARTS   AGE
account-service-5554576647-m29xr   1/1     Running   0          53m
account-service-5554576647-zwwml   1/1     Running   0          20s
web-service-6d775855c7-7lkvr       1/1     Running   0          29m
  1. 用浏览器访问地址: http://192.168.121.133:30519/account,如下图所示,account-sercice返回的hostname已经变成了两种,和前面查到的pod的name一致,可见web-service的确是通过ribbon轮询访问了多个account-service的pod:
    在这里插入图片描述

验证熔断能力

接下来验证web-service配置的熔断服务是否可以生效:

  1. 执行以下命令将account-service的deployment删除:
kubectl delete deployment account-service
  1. 再浏览器访问地址: http://192.168.121.133:30519/account,如下图所示,页面上的"Fallback"是配置的熔断方法返回的内容,可见熔断配置已经生效:
    在这里插入图片描述
  2. 再回到web-service的pom.xml所在位置执行以下命令,这样会重新构建部署一次web-service服务:
mvn cleaninstallfabric8:deploy -Dfabric8.generator.from=fabric8/java-jboss-openjdk8-jdk -Pkubernetes
  1. 再浏览器访问地址: http://192.168.121.133:30519/account,如下图所示,服务成功恢复:
    在这里插入图片描述

至此,spring-cloud-kubernetes的服务发现和轮询实战(含熔断)就全部完成了,利用API Server提供的信息,spring-cloud-kubernetes将原生的kubernetes服务带给了SpringCloud应用,帮助传统微服务更好的融合在kubernetes环境中,如果您也在考虑将应用迁移到kubernetes上,希望本文能给您一些参考。



SpringCloud实战十三:Gateway之 Spring Cloud Gateway 动态路由_zhuyu19911016520-CSDN博客

$
0
0

前面分别对 Spring Cloud Zuul 与 Spring Cloud Gateway 进行了简单的说明,它门是API网关,API网关负责服务请求路由、组合及协议转换,客户端的所有请求都首先经过API网关,然后由它将匹配的请求路由到合适的微服务,是系统流量的入口,在实际生产环境中为了保证高可靠和高可用,尽量避免重启,如果有新的服务要上线时,可以通过动态路由配置功能上线。

本篇拿 Spring Cloud Gateway 为例,对网关的动态路由进行简单分析,下一篇将分享动态路由的进阶实现

1.gateway配置路由主要有两种方式,1.用yml配置文件,2.写在代码里。而无论是 yml,还是代码配置,启动网关后将无法修改路由配置,如有新服务要上线,则需要先把网关下线,修改 yml 配置后,再重启网关
  • yml配置方式
    在这里插入图片描述
  • 代码配置方式
    在这里插入图片描述
2.gateway网关启动时,路由信息默认会加载内存中,路由信息被封装到 RouteDefinition 对象中,配置多个RouteDefinition组成gateway的路由系统,仔细的同学可能看到RouteDefinition中的字段与上面代码配置方式比较对应
  • RouteDefinition对象在 org.springframework.cloud.gateway.route包下,其定义如下:
    在这里插入图片描述
  • RouteDefinitionLocator是个接口,在org.springframework.cloud.gateway.route包下,如果想查看网关中所有的路由信息,调用此接口方法是一个办法,需要从先注入到容器,后面还有另外一种查看方式,是Spring Cloud Gateway 的Endpoint端点提供的方法
    在这里插入图片描述
3.Spring Cloud Gateway 提供了 Endpoint 端点,暴露路由信息,有获取所有路由、刷新路由、查看单个路由、删除路由等方法,源码在 org.springframework.cloud.gateway.actuate.GatewayControllerEndpoint 中,想访问端点中的方法需要添加 spring-boot-starter-actuator 注解,并在配置文件中暴露所有端点
# 暴露端点
management:
  endpoints:
    web:
      exposure:
        include: '*'
  endpoint:
    health:
      show-details: always
4.动态路由代码实现

前提:需要启动 3个服务,eureka、gateway、consumer-service

  • 1.eureka使用前面博客中的代码
  • 2.consumer-service是个web项目,提供一个hello方法,需注册到eureka上
  • 3.新建gateway,添加引用
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-client</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId></dependency>
  • 4.添加基本配置和注册到eureka,不要配置路由信息映射到consumer-service,由后面的动态路由功能路由过去
  • 5.根据Spring Cloud Gateway的路由模型定义数据传输模型,分别是:路由模型、过滤器模型、断言模型
//1.创建路由模型
public class GatewayRouteDefinition {
    //路由的Id
    private String id;
    //路由断言集合配置
    private List<GatewayPredicateDefinition> predicates = new ArrayList<>();
    //路由过滤器集合配置
    private List<GatewayFilterDefinition> filters = new ArrayList<>();
    //路由规则转发的目标uri
    private String uri;
    //路由执行的顺序
    private int order = 0;
    //此处省略get和set方法
}

//2.创建过滤器模型
public class GatewayFilterDefinition {
    //Filter Name
    private String name;
    //对应的路由规则
    private Map<String, String> args = new LinkedHashMap<>();
    //此处省略Get和Set方法
}

//3.路由断言模型
public class GatewayPredicateDefinition {
    //断言对应的Name
    private String name;
    //配置的断言规则
    private Map<String, String> args = new LinkedHashMap<>();
    //此处省略Get和Set方法
}
  • 6.编写动态路由实现类,需实现ApplicationEventPublisherAware接口
/**
 * 动态路由服务
 */
@Service
public class DynamicRouteServiceImpl implements ApplicationEventPublisherAware{

    @Autowired
    private RouteDefinitionWriter routeDefinitionWriter;
    private ApplicationEventPublisher publisher;

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.publisher = applicationEventPublisher;
    }


    //增加路由
    public String add(RouteDefinition definition) {
        routeDefinitionWriter.save(Mono.just(definition)).subscribe();
        this.publisher.publishEvent(new RefreshRoutesEvent(this));
        return "success";
    }
    //更新路由
    public String update(RouteDefinition definition) {
        try {
            delete(definition.getId());
        } catch (Exception e) {
            return "update fail,not find route  routeId: "+definition.getId();
        }
        try {
            routeDefinitionWriter.save(Mono.just(definition)).subscribe();
            this.publisher.publishEvent(new RefreshRoutesEvent(this));
            return "success";
        } catch (Exception e) {
            return "update route  fail";
        }
    }
    //删除路由
    public Mono<ResponseEntity<Object>> delete(String id) {
        return this.routeDefinitionWriter.delete(Mono.just(id)).then(Mono.defer(() -> {
            return Mono.just(ResponseEntity.ok().build());
        })).onErrorResume((t) -> {
            return t instanceof NotFoundException;
        }, (t) -> {
            return Mono.just(ResponseEntity.notFound().build());
        });
    }
}
  • 7.编写 Rest接口,通过这些接口实现动态路由功能,注意SpringCloudGateway使用的是WebFlux不要引用WebMvc
@RestController
@RequestMapping("/route")
public class RouteController {

    @Autowired
    private DynamicRouteServiceImpl dynamicRouteService;

    //增加路由
    @PostMapping("/add")
    public String add(@RequestBody GatewayRouteDefinition gwdefinition) {
        String flag = "fail";
        try {
            RouteDefinition definition = assembleRouteDefinition(gwdefinition);
            flag = this.dynamicRouteService.add(definition);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return flag;
    }
    //删除路由
    @DeleteMapping("/routes/{id}")
    public Mono<ResponseEntity<Object>> delete(@PathVariable String id) {
        try {
            return this.dynamicRouteService.delete(id);
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }
    //更新路由
    @PostMapping("/update")
    public String update(@RequestBody GatewayRouteDefinition gwdefinition) {
        RouteDefinition definition = assembleRouteDefinition(gwdefinition);
        return this.dynamicRouteService.update(definition);
    }

    //把传递进来的参数转换成路由对象
    private RouteDefinition assembleRouteDefinition(GatewayRouteDefinition gwdefinition) {
        RouteDefinition definition = new RouteDefinition();
        definition.setId(gwdefinition.getId());
        definition.setOrder(gwdefinition.getOrder());

        //设置断言
        List<PredicateDefinition> pdList=new ArrayList<>();
        List<GatewayPredicateDefinition> gatewayPredicateDefinitionList=gwdefinition.getPredicates();
        for (GatewayPredicateDefinition gpDefinition: gatewayPredicateDefinitionList) {
            PredicateDefinition predicate = new PredicateDefinition();
            predicate.setArgs(gpDefinition.getArgs());
            predicate.setName(gpDefinition.getName());
            pdList.add(predicate);
        }
        definition.setPredicates(pdList);

        //设置过滤器
        List<FilterDefinition> filters = new ArrayList();
        List<GatewayFilterDefinition> gatewayFilters = gwdefinition.getFilters();
        for(GatewayFilterDefinition filterDefinition : gatewayFilters){
            FilterDefinition filter = new FilterDefinition();
            filter.setName(filterDefinition.getName());
            filter.setArgs(filterDefinition.getArgs());
            filters.add(filter);
        }
        definition.setFilters(filters);

        URI uri = null;
        if(gwdefinition.getUri().startsWith("http")){
            uri = UriComponentsBuilder.fromHttpUrl(gwdefinition.getUri()).build().toUri();
        }else{
            // uri为 lb://consumer-service 时使用下面的方法
            uri = URI.create(gwdefinition.getUri());
        }
        definition.setUri(uri);
        return definition;
    }
}
  • 8.启动项目,查看网关路由信息,访问 localhost:9999/actuator/gateway/routes,因没有配置路由信息,因此返回结果为空数组
    在这里插入图片描述
  • 9.通过Postman发一个 post 请求新增路由,接口地址: http://localhost:9999/route/update,路由到consumer-service 上,然后通过网关访问查看是否转发请求了(这里直接调用的update,有就会覆盖,没有则新增)
    在这里插入图片描述
  • 10.再访问 localhost:9999/actuator/gateway/routes ,可以看到新的路由信息已经配置进去了,这就是动态路由配置,还可以调用删除、修改接口,操作动态操作路由信息
    在这里插入图片描述
  • 11.配置路由信息后,访问consumer-service服务,正常返回,说明路由已经生效,请求转发到consumer-service服务
    在这里插入图片描述
    好了,动态路由的简单实现了,一般在生产环境不使用此方式,因为网关都是多实例部署,还可能随时增加实例,需要已调用接口的方式一一调用网关所有的实例

本篇博客参考了许进的文章,地址: http://springcloud.cn/view/407,还有他写的 《重新定义SpringCloud》比较不错

代码已上传至码云, 源码,项目使用的版本信息如下:

- SpringBoot 2.0.6.RELEASE
- SpringCloud Finchley.SR2

下篇将介绍动态路由的进阶实现,实现方式:

  • 创建一个路由信息维护的项目
  • 实现增删改查路由信息到mysql
  • 然后发布,发布后将路由信息与版本信息保存到redis中,对外提供 rest 接口
  • 网关开启定时任务,定时拉取 rest 接口中最新版本的路由信息,这样网关发布多个实例后,都会单独的去拉取维护的路由信息
    参考了 https://blog.csdn.net/tianyaleixiaowu/article/details/83412301

中国央企国企信息化热点_阿朱=行业趋势+开发管理+架构-CSDN博客

$
0
0

(1)应用

我很奇怪:大家觉得变化越来越快,我为啥感觉变化越来越慢呢?

你看很多企业,仍然停留在四大管控:

财务统一管控

人力统一管控

流程统一管控

数据统一管控

而且,即使是这四大管控,其实后两个管控也没搞,就是主要做财务统一管控和人力统一管控。

对于流程统一管控,不外乎用用OA和工作流引擎。连流程管理部也没有,也没有人对流程的质量、效率、成本进行衡量,也没有人对衡量进行绩效考核,每个部门都按日积月累混沌形成的日常事实运转过程滴答滴答走着。还有一些企业还和我叫:我们有进步啊,我们把泛微、蓝凌、致远传统OA软件换成了移动钉钉和企业微信了。(这到底是什么鬼逻辑?)

对于数据统一管控,我曾经参加过无数的CIO群,从QQ群参加到微信群,都持续够十五年之久。但是这十五年来,这些CIO老生常谈的老是:

数据缺失

数据错误

数据没标准

数据不统一

这四大数据问题,都谈论二十年了,还是没有任何结果和实质进展。还在谈。这就是中国大部分CIO的水平写照。后来,我把这些CIO群全都退了。

另外,我再说说财务统一管控。其实财务统一管控早就好几代思想了:

第一代:整个集团所有子公司、分公司,统一上一套财务软件,目的是把统一的财务制度要求内置到这套财务软件中共同遵守

第二代:财务共享中心,把全国各地的财务人员都集约化到总部,目的是通过工业化流水线来实现财务工作效率加速和财务人员成本集约

第三代:正是现在正在搞的,但是在欧美上世纪60年代已经搞的。那就是企业财务管理公司,目的是集中统筹企业的资金运用,最大化发挥资金效率

第四代:欧美在上世纪70年代已经搞的,那就是外包给社会专业的会计师事务所或财务管理公司,目的是把事务性工作都外包,自己本公司的正式编制正式工作主要从事创造性的事情。只有创造性的事情才是增值的高利润的事情

中国大部分企业都停留在第一代。甚至更多的企业还停留在地下:那就是每个大区自行采购自行信息化。蛮符合中国地方割据封疆大吏的千古规律。

最后我再谈谈人力统一管控。很多人力部长和我谈编制、薪资、绩效、激励。我说啊,这些都谈不上。未来你们负责的这摊事:面临的最大的挑战就是:央企国企民企并购重组。咱就不谈别的,就说HR系统里的组织管理和人员管理吧,你们能不能很容易做到两个不同的央企国企的组织和人员很快地在HR IT系统里面做合并重组。咱们把这个问题解决了再谈上面的各个点上的应用。

(2)技术

很多企业的IT系统,用的技术极为落后。这都什么年月了,Web技术互联网早在2009年就结束了,移动App技术互联网也已经十周年了。但大部分企业,全国那么多子公司分公司、甚至在全球开展业务,都还是类Windows的客户端,或者是需要在每台电脑上都要装600M甚至1个G的插件。是每台电脑都要装啊,还要维护不出异常,还要升级。天哪,真不知道那些全国分公司全球子公司的企业是怎么做到的,还能正常运转这么二十年。

所以,很多企业现在特别求所谓的IT体验,不外乎是:纯WebUI、移动App UI、小程序UI。这都算先进的了。

另外,很多大企业现在特别热衷微服务中间件技术,以为微服务能解决过去软件重型大块不好升级、不好拆分、性能低下的问题。我想说的是:微服务技术的产生初衷,根本不是为了解决应用拆分的问题。我过去早就写过文章《从来没有一种技术是为了解决复用、灵活组合、定制开发的问题》,我在这里就不赘述了。大家点击链接去看。大家想解决的应用好拆合的问题,需要IT系统的每个模块以及整体系统上,在产品设计、应用架构设计、技术架构设计方面都得一起努力才能解决,不能说用了微服务中间件,就真的是微服务了,就真的好组合了,这不现实。

还有很多大企业现在也特别热衷大数据技术。我就纳了闷了,按说中国的海量数据应该在中国政府手里,或者在互联网电子商务企业手里,因为他们的本质都是社会性组织。单个其他企业怎么可能会有海量数据呢?后来我反复追问才弄明白,原来他们想用大数据平台解决刚才我提到的数据统一管控的问题。

我说他娘的你们全错了:大数据平台根本解决不了你们说的:数据缺失、数据错误、数据没标准、数据不统一这四大数据问题。

很多人问:那用什么解决呢?

我告诉大家怎么真正解决吧。但,我一会说的方法也是只是最大化缓解,而不能彻底解决,就和应用好拆合一样,没有终极彻底方案,所以请大家都现实点看问题。但我一会说的方法,绝对比你们现在能想到的方法都要好。

首先,做业务中台。过去呢是,各个来源来的数据,都往数据库这个桶里装。放到桶里再看怎么统一。所以做了许多所谓的:数据整合、数据清洗的事情。这就晚啦。应该是,用业务中台去解决问题。比如说现在搞电商,有来自京东的订单,有来自淘宝的订单,有来自微信的订单,有来自抖音的订单,如果你不经过一个业务中台来收拢一下,你直接放进数据库这个桶里,那就不好统一了,都成酱缸了。所以需要统一订单中心,定义统一规则。很多ERP厂商乙方不明白为啥现在的大企业都开始在云计算技术基础上都自己开发了,很多很多ERP厂商乙方不明白怎么会有业务中台这个伪命题这个王八混账东西,还一个劲说攻击:有钱的企业上了中台没钱了,没钱的企业上了中台死了。这就是水平啊。

然后,做主数据服务。尤其是企业、供应商、客户、产品、项目、科目,都搞成主数据,对外提供Open API服务,就这一份主数据,而不是N份主数据。还要在主数据上打标签,做关系图谱。否则这主数据在财务、生产、采购、销售、售后各个部门会越用越乱。

最后再说说私有云。很多大企业都现在兴奋地要搞私有云,说我们要高科技。这是什么脑回路逻辑?上了私有云就是高科技就是进步?私有云有个毛用?

我曾经问过CIO,你们上私有云有个毛用?

他们说:上云是未来啊。

我说:上公有云才是未来,你们上的可是私有云啊,私有云又不是未来。你们说说,你们为啥要上云技术?咱们先不谈公有云私有云,咱们先掰掰你们为啥要上云技术。现在不香吗?

他们说:现在不香。现在每搞一个应用都需要搞一台服务器,老板对服务器增加很难批预算。但是多个不同厂家的应用放一台服务器,我们也不知道会不会出啥问题,除了问题算谁的?所以我们需要上私有云技术。用容器或者虚拟机把这些应用都隔离了。

我说:我晕。我2006年就在用虚拟机集群管理软件,同志们,现在都2020年了,再有2个月就2021年了。这十五年前该做的事,现在才做啊。你们这都落后多少年了。你们这么多年到底干了些什么。另外,OpenStack也推出十年了。另外,容器也推出6年了。

还有,大家是用公有云技术解决互联网应用社会化应用波峰波谷弹性收缩的问题。现在大家都在搞营销云、全渠道零售云、社交化客户关系运营,所以才需求公有云的技术。你们啥也不搞,就企业这点员工、就企业这个内部管理摊子,你们就根本没有上云技术的诉求。你们就是环境隔离而已啊。

就这水平。还当前沿,还以为能解决性能扩张问题?这都什么脑子。


微服务设计模式:防腐层(Anti-corruption layer)_琦彦-CSDN博客

$
0
0

微软:微服务设计模式

2017年,微软  AzureCAT 模式和实践团队在  Azure 架构中心发布了  9 个新的微服务设计模式,并给出了这些模式解决的问题、方案、使用场景、实现考量等。微软团队称这 9 个模式有助于更好的设计和实现微服务,同时看到业界对微服务的兴趣日渐增长,所以也特意将这些模式记录并发布。

下图是微软团队建议如何在微服务架构中使用这些模式:

微软:微服务设计模式

 

文中提到的9 个模式包括:外交官模式(Ambassador),防腐层(Anti-corruption layer),后端服务前端(Backends for Frontends),舱壁模式(Bulkhead),网关聚合(Gateway Aggregation),网关卸载(Gateway Offloading),网关路由(Gateway Routing),挎斗模式(Sidecar)和绞杀者模式(Strangler)。这些模式绝大多数也是目前业界比较常用的模式,如:

  • 外交官模式(Ambassador)可以用与语言无关的方式处理常见的客户端连接任务,如监视,日志记录,路由和安全性(如 TLS)。
  • 防腐层\防损层(Anti-corruption layer)介于新应用和遗留应用之间,用于确保新应用的设计不受遗留应用的限制。
  • 后端服务前端(Backends for Frontends)为不同类型的客户端(如桌面和移动设备)创建单独的后端服务。这样,单个后端服务就不需要处理各种客户端类型的冲突请求。这种模式可以通过分离客户端特定的关注来帮助保持每个微服务的简单性。
  • 舱壁模式(Bulkhead)隔离了每个工作负载或服务的关键资源,如连接池、内存和 CPU。使用舱壁避免了单个工作负载(或服务)消耗掉所有资源,从而导致其他服务出现故障的场景。这种模式主要是通过防止由一个服务引起的级联故障来增加系统的弹性。
  • 网关聚合(Gateway Aggregation)将对多个单独微服务的请求聚合成单个请求,从而减少消费者和服务之间过多的请求。
  • 挎斗模式(Sidecar)将应用程序的辅助组件部署为单独的容器或进程以提供隔离和封装。

设计模式是对针对某一问题域的解决方案,它的出现也代表了工程化的可能。随着微服务在业界的广泛实践,相信这个领域将会走向成熟和稳定,笔者期望会有更多的模式和实践出现,帮助促进这一技术的进一步发展。

本文,主要介绍 防腐层(Anti-corruption layer)模式

防腐层(Anti-corruption layer)

在微服务(Microservices)架构实践中,人们大量地借用了DDD中的概念和技术,比如一个微服务应该对应DDD中的一个限界上下文(Bounded Context);在微服务设计中应该首先识别出DDD中的聚合根(Aggregate Root);还 有在微服务之间集成时采用DDD中的防腐层(Anti-Corruption Layer, ACL)。

防腐层(Anti-corruption layer,简称 ACL 介于新应用和遗留应用之间,用于确保新应用的设计不受老应用的限制。 是一种在不同应用间转换的机制。

创建一个 防腐层,以根据客户端自己的域模型为客户提供功能。该层通过其现有接口与另一个系统进行通信,几乎不需要或不需要对其进行任何修改。因此, 防腐层隔离不仅是为了保护您免受混乱的代码的侵害,还在于分离不同的域并确保它们在将来保持分离。

防腐层是将一个域映射到另一个域,这样使用第二个域的服务就不必被第一个域的概念“破坏”。

不共享相同语义的不同子系统之间实施外观或适配器层。 此层转换一个子系统向另一个子系统发出的请求。 使用 防腐层(Anti-corruption layer)模式可确保应用程序的设计不受限于对外部子系统的依赖。  防腐层(Anti-corruption layer)模式最先由 Eric Evans 在 Domain-Driven Design(域驱动的设计)中描述。

防腐层(Anti-corruption layer)提出背景

大多数应用程序依赖于其他系统的某些数据或功能。 例如,旧版应用程序迁移到新式系统时,可能仍需要现有的旧的资源。 新功能必须能够调用旧系统。 逐步迁移尤其如此,随着时间推移,较大型应用程序的不同功能迁移到新式系统中。

这些旧系统通常会出现质量问题,如复杂的数据架构或过时的 API。 旧系统使用的功能和技术可能与新式系统中的功能和技术有很大差异。 若要与旧系统进行互操作,新应用程序可能需要支持过时的基础结构、协议、数据模型、API、或其他不会引入新式应用程序的功能。

保持新旧系统之间的访问可以强制新系统至少支持某些旧系统的 API 或其他语义。 这些旧的功能出现质量问题时,支持它们“损坏”可能会是完全设计的新式应用程序。

不仅仅是旧系统,不受开发团队控制的任何外部系统(第三方系统)都可能出现类似的问题。

解决方案

在不同的子系统之间放置防损层以将其隔离。 此层转换两个系统之间的通信,在一个系统保持不变的情况下,使另一个系统可以避免破坏其设计和技术方法。

在不同的子系统之间放置防损层以将其隔离

 

上图显示了采用两个子系统的应用程序。 

子系统 A 通过防损层调用子系统 B。 子系统 A 与防损层之间的通信始终使用子系统 A 的数据模型和体系结构。防损层向子系统 B 发出的调用符合该子系统的数据模型或方法。 防损层包含在两个系统之间转换所必需的所有逻辑。

该层可作为应用程序内的组件或作为独立服务实现。

Anti-corruption layer注意事项

  • 防损层可能将延迟添加到两个系统之间的调用。
  • 防损层将添加一项必须管理和维护的其他服务。
  • 请考虑防损层的缩放方式。
  • 请考虑是否需要多个防损层。 可能需要使用不同的技术或语言将功能分解为多个服务,或者可能因其他原因对防损层进行分区。
  • 请考虑如何管理与其他应用程序或服务相关的防损层。 如何将其集成到监视、发布和配置进程中?
  • 确保维护并可以监视事务和数据一致性
  • 请考虑防损层是要处理不同子系统之间的所有通信,还是只需处理部分功能。
  • 如果防损层是应用程序迁移策略的一部分,请考虑该层是永久性的,还是在迁移所有旧功能后即会停用。
  • 经常和绞杀者模式(strangler)一起使用

Anti-corruption layer使用场景

在以下情况下使用此模式:

  • 迁移计划为发生在多个阶段,但是新旧系统之间的集成需要维护
    • 很多人一看到旧系统就想要赶快替换掉他,但是请不要急著想著去替换旧系统,因为这条路充满困难与失败,而且 旧系统通常反而是系统目前最赚钱的部分。更好的做法是在使用旧系统时包上一层 ACL,让你的开发不受影响,甚至可以一点一滴的替换旧系统的功能,达到即使不影响目前功能下也能开发新功能,达到重构的效果!
  • 两个或更多个子系统具有不同的语义,需要对外部上下文的访问进行一次转义
    • 例如:对接第三方系統。缴费软件中的收银台系统,需要对接不同的支付方式(支付宝、各个银行、信用卡等),这是就需要 收银台系统充当一个Anti-corruption layer,将用户的缴费支付信息,转换成各个三方支付系统需要的数据格式。
  • 如果内部多个组件对外部系统需要访问,那么可以考虑将其放到通用上下文中。
    • 例如:我们有一个抽奖平台,包含有现金券、折扣券、外卖券、出行券等组件,但他们都需要对接用户信息服务,这时就需要在抽奖平台中,搭建一个 Anti-corruption layer,作为抽奖平台对接用户信息的通用适配层。

如果新旧系统之间没有重要的语义差异,则此模式可能不适合。

Anti-corruption layer 示例1:JDK集合

在 JDK1.0 时我们用的集合还是 Vector(后来推荐使用 ArrayList),我们用的迭代器还是 Enumeration(后来推荐使用 Iterator)。现在我们需要一个适配器,搭建 Anti-corruption layer (EnumerationAdapter ),让 Vector 也能使用 Iterator 迭代器,即在 Enumeration 和 Iterator 之间做适配。

/**
 * 1、Iterator 是新版本的迭代器。
 * 2、Enumeration 是旧版本的迭代器。
 * 3、EnumerationAdapter 是适配者(Adapter)角色,相当于Anti-corruption layer 在 Enumeration 和 Iterator 之间做适配
 */
public class EnumerationAdapter implements Iterator {

    private Enumeration enumeration;

    public EnumerationAdapter(Enumeration enumeration) {
        this.enumeration = enumeration;
    }

    @Override
    public boolean hasNext() {
        return enumeration.hasMoreElements();
    }

    @Override
    public Object next() {
        return enumeration.nextElement();
    }

    @Override
    public void remove() {
        throw new UnsupportedOperationException("remove");
    }
}

// main方法
public static void main(String[] args) {
	Vector vector = new Vector();
	vector.add("java");
	vector.add("python");
	vector.add("javaScript");
	Enumeration enumeration = vector.elements();
	Iterator iterator = new EnumerationAdapter(enumeration);
	while (iterator.hasNext()) {
		System.out.println(iterator.next());
	}
}

 

Anti-corruption layer 示例2--企业员工管理系统新旧版本对接

  • 在企业员工管理系统中,我们经常需要通过ID查询雇员信息(EmployeeAccessService.findEmployee)
  • 但,我们在企业员工管理系统版本迭代中,可能存在老员工的信息在旧版本企业员工管理系统,新员工的信息在新版本企业员工管理系统。
  • 当我们查询老员工的信息,就需要委托给EmployeeAccessAdapter适配器,从旧版本企业员工管理系统(EmployeeAccessFacade)中获取一个员工信息。
  • 并通过EmployeeAccessTranslator,以将旧版本员工信息转换为应用程序模型中的域对象。

 

EmployeeAccessService(查询雇员信息

public Employee findEmployee(String empID){
    return adapter.findEmployee(empID);
}

EmployeeAccessAdapter (Anti-corruption layer,对接旧版本企业员工管理系统

// 旧版本 员工信息
private EmployeeAccessFacade facade;

public Employee findEmployee(String empID){
    EmployeeAccessContainer container = facade.findEmployeeAccess(empID);
    return translator.translate(container);
}

EmployeeAccessTranslator(将旧版本员工信息转换为应用程序模型中的域对象

public Employee translate(EmployeeAccessContainer container){
    Employee emp = null;
    if (container != null) {
        employee = new Employee();
        employee.setEmpID(idPrefix + container.getEmployeeDTO().getEmpID());
        ...(more complex mappings)

 

参考链接:

https://docs.microsoft.com/en-us/azure/architecture/

https://stackoverflow.com/questions/909264/ddd-anti-corruption-layer-how-to

https://docs.microsoft.com/zh-cn/azure/architecture/patterns/strangler

https://ithelp.ithome.com.tw/articles/10218591

https://tech.meituan.com/2017/12/22/ddd-in-practice.html

k8s部署springcloud架构的一些心得体会_浅抒流年的博客-CSDN博客

$
0
0

简介

最近在研究k8s,顺便将公司springcloud架构改造了一下,以更好适应用k8s来部署。期间遇到了一些问题,自己想办法解决了。在此记录下。

1. 提供者和消费者向eureka注册时的问题

大家都知道,springcloud架构是有一个注册中心的,无论是服务提供者还是服务消费者都要注册到该注册中心上。在上一篇博文中,已经介绍过如何把eureka部署到k8s上。接下来,我们应该将其他服务注册到eureka中,但是springcloud默认向注册中心注册时,是以 主机名:实例名称:端口 的形式注册上去的,注册结果如cloud-eureka-server-6f59b94548-fc9gl:o2o-gateway:9999,这样服务之间调用的时候会报unknowHostException异常。要解决这个问题,可以在springcoud项目中加入配置,使得服务向注册中心注册时,注册形式改变为 主机IP:PORT的方式。

eureka.instance.instance-id=${spring.cloud.client.ipAddress}:${server.port}		#springcloud版本不同,此处语法会稍有差异
eureka.instance.prefer-ip-address=true

这样服务向eureka注册的结果就会如下图一下
在这里插入图片描述
注意:在k8s上部署springcloud,注册上去的地址是pod地址

2. 多环境问题

所有公司肯定会有开发、测试、生产等多套不同的环境,而不同环境使用不同的配置文件,我们不可能所有环境都打一个对应环境的镜像,最理想的情况应该是所有环境都使用同一个镜像,只是在不同环境部署的时候,使用的配置文件不同而已。那么如何来实现呢?我们可以考虑把所有涉及到多环境配置的地方变量化,在部署的时候分别给这些变量不同的值来达到让项目加载不同环境配置文件的目的。
拿springcloud的网关服务来说明我是如何做的

spring.cloud.config.uri=http://${config.host:config-service}:${config.port:9999}
spring.cloud.config.name=o2o-gateway
spring.cloud.config.profile=${config.profile:default}

eureka.client.serviceUrl.defaultZone=http://${eureka.host:eureka-service}:9761/eureka/
eureka.instance.instance-id=${spring.cloud.client.ipAddress}:${server.port}
eureka.instance.prefer-ip-address=true


#client name
spring.application.name=cloud-o2o-gateway

在上边的代码中,我把涉及到多环境的地方都做了变量化,无论是配置中心地址、注册中心地址还是监听端口

然后看下我的dockerfile文件配置

FROM registry.11wlw.cn/common/java:8u111-jdk
RUN /bin/cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \&& echo 'Asia/Shanghai' >/etc/timezone
ADD /cloud-zuul-server-1.0.0.jar /
ADD ./run.sh /
ENV JAVA_OPTS=""
ENV EUREKA_HOST="eureka-service"
ENV CONFIG_HOST="config-service"
ENV CONFIG_PROFILE="default"
ENV SERVER_PORT="8105"
ENTRYPOINT [ "sh", "-c", "/run.sh" ]

我在dockerfile文件中设置了和bootstrap.properties相对应的环境变量,并赋予了默认值。

看下run.sh的内容

java $JAVA_OPTS -jar /cloud-zuul-server-1.0.0.jar \
    --eureka.host=$EUREKA_HOST --config.host=$CONFIG_HOST --config.profile=$CONFIG_PROFILE --server.port=$SERVER_PORT

在容器启动的时候,会去读取环境变量的值来决定服务注册到哪个环境的配置中心去,去哪个环境的配置中心拉取配置文件,拉取哪个环境的配置文件以及监听哪个端口。
这样我们在用k8s部署springcloud的时候,在部署yaml文件中通过重新赋予这些环境变量不同的值就可以达到解决这个问题的目的。

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: cloud-zuul-server
  namespace: local
spec:
  replicas: 1
  template:
    metadata:
     labels:
       app: cloud-zuul-server
    spec:
     containers:
     - name: cloud-module-auth
       image: registry.11wlw.cn/o2o/cloud-zuul-server
       env:
        - name: EUREKA_HOST
          value: eureka-service
        - name: CONFIG_HOST
          value: config-service
        - name: CONFIG_PROFILE
          value: newqa
       tty: true
       ports:
       - containerPort: 8105

上述yaml文件中我们通过env参数来重新为bootstrap.properties文件中的启动参数赋值,容器起来后,springcloud服务就会注册到不同的环境中,加载不同的配置文件了。

3 配置中心

springcloud架构有自己的配置中心组件,就是springConfig。我不知道别的公司是否在每套环境都搭了配置中心?但是我觉得,对于配置中心来说,所有环境可以共同只使用一套,在部署的时候,通过给CONFIG_HOST和CONFIG_PROFILE赋予不同的值的方式。

说明:整套架构要想走通,是建立在k8s安装了kubedns、ingress的基础上的

  1. springcloud各项目中eureka和config配置中配置的是eureka和config这两个pod的service的service名称,部署后通过kubedns解析成对于的ip
  2. springcloud网关服务通过ingress向外暴露服务入口
  3. 除了上述3个springcloud服务,其他服务不是必须创建pod对于的service,可以根据需要来决定

Spring Cloud 使用 Kubernetes 作为注册中心_HelloWood-CSDN博客

$
0
0

Spring Cloud 可以使用 Kubernetes 作为注册中心,实现服务注册和发现

创建两个应用,Consumer 和 Provider,Provider 提供一个 REST 接口供 Consumer 调用

Provider

添加依赖

  • build.gradle
dependencies {
    compile project(":discovery/common")

    implementation 'org.springframework.boot:spring-boot-starter-actuator'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.cloud:spring-cloud-starter-kubernetes-all'

    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'

    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

添加配置

  • application.properties

指定服务的名称,用于实现调用

spring.application.name=provider-service
server.port=8082
  • 1
  • 2
  • ProviderApplication.java

添加  @EnableDiscoveryClient启用服务发现

@SpringBootApplication
@EnableDiscoveryClient
public class ProviderApplication {

    public static void main(String[] args) {
        SpringApplication.run(ProviderApplication.class, args);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

添加接口

    @GetMapping("/ping")
    @ResponseBody
    public String ping() {
        try {
            return InetAddress.getLocalHost().getHostName();
        } catch (UnknownHostException e) {
            return "Pong";
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

部署到 Kubernetes

  • Dockerfile
FROM openjdk:8-jdk-alpine
VOLUME /tmp
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
ARG JAR_FILE
ADD discovery/provider/build/libs/provider-0.0.1-SNAPSHOT.jar app.jar
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom", "-Duser.timezone=GMT+08", "-jar","/app.jar"]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

构建并上传镜像

  • provider-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: provider-service
  labels:
    app.kubernetes.io/name: provider-service
spec:
  type: ClusterIP
  ports:
    - port: 8082
      targetPort: 8082
      protocol: TCP
      name: http
  selector:
    app.kubernetes.io/name: provider-service

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: provider-service
  labels:
    app.kubernetes.io/name: provider-service
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: provider-service
  template:
    metadata:
      labels:
        app.kubernetes.io/name: provider-service
        app.kubernetes.io/instance: sad-markhor
    spec:
      containers:
        - name: provider-service
          image: "docker.io/hellowoodes/spring-cloud-k8s-provider:1.2"
          imagePullPolicy: IfNotPresent
          ports:
            - name: http
              containerPort: 8082
              protocol: TCP
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

Consumer

添加依赖

  • build.gradle
dependencies {
    compile project(":discovery/common")

    implementation 'org.springframework.boot:spring-boot-starter-actuator'
    implementation 'org.springframework.boot:spring-boot-starter-web'

    implementation 'org.springframework.cloud:spring-cloud-starter-kubernetes-all'
    implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'
    implementation 'org.springframework.cloud:spring-cloud-starter-netflix-ribbon'
    implementation 'io.github.openfeign:feign-java8'

    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'

    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

不同的是 Consumer 还添加了 Feign 和 Ribbon 的依赖

添加配置

  • application.properties
spring.application.name=consumer-service
server.port=8081
  • 1
  • 2
  • ConsumerApplication.java

启用服务发现和 Feign 远程调用

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class ConsumerApplication {

    public static void main(String[] args) {
        SpringApplication.run(ConsumerApplication.class, args);
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

添加接口

  • ConsumerController.java
@RestController
@Slf4j
public class ConsumerController {

    @Autowired
    private DiscoveryClient discoveryClient;

    @Autowired
    private ProviderClient providerClient;


    @GetMapping("/service")
    public Object getClient() {
        return discoveryClient.getServices();
    }

    @GetMapping("/instance")
    public List<ServiceInstance> getInstance(String instanceId) {
        return discoveryClient.getInstances(instanceId);
    }

    @GetMapping("/ping")
    public OperationResponse ping() {
        return OperationResponse
                .builder()
                .success(true)
                .data(providerClient.ping())
                .build();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • ProviderClient.java
@FeignClient(name = "provider-service", fallback = ProviderClientFallback.class)
public interface ProviderClient {

    @RequestMapping(value = "/ping", method = RequestMethod.GET)
    String ping();
}

@Component
class ProviderClientFallback implements ProviderClient {

    @Override
    public String ping() {
        return "Error";
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

部署到 Kubernetes

  • Dockerfile
FROM openjdk:8-jdk-alpine
VOLUME /tmp
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
ARG JAR_FILE
ADD discovery/consumer/build/libs/consumer-0.0.1-SNAPSHOT.jar app.jar
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom", "-Duser.timezone=GMT+08", "-jar","/app.jar"]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

构建并上传镜像

  • consumer-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: consumer-service
  labels:
    app.kubernetes.io/name: consumer-service
spec:
  type: NodePort
  ports:
    - port: 8081
      nodePort: 30081
      protocol: TCP
      name: http
  selector:
    app.kubernetes.io/name: consumer-service

---

apiVersion: apps/v1
kind: Deployment
metadata:
  name: consumer-service
  labels:
    app.kubernetes.io/name: consumer-service
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: consumer-service
  template:
    metadata:
      labels:
        app.kubernetes.io/name: consumer-service
    spec:
      containers:
        - name: consumer-service
          image: "docker.io/hellowoodes/spring-cloud-k8s-consumer:1.2"
          imagePullPolicy: IfNotPresent
          ports:
            - name: http
              containerPort: 8081
              protocol: TCP
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

部署测试

部署

kubectl apply -f discovery/provider/provider-service.yaml
kubectl apply -f discovery/consumer/consumer-service.yaml
  • 1
  • 2

测试

待部署完成后,访问  ${NODE_IP}:30081/${PATH}即可得到服务信息

测试接口

  • ping
curl http://192.168.0.110:30081/ping
{"success":true,"message":null,"data":"provider-service-bb8844f9b-c74rj"}%
  • 1
  • 2
  • service
curl http://192.168.0.110:30081/service
["backend","consumer-service","kubernetes","provider-service","traefik","traefik-dashboard"]%
  • 1
  • 2
  • instance
curl http://192.168.0.110:30081/instance\?instanceId\=provider-service

[{"instanceId":"87f35232-cb2a-4a35-a094-f4577bce195e","serviceId":"provider-service","secure":false,"metadata":{"app.kubernetes.io/name":"provider-service","kubectl.kubernetes.io/last-applied-configuration":"{\"apiVersion\":\"v1\",\"kind\":\"Service\",\"metadata\":{\"annotations\":{},\"labels\":{\"app.kubernetes.io/name\":\"provider-service\"},\"name\":\"provider-service\",\"namespace\":\"default\"},\"spec\":{\"ports\":[{\"name\":\"http\",\"port\":8082,\"protocol\":\"TCP\",\"targetPort\":8082}],\"selector\":{\"app.kubernetes.io/name\":\"provider-service\"},\"type\":\"ClusterIP\"}}\n","port.http":"8082"},"uri":"http://10.32.0.8:8082","scheme":"http://","host":"10.32.0.8","port":8082}]%
  • 1
  • 2
  • 3

添加 Provider-Service 实例

kubectl scale deploy/provider-service --replicas=3
  • 1

待扩容完成后,多次访问 ping 接口,会看到在轮询访问每个 Provider 实例,每次返回的实例 ID 都不一样

curl http://192.168.0.110:30081/ping
{"success":true,"message":null,"data":"provider-service-bb8844f9b-lm589"}%

curl http://192.168.0.110:30081/ping
{"success":true,"message":null,"data":"provider-service-bb8844f9b-n5szb"}%

curl http://192.168.0.110:30081/ping
{"success":true,"message":null,"data":"provider-service-bb8844f9b-c74rj"}%

自动文摘(bytecup 2018)_狮子座明仔知识集散场-CSDN博客

$
0
0

自动文摘(Auto Text Summarization)

自动文摘,也称自动摘要生成,是NLP中较难的技术,难点很多,至今并没有一个非常让人满意的、成熟的技术来解决这个问题。

介绍

应用

  1. 自动文摘技术应用最广的领域在于新闻,由于新闻信息的过载,人们迫切地希望有这么一个工具可以帮助自己用最短的时间了解最多的最有用的新闻。
    ps:为什么不直接看标题呢?因为很多新闻为了哗众取宠,故意将标题起的特别吸引人眼球,但却名不副实,因此就有了Yahoo3000w$收购summly的交易。
  2. 搜索引擎也是应用之一,基于query的自动文摘会帮助用户尽快地找到感兴趣的内容。

前者是单文档摘要技术,后者是多文档摘要技术,后者较于前者会更加复杂一些。
自动文摘出现的重要原因之一是信息过载问题的困扰(个性化推荐系统也是一个好的办法),另外一个重要原因是人工文摘的成本较高。

问题描述

自动文摘要解决的问题描述:用一些精炼的话来概括整篇文章的大意,用户通过阅读文摘就可以了解到原文要表达的意思,即提炼文章的中心思想。

解决思路

问题包括两种解决思路:

  1. extractive,抽取式的,从原文中找到一些关键的句子,组合成一篇摘要;现阶段,相对成熟的是抽取式的方案,有很多很多的算法(TextRank、TextTeaser等),也有一些baseline的测试,但得到的摘要效果差强人意。
  2. abstractive,摘要式的,这需要计算机可以读懂原文的内容,并且用自己的意思将其表达出来。人类语言包括字、词、短语、句子、段落、文档这几个level,研究难度依次递增,理解句子、段落尚且困难,何况是文档,这是自动文摘最大的难点,也是更接近机器智能的味道。

难点

Abstractive是一个True AI的方法,要求系统理解文档所表达的意思,然后用可读性强的人类语言将其简练地总结出来。这里包含这么几个难点:

  1. 理解文档。所谓理解,和人类阅读一篇文章一样,可以说明白文档的中心思想,涉及到的话题等等。
  2. 可读性强。可读性是指生成的摘要要能够连贯(Coherence)与衔接(Cohesion),通俗地讲就是人类读起来几乎感觉不出来是AI生成的(通过图灵测试)。
  3. 简练总结。在理解了文档意思的基础上,提炼出最核心的部分,用最短的话讲明白全文的意思。

上述三个难点对于人类来说都不是一件容易的事情,对自然语言处理技术挑战也很大。

竞赛

2018 Byte Cup国际机器学习竞赛的主题是自动生成文本标题。比赛官网: https://biendata.com/competition/bytecup2018/

简介

说明比赛内容,数据,奖励,评测方法,取得的成绩。

数据预处理

  1. 分词
  2. 文本分析

a)训练数据的文章长度:
在这里插入图片描述
结论: 需要去太长及太少的文本,100<length<1000

b)标题长度:
在这里插入图片描述
结论: 5<length<25

c)待预测文章长度:
在这里插入图片描述
结论: length<500

模型

sequence-to-sequence(seq2seq)

论文:Rush et al.2015 Alexander M. Rush, Sumit Chopra, and Jason Weston. 2015. A neural attention model for abstractive sentence summarization. EMNLP 2015

Abstractive是学术界研究的热点,尤其是Machine Translation(MT)中的encoder-decoder框架和attention mechanism十分火热,大家都试着将abstractive问题转换为sequence-2-sequence问题,套用上面两种技术,得到state-of-the-art结果,2015年来已经有许多篇paper都是这种套路,于是就有了下面的吐槽:
在这里插入图片描述

Encoder-Decoder

Encoder-Decoder不是一种模型,而是一种框架,一种处理问题的思路,最早应用于机器翻译领域,输入一个序列,输出另外一个序列。机器翻译问题就是将一种语言序列转换成另外一种语言序列,将该技术扩展到其他领域,比如输入序列可以是文字,语音,图像,视频,输出序列可以是文字,图像,可以解决很多别的类型的问题。这一大类问题就是上图中的sequence-to-sequence问题。这里以输入为文本,输出也为文本作为例子进行介绍:
在这里插入图片描述

  1. encoder部分是将输入序列表示成一个带有语义的向量,使用最广泛的表示技术是Recurrent Neural Network,RNN是一个基本模型,在训练的时候会遇到gradient explode或者gradient vanishing的问题,导致无法训练,所以在实际中经常使用的是经过改良的LSTM RNN或者GRU RNN对输入序列进行表示,更加复杂一点可以用BiRNN、BiRNN with LSTM、BiRNN with GRU、多层RNN等模型来表示,输入序列最终表示为最后一个word的hidden state vector。
  2. decoder部分是以encoder生成的hidden state vector作为输入“解码”出目标文本序列,本质上是一个语言模型,最常见的是用Recurrent Neural Network Language Model(RNNLM),只要涉及到RNN就会有训练的问题,也就需要用LSTM、GRU和一些高级的model来代替。目标序列的生成和LM做句子生成的过程类似,只是说计算条件概率时需要考虑encoder向量。
    这里,每一种模型几乎都可以出一篇paper,尤其是在这个技术刚刚开始应用在各个领域中的时候,大家通过尝试不同的模型组合,得到state-of-the-art结果。
    该框架最早被应用在Google Translation中,paper详情可以见 Generating News Headlines with Recurrent Neural Networks,2014年12月发表。

模型优化

Seq2Seq RNNs and Beyond

论文:IBM Watson,Abstractive Text Summarization using Seq2Seq RNNs and Beyond. 2016

本文的贡献点有三个:

  1. 在两个新的数据集上应用seq2seq+attention模型,并且取得了state-of-the-art的结果,超越ABS(Rush,2015)模型的结果;
  2. 研究了关键词对于自动文摘所起到的关键作用,并且提出了一种新的模型;
  3. 提出了一个新的数据集,供研究者使用。

Introduction

目前,解决seq2seq的问题,都是借鉴machine translation的方法。
但文本摘要问题和机器翻译问题有着很大的不同。

  1. 文本摘要的问题输出长度一般都很短,并且不依赖于输入的长度。
  2. 文本摘要的问题一般都会损失大量的信息,只保留少量的核心概念作为输出,而机器翻译则要保证信息损失最低,在单词级别上保证对齐。
    那么机器翻译的相关技术是否会在文本摘要问题上表现同样突出呢?本文将会回答这个问题。受文本摘要与机器翻译问题的不同特点所启发,本文将超越一般的架构而提出新的模型来解决摘要问题。

本文与之前seq2seq类的paper有着一个很明显的区别,就是将摘要问题和机器翻译问题严格区别开,而不是简单地套用MT的技术来解决摘要问题,根据摘要问题的特点提出了更加合适的模型,相比于之前的研究更进了一步。

Related work

之前大量的研究都是集中于extractive摘要方法,在DUC2003和2004比赛中取得了不俗的成绩。但人类在做摘要工作时,都是采用abstractive方法,理解一篇文档然后用自己的语言转述出来。随着深度学习技术在NLP任务中的广泛使用,研究者们开始更多地研究abstractive方法,比如Rush组的工作,比如哈工大Hu的工作。
本文的贡献:

  1. 在两种不同数据集上应用seq2seq+attention的模型,得到了state-of-the-art结果。
  2. 根据摘要问题的特点提出了针对性的模型,结果更优。
  3. 提出了一个包括多句子摘要的数据集和基准。

Models

Encoder-Decoder with Attention

这个模型本文的基准模型,Encoder是一个双向GRU-RNN,Decoder是一个单向GRU-RNN,两个RNN的隐藏层大小相同,注意力模型应用在Encoder的hidden state上,一个softmax分类器应用在Decoder的生成器上。

分析:基准模型是套用Bahdanau,2014在机器翻译中的方法,解决方案的思路都与之前的paper类似,并无新颖之处。

Large Vocabulary Trick

这个模型引入了large vocabulary trick(LVT)技术到文本摘要问题上。本文方法中,每个mini batch中decoder的词汇表受制于encoder的词汇表,decoder词汇表中的词由一定数量的高频词构成。这个模型的思路重点解决的是由于decoder词汇表过大而造成softmax层的计算瓶颈。本模型非常适合解决文本摘要问题,因为摘要中的很多词都是来自于原文之中。

分析:LVT是一个针对文本摘要问题的有效方法,考虑到了摘要中的大部分词都是来源于原文之中,所以将decoder的词汇表做了约束,降低了decoder词汇表规模,加速了训练过程。

Vocabulary expansion

LVT技术很好地解决了decoder在生成词时的计算瓶颈问题,但却不能够生成新颖的有意义的词。为了解决这个问题,本文提出了一种扩展LVT词汇表的技术,将原文中所有单词的一度最邻近单词扩充到词汇表中,最邻近的单词在词向量空间中cosine相似度来计算得出。

分析:词汇表的扩展是一项非常重要的技术,word embedding在这里起到了关键作用。用原文中单词的最邻近单词来丰富词汇表,不仅仅利用LVT加速的优势,也弥补了LVT带来的问题。

Feature-rich Encoder

文本摘要面临一个很大的挑战在于确定摘要中应该包括哪些关键概念和关键实体。因此,本文使用了一些额外的features,比如:词性,命名实体标签,单词的TF和IDF。将features融入到了word embedding上,对于原文中的每个单词构建一个融合多features的word embedding,而decoder部分,仍采用原来的word embedding。
在这里插入图片描述

分析:本文的一个创新点在于融入了word feature,构建了一组新的word embedding,分别考虑了单词的词性、TF、IDF和是否为命名实体,让单词具有多个维度的意义,而这些维度上的意义对于生成摘要至关重要。本文结果的优秀表现再一次印证了简单粗暴的纯data driven方法比不上同时考虑feature的方法。后面的研究可以根据本文的思路进行进一步地改进,相信会取得更大的突破。

Switching Generator/Pointer

文本摘要中经常预见这样的问题,一些关键词出现很少但却很重要,由于模型基于word embedding,对低频词的处理并不友好,所以本文提出了一种decoder/pointer机制来解决这个问题。模型中decoder带有一个开关,如果开关状态是打开generator,则生成一个单词;如果是关闭,decoder则生成一个原文单词位置的指针,然后拷贝到摘要中。pointer机制在解决低频词时鲁棒性比较强,因为使用了encoder中低频词的隐藏层表示作为输入,是一个上下文相关的表示,而仅仅是一个词向量。
在这里插入图片描述

分析:Pointer机制与Copy机制有异曲同工之妙,都是用来解决OOV问题的,本文的最关键的是如何计算开关状态是generator的概率,这一层的计算关系到当前time step是采用generator模式还是pointer模式。

Hierarchical Encoder with Hieratchical Attention

数据集中的原文一般都会很长,原文中的关键词和关键句子对于形成摘要都很重要,这个模型使用两个双向RNN来捕捉这两个层次的重要性,一个是word-level,一个是sentence-level,为了区别与Li的工作,本文在两个层次上都使用注意力模型。
本文模型示意图如下:
在这里插入图片描述

分析:注意力机制的本质是一组decoder与encoder之间相关联的权重,本文在两个层次上重新定义了attention weight,既考虑了某个encoder中每个word对于decoder的重要性,也考虑了该word所在sentence对于decoder的重要性。

Experiments and Results

Gigaword Corpus

为了作对比,本文采用了Rush文章中的Gigaword数据集和他的开源代码来处理数据,形成了380万训练样本和约40万验证样本和测试样本,本文随机选取2000组样本作为验证和测试集来测试本文模型的性能,为了更加公平地对比,本文使用了Rush采用的测试集来对比。
初始词向量的生成是用Word2Vec,但在训练的过程中会更新词向量。训练的参数设置也都采用一般的训练设置。
在Decoder阶段,采用大小为5的beam search来生成摘要,并且约束摘要长度不大于30个单词。
评价指标方面,采用full-length Rouge召回率,然而该指标更加青睐于长摘要,所以在比较两个生成不同长度摘要的系统时并不公平,用full-length F1来评价更加合理。
对比实验共有以下几组:

  1. words-1sent:baseline模型,对应之前的Encoder-Decoder with Attention模型。1sent表示模型的输入是原文的第一句话。
  2. words-lvt2k-1sent:对应之前的Large Vocabulary Trick模型。lvt2k表示decoder的词汇表上限是2000。
  3. words-lvt2k-(2|5)sent:与第二组实验采用相同的模型,只是输入采用了原文的前两句话和前五句话。
  4. words-lvt2k-2sent-exp:对应之前的Vocabulary expansion模型。
  5. words-lvt2k-2sent-hieratt:对应之前的Hierarchical Encoder with Hieratchical Attention模型。
  6. big-words-lvt2k-(1|2)sent:模型与第二组实验相同,但将embedding size和hidden state size增大到了200和400。
  7. big-feats-lvt2k-2sent:对应之前的Feature-rich Encoder模型。
  8. feats-lvt2k-2sent-ptr:对应之前的Switching Generator/Pointer模型。
    实验结果如下:
    在这里插入图片描述

分析:从表中清晰地看到switching generator/pointer模型在各个指标上都是最好的模型,本文的模型在Rush测试集中的结果都优于Rush的ABS+模型。
Gigaword由于其数据量大的特点,常被用于文本摘要任务中作为训练数据。本文的训练、生成参数都沿用了传统的方法,评价指标选择了更合适的F1,共设计了8大组实验,从方方面面对比了各个模型之间的优劣,从多个角度说明了本文模型比前人的模型更加优秀。
数据集对于deep learning是至关重要的,构建一个合适的数据集是一个非常有意义的工作。哈工大之前有一个工作就是构建了微博摘要的数据集,方便了研究中文文本摘要的研究者。

Qualitative Analysis

本文的模型在一些数据的处理会理解错原文的意思,生成一些“错误”的摘要。另外,Switching Generator/Pointer模型不仅仅在处理命名实体上有优势,而且在处理词组上表现也非常好。未来的工作中,将会对该模型进行更多的实验。效果见下图:
在这里插入图片描述

分析:本文模型相对于Rush的模型有了更进一步的效果,但对于文本摘要问题来说,并没有本质上的提升,也会经常出现这样或者那样的错误。指标上的领先是一种进步,但与评价指标太过死板也有关系,现有的评价指标很难从语义这个层次上来评价结果。所以,文本摘要问题的解决需要解决方方面面的问题,比如数据集,比如评价指标,比如模型,任何一个方面的突破都会带来文本摘要问题的突破。

Review

本文是一篇非常优秀的paper,在seq2seq+attention的基础上融合了很多的features、trick进来,提出了多组对比的模型,并且在多种不同类型的数据集上做了评测,都证明了本文模型更加出色。从本文中得到了很多的启发。

  1. 单纯地data-driven模型并不能很好地解决文本摘要的问题,++针对文本摘要问题的特点,融合一些feature到模型之中,对模型的效果有很大的帮助。++
  2. ++他山之石可以攻玉。其他领域的研究成果,小的trick都可以尝试借鉴于文本摘要问题之中,比如seq2seq+attention的技术从机器翻译中借鉴过来应用于此,比如LVT技术等等。++
  3. 文本摘要问题的解决需要解决好方方面面的问题,不仅仅是模型方面,还有数据集,还有评价指标,每个方面的进步都会是一大进步。
  4. deep learning技术训练出的模型泛化能力和扩展能力还有很长的路要走,对训练数据的严重依赖,导致了泛化能力和扩展能力的不足。针对特定的问题,构建特定的训练数据集,这对corpus的建设提出了更高的要求。

代码分析

just show code: https://github.com/tshi04/AbsSum

携程Hadoop跨机房架构实践_yukangkk的技术博客-CSDN博客

$
0
0

陈昱康,携程架构师,对分布式计算和存储、调度、查询引擎、在线离线混部、高并发等方面有浓厚兴趣。

本文将分享携程Hadoop跨机房架构实践,包含Hadoop在携程的发展情况,整个跨机房项目的背景,我们跨机房的架构选型思路和落地实践,相关的改造和对未来的展望,希望给大家一些启迪。

 

一、Hadoop在携程的落地及发展情况

携程Hadoop是从2014年引进的,基本上每年较前一年以两倍的速度在增长,我们对Hadoop集群做了大量性能方面的改造和优化。

1)目前,HDFS存储层面拥有数百PB的数据,数千的节点,分为4个namespace做Federation,自研了namenode proxy来路由rpc到对应的namespace,2019年初上了一套基于Hadoop 3的Erasure Code集群来做对用户透明的冷热存储分离,目前已迁移几十PB数据到EC集群,节省一半的存储资源。

 

2)计算层面,搭建了两套离线和一套在线Yarn集群做Federation,总量15万+core,每天30万+ Hadoop作业,其中90%为spark。所有节点分布在四个机房,其中离线集群部署在其中两个机房,在线集群部署在三个机房。

二、跨机房项目背景

来看下整个项目的背景。之前我们的Hadoop机器部署在金和福两个机房,95%的机器在福。去年底,携程自建了日机房,同时福机房的机架数达到了物理上限,没办法继续扩容。另外按照目前计算和存储的增速来看,预计2024年底集群规模会达到万台,新采购的机器只能加在日机房,我们需要多机房架构和部署的能力。

这其中的难点在于,两个机房的带宽仅200Gbps,正常情况下网络延迟在1ms,当带宽打满情况下,延迟会达到10ms,同时会有10%的丢包率。我们需要尽可能减少跨机房的网络使用带宽。

 

2.1 原生Hadoop架构问题

看下原生Hadoop的问题。网络IO开销主要来自两方面,Shuffle读写和HDFS读写。

1)先看shuffle,MR和Spark作业的前一个stage会将中间临时文件刷到磁盘,下一个stage通过网络来Fetch。如果分配的map task在机房1,reducetask在机房2,就会产生跨机房流量开销。

2)其次HDFS层面,对于读场景,三个副本存放在不同的节点,客户端会从namenode拿到按照距离排好序的副本信息,优先从最近的副本所在的节点读取。但是当三个副本都和客户端不在一个机房的情况下,就会产生跨机房读网络IO开销。写场景的话,HDFS采用Pipeline写,选择副本时只考虑到机架层面的存放策略,会将三个副本放在两个机架,如果选择的两个机架跨机房了,也会有跨机房网络写开销。

2.2 可选的方案

当时我们讨论下来有两种架构解决方案,多机房多集群和多机房单集群,两种各有利弊。

2.2.1 多机房多集群

多机房多集群方案的优势是不需要修改源代码,可以直接部署。缺点是:

1)对用户不透明,用户需要修改配置,指定提交到某个集群;

2)运维成本较高,每个机房有独立的集群,配置管理麻烦;

3)最重要的是第三点,数据一致性难以保证。有些公共数据需要被多个事业部访问的话,只能跨机房读取,这个IO无法省掉,而如果用distcp,在本机房也放一些副本以省掉这部分流量开销的话,又会由于副本是通过不同的namenode管理的,导致数据可能会有不一致的问题。

 

2.2.2 多机房单集群

再来看多机房单集群架构,劣势是需要改Hadoop源代码,因为动了BlockManager的核心代码逻辑,会有风险,需要做好完备的测试和验证。但是好处也很明显。

1)对用户透明,用户不需要关心作业提交到了哪个机房,副本存放在哪里,无感知;

2)运维部署简单;

3)因为是由一个namenode来管理副本状态,所以可以保证多机房副本的一致性。

主要由于第一和第三点优势,我们希望保证用户使用时的透明性和一致性,最终选择了多机房单集群方案。

 

 

三、先期尝试——在线离线混部跨机房

其实对于第一种多机房多集群方案,我们之前在在线离线混部项目中采用过。当时的场景是,离线集群的资源在凌晨高峰打满,白天低峰较空。而在线k8s集群恰恰相反,我们希望利用k8s凌晨的计算资源帮我们减轻负担。而k8s集群部署在金和欧机房,数据没有本地性。所以我们希望将一些cpu密集,但是对IO压力又不大的作业,能分配到在线集群。

我们在k8s上部署了一套Yarn集群,并开发了一套作业资源画像系统,主要是采集作业的vcore/memory使用,shuffle,hdfs读写等metrics。由于zeus调度系统提交的作业一般不怎么修改,每个作业的历史执行时间和所消耗资源都有趋同性,我们按照zeus jobid聚合,根据历史多次执行情况分析出每个作业的资源使用趋势。下次作业启动时zeus会将shuffle量和hdfs读写量较低的作业分配到在线集群跑。

另外由于在线集群也跨了两个机房,我们在FairScheduler上开发了基于label的调度,一个label对应一个机房,会根据每个label的负载,动态分配作业到所属的label,一个app所有的task只会固定在一个label内执行,这样机房间不会产生shuffle流量。该方案上线后,可以缓解离线集群8%的计算压力。

四、多机房单集群方案

我们规划一个事业部对应的一个默认机房,数据尽可能在同机房内流动。由此对于多机房单集群架构改造主要包括四个方面:多机房单HDFS集群,多机房多Yarn集群,自动化数据和作业迁移工具,跨机房带宽监控和限流。

4.1 多机房单HDFS架构

先来看HDFS改造,我们改造了namenode源码,在机架感知之上,增加了机房感知,NetworkTopology形成了<机房,机架,Datanode>三元组。这样客户端读block时,计算出来和副本所在节点的距离,本地机房肯定小于跨机房,会优先读本地机房数据。

另外我们在namenode中增加了跨机房多副本管理能力,可以设置目录的多机房副本数,比如只在机房1设置3个副本,或者机房1和机房2各设置三个副本,对于没有设置跨机房副本的路径,我们会在zookeeper和内存中维护一个用户对应默认机房的mapping关系,写文件addBlock的时候,根据ugi找到对应的机房,在该机房内选择节点。

Decommission或者掉节点时候会有大量的副本复制操作,极易容易导致跨机房带宽被打爆。对此,我们修改了ReplicationMonitor线程的逻辑,在副本复制的时候,会优先选择和目标节点相同机房的源节点来进行复制,降低跨机房带宽。

为了持久化跨机房路径副本信息,我们增加Editlog Op来保存每一次跨机房副本设置变更记录,fsimage中新增了跨机房副本Section,这样namenode只会保存一份元数据,failover切换到standby的时候也能加载出来,没有其他外部依赖。

4.2 改造Balancer&Mover&EC

HDFS层面还有其他一些改造,比如Balancer,我们支持了多实例部署,每个Balancer增加IP范围列表,每个机房会起一个,只balance本机房IP的datanode的数据。对于Mover,我们也支持了多机房多实例部署,因为mover是在客户端选择目标副本节点的,所以需要改造按照目录的跨机房副本放置策略在客户端来选择合适的节点。

 

这边要注意一点的是,尽量保证proxy节点和target节点在同一个机房,因为真正迁移的网络IO是在这两个节点发生的。另外我们在新的日机房部署了一套基于Hadoop 3的Erasure Code集群,会将一部分历史冷数据迁移过去,目前这块没有做跨机房的代码改造,我们的EC迁移程序只会迁移那些已经被迁移到日机房的BU的冷数据到EC集群。

 

4.3 副本修正工具-Cross FSCK

由于我们有多个namespace,跨机房版本的HDFS是一个一个ns灰度上线的,灰度过程中,其他ns的副本放置还没有考虑机房维度,所以我们开发了Cross IDC Fsck工具,可以感知跨机房配置策略,来修正不正确放置的副本。

因为需要不停的读取副本信息,会产生大量的getBlockLocations rpc请求,我们将请求改成从standby namenode读,一旦发现不匹配会调用reportBadBlocks rpc给active namenode,BlockManager会删除错误的副本,重新选择新的副本。由于这个操作比较重,高峰时间对HDFS会有影响,所以我们在客户端加了rpc限流,控制调用次数。

 

4.4 多机房多Yarn集群

下面来看下Yarn的改造,我们在每个机房独立部署一套Yarn集群,自研了ResourceManager Proxy,它维护了用户和机房的mapping关系,这个信息是和namenode共用的,都是内存和zookeper各一份。

修改了Yarn Client,用户提交的Yarn作业会首先经过rmproxy,然后再提交到对应Yarn集群。这样一个app所有的Task只会在一个机房内调度,不会产生跨机房Shuffle。如果要切换用户账号对应的机房和集群也很方便,会立马通过zookeeper通知到所有rmproxy,修改内存中的mapping关系。

rmproxy可以多实例部署,互相独立,同时在Yarn Client做了降级策略,在本地定期缓存一份完整的mapping关系,一旦所有rmproxy都挂了,client也能在这段时间做本地路由提交到对应集群。

adhoc和分析报表大量使用了Sparkthrift service,presto,hive service来做计算。对这块常驻服务也做了改造,每个机房各部署一套,客户端之前都是通过jdbc直连对应的thrift service,改造后接入rmproxy,会先从rmproxy中拿到用户对应机房的服务jdbc url,再连接,这块同样对用户透明。

五、自动化迁移工具

由于日机房的节点会按采购到货情况逐步往上加,所以需要按照计算和存储的容量来规划该迁移哪些账号,这是一个漫长的过程,希望能尽量做到自动化迁移,以BU->账号的粒度进行迁移,我们梳理了迁移流程,分为如下四步:

1)批量设置BU对应Hive账号开始迁移(初始为3:0,即福机房3份,日机房0份)

2)按照Hive账号下的DB和用户Home目录依次设置3:3,数据复制到日机房

3)账号和队列迁移到日机房

4)观察跨机房流量,回收福机房的计算和存储资源(设置0:3)

迁移时间过程中有些注意点:

1)迁移过程会耗费大量跨机房网络带宽,需要在集群低峰时间执行,我们是放在早上10点到晚上11点之间,其他时间会自动暂停迁移,否则会影响线上报表和ETL作业的SLA。

2)即使在白天迁移,也需要控制迁移的速率,一方面是减少namenode本身的处理压力,另一方面也是降低带宽,白天也会有一些ETL和adhoc查询需要跨机房访问数据,若打满的话也会有性能影响。迁移中我们会实时监控namenode的UnderReplicatedBlocks和跨机房流量metrics,根据这些值动态调整迁移速率。

3)实时监控被迁移机房的hdfs可用容量,包括不同的StorageType的,防止磁盘打爆。还有有些hive DB库目录设置了hdfs quota,也会由于迁移设置3:3超过quota而报错,我们会自动暂时调高quota,等迁移整体完成后再把quota调回去。

4)公共库表由于被多个BU都有访问依赖,需要提前设置多机房的副本,我们有个白名单功能,可以手动设置,一般设为2:2,每个机房各放两份。

 

 

六、跨机房带宽监控&限流

实践中有些BU的表,会被当做公共表来使用,我们需要识别出来,设置跨机房多副本策略。目前的hdfs audit log中,没有dfsclient访问datanode,datanode和datanode传输数据的实际流量audit信息,而我们需要这部分信息来看实际的路径和block访问情况,做进一步数据分析,另外当流量打爆的况下,需要有一个限流服务按照作业优先级提供一定的SLA保障,优先让高优先级作业获取到带宽资源。

对此我们开发了限流服务,在dfsclient和datanode代码中埋点实时向限流服务汇报跨机房读写路径,block读写大小,zeus作业id等信息, 限流服务一方面会记录流量信息并吐到ES和HDFS做数据分析,另一方面会根据作业的优先级和当前容量决定是否放行,客户端只有获得限流服务的Permit,才能继续执行跨机房读写操作,否则sleep一段时间后再次尝试申请。

有了实际的流量信息后,通过离线数据分析,就很容易知道哪些表会被其他BU大量读,通过自动和手动结合方式设置这部分表的跨机房副本数2:2。设置后跨机房Block读请求量下降到原来的20%。跨机房带宽原来是打满的,现在下降到原来的10%。

 

 

七、总结与未来规划

总结一下,本文主要介绍了携程Hadoop跨机房实践,主要做了如下改造:

1)实现单hdfs集群机房感知功能,跨机房副本设置

2)实现基于rm proxy和yarn federation的计算调度

3)实时自动化存储和计算迁移工具

4)实现跨机房流量监控和限流服务

 

目前整套系统已在线上稳定运行了半年,迁移了40%的计算作业和50%的存储数据到新机房,跨机房带宽流量也在可控范围之内,迁移常态化,用户完全不需要感知。

未来我们希望能智能决定该迁移哪些账号,大多数公共路径设置为2:2四个副本,比通常会多加一个副本的物理存储量,现在是设置在表层面,希望能进一步细化到分区层面,因为分析出来大多数下游作业都是只依赖最近一天或者一周的分区。所以一旦过了时间,完全可以将历史分区设置回三副本来减少存储开销。最后是将跨机房的改造也应用到基于Hadoop 3的EC集群,也支持跨机房的能力。


弱网络环境下最优调度和优化传输层协议方案_justinjing的专栏-CSDN博客_网络较差用什么协议

$
0
0

转载: http://gad.qq.com/article/detail/10123

背景

与有线网络通信相比,无线网络通信受环境影响比较大(例如高层建筑、用户移动、环境噪音、相对封闭环境等等),网络的服务质量相对来说不是非常稳定,导致用户经常会在弱信号的网络环境下通信。而当用户在这种网络环境下通信时,则存在较多的丢包、误码、超时、连接中断以及难以接入网络等情况。通信除了受环境影响以外,网络覆盖和室分系统不完善、邻区漏配、导频污染、过载控制等原因也都会产生无线呼叫掉线、服务质量下降等问题。

如何度量和模拟“弱网络”对移动APP的开发有着重大的意义:节约测试成本、易于问题重现、加快产品上线等。一般的方法是使用“丢包率”和“网络延时”来定义和衡量“弱网络”[10-12]。

在无线网络通信中,无线资源(频率、时间、空间等)是非常稀缺的。为了满足更多用户的PS业务需求,移动系统会尽可能地把无线资源复用给不同的用户。比如,当用户在一定时间内(对于3G,一般是15秒,对于2G,可能会比较短)不再传送数据时,移动系统会主动回收分配给该用户的无线资源,以复用给其它用户。当该用户再次需要通信时,移动设备(Mobile Equipment)需要重新申请无线连接,然后才能发送数据。另外,当ME处于能够随时发送数据的状态时,会消耗大量的电能。所以为了节能,ME发现在一定的时间内(一般是2~10秒)没有数据传送时,也会主动要求释放无线资源。申请无线资源是非常昂贵的操作,需要比较多的信令(大约相当于发送或接收一条短消息),如果频繁发送小数据包(比如心跳),容易导致“信令风暴”。上述的这种类似“快速休眠”特性的延时到底有多长,与ME和网络都有关系,没有办法一概而论。无线网络的延时情况可以参考图1:

 

需要注意的是:在实际情况下,对于GPRS/EDGE网络出现秒级别的延时也不奇怪。

移动终端APP在通信的过程中消耗电量的情况如何,降低电能消耗的原则有[13]:

1)3G下传输数据消耗的电量是WIFI下的几十倍;

2) 手机在传输数据的状态下消耗的电量要远远高于IDLE状态下消耗的电量;

3)降低发包频率,尽可能地把数据包合并,然后一起发送;

 

ME通过无线网络接入服务器的整个过程如下(以ME主动发起请求为例):

A)分配无线连接,建立上下行专用的高速信道;

B) 解析接入点(APN)域名,获取接入Internet的网关GPRS支持节点(GGSN)的IP,然后GGSN进行鉴权,以及为ME分配IP;

C)GGSN执行DNS查询;

D)建立TCP连接;

E)接收或发送数据;

F)如果没有数据传输,超过一定的时间,释放无线连接;

G)如果没有数据传输,超过一定的时间,释放TCP连接;

 

在整个过程中,减少DNS的影响可能是首要的目标,移动网络的DNS有如下特点[1]:

·             有一部分全国范围的DNS承载了超过40%的全网用户;

·             很多山寨机的终端local dns设置是错误的;

·             也包括有线网络遇到的问题:域名劫持、DNS污染、老化和脆弱等问题;

·             现有的DNS不能把用户调度到服务的“最优接入点”;

 

从以上分析可知,如何保证移动互联网的产品提供稳定的、可预期的服务质量,具有非常大的挑战。以下几点原则可能会有帮助:

1) 断线重连。这可能是最重的一个特性,因为在无线网络中有太多的原因导致数据连接中断了。

2) 由于创建连接是一个非常昂贵的操作,所以应尽量减少数据连接的创建次数,且在一次请求中应尽量以批量的方式执行任务。如果多次发送小数据包,应该尽量保证在2秒以内发送出去。在短时间内访问不同服务器时,尽可能地复用无线连接。

3) 优化DNS查询。应尽量减少DNS查询、避免域名劫持、DNS污染,同时把用户调度到“最优接入点”。

4) 减小数据包大小和优化包量。通过压缩、精简包头、消息合并等方式,来减小数据包大小和包量。

5) 控制数据包大小不超过1500,避免分片。包括逻辑链路控制(Logic Link Control)分片、GGSN分片,以及IP分片。其中,当数据包大小超出GGSN所允许的最大大小时,GGSN的处理方式有以下三种:分片、丢弃和拒绝。

6)优化TCP socket参数,包括:是否关闭快速回收、初始RTO、初始拥塞窗口、socket缓存大小、Delay-ACK、Selective-ACK、TCP_CORK、拥塞算法(westwood/TLP/cubic)等。关于这些参数的优化建议可以参考[1-8, 30]。做这件事情的意义在于:由于2G/3G/4G/WIFI/公司内网等接入网络的QoS差异很大,所以不同网络下为了取得较好的服务质量,上述参数的取值差异可能会很大。

7) 在弱网络的情况下,TCP协议中的ACK包是非常昂贵的,延时甚至能够达到秒级别[14],而TCP协议的拥塞控制、快速重传、快速恢复等特性都非常依赖接收端反馈的ACK包。可想而知,如果发送端接收到的ACK包延时太长,会严重影响TCP协议的效率。但是如果发送ACK太多又会占用宝贵过多的无线资源。在移动网络下通信,“在可靠的连接上,如何在减少ACK包的情况下,降低数据包的延时”是研究的热点[15-28]。基本的思想:平衡冗余包和ACK包个数,达到降低延时,提高吞吐量的目的。例如SGSN和GGSN之间的通信实现:二者之间通过UDP协议通信,发送者在无新的数据包的情况下,每隔一定的时间重试已发送的包,达到最大重试次数后,则丢弃该包。

8) TCP的拥塞控制算法是以“丢包意味着网络出现拥塞”为假设设计的,很明显这个假设在无线网络环境下是不合适的。但是在无线网络环境下,在设计可靠UDP协议时是否能够完全丢弃拥塞控制呢?[39]中提出了几种在无线网络环境下的TCP友好的拥塞控制算法。

9) 长连接/短连接,支持不同协议(TCP/UDP, http、二进制协议等),支持不同端口等。关于更多的建议请参考[1,14,29]。

 

目的

在无线网络环境下,本文将从最优调度和优化传输层协议两个方面来讨论如何保证客户端和服务器的通信成功率,以及提高通信效率。

 

最优调度设计

系统分解

设计一个新的DNS服务器来实现最优调度,其拓扑结构如下:

 

TGCP SDK的职责:

I) 用HTTP的Get/Post方法从DNSvr获取游戏服务器和DNSvr本身的最优接入点列表。Get/Post方法的查询参数包括uin/openid、客户端版本号、IP列表的MD5(注意IP顺序)、域名列表、VIP、ServiceID等。

II) 缓存访问游戏服务器和DNSvr的IP列表,以及其它元数据(比如IP列表等),且以APN为主键[34]。

III) 满足一定的条件下,要主动更新缓存的IP列表,例如缓存过期。

 

注意:这些职责也可以从TGCP SDK中分离,形成一个独立的SDK。

 

Tconnd的职责:

 路由查询请求给活动的DNSvr;

DNSvr的职责:

I) 根据静态和动态策略来决定客户端的“最优接入点”。静态策略:根据uin/openid、客户端版本号或者强制规则来决定IP列表;动态策略:灯塔根据测速数据动态决定用户的游戏服务器接入点。

II) 支持以手动或自动的方式拉黑某些IP。自动方式:由游戏服务器的接入tconnd向DNSvr上报其是否存活(需要向多个点上报,包括用公网IP上报),如果在一定时间内没有接收到上报或者上报消息中明确所有的逻辑服务器已经挂掉,则自动拉黑相应的IP。如果业务恢复,则自动激活相应的IP。如果项目组接入TGW,对于某个IP和端口是否可用,则需要考虑进程与VIP的映射关系。

III) 在tcaplus中缓存灯塔的计算结果。此时要求DNSvr能够根据客户端IP判断所属的国家、省份、运营商和网关(可以通过访问MIG的IP库实现)[37]。如果缓存了灯塔的计算结果,当缓存超时后,要重新从灯塔拉取相应数据。

 

灯塔的职责(即哈雷项目[38]):

根据客户端IP和服务器接入点IP,返回最优的接入点列表,包括IP的排序,以及客户端接入的国家、省份、运营商、APN和网关。

 

Tcaplus的职责:

保存游戏接入的IP列表和端口、静态策略,或缓存灯塔的计算结果;

主要的流程

·             客户端批量解析域名流程

1.          TGCP以APN和域名列表为关键字查询缓存,如果存在且没有过期,则直接把IP返回给用户。如果指定强制解析域名列表,则跳过此步骤;

2.          TGCP用预配置或缓存的IP向DNSvr发起查询请求,如果成功返回结果,则执行步骤3,否则,重试IP列表中的其它IP,如果都失败,则用域名访问DNSvr。注意:如果是结果格式不正确,则使用上次的IP重试,不要更换IP重试[14,29]。

3.          DNSvr比较客户端IP列表和当前最新的IP列表的MD5,如果相等,则告诉客户端不需要更新本地缓存。否则,TGCP把接入游戏服务器和DNSvr的IP列表写入本地。注意:在访问服务器时,这些IP的优先级要高于静态配置在客户端的IP。

 

·             客户端使用域名访问游戏服务器流程

1.          如果本地存在有效的IP(即存在对应APN的IP列表,且没有失效),则使用IP访问游戏服务器。

2.          否则,发起“客户端批量解析域名流程”后,再访问游戏服务器。

 

·             游戏服务器接入tconnd主动上报状态流程(仅供参考)

1.          Tconnd周期性向DNSvr上报心跳消息,其中包含本接入点是否可用的信息。

2.          DNSvr在一定的时间内没有收到心跳消息或者相应的接入点不可用,则把相应的IP和端口拉黑,黑掉的IP不在下发给客户端。

注意:实际部署的时候,游戏接入的Tconnd要向多个DNSvr接入tconnd上报。

·             向客户端主动push接入点列表的流程

1.          当TGCP连接到游戏服务器接入的Tconnd时,Tconnd要向DNSvr发起请求,以校验当前接入IP的质量和时效性。如果IP列表发生变化,Tconnd要把最新的IP列表下发给客户端缓存起来。

2.          当TGCP下次访问游戏服务器时,则使用最新的IP列表。

·             客户端访问DNSvr失败的流程

1.          如果访问DNSvr失败(包括IP+域名),如果配置了本地IP,则直接用IP访问游戏服务器,否则用域名访问。

优化传输层协议设计

在原有tconnd支持的可靠UDP的基础之上,添加以下逻辑:

·              数据压缩;

·             数据加密;

·             合并多个数据包;

·             支持流式数据传输,便于控制每个UDP包的大小,也便于数据加密和压缩;

·             可选地支持[39]中的拥塞控制算法;

·             即使没有接收到ACK包,也需要主动重试以发送的数据包;

参考文件

[1]  一秒钟法则

[2] Linear network coding

[3] Network Coding for Wireless Applications: A Brief Tutorial

[4] Application of Network Coding in TCP

[5] CodeMP: Network Coded Multipath to Support TCP in Disruptive MANETs

[6] Enhanced Network Coding Scheme for Efficient Multicasting in Ad-Hoc Networks

[7] Evaluation and Enhancement of TCP with Network Coding in Wireless Multihop Networks

[8] Linear Network Coding to Increase TCP Throughput

[9] TenDoc: Network Coding-based Software for Wireless Ad hoc Networks,

[10] Network coding meets TCP: theory and implementation

[11] NCAPQ: Network Coding-Aware Priority Queueing for UDP Flows over COPE

[12] The Importance of Being Opportunistic: Practical Network Coding for Wireless Environments

[13] TCP and Network Coding Equilibrium and Dynamic Properties

[14] Low Computational Complexity Network Coding for Mobile Networks

[15] Video Streaming with Network Coding

[16] TCP-Friendly Congestion Control over Wireless Networks

如何构建更健壮的在线系统_heiyeluren的blog(黑夜路人的开源世界)-CSDN博客

$
0
0

【原创】如何构建更健壮的在线系统

 

作者: 黑夜路人(heiyeluren)

时间:2020年11月

 

说明:本文主要面对PHP为主要开发语言的业务系统,Golang、Java等语言可以学习参考。

 

 

0. 背景

 

Why 为什么要健壮的系统?


1. 为什么测试好好的,上到线上代码一堆bug,一上线就崩溃或者一堆问题?

2. 为什么感觉自己系统做的性能很好,上线后流量一上来就雪崩了?

3.  为什么自己的系统上线以后出问题不知道问题在哪儿,完全无法跟踪?

 

What 构建健壮系统包含哪些方面?


软件系统架构关注:可维护性、可扩展性、健壮性(容灾)

做出健壮的软件和系统的三个方向:

1. 良好的软件系统架构设计

2. 编程最佳实践和通用原则

3. 个人专业软素质

 

How 构建健壮系统执行细节?



1、系统架构:网络拓扑是什么、用什么存储、用什么缓存、整个数据流向是如何、那个核心服务采用那个开源软件支撑、用什么编程语言来构建整个软件连接各个服务、整个系统如何分层?

2、系统设计:采用什么编程语言、编程语言使用什么编程框架或中间件、使用什么设计模式来构建代码、中间代码如何分层?

3、编程实现:编程语言用什么框架、有什么规范、编程语言需要注意哪些细节、有哪些技巧、那些编程原则?

 


 

一、系统架构与设计遵循哪些关键原则?

 

0. 用架构师的视角来思考问题

程序员视角更多考虑的是我如何快速完成这个项目,架构师视角是不仅是我完成整个项目,更多需要思考整个架构是否清晰、是否可维护、是否可扩展、可靠性稳定性如何,整个技术框架和各种体系选型是否方便容易开发维护;整个思考维度和视角是完全不一样的。程序员视角是执行层面具体编码的视角,架构师视角是设计师的视角,会更宏观,想的更远。

(比如编程语言选型 PHP vs Golang、Java vs Golang、C++ vs Rust等等)

 

1. 服务架构链路要清晰

整个服务架构包含:接入层(网关、负载均衡)、应用层(PHP程序等)、服务层(微服务接口等)、存储层(DB、缓存、检索ES等)、离线计算层(不一定包含,一般会以服务层或存储层出现),服务互相不要混了,各干各的活;

服务链路要清晰,每一层各司其职:

 

2. 每个服务都需要考虑灾备或分布式,不能出现单点架构(持续进化)

比如mysql不能只有1个,最少考虑 master/slave架构,保持数据不丢,访问不断,数据量大以后是否做分布式存储等等,redis等同样;后端微服务层同样必须多台服务(通过ServiceMesh等调度,或者是etcd/zookeeper等服务发现等方式);

 

分布式进化:

初级阶段:

 

中级阶段:

 

高级阶段:

 

3. 每个服务都必须考虑最优的技术选型(最佳实践)

比如PHP框架选择,语言选择都是稳定成熟可靠(高性能API选Swoole、Golang等,业务系统考虑Laravel、Symfony、Yii等主流框架);比如微服务框架,远程访问接口协议(TCP/UDP/QUIC/HTTP2/HTTP3等),信息内容格式可靠(json/protobuf/yaml/toml等);选择的扩展稳定可靠(PHP各个可靠扩展,具备久经考验,超时、日志记录等基础特性);

一些优秀开源软件推荐:(个人最佳实践推荐)

 

4. 服务必须考虑熔断降级方案

在大流量下,如何保证最核心服务的运转(比如在线课堂中是老师讲课直播重要,还是弹幕或者点赞重要),需要把服务分层,切分一级核心服务、二级重要服务、三级可熔断服务等等区分,还需要有对应的预案;(防火/防火演练等);需要对应的系统支持,比如API网关的选择使用。(OpenResty/Kong/APISIX等,单核2W/qps,4核6W/qps)

 

5. 运维部署回滚监控等系统需要快速高效



        整个代码层次结构,编译上线整个流程,如何是保证高效率可靠的;上线方便,回滚也方便,或者回滚到任何一个版本必须可靠;日志监控、系统报警等等。(常规运维上线系统  Jenkins/Nagios/Zabbix/Ganglia/Grafana/OpenFalcon/Nightingale)

监控系统价值:

 

监控系统工作原理:

监控系统选择:

 

6. 编写的接口和前后端联合调试要方便快捷

接口可靠性测试必须充分,并且善于使用好的工具(比如Filder/Postman/SoapUI等),并且对应接口文档清晰,最好是能够通过一些工具生成好API文档(APIJson/Swagger/Eolinker),大家按照对应约定格式协议进行程序开发联调等;

 

 

 

7. 让整个系统完全可监控可追踪

比如对应的trace系统(包含trace_id),把从接入层、应用层、服务层、存储层等都能够串联起来,每个环节出现的问题都可追踪,快速定位问题找到bug或者服务瓶颈短板;也能够了解整个系统运行情况和细节。(比如一些日志采集系统 OpenResty/Filebeat/Flume/LogStash/ELK)

 

8. 系统中的每个细节都是需要可以量化的,不能是模糊不明确的

比如单个服务的QPS能力(预计流量需要多少服务器)、并发连接数(系统设置、系统承载连接)、单个进程内存占用、线程数、网络之间访问延迟时间(服务器之间延迟、机房到机房的延迟、客户端到服务器的延迟)、各种硬件性能参数(磁盘IO、服务器网卡吞吐量等)。

比如:【QPS计算PV和机器的方式】

 

原理:每天80%的访问集中在20%的时间里,这20%时间叫做峰值时间

公式:( 总PV数 * 80% ) / ( 每天秒数 * 20% ) = 峰值时间每秒请求数(QPS)

机器:峰值时间每秒QPS / 单台机器的QPS = 需要的机器

 

QPS统计方式

QPS = 总请求数 / ( 进程总数 * 请求时间 )

QPS: 单个进程每秒请求服务器的成功次数

 

单台服务器每天PV计算

公式1:每天总PV = QPS * 3600 * 6

公式2:每天总PV = QPS * 3600 * 8

 

问:每天300w PV 的在单台机器上,这台机器需要多少QPS?

答:( 3000000 * 0.8 ) / (86400 * 0.2 ) = 139 (QPS)

 

问:如果一台机器的QPS是58,需要几台机器来支持?

服务器数量 = ceil( 每天总PV / 单台服务器每天总PV )

答:139 / 58 = 3

 

PS: 在实际情况中,会把这个考虑的更多一点,就是把QPS再往多了调一调,以防万一

 

 

9. 让你的应用无状态化、容器化、微服务化


让你的应用可以做到:去中心化、原子化、语言无依赖、独立自治、快速组合、自动部署、快速扩容,采用微服务+容器化来解决。

在面对大并发量请求情况下,在寻求系统资源的状态利用场景,大部分考虑的都是横向扩展,简单说就加机器解决。在docker和k8s的新的容器化时代,横向扩展最好的方法是快速扩充新的应用运行容器;阻碍我们横向扩展的最大的阻碍就是“有状态”,有状态就是有很多应用会存储私有的东西在应用运行的内存、磁盘中,而不是采用通用的分布式缓存、分布式存储解决方案,这会导致我们的应用在容器化的情况下无法快速扩容,所以我们的应用需要“去有状态化”,让我们的应用全部“无状态化”。

微服务化的逻辑是让的每个服务可以独立运行,比如说用户中心系统对外提供的不是代码级别的API,而是基于RESTfu或者gRPC协议的远程一个服务多个接口,这个服务或接口核心用来解决把整个用户中心服务变成独立服务,谁都可以调用,并且这个服务本身不会对内外部有太多的耦合和依赖,对外暴露的也只是一个个独立的远程接口而已。

尽量把我们的关键服务可以抽象成为一个个微服务,虽然微服务会增加网络调用的成本,但是每个服务之间的互相依赖性等等都降低了,并且通过容器技术,可以把单给微服务快速的横向扩展部署增加性能,虽然不是银弹,也是一个非常好的解决方案。

基于容器的微服务化以后,整个业务系统从开发、测试、发布、线上运维、扩展 等多个方面都比较简单了,可以完全依赖于各种自动半自动化工具完成,整个人工干预参加的成分大幅减少。

 

从单体服务到微服务:

 

微服务后应用架构变化:

 

容器简单工作原理:

/*简单容器底层机制实现模拟演示
  说明:主要利用Linux的Namespace机制来实现,linux系统中unshare命令效果类似
  Docker 调用机制是: Docker -> libcontainer(like lxc) -> cgroup -> namespace
  Code by Black 2020.10.10
*/
#include <sys/types.h>
#include <sys/wait.h>
#include <linux/sched.h>
#include <sched.h>
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#define STACK_SIZE (1024 * 1024)

static char container_stack[STACK_SIZE];
char* const container_args[] = { "/bin/bash",  NULL };

//容器进行运行的程序主函数
int container_main(void *args)
{
    printf("容器进程开始. \n");
    sethostname("black-container", 16);
    //替换当前进程ps指令读取proc环节
    system("mount -t proc proc /proc");
    execv(container_args[0], container_args);
}

int main(int args, char *argv[])
{
    printf("======Linux 容器功能简单实现 ======\n");
    printf("======code by Black 2020.10 ======\n\n");
    printf("正常主进程开始\n");
    // clone 容器进程: hostname/消息通信/进程id 都独立 (CLONE_NEWUSER未实现)
    int container_pid = clone(container_main, container_stack+STACK_SIZE, 
        SIGCHLD | CLONE_NEWUTS | CLONE_NEWIPC | CLONE_NEWPID | CLONE_NEWNS | CLONE_NEWNET, NULL);
    // 等待容器进程结束
    waitpid(container_pid, NULL, 0);
    //恢复 /proc 下的内容
    system("mount -t proc proc /proc");
    printf("主要进程结束\n");
    return 0;
}

 

代码执行效果:

 

Docker工作机制:

 

K8s工作机制:

 

微服务+容器化后的架构:

容器+微服务运维部署架构:

 

 

架构设计结束语:


在架构设计中,没有最好的架构,只有适合业务的架构,只有持续优化进步的架构。

 


 

二、系统程序实现里哪些关注原则

 

0. 充分理解你的业务需求

保证理解业务后,整个程序设计符合需求或者未来几个月可以扩展,既不做过度设计,也不做各种临时硬编码。

 

1. 代码核心原则:KISS(Keep it simple,stupid)

来自于Unix编程艺术,你的东西必须足够简单足够愚蠢,好处非常多,比如容易读懂,容易维护交接,出问题容易追查等等。

因为,长期来看复杂的东西都是没有生命力的。(x86 vs ARM / 微型服务器 vs 大型机 / 北欧简约风 vs 欧洲皇室风 / 现在服装 vs 汉服)

 

2. 遵守编码规范,代码设计通用灵活

学会通过用函数和类进行封装(高内聚、低耦合)、如何定义函数,缩进方式,返回参数定义,注释如何定义、减少硬编码(通过配置、数据库存储变量来解决)。

 

3. 设计模式和代码结构需要清晰


比如我们常规使用的MVC设计模式,为了就是把各个层次代码区分开。(M干好数据读取或者接口访问的事儿,C干好变量收发基本教研,V干好模板渲染或者api接口输出;M可以拆分成为:DAO数据访问层和Service某服务提供层);

比如一个主流的MVC层结构图:

 

 

4. 程序中一定要写日志,代码日志要记录清晰


(Info、Debug、Waring、Trace等等,调用统一的日志库,不要害怕多写日志)

//程序里关键的日志都要记录(Debug/Notice/Warning/Error 等信息可以打印,warning/error 信息是一定要打印的
SeasLog::debug('TRACE_ID:{traceId}; this is a {userName} debug',array('{traceId}'=>9527, '{userName}' => 'Black'));
SeasLog::notice('TRACE_ID:{traceId}; this is a notice msg', array('{traceId}'=>9527));
SeasLog::warning('TRACE_ID:{traceId}; this is a warning', array('{traceId}'=>9527));
SeasLog::error('TRACE_ID:{traceId}; a error log', array('{traceId}'=>9527));
  •  

 

5. 稳健性编程小技巧(个人最佳实践)


a. 代码里尽量不要使用else(超级推荐,Unix编程艺术书籍推荐方法)

//获取一个整形的值 (使用else,共12行)
function getIntValue($val) {
    if ( $val != "" ) {
    $ret = (int)$val;
        if ($ret != 0 ) {
            return $ret;
        } else {
            return false;
        }
    } else {
        return false;
    }
}
//获取一个整形的值(不使用else,共10行)
function getIntValue($val) {
    if ( $val == "" ) {
        return false;
    }
    $ret = (int)$val;
    if ($ret == 0 ) {
        return false;
    } 
    return $ret;
}
  •  

b. 所有的循环必须有结束条件或约定,并且不会不可控

c. 不要申请超级大的变量或内存造成资源浪费

d. 无论静态还是动态语言,内存或对象使用完以后尽量及时释放

e. 输入数据务必要校验,用户输入数据必须不可信。

f. 尽量不要使用异步回调的方式(容易混乱,对js和nodejs的鄙视,对协程机制的尊敬)

 

6. 所有内外部访问都必须有超时机制:保证不连锁反应雪崩


超时是保证我们业务不会连带雪崩的很关键的地方,比如我们在访问后端资源或外部服务一定要经常使用超时操作。

超时细化下来,一般会包括很多类型:连接超时、读超时、写超时、通用超时 等等区分;一般超时粒度大部分都是秒为单位,对于时间敏感业务都是毫秒为单位,建议以毫秒(ms)为单位的超时更可靠,但是很多服务没有提供这类超时操作接口。

 

常用使用超时的场景:

 

Swoole框架里控制超时

//Swoole 里通用超时设置(针对TCP协议情况,包含通用超时、连接超时、读写超时)
Co::set([
    'socket_timeout' => 5,'socket_connect_timeout' => 1,'socket_read_timeout' => 1,'socket_write_timeout' => 1,
]);

//Swoole 4.x协程方式访问MySQL
Co\run(function () {
    $swoole_mysql = new Swoole\Coroutine\MySQL();
    $swoole_mysql->connect(['host'     => '127.0.0.1','port'     => 3306,'user'     => 'user','password' => 'pass','database' => 'test','timeout'     => '1',
    ]);
    $res = $swoole_mysql->query('select sleep(1)');
    var_dump($res);
});

//Swoole 4.x协程方式访问Redis
Co\run(function () {
    $redis = new Swoole\Coroutine\Redis();
    $redis->setOptions('connect_timeout'    => '1','timeout'    => '1',
    );
    $redis->connect('127.0.0.1', 6379);
    $val = $redis->get('key');
});

 

通过cURL接口访问HTTP服务超时设置

  • function http_call($url)
       $ch = curl_init($url);
       curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
       //注意,毫秒超时一定要设置这个
       curl_setopt($ch, CURLOPT_NOSIGNAL, 1);    
       //超时毫秒,cURL 7.16.2中被加入。从PHP 5.2.3起可使用
       curl_setopt($ch, CURLOPT_TIMEOUT_MS, 200);  
       $data = curl_exec($ch);
       $curl_errno = curl_errno($ch);
      $curl_error = curl_error($ch);
      curl_close($ch);
    }
    http_call('http://example.com')

     

访问MySQL超时处理(非Swoole情况),调用mysqli扩展方式:

mysql 默认在扩展层面没有把很多超时操作暴露给前台,所以需要用一些隐藏方式:

<?php
//自己定义读写超时常量
if (!defined('MYSQL_OPT_READ_TIMEOUT')) define('MYSQL_OPT_READ_TIMEOUT',  11);
if (!defined('MYSQL_OPT_WRITE_TIMEOUT')) define('MYSQL_OPT_WRITE_TIMEOUT', 12);

//设置超时
$mysqli = mysqli_init();
$mysqli->options(MYSQL_OPT_READ_TIMEOUT, 3); //读超时,没办法超过3秒
$mysqli->options(MYSQL_OPT_WRITE_TIMEOUT, 1); //写超时,最小可设置为1秒
 
//连接数据库
$mysqli->real_connect("localhost", "root", "root", "test");
//执行查询 sleep 1秒不超时
printf("Host information: %s/n", $mysqli->host_info);

//执行查询 sleep 1秒不超时
printf("Host information: %s/n", $mysqli->host_info);
if (!($res=$mysqli->query('select sleep(1)'))) {
    echo "query1 error: ". $mysqli->error ."/n";
} else {
    echo "Query1: query success/n";
}

//执行查询 sleep 9秒会超时,处理3秒超时情况,因为mysql自己会重试3次
if (!($res=$mysqli->query('select sleep(9)'))) {
    echo "query2 error: ". $mysqli->error ."/n";
} else {
    echo "Query2: query success/n";
}

$mysqli->close();
?>

目前大部分主流框架都没有提供mysql超时配置,包括Laravel、Symfony、Yii 等框架都没有提供,因为都是基于底层mysqli或pdo等扩展。

 

Socket或流处理超时:

// ## fsockopen访问HTTP ## 
$timeout = 5; //超时5秒
$fp = fsockopen("example.com", 80, $errno, $errstr, $timeout); 
if ($fp) { 
        fwrite($fp, "GET / HTTP/1.0\r\n"); 
        fwrite($fp, "Host: example.com\r\n"); 
        fwrite($fp, "Connection: Close\r\n\r\n"); 
        stream_set_blocking($fp, true);   //重要,设置为非阻塞模式
        stream_set_timeout($fp,$timeout);   //设置超时
        $info = stream_get_meta_data($fp); 
        while ((!feof($fp)) && (!$info['timed_out'])) { 
                $data .= fgets($fp, 4096); 
                $info = stream_get_meta_data($fp); 
                ob_flush; 
                flush(); 
        } 
        if ($info['timed_out']) { 
                echo "Connection Timed Out!"; 
        } 
       else { 
                echo $data; 
        } 
}

 

通过上下文环境处理超时:

//fopen & file_get_contents 访问HTTP超时控制
//设置超时和压入上下文环境
$timeout = array(
    'http' => array('timeout' => 5 //设置一个超时时间,单位为秒
    )
);
$ctx = stream_context_create($timeout);
//fopen
if ($fp = fopen("http://example.com/", "r", false, $ctx)) {
  while( $c = fread($fp, 8192)) {
    echo $c;
  }
  fclose($fp);
$text = file_get_contents("http://example.com/", 0, $ctx);
echo $text;

 

延伸阅读: https://blog.csdn.net/heiyeshuwu/article/details/7841366

 

 

7. 成熟稳定的SQL语言使用习惯和技巧


a. 所有SQL语句都必须有约束条件

常规习惯:

SELECT uid,uname,email,gender FROM user WHERE uid = '9527'

好习惯,增加对应的WHERE条件和LIMIT限制

SELECT uid,uname,email,gender FROM user WHERE uid = '9527' LIMIT 1

b. SQL查询里抽取字段中明确需要抽取的字段

常规习惯:

SELECT * FROM user WHERE uid = '9527'

良好习惯,需要什么字段提取什么字段:

SELECT uid,uname,email,gender FROM user WHERE uid = '9527' WHERE 1 LIMIT 1

主要受约束的是一些mysql的配置相关,包括:max_allowed_packet 之类的会超过限制或者是把网卡带宽打满;

c. 熟知各种SQL操作的最佳实践技巧,包括不限于:常用字段建立索引(单表不超过6个)、尽量减少OR、尽量减少连表查询、尽量不要SQL语句中使用函数(datetime之类)、使用exists代替in、使用explain观察SQL运行情况等等。

 

延伸学习: https://blog.csdn.net/jie_liang/article/details/77340905

 

 

 

 

8. 开发中时刻要记得代码安全


我们系统在完成业务开发的基础上,还需要考虑代码安全问题,大部分时候安全和方便中间会存在冲突,但是因为一个不安全的系统,对业务的伤害是巨大的,轻则被非法用户“薅羊毛”,重则服务器被攻陷,整个数据遭到泄露或者遭受恶意损失。

我们常见遇到的安全问题包括:SQL注入、XSS、CSRF、URL跳转漏洞、文件上传下载漏洞等等,很多在我们只是关注业务实现不关注安全的时候问题都会出现。

 

安全问题

常见解决方法

SQL注入

输入参数校验:intval、is_numeric、htmlspecialchars、trim

数据入库格式化:PDO::prepare、mysqli_real_escape_string

XSS

过滤危险的HTML/JS等输入参数和显示内容,在js代码中对HTML做相应编码,多使用正则或者 htmlspecialchars、htmlspecialchars_decode 等处理函数;

CSRF

Client和Server交互采用token校验操作,敏感操作判断来源IP等

URL跳转

跳转目标URL必须进行校验,或者采用URL白名单机制

文件上传下载

限制文件上传大小(upload_max_filesize / post_max_size配置)

采用可靠上传的组件(Flash/JS组件);检查上传文件类型(扩展名+内容头),控制服务器端目录和文件访问权限

配置安全

PHP环境配置做好安全配置,屏蔽敏感函数:eval / exec / system / get_included_files ;

敏感配置关闭:register_globals / allow_url_fopen / allow_url_include/ safe_mode / magic_quotes 等;

资源管控配置: max_execution_time / max_input_time /memory_limit / open_basedir / upload_tmp_dir 等

数据库安全

数据库访问不能用root账户,不同库不同的访问用户,读写账户分离;

敏感库表需要特殊处理(比如用户库表密码库表等加盐存储);

服务器安全

DDoS攻击防范(流量清洗、黑白名单)、服务安全运行权限(非root运行php进程)、服务器之间访问白名单机制

 


9. 调优后端服务的性能配置


开发语言只是一个粘合剂,整个过程是把前端用户操作进行逻辑处理,粘合后端存储和各种RPC服务的数据进行展现输出。除了你的PHP服务或代码、SQL执行效率高,同样也需要考虑前后端各个服务的性能是非常优化高性能的。

我把一些关键的Linux系统到各个各个服务一些关键性能相关选项简单罗列一下。

 

服务类型

核心性能影响配置

Linux系统

1. 并发文件描述符

永久修改: /etc/security/limits.conf

* soft nofile 1000000

* hard nofile 1000000

Session临时修改:ulimit -SHn 1000000

3. 进程数量限制

永久修改:/etc/security/limits.d/20-nproc.conf

*          soft    nproc  4096

root     soft    nproc  unlimited

4. 文件句柄数量

临时修改:echo 1000000 > /proc/sys/fs/file-max

永久修改:echo "fs.file-max = 1000000" >>/etc/sysctl.conf

5. 网络TCP选项,关注 *somaxconn/*backlog/*mem*系列/*time*系列等等

6. 关闭SWAP交换分区(服务器卡死元凶):

echo "vm.swappiness = 0">> /etc/sysctl.conf

Nginx/OpenResty

Nginx Worker性能:

worker_processes 4;

worker_cpu_affinity 01 10 01 10;

worker_rlimit_nofile 10240;

worker_connections 10240;

 

Nginx网络性能:

use epoll;

sendfile on;

tcp_nopush on;

tcp_nodelay on;

keepalive_timeout 30;

proxy_connect_timeout 10;

 

Nginx缓存配置:

fastcgi_buffer_size 64k;

client_max_body_size 300m;

client_header_buffer_size 4k;

open_file_cache max=65535 inactive=60s;

open_file_cache_valid 80s;

proxy_buffer_size 256k;

proxy_buffers 4 256k;

proxy_cache*

PHP/FPM

listen.backlog = -1 #backlog数,-1表示无限制

rlimit_files = 1024 #设置文件打开描述符的rlimit限制

rlimit_core = unlimited #生成core dump文件限制,受限于linux系统

pm.max_children = 256 #子进程最大数

pm.max_requests = 1000 #设置每个子进程重生之前服务的请求数

request_terminate_timeout = 0 #设置单个请求的超时中止时间

request_slowlog_timeout = 10s #当一个请求该设置的超时时间后

MySQL/MariaDB

MySQL服务选项:

wait_timeout=1800

max_connections=3000

max_user_connections=800

thread_cache_size=64

skip-name-resolve = 1

open_tables=512

max_allowed_packet = 64M

 

MySQL性能选项:

innodb_page_size = 8K #脏页大小

innodb_buffer_pool_size = 10G #建议设置为内存80%

innodb_log_buffer_size = 20M #日志缓存大小

innodb_flush_log_at_trx_commit = 0 #事务日志提交方式,设置为0比较合适

innodb_lock_wait_timeout = 30 #锁获取超时等待时间

innodb_io_capacity = 2000 #刷脏页的频次默认200,高一些会让io曲线稳

Redis

maxmemory 5000mb #最大内存占用

maxmemory-policy allkeys-lru #达到内存占用后淘汰策略,存在热点数据,淘汰不咋访问的

maxclients 1000 #客户端并发连接数

timeout 150 #客户端超时时间

tcp-keepalive 150 #向客户端发送tcp_ack探测存活

rdbcompression no #磁盘镜像压缩,开启占用cpu

rdbchecksum no #存储快照后crc64算法校验,增加10%cpu占用

vm-enabled no #不做数据交换

 


延伸阅读: https://blog.csdn.net/heiyeshuwu/article/details/45692407

 

 

 

 

10. 善用常用服务的系统监测工具

 

 

 

为了快速的监控我们的线上服务情况,需要能够熟练使用常用的性能监控的各种工具和指标。

服务类型

常用工具或指令

Linux

top、vmstat、iostat、netstat、sar、nmon、dstat、iftop、free、df/du、tcpdump

PHP

Xdebug、xhprof、Fiery、第三方APM工具

MySQL

MySQL主要指令:

show processlist

show global variables like '%xxx%'

show master status;

show slave status;

show status like '%xx%'

 

细节查询:

查看连接数:SHOW STATUS LIKE 'Thread_%';

查看执行事务:SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX;

查看锁定事务:SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS;

每秒查询QPS:SHOW GLOBAL STATUS LIKE 'Questions'; #QPS = Questions / Uptime

每秒事务TPS:SHOW GLOBAL STATUS LIKE 'Com_%'; #TPS = (Com_commit + Com_rollback) / Uptime

InnoDB Buffer命中率:show status like 'innodb_buffer_pool_read%'; #innodb_buffer_read_hits = (1 - innodb_buffer_pool_reads / innodb_buffer_pool_read_requests) * 100%

Redis

查看redis服务的各项状态:info / info stats / info CPU / info Keyspace

实时监控redis所有命令:monitor

查看redis慢日志:slowlog get 128

性能测试监控:redis-benchmark -h localhost -p 6379 -c 100 -n 100000

 

 

编码健壮性结束语:

在程序开发中,没有最优质的代码,只有解决业务问题,良好设计和规范,持续精进的程序。

 


 

三、健壮代码有哪些通用原则

 

1、模块性原则:写简单的,通过干净的接口可被连接的部件。(比如类、函数,高内聚低耦合)

2、清楚原则:清楚要比小聪明好。(代码中注释需要清晰明确,最好有历史迭代,不要耍小聪明,或者用一些奇怪的实现算法并且没注释)

3、合并原则:设计能被其它程序连接的程序。(提供好输入输出参数,或者设计好的openapi,尽量让程序可以复用)

4、简单原则:设计要简单;只有当你需要的时候,增加复杂性。(每个函数类要简单明确,不要太冗长)

5、 健壮性原则:健壮性是透明和简单的追随者。(透明+简单了,健壮就来了)

6、沉默补救原则:当一个程序没有异常的时候就只是记录,减少干扰或者啥都不说;当你必须失败的时候,尽可能快的吵闹地失败。(失败了一定要明确清晰的提示形式,包括错误代码,错误原因的信息,不要悄无声息)

7、经济原则:程序员的时间是宝贵的;优先机器时间节约它。(能够用内存+CPU搞定,不要过多纠结在算法上)

8、产生原则:避免手工堆砌;当你可能的时候,编写可以写程序的程序。(用程序来帮你实现重复的事儿)

9、优化原则:在雕琢之前先有原型;在你优化它之前,先让他可以运行。(先完成,再完美)

10、可扩展原则:为将来做设计,因为它可能比你认为来的要快。(尽量考虑你这个代码2,3,5年后才会重构,为未来负责)

 


 

四、个人软素质:细心、认真、谨慎、精进、专业

 

1、细心:写完代码都需要重新Review一遍,变量名是否正确,变量是否初始化,每个SQL语句是否性能高超或者不会导致超时死锁;

2、认真:每个函数是否都自己校验过输入输出是满足预期的;条件允许,是否核心函数都具备单元测试代码;

3、谨慎:不要相信任何外部输入的数据,包括数据库、文件、缓存、用户HTTP提交各种变量,都需要严格校验和过滤。

4、精进:不要惧怕别人说你代码烂,必须能够持续被人吐槽下优化,在在我革新下优化;要学习别人优秀的代码设计思想和代码风格,持续进步。

我持续三年优化过的一个代码:(大约十四年前第一版)

 

5. 专业:专业是一种工作态度,也是一种人生态度;代码要专业、架构要专业、变量名要专业、文档要专业、跟别人发工作消息邮件要专业、开会要专业、沟通要专业,等等,专业要伴随自己一生。

我在十三年前写的代码:

 

 


本文结束语:


愿每一位阅读本文的技术伙伴,都能够成为优秀的程序员,优秀的架构师。

 


作者简介:

​谢华亮,网名“黑夜路人”,目前是学而思网校技术委员会主席;作者系CSDN博客技术专家,互联网后端开发架构师,多年PHP/Golang/C 开发者,LNMP技术栈/分布式/高并发等技术爱好者,国内开源技术爱好和布道者。

 

个人博客: http://blog.csdn.net/heiyeshuwu

新浪微博: http://weibo.com/heiyeluren

微信公众号:黑夜路人

 

 

 

功能点估算法(一)_软件质量管理、项目管理、过程改进、软件自动化测试咨询与培训。-CSDN博客_功能点估算法

$
0
0


功能点估算法是软件项目管理众多知识中比较有技术含量的一个。在软件项目管理中项目计划制定的优劣直接关系到项目的成败,项目计划中对项目范围的估算又尤为重要,如果项目负责人对项目的规模没有一个比较客观的认识,没有对工作量、所需资源、完工时间等因素进行估算,那么项目计划也就没有存在的意义。

项目范围的估算在CMMI的“MA”度量分析管理和“PP”项目计划中均有涉及,对软件项目范围的估算有很多种方法,常见的就是LOC代码行和FP功能点法,它们之间的区别和关系如下:

1、 FP功能点估算法常用在项目开始或项目需求基本明确时使用,这时进行估算其结果的准确性比较高,假如这个时候使用LOC代码行估算法,则误差会比较大。

2、 使用FP功能点估算法无需懂得软件使用何种开发技术。LOC代码行估算法与软件开发技术密切相关。

3、 FP功能点法是以用户为角度进行估算,LOC代码行估算法则是以技术为角度进行估算的。

4、 通过一些行业标准

或企业自身度量的分析,FP功能点估算法是可以转换为LOC代码行的。在项目刚开始的时候进行功能点估算可以对项目的范围进行预测,在项目开发的过程中由于需求的变更和细化可能会导致项目范围的蔓延,计算出来的结果会与当初估计的不同,因此在项目结束时还需要对项目的范围情况进行估算,这个时候估算的结果才能最准确反映项目的规模。

功能点分析的步骤

    在本文中将以国际标准IFPUG(International Function Point Users Group)组织提供的功能点估算法V4.1.1为基础与大家进行讲解。如下图所示,首先大家应该了解功能点估算法的使用步骤。

 

图 功能点估算的步骤 

1、 识别功能点的类型。

2、 识别待估算应用程序的边界和范围。

3、 计算数据类型功能点所提供的未调整的功能点数量。

4、 计算人机交互功能所提供的未调整的功能点数量。

5、 确定调整因子。

6、 计算调整后的功能点数量。

识别项目的类型

国际的IFPUG组织将软件项目分为三类,功能点估算法适用于任何一类项目

l 新开发项目

l 二次开发的项目

l 功能增强的项目

识别项目的范围和边界    

使用UML的“UseCase”用例图是以用户角度进行识别项目范围和边界的最好方法,因为在画用例图时就必须明确系统的边界。通过系统的边界我们可以知道哪些功能要计算功能点,哪些功能点是外部系统负责计算的。以下图为例:一个外贸订单系统只包含录入、修改、删除、查询和统计订单的功能,而汇率查询转换服务是不属于该系统的。

    应用程序边界的识别规则大家一定要牢记,不能从技术角度去思考,必须从用户角度来定义;如果项目牵扯到多个系统,那么必须将这多个系统的边界全部描述清楚。

 

图 外贸订单系统用例图 

FP功能点估算分类    

FP功能点估算法将功能点分为以下5类:

1、 ILF:Internal Logical File内部逻辑文件

2、 EIF: External Interface File外部接口文件

3、 EI: External Input外部输入

4、 EO: External Output外部输出

5、 EQ: External Inquiry外部查询其中ILF和EIF属于数据类型的功能点,EI、EO、EQ属于人机交互类型的功能点。     

以外贸订单系统项目为例:

l  录入订单、修改订单、删除订单是EI;

l  查询订单是EO

l  统计订单是EQ

l  汇率查询转换系统为EIF

l  订单和客户是ILF

识别功能点的重要原则

ILF、EIF要与EI、EO、EQ分开计算。对ILF和EIF复杂度的计算可以简单理解为对数据库复杂度的计算。对EI、EO、EQ复杂度的计算可以理解为对程序开发复杂度的计算。一般软件项目都是由数据和程序构成的,因此计算ILF、EIF和计算EI、EO、EQ之间没有任何关系。  

  第二部分:内部逻辑文件与外部接口文件ILF内部逻辑文件

内部逻辑文件是指一组以用户角度识别的,在应用程序边界内且被维护的逻辑相关数据或控制信息。ILF的主要目的是通过应用程序的一个或多个基本处理过程来维护数据。

EIF外部接口文件外部接口文件是指一组在应用程序边界内被查询,但它是在其他应用程序中被维护的,以用户角度来识别的,逻辑上相关的数据。因此一个应用程序中的EIF必然是其他应用程序中的ILF。EIF的主要目的是为边界内的应用程序提供一个或多个通过基础操作过程来引用的一组数据或信息。EIF所遵循的规则:

n  从用户角度出发识别的一组逻辑数据。

n  这组数据是在应用程序外部,并被应用程序引用的。

n  计算功能点的这个应用程序并不维护该EIF

n  这组数据是作为另一个应用程序中的ILF被维护的。

ILF和EIF复杂性计算  

ILF和EIF的复杂性是取决于RET(Record element type)和DET(Data element type)的数量。DET是一个以用户角度识别的,非重复的有业务逻辑意义的字段。

DET计算的规则

●  通过一个基本处理过程的执行,对ILF进行维护或从ILF/EIF中返回一个特定的、用户可识别的、非重复的字段,那么每个这样的字段算一个DET。

ü  例如:添加一个外贸订单时需要保存“订单号码、订单日期、地址、邮编”,那么对于ILF订单来说它的DET就是4个。

ü  例如:保存订单时还会保存订单的明细,订单的明细往往作为一个子表进行保存,那么“订单号码”在主表和子表中都同时存在(主外键),但以用户角度来识别时,存盘操作是一个最小的单位,那么订单号码只能算做一个DET。 

●  当两个应用程序维护和/或引用相同的ILF/EIF,但是每个应用程序分别维护/引用它们相应的DET时,这些DET在这两个应用程序的维护或引用中将单独计算。

ü  例如一个应用程序的两个“Elementary Process”基本处理过程都需要使用到“地址”的信息,地址的信息又可以细分为“国家、城市、街道、邮编”。那么对于其中一个基本处理过程来说,他将整个地址信息作为一个整体进行处理,那就只算一个DET,另外一个基本处理过程使用每个地址的详细信息,那么DET就是4个。

Ø  RET计算的规则如下:

RET是指一个EIF/ILF中用户可以识别的DET的集合。如果把DET简单理解为字段的话,那RET就可以简单理解为数据库中的表。RET在ILF/EIF中分为两种类型:可选的(Optional)和必选的(Mandatory)。计算RET的规则为以下两点:

●  在一个ILF/EIF中每一个可选或必选的集合都被计算为一个RET。或者

●  如果一个ILF/EIF没有子集合,则ILF/EIF被计算为一个RET。

ü  例如:在外贸订单系统中添加一个订单时会保存“订单信息、客户的ID、部门的ID”。

那么订单系统ILF中RET为:

1、 订单信息(必选的)

2、 客户信息(必选的)

3、 部门信息(可选的)因此ILF中RET的个数为3个。

Ø  ILF/EIF复杂度的矩阵如下

 1~19个DET20~50个DET超过51个DET
1个RET中等
2~5个RET中等
6个以上RET中等
 

 

 

 

  FP功能点估算法的特点    

FPA功能点估算系统规模流程概述-威尔金的IT博客,51CTO-51CTO博客

$
0
0

有关什么是功能点分析法,为什么要用功能点分析法的内容,请阅读《 FPA笔记一 概述
1.   计算功能点的总体流程

FPA的计算流程比较复杂,主要分为三大步骤:定义分析目标;计算未调整功能点;计算调整功能点。具体图示请参见图一。
 
图表 1 FPA 计算流程
FPA 的主要步骤如下:
1)      决定分析类型和目的:开发项目、升级项目、应用。小特性开发属于应用类型。
2)      识别分析范围和应用边界。
3)      计算未经调整的功能点数UPFC。
(1) 列出系统的所有功能,包括数据功能和处理功能。
(2) 计算每一个功能的功能点。
                                    i.              识别该功能的类型:ILF、EIF、EI、EO、EQ。
                                  ii.              统计该功能包含元素的数目。数据功能统计DET和RET;处理功能统计DET和FTR。
                                iii.              根据该功能包含元素的数目,和相应功能类型的复杂度矩阵,确定其复杂度。
                                 iv.              根据相应功能类型的复杂度和功能点对照表,找到改功能的功能点数。
(3) 统计所有功能的功能点总和。
4)      确定调整系数。根据14个GSC确定VAF。
5)      计算调整后的功能点:AFP = UPFC * VAF
 
 

1.1.  

 
Determine Type of Count
Identify Counting Scope and Application Boundary
Count Data Functions
Count Transactional Functions
Determine Unadjusted Function Point Count
Determine Value Adjustment Factor
Calculate Adjusted Function Point Count
定义分析类型
FPA可应用于各类软件项目和应用系统。对于不同的项目和系统,FPA计算流程是一样的,但一些具体算法和规则上各有不同。FPA的目的也不尽相同。分析类型有三种:
l  新开发项目 Development Project
估算或度量系统的所有新功能点,包括新增的或系统切换的功能。度量的目的有:
n  定义需求
n  为项目计划提供估算数据:工作量,成本,人员,进度。
n  度量质量。
n  度量生产率。
l  升级项目 Enhancement Project
估算或度量系统中变化的功能点,包括新增,改变,减少和系统切换的功能。
l  应用软件Application
官方定义是度量已安装的应用软件的功能点。Appliction是指已经交付或从第三方获得的软件、软件包。小软件工具的开发也可算作应用类型。每次新开发项目完成后,都应当把交付的系统按应用软件度量一次。度量应用软件的目的有:
l  作为升级项目的基线。
l  度量软件质量
l  确定维护策略
l  确定维护的生产率
三种类型的分析关系如下图所示。
图表 2 项目FPA与应用FPA的关系

1.2.  定义范围边界

FPA是从用户视角和系统见交互的角度来分解功能。只有严格的界定了分析的范围和边界,才能很好的识别和分解功能。基于用户视角定义边界,用户能够理解和描述边界。
l  相关应用之间的边界是由用户看到的不同功能区域划分,而不是由技术考虑来划分。
l  应用之间的初始边界不会因为功能点分析而改变。
定义边界的技巧
l  获得一个系统的流程图,在系统周围画上边框,作为边界。
l  察看数据的维护方式。
l  察看数据的应用范围。
 

2.   计算未调整功能点UFPC

未调整功能点是从具体功能的复杂度计算得到,它包括三个步骤:分解功能,分析功能的复杂度,根据复杂度确定功能点数。

2.1.  识别,分解具体功能

所有系统的具体功能都可分为两种:数据功能和处理功能。正确识别出数据功能和处理功能的数目是FPA的关键。
l  数据功能:指为满足用户数据需求而提供的功能。它以文件为单位计数。文件分为两类:ILF和EIF。
n  内部逻辑文件ILF :系统内部维护的文件,如系统创建和更新的文件。
n  外部接口文件EIF :被目标系统应用,但由外部系统维护的文件。
l  处理功能:指为满足用户通过系统处理数据或控制信息而提供的功能。它以 处理元为单位计数。处理必然是发生在系统边界内外的一个交互过程,可分为三种:EI,EO和EQ。
n  外部输入EI :指处理来自系统外的文件的处理元。它的基本目的是维护一个或多个ILF,或者改变系统的行为。
n  外部输出 EO :指把文件发送到系统外的处理元。他的基本目的是给用户提供处理的结果。EO包含至少一个逻辑处理运算过程。
n  外部查询 EQ :EQ也是指把文件发送到系统外的处理元。它的基本目的是为用户获取指定的信息。EQ部包含逻辑处理运算过程。
请注意,FPA是从用户角度分析系统的。这里的文件和处理元也是从用户角度来定义的,完全与实现技术无关。特被是文件,一定要时刻记住他仅仅是一组数据,与计算机文件没有关系。
l  文件:一组用户可识别的,有逻辑关联的数据或控制信息。它不一定计算机系统实际产生,存储或使用的文件。
l  处理元:对用户有意义的最小活动单元。它与实际程序中的方法,进程和API无关。
 

2.2.  确定具体功能的复杂度

对于具体的文件和处理元,FPA采用三个指标度量其复杂度:RET,DET和FTR。这些指标都是直观的,可计量的。其中文件用RET和DET来度量,处理元用DET和FTR来度量。
l  记录元素类型RET :在一个文件内,一个用户可识别的数据元素组。
l  数据元素类型DET :用户可识别的,不重复的字段。
l  引用文件类型FTR :处理涉及到的文件,包括读取,更新和修改的文件。
FPA给概念术语的名称都比较冗繁。个人感觉把这三个术语中的“类型(Type)”去掉,会更易懂。这里的“类型(Type)”都是强调对相同的东西不能重复计算的。比如同一个ILF中的两个RET都包含同一个DET,只能记为一个DET。

2.3.  数据功能点权重矩阵

对于每一个文件(ILF或EIF),FPA是根据其复杂度来确定其功能点数。复杂度又根据文件所含的DET和RET的数量分为三级:低,中(平均)和高。
表格 1 文件功能点计算矩阵
DET个数
RET个数
1 ~ 19
20 ~50
 >= 50
 
复杂度
ILF 功能点数
EIF功能点数
1
7
5
2 ~ 5
10
7
>= 6
15
10
 

2.4.  处理功能点权重矩阵

同数据功能点类似,处理功能点也是根据三级复杂度确定的。而每个处理元的复杂度根据DET和FTR计算得来。但EI 、EQ和EO三者的计算方法不尽相同。
表格 2 处理元复杂度矩阵
EI复杂度
EQ、EO复杂度
DET个数
RET个数
1 ~ 4
5 ~15
 >=16
DET个数
RET个数
1 ~ 5
6 ~19
 >= 20
1
1
2
2 ~ 3
>= 3
>= 4
从表中可见同样文件作为输入要比输出的复杂度高。
 
表格 3 处理元功能点计算表
复杂度
EI、EQ功能点数
3
4
6
EO功能点数
4
5
7

2.5.  汇总未调整功能点

把系统中所有ILF, EIF,EI,EO,EQ的功能点数汇总,就是系统的总的未调整功能点数UFPC。

3.   计算调整功能点AFP

未调整功能点数是从用户角度计算得出的,完全没有考虑不同系统或不同功能的实现复杂度。FPA通过分析14个通用系统特性(GSC)对系统的影响程度(DI)得出每个系统的功能点值调整因子VAT。最后根据VAF调整功能点数,得出在系统和功能点可类比的调整功能点数。UFP、VAT和AFP三者的关系是:
AFP = UFP * VAT
请注意, 一个系统只有一个VAT,它是所有14个GSC分析汇总的结果。

3.1.  通用系统特性GSC

GSC是由IFPUG统一指定的标准。一共有14种GSC,适用于所有类型的系统和项目。
(1)    数据通讯 (Data Communications)
(2)    分布式数据处理 (Distributed Data Processing)
(3)    性能 (Performance)
(4)    使用强度高的配置 (Heavily Used Configuration)
(5)    事务速度 (Transaction Rate)
(6)    在线数据输入 (Online Data Entry)
(7)    最终用户的效率 (End-User Efficiency)
(8)    在线更新(Online Update)
(9)    复杂的处理 (Complex Processing)
(10)               可重用性 (Reusability)
(11)               安装的简易性 (Installation Ease)
(12)               运行的简易性 (Operational Ease)
(13)               多场地 (Multiple Sites)
(14)               允许变更 (Facilitate Change)
 

3.2.  影响程度DI和TDI

GSC对系统的影响程度分为6级,从0到5。各级定义如下:
0 不存在或者没有影响
1 偶尔的影响
2 轻微的影响
3 中等的影响
4 显著的影响
5 强烈的影响
IFPUG针对每一中GSC,给出了详细的DI等级指南。对于一些实在没有参考等级标准,用户也可以自己定义。
把14种GSC的DI都加起来,就得到系统的总影响程度TDI,即:TDI = ∑(DI)

3.3.  值调整因子VAF

在得出TDI后,VAF按如下公式计算。
VAF = (TDI * 0.01 ) + 0.65。
VAF只能在正负35%的范围调整功能点数。
AFP = UFP * VAT
 

4.   不同项目的调整功能点AFP

4.1.  开发项目功能点

DFP = (UFP + CFP) * VAF
l  DFP: 开发项目功能点。
l  UFP: 项目应用的UFPC。
l  CFP:额外的转换功能的UFPC。
l  VAFA:调整系数。

4.2.  升级项目功能点

EFP = (ADD + CHGA + CFP) * VAFA + DEL * VAFB
l  EFP:升级项目功能点。
l  ADD:升级项目新增UFPC。以升级后项目为基准。
l  CHGA:升级项目改变的UFPC。以升级后项目为基准。
l  CFP:额外的转换功能的UFPC。
l  VAFA:升级后的调整系数。
l  VAFB:升级前的调整系数。
l  DEL:升级项目中删除的UFPC。

4.3.  应用功能点

AFP = ADD * VAF
l  AFP:应用功能点
l  ADD:安装的功能UFPC。
l  VAF:调整系数。

4.4.  升级应用功能点

AFP = [( UFPB + ADD + CHGA) – (CHGB +DEL)] * VAFA
l  AFP:应用功能点
l  ADD:安装的功能UFPC。
l  VAFA:调整系数。
l  UFPB:升级前的UPFC。
l  CHGA:升级后改变的UFPC
l  CHGB:升级前改变的UFPC ?!
 

5.   附录

5.1.  度量功能点的工作量

很多公司不能推行FPA  ,并非主观上认为它没用。其主要原因有二:
1.         没有FPA的专家指导,不知从何做起,如何持续。
2.         迫于项目进度压力,担心FPA带来大量额外的工作量。
这里就推行FPA带来的额外工作量,给出一些参考数据。大多数组织是平均每小时估算出100个功能点。具体如下:
 
项目/应用系统的规模
很小
很大
功能点数
5 ~ 20
20 ~ 100
100 ~ 500
500 ~ 10K
10K ~ 100K
C++代码行
265 ~ 1K
1K ~ 5K
5K ~ 26K
26K ~ 500K
500K ~ 5M
开发工作量
0.5人天 ~ 1人月
1人月 ~ 10人月
10人月 ~ 72人月
72人月 ~ 200人年
200人年~ 8K人年
FPA工作量
15分钟 ~ 30分钟
30分钟 ~ 1小时
1小时 ~ 5小时
5小时 ~ 100小时
100小时 ~ 1K小时
 
 

5.2.  推行FPA的建议

应当做的事情
l  得到老板的支持和指导。
l  是度量成为每一个人工作的一部分。
l  安排专人总管和支持度量活动,不一定是全职。
l  培训技术人员和用户。让用户有功能点的概念。
l  关注在项目团队的收益上,不要一上来就资产管理什么的。
l  提供自动化的支持。
l  与组织的过程模式整合。
l  度量的结果应当发布出来,并得到利用。
不应当做的事情
l  不要觉得度量可有可无,或不可达到。
l  不要苛求完美的度量系统和环境。
l  不要依赖不准确的数据。
l  不要用于衡量个人的绩效。

5.3.  术语表

术语
英文
中文
说明
FPA
Function Point Analysis
功能点分析法
 
UFP
Unadjusted Function Point
未调整的功能点
 
AFP
Adjusted Function Point
调整功能点
 
VAF
Value Adjustment Factor
值调整因子
 
ILF
Internal Logic File
内部逻辑文件
 
EIF
External Interface File
外部接口文件
 
EI
External Input
外部输入
 
EO
External Output
外部输出
 
EQ
External Query
外部查询
 
GSC
General System Characteristic
通用系统特征
 
DET
Data Element Types
数据元素类型
 
RET
Record Element Types
记录元素类型
 
FTR
File Type Referenced
引用文件类型
 
DI
Degree of Influence
影响程度
 
TDI
Total Degree of Influence
整体影响程度
 
EP
Elementary Process
处理元
 

分布式存储ceph---ceph概念及原理(1) - Wolf_Coder - 博客园

$
0
0

一、Ceph简介:

  Ceph是一种为优秀的性能、可靠性和可扩展性而设计的统一的、分布式文件系统。ceph 的统一体现在可以提供 文件系统块存储对象存储,分布式体现在可以 动态扩展。在国内一些公司的云环境中,通常会采用 ceph 作为openstack 的唯一后端存储来提高数据转发效率。

  Ceph项目最早起源于Sage就读博士期间的工作(最早的成果于2004年发表),并随后贡献给开源社区。在经过了数年的发展之后,目前已得到众多云计算厂商的支持并被广泛应用。RedHat及OpenStack都可与Ceph整合以支持虚拟机镜像的后端存储。

  官网:https://ceph.com/

  官方文档:http://docs.ceph.com/docs/master/#

二、Ceph特点:

1、高性能:

  a. 摒弃了传统的集中式存储元数据寻址的方案,采用 CRUSH算法,数据分布均衡,并行度高。
  b.考虑了容灾域的隔离,能够实现各类负载的副本放置规则,例如跨机房、机架感知等。
  c. 能够支持上千个存储节点的规模,支持TB到PB级的数据。

2、高可用性:

  a. 副本数可以灵活控制。
  b. 支持故障域分隔,数据强一致性。
  c. 多种故障场景自动进行修复自愈。
  d. 没有单点故障,自动管理。

3、高可扩展性:

  a. 去中心化。
  b. 扩展灵活。
  c. 随着节点增加而线性增长。

4、特性丰富:

  a. 支持三种存储接口:块存储、文件存储、对象存储。
  b. 支持自定义接口,支持多种语言驱动。

三、Ceph应用场景

  Ceph可以提供对象存储、块设备存储和文件系统服务,其对象存储可以对接网盘(owncloud)应用业务等;其块设备存储可以对接(IaaS),当前主流的IaaS运行平台软件,如:OpenStack、CloudStack、Zstack、Eucalyptus等以及kvm等。

  Ceph是一个高性能、可扩容的分布式存储系统,它提供三大功能:

  (1) 对象存储(RADOSGW):提供RESTful接口,也提供多种编程语言绑定。兼容S3、Swift;
  (2) 块存储(RDB):由RBD提供,可以直接作为磁盘挂载,内置了容灾机制;
  (3) 文件系统(CephFS):提供POSIX兼容的网络文件系统CephFS,专注于高性能、大容量存储;

什么是块存储/对象存储/文件系统存储?

1、对象存储:

  也就是通常意义的键值存储,其接口就是简单的GET、PUT、DEL 和其他扩展,代表主要有 Swift 、S3 以及 Gluster 等;

2、块存储:

  这种接口通常以 QEMU Driver 或者 Kernel Module 的方式存在,这种接口需要实现 Linux 的 Block Device 的接口或者 QEMU 提供的 Block Driver 接口,如 Sheepdog,AWS 的 EBS,青云的云硬盘和阿里云的盘古系统,还有 Ceph 的 RBD(RBD是Ceph面向块存储的接口)。在常见的存储中 DAS、SAN 提供的也是块存储;

3、文件系统存储:

  通常意义是支持 POSIX 接口,它跟传统的文件系统如 Ext4 是一个类型的,但区别在于分布式存储提供了并行化的能力,如 Ceph 的 CephFS (CephFS是Ceph面向文件存储的接口),但是有时候又会把 GlusterFS ,HDFS 这种非POSIX接口的类文件存储接口归入此类。当然 NFS、NAS也是属于文件系统存储;

四、Ceph核心组件

(1) Monitors:监视器,维护集群状态的多种映射,同时提供认证和日志记录服务,包括有关monitor 节点端到端的信息,其中包括 Ceph 集群ID,监控主机名和IP以及端口。并且存储当前版本信息以及最新更改信息,通过 "ceph mon dump"查看 monitor map。

(2) MDS(Metadata Server):Ceph 元数据,主要保存的是Ceph文件系统的元数据。注意:ceph的块存储和ceph对象存储都不需要MDS。

(3) OSD:即对象存储守护程序,但是它并非针对对象存储。是物理磁盘驱动器,将数据以对象的形式存储到集群中的每个节点的物理磁盘上。 OSD负责存储数据、处理数据复制、恢复、回(Backfilling)、再平衡。完成存储数据的工作绝大多数是由 OSD daemon 进程实现。在构建 Ceph OSD的时候,建议采用SSD 磁盘以及xfs文件系统来格式化分区。此外OSD还对其它OSD进行 心跳检测,检测结果汇报给Monitor

(4) RADOS:Reliable Autonomic Distributed Object Store。RADOS是ceph存储集群的基础。在ceph中,所有数据都以 对象的形式存储,并且无论什么数据类型,RADOS对象存储都将负责保存这些对象。 RADOS层可以确保数据始终保持一致

(5) librados:librados库,为应用程度提供访问接口。同时也为块存储、对象存储、文件系统提供原生的 接口

(6) RADOSGW:网关接口,提供对象存储服务。它使用librgw和librados来实现允许应用程序与Ceph对象存储建立连接。并且提供S3 和 Swift(openstack)兼容的RESTful API接口。

(7) RBD:块设备,它能够自动精简配置并可调整大小,而且将数据分散存储在多个OSD上。

(8) CephFS:Ceph文件系统,与POSIX兼容的文件系统,基于librados封装原生接口。

五、Ceph存储系统的逻辑层次结构

六、RADOS的系统逻辑结构

七、Ceph 数据存储过程

  无论使用哪种存储方式(对象、块、文件系统),存储的数据都会被切分成Objects。Objects size大小可以由管理员调整,通常为2M或4M。每个对象都会有一个唯一的OID,由ino与ono生成,虽然这些名词看上去很复杂,其实相当简单。

   ino:即是文件的File ID,用于在全局唯一标识每一个文件
  ono:则是分片的编号

  比如:一个文件FileID为A,它被切成了两个对象,一个对象编号0,另一个编号1,那么这两个文件的oid则为A0与A1。

  File —— 此处的file就是用户需要存储或者访问的文件。对于一个基于Ceph开发的对象存储应用而言,这个file也就对应于应用中的“对象”,也就是用户直接操作的“对象”。

  Ojbect —— 此处的object是RADOS所看到的“对象”。Object与上面提到的file的区别是,object的最大size由RADOS限定(通常为2MB或4MB),以便实现底层存储的组织管理。因此,当上层应用向RADOS存入size很大的file时,需要将file切分成统一大小的一系列object(最后一个的大小可以不同)进行存储。为避免混淆,在本文中将尽量避免使用中文的“对象”这一名词,而直接使用file或object进行说明。

  PG(Placement Group)—— 顾名思义,PG的用途是对object的存储进行组织和位置映射。具体而言,一个PG负责组织若干个object(可以为数千个甚至更多),但一个object只能被映射到一个PG中,即,PG和object之间是“一对多”映射关系。同时,一个PG会被映射到n个OSD上,而每个OSD上都会承载大量的PG,即,PG和OSD之间是“多对多”映射关系。在实践当中,n至少为2,如果用于生产环境,则至少为3。一个OSD上的PG则可达到数百个。事实上,PG数量的设置牵扯到数据分布的均匀性问题。关于这一点,下文还将有所展开。

  OSD —— 即object storage device。唯一需要说明的是,OSD的数量事实上也关系到系统的数据分布均匀性,因此其数量不应太少。在实践当中,至少也应该是数十上百个的量级才有助于Ceph系统的设计发挥其应有的优势。

  基于上述定义,便可以对寻址流程进行解释了。具体而言, Ceph中的寻址至少要经历以下三次映射:

   (1)File -> object映射
  (2)Object -> PG映射,hash(oid) & mask -> pgid
  (3)PG -> OSD映射,CRUSH算法

  CRUSH,Controlled Replication Under Scalable Hashing,它表示数据存储的分布式选择算法, ceph 的高性能/高可用就是采用这种算法实现。CRUSH 算法取代了在元数据表中为每个客户端请求进行查找,它通过计算系统中数据应该被写入或读出的位置。CRUSH能够感知基础架构,能够理解基础设施各个部件之间的关系。并CRUSH保存数据的多个副本,这样即使一个故障域的几个组件都出现故障,数据依然可用。CRUSH 算是使得 ceph 实现了自我管理和自我修复。

  RADOS 分布式存储相较于传统分布式存储的优势在于:

  1)将文件映射到object后,利用Cluster Map 通过CRUSH 计算而不是查找表方式定位文件数据存储到存储设备的具体位置。优化了传统文件到块的映射和Block MAp的管理。

  2)RADOS充分利用OSD的智能特点,将部分任务授权给OSD,最大程度地实现可扩展。

八、Ceph IO流程及数据分布

1、正常IO流程图:

步骤:

  1)client 创建cluster handler。
  2)client 读取配置文件。
  3)client 连接上monitor,获取集群map信息。
  4)client 读写io 根据crshmap 算法请求对应的主osd数据节点。
  5)主osd数据节点同时写入另外两个副本节点数据。
  6)等待主节点以及另外两个副本节点写完数据状态。
  7)主节点及副本节点写入状态都成功后,返回给client,io写入完成。

2、新主IO流程图:

  说明:如果新加入的OSD1取代了原有的 OSD4成为 Primary OSD, 由于 OSD1 上未创建 PG , 不存在数据,那么 PG 上的 I/O 无法进行,怎样工作的呢?

新主IO流程步骤:

  1)client连接monitor获取集群map信息。
  2)同时新主osd1由于没有pg数据会主动上报monitor告知让osd2临时接替为主。
  3)临时主osd2会把数据全量同步给新主osd1。
  4)client IO读写直接连接临时主osd2进行读写。
  5)osd2收到读写io,同时写入另外两副本节点。
  6)等待osd2以及另外两副本写入成功。
  7)osd2三份数据都写入成功返回给client, 此时client io读写完毕。
  8)如果osd1数据同步完毕,临时主osd2会交出主角色。
  9)osd1成为主节点,osd2变成副本。

九、Ceph Pool和PG分布情况

  pool:是ceph存储数据时的逻辑分区,它起到namespace的作用。每个pool包含一定数量(可配置) 的PG。PG里的对象被映射到不同的Object上。pool是分布到整个集群的。 pool可以做故障隔离域,根据不同的用户场景不统一进行隔离。

 

Viewing all 532 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>