千寻

道路很长, 开始了就别停下!

0%

一、注解的基本概念和原理及其简单实用

注解(Annotation)提供了一种安全的类似注释的机制,为我们在代码中添加信息提供了一种形式化得方法,使我们可以在稍后某个时刻方便的使用这些数据(通过解析注解来使用这些数据),用来将任何的信息或者元数据与程序元素(类、方法、成员变量等)进行关联。其实就是更加直观更加明了的说明,这些说明信息与程序业务逻辑没有关系,并且是供指定的工具或框架使用的。Annotation像一种修饰符一样,应用于包、类型、构造方法、方法、成员变量、参数及本地变量的申明语句中。

Annotation其实是一种接口。通过[Java]的反射机制相关的API来访问Annotation信息。相关类(框架或工具中的类)根据这些信息来决定如何使用该程序元素或改变它们的行为。Java语言解释器在工作时会忽略这些Annotation,因此在JVM中这些Annotation是“不起作用”的,只能通过配套的工具才能对这些Annotation类型的信息进行访问和处理。

Annotation和interface的异同:

  • 1、 annotition的类型使用关键字@interface而不是interface。它继承了java.lang.annotition.Annotition接口,并非申明了一个interface。

  • 2、 Annotation类型、方法定义是独特的、受限制的。Annotation类型的方法必须申明为无参数、无异常抛出的。这些方法定义了Annotation的成员:方法名称为了成员名,而方法返回值称为了成员的类型。而方法返回值必须为primitive类型、Class类型、枚举类型、Annotation类型或者由前面类型之一作为元素的一位数组。方法的后面可以使用default和一个默认数值来申明成员的默认值,null不能作为成员的默认值,这与我们在非Annotation类型中定义方法有很大不同。Annotation类型和他的方法不能使用Annotation类型的参数,成员不能是generic。只有返回值类型是Class的方法可以在Annotation类型中使用generic,因为此方法能够用类转换将各种类型转换为Class。

  • 3、 Annotation类型又与接口有着近似之处。它们可以定义常量、静态成员类型(比如枚举类型定义)。Annotation类型也可以如接口一般被实现或者继承。

元注解@Target,@Retention,@Documented,@Inherited

  • @Target 表示该注解用于什么地方,可能的 ElemenetType 参数包括:

  • ElemenetType.CONSTRUCTOR 构造器声明

  • ElemenetType.FIELD 域声明(包括 enum 实例)

  • ElemenetType.LOCAL_VARIABLE 局部变量声明

  • ElemenetType.METHOD 方法声明

  • ElemenetType.PACKAGE 包声明

  • ElemenetType.PARAMETER 参数声明

  • ElemenetType.TYPE 类,接口(包括注解类型)或enum声明

  • @Retention 表示在什么级别保存该注解信息。可选的 RetentionPolicy 参数包括:

  • RetentionPolicy.SOURCE 注解将被编译器丢弃

  • RetentionPolicy.CLASS 注解在class文件中可用,但会被VM丢弃

  • RetentionPolicy.RUNTIME VM将在运行期也保留注释,因此可以通过反射机制读取注解的信息。

  • @Documented 将此注解包含在 javadoc 中

  • @Inherited 允许子类继承父类中的注解

1
2
3
4
@Target(ElementType.METHOD) 
@Retention(RetentionPolicy.RUNTIME) 
@Documented 
@Inherited

二、下面的示例来简单的讲述[spring]注解原理:

本例实现了在set方法上和在字段属性上注解的处理解析。

1、定义注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.yt.annotation;  

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* @Description:定义注解
* @ClassName: ZxfResource
* @Project: spring-aop
* @Author: zxf
* @Date: 2011-6-7
*/
// 在运行时执行
@Retention(RetentionPolicy.RUNTIME)
// 注解适用地方(字段和方法)
@Target({ ElementType.FIELD, ElementType.METHOD })
public @interface ZxfResource {

//注解的name属性
public String name() default "";
}

2、带有注解的服务类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package com.yt.annotation;  

/**
* @Description: 带有注解的服务
* @ClassName: UserDaoImpl
* @Project: spring-aop
* @Author: zxf
* @Date: 2011-6-7
*/
public class UserServiceImpl {

public UserDaoImpl userDao;
public User1DaoImpl user1Dao;

// 字段上的注解,可以配置name属性
@ZxfResource
public User2DaoImpl user2Dao;

// set方法上的注解,带有name属性
@ZxfResource(name = "userDao")
public void setUserDao(UserDaoImpl userDao) {
this.userDao = userDao;
}

// set方法上的注解,没有配置name属性
@ZxfResource
public void setUser1Dao(User1DaoImpl user1Dao) {
this.user1Dao = user1Dao;
}

public void show() {
userDao.show();
user1Dao.show1();
user2Dao.show2();
System.out.println("这里是Service方法........");
}
}

3、要注入的DAO

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.yt.annotation;  

/**
* @Description: 要注入的DAo类
* @ClassName: UserDaoImpl
* @Project: spring-aop
* @Author: zxf
* @Date: 2011-6-7
*/
public class UserDaoImpl {

String name ;

public void show(){
System.out.println("这里是dao方法........");
}
}
1
2
3
4
5
6
7
<?xml version="1.0" encoding="UTF-8"?>  
<beans>
<bean id = "userDao" class="com.yt.annotation.UserDaoImpl" />
<bean id = "user1Dao" class="com.yt.annotation.User1DaoImpl" />
<bean id = "user2Dao" class="com.yt.annotation.User2DaoImpl" />
<bean id = "userService" class = "com.yt.annotation.UserServiceImpl" />
</beans>

4、注解处理器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
package com.yt.annotation;  

import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.log4j.Logger;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

/**
* @Description: spring中的注解原理
* @ClassName: ClassPathXMLApplicationContext
* @Project: spring-aop
* @Author: zxf
* @Date: 2011-6-3
*/
public class ClassPathXMLApplicationContext {

Logger log = Logger.getLogger(ClassPathXMLApplicationContext.class);

List<BeanDefine> beanList = new ArrayList<BeanDefine>();
Map<String, Object> sigletions = new HashMap<String, Object>();

public ClassPathXMLApplicationContext(String fileName) {
//读取配置文件中管理的bean
this.readXML(fileName);
//实例化bean
this.instancesBean();
//注解处理器
this.annotationInject();
}

/**
* 读取Bean配置文件
* @param fileName
* @return
*/
@SuppressWarnings("unchecked")
public void readXML(String fileName) {
Document document = null;
SAXReader saxReader = new SAXReader();
try {
ClassLoader classLoader =
Thread.currentThread().getContextClassLoader();
document = saxReader.read(classLoader.getResourceAsStream(fileName));
Element beans = document.getRootElement();
for (Iterator<Element> beansList = beans.elementIterator();
beansList.hasNext();) {
Element element = beansList.next();
BeanDefine bean = new BeanDefine(
element.attributeValue("id"),
element.attributeValue("class"));
beanList.add(bean);
}
} catch (DocumentException e) {
log.info("读取配置文件出错....");
}
}

/**
* 实例化Bean
*/
public void instancesBean() {
for (BeanDefine bean : beanList) {
try {
sigletions.put(bean.getId(),
Class.forName(bean.getClassName()).newInstance());
} catch (Exception e) {
log.info("实例化Bean出错...");
}
}
}

/**
* 注解处理器
* 如果注解ZxfResource配置了name属性,则根据name所指定的名称获取要注入的实例引用,
* 如果注解ZxfResource;没有配置name属性,则根据属性所属类型来扫描配置文件获取要
* 注入的实例引用
*
*/
public void annotationInject(){
for(String beanName:sigletions.keySet()){
Object bean = sigletions.get(beanName);
if(bean!=null){
this.propertyAnnotation(bean);
this.fieldAnnotation(bean);
}
}
}

/**
* 处理在set方法加入的注解
* @param bean 处理的bean
*/
public void propertyAnnotation(Object bean){
try {
//获取其属性的描述
PropertyDescriptor[] ps =
Introspector.getBeanInfo(bean.getClass()).getPropertyDescriptors();
for(PropertyDescriptor proderdesc : ps){
//获取所有set方法
Method setter = proderdesc.getWriteMethod();
//判断set方法是否定义了注解
if(setter!=null && setter.isAnnotationPresent(ZxfResource.class)){
//获取当前注解,并判断name属性是否为空
ZxfResource resource = setter.getAnnotation(ZxfResource.class);
String name ="";
Object value = null;
if(resource.name()!=null&&!"".equals(resource.name())){
//获取注解的name属性的内容
name = resource.name();
value = sigletions.get(name);
}else{ //如果当前注解没有指定name属性,则根据类型进行匹配
for(String key : sigletions.keySet()){
//判断当前属性所属的类型是否在配置文件中存在
if(proderdesc.getPropertyType().isAssignableFrom(sigletions.get(key).getClass())){
//获取类型匹配的实例对象
value = sigletions.get(key);
break;
}
}
}
//允许访问private方法
setter.setAccessible(true);
//把引用对象注入属性
setter.invoke(bean, value);
}
}
} catch (Exception e) {
log.info("set方法注解解析异常..........");
}
}

/**
* 处理在字段上的注解
* @param bean 处理的bean
*/
public void fieldAnnotation(Object bean){
try {
//获取其全部的字段描述
Field[] fields = bean.getClass().getFields();
for(Field f : fields){
if(f!=null && f.isAnnotationPresent(ZxfResource.class)){
ZxfResource resource = f.getAnnotation(ZxfResource.class);
String name ="";
Object value = null;
if(resource.name()!=null&&!"".equals(resource.name())){
name = resource.name();
value = sigletions.get(name);
}else{
for(String key : sigletions.keySet()){
//判断当前属性所属的类型是否在配置文件中存在
if(f.getType().isAssignableFrom(sigletions.get(key).getClass())){
//获取类型匹配的实例对象
value = sigletions.get(key);
break;
}
}
}
//允许访问private字段
f.setAccessible(true);
//把引用对象注入属性
f.set(bean, value);
}
}
} catch (Exception e) {
log.info("字段注解解析异常..........");
}
}

/**
* 获取Map中的对应的bean实例
* @param beanId
* @return
*/
public Object getBean(String beanId) {
return sigletions.get(beanId);
}


public static void main(String[] args) {
ClassPathXMLApplicationContext path = new ClassPathXMLApplicationContext(
"configAnnotation.xml");
UserServiceImpl userService =(UserServiceImpl)path.getBean("userService");
userService.show();
}
}

1)前言

关于位运算符 与(&)、或(|)、异或(^)、取反(~)、左移(<<)、右移(>>)、无符号右移(>>>)

位运算其实就是二进制的运算,加减乘除适用于十进制,而位运算就是二进制的运算,但是由于我们的运算都是基于十进制来说的,所以会有点绕,略微有点难懂

2)关于二进制

我们在编码过程中应该会了解很多不同的进制,除去常用的十进制,还有二进制、八进制、十六进制,因为我们的位运算符主要用到二进制,就只讲讲二进制

几进制就是一个位上最多能表示几个数,如十进制的个位有0~9十个数字,八进制有0~7八个数字,二进制我们自然想到就是0~1两个数字

我们编码中最小的单位应该是字节,而一个字节有8位,每一位就是一个0或1,所以一个字节用二进制表示就是

这样的

3) 与运算符(&)

如果 4&7 那么这个应该怎么运算呢?

首先我们需要把两个十进制的数转换成二进制

4 : 0000 0100

7 : 0000 0111

在这里要提到一点,1表示true,0表示false

而与运算的时候相同位之间其实就是两个Boolean的运算

  • 全true(1),即为true(1)

  • 全false(0),即为false(0)

  • 一false(0)一true(1),还是false(0)

4)或运算符(|)

以 5|9 为例

5 : 0000 0101

9 : 0000 1001

在做与运算的时候

遇true(1)就是true(1),

无true(1)就是false(0)

5) 异或运算符(^)

以 7^15 为例

7: 0000 0111

15: 0000 1111

在异或的时候

  • 只要相同都是false(0)

  • 只有不同才是true(1)

6) 取反运算符(~)

例: ~15

同样的先变成二进制:15:0000 1111

这个其实挺简单的,就是把1变0,0变1

注意:二进制中,最高位是符号位 1表示负数,0表示正数

7) 左移运算(<<)

左移就是把所有位向左移动几位

如: 12 << 2 意思就是12向左移动两位

12的二进制是: 0000 1100

通过这个图我们可以看出来,所有的位全都向左移动两位,然后把右边空的两个位用0补上,最左边多出的两个位去掉,最后得到的结果就是00110000 结果就是48

我们用同样的办法算 12<<3 结果是 96 , 8<<4 结果是 128

由此我们得出一个快速的算法 M << n 其实可以这么算 M << n = M * 2n

8) 右移运算符(>>)

这个跟左移运算大体是一样的

例: 12 >> 2

我们可以看出来右移和左移其实是一样的,但是还是有点不同的,不同点在于对于正数和负数补位的时候补的不一样,负数补1,正数补0

如我们再做一个 -8 的 -8>>2

这里总结一下,关于负数或者正数来说,移位的时候是一样的,但是在补位的时候,如果最高位是0就补0,如果最高位是1就补1

由此我们得出一个快速的算法 M >> n 其实可以这么算 M >> n = M / 2^n

9无符号右移(>>>)

无符号右移(>>>)只对32位和64位有意义

在移动位的时候与右移运算符的移动方式一样的,区别只在于补位的时候不管是0还是1,都补0

在选择 Mysql 版本的时候,了解一下版本的变迁历史是有帮助的。
| 版本 | 发行日期
| — | —
| 3.23 | 2001
| 4.0 | 2003
| 4.1 | 2005
| 5.0 | 2006
| 5.1 | 2008
| 5.5 | 2010
| 5.6 | 2012
| 5.7 | 2015

版本3.23(2001)

一般认为这个版本的发布是Mysql真正“诞生”的时刻,其开始获得广泛使用。

  • 在这个版本,Mysql依然只是一个在平面文件(Flat File) 上实现了 SQL 查询的系统。
  • 但一个重要的改进是引入 MyISAM 代替了老旧而且有诸多限制的 ISAM 引擎。
  • InnoDB 引擎也已经可以使用,但没有包含在默认的二进制发行版中,因为它太新了。所以如果要使用 InnoDB,必须手工编译。
  • 版本 3.23 还引入了全文索引和复制。复制是 Mysql 成为互联网应用的数据库系统的关键特性。

版本4.0(2003)

  • 支持新的语法,比如 UNION 和多表 DELETE 语法。
  • 重写了复制,在备库使用了两个现成来实现复制,避免了之前一个线程所有复制工作的模式下任务切换导致的问题。
  • InnoDB 成为标准配备,包括了全部的特性:行级锁、外键等。
  • 引入了查询缓存(自那以后这部门改动不大),同时还支持通过 SSL 进行连接。

版本4.1(2005)

  • 引入了更多新的语法,比如子查询和 INSERT ON DUPLICATE KEY UPDATE。
  • 开始支持 UTF-8 字符集。
  • 支持新的二进制协议和 prepared 语句。

版本5.0(2006)

  • 这个版本出现了一些“企业级”特性:视图、触发器、存储过程和存储函数。
  • 老的 ISAM 引擎的代码被彻底移除,同时引入了新的 Federated 等引擎。

版本5.1(2008)

  • 这是 Sun 收购 MySQL AB 以后发布的首个版本,研发时间长达五年。
  • 引入了分区、基于行的复制,以及 plugin API(包括可插拔存储引擎的 API)。
  • 移除了 BerkeyDB 引擎,这是 MySQL 最早的事务存储引擎。
  • 其他如 Federated 引擎也被放弃。
  • 同时 Oracle 收购的 InnoDB Oy发布了 InnoDB plugin。

版本5.5(2010)

性能提升

  • 默认InnoDB plugin引擎。具有提交、回滚和crash恢复功能、ACID兼容。
  • 行级锁(一致性的非锁定读 MVCC)。- 表与索引存储在表空间、表大小无限制。
  • 支持dynamic(primary key缓存内存 避免主键查询引起的IO )与compressed(支持数据及索引压缩)行格式。
  • InnoDB plugin文件格式Barracuda、支持表压缩、节约存储、提供内存命中率、truncate table速度更快。
  • 原InnoDB只有一个UndoSegment,最多支持1023的并发;现在有128个Segments,支持128K个并发(同样,解决高并发带来的事务回滚)。
  • Innodb_thread_concurrency默认为0,线程并发数无限制,可根据具体应用设置最佳值。
  • Innodb_io_capacity可以动态调整刷新脏页的数量,改善大批量更新时刷新脏页跟不上导致的性能下降问题。Default:200,跟硬盘的IOPS有关。
  • 充分利用CPU多核处理能力innodb_read_io_threads阈值:1-64innodb_write_io_threads 阈值:1-64根据数据库的读写比灵活设置,充分发挥多CPU、高性能存储设备的性能,不支持动态加载 。
  • 自适应刷新脏页
  • 热数据存活更久
  • buffer pool多实例 :innodb_buffer_pool_instances 参数增加innodb_buffer_pool实例个数,大大降低buffer pool的mutex争抢过热情况。
  • Linux上实现异步IO
  • 重新支持组提交

稳定性提升

  • 支持半同步Replication。- 增加Relay Log 自我修复功能。
  • Crash recovery。
  • 引入红-黑树做插入排序的中间数据结构,时间复杂度大大降低,减少恢复时间。
  • Thread Pool 分组排队 限流
  • 增加Relay Log 自我修复功能。

版本5.6(2012)

  • 默认参数的改变
  • Back_log 排队队列
  • 支持全文索引
  • 支持online DDL create,alter,drop
  • 可以在建表时指定表空间位置
    create table external (x int unsigned not null primary key)data directory = ‘/volumes/external1/data’;
  • 新增参数innodb_page_size可以设置page大小
  • 整合了memcached API,可以使用API来直接访问innodb表,并非SQL(减少SQL解析、查询优化代价)
  • innodb只读事务,不需要设置TRX_ID字段,
  • 减少内部数据结构开销,减少read view
  • 仅仅非只读事务依然需要TRX_ID

innodb改进点

  • innodb表空间在线迁移(TransportableTablespaces)
  • undo log可独立出系统表空间
  • redo log最大可增长到512G
  • innodb后台线程独立出来

优化器改进

  • ICP
    可以在引擎层直接过滤数据,避免二次回表
    节省BP空间,提高查询性能
  • BKA
    全称Batch Key Access:
    SQL通过辅助索引要访问表数据时候,将大量的随机访问放入缓存,交给MRR接口合并为顺序访问。
  • MRR
    全称Multi Range Read:
    在BKA算法应用之后,通过MRR接口合并随机访问为顺序访问,再去检索表数据。
    变大量随机为顺序访问。在通过辅助索引检索大量数据时,性能提升明显
    磁头无需来回寻道,page只需读取一次,且较好利用了innodb线性预读功能(每次预读64个连续page)。
  • 统计信息持久化,mysqld重启后不丢失
  • explain语句支持insert,update,delete,replace语句,并且支持JSON格式
  • 子查询优化提升。

版本5.7(2015年)

安全性

  • 用户表 mysql.user 的 plugin字段不允许为空, 默认值是 mysql_native_password,而不是 mysql_old_password,不再支持旧密码格式;
  • 增加密码过期机制,过期后需要修改密码,否则可能会被禁用,或者进入沙箱模式;
  • 提供了更为简单SSL安全访问配置,并且默认连接就采用SSL的加密方式。

灵活性

  • MySQL数据库从5.7.8版本开始,也提供了对JSON的支持。
  • 可以混合存储结构化数据和非结构化数据,同时拥有关系型数据库和非关系型数据库的优点
  • 能够提供完整的事务支持
  • generated column是MySQL 5.7引入的新特性,所谓generated column,就是数据库中这一列由其他列计算而得

易用性

  • 在MySQL 5.7 之前,如果用户输入了错误的SQL语句,按下 ctrl+c ,虽然能够”结束”SQL语句的运行,但是,也会退出当前会话,MySQL 5.7对这一违反直觉的地方进行了改进,不再退出会话。
  • MySQL 5.7可以explain一个正在运行的SQL,这对于DBA分析运行时间较长的语句将会非常有用。
  • sys schema是MySQL 5.7.7中引入的一个系统库,包含了一系列视图、函数和存储过程, 该项目专注于MySQL的易用性。
    例如:如何查看数据库中的冗余索引;如何获取未使用的索引;如何查看使用全表扫描的SQL语句。

可用性

  • 在线设置 复制的过滤规则 不再需要重启MySQL,只需要停止SQLthread,修改完成以后,启动SQLthread。
  • 在线修改buffer pool的大小。
  • Online DDL MySQL 5.7支持重命名索引和修改varchar的大小,这两项操作在之前的版本中,都需要重建索引或表。
  • 在线开启GTID ,在之前的版本中,由于不支持在线开启GTID,用户如果希望将低版本的数据库升级到支持GTID的数据库版本,需要先关闭数据库,再以GTID模式启动,所以导致升级起来特别麻烦。

性能

  • 临时表的性能改进。
    临时表只在当前会话中可见
    临时表的生命周期是当前连接(MySQL宕机或重启,则当前连接结束)
  • 只读事务性能改进。
    MySQL 5.7通过 避免为只读事务分配事务ID ,不为只读事务分配回滚段,减少锁竞争等多种方式,优化了只读事务的开销,提高了数据库的整体性能。
  • 加速连接处理。
    在MySQL 5.7之前,变量的初始化操作(THD、VIO)都是在连接接收线程里面完成的,现在将这些工作下发给工作线程,以减少连接接收线程的工作量,提高连接的处理速度。这个优化对那些频繁建立短连接的应用,将会非常有用。
  • 复制性能的改进 (支持多线程复制(Multi-Threaded Slaves, 简称MTS)
    MySQL的默认配置是库级别的并行复制,为了充分发挥MySQL 5.7的并行复制的功能,我们需要将slave-parallel-type配置成LOGICAL_CLOCK。
  • 支持多源复制(Multi-source replication)

    严格性改变

  • 默认启用 STRICT_TRANS_TABLES 模式。
  • 对 ONLY_FULL_GROUP_BY 模式实现了更复杂的特性支持,并且也被默认启用。
  • 其他被默认启用的sql mode还有 NO_ENGINE_SUBSTITUTION。

默认参数的改变

  • 默认binlog格式调整为ROW格式
  • 默认binlog错误后的操作调整为ABORT_SERVER
    在先前的选项下(binlog_error_action=IGNORE_ERROR),如果一个错误发生,导致无法写入binlog,mysql-server会在错误日志中记录错误并强制关闭binlog功能。这会使mysql-server在不记录binlog的模式下继续运行,导致从库无法继续获取到主库的binlog。
  • 默认开启mysql崩溃时的binlog安全。
  • 默认调低slave_net_timeout。

安装不同

  • mysql_install_db已经不再推荐使用了,建议改成mysqld –initialize 完成实例初始化。如果 datadir 指向的目标目录下已经有数据文件,则会有[ERROR] Aborting;
  • 在初始化时如果加上 –initial-insecure,则会创建空密码的 root@localhost 账号,否则会创建带密码的 root@localhost 账号,密码直接写在 log-error 日志文件中;新用户登入后需要立刻修改密码,否则无法继续后续的工作。

本人在实际工作中总结出的Git使用规范,比较简单,容易落地。

1. 分支管理

开发过程主要存在以下分支:

  • master
  • develop
  • release
  • hotfix-[问题名称 | bug编号]
  • feature-[功能名称]
  • bugfix-[bug编号]
  • refactor-[重构名称]

1.1 master 主分支

  • master主分支 用于部署生产环境的分支,确保master分支稳定性,始终保持稳定的可发布版本
  • master 分支一般由release以及hotfix分支合并,任何时间都不能直接修改代码
  • 只有项目组主程才拥有master主分支的管理权限(例如其他分支合并到master必须由主程操作)

1.2 develop 开发分支

  • develop开发分支为不稳定版本,但已有的功能必须是完整的
  • 始终保持最新完成以及bug修复后的代码
  • 原则上不允许直接在develop分支上进行功能开发,必须新建feature分支进行开发

1.3 release分支 预上线分支

  • release 为预上线分支,发布提测阶段,会release分支代码为基准提测
  • 当有一组feature开发完成,首先会合并到develop分支,进入提测时,会创建release分支。
    如果测试过程中若存在bug需要修复,则直接由开发者在release分支修复并提交。
    当测试完成之后,合并release分支到master和develop分支,此时master为最新代码,用作上线。

1.4 hotfix-[问题名称 | bug编号] 紧急热修复分支

  • 线上出现紧急问题时,需要及时修复,以master分支为基线,创建hotfix分支,横线后面跟上问题名称或者对应的bug编号,仅仅适用于生产线问题紧急修复!!
  • 修复完成,测试通过,合并到master和develop分支上,然后将此分支删除

1.5 feature-[功能名称] 功能开发分支

  • 从develop分支创建,横线后跟功能名称,用于新功能开发,每天下班前push提交到远程
  • 开发完成以后,在远程发起向develop分支的合并请求,由指定的CodeReview人员审查通过以后进行合并,并删除该分支
  • 命名规则: feature-user_module、 feature-cart_module

1.6 bugfix-[bug编号] 问题修复分支

  • 从develop分支创建,用于修改测试提出的bug,横线后跟bug编号
  • 修复以后,在远程发起向develop分支的合并请求,并指定提交者自身(或其他人)作为CodeReview,合并以后删除该分支

1.7 refactor-[重构名称] 重构分支

  • 从develop分支创建,用于代码的重大规模重构(小规模重构创建feature分支即可)
  • 重构以后,必须经过严格测试通过,才能向develop分支合并。

常见任务

增加新功能

1
2
3
4
5
(develop)$: git checkout -b feature/xxx            # 从develop建立特性分支
(feature/xxx)$: blabla # 开发
(feature/xxx)$: git add xxx
(feature/xxx)$: git commit -m 'commit comment'
(develop)$: git merge feature/xxx --no-ff # 把特性分支合并到develop

修复紧急bug

1
2
3
4
5
6
(master)$: git checkout -b hotfix/xxx         # 从master建立hotfix分支
(hotfix/xxx)$: blabla # 开发
(hotfix/xxx)$: git add xxx
(hotfix/xxx)$: git commit -m 'commit comment'
(master)$: git merge hotfix/xxx --no-ff # 把hotfix分支合并到master,并上线到生产环境
(develop)$: git merge hotfix/xxx --no-ff # 把hotfix分支合并到develop,同步代码

预发布环境代码

1
(release)$: git merge develop --no-ff     # 把develop分支合并到release,然后在测试环境拉取并测试

生产环境上线

1
2
(master)$: git merge release --no-ff      # 把testing测试好的代码合并到master
(master)$: git tag -a v0.1 -m '部署包版本名' #给版本命名,打Tag

2. Commit 提交规范

2.1 commit提交的日志格式

:

  • type: 本次 commit 的类型,如feat fix doc refactor 等
  • subject: 简明扼要的阐述下本次 commit 的主旨,在原文中特意强调了几点 1. 使用祈使句,是不是很熟悉又陌生的一个词,来传送门在此 祈使句 2. 首字母不要大写 3. 结尾无需添加标点
  • body: 同样使用祈使句,在主体内容中我们需要把本次 commit 详细的描述一下,比如此次变更的动机,如需换行,则使用 |
  • footer: 描述下与之关联的 issue 或 break change,详见案例
类型 描述
feat feature,即新开发的功能
fix 问题修复
refactor 重构代码
doc 增加文档(如readme),注释等

例如:

fix:修复身份证含字母X的用户无法注册问题
fix: 紧急修复生产线用户积分不显示的问题
feat:商品详情页功能
doc:增加项目readme文档,修改结算条款结算逻辑的注释

2.2 Commit提交频率

每天下班前必须提交feature分支,并push到远程
hotfix、feature、bugfix、refactor分支尽量按照功能点或修复重构的问题及时commit(不要求push)

ab命令原理

Apache的ab命令模拟多线程并发请求,测试服务器负载压力,也可以测试nginx、lighthttp、IIS等其它Web服务器的压力。

阅读全文 »