mainidear.

2019-08-02
10002分布式事务实现TransactionManager

TCC型分布式事务原理和实现之TransactionManager

目前,还没有一款商用成熟的开源TCC框架,所以很多人基于不同思想实现了TCC的很多版本,本文所分析的TCC框架,是笔者在读了一些TCC开源代码、JTA设计思想之后逐步修改而来,由于代码还在逐步优化中,但是大体已经成型,所以将TCC框架的一点设计思想分享出来。

核心类图
enter image description here

事务管理器(TransactionManager)作为TCC分布式事务的核心组件,负责事务的管理工作(包括事务的提交和回滚)。 每一个事务的参与者(Participant)中都会有一个单例的事务管理器负责本地事务的管理工作。在TCC的try阶段,根事务(ROOT,表示事务的发起方)参与者中的事务管理器会负责创建根事务并持久化事务日志,同时会将创建的事务保存在ThreadLocal类型的队列中,处于分支事务参与者中的事务管理器会直接从事务上下文中传播(propagation)一个新的分支事务,同时也会持久化事务日志并将事务加入ThreadLocal队列中。在根事务端,try阶段结束后,TCC框架会根据try阶段是否有异常分别自动调用根事务管理器的commit和rollback方法,以commit为例,根事务管理器的commit又会调用根事务(transaction)的commit方法,根事务(transaction)的commit方法会遍历所有的事务参与者(Participant)的commit方法,这里的参与者一共有两种,分别为根事务参与者和分支事务参与者,这里的所谓的“根”和“分支”都只是逻辑上角色的区分,表示主动发起和被动参与的区别,它们在代码层面完全是共用一套逻辑的,比如,一个分支事务同样可以发起根事务,这样就是典型的嵌套型事务了。但是,整个事务的提交和回滚一定是由最顶层事务管理器发起的,其他的分支事务(包括多层嵌套事务)都是在最顶层根事务管理器的协调下完成自身的提交和回滚。至此,一个完整的分布式事务就结束了,当然,这只是一个大概的过程描述,还有很多细节没有提及,主要是目前的讨论还没有限制SOA框架,所以无法谈论具体的远程事务细节,等到后面专门讨论远程事务的文章(SOA为dubbo)时,会详细讨论根事务参与者和分支事务参与者。

事务管理器

事务管理器的属性只有transactionRepository和CURRENT,其中transactionRepository用于持久化事务日志,CURRENT用于保存该事务管理器上活动的事务,它是一个ThreadLocal队列。

enter image description here

事务管理器具有较多的方法,下面分表分析:

begin()

1
2
3
4
5
6
7
8
9
10
11
begin()表示开始一个事务,会在根事务(ROOT)的try方法中被调用。       

public Transaction begin() {
// 创建事务,事务类型为根事务ROOT
Transaction transaction = new Transaction(TransactionType.ROOT);
// 事务日志持久化
transactionRepository.create(transaction);
// 注册事务,就是加到线程局部变量的队列中
registerTransaction(transaction);
return transaction;
}

registerTransaction()

1
2
3
4
5
6
7
8
private void registerTransaction(Transaction transaction) {
// 如果队列还没有创建就先创建一个
if (CURRENT.get() == null) {
CURRENT.set(new LinkedList<Transaction>());
}
// 加入事务队列
CURRENT.get().push(transaction);
}

propagationNewBegin()
propagationNewBegin用于从一个事务上下文中传播一个新事务,通常会在分支事务(比如dubbo中的provider端)的try阶段被调用,此处的事务上下文可以使用方法传参,也可以使用特定SOA框架的隐式传参(比如dubbo)。

1
2
3
4
5
6
7
8
9
public Transaction propagationNewBegin(TransactionContext transactionContext) {
// 从transactionContext创建一个事务
Transaction transaction = new Transaction(transactionContext);
// 事务日志持久化
transactionRepository.create(transaction);
// 注册事务到事务管理器
registerTransaction(transaction);
return transaction;
}

propagationExistBegin()
propagationExistBegin用于从事务上下文中传播一个已存在的事务,通常会在分支事务(比如dubbo中的provider端)的confirm阶段和cancel阶段被调用,此处的事务上下文可以使用方法传参,也可以使用特定SOA框架的隐式传参(比如dubbo)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 public Transaction propagationExistBegin(TransactionContext transactionContext) throws NoExistedTransactionException {
// 从持久化日志中根据事务id拿到事务
Transaction transaction = transactionRepository.findByXid(transactionContext.getXid());
// 如果找到了事物
if (transaction != null) {
// 更改事务状态为transactionContext中的状态
transaction.changeStatus(TransactionStatus.valueOf(transactionContext.getStatus()));
// 注册事务
registerTransaction(transaction);
return transaction;
} else {
throw new NoExistedTransactionException();
}
}

commit()
commit会在事务try阶段没有异常的情况下,由TCC框架自动调用。它首先从ThreadLocal队列中取出当前要处理的事务(但不从队列中删除这个事务),然后将事务状态改为CONFIRMING状态,更新事务日志。随后调用事务的commit方法进行事务提交处理,如果事务提交成功(没有抛出任何异常),那么就从事务日志仓库中删除这个事务日志。如果在事务commit过程中抛出了异常,那么这个事物日志此时不会被删除(稍后会被recovery任务处理),同时,框架会将异常全部转为ConfirmingException向业务层抛出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 public void commit() {
// 获取本地线程上事务队列中的时间最久的事务
Transaction transaction = getCurrentTransaction();
if(transaction == null){
return;
}
// 更改事务状态为CONFIRMING
transaction.changeStatus(TransactionStatus.CONFIRMING);
// 更新事务持久化
transactionRepository.update(transaction);

try {
// 调用事务的commit
transaction.commit();
// 如果上面的commit没有抛出任何异常就说明事务成功,就从事务日志中删除这个事务
transactionRepository.delete(transaction);
} catch (Throwable commitException) {
// 事务commit过程抛出了异常
logger.error("compensable transaction confirm failed.", commitException);
// 转为抛出ConfirmingException异常,这样会导致事务在事务日志中不被删除,recovery会去处理长时间没有被删除的事务
throw new ConfirmingException(commitException);
}
}

getCurrentTransaction()
获取当前要处理的事务,此处要注意的是,队列的peek只是取出队列头部元素,但是不会将其删除。

1
2
3
4
5
6
7
public Transaction getCurrentTransaction() {
if (isTransactionActive()) {
// 拿到队列头的事务(但是不从队列中删除,删除是在cleanAfterCompletion中进行)
return CURRENT.get().peek();
}
return null;
}

isTransactionActive()
判断当前事务管理中是否还有活动的事务。

1
2
3
4
public boolean isTransactionActive() {
Deque<Transaction> transactions = CURRENT.get();
return transactions != null && !transactions.isEmpty();
}

rollback()

rollback和commit相对,当try阶段抛出了任何异常,TCC框架会自动调用。它首先从事务管理器中取出当前活动的事务,更改事务状态为CANCELLING,并更新事务日志。然后调用事务的rollback进行事务回滚(事务的rollback会遍历所有参与者,并分别调用参与者的rollback,通常,根事务端的参与者包含根事务参与者和分支事务参与者,而分支事务参与者通常只有一个本地的事务参与者,除非它也发起了TCC分布式事务)。如果rollback成功,事务会被从事务日志持久化仓库中删除,否则直接向业务层代码抛出CancellingException异常,残留的事务日志稍后会被recovery任务处理(可选、可配置)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public void rollback() {
// 回滚事务
Transaction transaction = getCurrentTransaction();
// 更改事务状态为CANCELLING
transaction.changeStatus(TransactionStatus.CANCELLING);
// 更新事务持久化日志
transactionRepository.update(transaction);

try {
// 调用事务的rollback
transaction.rollback();
// 没有异常,就从事务日志中删除这个事务
transactionRepository.delete(transaction);
} catch (Throwable rollbackException) {
logger.error("compensable transaction rollback failed.", rollbackException);
// 否则事务异常,抛出CancellingException
throw new CancellingException(rollbackException);
}
}

cleanAfterCompletion()
还记得前文所说的,每次从事务管理器获取当前活动事务的时候,都不会从队列中将其删除,那么这些事务会在什么时候删除呢,这就是cleanAfterCompletion的作用。在每次事务处理结束时,TCC框架都会调用cleanAfterCompletion进行事务的清理操作。清理之前要比对要清理的事务是不是当前事务。

1
2
3
4
5
6
7
8
9
10
 public void cleanAfterCompletion(Transaction transaction) {
if (isTransactionActive() && transaction != null) {
Transaction currentTransaction = getCurrentTransaction();
if (currentTransaction == transaction) {
CURRENT.get().pop();
} else {
throw new SystemException("Illegal transaction when clean after completion");
}
}
}

enlistParticipant()
enlistParticipant用于向事务中添加一个事务参与者,同上,这里的参与者包含了本地参与者和远程参与者,添加参与者之后必须更新事务日志。enlistParticipant会在添加到TCC事务方法的切面中被调用

1
2
3
4
5
 public void enlistParticipant(Participant participant) {
Transaction transaction = this.getCurrentTransaction();
transaction.enlistParticipant(participant);//将参与者加入事务的参与者列表中
transactionRepository.update(transaction);// 更新事务日志
}

标记TCC事务方法

本文多次提到了TCC事务方法和切面逻辑,那么什么是一个TCC事务方法呢?给一个方法添加标记肯定要使用注解了,TCC框架中,使用Compensable注解来表示一个方法是一个TCC事务方法,同时TCC框架针对标记了Compensable注解的方法提供了两个切面:CompensableTransactionAspect和ResourceCoordinatorAspect。其中CompensableTransactionAspect用于封装事务逻辑(事务开启、提交和回滚等),而ResourceCoordinatorAspect切面用于封装一个参与者并添加到事务中(上文提及)。由于还有专门的文章介绍这两个切面,所以本文暂时不作深入讨论。这里需要注意的是,这两个切面是有优先级排序的,CompensableTransactionAspect优先级高于ResourceCoordinatorAspect,这个只要实现spring框架中的Ordered接口就可以了。
enter image description here

Read More

2019-08-02
10003分布式事务实现Transaction与Participant

TCC型分布式事务实现之:Transaction与Participant

在TCC型分布式事务原理和实现之:TransactionManager一文中,介绍了TCC事务管理器的主要功能和实现原理。相较于事务管理器,事务则包含了更多的属性状态,下面的UML图中可以清晰的体现Transaction与Participant的关系。
enter image description here

事务
事务具有很多的属性状态。首先,事务必须具有一个唯一ID来标识自己(保证进程内唯一即可),这样不同的事务就可以进行隔离控制,常见的事务ID生成方法就是使用uuid了;TCC事务一共有try、confirm和cancel三个阶段,因此,事务必须有一个事务状态字段来标识事物当前的状态:TRYING, CONFIRMING, CANCELLING;在TransactionManager一文中,多次提到根事务和分支事务,此处再重新提一下,所谓根事务,就是指事务的主动发起方,而分支事务,就是事务的被动发起方,也就是谁先开始谁就是老大,剩下的都是追随者、参与者。那么事务当然需要一个类型字段来标识当前事务的类型了,根事务用ROOT标识,分支事务用BRANCH标识;事务不一定总是成功,否则的话分布式事务也就不再是什么难题和秘密了,事务失败了怎么办呢?很多人第一想法就是回滚啊,其实,可以完成回滚的事务我将其理解为“正常事务”,也就是事务回滚成功,事务的一致性依然保持。然后,真正的异常事务是指在commit和cancel阶段失败的事务,那么这个时候怎么办呢?业界常用的手段就是:补偿。很多人在第一次听说事务补偿的时候,都觉得这是一个很高大上的技术,恰恰相反,补偿甚至是事务处理中最笨的办法。补偿也可以理解为弥补,就是一件事情做错了,尽可能的通过各种方法去弥补,使之尽可能的变得正确。的确,补偿也不代表就一定能够成功,因此通常会给这个补偿动作加一个时长或者次数限制,实在不行,就需要人工介入了,这是最后一道防线了。这里的retriedCount就表示一个事务在异常之后又被补偿重试的次数统计,通常都会有专门的监控系统来监控该字段的变化。由于补偿通常意味着多次重试,因此需要补偿方法是幂等的;createTime、lastUpdateTime表示事务的创建时间和最近更新时间,这在处理事务的超时、事务统计和事务补偿时非常有用。version表示事务的版本;participants就表示事务的所有参与者了,这里的参与者包括事务发起方本身(我将其称为根事务参与者)和分支事务参与者。通常,分支事务参与者都代表了一个远程服务;attachments可以用于暂存事务的附加参数,该附加参数可以被事务上下文携带着传到分支事务(远程服务),也相当于dubbo中的隐式传参了。
enter image description here

事务本身仅含有很少的方法属性,首先来看其构造方法。

1
2
3
4
5
public Transaction(TransactionContext transactionContext) {
this.xid = transactionContext.getXid(); // 从transactionContext中获取事务id
this.status = TransactionStatus.TRYING; // 事务状态为TRYING(因为是首次嘛)
this.transactionType = TransactionType.BRANCH; // 事务类型为分支事务
}

该构造方法需要一个事务上下文作为参数,事务上下文和一次事务活动是一一对应的,它包含了事务ID、事务的当前状态、以及事务的附加参数,事务上下文必须是可序列化的,因为它会被序列化传送到远端(分支事务)。总而言之,整个分布式事务就是靠事务上下文串接起来的。该构造方法一般会在分支事务端被调用,用于根据从根事务端传递过来的事务上下文中创建一个分支事务。

下面还有一个重载的构造方法版本。它需要一个事务类型作为参数,该构造方法通常会在根事务端被调用。

1
2
3
4
5
public Transaction(TransactionType transactionType) {
this.xid = new TransactionXid(); // 获取新的事务id
this.status = TransactionStatus.TRYING;
this.transactionType = transactionType;
}

下面是事务中最核心的两个方法了:commit和rollback。原理很简单,遍历调用每一个参与者(Participant)的commit或rollback方法。如果你去对比JTA的实现,会发现代码如出一辙。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void commit() {
// 遍历所有参与者
for (Participant participant : participants) {
// 调用所有参与者的commit,这个参与者有本地参与者也有远程参与者
participant.commit();
}
}

// 回滚这个事物
public void rollback() {
for (Participant participant : participants) {
// 遍历所有参与者,并调用其rollback
participant.rollback();
}
}

事务参与者

事务参与者(Participant)表示事务的一个参与方,通常,一次事务活动中会有多个事务参与者,否则也就没有必要使用分布式事务了。在TCC分布式事务中,通常会有多个远程服务作为分支事务参与者。

下图为Participant的UML,先看属性字段。Participant需要一个事务ID来标识自己所属的事务。confirmInvocationContext和cancelInvocationContext都为InvocationContext类型,InvocationContext标识一个调用上下文,它非常像dubbo中的Invocation,封装了一个方法调用中的:目标对象、方法名、参数类型、参数列表等,不了解的可以先去看一下dubbo。其中,confirmInvocationContext标识参与者的confirm方法调用上下文,cancelInvocationContext标识cancel方法的调用上下文,这些上下文通过构造器注入的,下文将会提到。Terminator表示终结的意思,他表示执行最终的方法调用,暂时不做细说,后文再论。最后一个属性transactionContextEditorClass标识事务上下文的获取工厂,因为TCC框架本身要做到与具体的SOA框架无关,因此默认情况下,TCC的事务上下文都会作为事务方法的第一个参数显示传递,这样做的好处是通用性比较好,缺点就是对业务代码造成了侵入。实际上,某些SOA框架(比如dubbo)提供了非常良好的隐式传参的特性,因此事务上下文无需作为方法第一个参数了。为了TCC框架本身在保持框架无关性的同时,又能保证针对特定SOA的优化,所以对事务上下文抽象出了工厂,目前工厂的主要实现由:DubboTransactionContextEditor和MethodTransactionContextEditor,该属性可以在Compensable注解中指定,也就是在一次事务活动中,不同类型的事务参与者(比如基于dubbo的、基于http等)可以使用不同的事务上下文传递方式。
enter image description here

下面是Participant的构造方法,该构造方法会在ResourceCoordinatorInterceptor切面中被调用。

1
2
3
4
5
6
public Participant(TransactionXid xid, InvocationContext confirmInvocationContext, InvocationContext cancelInvocationContext, Class<? extends TransactionContextEditor> transactionContextEditorClass) {
this.xid = xid;
this.confirmInvocationContext = confirmInvocationContext;
this.cancelInvocationContext = cancelInvocationContext;
this.transactionContextEditorClass = transactionContextEditorClass;
}

由于Participant的commit和rollback方法执行逻辑基本相同,因此此处只以commit方法为例。

1
2
3
4
public void commit() {
// 会调用真正的commit方法(业务提供的)
terminator.invoke(new TransactionContext(xid, TransactionStatus.CONFIRMING.getId()), confirmInvocationContext, transactionContextEditorClass);
}

这里用到了上文提到的Terminator,看一下invoke方法源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
  public Object invoke(TransactionContext transactionContext, InvocationContext invocationContext, Class<? extends TransactionContextEditor> transactionContextEditorClass) {


if (StringUtils.isNotEmpty(invocationContext.getMethodName())) {

try {
// 获取targetClass单例
Object target = FactoryBuilder.factoryOf(invocationContext.getTargetClass()).getInstance();

Method method = null;

// 反射拿到真正方法
method = target.getClass().getMethod(invocationContext.getMethodName(), invocationContext.getParameterTypes());
// dubbo隐士传参或者方法显示传参
FactoryBuilder.factoryOf(transactionContextEditorClass).getInstance().set(transactionContext, target, method, invocationContext.getArgs());
// 反射调用真正的方法(本地或者远程)
return method.invoke(target, invocationContext.getArgs());

} catch (Exception e) {
throw new SystemException(e);
}
}
return null;
}

invoke需要使用事务上下文、调用上下文以及事务上下文工厂作为参数,具体的调用原理非常简单,使用调用上下文携带的信息,先反射拿到要调用的方法,然后执行调用。同时,这里也可以清晰看到使用事务上下文工厂的好处。

Read More

2019-08-02
10001分布式事务原理

分布式事务原理

一个TCC事务框架需要解决的当然是分布式事务的管理。关于TCC事务机制的介绍,可以参考TCC事务机制简介。
TCC事务模型虽然说起来简单,然而要基于TCC实现一个通用的分布式事务框架,却比它看上去要复杂的多,不只是简单的调用一下Confirm/Cancel业务就可以了的。

本文将以Spring容器为例,试图分析一下,实现一个通用的TCC分布式事务框架需要注意的一些问题。

一、TCC全局事务必须基于RM本地事务来实现全局事务

TCC服务是由Try/Confirm/Cancel业务构成的,
其Try/Confirm/Cancel业务在执行时,会访问资源管理器(Resource Manager,下文简称RM)来存取数据。这些存取操作,必须要参与RM本地事务,以使其更改的数据要么都commit,要么都rollback。

这一点不难理解,考虑一下如下场景:

image

假设图中的服务B没有基于RM本地事务(以RDBS为例,可通过设置auto-commit为true来模拟),那么一旦[B:Try]操作中途执行失败,TCC事务框架后续决定回滚全局事务时,该[B:Cancel]则需要判断[B:Try]中哪些操作已经写到DB、哪些操作还没有写到DB:假设[B:Try]业务有5个写库操作,[B:Cancel]业务则需要逐个判断这5个操作是否生效,并将生效的操作执行反向操作。
不幸的是,由于[B:Cancel]业务也有n(0<=n<=5)个反向的写库操作,此时一旦[B:Cancel]也中途出错,则后续的[B:Cancel]执行任务更加繁重。因为,相比第一次[B:Cancel]操作,后续的[B:Cancel]操作还需要判断先前的[B:Cancel]操作的n(0<=n<=5)个写库中哪几个已经执行、哪几个还没有执行,这就涉及到了幂等性问题。而对幂等性的保障,又很可能还需要涉及额外的写库操作,该写库操作又会因为没有RM本地事务的支持而存在类似问题。。。可想而知,如果不基于RM本地事务,TCC事务框架是无法有效的管理TCC全局事务的。
反之,基于RM本地事务的TCC事务,这种情况则会很容易处理:[B:Try]操作中途执行失败,TCC事务框架将其参与RM本地事务直接rollback即可。后续TCC事务框架决定回滚全局事务时,在知道“[B:Try]操作涉及的RM本地事务已经rollback”的情况下,根本无需执行[B:Cancel]操作。

换句话说,基于RM本地事务实现TCC事务框架时,一个TCC型服务的cancel业务要么执行,要么不执行,不需要考虑部分执行的情况。

二、TCC事务框架应该接管Spring容器的TransactionManager

基于RM本地事务的TCC事务框架,可以将各Try/Confirm/Cancel业务看着一个原子服务:一个RM本地事务提交,参与该RM本地事务的所有Try/Confirm/Cancel业务操作都生效;反之,则都不生效。掌握每个RM本地事务的状态以及它们与Try/Confirm/Cancel业务方法之间的对应关系,以此为基础,TCC事务框架才能有效的构建TCC全局事务。

TCC服务的Try/Confirm/Cancel业务方法在RM上的数据存取操作,其RM本地事务是由Spring容器的PlatformTransactionManager来commit/rollback的,TCC事务框架想要了解RM本地事务的状态,只能通过接管Spring的事务管理器功能。

2.1. 为什么TCC事务框架需要掌握RM本地事务的状态?
首先,根据TCC机制的定义,TCC事务是通过执行Cancel业务来达到回滚效果的。仔细分析一下,这里暗含一个事实:
只有生效的Try业务操作才需要执行对应的Cancel业务操作。换句话说,只有Try业务操作所参与的RM本地事务被commit了,后续TCC全局事务回滚时才需要执行其对应的Cancel业务操作;否则,如果Try业务操作所参与的RM本地事务被rollback了,后续TCC全局事务回滚时就不能执行其Cancel业务,此时若盲目执行Cancel业务反而会导致数据不一致。

其次,Confirm/Cancel业务操作必须保证生效。Confirm/Cancel业务操作也会涉及RM数据存取操作,其参与的RM本地事务也必须被commit。TCC事务框架需要在确切的知道所有Confirm/Cancel业务操作参与的RM本地事务都被成功commit后,才能将标记该TCC全局事务为完成。如果TCC事务框架误判了Confirm/Cancel业务参与RM本地事务的状态,就会造成全局事务不一致。

最后,未完成的TCC全局,TCC事务框架必须重新尝试提交/回滚操作。重试时会再次调用各TCC服务的Confirm/Cancel业务操作。如果某个服务的Confirm/Cancel业务之前已经生效(其参与的RM本地事务已经提交),重试时就不应该再次被调用。否则,其Confirm/Cancel业务被多次调用,就会有“服务幂等性”的问题。

2.2. 拦截TCC服务的Try/Confirm/Cancel业务方法的执行,根据其异常信息可否知道其RM本地事务是否commit/rollback了呢?

基本上很难做到。为什么这么说呢?
第一,事务是可以在多个(本地/远程)服务之间互相传播其事务上下文的,一个业务方法(Try/Confirm/Cancel)执行完毕并不一定会触发当前事务的commit/rollback操作。比如,被传播事务上下文的业务方法,在它开始执行时,容器并不会为其创建新的事务,而是它的调用方参与的事务,使得二者操作在同一个事务中;同样,在它执行完毕时,容器也不会提交/回滚它参与的事务的。因此,这类业务方法上的异常情况并不能反映他们是否生效。不接管Spring的TransactionManager,就无法了解事务于何时被创建,也无法了解它于何时被提交/回滚。
第二、一个业务方法可能会包含多个RM本地事务的情况。比如: A(REQUIRED)->B(REQUIRES_NEW)->C(REQUIRED),这种情况下,A服务所参与的RM本地事务被提交时,B服务和C服务参与的RM本地事务则可能会被回滚。
第三、并不是抛出了异常的业务方法,其参与的事务就回滚了。Spring容器的声明式事务定义了两类异常,其事务完成方向都不一样:系统异常(一般为Unchecked异常,默认事务完成方向是rollback)、应用异常(一般为Checked异常,默认事务完成方向是commit)。二者的事务完成方向又可以通过@Transactional配置显式的指定,如rollbackFor/noRollbackFor等。
第四、Spring容器还支持使用setRollbackOnly的方式显式的控制事务完成方向;
最后、自行拦截业务方法的拦截器和Spring的事务处理的拦截器还会存在执行先后、拦截范围不同等问题。例如,如果自行拦截器执行在前,就会出现业务方法虽然已经执行完毕但此时其参与的RM本地事务还没有commit/rollback。

TCC事务框架的定位应该是一个TransactionManager,其职责是负责commit/rollback事务。而一个事务应该commit、还是rollback,则应该是由Spring容器来决定的:Spring决定提交事务时,会调用TransactionManager来完成commit操作;Spring决定回滚事务时,会调用TransactionManager来完成rollback操作。

接管Spring容器的TransactionManager,TCC事务框架可以明确的得到Spring的事务性指令,并管理Spring容器中各服务的RM本地事务。否则,如果通过自行拦截的机制,则使得业务系统存在TCC事务处理、RM本地事务处理两套事务处理逻辑,二者互不通信,各行其是。这种情况下要协调TCC全局事务,基本上可以说是缘木求鱼,本地事务尚且无法管理,更何谈管理分布式事务?

三、TCC事务框架应该具备故障恢复机制

一个TCC事务框架,若是没有故障恢复的保障,是不成其为分布式事务框架的。

分布式事务管理框架的职责,不是做出全局事务提交/回滚的指令,而是管理全局事务提交/回滚的过程。它需要能够协调多个RM资源、多个节点的分支事务,保证它们按全局事务的完成方向各自完成自己的分支事务。这一点,是不容易做到的。因为,实际应用中,会有各种故障出现,很多都会造成事务的中断,从而使得统一提交/回滚全局事务的目标不能达到,甚至出现”一部分分支事务已经提交,而另一部分分支事务则已回滚”的情况。比较常见的故障,比如:业务系统服务器宕机、重启;数据库服务器宕机、重启;网络故障;断电等。这些故障可能单独发生,也可能会同时发生。作为分布式事务框架,应该具备相应的故障恢复机制,无视这些故障的影响是不负责任的做法。

一个完整的分布式事务框架,应该保障即使在最严苛的条件下也能保证全局事务的一致性,而不是只能在最理想的环境下才能提供这种保障。退一步说,如果能有所谓“理想的环境”,那也无需使用分布式事务了。

TCC事务框架要支持故障恢复,就必须记录相应的事务日志。事务日志是故障恢复的基础和前提,它记录了事务的各项数据。TCC事务框架做故障恢复时,可以根据事务日志的数据将中断的事务恢复至正确的状态,并在此基础上继续执行先前未完成的提交/回滚操作。

四、TCC事务框架应该提供Confirm/Cancel服务的幂等性保障

一般认为,服务的幂等性,是指针对同一个服务的多次(n>1)请求和对它的单次(n=1)请求,二者具有相同的副作用。

在TCC事务模型中,Confirm/Cancel业务可能会被重复调用,其原因很多。比如,全局事务在提交/回滚时会调用各TCC服务的Confirm/Cancel业务逻辑。执行这些Confirm/Cancel业务时,可能会出现如网络中断的故障而使得全局事务不能完成。因此,故障恢复机制后续仍然会重新提交/回滚这些未完成的全局事务,这样就会再次调用参与该全局事务的各TCC服务的Confirm/Cancel业务逻辑。

既然Confirm/Cancel业务可能会被多次调用,就需要保障其幂等性。
那么,应该由TCC事务框架来提供幂等性保障?还是应该由业务系统自行来保障幂等性呢?
个人认为,应该是由TCC事务框架来提供幂等性保障。如果仅仅只是极个别服务存在这个问题的话,那么由业务系统来负责也是可以的;然而,这是一类公共问题,毫无疑问,所有TCC服务的Confirm/Cancel业务存在幂等性问题。TCC服务的公共问题应该由TCC事务框架来解决;而且,考虑一下由业务系统来负责幂等性需要考虑的问题,就会发现,这无疑增大了业务系统的复杂度。

五、TCC事务框架不能盲目的依赖Cancel业务来回滚事务

前文以及提到过,TCC事务通过Cancel业务来对Try业务进行回撤的机制暗含了一个事实:Try操作已经生效。也就是说,只有Try操作所参与的RM本地事务已经提交的情况下,才需要执行其Cancel操作进行回撤。没有执行、或者执行了但是其RM本地事务被rollback的Try业务,是一定不能执行其Cancel业务进行回撤的。因此,TCC事务框架在全局事务回滚时,应该根据TCC服务的Try业务的执行情况选择合适的处理机制。而不能盲目的执行Cancel业务,否则就会导致数据不一致。

一个TCC服务的Try操作是否生效,这是TCC事务框架应该知道的,因为其Try业务所参与的RM事务也是由TCC事务框架所commit/rollbac的(前提是TCC事务框架接管了Spring的事务管理器)。所以,TCC事务回滚时,TCC事务框架可考虑如下处理策略:
1)如果TCC事务框架发现某个服务的Try操作的本地事务尚未提交,应该直接将其回滚,而后就不必再执行该服务的cancel业务;
2)如果TCC事务框架发现某个服务的Try操作的本地事务已经回滚,则不必再执行该服务的cancel业务;
3)如果TCC事务框架发现某个服务的Try操作尚未被执行过,那么,也不必再执行该服务的cancel业务。

总之,TCC事务框架应该保障:
1)已生效的Try操作应该被其Cancel操作所回撤;
2)尚未生效的Try操作,则不应该执行其Cancel操作。这一点,不是幂等性所能解决的问题。如上文所述,幂等性是指服务被执行一次和被执行n(n>0)次所产生的影响相同。但是,未被执行和被执行过,二者效果肯定是不一样的,这不属于幂等性的范畴。

六、Cancel业务与Try业务并行,甚至先于Try操作完成

这应该算TCC事务机制特有的一个不可思议的陷阱。一般来说,一个特定的TCC服务,其Try操作的执行,是应该在其Confirm/Cancel操作之前的。Try操作执行完毕之后,Spring容器再根据Try操作的执行情况,指示TCC事务框架提交/回滚全局事务。然后,TCC事务框架再去逐个调用各TCC服务的Confirm/Cancel操作。

然而,超时、网络故障、服务器的重启等故障的存在,使得这个顺序会被打乱。比如:

image

上图中,假设[B:Try]操作执行过程中,网络闪断,[A:Try]会收到一个RPC远程调用异常。A不处理该异常,导致全局事务决定回滚,TCC事务框架就会去调用[B:Cancel],而此刻A、B之间网络刚好已经恢复。如果[B:Try]操作耗时较长(网络阻塞/数据库操作阻塞),就会出现[B:Try]和[B:Cancel]二者并行处理的现象,甚至[B:Cancel]先完成的现象。
这种情况下,由于[B:Cancel]执行时,[B:Try]尚未生效(其RM本地事务尚未提交),因此,[B:Cancel]是不能执行的,至少是不能生效(执行了其RM本地事务也要rollback)的。然而,当
[B:Cancel]处理完毕(跳过执行、或者执行后rollback其RM本地事务)后,[B:Try]操作完成又生效了(其RM本地事务成功提交),这就会使得[B:Cancel]虽然提供了,但却没有起到回撤[B:Try]的作用,导致数据的不一致。

所以,TCC框架在这种情况下,需要:
1)将[B:Try]的本地事务标注为rollbackOnly,阻止其后续生效;
2)禁止其再次将事务上下文传递给其他远程分支,否则该问题将在其他分支上出现;
3)相应地,[B:Cancel]也不必执行,至少不能生效。

当然,TCC事务框架也可以简单的选择阻塞[B:Cancel]的处理,待[B:Try]执行完毕后,再根据它的执行情况判断是否需要执行[B:Cancel]。不过,这种处理方式因为需要等待,所以,处理效率上会有所不及。
同样的情况也会出现在confirm业务上,只不过,发生在Confirm业务上的处理逻辑与发生在Cancel业务上的处理逻辑会不一样,TCC框架必须保证:
1)Confirm业务在Try业务之后执行,若发现并行,则只能阻塞相应的Confirm业务操作;
2)在进入Confirm执行阶段之后,也不可以再提交同一全局事务内的新的Try操作的RM本地事务。

七、TCC服务复用性是不是相对较差?

TCC事务机制的定义,决定了一个服务需要提供三个业务实现:Try业务、Confirm业务、Cancel业务。可能会有人因此认为TCC服务的复用性较差。怎么说呢,要是将 Try/Confirm/Cancel业务逻辑单独拿出来复用,其复用性当然是不好的,Try/Confirm/Cancel 逻辑作为TCC型服务中的一部分,是不能单独作为一个组件来复用的。Try、Confirm、Cancel业务共同才构成一个组件,如果要复用,应该是复用整个TCC服务组件,而不是单独的Try/Confirm/Cancel业务。

八、TCC服务是否需要对外暴露三个服务接口?

不需要。TCC服务与普通的服务一样,只需要暴露一个接口,也就是它的Try业务。Confirm/Cancel业务逻辑,只是因为全局事务提交/回滚的需要才提供的,因此Confirm/Cancel业务只需要被TCC事务框架发现即可,不需要被调用它的其他业务服务所感知。

换句话说,业务系统的其他服务在需要调用TCC服务时,根本不需要知道它是否为TCC型服务。因为,TCC服务能被其他业务服务调用的也仅仅是其Try业务,Confirm/Cancel业务是不能被其他业务服务直接调用的。

九、TCC服务A的Confirm/Cancel业务中能否调用它依赖的TCC服务B的Confirm/Cancel业务?

最好是不要这样做。首先,没有必要。TCC服务A依赖TCC服务B,那么[A:Try]已经将事务上下文传播给[B:Try]了,后续由TCC事务框架来调用各自的Confirm/Cancel业务即可;其次,Confirm/Cancel业务如果被允许调用其他服务,那么它就有可能再次发起新的TCC全局事务。如此递归下去,将会导致全局事务关系混乱且不可控。

TCC全局事务,应该尽量在Try操作阶段传播事务上下文。Confirm/Cancel操作阶段仅需要完成各自Try业务操作的确认操作/补偿操作即可,不适合再做远程调用,更不能

Blockquote

再对外传播事务上下文。

综上所述,本文倾向于认为,实现一个通用的TCC分布式事务管理框架,还是相对比较复杂的。一般业务系统如果需要使用TCC事务机制,并不推荐自行设计实现。
这里,给大家推荐一款开源的TCC分布式事务管理器ByteTCC。ByteTCC基于Try/Confirm/Cancel机制实现,可与Spring容器无缝集成,兼容Spring的声明式事务管理。提供对dubbo框架、Spring Cloud的开箱即用的支持,可满足多数据源、跨应用、跨服务器等各种分布式事务场景的需求。

Read More

2019-07-02
10012 CentOS 7上安装Nginx

第一步 - 添加Nginx存储库

要添加CentOS 7 EPEL仓库,请打开终端并使用以下命令:

1
sudo yum install epel-release

第二步 - 安装Nginx

现在Nginx存储库已经安装在您的服务器上,使用以下yum命令安装Nginx :

1
sudo yum install nginx

在对提示回答yes后,Nginx将在服务器上完成安装。

第三步 - 启动Nginx

Nginx不会自行启动。要运行Nginx,请输入:

1
sudo systemctl start nginx

如果您正在运行防火墙,请运行以下命令以允许HTTP和HTTPS通信:

1
2
3
sudo firewall-cmd --permanent --zone=public --add-service=http 
sudo firewall-cmd --permanent --zone=public --add-service=https
sudo firewall-cmd --reload

您将会看到默认的CentOS 7 Nginx网页,这是为了提供信息和测试目的。

它应该看起来像这样:

CentOS 7 Nginx默认

如果看到这个页面,那么你的Web服务器现在已经正确安装了。

如果想在系统启动时启用Nginx。请输入以下命令:

1
sudo systemctl enable nginx

恭喜!Nginx现在已经安装并运行了!

Read More

2019-07-02
10011 CentOS7 通过YUM安装MySQL5.7

Centos7.3安装和配置Mysql5.7

第一步:获取mysql YUM源

进入mysql官网获取RPM包下载地址

https://dev.mysql.com/downloads/repo/yum/

img

点击 下载

img

右击 复制链接地址 https://dev.mysql.com/get/mysql57-community-release-el7-11.noarch.rpm

得到这个 这个就是Yum仓库的rpm包 其实就是一个下载地址

第二步:下载和安装mysql源

先下载 mysql源安装包

[root@localhost ~]# wget https://dev.mysql.com/get/mysql57-community-release-el7-11.noarch.rpm

-bash: wget: 未找到命令

我们先安装下wget

yum -y install wget

然后执行 wget https://dev.mysql.com/get/mysql57-community-release-el7-11.noarch.rpm

安装mysql源

yum -y localinstall mysql57-community-release-el7-11.noarch.rpm

第三步:在线安装Mysql

yum -y install mysql-community-server

下载的东西比较多 要稍微等会;

第四步:启动Mysql服务

systemctl start mysqld

第五步:设置开机启动

[root@localhost ~]# systemctl enable mysqld

[root@localhost ~]# systemctl daemon-reload

第六步:修改root本地登录密码

mysql安装完成之后,在/var/log/mysqld.log文件中给root生成了一个临时的默认密码。

[root@localhost ~]# vi /var/log/mysqld.log

img

这里的临时密码 eMV.R#mWe3ha

[root@localhost ~]# mysql -u root -p

Enter password:

输入临时密码 进入mysql命令行;

mysql> ALTER USER ‘root‘@’localhost’ IDENTIFIED BY ‘ZhipengWang2012@’;

Query OK, 0 rows affected (0.00 sec)

修改密码为 ZhipengWang2012@ (备注 mysql5.7默认密码策略要求密码必须是大小写字母数字特殊字母的组合,至少8位)

第七步:设置允许远程登录

Mysql默认不允许远程登录,我们需要设置下,并且防火墙开放3306端口;

mysql> GRANT ALL PRIVILEGES ON . TO ‘root‘@’%’ IDENTIFIED BY ‘ZhipengWang2012@’ WITH GRANT OPTION;

Query OK, 0 rows affected, 1 warning (0.01 sec)

mysql> exit;

Bye

退出下;

[root@localhost ~]# firewall-cmd –zone=public –add-port=3306/tcp –permanent

success

[root@localhost ~]# firewall-cmd –reload

success

[root@localhost ~]#

开放3306端口

第八步:配置默认编码为utf8

修改/etc/my.cnf配置文件,在[mysqld]下添加编码配置,如下所示:

[mysqld]

character_set_server=utf8

init_connect=’SET NAMES utf8’

[root@localhost ~]# vi /etc/my.cnf

img

编辑保存完 重启mysql服务;

[root@localhost ~]# systemctl restart mysqld

[root@localhost ~]#

查看下编码:

mysql> show variables like ‘%character%’;

+————————–+—————————-+

| Variable_name | Value |

+————————–+—————————-+

| character_set_client | utf8 |

| character_set_connection | utf8 |

| character_set_database | utf8 |

| character_set_filesystem | binary |

| character_set_results | utf8 |

| character_set_server | utf8 |

| character_set_system | utf8 |

| character_sets_dir | /usr/share/mysql/charsets/ |

+————————–+—————————-+

8 rows in set (0.00 sec)

第九步:测试

我们用本机的sqlyog远程连接下虚拟机里的mysql

img

img

OK 至此 Mysql安装配置完毕;

Read More

2019-07-02
10010 CentOS7 通过YUM安装MySQL5.7

CentOS7 通过YUM安装MySQL5.7

1.进入到要存放安装包的位置

1
cd /home/lnmp

2.查看系统中是否已安装 MySQL 服务,以下提供两种方式:

1
2
rpm -qa | grep mysql
yum list installed | grep mysql

3.如果已安装则删除 MySQL 及其依赖的包:

1
yum -y remove mysql-libs.x86_64

4.下载 mysql57-community-release-el7-8.noarch.rpm 的 YUM 源: (略)

1
wget http://repo.mysql.com/mysql57-community-release-el7-8.noarch.rpm

5.安装 mysql57-community-release-el7-8.noarch.rpm:略)

1
rpm -ivh mysql57-community-release-el7-8.noarch.rpm

安装完后,得到如下两个包:

mysql-community.repo
mysql-community-source.repo

6.安装 MySQL,出现提示的话,一路 Y 到底

1
yum install mysql-server

安装完毕后,运行mysql,然后在 /var/log/mysqld.log 文件中会自动生成一个随机的密码,我们需要先取得这个随机密码,以用于登录 MySQL 服务端:

1
2
service mysqld start
grep "password" /var/log/mysqld.log

将会返回如下内容,末尾字符串就是密码,把它复制下来:

1
A temporary password is generated for root@localhost: hilX0U!9i3_6

7.登录到 MySQL 服务端并更新用户 root 的密码:

注意:由于 MySQL5.7 采用了密码强度验证插件 validate_password,故此我们需要设置一个有一定强度的密码;

1
2
mysql -u root -p
hilX0U!9i3_6

然后更改密码

1
2
3
SET PASSWORD = PASSWORD('your new password');
ALTER USER 'root'@'localhost' PASSWORD EXPIRE NEVER;
flush privileges;

设置用户 root 可以在任意 IP 下被访问:

1
grant all privileges on *.* to root@"%" identified by "new password";

设置用户 root 可以在本地被访问:

1
grant all privileges on *.* to root@"localhost" identified by "new password";

刷新权限使之生效:

1
flush privileges;

OK,输入 exit 后用新密码再次登录看看吧!

注意:如果用远程工具还是连接不上,试试用 iptables -F 命令来清除防火墙中链中的规则

8.MySQL控制命令:启动、停止、重启、查看状态

1
2
3
4
5
6
7
8
9
service mysqld start
service mysqld stop
service mysqld restart
service mysqld status

systemctl start mysqld
service mysqld stop
service mysqld restart
systemctl status mysqld

9.设置 MySQL 的字符集为 UTF-8:

打开 /etc 目录下的 my.cnf 文件(此文件是 MySQL 的主配置文件):

1
vim /etc/my.cnf

在 [mysqld] 前添加如下代码:

1
2
[client]
default-character-set=utf8

在 [mysqld] 后添加如下代码:

1
character_set_server=utf8

再登录mysql,看看字符集,6个utf8就算OK

1
show variables like '%character%';

10.查看指定的数据库中指定数据表的字符集,如查看 mysql 数据库中 servers 表的字符集:

1
show table status from mysql like '%servers%';

查看指定数据库中指定表的全部列的字符集,如查看 mysql 数据库中 servers 表的全部的列的字符集:

1
show full columns from servers;

\11. 忘记密码时,可用如下方法重置:

1
2
3
service mysqld stop
mysqld_safe --user=root --skip-grant-tables --skip-networking &
mysql -u root

进入MySQL后

1
2
3
use mysql;
update user set password=password("new_password") where user="root";
flush privileges;

12.一些文件的存放目录

配置文件

1
vim /etc/my.cnf

存放数据库文件的目录

1
cd /var/lib/mysql

日志记录文件

1
vim /var/log/ mysqld.log

服务启动脚本

1
/usr/lib/systemd/system/mysqld.service

socket文件

1
/var/run/mysqld/mysqld.pid

13.MySQL 采用的 TCP/IP 协议传输数据,默认端口号为 3306,我们可以通过如下命令查看:

1
netstat -anp
Read More

2019-07-02
10009 jdk环境搭建

卸载

查看已经安装的jdk

1
2
3
4
5
[root@bogon jre]# rpm -qa|grep jdk
java-1.8.0-openjdk-headless-1.8.0.65-3.b17.el7.x86_64
java-1.7.0-openjdk-1.7.0.91-2.6.2.3.el7.x86_64
java-1.7.0-openjdk-headless-1.7.0.91-2.6.2.3.el7.x86_64
java-1.8.0-openjdk-1.8.0.65-3.b17.el7.x86_6412345

卸载命令

1
[root@bogon jre]# yum -y remove java-1.8.0-openjdk-headless-1.8.0.65-3.b17.el7.x86_641

卸载完成之后Java命令不被识别

1
2
[root@bogon lib]# java -version
bash: java: command not found...12

安装

去官网下载jdk:http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html
解压到安装目录

1
[root@bogon software]# tar -zxvf jdk-8u101-linux-x64.tar.gz -C /usr/local/java/

安装完毕之后在/etc/profile文件末尾添加

1
2
3
4
5
[root@bogon software]# vim /etc/profile
export JAVA_HOME=/usr/local/java/jdk1.8.0_181
export JRE_HOME=${JAVA_HOME}/jre
export CLASSPATH=.:${JAVA_HOME}/lib:${JRE_HOME}/lib
export PATH=${JAVA_HOME}/bin:$PATH

使/etc/profile生效

1
[root@bogon jdk1.8.0_101]# source /etc/profile

检测安装是否成功

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
[root@bogon jdk1.8.0_101]# java -version
java version "1.8.0_101"
Java(TM) SE Runtime Environment (build 1.8.0_101-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.101-b13, mixed mode)
[root@bogon jdk1.8.0_101]# javac
Usage: javac <options> <source files>
where possible options include:
-g Generate all debugging info
-g:none Generate no debugging info
-g:{lines,vars,source} Generate only some debugging info
-nowarn Generate no warnings
-verbose Output messages about what the compiler is doing
-deprecation Output source locations where deprecated APIs are used
-classpath <path> Specify where to find user class files and annotation processors
-cp <path> Specify where to find user class files and annotation processors
-sourcepath <path> Specify where to find input source files
-bootclasspath <path> Override location of bootstrap class files
-extdirs <dirs> Override location of installed extensions
-endorseddirs <dirs> Override location of endorsed standards path
-proc:{none,only} Control whether annotation processing and/or compilation is done.
-processor <class1>[,<class2>,<class3>...] Names of the annotation processors to run; bypasses default discovery process
-processorpath <path> Specify where to find annotation processors
-parameters Generate metadata for reflection on method parameters
-d <directory> Specify where to place generated class files
-s <directory> Specify where to place generated source files
-h <directory> Specify where to place generated native header files
-implicit:{none,class} Specify whether or not to generate class files for implicitly referenced files
-encoding <encoding> Specify character encoding used by source files
-source <release> Provide source compatibility with specified release
-target <release> Generate class files for specific VM version
-profile <profile> Check that API used is available in the specified profile
-version Version information
-help Print a synopsis of standard options
-Akey[=value] Options to pass to annotation processors
-X Print a synopsis of nonstandard options
-J<flag> Pass <flag> directly to the runtime system
-Werror Terminate compilation if warnings occur
@<filename> Read options and filenames from file

https://www.cnblogs.com/zhongkaiuu/p/BC.html
问题:JCE cannot authenticate the provider BC

1
2
3
1. 找到 java.security 在jvm安装地方 /path_to_your_jvm/jre/lib/security
security.provider.9=org.bouncycastle.jce.provider.BouncyCastleProvider
3. 添加 bcprov-jdk16-146.jar到 /path_to_your_jvm/jre/lib/ext (提供maven的地址)

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
[webadmin@wenhuatest2250 security]$ pwd
/usr/local/jdk1.6.0_45/jre/lib/security
[webadmin@wenhuatest2250 security]$ ll
total 116
-rw-r--r--. 1 webadmin webadmin 2177 Mar 27 2013 blacklist
-rw-r--r--. 1 webadmin webadmin 81202 Mar 27 2013 cacerts
-rw-r--r--. 1 webadmin webadmin 2253 Mar 27 2013 java.policy
-rw-r--r--. 1 webadmin webadmin 13481 Jul 31 18:15 java.security
-rw-r--r--. 1 webadmin webadmin 109 Mar 27 2013 javaws.policy
-rw-r--r--. 1 webadmin webadmin 2940 Mar 27 2013 local_policy.jar
-rw-r--r--. 1 webadmin webadmin 0 Mar 27 2013 trusted.libraries
-rw-r--r--. 1 webadmin webadmin 2469 Mar 27 2013 US_export_policy.jar
[webadmin@wenhuatest2250 security]$ stat java.security
File: ‘java.security’
Size: 13481 Blocks: 32 IO Block: 4096 regular file
Device: fd01h/64769d Inode: 6508959 Links: 1
Access: (0644/-rw-r--r--) Uid: ( 1000/webadmin) Gid: ( 1000/webadmin)
Context: unconfined_u:object_r:usr_t:s0
Access: 2019-08-22 09:15:39.110675245 +0800
Modify: 2019-07-31 18:15:20.880153567 +0800
Change: 2019-07-31 18:15:20.904165564 +0800
Birth: -

[webadmin@wenhuatest2250 ext]$ pwd
/usr/local/jdk1.6.0_45/jre/lib/ext
[webadmin@wenhuatest2250 ext]$ ll
total 3192
-rw-r--r--. 1 webadmin webadmin 1997327 Jul 31 17:43 bcprov-jdk15on-1.47.jar
-rw-r--r--. 1 webadmin webadmin 8238 Mar 27 2013 dnsns.jar
-rw-r--r--. 1 webadmin webadmin 845352 Jul 8 16:36 localedata.jar
-rw-r--r--. 1 webadmin webadmin 429 Mar 27 2013 meta-index
-rw-r--r--. 1 webadmin webadmin 170315 Mar 27 2013 sunjce_provider.jar
-rw-r--r--. 1 webadmin webadmin 232338 Mar 27 2013 sunpkcs11.jar
Read More

2019-07-02
10009.2 安装nginx

目录

1.概览
2.详情
–2.1安装依赖
—-2.1.1安装gcc
—-2.1.2安装pcre
—-2.1.3安装libstdc++(gcc-c++依赖)
—-2.1.4安装gcc-c++
—-2.1.5安装zlib
–2.2 安装nginx
–2.3 常用命令
–2.4 关闭Nginx代理服务器防火墙
–2.5 端口验证
–2.6 自启动设置

1.概览

服务器无法访问外网,nginx只能离线装,步骤如下:

离线状态,可先联网下载需要的依赖,需要的依赖包括:gcc、pcre、libstdc++、libstdc++-devel、gcc-c++、zlib,下载地址https://pkgs.org,选择对应的系统版本下载。与openssl

注意:

  • 1.统一使用rpm格式;
  • 2.各种依赖版本务必统一;
  • 3.服务器如果在安装时没有点选一些模块,可能需要下载的东西较多,在服务器性能允许的情况下,建议全选安装
  • 4.服务器上已有的一些依赖,版本通常较低,rpm安装时可使用 --force 强制升级安装。

2.详情

2.1安装依赖

2.1.1安装gcc

1
rpm -ivh gcc-4.8.5-36.el7.x86_64.rpm

2.1.2安装pcre

1
2
3
4
# 由于机器上已经有低版本的pcre,所以强制安装
rpm -ivh pcre-8.32-17.el7.x86_64.rpm --force

rpm -ivh pcre-devel-8.32-17.el7.x86_64.rpm --force

2.1.3安装libstdc++(gcc-c++依赖)

1
2
rpm -ivh libstdc++-4.8.5-36.el7.x86_64.rpm --force
rpm -ivh libstdc++-devel-4.8.5-36.el7.x86_64.rpm --force

2.1.4安装gcc-c++

1
rpm -ivh gcc-c++-4.4.7-4.el6.x86_64.rpm --force

2.1.5安装zlib

1
2
3
rpm -ivh zlib-1.2.3-29.el6.x86_64.rpm

rpm -ivh zlib-devel-1.2.3-29.el6.x86_64.rpm

2.2 安装nginx

Nginx官网下载最新Stable版本,本次下载安装版本为1.41.2(20190417)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 下载解压

tar -xzvf nginx-1.14.2.tar.gz

# 移动源代码到对应目录下
sudo mv /path/to/nginx-1.14.2 /usr/local/
cd /usr/local/nginx-1.14.2
#
./configure --prefix=/usr/local/nginx --with-zlib=/home/webadmin/nginx-1.8.1/auto/lib/zlib --with-pcre=/home/webadmin/nginx-1.8.1/auto/lib/pcre

# 编译
make
#安装
make install

# 运行nginx
cd /usr/local/nginx/sbin
./nginx

8.验证

检查nginx.conf配置文件是否正确。

1
/usr/local/nginx/sbin/nginx  -t

正确之后,浏览器打开localhost:8080,如果可访问 welcome to nginx 页面,表示配置完成。

2.3 常用命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 通过help查看常用命令
/usr/local/nginx/sbin/nginx -h

Options:
-?,-h : this help
-v : show version and exit
-V : show version and configure options then exit
-t : test configuration and exit
-T : test configuration, dump it and exit
-q : suppress non-error messages during configuration testing
-s signal : send signal to a master process: stop, quit, reopen, reload
-p prefix : set prefix path (default: /usr/local/nginx/)
-c filename : set configuration file (default: conf/nginx.conf)
-g directives : set global directives out of configuration file
1
2
3
4
5
6
7
./nginx -v  显示nginx的版本号
./nginx -V 显示nginx的版本号和编译信息
./nginx -t 检查nginx配置文件的正确性
./nginx -t 检查nginx配置文件的正确定及配置文件的详细配置内容
./nginx -s 向主进程发送信号,如:./nginx -s reload 配置文件变化后重新加载配置文件并重启nginx服务
./nginx -p 设置nginx的安装路径
./nginx -c 设置nginx配置文件的路径

2.4 关闭Nginx代理服务器防火墙

1
2
3
4
5
6
7
8
# 临时关闭
systemctl stop firewalld.service

# 禁止开机启动
systemctl disable firewalld

# 查看防火墙状态
systemctl status firewalld

2.5 端口验证

确保映射需要的端口到外网,如80、8080、8081

2.6 自启动设置

1、在系统服务目录里创建nginx.service文件

1
vim  /usr/lib/systemd/system/nginx.service

2、写入内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
[Unit]
Description=nginx
After=network.target

[Service]
Type=forking
ExecStart=/usr/local/nginx/sbin/nginx
ExecReload=/usr/local/nginx/sbin/nginx -s reload
ExecStop=/usr/local/nginx/sbin/nginx -s quit
PrivateTmp=true

[Install]
WantedBy=multi-user.target

3、设置开机自启动

1
systemctl enable nginx.service

4、查看Nginx状态

1
systemctl status nginx.service

很奇怪,明明启动成功了,为什么显示Active: inactive (dead)?

5、杀死Nginx重启Nginx

1
2
3
pkill  -9  nginx
ps aux | grep nginx
systemctl start nginx

再次查看状态,变成了active。

6、重启服务器测试效果

1
reboot

7、再次连接后,查看服务状态

1
systemctl  status  nginx.service

看到Nginx已经启动,至此,Nginx自启动配置成功。

PS: 批量安装 rpm -Uvh ./*.rpm –nodeps –force

Read More

2019-07-02
10009.1 安装nginx

[Centos下 Nginx安装与配置]

https://www.cnblogs.com/zhanghaoyong/p/7737536.html

https://blog.csdn.net/u012946310/article/details/85106653 –https

Nginx是一款轻量级的网页服务器、反向代理服务器。相较于Apache、lighttpd具有占有内存少,稳定性高等优势。它最常的用途是提供反向代理服务。

安装


在Centos下,yum源不提供nginx的安装,可以通过切换yum源的方法获取安装。也可以通过直接下载安装包的方法,以下命令均需root权限执行

首先安装必要的库(nginx 中gzip模块需要 zlib 库,rewrite模块需要 pcre 库,ssl 功能需要openssl库)。选定/usr/local为安装目录,以下具体版本号根据实际改变。

1.安装PCRE库

1
2
3
4
5
6
7
$ cd /usr/local/
$ wget ftp://ftp.csx.cam.ac.uk/pub/software/programming/pcre/pcre-8.36.tar.gz
$ tar -zxvf pcre-8.36.tar.gz
$ cd pcre-8.36
$ ./configure
$ make
$ make install

2.安装zlib库

1
2
3
4
5
6
7
$ cd /usr/local/ 
$ wget http://zlib.net/zlib-1.2.8.tar.gz
$ tar -zxvf zlib-1.2.8.tar.gz
$ cd zlib-1.2.8
$ ./configure
$ make
$ make install

3.安装ssl

1
2
3
4
5
6
$ cd /usr/local/
$ wget http://www.openssl.org/source/openssl-1.0.1j.tar.gz
$ tar -zxvf openssl-1.0.1j.tar.gz
$ ./config
$ make
$ make install

4.安装nginx

1
2
3
4
5
6
7
8
9
10
$ cd /usr/local/
$ wget http://nginx.org/download/nginx-1.8.0.tar.gz
$ tar -zxvf nginx-1.8.0.tar.gz
$ cd nginx-1.8.0
$ ./configure --prefix=/usr/local/nginx (用下面的)

(./configure --prefix=/usr/local/nginx --with-pcre=/usr/local/pcre-8.36 --with-zlib=/usr/local/zlib-1.2.8 --with-http_stub_status_module --with-http_ssl_module --with-openssl=/usr/local/openssl-1.0.1j)

$ make
$ make install

在–prefix后面接以下命令:

1
2
--with-pcre=/usr/local/pcre-8.36 指的是pcre-8.36 的源码路径。
--with-zlib=/usr/local/zlib-1.2.8 指的是zlib-1.2.8 的源码路径。

点击此处下载安装脚本

5.启动

1
$ /usr/local/nginx/sbin/nginx

检查是否启动成功:

打开浏览器访问此机器的 IP,如果浏览器出现 Welcome to nginx! 则表示 Nginx 已经安装并运行成功。

部分命令如下:

重启:
$ /usr/local/nginx/sbin/nginx -s reload

停止:
$ /usr/local/nginx/sbin/nginx -s stop

测试配置文件是否正常:
$ /usr/local/nginx/sbin/nginx -t

强制关闭:
$ pkill nginx

配置


以上安装方法nginx的配置文件位于

1
/usr/local/nginx/conf/nginx.conf

Nginx配置文件常见结构的从外到内依次是「http」「server」「location」等等,缺省的继承关系是从外到内,也就是说内层块会自动获取外层块的值作为缺省值。

Server

接收请求的服务器需要将不同的请求按规则转发到不同的后端服务器上,在 nginx 中我们可以通过构建虚拟主机(server)的概念来将这些不同的服务配置隔离。

1
2
3
4
5
6
server {
listen 80;
server_name localhost;
root html;
index index.html index.htm;
}

例如我们笔戈玩下的两个子项目 passport 和 wan 就可以通过在 nginx 的配置文件中配置两个 server,servername 分别为 passport.bigertech.com 和 wan.bigertech.com。这样的话不同的 url 请求就会对应到 nginx 相应的设置,转发到不同的后端服务器上。

这里的 listen 指监听端口,server_name 用来指定IP或域名,多个域名对应统一规则可以空格分开,index 用于设定访问的默认首页地址,root 指令用于指定虚拟主机的网页跟目录,这个地方可以是相对地址也可以是绝对地址。

通常情况下我们可以在 nginx.conf 中配置多个server,对不同的请求进行设置。就像这样:

1
2
3
4
5
6
7
8
9
10
11
12
server {
listen 80;
server_name host1;
root html;
index index.html index.htm;
}
server {
listen 80;
server_name host2;
root /data/www/html;
index index.html index.htm;
}

但是当 server 超过2个时,建议将不同对虚拟主机的配置放在另一个文件中,然后通过在主配置文件 nginx.conf 加上 include 指令包含进来。更便于管理。

1
include vhosts/*.conf;

就可以把vhosts的文件都包含进去啦。

Localtion

每个 url 请求都会对应的一个服务,nginx 进行处理转发或者是本地的一个文件路径,或者是其他服务器的一个服务路径。而这个路径的匹配是通过 location 来进行的。我们可以将 server 当做对应一个域名进行的配置,而 location 是在一个域名下对更精细的路径进行配置。

以上面的例子,可以将root和index指令放到一个location中,那么只有在匹配到这个location时才会访问root后的内容:

1
2
3
4
location / {
root /data/www/host2;
index index.html index.htm;
}

location 匹配规则

1
2
3
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
location  = / {
# 只匹配"/".
[ configuration A ]
}
location / {
# 匹配任何请求,因为所有请求都是以"/"开始
# 但是更长字符匹配或者正则表达式匹配会优先匹配
[ configuration B ]
}
location ^~ /images/ {
# 匹配任何以 /images/ 开始的请求,并停止匹配 其它location
[ configuration C ]
}
location ~* \.(gif|jpg|jpeg)$ {
# 匹配以 gif, jpg, or jpeg结尾的请求.
# 但是所有 /images/ 目录的请求将由 [Configuration C]处理.
[ configuration D ]
}

请求:
/ -> 符合configuration A
/documents/document.html -> 符合configuration B
/images/1.gif -> 符合configuration C
/documents/1.jpg ->符合 configuration D

静态文件映射

访问文件的配置主要有 root 和 aliasp’s 两个指令。这两个指令的区别容易弄混:

alias

alias后跟的指定目录是准确的,并且末尾必须加 /。

1
2
3
location /c/ {
alias /a/;
}

如果访问站点http://location/c访问的就是/a/目录下的站点信息。

root

root后跟的指定目录是上级目录,并且该上级目录下要含有和location后指定名称的同名目录才行。

1
2
3
location /c/ {
root /a/;
}

这时访问站点http://location/c访问的就是/a/c目录下的站点信息。

如果你需要将这个目录展开,在这个location的末尾加上「autoindex on; 」就可以了

转发

配置起来很简单比如我要将所有的请求到转移到真正提供服务的一台机器的 8001 端口,只要这样:

1
2
3
location / {
proxy_pass 172.16.1.1:8001;
}

这样访问host时,就都被转发到 172.16.1.1的8001端口去了。

负载均衡

1
2
3
4
5
6
7
8
9
10
upstream myserver; {
ip_hash;
server 172.16.1.1:8001;
server 172.16.1.2:8002;
server 172.16.1.3;
server 172.16.1.4;
}
location / {
proxy_pass http://myserver;
}

我们在 upstream 中指定了一组机器,并将这个组命名为 myserver,这样在 proxypass 中只要将请求转移到 myserver 这个 upstream 中我们就实现了在四台机器的反向代理加负载均衡。其中的 ip_hash 指明了我们均衡的方式是按照用户的 ip 地址进行分配。另外还有轮询、指定权重轮询、fair、url_hash几种调度算法。

总结


以上是最简单的通过 nginx 实现静态文件转发、反向代理和负载均衡的配置。在 nginx 中所有的功能都是通过模块来实现的,比如当我们配置 upstream 时是用 upstream 模块,而 server 和 location 是在 http core 模块,其他的还有流控的 limt 模块,邮件的 mail 模块,https 的 ssl 模块。他们的配置都是类似的可以再 nginx 的模块文档中找到详细的配置说明。

Read More

2019-07-02
10009.3 安装jboss7.1

jboss配置修改

1
2
3
4
5
6
7
8
9
10
11
<interfaces>
<interface name="management">
<inet-address value="${jboss.bind.address.management:0.0.0.0}"/>
</interface>
<interface name="public">
<inet-address value="${jboss.bind.address:172.17.22.50}"/>
</interface>
<interface name="unsecure">
<inet-address value="${jboss.bind.address.unsecure:172.17.22.50}"/>
</interface>
</interfaces>

jboss启动

1
LAUNCH_JBOSS_IN_BACKGROUND=1 JBOSS_PIDFILE=/usr/local/jboss-as-7.1.1.Final/standalone/jboss-standalone.pid  nohup sh /usr/local/jboss-as-7.1.1.Final/bin/standalone.sh &> /dev/null 2>&1&

jboss 暂停

1
/usr/local/jboss-as-7.1.1.Final/bin/jboss-cli.sh  --connect --command=:shutdown
Read More