该如何创建字符串,使用“ ”还是构造函数

实例一
String a = "abcd";
String b = "abcd";
System.out.println("a == b : "+(a == b)); // true
System.out.println("a.equals(b) : "+(a.equals(b))); // true

当相同的字符串常量被多次创建时,只会保存字符串常亮的一份副本,这称为“字符串驻留”。在Java中,所有编译时字符串常亮都是驻留的。

实例二
String c = new String("abcd");
String d = new String("abcd");
System.out.println("c == d : "+(c == d)); // false
System.out.println("c.equals(d) : "+(c.equals(d))); // true

运行时字符串驻留

String c = new String("abcd").intern();
String d = new String("abcd").intern();
System.out.println("c == d : "+(c == d)); // true
System.out.println("c.equals(d) : "+(c.equals(d))); // true    (JDK1.7)
2016/3/7 posted in  Java

分布式系统的事务处理

通常,我们会通过两种手段来扩展我们的数据服务:

  • 数据分区:把数据分块放在不同的服务器上
  • 数据镜像:让所有的服务器都有相同的数据,提供相当的服务

数据服务的高可用性只能通过第二种方法来完成--数据的冗余存储。但是,加入更多的机器,会让我们的数据服务变得很复杂,尤其是跨服务器的事务处理,也就是跨服务器的数据一致性。

数据一致性,简单说有三种类型:

  • Weak弱一致性:当你写入一个新值后,读操作在数据副本上可能读出来,也可能读不出来
  • Eventually最终一致性:当你写入一个新值后,有可能读不出来,但在某个时间窗口之后保证最终能读出来
  • Strong强一致性:新的数据一旦写入,在任意副本任意时刻都能读到新值

Master-Slave

  • 读写请求都由Master负责。

  • 写请求写到Master上后,由Master同步到Slave上。

Master同步到Slave上,你可以使用异步,也可以使用同步,可以使用Masterpush,也可以使用Slavepull。 通常来说是Slave来周期性的pull,所以,是最终一致性。

Master-Master

一个系统存在两个或多个Master,每个Master都提供read-write服务。这个模型是Master-Slave的加强版,数据间同步一般是通过Master间的异步完成,所以是最终一致性。

Two/Three Phase Commit

在分布式系统中,每个节点虽然可以知晓自己在操作时是成功还是失败,却无法知道其他节点的操作的成功或失败。当一个事务跨越多个节点的时候,为了保持事务的ACID特征,需要引入一个座位协调者的组件来统一掌控所有节点(称作参与者)的操作结果并最终指示这些节点是否要把操作结果进行真正的提交。

第一阶段
  • 协调者会问所有的参与者结点,是否可以执行提交操作。
  • 各个参与者开始事务执行的准备工作:如:为资源上锁,预留资源,写undo/redo log……
  • 参与者响应协调者,如果事务的准备工作成功,则回应“可以提交”,否则回应“拒绝提交”。
第二阶段
  • 如果所有的参与者都回应“可以提交”,那么,协调者向所有的参与者发送“正式提交”的命令。参与者完成正式提交,并释放所有资源,然后回应“完成”,协调者收集各结点的“完成”回应后结束这个Global Transaction
  • 如果有一个参与者回应“拒绝提交”,那么,协调者向所有的参与者发送“回滚操作”,并释放所有资源,然后回应“回滚完成”,协调者收集各结点的“回滚”回应后,取消这个Global Transaction

先尝试再提交

1)牧师分别问新郎和新娘:你是否愿意……不管生老病死……(询问阶段)
2)当新郎和新娘都回答愿意后(锁定一生的资源),牧师就会说:我宣布你们……(事务提交)

一些问题:

  • 其中一个是同步阻塞操作,会非常大地影响性能
  • Timeout
  • 如果第一阶段完成后,参与者在第二阶段没有收到决策,那么数据结点会进入不知所措的状态,这个状态会block住整个事务

Paxis算法

任何一个点都可以提出要修改某个数据的提案,是否通过这个提案取决于这个集群中是否有超过半数的结点同意(所以Paxos算法需要集群中的结点是单数)。

Reference

http://coolshell.cn/articles/10910.html

2016/3/4 posted in  数据库

SpringMyBatis 事务管理

框架简介

Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架,具有两个重要模块:Spring 面向切面编程(AOP)和控制反转(IOC)容器。

  • 控制反转模式(也称作依赖性介入)的基本概念是:不创建对象,但是描述创建它们的方式。在代码中不直接与对象和服务连接,但在配置文件中描述哪一个组件需要哪一项服务。

  • 容器(在Spring 框架中是 IOC 容器)负责将这些联系在一起。在典型的IOC场景中,容器创建了所有对象,并设置必要的属性将它们连接在一起,决定什么时间调用方法。

MyBatis是支持普通SQL查询,存储过程和高级映射的优秀持久层框架,消除了几乎所有的JDBC代码和参数的手工设置以及结果集的检索,使用简单的XML或注解用于配置和原始映射,将接口和Java对象映射成数据库中的记录。

一、什么是事务

对于一个软件系统来说,需要相应的数据资源(比如,数据库,文件系统等)来保存系统状态,在对系统状态所依托的数据资源进行访问的时候,为了保证系统始终处于一个“正确”的状态,我们就必须对这些访问操作进行一些必要的限定,以此来保证系统状态的完整性。

事务就是以可控的方式对数据资源进行访问的一组操作,为了保证事务执行前后数据资源所承载的系统状态始终处于“正确”状态。

二、事务的特性

原子性(Atomicity),一致性(Consistency),隔离性(Isolation)以及持久性(Durability),也就是常说的事务的ACID属性。

1、事务的原子性(Atomicity)

要么同时成功,要么同时失败

原子性要求事务所包含的全部操作是一个不可分割的整体,这些操作要么全部提交成功,要么只要其中一个操作失败。

2、事务的一致性(Consistency)

一致性要求事务所包含的操作不能违反数据资源的一致性检查,数据资源在事务执行之前处于一个数据的一致性状态,那么,事务执行之后也需要依然保持数据间的一致性状态。

3、事务的隔离性(Isolation)

事务的隔离性主要规定了各个事务之间相互影响的程度。隔离性概念主要面向对数据资源的并发访问(Concurrency),并兼顾影响事务的一致性。当两个事务或者更多事务同时访问同一数据资源的时候, 不同的隔离级别决定了各个事务对该数据资源访问的不同行为。

不出意外的话,我们可以为事务指定四种类型的隔离级别,隔离程度按照从弱到强分别为Read UncommittedRead CommittedRepeatable ReadSerializable:

  • Read Uncommitted:最低的隔离级别,Read Uncommitted最直接的效果就是一个事务可以读取另一个事务并未提交的更新结果。

  • Read CommittedRead Committed通常是大部分数据库采用的默认隔离级别,它在Read Uncommitted隔离级别基础上所做的限定更进一步,在该隔离级别下,一个事务的更新操作结果只有在该事务提交之后,另一个事务才可能读取到同一笔数据更新后的结果。

  • Repeatable ReadRepeatable Read隔离级别可以保证在整个事务的过程中,对同一笔数据的读取结果是相同的,不管其他事务是否同时在对同一笔数据进行更新,也不管其他事务对同一笔数据的更新提交与否。Repeatable Read隔离级别避免了脏读和不可重复读取的问题,但无法避免幻读。

  • Serializable:最为严格的隔离级别,所有的事务操作都必须依次顺序执行,可以避免其他隔离级别遇到的所有问题,是最为安全的隔离级别,但同时也是性能最差的隔离级别。

4、事务的持久性(Durability)

事务的持久性是指一旦整个事务操作成功提交完成,对数据所做的变更将被记载并不可逆转。

三、Spring事务性

Spring所有的事务管理策略类都继承自org.springframework.transaction.PlatformTransactionManager接口。

public interface PlatformTransactionManager 
{
    TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
    void commit(TransactionStatus status) throws TransactionException;
    void rollback(TransactionStatus status) throws TransactionException;
}

其中TransactionDefinition接口定义以下特性:

1、传播行为

传播行为定义关于客户端和被调用方法的事务边界。在TransactionDefinition定义中包括了如下几个表示传播行为的常量:

传播行为意义
PROPAGATION_MANDATORY该方法必须运行在一个事务中。如果当前没有事务正在发生,将抛出一个异常。
PROPAGATION_NESTED如果当前正有一个事务在进行中,则该方法应当运行在一个嵌套式事务中。被嵌套的事务可以独立于封装事务进行提交或回滚。如果封装事务不存在,行为就像 PROPAGATION_REQUIRES一样。
PROPAGATION_NEVER当前的方法不应该在一个事务中运行。如果一个事务正在进行,则会抛出一个异常。
PROPAGATION_NOT_SUPPORTED该方法不应该在一个事务中运行。如果一个现有事务正在进行中,它将在该方法的运行期间被挂起。
PROPAGATION_SUPPORTS当前方法不需要事务性上下文,但是如果有一个事务已经在运行的话,它也可以在这个事务里运行。
PROPAGATION_REQUIRES_NEW当前方法必须在它自己的事务里运行。一个新的事务将被启动,而且如果有一个现有事务在运行的话,则将在这个方法运行期间被挂起。
PROPAGATION_REQUIRES当前方法必须在一个事务中运行。如果一个现有事务正在进行中,该方法将在那个事务中运行,否则就要开始一个新事务。

传播行为回答了这样一个问题,就是一个新的事务应该被启动还是被挂起,或者是一个方法是否应该在事务性上下文中运行。

2、隔离级别

声明式事务的第二个方面是隔离级别。隔离级别定义一个事务可能受其他并发事务活动活动影响的程度。另一种考虑一个事务的隔离级别的方式,是把它想象为那个事务对于事物处理数据的自私程度。

在一个典型的应用程序中,多个事务同时运行,经常会为了完成他们的工作而操作同一个数据。并发虽然是必需的,但是会导致一下问题:

  • 脏读(Dirty read)-- 脏读发生在一个事务读取了被另一个事务改写但尚未提交的数据时。如果这些改变在稍后被回滚了,那么第一个事务读取的数据就会是无效的。

  • 不可重复读(Nonrepeatable read)-- 不可重复读发生在一个事务执行相同的查询两次或两次以上,但每次查询结果都不相同时。这通常是由于另一个并发事务在两次查询之间更新了数据。

  • 幻影读(Phantom reads)-- 幻影读和不可重复读相似。当一个事务(T1)读取几行记录后,另一个并发事务(T2)插入了一些记录时,幻影读就发生了。在后来的查询中,第一个事务(T1)就会发现一些原来没有的额外记录。

在理想状态下,事务之间将完全隔离,从而可以防止这些问题发生。然而,完全隔离会影响性能,因为隔离经常牵扯到锁定在数据库中的记录(而且有时是锁定完整的数据表)。侵占性的锁定会阻碍并发,要求事务相互等待来完成工作。

考虑到完全隔离会影响性能,而且并不是所有应用程序都要求完全隔离,所以有时可以在事务隔离方面灵活处理。TransactionDefinition 接口中定义了五个表示隔离级别的常量:

隔离级别含义
ISOLATION_DEFAULT使用数据库默认的隔离级别。
ISOLATION_READ_UNCOMMITTED允许读取尚未提交的更改。可能导致脏读、幻影读或不可重复读。
ISOLATION_READ_COMMITTED允许从已经提交的并发事务读取。可防止脏读,但幻影读和不可重复读仍可能会发生。
ISOLATION_REPEATABLE_READ对相同字段的多次读取的结果是一致的,除非数据被当前事务本身改变。可防止脏读和不可重复读,但幻影读仍可能发生。
ISOLATION_SERIALIZABLE完全服从ACID的隔离级别,确保不发生脏读、不可重复读和幻影读。这在所有隔离级别中也是最慢的,因为它通常是通过完全锁定当前事务所涉及的数据表来完成的。

3、超时

为了使一个应用程序很好地执行,它的事务不能运行太长时间。因此,声明式事务的下一个特性就是它的超时。

假设事务的运行时间变得格外的长,由于事务可能涉及对后端数据库的锁定,所以长时间运行的事务会不必要地占用数据库资源。这时就可以声明一个事务在特定秒数后自动回滚,不必等它自己结束。

由于超时时钟在一个事务启动的时候开始的,因此,只有对于那些具有可能启动一个新事务的传播行为(PROPAGATION_REQUIRES_NEWPROPAGATION_REQUIREDROPAGATION_NESTED)的方法来说,声明事务超时才有意义。

4、只读

声明式事务的第四个特性是它是否是一个只读事务。如果一个事务只对后端数据库执行读操作,那么该数据库就可能利用那个事务的只读特性,采取某些优化措施。通过把一个事务声明为只读,可以给后端数据库一个机会来应用那些它认为合适的优化措施。由于只读的优化措施是在一个事务启动时由后端数据库实施的, 因此,只有对于那些具有可能启动一个新事务的传播行为(PROPAGATION_REQUIRES_NEWPROPAGATION_REQUIREDROPAGATION_NESTED)的方法来说,将事务声明为只读才有意义。

5、回滚规则

指示Spring事务管理器回滚一个事务的推荐方法是在当前事务的上下文内抛出异常。Spring事务管理器会捕捉任何未处理的异常,然后依据规则决定是否回滚抛出异常的事务。

默认配置下,Spring只有在抛出的异常为运行时unchecked异常时才回滚该事务,也就是抛出的异常为RuntimeException的子类(Errors也会导致事务回滚),而抛出 checked 异常则不会导致事务回滚。

可以明确的配置在抛出那些异常时回滚事务,包括checked异常。也可以明确定义那些异常抛出时不回滚事务。

还可以编程性的通过setRollbackOnly()方法来指示一个事务必须回滚,在调用完setRollbackOnly()后你所能执行的唯一操作就是回滚。

四、Spring 事务管理

Spring对事务管理提供了一致的抽象,其特点如下:

  • 为不同的事务API提供一致的编程模型,比如JTA(Java Transaction API), JDBC, Hibernate, JPA(Java Persistence APIJDO(Java Data Objects)

  • 支持声明式事务管理,特别是基于注解的声明式事务管理,简单易用

  • 提供比其他事务APIJTA更简单的编程式事务管理API

  • spring数据访问抽象的完美集成

1、事务管理方式

Spring支持编程式事务管理和声明式事务管理两种方式。

编程式事务管理使用TransactionTemplate或直接使用底层PlatformTransactionManagerSpring推荐使用 TransactionTemplate

声明式事务管理建立在AOP之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。声明式事务最大的优点就是不需要通过编程的方式管理事务,这样就不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明(或通过基于@Transactional注解的方式),便可以将事务规则应用到业务逻辑中。

显然,声明式事务管理要优于编程式事务管理,这正是Spring倡导的非侵入式的开发方式。声明式事务管理使业务代码不受污染,一个普通的POJO对象,只要加上注解就可以获得完全的事务支持。和编程式事务相比,声明式事务唯一不足地方是,后者的最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。但是即便有这样的需求,也存在很多变通的方法,比如,可以将需要进行事务管理的代码块独立为方法等等。

声明式事务管理也有两种常用的方式,一种是基于txaop名字空间的xml配置文件,另一种就是基于 @Transactional注解。显然基于注解的方式更简单易用,更清爽。

2、自动提交(AutoCommit)

默认情况下,数据库处于自动提交模式。每一条语句处于一个单独的事务中,在这条语句执行完毕时,如果执行成功则隐式的提交事务,如果执行失败则隐式的回滚事务。

对于正常的事务管理,是一组相关的操作处于一个事务之中,因此必须关闭数据库的自动提交模式。不过,这个我们不用担心,Spring会将底层连接的自动提交特性设置为false

org/springframework/jdbc/datasource/DataSourceTransactionManager.java

// switch to manual commit if necessary. this is very expensive in some jdbc drivers,
// so we don't want to do it unnecessarily (for example if we've explicitly
// configured the connection pool to set it already).
if (con.getautocommit()) {
    txobject.setmustrestoreautocommit(true);
    if (logger.isdebugenabled()) {
        logger.debug("switching jdbc connection [" + con + "] to manual commit");
    }
    con.setautocommit(false);
}

五、基于@Transactional注解的AOP事务管理

Spring 配置文件里需要:

<tx:annotation-driven transaction-manager="transactionManager"/>

<aop:aspectj-autoproxy/>

tx:annotation-driven是注解驱动的事务管理支持的核心。

标签属性:

a、transaction-manager:指定到现有的 PlatformTransactionManager bean的引用,通知会使用该引用,default = "transactionManager"

b、mode:指定Spring事务管理框架创建通知bean的方式。可用的值有proxyaspectj。前者是默认值,表示通知对象是个JDK代理;后者表示Spring AOP会使用AspectJ创建代理。

c、order:指定创建的切面的顺序。只要目标对象有多个通知就可以使用该属性。

d、proxy-target-class:该属性如果为true就表示你想要代理目标类而不是bean所实现的所有接口default="false"

1、@Transactional属性

属性类型描述
valueString可选的限定描述符,指定使用的事务管理器。
propagationenum: Propagation可选的事务传播行为设置。
isolationenum: Isolation可选的事务隔离级别设置
readOnlyboolean读写或只读事务,默认读写
timeoutint(in seconds granularity)事务超时时间设置
rollbackForClass对象数组,必须继承自Throwable导致事务回滚的异常类数组
rollbackForClassName类名数组,必须继承自Throwable导致事务回滚的异常类名字数组
noRollbackForClass对象数组,必须继承自Throwable不会导致事务回滚的异常类数组
noRollbackForClassName类名数组,必须继承自Throwable不会导致事务回滚的异常类名字数组

2、用法

@Transactional可以作用于接口、接口方法、类以及类方法上。当作用于类上时,该类的所有public方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。

虽然@Transactional注解可以作用于接口、接口方法、类以及类方法上,但是 Spring 建议不要在接口或者接口方法上使用该注解,因为这只有在使用基于接口的代理时它才会生效。另外, @Transactional注解应该只被应用到public方法上,这是由Spring AOP的本质决定的。如果你在 protectedprivate或者默认可见性的方法上使用 @Transactional注解,这将被忽略,也不会抛出任何异常。

默认情况下,只有来自外部的方法调用才会被AOP代理捕获,也就是,类内部方法调用本类内部的其他方法并不会引起事务行为,即使被调用方法使用@Transactional注解进行修饰。

示例代码:

@Transactional(readOnly = true)
public class getInfoService implements BaseService 
{
  public Info getInfo(String name) 
  {
    // do something
  }
  // these settings have precedence for this method
  //方法上注解属性会覆盖类注解上的相同属性
  @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
  public void updateInfo(Info info) 
  {
    // do something
  }
}

如果在每个方法上都定义注解,那么就会很麻烦。(可以使用XML AOP事务管理能更好的处理这种情况)

六、基于XMLAOP事务管理

1、基于XMLAOP事务管理配置文件如下:

<aop:config>
    <aop:pointcut id="allServiceMethods"  
                  expression="execution(* cn.hao24.mobauto.service.*.*(..))"/>  
    <aop:advisor advice-ref="defaultTransactionAdvice"
                    pointcut-ref="allServiceMethods"/>  
</aop:config>  
  
<tx:advice id="defaultTransactionAdvice" transaction-manager="transactionManager">  
    <tx:attributes>  
        <tx:method  
                name="*"  
                isolation="DEFAULT"  
                propagation="REQUIRED"  
                no-rollback-for="java.lang.RuntimeException"  
                timeout="100"/>  
        <tx:method  
                name="get*"  
                read-only="true"/>  
    </tx:attributes>  
</tx:advice>

2、tx:advice标签简介

该标签会创建一个事务处理通知。

id是该advice bean的标识,而transaction-manager则必须引用一个PlatformTransactionManager bean
还可以通过<tx:attributes>标签定制<tx:advice>标签所创建的通知的行为。

3、<tx:method/>标签的属性

name:方法名的匹配模式,通知根据该模式寻找匹配的方法。

其余属性(propagationisolationtimeoutread-onlyno-rollback-forrollback-for)不再赘述。

<tx:method>isolation(隔离)和propagation(传播)参数的含义:
getIsolationLevel:他对其他事务所看到的数据变化进行控制。

七、MyBatis-Spring 配置

使用MyBatis-Spring的主要原因是它允许MyBatis参与到 Spring 的事务管理中。而不是给MyBatis创建一个新的特定的事务管理器,MyBatis-Spring利用了存在于 Spring中的DataSourceTransactionManager

一旦SpringPlatformTransactionManager配置好了,你可以在Spring中以你通常的做法来配置事务。@Transactional注解和AOP XML的配置都是支持的。在事务处理期间,一个单独的SqlSession对象将会被创建和使用。当事务完成时,这个session会以合适的方式提交或回滚。

一旦事务创建之后,MyBatis-Spring将会透明的管理事务。配置代码如下:

<!-- 创建SqlSessionFactory,同时指定数据源 -->
<bean id="mySqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">  
    <property name="dataSource" ref="dataSource" />
    <!-- 自动扫描entity目录, 省掉Configuration.xml里的手工配置 -->
    <property name="mapperLocations" value="classpath:sqlmaps/**/*Mapper.xml" />
</bean>

<!-- Mapper接口所在包名,Spring会自动查找其下的类 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">  
    <property name="basePackage" value="cn.hao24.mobauto.mapper" />
    <property name="sqlSessionFactoryBeanName" value="mySqlSessionFactory" />
</bean>

MyBatisSqlSession提供指定的方法来处理编程式的事务。 但是当使用MyBatis-Spring时, bean将会使用 Spring管理的SqlSession或映射器来注入。 那就是说Spring通常是处理事务的。

你不能在Spring管理的SqlSession上调用 SqlSession.commit()SqlSession.rollback()SqlSession.close()方法 。 如果这样做了 , 就 会 抛 出UnsupportedOperationException异常。注意在使用注入的映射器时不能访问那些方法。

无论JDBC连接是否设置为自动提交, SqlSession数据方法的执行或在Spring事务之外任意调用映射器方法都将会自动被提交。

Local vs. Global Transactions

Local transactions are specific to a single transactional resource like a JDBC connnection, whereas globa transactions can span multiple transactional resources like transaction in a distributed system.

事务类型

数据库事务类型有本地事务和分布式事务:

  • 本地事务:就是普通事务,能保证单台数据库上的操作的ACID,被限定在一台数据库上
  • 分布式事务:涉及两个或多个数据库源的事务,即跨越多台同类或异类数据库的事务(由每台数据库的本地事务组成的)

Java事务类型有JDBC事务和JTA事务:

  • JDBC事务:就是数据库事务类型中的本地事务,通过Connection对象的控制来管理事务
  • JTA事务:由应用程序服务器厂商提供实现

JavaEE事务类型有本地事务和全局事务:

  • 本地事务:使用JDBC变成实现事务
  • 全局事务:由应用程序服务器提供,使用JTA事务
2016/3/4 posted in  数据库

ElasticSearch查询语法

基础概念

Elasticsearch有几个核心概念,从一开始理解这些概念会对整个学习过程有莫大的帮助。

接近实时(NRT)

Elasticsearch是一个接近实时的搜索平台。这意味着,从索引一个文档直到这个文档能够被搜索到有一个轻微的延迟(通常是1秒)。

集群(cluster)

一个集群就是由一个或多个节点组织在一起,它们共同持有你整个的数据,并一起提供索引和搜索功能。

一个集群由一个唯一的名字标识,这个名字默认就是 elasticsearch。这个名字是重要的,因为一个节点只能通过指定某个集群的名字,来加入这个集群。

在产品环境中显式地设定这个名字是一个好习惯,但是使用默认值来进行测试/开发也是不错的。

节点(node)

一个节点是你集群中的一个服务器,作为集群的一部分,它存储你的数据,参与集群的索引和搜索功能。和集群类似,一个节点也是由一个名字来标识的,默认情况下,这个名字是一个随机的漫威漫画角色的名字,这个名字会在启动的时候赋予节点。这个名字对于管理工作来说挺重要的,因为在这个管理过程中,你会去确定网络中的哪些服务器对应于Elasticsearch集群中的哪些节点。

一个节点可以通过配置集群名称的方式来加入一个指定的集群。默认情况下,每个节点都会被安排加入到一个叫做elasticsearch的集群中,这意味着,如果你在你的网络中启动了若干个节点,并假定它们能够相互发现彼此,它们将会自动地形成并加入到一个叫做elasticsearch的集群中。

在一个集群里,只要你想,可以拥有任意多个节点。而且,如果当前你的网络中没有运行任何Elasticsearch节点,这时启动一个节点,会默认创建并加入一个叫做elasticsearch的集群。

索引(index)

一个索引就是一个拥有几分相似特征的文档的集合。比如说,你可以有一个客户数据的索引,另一个产品目录的索引,还有一个订单数据的索引。一个索引由一个名字来标识(必须全部是小写字母的),并且当我们要对对应于这个索引中的文档进行索引、搜索、更新和删除的时候,都要使用到这个名字。

在一个集群中,如果你想,可以定义任意多的索引。

类型(type)

在一个索引中,你可以定义一种或多种类型。一个类型是你的索引的一个逻辑上的分类/分区,其语义完全由你来定。通常,会为具有一组共同字段的文档定义一个类型。比如说,我们假设你运营一个博客平台并且将你所有的数据存储到一个索引中。在这个索引中,你可以为用户数据定义一个类型,为博客数据定义另一个类型,当然,也可以为评论数据定义另一个类型。

文档(document)

一个文档是一个可被索引的基础信息单元。比如,你可以拥有某一个客户的文档,某一个产品的一个文档,当然,也可以拥有某个订单的一个文档。文档以 JSON格式来表示,而JSON是一个到处存在的互联网数据交互格式。

在一个index/type里面,只要你想,你可以存储任意多的文档。注意,尽管一个文档,物理上存在于一个索引之中,文档必须被索引/赋予一个索引的type

分片和复制(shards & replicas)

一个索引可以存储超出单个结点硬件限制的大量数据。比如,一个具有10亿文档的索引占据1TB的磁盘空间,而任一节点都没有这样大的磁盘空间;或者单个节点处理搜索请求,响应太慢。

为了解决这个问题,Elasticsearch提供了将索引划分成多份的能力,这些份就叫做分片。当你创建一个索引的时候,你可以指定你想要的分片的数量。每个分片本身也是一个功能完善并且独立的“索引”,这个“索引”可以被放置到集群中的任何节点上。

分片之所以重要,主要有两方面的原因:

  • 允许你水平分割/扩展你的内容容量
  • 允许你在分片(潜在地,位于多个节点上)之上进行分布式的、并行的操作,进而提高性能/吞吐量

至于一个分片怎样分布,它的文档怎样聚合回搜索请求,是完全由Elasticsearch管理的,对于作为用户的你来说,这些都是透明的。

在一个网络/云的环境里,失败随时都可能发生,在某个分片/节点不知怎么的就处于离线状态,或者由于任何原因消失了,这种情况下,有一个故障转移机制是非 常有用并且是强烈推荐的。为此目的,Elasticsearch允许你创建分片的一份或多份拷贝,这些拷贝叫做复制分片,或者直接叫复制。

复制之所以重要,有两个主要原因:

  • 在分片/节点失败的情况下,提供了高可用性。因为这个原因,注意到复制分片从不与原/主要(original/primary)分片置于同一节点上是非常重要的。
  • 扩展你的搜索量/吞吐量,因为搜索可以在所有的复制上并行运行

总之,每个索引可以被分成多个分片。一个索引也可以被复制0次(意思是没有复制)或多次。一旦复制了,每个索引就有了主分片(作为复制源的原来的分片)和 复制分片(主分片的拷贝)之别。分片和复制的数量可以在索引创建的时候指定。在索引创建之后,你可以在任何时候动态地改变复制的数量,但是你事后不能改变 分片的数量。

默认情况下,Elasticsearch中的每个索引被分片5个主分片和1个复制,这意味着,如果你的集群中至少有两个节点,你的索引将会有5个主分片和另外5个复制分片(1个完全拷贝),这样的话每个索引总共就有10个分片。

基本语法

集群健康

当我们询问集群状态的时候,我们要么得到绿色、黄色或红色。绿色代表一切正常(集群功能齐全),黄色意味着所有的数据都是可用的,但是某些复制没有被分配 (集群功能齐全),红色则代表因为某些原因,某些数据不可用。注意,即使是集群状态是红色的,集群仍然是部分可用的(它仍然会利用可用的分片来响应搜索请 求),但是可能你需要尽快修复它,因为你有丢失的数据。

curl 'localhost:9200/_cat/health?v'
  • 节点列表
curl 'localhost:9200/_cat/nodes?v'
  • 索引列表
curl 'localhost:9200/_cat/indices?v'
  • 查看分片
curl '172.168.5.110:9200/_cat/shards?v'
  • 创建一个索引, pretty的意思为格式化返回的JSON信息
curl -XPUT 'localhost:9200/index1?pretty'
  • 创建一个索引,并且指定分片数与备份数
curl -XPUT 'localhost:9200/index1'?pretty -d '{
            "index.number_of_replicas" : 1,
            "number_of_shards": 5
        }'
  • 设置索引打开与关闭
curl -XPOST 'localhost:9200/index1/_open'

curl -XPOST 'localhost:9200/index1/_close'
  • 向索引内添加一条数据,并指定Id为1, 文档类型为external
curl -XPUT 'localhost:9200/index1/external/1?pretty' -d '{
            "name": "John Doe"
        }'
  • 读取索引中的文档,Id为1,文档类型为external
curl -XGET 'localhost:9200/index1/external/1?pretty'
  • 更新索引信息
curl -XPOST 'localhost:9200/index1/external/1/_update?pretty' -d '{
        "name":"John Doe"
    }'
  • 删除索引
curl -XDELETE 'localhost:9200/index1?pretty'
  • 查询索引内的所有数据
curl -XPOST 'localhost:9200/index1/_search?pretty' -d '{
        "query": {
            "match_all": {}
        }
    }'
  • 查询索引内的所有数据,指定开始位置与数量
curl -XPOST 'localhost:9200/index1/_search?pretty' -d '{
        "query": {
            "match_all": {}
        },
        "from": 10,
        "size": 10
    }'
  • 查询索引,指定排序方式
curl -XPOST 'localhost:9200/index1/_search?pretty' -d '{
        "query": {
            "match_all": {}
        },
        "sort": {
            "balance": {
                "order": "desc"
            }
        }
    }'
  • 查询索引,指定返回数据的字段列表
curl -XPOST 'localhost:9200/index1/_search?pretty' -d '{
        "query": {
            "match_all": {}
        },
        "_source": ["name", "balance"]
    }'
  • 查询索引,数据匹配
curl -XPOST 'localhost:9200/index1/_search?pretty' -d '{
        "query": {
            "match": {
                "name": "John"
            }
        }
    }'
  • 查询索引,数据匹配, operator默认为or
curl -XPOST 'localhost:9200/index1/_search?pretty' -d '{
        "query": {
            "match": {
                "address": {
                    "query": "mill lane",
                    "operator": "and"
                }
            }
        }
    }'
  • 查询索引, 数据匹配百分比
curl -XPOST 'localhost:9200/index1/_search?pretty' -d '{
        "query": {
            "match": {
                "address": {
                    "query": "mill lane",
                    "minimum_should_match": "75%"
                }
            }
        }
    }'
  • 精确匹配
curl -XPOST 'localhost:9200/index1/_search?pretty' -d '{
        "query": {
            "match_phrase": {
                "address": "mill lane"
            }
        }
    }'
  • 多条件匹配 (must, should, must_not)

    1. must 条件全部符合
    2. should 有一条符合即可
    3. must_not 全都不符合
curl -XPOST 'localhost:9200/index1/_search?pretty' -d '{
        "query": {
            "bool": {
                "must": [
                    {
                        "match": { "address": "mill" }
                    },
                    {
                        "match": { "address": "lane" }
                    }
                ]
            }
        }
    }'
  • 指定匹配条件数量
curl -XPOST 'localhost:9200/index1/_search?pretty' -d '{
        "query": {
            "bool": {
                "should": [
                    {
                        "match": {
                            "address": "mill"
                        }
                    },
                    {
                        "match": {
                            "address": "lane"
                        }
                    },
                    {
                        "match": {
                            "address": "place"
                        }
                    }
                ],
                "minimum_should_match": 2
            }
        }
    }'
  • 过滤器
curl -XPOST 'localhost:9200/index1/_search?pretty' -d '{
        "query": {
            "filtered": {
                "query": {
                    "match_all": {}
                },
                "filter": {
                    "range": {
                        "balance": {
                            "gte": 20000,
                            "lte": 30000
                        }
                    }
                }
            }
        }
    }'
  • 数据聚合
    1. size设置成0,只返回聚合结果,而不会显示命中的结果
    curl -XPOST 'localhost:9200/index1/_search?pretty' -d '{
        "size": 0,
        "aggs": {
            "group_by_state": {
                "terms": {
                    "field": "state"
                }
            }
        }
    }'
  • 嵌套聚合
curl -XPOST 'localhost:9200/index1/_search?pretty' -d '{
        "size": 0,
        "aggs": {
            "group_by_state": {
                "terms": {
                    "field": "state"
                },
                "aggs": {
                    "average_balance": {
                        "avg": {
                            "field": "balance"
                        }
                    }
                }
            }
        }
    }'
  • 前缀查询
curl -XPOST 'localhost:9200/index1/_search?pretty' -d '{
        "query": {
            "prefix": {
                "address": "jiangsu"
            }
        }
    }'
  • WildcardQuery:

    1. *号代表0-n个字符
    2. ?号代表一个字符
curl -XPOST 'localhost:9200/index1/_search?pretty' -d '{
        "query": {
            "wildcard": {
                "address": "mill**"
            }
        }
    }'
  • 正则表达式查询
curl -XPOST 'localhost:9200/index1/_search?pretty' -d '{
        "query": {
            "regexp": {
                "address": "m.*"
            }
        }
    }'
  • 高亮显示查询结果

    1. slop指定每个相邻词之间允许相隔多远。此处设置为0,以实现完全匹配。
curl -XPOST 'localhost:9200/index1/_search?pretty' -d '{
        "query": {
            "multi_match": {
                "query": "@189.cn",
                "type": "phrase",
                "slop": 0,
                "fields": ["sender"],
                "analyzer": "charSplit", 
                "max_expansions": 1   
            }
        },
        "highlight": {
            "pre_tags": ["&lt;b&gt;"],
            "post_tags": ["&lt;/b&gt;"],
            "fragment_size": 100,
            "number_of_fragments": 2,
            "require_field_match": true,
            "fields": {
                "sender": {}
            }
        }
    }'

其它语句

  • 设置系统备份分片数
curl -XPUT "localhost:9200/_settings" -d '{
        "number_of_replicas" : 0
    }'
  • 设置索引备份分片数
curl -XPUT "localhost:9200/index1/_settings" -d '{
        "index.number_of_replicas" : 0
    }'
  • 设置索引使用分词插件
curl -XPUT "localhost:9200/index1/_settings" -d '{
        "analysis": {
            "analyzer":{
                "ikAnalyzer":{
                    "type":"org.elasticsearch.index.analysis.IkAnalyzerProvider",
                    "alias":"ik"
                }
            }
        }
    }'
2016/3/4 posted in  ELK

面向GC的Java编程

GC分代的基本假设

GC分代的基本假设是:

绝大部分对象的生命周期都非常短暂,存活时间短。

基于这个前提,在编码过程中,我们应该尽可能地缩短对象的生命周期。

结论:

  • 分配小对象的开销非常小,不要吝啬去创建
  • GC最喜欢这种小而短命的对象
  • 让对象的生命周期尽可能短,例如在方法体内创建,使其能尽快地在YoungGC中被回收,不会晋升到年老代(Old Generation

对象分配的优化

基于大部分对象都是小而短命,并且不存在多线程的数据竞争。这些小对象的分配,会优先在线程私有的TLAB中分配,TLAB中创建的对象,不存在锁甚至是CAS的开销。

TLAB占用的空间在Eden Generation

当对象比较大,TLAB的空间不足以放下,而JVM又认为当前线程占用的TLAB剩余空间还足够时,就会直接在Eden Generation上分配,此时是存在并发竞争的,所以会有CAS的开销,但是也还好。

当对象大到Eden Generation放不下时,JVM只能尝试去Old Generation分配,这种情况需要尽可能避免,因为一旦在Old Generation上分配,这个对象只能被Old GenerationGC或者是Full GC回收了。

不可变对象的好处

GC算法在扫描存活对象时通常需要从ROOT节点开始,扫描所有存活对象的引用,构建出对象图。

不可变对象对GC的优化,主要体现在Old Generation中。

如果存在Old Generation的对象引用了Young Generation的对象,那么在每次YoungGC的过程中,就必须考虑到这种情况。

卡表(Card Table)

Old Generation中的对象发生对Young Generation中的对象产生新的引用关系或释放引用时,都会在卡表中相应的标记上标为脏(dirty);而当YoungGC时,只需要扫描这些dirty的项就可以了。

可变对象对其他对象的引用关系可能会频繁变化,并且有可能在运行过程中持有越来越多的引用,特别是容器。这些都会导致对应的卡表项被频繁标记为dirty

而不可变对象的引用关系非常稳定,在扫描卡表时就不会扫到它们对应的项了。

这里的不可变对象,不是指仅仅自身引用不可变的final对象,而是真正的Immutable Objects

指定容器初始化大小

如果采用默认无参构造函数,构建一个ArrayList,不断增加元素知道OOM,那么在此过程中会导致:

  • 多次数组扩容,重新分配更大空间的数组
  • 多次数组拷贝
  • 内存碎片

对象池

为了减少对象分配开销,提高性能,可能有人会采用对象池的方式来缓存对象集合,作为复用的手段。

但是对象池中的对象由于在运行期长期存活,大部分会晋升到Old Generation,因此无法通过YoungGC回收。

可以借助成熟的开源框架,例如Apache Commons Pool

对象作用域

尽可能缩小对象的作用域,即生命周期

  • 如果可以在方法内声明的局部变量,就不要声明为实例变量
  • 除非你的对象是单例的或者不变的,否则尽可能少地声明static变量

Reference

http://coolshell.cn/articles/11541.html

2016/3/4 posted in  Java