次要技术
- 根底框架: springboot
- 微服务架构: dubbo,springboot cloud
- ORM框架: mybatis plus
- 数据库连接池: Alibaba Druid
- 网关(对立对外接口 ): zuul
- 缓存: redis
- 注册核心: zookeeper,eureka
- 音讯队列:
- 作业调度框架: Quartz
- 分布式文件系统:
- 接口测试框架: Swagger2
- 数据库版本控制: Liquibase (flyway)
- 部署: docker
- 继续集成: jenkins
自动化测试: testNG
ORM框架-Mybatis Plus
MyBatis Plus是在 MyBatis 的根底上只做加强不做扭转,能够简化开发,提高效率.
Mybatis Plus外围性能
- 反对通用的 CRUD,代码生成器与条件结构器
- 通用CRUD: 定义好Mapper接口后,只须要继承 BaseMapper<T>接口即可取得通用的增删改查性能,无需编写任何接口办法与配置文件
- 条件结构器: 通过EntityWrapper<T>(实体包装类),能够用于拼接SQL语句,并且反对排序,分组查问等简单的 SQL
代码生成器: 反对一系列的策略配置与全局配置,比 MyBatis 的代码生成更好用
BaseMapper<T>接口中通用的 CRUD 办法:MyBatis Plus与SpringBoot集成
数据库USER
DROP TABLE IF EXISTS user;CREATE TABLE user(id bigint(20) DEFAULT NULL COMMENT '惟一标示',code varchar(20) DEFAULT NULL COMMENT '编码',name varchar(64) DEFAULT NULL COMMENT '名称',status char(1) DEFAULT 1 COMMENT '状态 1启用 0 停用',gmt_create datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创立工夫',gmt_modified datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '批改工夫') ENGINE=InnoDB DEFAULT CHARSET=utf8;
pom.xml依赖
<!--mybatis plus --><dependency> <groupId>com.baomidou</groupId> <artifactId>mybatisplus-spring-boot-starter</artifactId> <version>1.0.5</version></dependency><dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus</artifactId> <version>2.1.9</version></dependency>
spring-mybatis.xml配置文件
也能够间接应用@Bean的形式进行或者通过application配置文件进行<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <!--创立jdbc数据源 这里间接应用阿里的druid数据库连接池 --> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close"> <property name="driverClassName" value="${mysql.driver}"/> <property name="url" value="${mysql.url}"/> <property name="username" value="${mysql.username}"/> <property name="password" value="${mysql.password}"/> <!-- 初始化连贯大小 --> <property name="initialSize" value="0"/> <!-- 连接池最大应用连贯数量 --> <property name="maxActive" value="20"/> <!-- 连接池最大闲暇 --> <property name="maxIdle" value="20"/> <!-- 连接池最小闲暇 --> <property name="minIdle" value="0"/> <!-- 获取连贯最大等待时间 --> <property name="maxWait" value="60000"/> <property name="validationQuery" value="${validationQuery}"/> <property name="testOnBorrow" value="false"/> <property name="testOnReturn" value="false"/> <property name="testWhileIdle" value="true"/> <!-- 配置距离多久才进行一次检测,检测须要敞开的闲暇连贯,单位是毫秒 --> <property name="timeBetweenEvictionRunsMillis" value="60000"/> <!-- 配置一个连贯在池中最小生存的工夫,单位是毫秒 --> <property name="minEvictableIdleTimeMillis" value="25200000"/> <!-- 关上removeAbandoned性能 --> <property name="removeAbandoned" value="true"/> <!-- 1800秒,也就是30分钟 --> <property name="removeAbandonedTimeout" value="1800"/> <!-- 敞开abanded连贯时输入谬误日志 --> <property name="logAbandoned" value="true"/> <!-- 监控数据库 --> <property name="filters" value="mergeStat"/> </bean> <!-- (事务管理)transaction manager, use JtaTransactionManager for global tx --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <!-- 可通过注解管制事务 --> <tx:annotation-driven transaction-manager="transactionManager"/> <!--mybatis--> <bean id="sqlSessionFactory" class="com.baomidou.mybatisplus.spring.MybatisSqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <!-- 主动扫描mapper.xml文件,反对通配符 --> <property name="mapperLocations" value="classpath:mapper/**/*.xml"/> <!-- 配置文件,比方参数配置(是否启动驼峰等)、插件配置等 --> <property name="configLocation" value="classpath:mybatis/mybatis-config.xml"/> <!-- 启用别名,这样就无需写全门路类名了,具体可自行查阅材料 --> <property name="typeAliasesPackage" value="cn.lqdev.learning.springboot.chapter9.biz.entity"/> <!-- MP 全局配置注入 --> <property name="globalConfig" ref="globalConfig"/> </bean> <bean id="globalConfig" class="com.baomidou.mybatisplus.entity.GlobalConfiguration"> <!-- AUTO->`0`("数据库ID自增")QW INPUT->`1`(用户输出ID") ID_WORKER->`2`("全局惟一ID") UUID->`3`("全局惟一ID") --> <property name="idType" value="3" /> </bean> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <!-- 主动扫描包门路,接口主动注册为一个bean类 --> <property name="basePackage" value="cn.lqdev.learning.springboot.chapter9.biz.dao"/> </bean></beans>
编写启动类,利用启动时主动加载配置xml文件
@Configuration@ImportResource(locations = {"classpath:/mybatis/spring-mybatis.xml"})//@MapperScan("cn.lqdev.learning.springboot.chapter9.biz.dao")//@EnableTransactionManagementpublic class MybatisPlusConfig {}
MyBatis Plus集成Spring
数据表构造
DROP TABLE IF EXISTS tbl_employee;CREATE TABLE tbl_employee(id int(11) NOT NULL AUTO_INCREMENT,last_name varchar(50) DEFAULT NULL,email varchar(50) DEFAULT NULL,gender char(1) DEFAULT NULL,age int(11) DEFAULT NULL,PRIMARY KEY (id)) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8;
pom.xml
<dependencies> <!-- MP --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus</artifactId> <version>2.3</version> </dependency> <!-- 测试 --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> <!-- 数据源 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.10</version> </dependency> <!-- 数据库驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.39</version> </dependency> <!-- Spring 相干 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>4.3.9.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <version>4.3.9.RELEASE</version> </dependency> </dependencies>
MyBatis全局配置文件mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"><!-- 不作任何配置 --><configuration />
数据源db.properties
jdbc.url=jdbc:mysql://localhost:3306/mpjdbc.username=mpjdbc.password=mp
Spring 配置文件applicationContext.xml
<!-- 数据源 --> <context:property-placeholder location="classpath:db.properties"/> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="url" value="${jdbc.url}"></property> <property name="username" value="${jdbc.username}"></property> <property name="password" value="${jdbc.password}"></property> </bean> <!-- MP 提供的 MybatisSqlSessionFactoryBean --> <bean id="sqlSessionFactoryBean" class="com.baomidou.mybatisplus.spring.MybatisSqlSessionFactoryBean"> <!-- 数据源 --> <property name="dataSource" ref="dataSource"></property> <!-- mybatis 全局配置文件 --> <property name="configLocation" value="classpath:mybatis-config.xml"></property> <!-- 别名解决 --> <property name="typeAliasesPackage" value="com.jas.bean"></property> <!-- 注入全局MP策略配置 --> <property name="globalConfig" ref="globalConfiguration"></property> <!-- 插件注册 --> <property name="plugins"> <list> <!-- 注册分页插件 --> <bean class="com.baomidou.mybatisplus.plugins.PaginationInterceptor" /> <!-- 注入 SQL 性能剖析插件,倡议在开发环境中应用,能够在控制台查看 SQL 执行日志 --> <bean class="com.baomidou.mybatisplus.plugins.PerformanceInterceptor"> <property name="maxTime" value="1000" /> <!--SQL 是否格式化 默认false--> <property name="format" value="true" /> </bean> </list> </property> </bean> <!-- 定义 MybatisPlus 的全局策略配置--> <bean id ="globalConfiguration" class="com.baomidou.mybatisplus.entity.GlobalConfiguration"> <!-- 在 2.3 版本当前,dbColumnUnderline 默认值是 true --> <property name="dbColumnUnderline" value="true"></property> <!-- 全局的主键策略 --> <property name="idType" value="0"></property> <!-- 全局的表前缀策略配置 --> <property name="tablePrefix" value="tbl_"></property> </bean> <!-- 配置mybatis 扫描mapper接口的门路 --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.jas.mapper"></property> </bean>
MyBatis Plus应用示例
实体类Employee
@TableName(value = "tbl_employee")public class Employee { @TableId(value = "id", type = IdType.AUTO) private Integer id; @TableField(value = "last_name") private String lastName; private String email; private Integer gender; private Integer age; public Employee() { super(); } public Employee(Integer id, String lastName, String email, Integer gender, Integer age) { this.id = id; this.lastName = lastName; this.email = email; this.gender = gender; this.age = age; } // 省略 set、get 与 toString() 办法
mapper接口
不定义任何接口办法
*/
public interface EmployeeMapper extends BaseMapper<Employee> {}在测试类中生成测试的mapper对象
private ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml"); private EmployeeMapper employeeMapper = context.getBean("employeeMapper", EmployeeMapper.class);
查问:
@Test public void getEmpByIdTest() { Employee employee = employeeMapper.selectById(1); System.out.println(employee); }
分页查问:
@Test public void getEmpByPage() { Page<?> page = new Page<>(1, 5); List<Employee> list = employeeMapper.selectPage(page, null); System.out.println("总记录数:" + page.getTotal()); System.out.println("总页数" + page.getPages()); System.out.println(list); }
条件结构器:
@Test public void getEmpByName() { EntityWrapper<Employee> wrapper = new EntityWrapper<>(); // 'last_name' 与 'age' 对应数据库中的字段 wrapper.like("last_name", "张"); wrapper.eq("age", 20); List<Employee> list = employeeMapper.selectList(wrapper); System.out.println(list); }
控制台输入的SQL剖析日志
简略的数据库操作不须要在 EmployeeMapper 接口中定义任何办法,也没有在配置文件中编写SQL语句,而是通过继承BaseMapper<T>接口取得通用的的增删改查办法,简单的SQL也能够应用条件结构器拼接.不过简单的业务需要还是要编写SQL语句的,流程和MyBatis一样.MyBatis Plus应用场景
代码生成器
代码生成器依赖velocity模版引擎,引入依赖
<dependency> <groupId>org.apache.velocity</groupId> <artifactId>velocity-engine-core</artifactId> <version>2.0</version> <scope>test</scope></dependency>
代码生成器类MysqlGenerator:
public class MysqlGenerator { private static final String PACKAGE_NAME = "cn.lqdev.learning.springboot.chapter9"; private static final String MODULE_NAME = "biz"; private static final String OUT_PATH = "D:\\develop\\code"; private static final String AUTHOR = "oKong"; private static final String DRIVER = "com.mysql.jdbc.Driver"; private static final String URL = "jdbc:mysql://127.0.0.1:3306/learning?useUnicode=true&characterEncoding=UTF-8"; private static final String USER_NAME = "root"; private static final String PASSWORD = "123456"; /** * <p> * MySQL 生成演示 * </p> */ public static void main(String[] args) { // 自定义须要填充的字段 List<TableFill> tableFillList = new ArrayList<TableFill>(); // 代码生成器 AutoGenerator mpg = new AutoGenerator().setGlobalConfig( // 全局配置 new GlobalConfig().setOutputDir(OUT_PATH)// 输入目录 .setFileOverride(true)// 是否覆盖文件 .setActiveRecord(true)// 开启 activeRecord 模式 .setEnableCache(false)// XML 二级缓存 .setBaseResultMap(false)// XML ResultMap .setBaseColumnList(true)// XML columList .setAuthor(AUTHOR) // 自定义文件命名,留神 %s 会主动填充表实体属性! .setXmlName("%sMapper").setMapperName("%sDao") // .setServiceName("MP%sService") // .setServiceImplName("%sServiceDiy") // .setControllerName("%sAction") ).setDataSource( // 数据源配置 new DataSourceConfig().setDbType(DbType.MYSQL)// 数据库类型 .setTypeConvert(new MySqlTypeConvert() { // 自定义数据库表字段类型转换【可选】 @Override public DbColumnType processTypeConvert(String fieldType) { System.out.println("转换类型:" + fieldType); // if ( fieldType.toLowerCase().contains( "tinyint" ) ) { // return DbColumnType.BOOLEAN; // } return super.processTypeConvert(fieldType); } }).setDriverName(DRIVER).setUsername(USER_NAME).setPassword(PASSWORD).setUrl(URL)) .setStrategy( // 策略配置 new StrategyConfig() // .setCapitalMode(true)// 全局大写命名 .setDbColumnUnderline(true)// 全局下划线命名 // .setTablePrefix(new String[]{"unionpay_"})// 此处能够批改为您的表前缀 .setNaming(NamingStrategy.underline_to_camel)// 表名生成策略 // .setInclude(new String[] {"citycode_org"}) // 须要生成的表 // .setExclude(new String[]{"test"}) // 排除生成的表 // 自定义实体,公共字段 // .setSuperEntityColumns(new String[]{"test_id"}) .setTableFillList(tableFillList) // 自定义实体父类 // .setSuperEntityClass("com.baomidou.demo.common.base.BsBaseEntity") // // 自定义 mapper 父类 // .setSuperMapperClass("com.baomidou.demo.common.base.BsBaseMapper") // // 自定义 service 父类 // .setSuperServiceClass("com.baomidou.demo.common.base.BsBaseService") // // 自定义 service 实现类父类 // .setSuperServiceImplClass("com.baomidou.demo.common.base.BsBaseServiceImpl") // 自定义 controller 父类 // .setSuperControllerClass("com.baomidou.demo.TestController") // 【实体】是否生成字段常量(默认 false) // public static final String ID = "test_id"; .setEntityColumnConstant(true) // 【实体】是否为构建者模型(默认 false) // public User setName(String name) {this.name = name; return this;} .setEntityBuilderModel(true) // 【实体】是否为lombok模型(默认 false)<a href="https://projectlombok.org/">document</a> .setEntityLombokModel(true) // Boolean类型字段是否移除is前缀解决 // .setEntityBooleanColumnRemoveIsPrefix(true) // .setRestControllerStyle(true) // .setControllerMappingHyphenStyle(true) ).setPackageInfo( // 包配置 new PackageConfig().setModuleName(MODULE_NAME).setParent(PACKAGE_NAME)// 自定义包门路 .setController("controller")// 这里是控制器包名,默认 web .setXml("mapper").setMapper("dao") ).setCfg( // 注入自定义配置,能够在 VM 中应用 cfg.abc 设置的值 new InjectionConfig() { @Override public void initMap() { Map<String, Object> map = new HashMap<String, Object>(); map.put("abc", this.getConfig().getGlobalConfig().getAuthor() + "-mp"); this.setMap(map); } }.setFileOutConfigList( Collections.<FileOutConfig>singletonList(new FileOutConfig("/templates/mapper.xml.vm") { // 自定义输入文件目录 @Override public String outputFile(TableInfo tableInfo) { return OUT_PATH + "/xml/" + tableInfo.getEntityName() + "Mapper.xml"; } }))) .setTemplate( // 敞开默认 xml 生成,调整生成 至 根目录 new TemplateConfig().setXml(null) // 自定义模板配置,模板能够参考源码 /mybatis-plus/src/main/resources/template 应用 copy // 至您我的项目 src/main/resources/template 目录下,模板名称也可自定义如下配置: // .setController("..."); // .setEntity("..."); // .setMapper("..."); // .setXml("..."); // .setService("..."); // .setServiceImpl("..."); ); // 执行生成 mpg.execute(); }}
通用CRUD
通用CRUD测试类GeneralTest:
@RunWith(SpringRunner.class)//SpringBootTest 是springboot 用于测试的注解,可指定启动类或者测试环境等,这里间接默认。@SpringBootTest @Slf4jpublic class GeneralTest { @Autowired IUserService userService; @Test public void testInsert() { User user = new User(); user.setCode("001"); user.setName("okong-insert"); //默认的插入策略为:FieldStrategy.NOT_NULL,即:判断 null //对应在mapper.xml时写法为:<if test="field!=null"> //这个能够批改的,设置字段的@TableField(strategy=FieldStrategy.NOT_EMPTY) //所以这个时候,为null的字段是不会更新的,也能够开启性能插件,查看sql语句就能够晓得 userService.insert(user); //新增所有字段, userService.insertAllColumn(user); log.info("新增完结"); } @Test public void testUpdate() { User user = new User(); user.setCode("101"); user.setName("oKong-insert"); //这就是ActiveRecord的性能 user.insert(); //也能够间接 userService.insert(user); //更新 User updUser = new User(); updUser.setId(user.getId()); updUser.setName("okong-upd"); updUser.updateById(); log.info("更新完结"); } @Test public void testDelete() { User user = new User(); user.setCode("101"); user.setName("oKong-delete"); user.insert(); //删除 user.deleteById(); log.info("删除完结"); } @Test public void testSelect() { User user = new User(); user.setCode("201"); user.setName("oKong-selecdt"); user.insert(); log.info("查问:{}",user.selectById()); }}
MyBatis Plus定义的数据库操作方法
对于通用代码如何注入的,可查看com.baomidou.mybatisplus.mapper.AutoSqlInjector类,这个就是注入通用的CURD办法的类.条件结构器
条件结构器次要提供了实体包装器,用于解决SQL语句拼接,排序,实体参数查问:应用的是数据库字段,不是Java属性
sql条件拼接:
SQL条件拼接测试类ConditionTest@RunWith(SpringRunner.class)//SpringBootTest 是springboot 用于测试的注解,可指定启动类或者测试环境等,这里间接默认。@SpringBootTest @Slf4jpublic class ConditionTest { @Autowired IUserService userService; @Test public void testOne() { User user = new User(); user.setCode("701"); user.setName("okong-condition"); user.insert(); EntityWrapper<User> qryWrapper = new EntityWrapper<>(); qryWrapper.eq(User.CODE, user.getCode()); qryWrapper.eq(User.NAME, user.getName()); //也能够间接 // qryWrapper.setEntity(user); //打印sql语句 System.out.println(qryWrapper.getSqlSegment()); //设置select 字段 即:select code,name from qryWrapper.setSqlSelect(User.CODE,User.NAME); System.out.println(qryWrapper.getSqlSelect()); //查问 User qryUser = userService.selectOne(qryWrapper); System.out.println(qryUser); log.info("拼接一完结"); } @Test public void testTwo() { User user = new User(); user.setCode("702"); user.setName("okong-condition"); user.insert(); EntityWrapper<User> qryWrapper = new EntityWrapper<>(); qryWrapper.where("code = {0}", user.getCode()) .and("name = {0}",user.getName()) .andNew("status = 0"); System.out.println(qryWrapper.getSqlSegment()); //等等很简单的。 //简单的倡议间接写在xml外面了,要是非动静的话 比拟xml一眼看得懂呀 //查问 User qryUser = userService.selectOne(qryWrapper); System.out.println(qryUser); log.info("拼接二完结"); }}
MyBatis Plus提供的条件构造方法com.baomidou.mybatisplus.mapper.Wrapper<T>
自定义SQL应用条件结构器:
UserDao.java退出接口办法:/** * * @param rowBounds 分页对象 间接传入page即可 * @param wrapper 条件结构器 * @return */ List<User> selectUserWrapper(RowBounds rowBounds, @Param("ew") Wrapper<User> wrapper);
UserMapper.xml退出对应的xml节点:
<!-- 条件结构器模式 --> <select id="selectUserWrapper" resultType="user"> SELECT <include refid="Base_Column_List" /> FROM USER <where> ${ew.sqlSegment} </where> </select>
自定义SQL应用条件结构器测试类:
@Test public void testCustomSql() { User user = new User(); user.setCode("703"); user.setName("okong-condition"); user.insert(); EntityWrapper<User> qryWrapper = new EntityWrapper<>(); qryWrapper.eq(User.CODE, user.getCode()); Page<User> pageUser = new Page<>(); pageUser.setCurrent(1); pageUser.setSize(10); List<User> userlist = userDao.selectUserWrapper(pageUser, qryWrapper); System.out.println(userlist.get(0)); log.info("自定义sql完结"); }
xml模式应用wrapper:
UserDao.java:/** * * @param rowBounds 分页对象 间接传入page即可 * @param wrapper 条件结构器 * @return */ List<User> selectUserWrapper(RowBounds rowBounds, @Param("ew") Wrapper<User> wrapper);
UserMapper.xml:
<!-- 条件结构器模式 --> <select id="selectUserWrapper" resultType="user"> SELECT <include refid="Base_Column_List" /> FROM USER <where> ${ew.sqlSegment} </where> </select>
- 条件参数阐明:
查问形式 | 应用阐明 |
---|---|
setSqlSelect | 设置SELECT查问字段 |
where | WHERE语句,拼接+WHERE条件 |
and | AND语句,拼接+AND 字段=值 |
andNew | AND 语句,拼接+AND(字段=值) |
or | OR 语句,拼接+OR 字段=值 |
orNew | OR 语句,拼接+OR(字段=值) |
eq | 等于= |
allEq | 基于map内容等于= |
ne | 不等于<> |
gt | 大于> |
ge | 大于等于>= |
lt | 小于< |
le | 小于等于<= |
like | 含糊查问 LIKE |
notLike | 含糊查问NOT LIKE |
in | IN 查问 |
notIn | NOT IN查问 |
isNull | NULL值查问 |
isNotNull | IS NOT NULL |
groupBy | 分组GROUP BY |
having | HAVING关键词 |
orderBy | 排序ORDER BY |
orderAsc | 排序ASC ORDER BY |
orderDesc | 排序DESC ORDER BY |
exists | EXISTS条件语句 |
notExists | NOT EXISTS条件语句 |
between | BETWEEN条件语句 |
notBetween | NOT BETWEEN条件语句 |
addFilter | 自在拼接SQL |
last | 拼接在最初 |
自定义SQL语句
在多表关联时,条件结构器和通用CURD都无奈满足时,能够编写SQL语句进行扩大.这些都是mybatis的用法.首先革新UserDao接口,有两种形式:
注解模式:
@Select("SELECT * FROM USER WHERE CODE = #{userCode}") List<User> selectUserCustomParamsByAnno(@Param("userCode")String userCode);
xml模式:
List<User> selectUserCustomParamsByXml(@Param("userCode")String userCode);
UserMapper.xml新增一个节点:
<!-- 因为设置了别名:typeAliasesPackage=cn.lqdev.learning.mybatisplus.samples.biz.entity,所以resultType能够不写全门路了。 --> <select id="selectUserCustomParamsByXml" resultType="user"> SELECT <include refid="Base_Column_List"/> FROM USER WHERE CODE = #{userCode} </select>
自定义SQL语句测试类CustomSqlTest:
@RunWith(SpringRunner.class)//SpringBootTest 是springboot 用于测试的注解,可指定启动类或者测试环境等,这里间接默认。@SpringBootTest @Slf4jpublic class CustomSqlTest { @Autowired UserDao userDao; @Test public void testCustomAnno() { User user = new User(); user.setCode("901"); user.setName("okong-sql"); user.insert(); List<User> userlist = userDao.selectUserCustomParamsByAnno(user.getCode()); //因为新增的 必定不为null 故不判断了。 System.out.println(userlist.get(0).toString()); log.info("注解模式完结------"); } @Test public void testCustomXml() { User user = new User(); user.setCode("902"); user.setName("okong-sql"); user.insert(); List<User> userlist = userDao.selectUserCustomParamsByXml(user.getCode()); //因为新增的 必定不为null 故不判断了。 System.out.println(userlist.get(0).toString()); log.info("xml模式完结------"); }}
==留神:==
在应用spring-boot-maven-plugin插件打包成springboot运行jar时,须要留神:因为springboot的jar扫描门路形式问题,会导致别名的包未扫描到,所以这个只须要把mybatis默认的扫描设置为Springboot的VFS实现.批改spring-mybatis.xml文件:<!--mybatis--> <bean id="sqlSessionFactory" class="com.baomidou.mybatisplus.spring.MybatisSqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <!-- 主动扫描mapper.xml文件,反对通配符 --> <property name="mapperLocations" value="classpath:mapper/**/*.xml"/> <!-- 配置文件,比方参数配置(是否启动驼峰等)、插件配置等 --> <property name="configLocation" value="classpath:mybatis/mybatis-config.xml"/> <!-- 启用别名,这样就无需写全门路类名了,具体可自行查阅材料 --> <property name="typeAliasesPackage" value="cn.lqdev.learning.mybatisplus.samples.biz.entity"/> <!-- MP 全局配置注入 --> <property name="globalConfig" ref="globalConfig"/> <!-- 设置vfs实现,防止门路扫描问题 --> <property name="vfs" value="com.baomidou.mybatisplus.spring.boot.starter.SpringBootVFS"></property> </bean>
分页插件,性能剖析插件
mybatis的插件机制应用只须要注册即可
mybatis-config.xml
<plugins> <!-- SQL 执行性能剖析,开发环境应用,线上不举荐。 --> <plugin interceptor="com.baomidou.mybatisplus.plugins.PerformanceInterceptor"></plugin> <!-- 分页插件配置 --> <plugin interceptor="com.baomidou.mybatisplus.plugins.PaginationInterceptor"></plugin> </plugins>
分页测试类(性能剖析,配置后能够输入sql及取数时间):
@RunWith(SpringRunner.class)//SpringBootTest 是springboot 用于测试的注解,可指定启动类或者测试环境等,这里间接默认。@SpringBootTest @Slf4jpublic class PluginTest { @Autowired IUserService userService; @Test public void testPagination() { Page<User> page = new Page<>(); //每页数 page.setSize(10); //以后页码 page.setCurrent(1); //无条件时 Page<User> pageList = userService.selectPage(page); System.out.println(pageList.getRecords().get(0)); //新增数据 防止查问不到数据 User user = new User(); user.setCode("801"); user.setName("okong-Pagination"); user.insert(); //退出条件结构器 EntityWrapper<User> qryWapper = new EntityWrapper<>(); //这里也能间接设置 entity 这是条件就是entity的非空字段值了// qryWapper.setEntity(user); //这里倡议间接用 常量 // qryWapper.eq(User.CODE, user.getCode()); pageList = userService.selectPage(page, qryWapper); System.out.println(pageList.getRecords().get(0)); log.info("分页完结"); }}
性能插件体现,控制台输入:
Time:4 ms - ID:cn.lqdev.learning.mybatisplus.samples.biz.dao.UserDao.selectPage Execute SQL: SELECT id AS id,code,`name`,`status`,gmt_create AS gmtCreate,gmt_modified AS gmtModified FROM user WHERE id=1026120705692434433 AND code='801' AND `name`='okong-Pagination' LIMIT 0,10
公共字段主动填充
通常,每个公司都有本人的表定义,在《阿里巴巴Java开发手册》中,就强制规定表必备三字段:id,gmt_create,gmt_modified.所以通常咱们都会写个公共的拦截器去实现主动填充比方创立工夫和更新工夫的,无需开发人员手动设置.而在MP中就提供了这么一个公共字段主动填充性能
- 设置填充字段的填充类型:
User
==留神==能够在代码生成器外面配置规定的,可主动配置/** * 创立工夫 */ @TableField(fill=FieldFill.INSERT) private Date gmtCreate; /** * 批改工夫 */ @TableField(fill=FieldFill.INSERT_UPDATE) private Date gmtModified;
- 定义解决类:
MybatisObjectHandler
public class MybatisObjectHandler extends MetaObjectHandler{ @Override public void insertFill(MetaObject metaObject) { //新增时填充的字段 setFieldValByName("gmtCreate", new Date(), metaObject); setFieldValByName("gmtModified", new Date(), metaObject); } @Override public void updateFill(MetaObject metaObject) { //更新时 须要填充字段 setFieldValByName("gmtModified", new Date(), metaObject); }}
批改springb-mybatis.xml文件,退出此配置
<bean id="globalConfig" class="com.baomidou.mybatisplus.entity.GlobalConfiguration"> <!-- AUTO->`0`("数据库ID自增")QW INPUT->`1`(用户输出ID") ID_WORKER->`2`("全局惟一ID") UUID->`3`("全局惟一ID") --> <property name="idType" value="2" /> <property name="metaObjectHandler" ref="mybatisObjectHandler"></property> </bean> <bean id="mybatisObjectHandler" class="cn.lqdev.learning.mybatisplus.samples.config.MybatisObjectHandler"/>
再新增或者批改时,对应工夫就会进行更新:
Time:31 ms - ID:cn.lqdev.learning.mybatisplus.samples.biz.dao.UserDao.insert Execute SQL: INSERT INTO user ( id, code, `name`, gmt_create,gmt_modified ) VALUES ( 1026135016838037506, '702', 'okong-condition', '2018-08-05 23:57:07.344','2018-08-05 23:57:07.344' )
数据库连接池-Alibaba Druid
Druid是JDBC组件,包含三个局部:
- DruidDriver: 代理Driver,可能提供基于Filter-Chain模式的插件体系
- DruidDataSource: 高效可治理的数据库连接池
- SQL Parser: Druid内置应用SQL Parser来实现进攻SQL注入(WallFilter),合并统计没有参数化的SQL(StatFilter的mergeSql),SQL格式化,分库分表
Druid的作用:
- 监控数据库拜访性能: Druid内置提供了一个功能强大的StatFilter插件,可能具体统计SQL的执行性能,晋升线上剖析数据库拜访性能
- 替换DBCP和C3P0: Druid提供了一个高效,功能强大,可扩展性好的数据库连接池
- 数据库明码加密: 间接把数据库明码写在配置文件容易导致平安问题,DruidDruiver和DruidDataSource都反对PasswordCallback
- 监控SQL执行日志: Druid提供了不同的LogFilter,可能反对Common-Logging,Log4j和JdkLog,能够按须要抉择相应的LogFilter,监控数据库拜访状况
- 扩大JDBC: 通过Druid提供的Filter-Chain机制,编写JDBC层的扩大
- 配置参数: Druid的DataSource:com.alibaba.druid.pool.DruidDataSource
配置参数 | 缺省值 | 阐明 |
---|---|---|
name | 如果存在多个数据源,监控时能够通过name属性进行辨别,如果没有配置,将会生成一个名字:"DataSource-"+System.identityHashCode(this) | |
jdbcUrl | 连贯数据库的url,不同的数据库url示意形式不同: mysql:jdbc:mysql://192.16.32.128:3306/druid2 oracle : jdbc:oracle:thin:@192.16.32.128:1521:druid2 | |
username | 连贯数据库的用户名 | |
password | 连贯数据库的明码,明码不呈现在配置文件中能够应用ConfigFilter | |
driverClassName | 依据jdbcUrl自动识别 | 能够不配置,Druid会依据jdbcUrl自动识别dbType,抉择相应的driverClassName |
initialSize | 0 | 初始化时建设物理连贯的个数. 初始化过程产生在:显示调用init办法;第一次getConnection |
maxActive | 8 | 最大连接池数量 |
minIdle | 最小连接池数量 | |
maxWait | 获取连贯时最大等待时间,单位毫秒. 配置maxWait默认应用偏心锁期待机制,并发效率会降落.能够配置useUnfairLock为true应用非偏心锁 | |
poolPreparedStatements | false | 是否缓存preparedStatement,即PSCache. PSCache可能晋升对反对游标的数据库性能. 在Oracle中应用,在MySQL中敞开 |
maxOpenPreparedStatements | -1 | 要启用PSCache,必须配置参数值>0,poolPreparedStatements主动触发批改为true. Oracle中能够配置数值为100,Oracle中不会存在PSCache过多的问题 |
validationQuery | 用来检测连贯的是否为无效SQL,要求是一个查问语句 如果validationQuery=null,那么testOnBorrow,testOnReturn,testWhileIdle都不会起作用 | |
testOnBorrow | true | 申请连贯时执行validationQuery检测连贯是否无效,会升高性能 |
testOnReturn | false | 偿还连贯时执行validationQuery检测连贯是否无效,会升高性能 |
testWhileIdle | false | 申请连贯时,闲暇工夫大于timeBetweenEvictionRunsMillis时,执行validationQuery检测连贯是否无效 不影响性能,保障安全性,倡议配置为true |
timeBetweenEvictionRunsMillis | Destroy线程会检测连贯的间隔时间 testWhileIdle的判断根据 | |
connectionInitSqls | 物理连贯初始化时执行SQL | |
exceptionSorter | 依据dbType自动识别 | 当数据库跑出不可复原的异样时,摈弃连贯 |
filters | 通过别名的形式配置扩大插件,属性类型是字符串: 罕用的插件: 监控统计用的filter:stat 日志用的filter:log4j 进攻sql注入的filter:wall | |
proxyFilters | 类型是List<com.alibaba.druid.filter.Filter>,如果同时配置了filters和proxyFilters是组合关系,不是替换关系 |
Druid的架构
Druid数据结构
- Druid架构相辅相成的是基于DataSource和Segment的数据结构
DataSource数据结构: 是逻辑概念, 与传统的关系型数据库相比拟DataSource能够了解为表
- 工夫列: 表明每行数据的工夫值
- 维度列: 表明数据的各个维度信息
- 指标列: 须要聚合的列的数据
Segment构造: 理论的物理存储格局,
- Druid通过Segment实现了横纵向切割操作
- Druid将不同的工夫范畴内的数据寄存在不同的Segment文件块中,通过工夫实现了横向切割
- Segment也面向列进行数据压缩存储,实现纵向切割
Druid架构蕴含四个节点和一个服务:
- 实时节点(RealTime Node): 即时摄入实时数据,并且生成Segment文件
- 历史节点(Historical Node): 加载曾经生成好的数据文件,以供数据查问应用
- 查问节点(Broker Node): 对外提供数据查问服务,并且从实时节点和历史节点汇总数据,合并后返回
- 协调节点( Coordinator Node): 负责历史节点的数据的负载平衡,以及通过规定治理数据的生命周期
索引服务(Indexing Service): 有不同的获取数据的形式,更加灵便的生成segment文件治理资源
实时节点
- 次要负责即时摄入实时数据,以及生成Segment文件
实时节点通过firehose进行数据的摄入,firehose是Druid实时生产模型
通过kafka生产,就是kafkaFireHose.同时,实时节点的另外一个模块Plumer,用于Segment的生成,并且依照指定的周期,将本周期内生成的所有数据块合并成一个
Segment文件从制作到流传过程:
1.实时节点生产出Segment文件,并且存到文件系统中2.Segment文件的<MetaStore>寄存到Mysql等其余内部数据库中3.Master通过Mysql中的MetaStore,通过肯定的规定,将Segment调配给属于它的节点4.历史节点失去Master发送的指令后会从文件系统中拉取属于本人的Segment文件,并且通过zookeeper,告知集群,本人提供了此块Segment的查问服务5.实时节点抛弃Segment文件,并且申明不在提供此块文件的查问服务
历史节点
历史节点再启动的时候:
- 优先查看本人的本地缓存中是否曾经有了缓存的Segment文件
- 而后从文件系统中下载属于本人,但还不存在的Segment文件
- 无论是何种查问,历史节点首先将相干的Segment从磁盘加载到内存.而后再提供服务
历史节点的查问效率受内存空间充裕水平的影响很大:
- 内存空间充裕,查问时须要从磁盘加载数据的次数缩小,查问速度就快
- 内存空间有余,查问时须要从磁盘加载数据的次数就多,查问速度就绝对较慢
原则上历史节点的查问速度与其内存大小和所负责的Segment数据文件大小成正比关系
查问节点
查问节点便是整个集群的查问中枢:
- 在惯例状况下,Druid集群间接对外提供查问的节点只有查问节点, 而查问节点会将从实时节点与历史节点查问到的数据合并后返回给客户端
- Druid应用了Cache机制来进步本人的查问效率.
Druid提供两类介质作为Cache:
- 内部cache:Memcached
外部Cache: 查问节点或历史节点的内存, 如果用查问节点的内存作为Cache,查问的时候会首先拜访其Cache,只有当不命中的时候才会去拜访历史节点和实时节点查问数据
协调节点
- 对于整个Druid集群来说,其实并没有真正意义上的Master节点.
- 实时节点与查问节点能自行治理并不听命于任何其余节点,
- 对于历史节点来说,协调节点便是他们的Master,因为协调节点将会给历史节点调配数据,实现数据分布在历史节点之间的负载平衡.
- 历史节点之间是互相不进行通信的,全副通过协调节点进行通信
利用规定治理数据的生命周期:
- Druid利用针对每个DataSoure设置的规定来加载或者抛弃具体的文件数据,来治理数据的生命周期
- 能够对一个DataSource按程序增加多条规定,对于一个Segment文件来说,协调节点会逐条查看规定
当碰到以后Segment文件负责某条规定的状况下,协调节点会立刻命令历史节点对该文件执行此规定,加载或者抛弃,并进行余下的规定,否则持续查看
索引服务
除了通过实时节点生产Segment文件之外,druid还提供了一组索引服务来摄入数据
索引服务的长处:
- 有不同的获取数据的形式,反对pull和push
- 能够通过API编程的形式来配置工作
- 能够更加灵便地应用资源
- 灵便地操作Segment文件
索引服务的主从架构:
索引服务蕴含一组组件,并以主从构造作为架构形式,统治节点 Overload node为主节点,两头管理者Middle Manager为从节点Overload node: 索引服务的主节点.对外负责接管工作申请,对内负责将工作合成并下发到从节点即Middle Manager.有两种运行模式:
- 本地模式(默认): 此模式主节点不仅须要负责集群的调度,协调调配工作,还须要负责启动Peon(苦工)来实现一部分具体的工作
近程模式: 主从节点别离运行在不同的节点上,主节点只负责协调调配工作.不负责实现工作,并且提供rest服务,因而客户端能够通过HTTP POST来提交工作
Middle Manager与Peon(苦工):Middle Manager即是Overload node 的工作节点,负责接管Overload node调配的工作,而后启动相干的Peon来实现工作这种模式和yarn的架构比拟相似1.Overload node相当于Yarn的ResourceManager,负责资源管理和任务分配2.Middle Manager相当于Yarn的NodeManager,负责管理独立节点的资源,并且接管工作3.Peon 相当于Yarn的Container,启动在具体节点上具体任务的执行
网关-Zuul
Zuul是netflix开源的一个API Gateway 服务器, 实质上是一个web servlet利用
-Zuul是一个基于JVM路由和服务端的负载均衡器,提供动静路由,监控,弹性,平安等边缘服务的框架,相当于是设施和 Netflix 流利用的 Web 网站后端所有申请的前门Zuul工作原理
过滤器机制
Zuul提供了一个框架,能够对过滤器进行动静的加载,编译,运行
1.Zuul的过滤器之间没有间接的互相通信,他们之间通过一个RequestContext的动态类来进行数据传递的。RequestContext类中有ThreadLocal变量来记录每个Request所须要传递的数据2.Zuul的过滤器是由Groovy写成,这些过滤器文件被放在Zuul Server上的特定目录上面,Zuul会定期轮询这些目录,批改过的过滤器会动静的加载到Zuul Server中以便过滤申请应用
规范过滤器类型:
Zuul大部分性能都是通过过滤器来实现的。Zuul中定义了四种规范过滤器类型,这些过滤器类型对应于申请的典型生命周期- PRE: 在申请被路由之前调用,利用这种过滤器实现身份验证、在集群中抉择申请的微服务、记录调试信息等
- ROUTING: 申请路由到微服务,用于构建发送给微服务的申请,应用Apache HttpClient或Netfilx Ribbon申请微服务
- POST: 在路由到微服务当前执行,用来为响应增加规范的HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等
ERROR: 在其余阶段产生谬误时执行该过滤器
- 内置的非凡过滤器:
- StaticResponseFilter: StaticResponseFilter容许从Zuul自身生成响应,而不是将申请转发到源
- SurgicalDebugFilter: SurgicalDebugFilter容许将特定申请路由到分隔的调试集群或主机
- 自定义的过滤器:
除了默认的过滤器类型,Zuul还容许咱们创立自定义的过滤器类型。如STATIC类型的过滤器,间接在Zuul中生成响应,而不将申请转发到后端的微服务
- 过滤器的生命周期
Zuul申请的生命周期详细描述了各种类型的过滤器的执行程序 - 过滤器调度过程
动静加载过滤器
Zuul的作用
Zuul能够通过加载动静过滤机制实现Zuul的性能:
- 验证与平安保障: 辨认面向各类资源的验证要求并回绝那些与要求不符的申请
- 审查与监控: 在边缘地位追踪有意义数据及统计后果,失去精确的生产状态论断
- 动静路由: 以动静形式依据须要将申请路由至不同后端集群处
- 压力测试: 逐步减少指向集群的负载流量,从而计算性能程度
- 负载调配: 为每一种负载类型调配对应容量,并弃用超出限定值的申请
- 动态响应解决: 在边缘地位间接建设局部响应,从而防止其流入外部集群
多区域弹性: 逾越AWS区域进行申请路由,旨在实现ELB应用多样化并保障边缘地位与使用者尽可能靠近
Zuul与利用的集成形式
ZuulServlet - 解决申请(调度不同阶段的filters,解决异样等)
- 所有的Request都要通过ZuulServlet的解决,
- Zuul对request解决逻辑的三个外围的办法: preRoute(),route(), postRoute()
- ZuulServletZuulServlet交给ZuulRunner去执行。因为ZuulServlet是单例,因而ZuulRunner也仅有一个实例。ZuulRunner间接将执行逻辑交由FilterProcessor解决,FilterProcessor也是单例,其性能就是根据filterType执行filter的解决逻辑
FilterProcessor对filter的解决逻辑:
1.首先依据Type获取所有输出该Type的filter:List<ZuulFilter> list2.遍历该list,执行每个filter的解决逻辑:processZuulFilter(ZuulFilter filter)3.RequestContext对每个filter的执行情况进行记录,应该注意,此处的执行状态次要包含其执行工夫、以及执行胜利或者失败,如果执行失败则对异样封装后抛出4.到目前为止,Zuul框架对每个filter的执行后果都没有太多的解决,它没有把上一filter的执行后果交由下一个将要执行的filter,仅仅是记录执行状态,如果执行失败抛出异样并终止执行
ContextLifeCycleFilter - RequestContext 的生命周期治理:
- ContextLifecycleFilter的外围性能是为了革除RequestContext;申请上下文RequestContext通过ThreadLocal存储,须要在申请实现后删除该对象RequestContext提供了执行filter Pipeline所须要的Context,因为Servlet是单例多线程,这就要求RequestContext即要线程平安又要Request平安。context应用ThreadLocal保留,这样每个worker线程都有一个与其绑定的RequestContext,因为worker仅能同时解决一个Request,这就保障了Request Context 即是线程平安的由是Request平安的。
GuiceFilter - GOOLE-IOC(Guice是Google开发的一个轻量级,基于Java5(次要使用泛型与正文个性)的依赖注入框架(IOC).Guice十分小而且快.)
- StartServer - 初始化 zuul 各个组件(ioc,插件,filters,数据库等)
- FilterScriptManagerServlet - uploading/downloading/managing scripts, 实现热部署
Filter源码文件放在zuul 服务特定的目录, zuul server会定期扫描目录下的文件的变动,动静的读取\编译\运行这些filter,如果有Filter文件更新,源文件会被动静的读取,编译加载进入服务,接下来的Request解决就由这些新退出的filter解决
缓存-Redis
- Redis: Redis是一个开源的内存中的数据结构存储系统,能够用作数据库,缓存和消息中间件
操作工具:Redis Desktop Manager
整合Redis缓存
在pom.xml中引入redis依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId></dependency>
配置redis,在application.properties中配置redis
spring.redis.host=192.168.32.242
RedisTemplate:(操作k-v都是对象)
@Bean @ConditionalOnMissingBean( name = {"redisTemplate"} ) public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException { RedisTemplate<Object, Object> template = new RedisTemplate(); template.setConnectionFactory(redisConnectionFactory); return template; }
- 保留对象时,应用JDK的序列化机制,将序列化后的数据保留到redis中
为了加强Redis数据库中的数据可读性:
将对象数据以==json==形式保留:
- 将对象转化为json
配置redisTemplate的json序列化规定
@Configurationpublic class MyRedisConfig {@Beanpublic RedisTemplate<Object, Employee> empRedisTemplate(RedisConnectionFactory redisConnectionFactory){ RedisTemplate<Object,Employee> redisTemplate=new RedisTemplate<Object,Employee>(); redisTemplate.setConnectionFactory(redisConnectionFactory); Jackson2JsonRedisSerializer<Employee> serializer = new Jackson2JsonRedisSerializer<Employee>(Employee.class); redisTemplate.setDefaultSerializer(serializer); return redisTemplate;}}
Redis常见的数据类型:String-字符串List-列表Set-汇合Hash-散列ZSet-有序汇合redisTemplate.opsForValue()--String(字符串)redisTemplate.opsForList()--List(列表)redisTemplate.opsForSet()--Set(汇合)redisTemplate.opsForHash()--Hash(散列)redisTemplate.opsForZSet()--ZSet(有序汇合)
StringRedisTemplate(操作k-v都是字符串)
在RedisAutoConfiguration中:@Bean @ConditionalOnMissingBean public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException { StringRedisTemplate template = new StringRedisTemplate(); template.setConnectionFactory(redisConnectionFactory); return template; }
在StringRedisTemplate中:
public class StringRedisTemplate extends RedisTemplate<String, String> { public StringRedisTemplate() { this.setKeySerializer(RedisSerializer.string()); this.setValueSerializer(RedisSerializer.string()); this.setHashKeySerializer(RedisSerializer.string()); this.setHashValueSerializer(RedisSerializer.string()); } public StringRedisTemplate(RedisConnectionFactory connectionFactory) { this(); this.setConnectionFactory(connectionFactory); this.afterPropertiesSet(); } protected RedisConnection preProcessConnection(RedisConnection connection, boolean existingConnection) { return new DefaultStringRedisConnection(connection); }}
Redis常见的数据类型: String-字符串 List-列表 Set-汇合 Hash-散列 ZSet-有序汇合stringRedisTemplate.opsForValue()--String(字符串)stringRedisTemplate.opsForList()--List(列表)stringRedisTemplate.opsForSet()--Set(汇合)stringRedisTemplate.opsForHash()--Hash(散列)stringRedisTemplate.opsForZSet()--ZSet(有序汇合)
注册核心-Zookeeper,Eureka
Zookeeper基本概念
- Zookeeper是一个分布式的,开放源码的分布式应用程序协调服务
- Zookeeper是hadoop的一个子项目
- 蕴含一个简略的原语集, 分布式应用程序能够基于它实现同步服务,配置保护和命名服务等
- 在分布式应用中,因为工程师不能很好地应用锁机制,以及基于音讯的协调机制不适宜在某些利用中应用,Zookeeper提供一种牢靠的,可扩大的,分布式的,可配置的协调机制来对立零碎的状态
- Zookeeper中的角色:
- 零碎模型图:
Zookeeper特点:
- 最终一致性: client不管连贯到哪个Server,展现给它都是同一个视图,这是Zookeeper最重要的性能
- 可靠性: 具备简略,强壮,良好的性能,如果音讯m被到一台服务器承受,那么它将被所有的服务器承受
- 实时性: Zookeeper保障客户端将在一个工夫距离范畴内取得服务器的更新信息,或者服务器生效的信息.但因为网络延时等起因,Zookeeper不能保障两个客户端能同时失去刚更新的数据,如果须要最新数据,应该在读数据之前调用sync()接口
- 期待无关(wait-free): 慢的或者生效的client不得干涉疾速的client的申请,使得每个client都能无效的期待
- 原子性: 更新只能胜利或者失败,没有中间状态
程序性: 包含全局有序和偏序两种:全局有序是指如果在一台服务器上音讯a在音讯b前公布,则在所有Server上音讯a都将在音讯b前被公布.偏序是指如果一个音讯b在音讯a后被同一个发送者公布,a必将排在b后面
Zookeeper工作原理
- Zookeeper的外围是原子播送,这个机制保障了各个Server之间的同步实现这个机制的协定叫做Zab协定
Zab协定有两种模式:恢复模式(选主),播送模式(同步)
- 当服务启动或者在领导者解体后,Zab就进入了恢复模式,当领导者被选举进去,且大多数Server实现了和leader的状态同步当前,恢复模式就完结了
- 状态同步保障了leader和Server具备雷同的零碎状态
- 为了保障事务的程序一致性,zookeeper采纳了递增的事务id号(zxid)来标识事务
- 所有的提议(proposal)都在被提出的时候加上了zxid.实现中zxid是一个64位的数字,它高32位是epoch用来标识leader关系是否扭转,每次一个leader被选出来,它都会有一个新的epoch,标识以后属于那个leader的统治期间.低32位用于递增计数
- 每个Server在工作过程中有三种状态:
- LOOKING: 以后Server不晓得leader是谁,正在搜查
- LEADING: 以后Server即为选举进去的leader
FOLLOWING: leader曾经选举进去,以后Server与之同步
选主流程
- 当leader解体或者leader失去大多数的follower这时候Zookeeper进入恢复模式
- 恢复模式须要从新选举出一个新的leader,让所有的Server都复原到一个正确的状态.
Zookeeper的选举算法有两种:零碎默认的选举算法为fast paxos
- 基于fast paxos算法
- 基于basic paxos算法
- 基于fast paxos算法:
fast paxos流程是在选举过程中,某Server首先向所有Server提议本人要成为leader,当其它Server收到提议当前,解决epoch和zxid的抵触,并承受对方的提议,而后向对方发送承受提议实现的音讯,反复这个流程,最初肯定能选举出Leader 基于basic paxos算法:
- 选举线程由以后Server发动选举的线程负责,其次要性能是对投票后果进行统计,并选出举荐的Server
- 选举线程首先向所有Server发动一次询问(包含本人)
- 选举线程收到回复后,验证是否是本人发动的询问(验证zxid是否统一),而后获取对方的id(myid),并存储到以后询问对象列表中,最初获取对方提议的leader相干信息(id,zxid),并将这些信息存储到当次选举的投票记录表中
- 收到所有Server回复当前,就计算出zxid最大的那个Server,并将这个Server相干信息设置成下一次要投票的Server;
- 线程将以后zxid最大的Server设置为以后Server要举荐的Leader,如果此时获胜的Server取得n/2+1的Server票数,设置以后举荐的leader为获胜的Server,将依据获胜的Server相干信息设置本人的状态,否则,持续这个过程,直到leader被选举进去
通过流程剖析咱们能够得出:要使Leader取得少数Server的反对,则Server总数必须是奇数2n+1,且存活的Server的数目不得少于n+1.每个Server启动后都会反复以上流程.在恢复模式下,如果是刚从解体状态复原的或者刚启动的server还会从磁盘快照中复原数据和会话信息,Zookeeper会记录事务日志并定期进行快照,不便在复原时进行状态复原.选主的具体流程图如下所示:
同步流程
选完leader当前,Zookeeper就进入状态同步过程:
- leader期待server连贯
- Follower连贯leader,将最大的zxid发送给leader
- Leader依据follower的zxid确定同步点
- 实现同步后告诉follower曾经成为uptodate状态
Follower收到uptodate音讯后,又能够从新承受client的申请进行服务
工作流程
Leader工作流程:
Leader次要有三个性能:- 复原数据
- 维持与Learner的心跳,接管Learner申请并判断Learner的申请音讯类型
Learner的音讯类型次要有PING音讯,REQUEST音讯,ACK音讯,REVALIDATE音讯,依据不同的音讯类型,进行不同的解决
- PING音讯: Learner的心跳信息
- REQUEST音讯: Follower发送的提议信息,包含写申请及同步申请
- ACK音讯: Follower的对提议的回复.超过半数的Follower通过,则commit该提议
- REVALIDATE音讯: 用来缩短SESSION无效工夫
- Leader的工作流程简图如下所示,在理论实现中,流程要比下图简单得多,启动了三个线程来实现性能:
- Follower工作流程:
Follower次要有四个性能:
- 向Leader发送申请(PING音讯,REQUEST音讯,ACK音讯,REVALIDATE音讯)
- 接管Leader音讯并进行解决
- 接管Client的申请,如果为写申请,发送给Leader进行投票
- 返回Client后果
Follower的音讯循环解决如下几种来自Leader的音讯:
- PING音讯: 心跳音讯
- PROPOSAL音讯: Leader发动的提案,要求Follower投票
- COMMIT音讯: 服务器端最新一次提案的信息
- UPTODATE音讯: 表明同步实现
- REVALIDATE音讯: 依据Leader的REVALIDATE后果,敞开待revalidate的session还是容许其承受音讯
- SYNC音讯: 返回SYNC后果到客户端,这个音讯最后由客户端发动,用来强制失去最新的更新
- Follower的工作流程简图如下所示,在理论实现中,Follower是通过5个线程来实现性能的:
observer流程和Follower的惟一不同的中央就是observer不会加入leader发动的投票
Zookeeper利用场景
配置管理
- 集中式的配置管理在利用集群中是十分常见的,个别都会实现一套集中的配置管理核心,应答不同的利用集群对于共享各自配置的需要,并且在配置变更时可能告诉到集群中的每一个机器,也能够细分进行分层级监控
Zookeeper很容易实现这种集中式的配置管理,比方将APP1的所有配置配置到/APP1 znode下,APP1所有机器一启动就对/APP1这个节点进行监控(zk.exist("/APP1",true)),并且实现回调办法Watcher,那么在zookeeper上/APP1 znode节点下数据发生变化的时候,每个机器都会收到告诉,Watcher办法将会被执行,那么利用再取下数据即可(zk.getData("/APP1",false,null))
集群治理
- 利用集群中,咱们经常须要让每一个机器晓得集群中(或依赖的其余某一个集群)哪些机器是活着的,并且在集群机器因为宕机,网络断链等起因可能不在人工染指的状况下迅速告诉到每一个机器
Zookeeper同样很容易实现这个性能,比方我在zookeeper服务器端有一个znode叫 /APP1SERVERS, 那么集群中每一个机器启动的时候都去这个节点下创立一个EPHEMERAL类型的节点,比方server1创立/APP1SERVERS/SERVER1(能够应用ip,保障不反复),server2创立/APP1SERVERS/SERVER2,而后SERVER1和SERVER2都watch /APP1SERVERS这个父节点,那么也就是这个父节点下数据或者子节点变动都会告诉对该节点进行watch的客户端.因为EPHEMERAL类型节点有一个很重要的个性,就是客户端和服务器端连贯断掉或者session过期就会使节点隐没,那么在某一个机器挂掉或者断链的时候,其对应的节点就会隐没,而后集群中所有对/APP1SERVERS进行watch的客户端都会收到告诉,而后获得最新列表即可
- 另外有一个利用场景就是集群选master: 一旦master挂掉可能马上能从slave中选出一个master,实现步骤和前者一样,只是机器在启动的时候在APP1SERVERS创立的节点类型变为EPHEMERAL_SEQUENTIAL类型,这样每个节点会主动被编号
咱们默认规定编号最小的为master,所以当咱们对/APP1SERVERS节点做监控的时候,失去服务器列表,只有所有集群机器逻辑认为最小编号节点为master,那么master就被选出,而这个master宕机的时候,相应的znode会隐没,而后新的服务器列表就被推送到客户端,而后每个节点逻辑认为最小编号节点为master,这样就做到动静master选举
Zookeeper监督
Zookeeper所有的读操作-getData(),getChildren(),和exists() 都能够设置监督(watch),监督事件能够了解为一次性的触发器. 官网定义如下: a watch event is one-time trigger, sent to the client that set the watch, which occurs when the data for which the watch was set changes:
One-time trigger(一次性触发)
- 当设置监督的数据产生扭转时,该监督事件会被发送到客户端
- 例如:如果客户端调用了getData("/znode1", true)并且稍后/znode1节点上的数据产生了扭转或者被删除了,客户端将会获取到/znode1发生变化的监督事件,而如果/znode1再一次产生了变动,除非客户端再次对/znode1设置监督,否则客户端不会收到事件告诉
Sent to the client(发送至客户端)
- Zookeeper客户端和服务端是通过socket进行通信的,因为网络存在故障,所以监督事件很有可能不会胜利地达到客户端,监督事件是异步发送至监督者的
- Zookeeper自身提供了保序性(ordering guarantee):即客户端只有首先看到了监督事件后,才会感知到它所设置监督的znode产生了变动(a client will never see a change for which it has set a watch until it first sees the watch event).网络提早或者其余因素可能导致不同的客户端在不同的时刻感知某一监督事件,然而不同的客户端所看到的所有具备统一的程序
The data for which the watch was set(被设置watch的数据)
- znode 节点自身具备不同的改变方式
- 例如:Zookeeper 保护了两条监督链表:数据监督和子节点监督(data watches and child watches) getData() and exists()设置数据监督,getChildren()设置子节点监督
- 又例如:Zookeeper设置的不同监督返回不同的数据,getData()和exists()返回znode节点的相干信息,而getChildren()返回子节点列表.因而,setData()会触发设置在某一节点上所设置的数据监督(假设数据设置胜利),而一次胜利的create()操作则会登程以后节点上所设置的数据监督以及父节点的子节点监督.一次胜利的delete()操作将会触发以后节点的数据监督和子节点监督事件,同时也会触发该节点父节点的child watch
Zookeeper中的监督是轻量级的,因而容易设置,保护和散发.当客户端与 Zookeeper 服务器端失去分割时,客户端并不会收到监督事件的告诉,只有当客户端从新连贯后,若在必要的状况下,以前注册的监督会从新被注册并触发,对于开发人员来说这通常是通明的.只有一种状况会导致监督事件的失落,即:通过exists()设置了某个znode节点的监督,然而如果某个客户端在此znode节点被创立和删除的工夫距离内与zookeeper服务器失去了分割,该客户端即便稍后从新连贯zookeeper服务器后也得不到事件告诉
Eureka(服务发现框架)
Eureka是一个基于REST的服务,次要用于定位运行在AWS域中的中间层服务,以达到负载平衡和中间层服务故障转移的目标. SpringCloud将它集成在其子我的项目spring-cloud-netflix中,以实现SpringCloud的服务发现性能
Eureka的两个组件
- Eureka Server: Eureka Server提供服务注册服务,各个节点启动后,会在Eureka Server中进行注册,这样EurekaServer中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息能够在界面中看到. Eureka Server之间通过复制的形式实现数据的同步
- Eureka Client: 是一个java客户端,用于简化与Eureka Server的交互,客户端同时也就是一个内置的、应用轮询(round-robin)负载算法的负载均衡器
Eureka通过心跳查看、客户端缓存等机制,确保了零碎的高可用性、灵活性和可伸缩性
- 在利用启动后,将会向Eureka Server发送心跳, 如果Eureka Server在多个心跳周期内没有接管到某个节点的心跳,Eureka Server将会从服务注册表中把这个服务节点移除。
Eureka还提供了客户端缓存机制,即便所有的Eureka Server都挂掉,客户端仍然能够利用缓存中的信息生产其余服务的API。Eureka通过心跳查看、客户端缓存等机制,确保了零碎的高可用性、灵活性和可伸缩性
作业调度框架-Quartz
Quartz作业调度框架概念
- Quartz是一个齐全由java编写的开源作业调度框架,是OpenSymphony开源组织在Job scheduling畛域的开源我的项目,它能够与J2EE与J2SE应用程序相结合也能够独自应用,Quartz框架整合了许多额定性能.Quartz能够用来创立简略或运行十个,百个,甚至是好几万个Jobs这样简单的程序
Quartz三个次要的概念:
调度器:
- Quartz框架的外围是调度器
- 调度器负责管理Quartz利用运行时环境
- 调度器不是靠本人做所有的工作,而是依赖框架内一些十分重要的部件
- Quartz怎么能并发运行多个作业的原理: Quartz不仅仅是线程和线程池治理,为确保可伸缩性,Quartz采纳了基于多线程的架构.启动时,框架初始化一套worker线程,这套线程被调度器用来执行预约的作业.
- Quartz依赖一套松耦合的线程池治理部件来治理线程环境
工作:
- 本人编写的业务逻辑,交给quartz执行
触发器:
调度作业,什么时候开始执行,什么时候完结执行
Quartz设计模式
- Builer模式
- Factory模式
- 组件模式
链式写法
Quartz体系结构
Quartz框架中的外围类:
JobDetail:
- Quartz每次运行都会间接创立一个JobDetail,同时创立一个Job实例.
- 不间接承受一个Job的实例,承受一个Job的实现类
- 通过new instance()的反射形式来实例一个Job,在这里Job是一个接口,须要编写类去实现这个接口
Trigger:
- 它由SimpleTrigger和CronTrigger组成
- SimpleTrigger实现相似Timer的定时调度工作,CronTrigger能够通过cron表达式实现更简单的调度逻辑
Scheduler:
- 调度器
JobDetail和Trigger能够通过Scheduler绑定到一起
Quartz重要组件
Job接口
能够通过实现该接口来实现咱们本人的业务逻辑,该接口只有execute()一个办法,咱们能够通过上面的形式来实现Job接口来实现咱们本人的业务逻辑
public class HelloJob implements Job{ public void execute(JobExecutionContext context) throws JobExecutionException { //编写咱们本人的业务逻辑 }
JobDetail
每次都会间接创立一个JobDetail,同时创立一个Job实例,它不间接承受一个Job的实例,然而它承受一个Job的实现类,通过new instance()的反射形式来实例一个Job.能够通过上面的形式将一个Job实现类绑定到JobDetail中
JobDetail jobDetail=JobBuilder.newJob(HelloJob.class). withIdentity("myJob", "group1") .build();
JobBuiler
次要是用来创立JobDeatil实例
JobStore
绑定了Job的各种数据
Trigger
次要用来执行Job实现类的业务逻辑的,咱们能够通过上面的代码来创立一个Trigger实例
CronTrigger trigger = (CronTrigger) TriggerBuilder .newTrigger() .withIdentity("myTrigger", "group1") //创立一个标识符 .startAt(date)//什么时候开始触发 //每秒钟触发一次工作 .withSchedule(CronScheduleBuilder.cronSchedule("* * * * * ? *")) .build();
Scheduler
创立Scheduler有两种形式
通过StdSchedulerFactory来创立
SchedulerFactory sfact=new StdSchedulerFactory();Scheduler scheduler=sfact.getScheduler();
通过DirectSchedulerFactory来创立
DiredtSchedulerFactory factory=DirectSchedulerFactory.getInstance();Scheduler scheduler=factory.getScheduler();
Scheduler配置参数个别存储在quartz.properties中,咱们能够批改参数来配置相应的参数.通过调用getScheduler() 办法就能创立和初始化调度对象
Scheduler的次要函数:
- Date schedulerJob(JobDetail,Trigger trigger): 返回最近触发的一次工夫
- void standby(): 临时挂起
- void shutdown(): 齐全敞开,不能重新启动
- shutdown(true): 示意期待所有正在执行的job执行结束之后,再敞开scheduler
- shutdown(false): 间接敞开scheduler
quartz.properties资源文件:
在org.quartz这个包下,当咱们程序启动的时候,它首先会到咱们的根目录下查看是否配置了该资源文件,如果没有就会到该包下读取相应信息,当咱们咋实现更简单的逻辑时,须要本人指定参数的时候,能够本人配置参数来实现org.quartz.scheduler.instanceName: DefaultQuartzSchedulerorg.quartz.scheduler.rmi.export: falseorg.quartz.scheduler.rmi.proxy: falseorg.quartz.scheduler.wrapJobExecutionInUserTransaction: falseorg.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPoolorg.quartz.threadPool.threadCount: 10org.quartz.threadPool.threadPriority: 5org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: trueorg.quartz.jobStore.misfireThreshold: 60000org.quartz.jobStore.class: org.quartz.simpl.RAMJobStore
quartz.properties资源文件次要组成部分:
- 调度器属性
- 线程池属性
- 作业存储设置
- 插件设置
调度器属性:
- org.quartz.scheduler.instanceName属性用来辨别特定的调度器实例,能够依照性能用处来给调度器起名
- org.quartz.scheduler.instanceId属性和前者一样,也容许任何字符串,但这个值必须是在所有调度器实例中是惟一的,尤其是在一个集群当中,作为集群的惟一key.如果想quartz生成这个值的话,能够设置为Auto
线程池属性:
- threadCount: 设置线程的数量
- threadPriority: 设置线程的优先级
- org.quartz.threadPool.class: 线程池的实现
作业存储设置:
- 形容了在调度器实例的申明周期中,job和trigger信息是怎么样存储的
插件配置:
满足特定需要用到的quartz插件的配置
监听器
对事件进行监听并且退出本人相应的业务逻辑,次要有以下三个监听器别离对Job,Trigger,Scheduler进行监听:
- JobListener
- TriggerListener
SchedulerListener
Cron表达式
字段 | 允许值 | 容许特殊字符 |
---|---|---|
秒 | 0-59 | , - * / |
分 | 0-59 | , - * / |
小时 | 0-23 | , - * / |
日期 | 1-31 | , - * ? / L W C |
月份 | 1-12 | , - * / |
星期 | 0-7或SUN-SAT,0和7是SUN | , - * / |
特殊字符 | 含意 |
---|---|
, | 枚举 |
- | 区间 |
* | 任意 |
/ | 步长 |
? | 日和星期的抵触匹配 |
L | 最初 |
w | 工作日 |
C | 与calendar分割后计算过的值 |
# | 星期: 4#2-第2个星期三 |
second(秒),minute(分),hour(时),day of month(日),month(月),day of week(周几)0 * * * * MON-FRI@Scheduled(cron="0 * * * * MON-FRI")@Scheduled(cron="1,2,3 * * * * MON-FRI")-枚举: ,@Scheduled(cron="0-15 * * * * MON-FRI")-区间: -@Scheduled(cron="0/4 * * * * MON-FRI")-步长: / 从0开始,每4秒启动一次cron="0 0/5 14,18 * * ?" 每天14点整和18点整,每隔5分钟执行一次cron="0 15 10 ? * 1-6" 每个月的周一至周六10:15分执行一次cron="0 0 2 ? * 6L" 每个月的最初一个周六2点执行一次cron="0 0 2 LW * ?" 每个月的最初一个工作日2点执行一次cron="0 0 2-4 ? * 1#1" 每个月的第一个周一2点到4点,每个整点执行一次
接口测试框架-Swagger2
Swagger介绍
- Swagger是一款RESTful接口的文档在线生成和接口测试工具
- Swagger是一个标准残缺的框架,用于生成,形容,调用和可视化RESTful格调的web服务
- 总体目标是使客户端和文件系统作为服务器以同样的速度更新
文件的办法,参数和模型严密集成到服务器端代码,容许API始终保持同步
Swagger作用
- 接口文档在线主动生成
功能测试
Swagger次要我的项目
- Swagger-tools: 提供各种与Swagger进行集成和交互的工具. 比方Swagger Inspector,Swagger Editor
- Swagger-core: 用于Java或者Scala的Swagger实现,与JAX-RS,Servlets和Play框架进行集成
- Swagger-js: 用于JavaScript的Swagger实现
- Swagger-node-express: Swagger模块,用于node.js的Express Web利用框架
- Swagger-ui: 一个无依赖的html,js和css汇合,能够为Swagger的RESTful API动静生成文档
Swagger-codegen: 一个模板驱动引擎,通过剖析用户Swagger资源申明以各种语言生成客户端代码
Swagger工具
Swagger Codegen:
- 通过Codegen能够将形容文件生成html格局和cwiki模式的接口文档,同时也能生成多种语言的服务端和客户端的代码
- 反对通过jar包 ,docker,node等形式在本地化执行生成,也能够在前面Swagger Editor中在线生成
Swagger UI:
- 提供一个可视化的UI页面展现形容文件
- 接口的调用方,测试,项目经理等都能够在该页面中对相干接口进行查阅和做一些简略的接口申请
- 该我的项目反对在线导入形容文件和本地部署UI我的项目
Swagger Editor:
- 相似于markdown编辑器用来编辑Swagger形容文件的编辑器
- 该编辑器反对实时预览形容文件的更新成果
- 提供了在线编辑器和本地部署编辑器两种形式
Swagger Inspector:
- 在线对接口进行测试
- 会比Swagger外面做接口申请会返回更多的信息,也会保留申请的理论申请参数等数据
Swagger Hub:
- 集成下面的所有工具的各个性能
能够以我的项目和版本为单位,将形容文件上传到Swagger Hub中,在Swagger Hub中能够实现下面我的项目的所有工作
Swagger注解
@Api
该注解将一个controller类标注为一个Swagger API. 在默认状况下 ,Swagger core只会扫描解析具备 @Api注解的类,而疏忽其它类别的资源,比方JAX-RS endpoints, Servlets等注解. 该注解的属性有:
- tags: API分组标签,具备雷同标签的API将会被归并在一组内显示
value: 如果tags没有定义 ,value将作为Api的tags应用
@ApiOperation
在指定接口门路上,对一个操作或者http办法进行形容. 具备雷同门路的不同操作会被归组为同一个操作对象. 紧接着是不同的http申请办法注解和门路组合形成一个惟一操作. 该注解的属性有:
- value: 对操作进行简略阐明
- notes: 对操作进行具体阐明
- httpMethod: http申请动作名,可选值有 :GET, HEAD, POST, PUT, DELETE, OPTIONS, PATCH
code: 胜利操作后的返回类型. 默认为200, 参照规范Http Status Code Definitions
@ApiParam
减少对参数的元信息阐明,紧接着应用Http申请参数注解. 次要属性有:
- required: 是否为必传参数
value: 参数简短阐明
@ApiResponse
形容一个操作的可能返回后果. 当RESTful申请产生时,这个注解可用于形容所有可能的胜利与错误码.能够应用也能够不应用这个注解去形容操作返回类型. 但胜利操作后的返回类型必须在 @ApiOperation中定义. 如果API具备不同的返回类型,那么须要别离定义返回值,并将返回类型进行关联. 然而Swagger不反对同一返回码,多种返回类型的注解. 这个注解必须被蕴含在 @ApiResponses中:
- code: http申请返回码,参照规范Http Status Code Definitions
- message: 更加易于了解的文本音讯
- response: 返回类型信息,必须应用齐全限定类名,即类的残缺门路
responseContainer: 如果返回值类型为容器类型,能够设置相应的值. 有效值 :List, Set, Map. 其它的值将会被疏忽
@ApiResponses
注解 @ApiResponse的包装类,数组构造. 即便须要应用一个 @ApiResponse注解,也须要将 @ApiResponse注解蕴含在注解 @ApiResponses内
@ApiImplicitParam
对API的繁多参数进行注解. 注解 @ApiParam须要同JAX-RS参数相绑定, 但这个 @ApiImplicitParam注解能够以对立的形式定义参数列表,这是在Servlet和非JAX-RS环境下惟一的形式参数定义形式. 留神这个注解 @ApiImplicitParam必须被蕴含在注解 @ApiImplicitParams之内,能够设置以下重要属性:
- name: 参数名称
- value: 参数简短形容
- required: 是否为必传参数
- dataType: 参数类型,能够为类名,也能够为根本类型,比方String,int,boolean等
paramType: 参数的申请类型,可选的值有path, query, body, header, from
@ApiImplicitParams
注解 @ApiImplicitParam的容器类,以数组形式存储
@ApiModel
提供对Swagger model额定信息的形容. 在标注 @ApiOperation注解的操作内,所有类将主动introspected. 利用这个注解能够做一些更具体的model构造阐明. 次要属性值有:
- value: model的别名,默认为类名
description: model的详细描述
@ApiModelProperty
对model属性的注解,次要属性值有:
- value: 属性简短形容
- example: 属性示例值
required: 是否为必须值
数据库版本控制-Liquibase,flyway
Liquibase
Liquibase基本概念
- Liquibase是一个用于跟踪,治理和利用数据库变动的数据重构和迁徙的开源工具,通过日志文件的模式记录数据库的变更,而后执行日志文件中的批改,将数据库更新或回滚到统一的状态
Liquibase的次要特点:
- 不依赖于特定的数据库,反对所有支流的数据库. 比方MySQL, PostgreSQL, Oracle, SQL Server, DB2等.这样在数据库的部署和降级环节能够帮忙利用零碎反对多数据库
- 提供数据库比拟性能,比拟后果保留在XML中,基于XML能够用Liquibase部署和降级数据库
- 反对多开发者的合作保护,以XML存储数据库变动,以author和id惟一标识一个changeSet, 反对数据库变动的合并
- 日志文件反对多种格局. 比方XML, YAML, JSON, SQL等
- 反对多种运行形式. 比方命令行, Spring集成, Maven插件, Gradle插件等
- 在数据库中保留数据库批改历史DatabaseChangeHistory, 在数据库降级时主动跳过已利用的变动
- 提供变动利用的回滚性能,可按工夫,数量或标签tag回滚曾经利用的变动
可生成html格局的数据库批改文档
日志文件changeLog
- changeLog是Liquibase用来记录数据库变更的日志文件,个别放在classpath下,而后配置到执行门路中
- changeLog反对多种格局, 次要有XML, JSON, YAML, SQL, 举荐应用XML格局
- 一个 < changeSet > 标签对应一个变更集, 由属性id, name, changelog的文件门路惟一标识组合而成
- changelog在执行时不是依照id的程序,而是依照changSet在changlog中呈现的程序
- 在执行changelog时 ,Liquibase会在数据库中新建2张表,写执行记录:databasechangelog - changelog的执行日志和databasechangeloglock - changelog锁日志
- 在执行changelog中的changeSet时,会首先查看databasechangelog表,如果曾经执行过,则会跳过,除非changeSet的runAlways属性为true, 如果没有执行过,则执行并记录changelog日志
changelog中的一个changeSet对应一个事务,在changeSet执行完后commit, 如果呈现谬误就会rollback
罕用标签及命令
changeSet标签
< changeSet > 标签的次要属性有:
runAlways: 即便执行过,依然每次都要执行
- 因为databasechangelog中还记录了changeSet的MD5校验值MD5SUM, 如果changeSet的id和name没变,而内容变动.则MD5值变动,这样即便runAlways的值为true, 也会导致执行失败报错.
- 这时应该应用runOnChange属性
- runOnChange: 第一次的时候以及当changeSet发生变化的时候执行,不受MD5校验值的束缚
runInTransaction: 是否作为一个事务执行,默认为true.
- 如果设置为false, 须要留神: 如果执行过程中出错了不会rollback, 会导致数据库处于不统一的状态
< changeSet > 有一个 < rollback > 子标签,用来定义回滚语句:
- 对于create table, rename column, add column等 ,Liquibase会主动生成对应的rollback语句
对于drop table, insert data等须要显式定义rollback语句
include标签
当changelog文件越来越多时,须要应用 < include > 标签将文件治理起来:
- file: 蕴含的changelog文件的门路,这个文件能够是Liquibase反对的任意格局
relativeToChangelogFile: 绝对于changelogFile的门路,示意file属性的文件门路是绝对于changelogFile的而不是classpath的,默认为false
<?xml version="1.0" encoding="utf-8"?><databaseChangeLogxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns="http://www.liquibase.org/xml/ns/dbchangelog"xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangeloghttp://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd"><include file="logset-20160408/0001_authorization_init.sql" relativeToChangelogFile="true"/></databaseChangeLog>
< include >标签存在循环援用和反复援用的问题,循环援用会导致有限循环,须要留神
includeAll标签
< includeAll > 标签指定的是changelog的目录,而不是文件
<includeAll path="com/example/changelogs/"/>
diff命令
diff命令用于比拟数据库之间的异同
java -jar liquibase.jar --driver=com.mysql.jdbc.Driver \ --classpath=./mysql-connector-java-5.1.29.jar \ --url=jdbc:mysql://127.0.0.1:3306/test \ --username=root --password=passwd \ diff \ --referenceUrl=jdbc:mysql://127.0.0.1:3306/authorization \ --referenceUsername=root --referencePassword=passwd
generateChangeLog
在已有我的项目上应用LiquiBase, 须要生成以后数据的changeSet, 能够应用两种形式:
- 应用数据库工具导出SQL数据,而后在changLog文件中以SQL格局记录
应用generateChangeLog命令生成changeLog文件
liquibase --driver=com.mysql.jdbc.Driver \ - classpath=./mysql-connector-java-5.1.29.jar \ - changeLogFile=liquibase/db.changeLog.xml \ --url=jdbc:mysql://127.0.0.1:3306/test \ --username=root --password=root generateChangeLog
generateChangeLog不反对存储过程,函数以及触发器
Liquibase应用示例
在application.properties中配置changeLog门路:
# Liquibase配置liquibase=true# changelog默认门路liquibase.change-log=classpath:/db/changelog/sqlData.xml
xml配置sample:
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-2.0.xsd"> <changeSet author="chova" id="sql-01"> <sqlFile path="classpath:db/changelog/sqlfile/init.sql" encoding="UTF-8" /> <sqlFile path="classpath:db/changelog/sqlfile/users.sql" encoding="UTF-8" /> </changeSet> <changeSet author="chova" id="sql-02"> <sqlFile path="classpath:db/changelog/sqlfile/users2.sql" encoding="UTF-8" /> </changeSet> </databaseChangeLog>
待执行的SQL语句 - init.sql:
CREATE TABLE usersTest( user_id varchar2(14) DEFAULT '' NOT NULL, user_name varchar2(128) DEFAULT '' NOT NULL)STORAGE(FREELISTS 20 FREELIST GROUPS 2) NOLOGGING TABLESPACE USER_DATA;insert into usersTest(user_id,user_name) values ('0','test');
- 启动我的项目.
在maven配置插件生成已有数据库的changelog文件: 须要在pom.xml中减少配置,而后配置liquibase.properties
<build> <plugins> <plugin> <groupId>org.liquibase</groupId> <artifactId>liquibase-maven-plugin</artifactId> <version>3.4.2</version> <configuration> <propertyFile>src/main/resources/liquibase.properties</propertyFile> <propertyFileWillOverride>true</propertyFileWillOverride> <!--生成文件的门路--> <outputChangeLogFile>src/main/resources/changelog_dev.xml</outputChangeLogFile> </configuration> </plugin> </plugins></build>
changeLogFile=src/main/resources/db/changelog/sqlData.xmldriver=oracle.jdbc.driver.OracleDriverurl=jdbc:oracle:thin:@chovausername=chovapassword=123456verbose=true# 生成文件的门路outputChangeLogFile=src/main/resources/changelog.xml
而后执行 [ mvn liquibase:generateChangeLog ] 命令,就是生成changelog.xml文件
liquibase:update
执行changeLog中的变更
mnv liquibase:update
liquibase:rollback
- rollbackCount: 示意rollback的changeSet的个数
- rollbackDate: 示意rollback到指定日期
rollbackTag: 示意rollback到指定的tag, 须要应用liquibase在具体的工夫点上打上tag
rollbackCount示例:
mvn liquibase:rollback -Dliquibase.rollbackCount=3
rollbackDate示例: 须要留神日期格局,必须匹配以后平台执行DateFormat.getDateInstance() 失去的格局,比方 MMM d, yyyy
mvn liquibase:rollback -Dliquibase.rollbackDate="Apr 10, 2020"
rollbackTag示例: 应用tag标识,须要先打tag, 而后rollback到tag
mvn liquibase:tag -Dliquibase.tag=tag20200410 mvn liquibase:rollback -Dliquibase.rollbackTag=tag20200410
flyway
flyway基本概念
- flyway是一款数据库版本控制管理工具,反对数据库版本主动降级,不仅反对Command Line和Java API, 同时也反对Build构建工具和SpringBoot, 也能够在分布式环境下安全可靠地降级数据库,同时也反对失败复原
flyway是一款数据库迁徙 (migration) 工具,也就是在部署利用的时候,执行数据库脚本的利用,反对SQL和Java两种类型的脚本,能够将这些脚本打包到应用程序中,在应用程序启动时,由flyway来治理这些脚本的执行,这些脚本在flyway中叫作migration
没有应用flyway时部署利用的流程:
- 开发人员将程序利用打包,按程序汇总并整顿数据库降级脚本
- DBA拿到数据库降级脚本查看,备份,执行,以实现数据库降级
- 利用部署人员拿到利用部署包,备份,替换,实现应用程序降级
引入flyway时部署利用的流程:
- 开发人员将程序打包
- 利用部署人员拿到利用部署包,备份,替换,实现应用程序降级.期间flyway主动执行降级,备份脚本
- flyway的外围: MetaData表 - 用于记录所有版本演变和状态
flyway首次启动会创立默认名为SCHMA_VERSION表,保留了版本,形容和要执行的SQL脚本
flyway次要个性
- 一般SQL: 纯SQL脚本,包含占位符替换,没有专有的XML格局
- 无限度: 能够通过Java代码实现高级数据操作
- 零依赖: 只需运行在Java 6以上版本及数据库所需的JDBC驱动
- 约定大于配置: 数据库迁徙时,主动查找系统文件和类门路中的SQL文件或Java类
- 高可靠性: 在集群环境下进行数据库的降级是安全可靠的
- 云反对: 齐全反对Microsoft SQL Azure, Google Cloud SQL & App Engine, Heroku Postgres和Amazon RDS
- 主动迁徙: 应用flyway提供的API, 能够让利用启动和数据库迁徙同时工作
- 疾速失败: 损坏的数据库或失败的迁徙能够避免应用程序启动
数据库清理: 在一个数据库中删除所有的表,视图,触发器. 而不是删除数据库自身
SQL脚本
格局 : V + 版本号 + 双下划线 + 形容 + 结束符
V1_INIT_DATABASE.sql
V是默认值,能够进行自定义配置:
flyway.sql-migration-prefix=指定前缀
flyway工作原理
flyway对数据库进行版本治理次要由Metadata表和6种命令 : Migrate, Clean, Info, Validate, Undo, Baseline, Repair实现
- 在一个空数据库上部署集成flyway利用:
- 应用程序启动时 ,flyway在这个数据库中创立一张表,用于记录migration的执行状况,表名默认为:schema_version:
- 而后 ,flyway依据表中的记录决定是否执行利用程序包中提供的migration:
- 最初,将执行后果写入schema_version中并校验执行后果:
- 下次版本迭代时,提供新的migration, 会依据schema_version的记录执行新的migration:
flyway外围
Metadata Table
- 在一个空数据库上部署集成flyway利用:
- flyway中最外围的就是用于记录所有版本演变和状态的Metadata表
- 在flyway首次启动时会创立默认表名为SCHEMA_VERSION的元数据表,表构造如下:
列名 | 类型 | 是否为null | 键值 | 默认值 |
---|---|---|---|---|
version_rank | int(11) | 否 | MUL | NULL |
installed_rank | int(11) | 否 | MUL | NULL |
version | varchar(50) | 否 | PRI | NULL |
description | varchar(200) | 否 | NULL | |
type | varchar(20) | 否 | NULL | |
script | varchar(1000) | 否 | NULL | |
checksum | int(11) | 是 | NULL | |
installed_by | varchar(100) | 否 | NULL | |
installed_on | timestamp | 否 | CURRENT_TIMESTAMP | |
execution_time | int(11) | 否 | NULL | |
success | tinyint(1) | 否 | MUL | NULL |
Migration
flyway将每一个数据库脚本称之为migration,flyway次要反对两种类型的migrations:
Versioned migrations:
- 最罕用的migration,用于版本升级
- 每一个版本都有一个惟一的标识并且只能被利用一次,并且不能再批改曾经加载过的Migrations,因为Metadata表会记录Checksum值
- version标识版本号由一个或多个数字形成,数字之间的分隔符能够采纳点或下划线,在运行时下划线其实也是被替换成点了,每一部分的前导数字0都会被主动疏忽
Repeatable migrations:
- 指的是可反复加载的Migrations,每一次的更新会影响Checksum值,而后都会被从新加载,并不用于版本升级.对于治理不稳固的数据库对象更新时十分有用
- Repeatable的Migrations总是在Versioned的Migrations之后按程序执行,开发者须要保护脚本并且确保能够反复执行.通常会在sql语句中应用CREATE OR REPLACE来确保可反复执行
Migration命名标准:
- prefix: 前缀标识.能够配置,默认状况下: V - Versioned, R - Repeatable
- version: 标识版本号. 由一个或多个数字形成,数字之间的分隔符能够应用点或者下划线
- separator: 用于宰割标识版本号和形容信息. 可配置,默认状况下是两个下划线
- description: 形容信息. 文字之间能够用下划线或空格宰割
suffix: 后续标识. 可配置,默认为 .sql
- 确保版本号惟一 ,flyway依照版本号程序执行 . repeatable没有版本号,因为repeatable migration会在内容扭转时反复执行
- 默认状况下 ,flyway会将单个migration放在一个事务里执行,也能够通过配置将所有migration放在同一个事务里执行
每个Migration反对两种编写形式:
- Java API
SQL脚本
Java API: 通过实现org.flywaydb.core.api.migration.jdbc.JdbcMigration接口来创立一个Migration, 也就是通过JDBC来执行SQL, 对于类是CLOB或者BLOB这种不适宜在SQL中实现的脚本比拟不便
public class V1_2_Another_user implements JdbcMigration { public void migrate(Connection connection) throws Exception { PreparedStatement statement = connection.prepareStatement("INSERT INTO test_user (name) VALUES ("Oxford")"); try { statement.execute(); } finally { statement.close(); } }}
SQL脚本: 简略的SQL脚本文件
// 单行命令CREATE TABLE user (name VARCHAR(25) NOT NULL, PRIMARY KEY(name)); // 多行命令 -- Placeholder INSERT INTO ${tableName} (name) VALUES ("oxford");
Callbacks
- flyway在执行migration时提供了一系列的hook, 能够在执行过程中进行额定的操作:
Name | Execution |
---|---|
beforeMigrate | Before Migrate runs |
beforeEachMigrate | Before every single migration during Migrate |
afterEachMigrate | After every single successful migration during Migrate |
afterEachMigrateError | After every single failed migration during Migrate |
afterMigrate | After successful Migrate runs |
afterMigrateError | After failed Migrate runs |
beforeClean | Before clean runs |
afterClean | After successful Clean runs |
afterCleanError | After failed Clean runs |
beforeInfo | Before Info runs |
afterInfo | After successful Info runs |
afterInfoError | After failed Info runs |
beforeValidate | Before Validate runs |
afterValidate | After successful Validate runs |
afterValidateError | After failed Validate runs |
beforeBaseline | Before Baseline runs |
afterBaseline | After successful Baseline runs |
afterBaselineError | After failed Baseline runs |
beforeRepair | BeforeRepair |
afterRepair | After successful Repair runs |
afterRepairError | After failed Repair runs |
只有将migration的名称以hook结尾,这些hook就能够执行SQL和Java类型的migrations:
SQL类型的hook:
- beforeMigrate.sql
- beforeEachMigrate.sql
- beforeRepair_vacuum.sql
Java类型的hook须要实现接口 : org.flyway.core.api.callback.CallBack
flyway中6种命令
Migrate:
- 将数据库迁徙到最新版本,是flyway工作流的外围性能.
- flyway在Migrate时会查看元数据Metadata表.如果不存在会创立Metadata表,Metadata表次要用于记录版本变更历史以及Checksum之类
- 在Migrate时会扫描指定文件系统或classpath下的数据库的版本脚本Migrations, 并且会逐个比对Metadata表中曾经存在的版本记录,如果未利用的Migrations,flyway会获取这些Migrations并按秩序Apply到数据库中,否则不会做任何事件
- 通常会在应用程序启动时默认执行Migrate操作,从而防止程序和数据库的不统一
Clean:
- 来革除掉对应数据库的Schema的所有对象 .flyway不是删除整个数据库,而是革除所有表构造,视图,存储过程,函数以及所有相干的数据
- 通常在开发和测试阶段应用,可能疾速无效地更新和从新生成数据库表构造.然而不应该在production的数据库应用
Info:
- 打印所有Migrations的具体和状态信息,是通过Metadata表和Migrations实现的
- 可能疾速定位以后数据库版本,以及查看执行胜利和失败的Migrations
Validate:
- 验证曾经Apply的Migrations是否有变更 ,flyway是默认开启验证的
- 操作原理是比照Metadata表与本地Migration的Checksum值,如果雷同则验证通过,否则验证失败,从而能够避免对曾经Apply到数据库的本地Migrations的无心批改
Baseline:
- 针对曾经存在Schema构造的数据库的一种解决方案
- 实现在非空数据库中新建Metadata表,并将Migrations利用到该数据库
- 能够利用到特定的版本,这样在已有表构造的数据库中也能够实现增加Metadata表,从而利用flyway进行新的Migrations的治理
Repair:
- 修复Metadata表,这个操作在Metadata表体现谬误时很有用
通常有两种用处:
- 移除失败的Migration记录,这个问题针对不反对DDL事务的数据库
从新调整曾经利用的Migrations的Checksums的值. 比方,某个Migration曾经被利用,但本地进行了批改,又冀望从新利用并调整Checksum值. 不倡议对数据库进行本地批改
flyway的应用
正确创立Migrations
Migrations: flyway在更新数据库时应用的版本脚本
- 一个基于sql的Migration命名为V1_ _init_tables.sql, 内容即为创立所有表的sql语句
- flyway也反对基于Java的Migration
- flyway加载Migrations的默认Locations为classpath:db/migration, 也能够指定filesystem:/project/folder. Migrations的加载是在运行时主动递归执行的
除了指定的Locations外,flyway须要听从命名格局对Migrations进行扫描,次要分为两类:
Versioned migrations:
- Versioned类型是罕用的Migration类型
- 用于版本升级,每一个版本都有一个惟一的标识并且只能被利用一次. 并且不能再批改曾经加载过的Migrations, 因为Metadata表会记录Checksum值
- 其中的version标识版本号,由一个或者多个数字形成,数字之间的分隔符能够采纳点或者下划线,在运行时下划线也是被替换成点了. 每一部分的前导零都会被省略
Repeatable migrations:
- Repeatable是指可反复加载的Migrations, 其中每一次更新都会更新Checksum值,而后都会被从新加载,并不用于版本升级. 对于治理不稳固的数据库对象的更新时十分有用
Repeatable的Migrations总是在Versioned之后按程序执行,开发者须要保护脚本并确保能够反复执行,通常会在sql语句中应用CREATE OR REPLACE来保障可反复执行
flyway数据库
flyway反对多种数据库:
- Oracle
- SQL Server
- SQL Azure
- DB2
- DB2 z/OS
- MySQL
- Amazon RDS
- Maria DB
- Google Cloud SQL
- PostgreSQL
- Heroku
- Redshift
- Vertica
- H2
- Hsql
- Derby
- SQLite
- SAP HANA
- solidDB
- Sybase ASE and Phoenix
目前支流应用的数据库有MySQL,H2,Hsql和PostgreSQL. 对应的flyway.url配置如下:
# MySQLflyway.url=jdbc:mysql://localhost:3306/db?serverTimezone=UTC&useSSL=true# H2flyway.url=jdbc:h2:./.tmp/db# Hsqlflyway.url=jdbc:hsqldb:hsql//localhost:1476/db# PostgreSQLflyway.url=jdbc:postgresql://localhost:5432/postgres?currentSchema=schema
flyway命令行
- flyway命令行工具反对间接在命令行中运行Migrate,Clean,Info,Validate,Baseline和Repair这6种命令
flyway会顺次搜寻以下配置文件:
- /conf/flyway.conf
- /flyway.conf
前面的配置会笼罩后面的配置
SpringBoot集成flyway
引入flyway依赖:
<dependency> <groupId>org.flywaydb</groupId> <artifactId>flyway-core</artifactId> <version>5.0.3</version></dependency><plugin> <groupId>org.flywaydb</groupId> <artifactId>flyway-maven-plugin</artifactId> <version>5.0.3</version></plugin>
创立的springboot的maven我的项目,配置数据源信息:
server.port=8080spring.datasource.url=jdbc:mysql://127.0.0.1:3306/dbspring.datasource.username=rootspring.datasource.password=123456spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
在classpath目录下新建/db/migration文件夹,并创立SQL脚本:
use db;CREATE TABLE person ( id int(1) NOT NULL AUTO_INCREMENT, firstname varchar(100) NOT NULL, lastname varchar(100) NOT NULL, dateofbirth DATE DEFAULT NULL, placeofbirth varchar(100) NOT NULL, PRIMARY KEY (id)) ENGINE=InnoDB DEFAULT CHARSET=utf8insert into person (firstname,lastname,dateofbirth,placeofbirth) values ('oxford','Eng',STR_TO_DATE('02/10/1997', '%m/%d/%Y'),'China');insert into person (firstname,lastname,dateofbirth,placeofbirth) values ('oxfordd','Engg',STR_TO_DATE('02/10/1995', '%m/%d/%Y'),'China');
启动springboot我的项目:
- 在我的项目启动时 ,flyway加载了SQL脚本并执行
查看数据库:
- 默认状况下,生成flyway-schema-history表
如果须要指定schema表的命名,能够配置属性 : flyway.tableflyway
flyway配置
属性名 默认值 形容 baseline-description / 对执行迁徙时基准版本的形容 baseline-on-migrate false 当迁徙发现指标schema非空,而且带有没有元数据的表时,是否主动执行基准迁徙 baseline-version 1 开始执行基准迁徙时对现有的schema的版本设置标签 check-location false 查看迁徙脚本的地位是否存在 clean-on-validation-error false 校验谬误时是否主动调用clean操作清空数据 enabled true 是否开启flyway encoding UTF-8 设置迁徙时的编码 ignore-failed-future-migration false 当读取元数据时,是否疏忽谬误的迁徙 init-sqls / 初始化连贯实现时须要执行的SQL locations db/migration 迁徙脚本的地位 out-of-order false 是否容许无序迁徙 password / 指标数据库明码 placeholder-prefix ${ 设置每个placeholder的前缀 placeholder-suffix } 设置每个placeholder的后缀 placeholders.[placeholder name] / 设置placeholder的value placeholder-replacement true placeholders是否要被替换 schemas 默认的schema 设置flyway须要迁徙的schema,大小写敏感 sql-migration-prefix V 迁徙文件的前缀 sql-migration-separator 迁徙脚本的文件名分隔符 sql-migration-suffix .sql 迁徙脚本的后缀 tableflyway schema_version 应用的元数据表名 target latest version 迁徙时应用的指标版本 url 配置的主数据源 迁徙时应用的JDBC URL user / 迁徙数据库的用户名 validate-on-migrate true 迁徙时是否校验 部署-Docker
Docker基本概念
Docker
- 是用于开发利用,交付利用,运行利用的开源软件的一个开放平台
- 容许用户将基础设施中的利用独自宰割进去,造成更细小的容器,从而提交交付软件的速度
Docker容器:
相似虚拟机,不同点是:
- Docker容器是将操作系统层虚拟化
- 虚拟机则是虚拟化硬件
- Docker容器更具备便携性,可能高效地利用服务器
- 容器更多的是用于示意软件的一个标准化单元,因为容器的标准化,因而能够忽视基础设施的差别,部署到任何一个中央
- Docker也为容器提供更强的业界隔离兼容
Docker利用Linux内核中的资源拆散机制cgroups以及Linux内核的namespace来创立独立的容器containers
- 能够在Linux实体下运作,防止疏导一个虚拟机造成的额外负担
Linux内核对namespace的反对能够齐全隔离工作环境下的应用程序,包含:
- 线程树
- 网络
- 用户ID
- 挂载文件系统
Linux内核的cgroups提供资源隔离,包含:
- CPU
- 存储器
- block I/O
网络
Docker基础架构
Docker引擎
Docker引擎: Docker Engine
- 是一个服务端 - 客户端构造的利用
次要组成部分:
Docker守护过程: Docker daemons,也叫dockerd.
- 是一个长久化过程,用户治理容器
- Docker守护过程会监听Docker引擎API的申请
Docker引擎API: Docker Engine API
- 用于与Docker守护过程交互应用的API
- 是一个RESTful API,不仅能够被Docker客户端调用,也能够被wget和curl等命令调用
Docker客户端: docker
- 是大部分用户与Docker交互的次要形式
- 用户通过客户端将命令发送给守护过程
命令遵循Docker Engine API
Docker注册核心
- Docker注册核心: Docker registry,用于存储Docker镜像
Docker Hub: Docker的公共注册核心,默认状况下,Docker在这里寻找镜像.也能够自行构建公有的注册核心
Docker对象
Docker对象指的是 :Images,Containers,Networks, Volumes,Plugins等等
镜像: Images
- 一个只读模板,用于批示创立容器
- 镜像是分层构建的,定义这些档次的文件叫作Dockerfile
容器: Containers
- 镜像可运行的实例
- 容器能够通过API或者CLI(命令行)进行操作
服务: Services
- 容许用户逾越不同的Docker守护过程的状况下减少容器
并将这些容器分为管理者(managers)和工作者(workers),来为swarm独特工作
Docker扩大架构
Docker Compose
- Docker Compose是用来定义和运行多个容器Docker应用程序的工具
- 通过Docker Compose, 能够应用YAML文件来配置应用程序所须要的所有服务,而后通过一个命令,就能够创立并启动所有服务
Docker Compose对应的命令为 : docker-compose
Swarm Mode
- 从Docker 1.12当前 ,swarm mode集成到Docker引擎中,能够应用Docker引擎API和CLI命令间接应用
Swarm Mode内置 k-v 存储性能,特点如下:
- 具备容错能力的去中心化设计
- 内置服务发现
- 负载平衡
- 路由网格
- 动静伸缩
- 滚动更新
- 平安传输
- Swarm Mode的相干个性使得Docker本地的Swarm集群具备与Mesos.Kubernetes竞争的实力
cluster: 集群
- Docker将集群定义为 - 一群独特作业并提供高可用性的机器
swarm: 群
一个集群的Docker引擎以swarm mode模式运行
- swarm mode是指Docker引擎内嵌的集群治理和编排性能
- 当初始化一个cluster中的swarm或者将节点退出一个swarm时 ,Docker引擎就会以swarm mode的模式运行
Swarm Mode原理:
swarm中的Docker机器分为两类:
- managers: 管理者. 用于解决集群关系和委派
workers: 工作者. 用于执行swarm服务
- 当创立swarm服务时,能够减少各种额定的状态: 数量,网络,端口,存储资源等等
Docker会去维持用户须要的状态:
- 比方,一个工作节点宕机后,那么Docker就会把这个节点的工作委派给另外一个节点
- 这里的工作task是指: 被swarm管理者治理的一个运行中的容器
swarm绝对于独自容器的长处:
- 批改swarm服务的配置后无需重启
- Docker以swarm mode模式运行时,能够抉择间接启动独自的容器
- 在swarm mode下,能够通过docker stack deploy应用Compose文件部署利用栈
swarm服务分为两种:
- replicated services: 能够指定节点工作的总数量
- global services: 每个节点都会运行一个指定工作
- swarm管理员能够应用ingress负载平衡使服务能够被内部接触
swarm管理员会主动地给服务调配PublishedPort, 或者手动配置.
- 内部组件,比方云负载均衡器能通过集群中任何节点上的PublishedPort去染指服务,无论服务是否启动
- Swarm Mode有外部DNS组件,会为每个服务调配一个DNS条目 . swarm管理员应用internal load balancing去散发申请时,就是依附的这个DNS组件
Swarm Mode性能是由swarmkit提供的,实现了Docker的编排层,使得swarm能够间接被Docker应用
文件格式
Docker有两种文件格式:
- Dockerfile: 定义了单个容器的内容和启动时候的行为
Compose文件: 定义了一个多容器利用
Dockerfile
Docker能够按照Dockerfile的内容,自动化地构建镜像
Dockerfile蕴含着用户想要如何构建镜像的所有命令的文本
FROM ubuntu:18.04COPY . /appRUN make /appCMD python /app/app.py
RUN:
- RUN会在以后镜像的顶层上增加新的一层layer,并在该层上执行命令,执行后果将会被提交
- 提交后的后果将会利用于Dockerfile的下一步
ENTRYPOINT:
- 入口点
- ENTRYPOINT容许配置容器,使之成为可执行程序. 即ENTRYPOINT容许为容器减少一个入口点
- ENTRYPOINT与CMD相似,都是在容器启动时执行,然而ENTRYPOINT的操作稳固并且不可被笼罩
- 通过在命令行中指定 - -entrypoint命令的形式,能够在运行时将Dockerfile文件中的ENTRYPOINT笼罩
CMD:
- command的缩写
- CMD用于为曾经创立的镜像提供默认的操作
- 如果不想应用CMD提供的默认操作,能够应用docker run IMAGE [:TAG|@DIGEST] [COMMAND] 进行替换
当Dockerfile领有入口点的状况下,CMD用于为入口点赋予参数
Compose文件
Compose文件是一个YAML文件,定义了服务, 网络 和卷:
- service: 服务. 定义各容器的配置,定义内容将以命令行参数的形式传给docker run命令
- network: 网络. 定义各容器的配置,定义内容将以命令行参数的形式传给docker network create命令
- volume: 卷. 定义各容器的配置,定义内容将以命令行参数的形式传给docker volume create命令
- docker run命令中有一些选项,和Dockerfile文件中的指令成果是一样的: CMD, EXPOSE, VOLUME, ENV. 如果Dockerfile文件中曾经应用了这些命令,那么这些指令就被视为默认参数,所以无需在Compose文件中再指定一次
Compose文件中能够应用Shell变量:
db: image: "postgres:${POSTGRES_VERSION}"
Compse文件可通过本身的ARGS变量,将参数传递给Dockerfile中的ARGS指令
网络
bridge
- Docker中的网桥应用的软件模式的网桥
- 应用雷同的网桥的容器连贯进入该网络,非该网络的容器无奈进入
- Docker网桥驱动会主动地在Docker主机上安装规定,这些规定使得不同桥接网络之间不能间接通信
桥接常常用于:
- 在独自容器上运行利用时,能够通过网桥进行通信
- 网桥网络实用于容器运行在雷同的Docker守护过程的主机上
- 不同Docker守护过程主机上的容器之间的通信须要依附操作系统档次的路由,或者能够应用overlay网络进行代替
bridge: 网桥驱动
- 是Docker默认的网络驱动,接口名为docker0
- 当没有为容器指定一个网络时,Docker将应用这个驱动
- 能够通过daemon.json文件批改相干配置
自定义网桥能够通过 brtcl 命令进行配置
host
host: 主机模式
- 用于独自容器,该网络下容器只能和Docker主机进行间接连贯
- 这种host主机模式只实用于Docker 17.06当前版本的swarm服务
host网络和VirtualBox的仅主机网络Host-only Networking相似
overlay
overlay: 笼罩模式
- 网络驱动将会创立分布式网络,该网络能够笼罩若干个Docker守护过程主机
- overlay是基于主机特定网络host-specific networks, 当加密性能开启时,容许swarm服务和容器进行平安通信
- 在笼罩网络overlay下,Docker可能清晰地把握数据包路由以及发送接管容器
overlay有两种网络类型网络:
- ingress: 是可掌控swarm服务的网络流量, ingress网络是overlay的默认网络
- docker_gwbridge: 网桥网络, docker_gwbridge网络会将独自的Docker守护过程连贯至swarm里的另外一个守护过程
在overlay网络下:
- 独自的容器和swarm服务的行为和配置概念是不一样的
overlay策略不须要容器具备操作系统级别的路由,因为Docker负责路由
macvlan
macvlan:
- 容许赋予容器MAC地址
在该网络里,容器会被认为是物理设施
none
- 在该策略下,容器不应用任何网络
none经常用于连贯自定义网络驱动的状况下
其它网络策略模式
要想使用其它网络策略模式须要依赖其它第三方插件
数据管理
在默认状况下,Docker所有文件将会存储在容器里的可写的容器层container layer:
- 数据与容器共为一体: 随着容器的隐没,数据也会隐没. 很难与其它容器程序进行数据共享
- 容器的写入层与宿主机器紧紧耦合: 很难挪动数据到其它容器
- 容器的写入层是通过存储驱动storage driver治理文件系统: 存储驱动会应用Linux内核的链合文件系统union filesystem进行挂载,相比拟于间接操作宿主机器文件系统的数据卷,这个额定的形象层会升高性能
容器有两种永久化存储形式:
- volumes: 卷
- bind mounts: 绑定挂载
- Linux中能够应用tmpfs进行挂载, windows用户能够应用命名管道named pipe.
在容器中,不论应用哪种永久化存储,表现形式都是一样的
卷
卷: volumes.
- 是宿主机器文件系统的一部分
- 由Docker进行治理. 在Linux中,卷存储于 /var/lib/docker/volumes/
- 非Docker程序不应该去批改这些文件
- Docker举荐应用卷进行长久化数据
- 卷能够反对卷驱动volume drivers: 该驱动容许用户将数据存储到近程主机或云服务商cloud provider或其它
没有名字的卷叫作匿名卷anonymous volume. 有名字的卷叫作命名卷named volume. 匿名卷没有明确的名字,当被初始化时,会被赋予一个随机名字
绑定挂载
绑定挂载: bind mounts
- 通过将宿主机器的门路挂载到容器里的这种形式,从而实现数据继续化,因而绑定挂载可将数据存储在宿主机器的文件系统中的任何中央
- 非Docker程序能够批改这些文件
- 绑定挂载在Docker早起就曾经存在,与卷存储相比拟,绑定挂载非常简单明了
- 在开发Docker利用时,应应用命名卷named volume代替绑定挂载,因为用户不能对绑定挂载进行Docker CLI命令操作
绑定挂载的应用场景:
同步配置文件
- 将宿主机的DNS配置文件(/etc/resolv.conf)同步到容器中
在开发程序过程中,将源代码或者Artifact同步至容器中. 这种用法与Vagrant相似
tmpfs挂载
tmpfs挂载: tmpfs mounts
- 仅仅存储于内存中,不操作宿主机器的文件系统.即不长久化于磁盘
用于存储一些非长久化状态,敏感数据
swarm服务通过tmpfs将secrets数据(明码,密钥,证书等)存储到swarm服务
命名管道
命名管道: named pipes
通过pipe挂载的模式,使Docker主机和容器之间相互通信
在容器内运行第三方工具,并应用命名管道连贯到Docker Engine API
笼罩问题
- 当挂载空的卷至一个目录中,目录中你的内容会被复制于卷中,不会笼罩
如果挂载非空的卷或绑定挂载至一个目录中,那么该目录的内容将会被暗藏obscured,当卸载后内容将会复原显示
日志
在Linux和Unix中,常见的I/O流分为三种:
- STDIN: 输出
- STDOUT: 失常输入
- STDERR: 谬误输入
默认配置下,Docker的日志所记录的是命令行的输入后果:
- STDOUT : /dev/stdout
- STDERR : /dev/stderr
也能够在宿主主机上查看容器的日志,应用命令能够查看容器日志的地位:
docker inspect --format='{{.LogPath}}' $INSTANCE_ID
继续集成-jenkins
jenkins基本概念
- jenkins是一个开源的,提供敌对操作页面的继续集成(CI)工具
- jenkins次要用于继续,主动的构建或者测试软件我的项目,监控内部工作的运行
- jenkins应用Java语言编写,能够在Tomcat等风行的servlet容器中运行,也能够独立运行
- 通常与版本管理工具SCM, 构建工具联合应用
- 罕用的版本控制工具有SVN,GIT
常见的构建工具有Maven,Ant,Gradle
CI/CD
CI: Continuous integration, 继续集成
- 继续集成强调开发人员提交新代码之后,like进行构建,单元测试
- 依据测试后果,能够确定新代码和原有代码是否正确地合并在一起
CD: Continuous Delivery, 继续交付
在继续集成的根底上,将集成后的代码部署到更贴近实在运行环境中,即类生产环境中
- 比方在实现单元测试后,能够将代码部署到连贯数据库的Staging环境中进行更多的测试
如果代码没有问题,能够持续手动部署到生产环境
jenkins应用配置
- 登录jenkins,点击新建,创立一个新的构建工作:
跳转到新建界面:
- 工作名称能够自行设定,但须要全局惟一
- 输出名称后,抉择构建一个自在格调的软件我的项目
- 点击下方的创立按钮
- 这样就创立了一个构建工作,而后会跳转到该工作的配置页面
在构建工作页面,能够看到几个选项:
- General
- 源码治理
- 构建触发器
- 构建环境
- 构建
构建后操作
General
General用于构建工作的一些根本配置: 名称,形容等
- 项目名称: 方才创立构建工作设置的名称,能够在这里进行批改
- 形容: 对构建工作的形容
抛弃旧的构建: 服务资源是无限的,如果保留太多的历史构建,会导致jenkins速度变慢,并且服务器硬盘资源也会被占满
- 放弃构建天数: 能够自定义,依据理论状况确定一个正当的值
放弃构建的最大个数: 能够自定义,依据理论状况确定一个正当的值
源码治理
- 源码治理用于配置代码的寄存地位
- Git: 反对支流的github和gitlab代码仓库
- Repository URL: 仓库地址
- Credentials: 凭证. 能够应用HTTP形式的用户名和明码,也能够是RSA文件.然而要通过前面的[ADD]按钮增加凭证
- Branches to build: 构建分支. */master示意master分支,也能够设置为另外的分支
- 源码浏览器: 所应用的代码仓库管理工具,如github,gitlab
- URL: 填入上方的仓库地址即可
- Version: gitlab服务器版本
Subversion: 就是SVN
构建触发器
- 构建工作的触发器
- 触发近程构建(例如,应用脚本): 这个选项会提供一个接口,能够用来在代码层面触发构建
- Build after other project are built: 在其它我的项目构建后构建
Build periodically: 周期性地构建.每隔一段时间进行构建
- 日程表: 相似linux cronttab书写格局. 下图示意每隔30分钟进行一次构建
Build when a change is pushed to Gitlab: 罕用的构建触发器,当有代码push到gitlab代码仓库时就进行构建
- webhooks: 触发构建的地址,须要将这个地址配置到gitlab中
Poll SCM: 这个性能须要与下面的这个性能配合应用. 当代码仓库产生变动时,jekins并不知道. 这时,须要配置这个选项,周期性地查看代码仓库是否产生变动
构建环境
- 日程表: 相似linux cronttab书写格局. 下图示意每隔30分钟进行一次构建
- 构建环境: 构建之前的筹备工作. 比方指定构建工具,这里应用Ant
With Ant: 抉择这个选项,并指定Ant版本和JDK版本. 须要当时在jenkins服务器上安装这两个版本的工具,并且在jenkins全局工具中配置好
构建
- 点击下方的减少构建步骤:
这里有多种减少构建步骤的形式,在这里介绍Execute shell和Invoke Ant - Execute shell: 执行shell命令. 该工具是针对linux环境的,windows中对应的工具是 [Execute Windows batch command]. 在构建之前,须要执行一些命令: 比方压缩包的解压等等
Invoke Ant: Ant是一个Java我的项目构建工具,也能够用来构建PHP
- Ant Version: 抉择Ant版本. 这个Ant版本是装置在jenkins服务器上的版本,并且须要在jenkins[零碎工具]中设置好
- Targets: 须要执行的操作. 一行一个操作工作: 比方上图的build是构建,tar是打包
- Build File: Ant构建的配置文件. 如果不指定,默认是在我的项目门路下的workspace目录中的build.xml
- properties: 设定一些变量. 这些变量能够在build.l中被援用
Send files or execute commands over SSH: 发送文件到近程主机或者执行命令脚本
- Name: SSH Server的名称. SSH Server能够在jenkins[零碎设置]中配置
- Source files: 须要发送给近程主机的源文件
- Remove prefix: 移除后面的门路. 如果不设置这个参数,默认状况下近程主机会主动创立构建源source file蕴含的门路
- Romote directory: 近程主机目录
Exec command: 在近程主机上执行的命令或者脚本
构建后操作
- 构建后操作: 对构建实现的我的项目实现一些后续操作:比方生成相应的代码测试报告
- Publish Clover PHP Coverage Report: 公布代码覆盖率的xml格局的报告. 门路在build.xml中定义
- Publish HTML reports: 公布代码覆盖率的HTML报告
- Report Crap: 公布Crap报告
E-mail Notification: 邮件告诉. 构建实现后发送邮件到指定的邮箱
配置实现后,点击[保留]其它配置
SSH Server配置
- 登录jenkins
- 系统管理
- 零碎设置
SSH Servers: jenkins服务器公钥文件配置好之后新增SSH Server只须要配置这一个选项即可
- name: 服务名称.自定义,须要全局惟一
- HostName: 主机名. 间接应用IP地址即可
- Username: 新增Server的用户名,这里配置的是root
Remote Directory: 近程目录. jenkins服务器发送文件给新增的server时默认在这个目录
Ant配置文件 - build.xml
- Ant构建配置文件build.xml :
- project name: 项目名称. 和jenkins所构建的项目名称对应
target name="build": 构建的名称. 和jekins构建步骤中的targets对应.
- depends: 指明构建须要进行的一些操作
- property: 用来设置变量
fileset: 指明一个文件夹
- include: 指明须要蕴含的文件
- exclude: 指明不须要蕴含的文件
- tar: 打包这个文件夹匹配到的文件
target: 理论的操作步骤:
- make_runtime: 创立一些目录
- phpcs: 利用PHP_CodeSniffer工具对PHP代码标准与质量检查工具
target name="tar": 打包文件
- 因为build中没有蕴含这个target.所以默认状况下,执行build是不会打包文件的
- 所以在jenkins配置界面中Ant构建步骤中的[targets],才会有[build]和[tar]这两个targets
如果build.xml中build这个target depends中曾经蕴含tar, 就不须要在jenkins中减少tar了
配置Gitlab webhooks
- 在gitlab的project页面关上settings
- 关上web hooks
- 点击[ADD WEB HOOK] 来增加webhook
- 将之前的jenkins配置中的url增加到这里
增加实现后,点击 [TEST HOOK] 进行测试,如果显示SUCCESS则示意增加胜利
配置phpunit.xml
- phpunit.xml: 是phpunit工具用来单元测试所须要的配置文件
- 这个文件的名称是能够自定义的,只有在build.xml中配置好名字即可
- 默认状况下,如果应用phpunit.xml, 就不须要在build.xml中配置文件名
fileset dir: 指定单元测试文件所在门路.
- include: 指定蕴含哪些文件,反对通配符
exclude: 指定不蕴含的文件
构建jenkins project
- 第一次配置好jenkins project后,会触发一次构建
- 尔后,每当有commit提交到master分支(依据配置中的分支触发), 就会触发一次构建
也能够在project页面手动触发构建: 点击 [立刻构建] 即可手动触发构建
构建后果阐明
构建状态
- Successful: 蓝色. 构建实现,并且是稳固的
- Unstable: 黄色. 构建实现,然而是不稳固的
- Failed: 红色. 构建失败
Disable: 灰色. 构建已禁用
构建稳定性
构建稳定性用天气示意: 天气越好示意构建越稳固
- 晴
- 晴转多云
- 多云
- 小雨
雷阵雨
构建历史界面
console output: 输入构建的日志信息
jenkins权限治理
- jenkins中默认的权限管理体系不反对用户组和角色配置,因而须要装置第三方插件来反对角色的配置
应用Role Strategy Plugin进行权限治理:
- 我的项目视图:
- 装置Role Strategy Plugin插件
- 装置Role Stratey Plugin后进入零碎设置页面,依照如下配置后,点击 [保留] :
- 点击 [系统管理] -> [Manage and Assign Roles] 进入角色治理页面:
抉择 [Manager Roles], 依照下图配置后点击 [保留]:
- job_read只加overall的read权限
- job_create只加job的create权限
project roles中Pattern正则表达式和脚本里的是不一样的:
- 比方过滤TEST结尾的jobs,要写成 : TEST.,而不是 TEST
进入[零碎设置] -> [Manage and Assign Roles] -> [Assign Roles] , 依照如下模板配置后,点击 [保留]
- Anonymous必须变成用户,给job_create组和job_read组权限,否则将没有OverAll的read权限
- project roles: 用于对应用户不同的权限
- 验证: 登录对应的用户权限后查看用户相干权限
- 视图通过正则表达式过滤job: 设置正则表达式为wechat.*,示意过滤所有以wechat结尾的我的项目
- 设置后的成果如图:
自动化测试-TestNG
TestNG基本概念
- 我的项目视图:
- TestNG是一个Java语言的开源测试框架,相似JUnit和NUnit,然而功能强大,更易于应用
TestNG的设计指标是为了笼罩更宽泛的测试类别范畴:
- 单元测试
- 功能测试
- 端到端测试
- 集成测试
TestNG的次要性能:
- 反对注解
- 反对参数化和数据驱动测试: 应用@DataProvider或者XML配置
- 反对同一类的多个实例: @Factory
灵便的执行模式:
- TestNG的运行,既能够通过Ant的build.xml: 有或这没有一个测试套定义. 又能够通过带有可视化成果的IDE插件
- 不须要TestSuite类,测试包,测试组以及抉择运行的测试. 都通过XML文件来定义和配置
并发测试:
- 测试能够运行在任意大的线程池中,并有多种运行策略能够抉择: 所有办法都有本人的线程,或者每一个测试类一个线程等等
- 测试代码是否线程平安
- 嵌入BeanShell能够取得更大的灵活性
- 默认应用JDK运行和相干日志性能,不须要额定减少依赖
- 应用服务器测试的依赖办法
分布式测试: 容许在从机上进行分布式测试
TestNG环境配置
- 配置好主机的Java环境,应用命令 java -version查看
- 在TestNG官网,下载TestNG对应零碎下的jar文件
- 零碎环境变量中增加指向jar文件的门路
在IDEA中装置TestNG
TestNG的根本用法
import org.junit.AfterClass;import org.junit.BeforeClass;import org.testng.annotations.Test;public class TestNGLearn1 { @BeforeClass public void beforeClass() { System.out.println("this is before class"); } @Test public void TestNgLearn() { System.out.println("this is TestNG test case"); } @AfterClass public void afterClass() { System.out.println("this is after class"); }}
TestNG的根本注解
注解 形容 @BeforeSuit 注解办法只运行一次,在此套件中所有测试之前运行 @AfterSuite 注解办法只运行一次,在此套件中所有测试之后运行 @BeforeClass 注解办法只运行一次,在以后类中所有办法调用之前运行 @AfterClass 注解办法只运行一次,在以后类中所有办法调用之后运行 @BeforeTest 只运行一次,在所有的测试方法执行之前运行 @AfterTest 只运行一次,在所有的测试方法执行之后运行 @BeforeGroups 组的列表,配置办法之前运行.
此办法是保障在运行属于任何这些组的第一个测试,该办法将被调用@AfterGroups 组的名单,配置办法之后运行.
此办法是保障运行属于任何这些组的最初一个测试后不久,,该办法将被调用@BeforeMethod 在每一个@test测试方法运行之前运行
比方:在执行完测试用例后要重置数据能力执行第二条测试用例时,能够应用这种注解形式@AfterMethod 在每一个@test测试方法运行之后运行 @DataProvider 标记一个办法,提供数据的一个测试方法
注解的办法必须返回一个Object[][],其中每个对象的[]的测试方法的参数列表能够调配
如果有@Test办法,想要应用从这个DataProvider中接管的数据,须要应用一个dataProvider名称等于这个注解的名称@Factory 作为一个工厂,返回TestNG的测试类对象中被用于标记的办法
该办法必须返回Object[]@Listeners 定义一个测试类的监听器 @Parameters 定义如何将参数传递给@Test办法 @Test 标记一个类或者办法作为测试的一部分 testng.xml
属性 形容 name 套件suite的名称,这个名称会呈现在测试报告中 junit 是否以junit模式运行 verbose 设置在控制台中的输入形式. 这个设置不影响html版本的测试报告 parallel 是否应用多线程进行测试,能够减速测试 configfailurepolicy 是否在运行失败了一次之后持续尝试或者跳过 thread-count 如果设置了parallel,能够设置线程数 annotations 如果有javadoc就在javadoc中寻找,没有就应用jdk5的正文 time-out 在终止method(parallel="methods")或者test(parallel="tests")之前设置以毫秒为单位的等待时间 skipfailedinvocationcounts 是否跳过失败的调用 data-provider-thread-count 提供一个线程池的范畴来应用parallel data object-factory 用来实例化测试对象的类,继承自IObjectFactory类 <?xml version="1.0" encoding="UTF-8"?><!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" ><suite name="Suite" parallel="tests" thread-count="5"> <test name="Test" preserve-order="true" verbose="2"> <parameter name="userName" value="15952031403"></parameter> <parameter name="originPwd" value="c12345"></parameter> <classes> <class name="com.oxford.testng.RegisterTest"> </class> </classes> </test> <test name="Test1" preserve-order="true"> <classes> <class name="com.oxford.testng.Test2"> </class> </classes> </test> <test name="Test2" preserve-order="true"> <classes> <class name="com.oxford.testng.Test3"> </class> </classes> </test></suite>
在suite中,同时应用parallel和thread-count:
- parallel: 指定并行测试范畴tests,methods,classes
- thread-count: 并行线程数
- preserve-order: 当设置为true时,节点下的办法按程序执行
- verbose: 示意记录日志的级别,在0 - 10之间取值
< parameter name="userName", value="15952031403" > : 给测试代码传递键值对参数,在测试类中通过注解 @Parameter({"userName"}) 获取
参数化测试
- 当测试逻辑一样,只是参数不一样时,能够采纳数据驱动测试机制,防止反复代码
- TestNG通过 @DataProvider实现数据驱动
应用@DataProvider做数据驱动:
- 数据源文件能够是EXCEL,XML,甚至能够是TXT文本
比方读取xml文件:
- 通过@DataProvider读取XML文件中的数据
- 而后测试方法只有标示获取数据起源的DataProvider
对应的DataProvider就会将读取的数据传递给该test办法
构建XML数据文件
<?xml version="1.0" encoding="UTF-8"?><data><login> <username>user1</username> <password>123456</password></login><login> <username>user2</username> <password>345678</password></login></data>
读取XML文件
import java.io.File;import java.util.ArrayList;import java.util.HashMap;import java.util.Iterator;import java.util.List;import java.util.Map;import org.dom4j.Document;import org.dom4j.DocumentException;import org.dom4j.Element;import org.dom4j.io.SAXReader;public class ParseXml {/** * 利用Dom4j解析xml文件,返回list * @param xmlFileName * @return */public static List parse3Xml(String xmlFileName){File inputXml = new File(xmlFileName); List list= new ArrayList(); int count = 1; SAXReader saxReader = new SAXReader(); try { Document document = saxReader.read(inputXml); Element items = document.getRootElement(); for (Iterator i = items.elementIterator(); i.hasNext();) { Element item = (Element) i.next(); Map map = new HashMap(); Map tempMap = new HashMap(); for (Iterator j = item.elementIterator(); j.hasNext();) { Element node = (Element) j.next(); tempMap.put(node.getName(), node.getText()); } map.put(item.getName(), tempMap); list.add(map); } } catch (DocumentException e) { System.out.println(e.getMessage()); } System.out.println(list.size()); return list;} }
DataProvider类
import java.lang.reflect.Method;import java.util.ArrayList;import java.util.List;import java.util.Map;import org.testng.Assert;import org.testng.annotations.DataProvider;public class GenerateData {public static List list = new ArrayList();@DataProvider(name = "dataProvider")public static Object[][] dataProvider(Method method){ list = ParseXml.parse3Xml("absolute path of xml file"); List<Map<String, String>> result = new ArrayList<Map<String, String>>(); for (int i = 0; i < list.size(); i++) { Map m = (Map) list.get(i); if(m.containsKey(method.getName())){ Map<String, String> dm = (Map<String, String>) m.get(method.getName()); result.add(dm); } } if(result.size() > 0){ Object[][] files = new Object[result.size()][]; for(int i=0; i<result.size(); i++){ files[i] = new Object[]{result.get(i)}; } return files; }else { Assert.assertTrue(result.size()!=0,list+" is null, can not find"+method.getName() ); return null;}}}
在test办法中援用DataProvider
public class LoginTest { @Test(dataProvider="dataProvider", dataProviderClass= GenerateData.class)public void login(Map<String, String> param) throws InterruptedException{ List<WebElement> edits = findElementsByClassName(AndroidClassName.EDITTEXT); edits.get(0).sendkeys(param.get("username")); edits.get(1).sendkeys(param.get("password"));}}
xml中的父节点与test的办法名对应:
- xml中同名父节点的个数就意味着该test办法会被反复执行多少次
当DataProvider与test办法不在同一个类时,须要指明DataProvider类:
@Test(dataProvider="dataProvider", dataProviderClass= GenerateData.class)
TestNG重写监听类
TestNG会监听每个测试用例的运行后果.能够应用监听定制一些自定义的性能,比方主动截图,发送数据给服务器:
- 新建一个继承自TestListenerAdapter的类
重写实现后,在test办法前增加 @Listener(TestNGListener.class) 注解
package com.oxford.listener;import java.text.SimpleDateFormat;import java.util.ArrayList;import java.util.Date;import java.util.List;import org.testng.ITestContext;import org.testng.ITestResult;import org.testng.TestListenerAdapter;import com.google.gson.Gson;import com.google.gson.JsonObject;import com.unionpay.base.BaseTest;import com.unionpay.constants.CapabilitiesBean;import com.unionpay.constants.CaseCountBean;import com.unionpay.constants.ResultBean;import com.unionpay.util.Assertion;import com.unionpay.util.PostService;import com.unionpay.util.ReadCapabilitiesUtil;/** * 带有post申请的testng监听 * @author lichen2 */public class TestNGListenerWithPost extends TestListenerAdapter{//接管每个case后果的接口private String caseUrl;//接管整个test运行数据的接口private String countUrl;//接管test运行状态的接口private String statusUrl;private JsonObject caseResultJson = new JsonObject();private JsonObject caseCountJson = new JsonObject();private Gson gson = new Gson();private ResultBean result = new ResultBean();private CaseCountBean caseCount = new CaseCountBean();private SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");private CapabilitiesBean capabilitiesBean = ReadCapabilitiesUtil.readCapabilities("setting.json");private String testStartTime;private String testEndTime;private String runId;//testng初始化@Overridepublic void onStart(ITestContext testContext) { super.onStart(testContext); String serverUrl = capabilitiesBean.getServerurl();caseUrl = "http://"+serverUrl+"/api/testcaseResult";countUrl = "http://"+serverUrl+"/api/testcaseCount";statusUrl = "http://"+serverUrl+"/api/testStatus"; runId = capabilitiesBean.getRunid(); result.setRunId(runId); caseCount.setRunId(runId);}//case开始@Overridepublic void onTestStart(ITestResult tr) {Assertion.flag = true;Assertion.errors.clear();sendStatus("运行中");result.setStartTime(format.format(new Date()));}//case胜利执行@Overridepublic void onTestSuccess(ITestResult tr) { super.onTestSuccess(tr); sendResult(tr); takeScreenShot(tr);}//case执行失败@Overridepublic void onTestFailure(ITestResult tr) { super.onTestFailure(tr); sendResult(tr); try { takeScreenShot(tr); } catch (SecurityException e) { e.printStackTrace(); } catch (IllegalArgumentException e) { e.printStackTrace(); } this.handleAssertion(tr);}//case被跳过@Overridepublic void onTestSkipped(ITestResult tr) { super.onTestSkipped(tr); takeScreenShot(tr); sendResult(tr); this.handleAssertion(tr);}//所有case执行实现@Overridepublic void onFinish(ITestContext testContext) { super.onFinish(testContext); sendStatus("正在生成报告"); sendFinishData(testContext);}/** * 发送case测试后果 * @param tr */public void sendResult(ITestResult tr){result.setTestcaseName(tr.getName());result.setEndTime(format.format(new Date()));float tmpDuration = (float)(tr.getEndMillis() - tr.getStartMillis());result.setDuration(tmpDuration / 1000);switch (tr.getStatus()) {case 1: result.setTestResult("SUCCESS"); break;case 2: result.setTestResult("FAILURE"); break;case 3: result.setTestResult("SKIP"); break;case 4: result.setTestResult("SUCCESS_PERCENTAGE_FAILURE"); break;case 16: result.setTestResult("STARTED"); break;default: break;}caseResultJson.addProperty("result", gson.toJson(result));PostService.sendPost(caseUrl, caseResultJson.toString());}/** * 告诉test实现 * @param testContext */public void sendFinishData(ITestContext tc){testStartTime = format.format(tc.getStartDate());testEndTime = format.format(tc.getEndDate());long duration = getDurationByDate(tc.getStartDate(), tc.getEndDate());caseCount.setTestStartTime(testStartTime);caseCount.setTestEndTime(testEndTime);caseCount.setTestDuration(duration);caseCount.setTestSuccess(tc.getPassedTests().size());caseCount.setTestFail(tc.getFailedTests().size());caseCount.setTestSkip(tc.getSkippedTests().size());caseCountJson.addProperty("count", gson.toJson(caseCount));PostService.sendPost(countUrl, caseCountJson.toString());}/** * 告诉test运行状态 */public void sendStatus(String status){JsonObject jsonObject = new JsonObject();jsonObject.addProperty("runId", runId);jsonObject.addProperty("status", status);JsonObject sendJson = new JsonObject();sendJson.addProperty("status", jsonObject.toString());PostService.sendPost(statusUrl, sendJson.toString());}//计算date间的时差(s)public long getDurationByDate(Date start, Date end){long duration = end.getTime() - start.getTime();return duration / 1000;}//截图private void takeScreenShot(ITestResult tr) { BaseTest b = (BaseTest) tr.getInstance(); b.takeScreenShot(tr);}}
运行测试
package com.oxford.base;import org.testng.ITestResult;import com.unionpay.listener.TestNGListenerWithPost;@Listeners(TestNGListenerWithPost.class)public abstract class BaseTest { public AndroidDriver<WebElement> driver; public BaseTest() { driver = DriverFactory.getDriverByJson(); } /** * 截屏并保留到本地 * @param tr */ public void takeScreenShot(ITestResult tr) { String fileName = tr.getName() + ".jpg"; File dir = new File("target/snapshot"); if (!dir.exists()) { dir.mkdirs(); } String filePath = dir.getAbsolutePath() + "/" + fileName; if (driver != null) { try { File scrFile = driver.getScreenshotAs(OutputType.FILE); FileUtils.copyFile(scrFile, new File(filePath)); } catch (IOException e) { e.printStackTrace(); } } }}