微服務架構技術棧:程序員必須掌握的微服務架構框架詳細解析

本文已參與好文召集令活動,點擊查看:後端、大前端雙賽道投稿,2萬元獎池等你挑戰!php

主要技術

  • 基礎框架: 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 的基礎上只作加強不作改變,能夠簡化開發,提升效率.css

Mybatis Plus核心功能

  • 支持通用的 CRUD,代碼生成器與條件構造器
  • 通用CRUD: 定義好Mapper接口後,只須要繼承 BaseMapper接口便可得到通用的增刪改查功能,無需編寫任何接口方法與配置文件
  • 條件構造器: 經過EntityWrapper(實體包裝類),能夠用於拼接SQL語句,而且支持排序,分組查詢等複雜的 SQL
  • 代碼生成器: 支持一系列的策略配置與全局配置,比 MyBatis 的代碼生成更好用

BaseMapper接口中通用的 CRUD 方法: 在這裏插入圖片描述 簡單的數據庫操做不須要在 EmployeeMapper 接口中定義任何方法,也沒有在配置文件中編寫SQL語句,而是經過繼承BaseMapper接口得到通用的的增刪改查方法,複雜的SQL也可使用條件構造器拼接.不過複雜的業務需求仍是要編寫SQL語句的,流程和MyBatis同樣.html

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 
@Slf4j
public 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屬性java

  • sql條件拼接:

SQL條件拼接測試類ConditionTestnode

@RunWith(SpringRunner.class)
//SpringBootTest 是springboot 用於測試的註解,可指定啓動類或者測試環境等,這裏直接默認。
@SpringBootTest 
@Slf4j
public 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 在這裏插入圖片描述python

  • 自定義SQL使用條件構造器:

UserDao.java加入接口方法:mysql

/** * * @param rowBounds 分頁對象 直接傳入page便可 * @param wrapper 條件構造器 * @return */
    List<User> selectUserWrapper(RowBounds rowBounds, @Param("ew") Wrapper<User> wrapper);
複製代碼

UserMapper.xml加入對應的xml節點:linux

<!-- 條件構造器形式 -->
    <select id="selectUserWrapper" resultType="user">
        SELECT
        <include refid="Base_Column_List" />
        FROM USER
        <where>
            ${ew.sqlSegment}
        </where>
    </select>
複製代碼

自定義SQL使用條件構造器測試類:git

@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 
@Slf4j
public 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 
@Slf4j
public 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("分頁結束");
    }

}
複製代碼
  • 性能插件體現,控制檯輸出:
Time4 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"/>
複製代碼

再新增或者修改時,對應時間就會進行更新:

Time31 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> list
      2.遍歷該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序列化規則
@Configuration
public class MyRedisConfig {
 @Bean
 public 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: DefaultQuartzScheduler
org.quartz.scheduler.rmi.export: false
org.quartz.scheduler.rmi.proxy: false
org.quartz.scheduler.wrapJobExecutionInUserTransaction: false

org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount: 10
org.quartz.threadPool.threadPriority: 5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true

org.quartz.jobStore.misfireThreshold: 60000

org.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將做爲Apitags使用
@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存儲數據庫變化,以authorid惟一標識一個changeSet, 支持數據庫變化的合併
    • 日誌文件支持多種格式. 好比XML, YAML, JSON, SQL
    • 支持多種運行方式. 好比命令行, Spring集成, Maven插件, Gradle插件
    • 在數據庫中保存數據庫修改歷史DatabaseChangeHistory, 在數據庫升級時自動跳過已應用的變化
    • 提供變化應用的回滾功能,可按時間,數量或標籤tag回滾已經應用的變化
    • 可生成html格式的數據庫修改文檔

日誌文件changeLog

  • changeLogLiquibase用來記錄數據庫變動的日誌文件,通常放在classpath下,而後配置到執行路徑中
  • changeLog支持多種格式, 主要有XML, JSON, YAML, SQL, 推薦使用XML格式
  • 一個 < changeSet > 標籤對應一個變動集, 由屬性id, name, changelog的文件路徑惟一標識組合而成
  • changelog在執行時不是按照id的順序,而是按照changSetchanglog中出現的順序
  • 在執行changelog,Liquibase會在數據庫中新建2張表,寫執行記錄:databasechangelog - changelog的執行日誌databasechangeloglock - changelog鎖日誌
  • 在執行changelog中的changeSet時,會首先查看databasechangelog表,若是已經執行過,則會跳過,除非changeSetrunAlways屬性爲true, 若是沒有執行過,則執行並記錄changelog日誌
  • changelog中的一個changeSet對應一個事務,在changeSet執行完後commit, 若是出現錯誤就會rollback

經常使用標籤及命令

changeSet標籤

< changeSet > 標籤的主要屬性有:

  • runAlways: 即便執行過,仍然每次都要執行
    • 因爲databasechangelog中還記錄了changeSetMD5校驗值MD5SUM, 若是changeSetidname沒變,而內容變化.則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"?>
<databaseChangeLog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://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.xml
driver=oracle.jdbc.driver.OracleDriver
url=jdbc:oracle:thin:@chova
username=chova
password=123456
verbose=true
# 生成文件的路徑
outputChangeLogFile=src/main/resources/changelog.xml
複製代碼

而後執行 [ mvn liquibase:generateChangeLog ] 命令,就是生成changelog.xml文件

  • liquibase:update
    • 執行changeLog中的變動
    mnv liquibase:update
    複製代碼
  • liquibase:rollback
    • rollbackCount: 表示rollbackchangeSet的個數
    • 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, 而後rollbacktag
      mvn liquibase:tag -Dliquibase.tag=tag20200410
      
      mvn liquibase:rollback -Dliquibase.rollbackTag=tag20200410
      複製代碼

flyway

flyway基本概念

  • flyway是一款數據庫版本控制管理工具,支持數據庫版本自動升級,不只支持Command LineJava API, 同時也支持Build構建工具和SpringBoot, 也能夠在分佈式環境下安全可靠地升級數據庫,同時也支持失敗恢復
  • flyway是一款數據庫遷移 (migration) 工具,也就是在部署應用的時候,執行數據庫腳本的應用,支持SQLJava兩種類型的腳本,能夠將這些腳本打包到應用程序中,在應用程序啓動時,由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 PostgresAmazon 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中最核心的就是用於記錄全部版本演化和狀態的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命名規範:

在這裏插入圖片描述

  1. prefix: 前綴標識.能夠配置,默認狀況下: V - Versioned, R - Repeatable
  2. version: 標識版本號. 由一個或多個數字構成,數字之間的分隔符可使用點或者下劃線
  3. separator: 用於分割標識版本號和描述信息. 可配置,默認狀況下是兩個下劃線 _ _
  4. description: 描述信息. 文字之間能夠用下劃線或空格分割
  5. 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工做流的核心功能.
    • flywayMigrate時會檢查元數據Metadata表.若是不存在會建立Metadata表,Metadata表主要用於記錄版本變動歷史以及Checksum之類
    • Migrate時會掃描指定文件系統或classpath下的數據庫的版本腳本Migrations, 而且會逐一比對Metadata表中已經存在的版本記錄,若是未應用的Migrations,flyway會獲取這些Migrations並按次序Apply到數據庫中,不然不會作任何事情
    • 一般會在應用程序啓動時默認執行Migrate操做,從而避免程序和數據庫的不一致
  • Clean:
    • 來清除掉對應數據庫的Schema的全部對象 .flyway不是刪除整個數據庫,而是清除全部表結構,視圖,存儲過程,函數以及全部相關的數據
    • 一般在開發和測試階段使用,可以快速有效地更新和從新生成數據庫表結構.可是不該該在production的數據庫使用
  • Info:
    • 打印全部Migrations的詳細和狀態信息,是經過Metadata表和Migrations完成的
    • 可以快速定位當前數據庫版本,以及查看執行成功和失敗的Migrations
  • Validate:
    • 驗證已經ApplyMigrations是否有變動 ,flyway是默認開啓驗證的
    • 操做原理是對比Metadata表與本地MigrationChecksum值,若是相同則驗證經過,不然驗證失敗,從而能夠防止對已經Apply到數據庫的本地Migrations的無心修改
  • Baseline:
    • 針對已經存在Schema結構的數據庫的一種解決方案
    • 實如今非空數據庫中新建Metadata表,並將Migrations應用到該數據庫
    • 能夠應用到特定的版本,這樣在已有表結構的數據庫中也能夠實現添加Metadata表,從而利用flyway進行新的Migrations的管理
  • Repair:
    • 修復Metadata表,這個操做在Metadata表表現錯誤時頗有用
    • 一般有兩種用途:
      • 移除失敗的Migration記錄,這個問題針對不支持DDL事務的數據庫
      • 從新調整已經應用的MigrationsChecksums的值. 好比,某個Migration已經被應用,但本地進行了修改,又指望從新應用並調整Checksum值. 不建議對數據庫進行本地修改

flyway的使用

正確建立Migrations
  • Migrations: flyway在更新數據庫時使用的版本腳本
    • 一個基於sqlMigration命名爲V1_ _init_tables.sql, 內容即爲建立全部表的sql語句
    • flyway也支持基於JavaMigration
    • flyway加載Migrations的默認Locationsclasspath:db/migration, 也能夠指定filesystem:/project/folder. Migrations的加載是在運行時自動遞歸執行的
  • 除了指定的Locations外,flyway須要聽從命名格式對Migrations進行掃描,主要分爲兩類:
    • Versioned migrations:
      • Versioned類型是經常使用的Migration類型
      • 用於版本升級,每個版本都有一個惟一的標識而且只能被應用一次. 而且不能再修改已經加載過的Migrations, 由於Metadata表會記錄Checksum
      • 其中的version標識版本號,由一個或者多個數字構成,數字之間的分隔符能夠採用點或者下劃線,在運行時下劃線也是被替換成點了. 每一部分的前導零都會被省略
    • Repeatable migrations:
      • Repeatable是指可重複加載的Migrations, 其中每一次更新都會更新Checksum值,而後都會被從新加載,並不用於版本升級. 對於管理不穩定的數據庫對象的更新時很是有用
      • RepeatableMigrations老是在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,HsqlPostgreSQL. 對應的flyway.url配置以下:
# MySQL
flyway.url=jdbc:mysql://localhost:3306/db?serverTimezone=UTC&useSSL=true
 # H2
flyway.url=jdbc:h2:./.tmp/db
 # Hsql
flyway.url=jdbc:hsqldb:hsql//localhost:1476/db
 # PostgreSQL
flyway.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=8080
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/db
spring.datasource.username=root
spring.datasource.password=123456
spring.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=utf8

insert 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引擎APICLI命令直接使用
  • 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服務的配置後無需重啓
    • Dockerswarm 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.04
    COPY . /app
    RUN make /app
    CMD 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並不知道. 這時,須要配置這個選項,週期性地檢查代碼倉庫是否發生變更
    -
構建環境
  • 構建環境: 構建以前的準備工做. 好比指定構建工具,這裏使用Ant

在這裏插入圖片描述

  • With Ant: 選擇這個選項,並指定Ant版本和JDK版本. 須要事先在jenkins服務器上安裝這兩個版本的工具,而且在jenkins全局工具中配置好
構建
  • 點擊下方的增長構建步驟:

- 這裏有多種增長構建步驟的方式,在這裏介紹Execute shellInvoke 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只加overallread權限
  • job_create只加jobcreate權限
  • project roles中Pattern正則表達式和腳本里的是不同的:
    • 好比過濾TEST開頭的jobs,要寫成 : TEST.,而不是 TEST
  • 進入**[系統設置]** -> [Manage and Assign Roles] -> [Assign Roles] , 按照以下模板配置後,點擊 [保存]

在這裏插入圖片描述

  • Anonymous必須變成用戶,給job_create組和job_read組權限,不然將沒有OverAllread權限
  • project roles: 用於對應用戶不一樣的權限
  • 驗證: 登陸對應的用戶權限後查看用戶相關權限 在這裏插入圖片描述
  • 視圖經過正則表達式過濾job: 設置正則表達式爲wechat.*,表示過濾全部以wechat開頭的項目

在這裏插入圖片描述

  • 設置後的效果如圖:

在這裏插入圖片描述

在這裏插入圖片描述

自動化測試-TestNG

TestNG基本概念

  • TestNG是一個Java語言的開源測試框架,相似JUnit和NUnit,可是功能強大,更易於使用
  • TestNG的設計目標是爲了覆蓋更普遍的測試類別範圍:
    • 單元測試
    • 功能測試
    • 端到端測試
    • 集成測試
  • TestNG的主要功能:
    • 支持註解
    • 支持參數化和數據驅動測試: 使用@DataProvider或者XML配置
    • 支持同一類的多個實例: @Factory
    • 靈活的執行模式:
      • TestNG的運行,既能夠經過Antbuild.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中,同時使用parallelthread-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初始化
    @Override
    public 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開始
    @Override
    public void onTestStart(ITestResult tr) {
    Assertion.flag = true;
    Assertion.errors.clear();
    sendStatus("運行中");
    result.setStartTime(format.format(new Date()));
    }
    
    //case成功執行
    @Override
    public void onTestSuccess(ITestResult tr) {
        super.onTestSuccess(tr);
        sendResult(tr);
        takeScreenShot(tr);
    }

    //case執行失敗
    @Override
    public 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被跳過
    @Override
    public void onTestSkipped(ITestResult tr) {
        super.onTestSkipped(tr);
        takeScreenShot(tr);
        sendResult(tr);
        this.handleAssertion(tr);
    }

    //全部case執行完成
    @Override
    public 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();
        }
    }
    }
}
複製代碼
相關文章
相關標籤/搜索