java综合技术分享

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/zc529739024/article/details/55519733

1:心跳机制

1.1心跳包机制

  跳包之所以叫心跳包是因为:它像心跳一样每隔固定时间发一次,以此来告诉服务器,这个客户端还活着。事实上这是为了保持长连接,至于这个包的内容,是没有什么特别规定的,不过一般都是很小的包,或者只包含包头的一个空包。
   在TCP的机制里面,本身是存在有心跳包的机制的,也就是TCP的选项:SO_KEEPALIVE。系统默认是设置的2小时的心跳频率。但是它检查不到机器断电、网线拔出、防火墙这些断线。而且逻辑层处理断线可能也不是那么好处理。一般,如果只是用于保活还是可以的。
   心跳包一般来说都是在逻辑层发送空的echo包来实现的。下一个定时器,在一定时间间隔下发送一个空包给客户端,然后客户端反馈一个同样的空包回来,服务器如果在一定时间内收不到客户端发送过来的反馈包,那就只有认定说掉线了。
   其实,要判定掉线,只需要send或者recv一下,如果结果为零,则为掉线。但是,在长连接下,有可能很长一段时间都没有数据往来。理论上说,这个连接是一直保持连接的,但是实际情况中,如果中间节点出现什么故障是难以知道的。更要命的是,有的节点(防火墙)会自动把一定时间之内没有数据交互的连接给断掉。在这个时候,就需要我们的心跳包了,用于维持长连接,保活。
   在获知了断线之后,服务器逻辑可能需要做一些事情,比如断线后的数据清理呀,重新连接呀……当然,这个自然是要由逻辑层根据需求去做了。
   总的来说,心跳包主要也就是用于长连接的保活和断线处理。一般的应用下,判定时间在30-40秒比较不错。如果实在要求高,那就在6-9秒

1.2心跳检测步骤
1客户端每隔一个时间间隔发生一个探测包给服务器
2客户端发包时启动一个超时定时器
3服务器端接收到检测包,应该回应一个包
4如果客户机收到服务器的应答包,则说明服务器正常,删除超时定时器
5如果客户端的超时定时器超时,依然没有收到应答包,则说明服务器挂了

1.3 轮询机制 vs 心跳机制

轮询:概括来说是服务端定时主动的去与要监控状态的客户端(或者叫其他系统)通信,询问当前的某种状态,客户端返回状态信息,客户端没有返回或返回错误、失效信息、则认为客户端已经宕机,然后服务端自己内部把这个客户端的状态保存下来(宕机或者其他),如果客户端正常,那么返回正常状态,如果客户端宕机或者返回的是定义的失效状态那么当前的客户端状态是能够及时的监控到的,如果客户端宕机之后重启了那么当服务端定时来轮询的时候,还是可以正常的获取返回信息,把其状态重新更新。

心跳:最终得到的结果是与轮询一样的但是实现的方式有差别,心跳不是服务端主动去发信息检测客户端状态,而是在服务端保存下来所有客户端的状态信息,然后等待客户端定时来访问服务端,更新自己的当前状态,如果客户端超过指定的时间没有来更新状态,则认为客户端已经宕机或者其状态异常。

心跳机制与轮询的比较,在我们的应用中,采用的是心跳,这样一是避免服务端的压力,二是灵活好控制,上一篇文章中提到过,我们的外网服务端(服务端)不知道内网服务端(客户端)的地址,有虽然有保存客户端的socket会话,但是客户端宕机会话就失效了。所以只能等着他主动来报告状态。

 

2Java模版引擎:Velocity 和 FreeMarker 

相比较 FreeMarker 而言,Velocity更加简单、轻量级,但它的功能却没有 FreeMarker 那么强大。

对于大部分的应用来说,使用 FreeMarker 比 Velocity 更简单,因为 Velocity 还必须编写一些自定义的toolbox类以及一遍遍重复的编写一些比较通用的模版代码,因此也就丧失了刚开始开发时更多的宝贵时间。另外使用工具类和变通的方法在模版引擎中似乎不是一个非常有效的做法。同时,Velocity 的做法使得在Velocity的模版中大量的跟 Java 对象进行交互,这违反了简单的原则,尽管你也可以将代码转入控制器中实现。当然,如果你像使用 Velocity 一样来使用 FreeMarker ,那么 FreeMarker 也可以跟 Velocity 一样简单。

Velocity 一个优于FreeMarker 的地方在于它有很广泛的第三方支持以及一个非常庞大的用户社区,你可以通过这个社区获得到很多的帮助,相反的 FreeMarker 在这方面要差很多。当然,也有越来越多的第三方软件开始在支持FreeMarker 。

下面是一些 FreeMarker 能做到的,而Velocity 做不到的功能列表(且看着):

1. 日期和数字的支持
您可以执行运算和比较,对任意数量的类型,包括任意精度类型,而不仅仅是整数。
您可以比较和显示(格式化)日期/时间值。

2. 国际化
您可以格式数字区域,各种各样的内置和自定义数字格式模式。
您可以格式日期地区和时区,各种各样的内置和定制的日期格式模式。
标识符(变量名)可以包含非英语字母一样重音字母,阿拉伯字母,汉字等

3. 循环处理
您可以退出循环
您可以访问控制变量外循环机构的内部循环
您可以得知当前是否到了循环的结束位置

4. 模版级别的数组处理
您可以使用[i]的语法来访问数组元素,包括原始的和非原始的指数
可以获取到数组的长度

5. 宏定义
宏调用可以通过位置或名称进行参数传递
宏的参数可以设定默认值,在调用宏时如果没有指定该参数,则使用默认值代替
通过 <@myMacro>body</@myMacro> 可以支持宏的嵌套
可以通过文本表达的宏的名称来直接调用某个宏
宏允许先使用再定义
宏可以定义局部变量(新版本的Velocity也通过#local指令来实现该功能,尽管官方的文档还没有进行介绍)

6. 命名空间
您可以使用多个名称空间的变数。当您建立“宏库”时是非常有用的,因为可以防止名称冲突与申请特定变量或与其他宏变量的库。

7. 内置与 Java 语言无关的字符串、列表、Map 的操作方法

8. 能提示模版中的拼写错误以及其他错误
当访问一个不存在的变量时,FreeMarker 在执行该模版时会报错,通过配置,你可以指定 FreeMarker 在碰到此类错误时是停止执行,还是忽略该错误,同时 FreeMarker 会在日志中记录此问题;
如果您输入错误指令的名称,FreeMarker将抛出一个异常。

9. 更高级的文本输出工具
You can enclose a block of template in a set of tags that will cause it toapply HTML escaping or XML escaping (or any other transformation you canexpress as a FreeMarker expression for that matter) on all interpolations (${foo}) in the block.
FreeMarker has transforms, which are blocks of template that when rendered, gothrough a transforming filter. Built-in transforms include whitespacecompressor, HTML and XML escaper. Best of all, you can implement your owntransformers as well (i.e. if you generate Java source code, you can write aJava code pretty-printer transform and insert it into the template). Naturally,transforms can be nested.
You can explicitly flush the output writer with a built-in flush-directive.
You can stop the rendering with a built-in stop-directive.

10.文本处理
支持Java的特殊字符处理,例如\b\t\n\f\r\"\'\\,以及UNICODE的\xXXXX
除了通常的字符串,数字,和布尔常量您可以定义列表和地图文字以及内部模板

11.高级的空格清除
FreeMarker 将删除一些多余的空格、跳格、换行等字符,从而消除一些令人厌烦的明显多余的空格
FreeMarker 也提供指令来删除多于的空格

12.与其他技术的集成
提供 JSP 标签库以便在 JSP 中嵌入FreeMarker 模版
可以直接跟 Python 对象一起工作

13.更强大的XML转换功能

14.先进的模板元程序
您可以捕捉到输出的任意部分范本背景变量
您可以任意解释的范围变量,就好像它是一个模板定义

结束语:功能强不强大并不是最重要的,关键在于是否适应你的要求。

参考:https://www.oschina.net/question/12_460

3:RMI,socket,rpc,hessian,http比较

SOCKET使用时可以指定协议TCP,UDP等;

RIM:使用JRMP协议,JRMP又是基于TCP/IP;

RPC:底层使用SOCKET接口,定义了一套远程调用方法;

HTTP:是建立在TCP上,不是使用SOCKET接口,需要连接方主动发数据给服务器,服务器无法主动发数据个客户端;

可以用socket实现HTTP;

其实符合HTTP规范的就是HTTP协议,不管用什么技术。

 

Hessian:是一套用于建立web service的简单的二进制协议,用于替代基于XML的web service,是建立在rpc上的,hessian有一套自己的序列化格式将数据序列化成流,然后通过http协议发送给服务器,看源码发现其实是使用HttpURLConnection和servlet建立连接,然后发送流

 

3.1:RMI VS SOCKET

第一、.RMI是面向对象的,而后者不是。
 第二、.RMI是与语言相绑定的。比如当你使用Java RMI技术的时候,客户端与服务器端都必须使用Java开发(java.rmi.*)。而socket的网络编程是使用独立于开发语言的,甚至独立于平台。基于socket的网络编程,客户端与服务器端可以使用不同开发语言和不同的平台。
第三、从网络协议栈的观点来看,RMIsocket的网络编程处于不同层次上。基于socket的网络编程位于TCP协议之上,而RMITCP协议之上,又定义了自己的应用协议,其传输层采用的是Java远程方法协议(JRMP)。可见,在网络协议栈上,基于RMI的应用位置更高一些,这也决定了,与socket的网络编程相比,RMI会丧失一些灵活性和可控性,但是好处是它带给了应用开发者更多的简洁,方便和易用。比如:如果你用的是RMI,你不需要关心消息是怎么序列化的,你只需要像本地方法调用一样,使用RMI。代价是:应用开发者无法很好地控制消息的序列化机制。

第四:RMITCP based socket相比,传输相同的有效数据,RMI需要占用更多的网络带宽(protocoloverhead)。从这里,我们可以得出一个一般性的结论:RMI主要是用于远程方法的调用RMI是多么的名符其实:),其技术内涵强调的是调用,基于此,我能想到的是:移动计算,和远程控制,当你的应用不需要在clientserver之间传输大量的数据时,RMI是较好的选择,它简洁、易于开发。但是,一旦你的应用需要在clientserver之间传输大量的数据,极端的,比如FTP应用,则RMI是不适合的,我们应该使用socket

 

3.2 RMI VS RPC

1. RPC 不支持对象,采用http协议

2. RMI支持传输对象。采用JRMP(Java Remote Method Protocol)通讯协议,是构建在TCP/IP协议上的一种远程调用方法

3. RMI只限于Java语言,RPC跨语言

RPCRMI的简单比较

  在RMIRPC之间最主要的区别在于方法是如何被调用的。在RMI中,远程接口使每个远程方法都具有方法签名。如果一个方法在服务器上执行,但是没有相匹配的签名被添加到这个远程接口上,那么这个新方法就不能被RMI客户方所调用。在RPC中,当一个请求到达RPC服务器时,这个请求就包含了一个参数集和一个文本值,通常形成“classname.methodname”的形式。这就向RPC服务器表明,被请求的方法在为 “classname”的类中,名叫“methodname”。然后RPC服务器就去搜索与之相匹配的类和方法,并把它作为那种方法参数类型的输入。这里的参数类型是与RPC请求中的类型是匹配的。一旦匹配成功,这个方法就被调用了,其结果被编码后返回客户方。

 

分布式计算系统要求运行在不同地址空间不同主机上的对象互相调用。各种分布式系统都有自己的调用协议,如CORBAIIOP(InternetInterORB Protocol), MTSDCOM。那么EJB组件呢?在Java里提供了完整的sockets通讯接口,但sockets要求客户端和服务端必须进行应用级协议的编码交换数据,采用sockets是非常麻烦的。 
一个代替Sockets的协议是RPC(Remote Procedure Call), 它抽象出了通讯接口用于过程调用,使得编程者调用一个远程过程和调用本地过程同样方便。RPC系统采用XDR来编码远程调用的参数和返回值。 

RPC 并不支持对象,而EJB构造的是完全面向对象的分布式系统,所以,面向对象的远程调用RMI(Remote Method Invocation)成为必然选择。采用RMI,调用远程对象和调用本地对象同样方便。RMI采用JRMP(Java Remote Method Protocol)通讯协议,是构建在TCP/IP协议上的一种远程调用方法。 

RMI
调用机制 
RMI
采用stubs skeletons 来进行远程对象(remote object)的通讯。stub 充当远程对象的客户端代理,有着和远程对象相同的远程接口,远程对象的调用实际是通过调用该对象的客户端代理对象stub来完成的。

3.3 HTTP VS SOCKET

1TCP连接

要想明白Socket连接,先要明白TCP连接。手机能够使用联网功能是因为手机底层实现了TCP/IP协议,可以使手机终端通过无线网络建立TCP连接。TCP协议可以对上层网络提供接口,使上层网络数据的传输建立在无差别的网络之上。

建立起一个TCP连接需要经过三次握手

第一次握手:客户端发送syn(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认;

第二次握手:服务器收到syn包,必须确认客户的SYNack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;

第三次握手:客户端收到服务器的SYNACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。

握手过程中传送的包里不包含数据,三次握手完毕后,客户端与服务器才正式开始传送数据。理想状态下,TCP连接一旦建立,在通信双方中的任何一方主动关闭连接之前,TCP 连接都将被一直保持下去。断开连接时服务器和客户端均可以主动发起断开TCP连接的请求,断开过程需要经过四次握手(过程就不细写了,就是服务器和客户端交互,最终确定断开)


2HTTP连接

HTTP协议即超文本传送协议(HypertextTransfer Protocol ),是Web联网的基础,也是手机联网常用的协议之一,HTTP协议是建立在TCP协议之上的一种应用。

HTTP连接最显著的特点是客户端发送的每次请求都需要服务器回送响应,在请求结束后,会主动释放连接。从建立连接到关闭连接的过程称为一次连接

1)在HTTP 1.0中,客户端的每次请求都要求建立一次单独的连接,在处理完本次请求后,就自动释放连接。

2
)在HTTP 1.1中则可以在一次连接中处理多个请求,并且多个请求可以重叠进行,不需要等待一个请求结束后再发送下一个请求。

由于HTTP在每次请求结束后都会主动释放连接,因此HTTP连接是一种短连接,要保持客户端程序的在线状态,需要不断地向服务器发起连接请求。通常的做法是即时不需要获得任何数据,客户端也保持每隔一段固定的时间向服务器发送一次保持连接的请求,服务器在收到该请求后对客户端进行回复,表明知道客户端在线。若服务器长时间无法收到客户端的请求,则认为客户端下线,若客户端长时间无法收到服务器的回复,则认为网络已经断开。


3SOCKET原理

3.1套接字(socket)概念

套接字(socket)是通信的基石,是支持TCP/IP协议的网络通信的基本操作单元。它是网络通信过程中端点的抽象表示,包含进行网络通信必须的五种信息:连接使用的协议,本地主机的IP地址,本地进程的协议端口,远地主机的IP地址,远地进程的协议端口。

应用层通过传输层进行数据通信时,TCP会遇到同时为多个应用程序进程提供并发服务的问题。多个TCP连接或多个应用程序进程可能需要通过同一个 TCP协议端口传输数据。为了区别不同的应用程序进程和连接,许多计算机操作系统为应用程序与TCPIP协议交互提供了套接字(Socket)接口。应用层可以和传输层通过Socket接口,区分来自不同应用程序进程或网络连接的通信,实现数据传输的并发服务。


3.2
建立socket连接
建立Socket连接至少需要一对套接字,其中一个运行于客户端,称为ClientSocket ,另一个运行于服务器端,称为ServerSocket

套接字之间的连接过程分为三个步骤:服务器监听,客户端请求,连接确认。

服务器监听:服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态,等待客户端的连接请求。

客户端请求:指客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就向服务器端套接字提出连接请求。

连接确认:当服务器端套接字监听到或者说接收到客户端套接字的连接请求时,就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发给客户端,一旦客户端确认了此描述,双方就正式建立连接。而服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求。


4SOCKET连接与TCP连接

创建Socket连接时,可以指定使用的传输层协议,Socket可以支持不同的传输层协议(TCPUDP),当使用TCP协议进行连接时,该Socket连接就是一个TCP连接。


5Socket连接与HTTP连接

由于通常情况下Socket连接就是TCP连接,因此Socket连接一旦建立,通信双方即可开始相互发送数据内容,直到双方连接断开。但在实际网络应用中,客户端到服务器之间的通信往往需要穿越多个中间节点,例如路由器、网关、防火墙等,大部分防火墙默认会关闭长时间处于非活跃状态的连接而导致 Socket 连接断连,因此需要通过轮询告诉网络,该连接处于活跃状态。

HTTP连接使用的是请求响应的方式,不仅在请求时需要先建立连接,而且需要客户端向服务器发出请求后,服务器端才能回复数据。

很多情况下,需要服务器端主动向客户端推送数据,保持客户端与服务器数据的实时与同步。此时若双方建立的是Socket连接,服务器就可以直接将数据传送给客户端;若双方建立的是HTTP连接,则服务器需要等到客户端发送一次请求后才能将数据传回给客户端,因此,客户端定时向服务器端发送连接请求,不仅可以保持在线,同时也是在询问服务器是否有新的数据,如果有就将数据传给客户端。

 

 

 

 

 

 

 

4redis集群(version3+自身才提供集群)

https://www.zhihu.com/question/21419897

1:为什么要集群?

通常,为了提高网站响应速度,总是把热点数据保存在内存中而不是直接从后端数据库中读取。Redis是一个很好的Cache工具。大型网站应用,热点数据量往往巨大,几十G上百G是很正常的事儿,在这种情况下,如何正确架构Redis呢?
   
首先,无论我们是使用自己的物理主机,还是使用云服务主机,内存资源往往是有限制的,scale up不是一个好办法,我们需要scale out横向可伸缩扩展,这需要由多台主机协同提供服务,即分布式多个Redis实例协同运行。
   
其次,目前硬件资源成本降低,多核CPU,几十G内存的主机很普遍,对于主进程是单线程工作的Redis,只运行一个实例就显得有些浪费。同时,管理一个巨大内存不如管理相对较小的内存高效。因此,实际使用中,通常一台机器上同时跑多个Redis实例

2:方案

1:主从架构—哨兵模式

通过哨兵Sentinel模式的主从结构

问题:单点故障

2:一致性哈希分片

Redis-2.4.15目前没有提供集群的功能,Redis作者在博客中说将在3.0中实现集群机制。目前Redis实现集群的方法主要是采用一致性哈稀分片(Shard,将不同的key分配到不同的redis server上,达到横向扩展的目的。

     对于一致性哈稀分片的算法Jedis-2.0.0已经提供了,下面是使用示例代码(以ShardedJedisPool为例):

package com.jd.redis.client;

import Java.util.ArrayList;

import java.util.List;

import redis.clients.jedis.JedisPoolConfig;

import redis.clients.jedis.JedisShardInfo;

import redis.clients.jedis.ShardedJedis;

import redis.clients.jedis.ShardedJedisPool;

import redis.clients.util.Hashing;

import redis.clients.util.Sharded;

publicclass RedisShardPoolTest {

    static ShardedJedisPoolpool;

    static{

        JedisPoolConfig config =new JedisPoolConfig();//Jedis池配置

        config.setMaxActive(500);//最大活动的对象个数

        config.setMaxIdle(1000 * 60);//对象最大空闲时间

        config.setMaxWait(1000 * 10);//获取对象时最大等待时间

        config.setTestOnBorrow(true);

         String hostA = "10.10.224.44";

        int portA = 6379;

        String hostB = "10.10.224.48";

        int portB = 6379;

        List<JedisShardInfo> jdsInfoList =new ArrayList<JedisShardInfo>(2);

        JedisShardInfo infoA = new JedisShardInfo(hostA, portA);

        infoA.setPassword("redis.360buy");

        JedisShardInfo infoB = new JedisShardInfo(hostB, portB);

        infoB.setPassword("redis.360buy");

        jdsInfoList.add(infoA);

        jdsInfoList.add(infoB);

        pool =new ShardedJedisPool(config, jdsInfoList, Hashing.MURMUR_HASH,

Sharded.DEFAULT_KEY_TAG_PATTERN);

    }

   

    /**

     * @param args

     */

    publicstaticvoid main(String[] args) {

        for(int i=0; i<100; i++){

            String key = generateKey();

            //key += "{aaa}";

            ShardedJedis jds = null;

            try {

                jds = pool.getResource();

                System.out.println(key+":"+jds.getShard(key).getClient().getHost());

                System.out.println(jds.set(key,"1111111111111111111111111111111"));

            } catch (Exception e) {

                e.printStackTrace();

            }

            finally{

                pool.returnResource(jds);

            }

        }

    }

 

    privatestaticintindex = 1;

    publicstatic String generateKey(){

        return String.valueOf(Thread.currentThread().getId())+"_"+(index++);

    }

}

从运行结果中可以看到,不同的key被分配到不同的Redis-Server上去了。

 

       实际上,上面的集群模式还存在两个问题

1.      扩容问题:

因为使用了一致性哈稀进行分片,那么不同的key分布到不同的Redis-Server上,当我们需要扩容时,需要增加机器到分片列表中,这时候会使得同样的key算出来落到跟原来不同的机器上,这样如果要取某一个值,会出现取不到的情况,对于这种情况,Redis的作者提出了一种名为Pre-Sharding的方式:

Pre-Sharding方法是将每一个台物理机上,运行多个不同断口的Redis实例,假如有三个物理机,每个物理机运行三个Redis实际,那么我们的分片列表中实际有9Redis实例,当我们需要扩容时,增加一台物理机,步骤如下:

A.    在新的物理机上运行Redis-Server

B.     Redis-Server从属于(slaveof)分片列表中的某一Redis-Server(假设叫RedisA);

C.     等主从复制(Replication)完成后,将客户端分片列表中RedisAIP和端口改为新物理机上Redis-ServerIP和端口;(只是相当于换了一台大机器,并没有增加机器的数量)

D.    停止RedisA

这样相当于将某一Redis-Server转移到了一台新机器上。Pre-Sharding实际上是一种在线扩容的办法,但还是很依赖Redis本身的复制功能的,如果主库快照数据文件过大,这个复制的过程也会很久,同时会给主库带来压力。所以做这个拆分的过程最好选择为业务访问低峰时段进行。

http://blog.nosqlfan.com/html/3153.html

2.      单点故障问题:

还是用到Redis主从复制的功能,两台物理主机上分别都运行有Redis-Server,其中一个Redis-Server是另一个的从库,采用双机热备技术,客户端通过虚拟IP访问主库的物理IP,当主库宕机时,切换到从库的物理IP。只是事后修复主库时,应该将之前的从库改为主库(使用命令slaveof no one),主库变为其从库(使命令slaveof IP PORT),这样才能保证修复期间新增数据的一致性。

 

3redis3+官方集群方案RedisCluster(服务器端分片sharding)
Redis Cluster是一种服务器Sharding技术,3.0版本开始正式提供。
    Redis Cluster
中,Sharding采用slot()的概念,一共分成16384个槽,这有点儿类似前面讲的presharding思路。对于每个进入Redis的键值对,根据key进行散列,分配到这16384slot中的某一个中。使用的hash算法也比较简单,就是CRC1616384取模。
    Redis
集群中的每个node(节点)负责分摊这16384slot中的一部分,也就是说,每个slot都对应一个node负责处理。当动态添加或减少node节点时,需要将16384个槽做个再分配,槽中的键值也要迁移。当然,这一过程,在目前实现中,还处于半自动状态,需要人工介入。
    Redis
集群,要保证16384个槽对应的node都正常工作,如果某个node发生故障,那它负责的slots也就失效,整个集群将不能工作。为了增加集群的可访问性,官方推荐的方案是node配置成主从结构,即一个master主节点,挂nslave从节点。这时,如果主节点失效,Redis Cluster会根据选举算法从slave节点中选择一个上升为主节点,整个集群继续对外提供服务。这非常类似前篇文章提到的RedisSharding场景下服务器节点通过Sentinel(哨兵)监控架构成主从结构,只是RedisCluster本身提供了故障转移容错的能力。
Redis Cluster
的新节点识别能力、故障判断及故障转移能力是通过集群中的每个node都在和其它nodes进行通信,这被称为集群总线(cluster bus)。它们使用特殊的端口号,即对外服务端口号加10000。例如如果某个node的端口号是6379,那么它与其它nodes通信的端口号是16379nodes之间的通信采用特殊的二进制协议。
    
对客户端来说,整个cluster被看做是一个整体,客户端可以连接任意一个node进行操作,就像操作单一Redis实例一样,当客户端操作的key没有分配到该node上时,Redis会返回转向指令,指向正确的node,这
有点儿像浏览器页面的302redirect跳转。
Redis Cluster
Redis3.0以后才正式推出,时间较晚,目前能证明在大规模生产环境下成功的案例还不是很多,需要时间检验。

4.Redis Sharding集群(客户端分片)

     Redis 3
正式推出了官方集群技术,解决了多Redis实例协同服务问题。Redis Cluster可以说是服务端Sharding分片技术的体现,即将键值按照一定算法合理分配到各个实例分片上,同时各个实例节点协调沟通,共同对外承担一致服务。多Redis实例服务,比单Redis实例要复杂的多,这涉及到定位、协同、容错、扩容等技术难题。这里,我们介绍一种轻量级的客户端Redis Sharding技术
Redis  Sharding
可以说是RedisCluster出来之前,业界普遍使用的多Redis实例集群方法。其主要思想是采用哈希算法Redis数据的key进行散列,通过hash函数,特定的key会映射到特定的Redis节点上。这样,客户端就知道该向哪个Redis节点操作数据。Sharding架构如图:
庆幸的是,javaredis客户端驱动jedis,已支持RedisSharding功能,即ShardedJedis以及结合缓存池的ShardedJedisPool
J
edisRedis Sharding实现具有如下特点:
1
:采用一致性哈希算法(consistenthashing),将key和节点name同时hashing,然后进行映射匹配,采用的算法是MURMUR_HASH。采用一致性哈希而不是采用简单类似哈希求模映射的主要原因是当增加或减少节点时,不会产生由于重新匹配造成的rehashing一致性哈希只影响相邻节点key分配,影响量小。
2.:
为了避免一致性哈希只影响相邻节点造成节点分配压力ShardedJedis会对每个Redis节点根据名字(没有,Jedis会赋予缺省名字)会虚拟化出160个虚拟节点进行散列。根据权重weight,也可虚拟化出160倍数的虚拟节点。用虚拟节点做映射匹配,可以在增加或减少Redis节点时,key在各Redis节点移动再分配更均匀,而不是只有相邻节点受影响。
3.ShardedJedis
支持keyTagPattern模式,即抽取key的一部分keyTagsharding,这样通过合理命名key,可以将一组相关联的key放入同一个Redis节点,这在避免跨节点访问相关数据时很重要。

Redis Sharding
采用客户端Sharding方式,服务端Redis还是一个个相对独立的Redis实例节点,没有做任何变动。同时,我们也不需要增加额外的中间处理组件,这是一种非常轻量、灵活的Redis多实例集群方法。
当然,RedisSharding这种轻量灵活方式必然在集群其它能力方面做出妥协。比如扩容,当想要增加Redis节点时,尽管采用一致性哈希,毕竟还是会有key匹配不到而丢失,这时需要键值迁移。
作为轻量级客户端sharding,处理Redis键值迁移是不现实的,这就要求应用层面允许Redis中数据丢失或从后端数据库重新加载数据。但有些时候,击穿缓存层,直接访问数据库层,会对系统访问造成很大压力。有没有其它手段改善这种情况?Redis
作者给出了一个比较讨巧的办法--presharding,即预先根据系统规模尽量部署好多个Redis实例,这些实例占用系统资源很小,一台物理机可部署多个,让他们都参与sharding,当需要扩容时,选中一个实例作为主节点,新加入的Redis节点作为从节点进行数据复制。数据同步后,修改sharding配置,让指向原实例的Shard指向新机器上扩容后的Redis节点,同时调整新Redis节点为主节点,原实例可不再使用。
presharding
是预先分配好足够的分片,扩容时只是将属于某一分片的原Redis实例替换成新的容量更大的Redis实例。参与sharding的分片没有改变,所以也就不存在key值从一个区转移到另一个分片区的现象,只是将属于同分片区的键值从原Redis实例同步到新Redis实例。

并不是只有增
Redis节点引起键值丢失问题,更大的障碍来自Redis节点突然宕机。在Redis持久化》一文中已提到,为不影响Redis性能,尽量不开启
AOF
RDB文件保存功能,可架构Redis主备模式,主Redis宕机,数据不会丢失,备Redis留有备份。
这样,我们的架构模式变
成一个Redis节点切片包含一个主Redis和一个备Redis。在主Redis宕机时,备Redis接管过来,上升为主Redis,继续提供服务。主备共同组成一个Redis节点,通过自动故障转移,保证了节点的高可用性。则Sharding架构演变成:

Redis Sentinel
提供了主备模式下Redis监控、故障转移功能达到系统的高可用性

高访问量下,即使采用Sharding分片,一个单独节点还是承担了很大的访问压力,这时我们还需要进一步分解。通常情况下,应用访问Redis读操作量和写操作量差异很大,读常常是写的数倍,这时我们可以将读写分离,而且读提供更多的实例数。
可以利用主从模式实现读写分离,主负责写,从负责只读,同时一主挂多个从。在Sentinel监控下,还可以保障节点故障的自动监测。

5.利用代理中间件实现大规模Redis集群
上面分别介绍了多Redis服务器集群的两种方式,它们是基于客户端shardingRedisSharding和基于服务端shardingRedisCluster

客户端sharding技术其优势在于服务端的Redis实例彼此独立,相互无关联,每个Redis实例像单服务器一样运行,非常容易线性扩展,系统的灵活性很强。其不足之处在于:
由于sharding处理放到客户端,规模进步扩大时给运维带来挑战。
服务端Redis实例群拓扑结构有变化时,每个客户端都需要更新调整。
连接不能共享,当应用规模增大时,资源浪费制约优化。
服务端shardingRedisCluster其优势在于服务端Redis集群拓扑结构变化时,客户端不需要感知,客户端像使用单Redis服务器一样使用Redis集群,运维管理也比较方便。
不过RedisCluster正式版推出时间不长,系统稳定性、性能等都需要时间检验,尤其在大规模使用场合。
能不能结合二者优势?即能使服务端各实例彼此独立,支持线性可伸缩,同时sharding又能集中处理,方便统一管理?本篇介绍的Redis代理中间件twemproxy就是这样一种利用中间件做sharding的技术。
twemproxy
处于客户端和服务器的中间,将客户端发来的请求,进行一定的处理后(sharding),再转发给后端真正的Redis服务器。也就是说,客户端不直接访问Redis服务器,而是通过twemproxy代理中间件间接访问。
参照RedisSharding架构,增加代理中间件的Redis集群架构如下:

twemproxy
中间件的内部处理是无状态的,它本身可以很轻松地集群,这样可避免单点压力或故障。
twemproxy
又叫nutcracker,起源于twitter系统中redis/memcached集群开发实践,运行效果良好,后代码奉献给开源社区。其轻量高效,采用C语言开发,工程网址是:GitHub- twitter/twemproxy: A fast, light-weight proxy for memcached and redis
twemproxy
后端不仅支持redis,同时也支持memcached,这是twitter系统具体环境造成的。
由于使用了中间件,twemproxy可以通过共享与后端系统的连接,降低客户端直接连接后端服务器的连接数量。同时,它也提供sharding功能,支持后端服务器集群水平扩展。统一运维管理也带来了方便。
当然,也是由于使用了中间件代理,相比客户端直连服务器方式,性能上会有所损耗,实测结果大约降低了20%左右。

 

5:负载均衡软件(Nginx|LVS|HAPROXY)

软件负载均衡一般通过两种方式来实现:基于操作系统的软负载实现基于第三方应用的软负载实现LVS就是基于Linux操作系统实现的一种软负载,HAProxy就是开源的并且基于第三应用实现的软负载。

Nginx/LVS/HAProxy是目前使用最广泛的三种负载均衡软件.

 

一般对负载均衡的使用是随着网站规模的提升根据不同的阶段来使用不同的技术。具体的应用需求还得具体分析,如果是中小型的Web应用,比如日PV小于1000万,用Nginx就完全可以了;如果机器不少,可以用DNS轮询,LVS所耗费的机器还是比较多的;大型网站或重要的服务,且服务器比较多时,可以考虑用LVS

一种:是通过硬件来进行,常见的硬件有比较昂贵的F5Array等商用的负载均衡器,它的优点就是有专业的维护团队来对这些服务进行维护、缺点就是花销太大,所以对于规模较小的网络服务来说暂时还没有需要使用;

另外一种:就是类似于Nginx/LVS/HAProxy的基于Linux的开源免费的负载均衡软件,这些都是通过软件级别来实现,所以费用非常低廉。

 

 

目前关于网站架构一般比较合理流行的架构方案

Web前端采用Nginx/HAProxy+Keepalived作负载均衡器;

后端采用MySQL数据库一主多从和读写分离,采用LVS+Keepalived的架构。当然要根据项目具体需求制定方案。

网络7层:物理层-数据链路层-网络层-传输层-会话层-表示层-应用层。
下面说说各自的特点和适用场合。

一、Nginx

Nginx的优点是:

1、工作在网络的7层之上,可以针对http应用做一些分流的策略,比如针对域名、目录结构,它的正则规则HAProxy更为强大和灵活,这也是它目前广泛流行的主要原因之一,Nginx单凭这点可利用的场合就远多于LVS了。
2
Nginx对网络稳定性的依赖非常小,理论上能ping通就就能进行负载功能,这个也是它的优势之一;相反LVS对网络稳定性依赖比较大,这点本人深有体会;
3
Nginx安装和配置比较简单,测试起来比较方便,它基本能把错误用日志打印出来。LVS的配置、测试就要花比较长的时间了,LVS对网络依赖比较大。
3
、可以承担高负载压力且稳定,在硬件不差的情况下一般能支撑几万次的并发量,负载度比LVS相对小些。
4
Nginx可以通过端口检测到服务器内部的故障,比如根据服务器处理网页返回的状态码、超时等等,并且会把返回错误的请求重新提交到另一个节点,不过其中缺点就是不支持url来检测。比如用户正在上传一个文件,而处理该上传的节点刚好在上传过程中出现故障,Nginx会把上传切到另一台服务器重新处理,而LVS就直接断掉了,如果是上传一个很大的文件或者很重要的文件的话,用户可能会因此而不满。
5
Nginx不仅仅是一款优秀的负载均衡器/反向代理软件,它同时也是功能强大的Web应用服务器LNMP也是近几年非常流行的web架构,在高流量的环境中稳定性也很好。(LNMP是一个基于CentOS/Debian编写的NginxPHPMySQLphpMyAdmineAccelerator一键安装包
6
Nginx现在作为Web反向加速缓存越来越成熟了,速度比传统的Squid服务器更快,可以考虑用其作为反向代理加速器。
7
Nginx可作为中层反向代理使用,这一层面Nginx基本上无对手,唯一可以对比Nginx的就只有lighttpd了,不过lighttpd目前还没有做到Nginx完全的功能,配置也不那么清晰易读,社区资料也远远没Nginx活跃。
8
Nginx也可作为静态网页和图片服务器,这方面的性能也无对手。还有Nginx社区非常活跃,第三方模块也很多。

淘宝的前端使用的Tengine就是基于nginx做的二次开发定制版。

Nginx常规的HTTP请求和响应流程图:

 

Nginx的缺点是:
1
Nginx仅能支持httphttpsEmail协议,这样就在适用范围上面小些,这个是它的缺点。
2
对后端服务器的健康检查,只支持通过端口来检测,不支持通过url来检测。不支持Session的直接保持,但能通过ip_hash来解决。

二、LVSLinux Virtual Server

LVS:使用Linux内核集群实现一个高性能、高可用的负载均衡服务器,它具有很好的可伸缩性(Scalability)、可靠性(Reliability)和可管理性(Manageability)

LVS的优点是:
1
、抗负载能力强、是工作在网络4层之上仅作分发之用,没有流量的产生,这个特点也决定了它在负载均衡软件里的性能最强的,对内存和cpu资源消耗比较低。
2
、配置性比较低,这是一个缺点也是一个优点,因为没有可太多配置的东西,所以并不需要太多接触,大大减少了人为出错的几率。
3
、工作稳定,因为其本身抗负载能力很强,自身有完整的双机热备方案,如LVS+Keepalived,不过我们在项目实施中用得最多的还是LVS/DR+Keepalived
4
、无流量,LVS只分发请求,而流量并不从它本身出去,这点保证了均衡器IO的性能不会收到大流量的影响。
5
、应用范围比较广,因为LVS工作在4层,所以它几乎可以对所有应用做负载均衡,包括http、数据库、在线聊天室等等。

LVS DR(Direct Routing)模式的网络流程图:

LVS的缺点是:
1
、软件本身不支持正则表达式处理,不能做动静分离;而现在许多网站在这方面都有较强的需求,这个是Nginx/HAProxy+Keepalived的优势所在。
2
、如果是网站应用比较庞大的话,LVS/DR+Keepalived实施起来就比较复杂了,特别后面有Windows Server的机器的话,如果实施及配置还有维护过程就比较复杂了,相对而言,Nginx/HAProxy+Keepalived就简单多了。

三、HAProxy

HAProxy的特点是:
1
HAProxy也是支持虚拟主机的。
2
HAProxy的优点能够补充Nginx的一些缺点,比如支持Session的保持,Cookie的引导;同时支持通过获取指定的url来检测后端服务器的状态。
3
HAProxyLVS类似,本身就只是一款负载均衡软件;单纯从效率上来讲HAProxy会比Nginx有更出色的负载均衡速度,在并发处理上也是优于Nginx的。
4
HAProxy支持TCP协议的负载均衡转发,可以对MySQL读进行负载均衡,对后端的MySQL节点进行检测和负载均衡,大家可以用LVS+KeepalivedMySQL主从做负载均衡。
5
HAProxy负载均衡策略非常多,HAProxy的负载均衡算法现在具体有如下8种:
roundrobin,表示简单的轮询,这个不多说,这个是负载均衡基本都具备的;
static-rr,表示根据权重,建议关注;
leastconn,表示最少连接者先处理,建议关注;
source,表示根据请求源IP,这个跟NginxIP_hash机制类似,我们用其作为解决session问题的一种方法,建议关注;
ri,表示根据请求的URI
rl_param,表示根据请求的URl参数’balanceurl_param’ requires an URL parameter name
hdr(name),表示根据HTTP请求头来锁定每一次HTTP请求;
rdp-cookie(name),表示根据据cookie(name)来锁定并哈希每一次TCP请求。

四、总结

NginxLVS对比的总结:
1
Nginx工作在网络的7层,所以它可以针对http应用本身来做分流策略,比如针对域名、目录结构等,相比之下LVS并不具备这样的功能,所以Nginx单凭这点可利用的场合就远多于LVS了;但Nginx有用的这些功能使其可调整度要高于LVS,所以经常要去触碰触碰,触碰多了,人为出问题的几率也就会大。
2
Nginx对网络稳定性的依赖较小,理论上只要ping得通,网页访问正常,Nginx就能连得通,这是Nginx的一大优势!Nginx同时还能区分内外网,如果是同时拥有内外网的节点,就相当于单机拥有了备份线路;LVS就比较依赖于网络环境,目前来看服务器在同一网段内并且LVS使用direct方式分流,效果较能得到保证。另外注意,LVS需要向托管商至少申请多一个ip来做Visual IP,貌似是不能用本身的IP来做VIP的。要做好LVS管理员,确实得跟进学习很多有关网络通信方面的知识,就不再是一个HTTP那么简单了。
3
Nginx安装和配置比较简单,测试起来也很方便,因为它基本能把错误用日志打印出来。LVS的安装和配置、测试就要花比较长的时间了;LVS对网络依赖比较大,很多时候不能配置成功都是因为网络问题而不是配置问题,出了问题要解决也相应的会麻烦得多。
4
Nginx也同样能承受很高负载且稳定,但负载度和稳定度差LVS还有几个等级:Nginx处理所有流量所以受限于机器IO和配置;本身的bug也还是难以避免的。
5
Nginx可以检测到服务器内部的故障,比如根据服务器处理网页返回的状态码、超时等等,并且会把返回错误的请求重新提交到另一个节点。目前LVS ldirectd也能支持针对服务器内部的情况来监控,但LVS的原理使其不能重发请求。比如用户正在上传一个文件,而处理该上传的节点刚好在上传过程中出现故障,Nginx会把上传切到另一台服务器重新处理,而LVS就直接断掉了,如果是上传一个很大的文件或者很重要的文件的话,用户可能会因此而恼火。
6
Nginx对请求的异步处理可以帮助节点服务器减轻负载,假如使用apache直接对外服务,那么出现很多的窄带链接时apache服务器将会占用大量内存而不能释放,使用多一个Nginxapache代理的话,这些窄带链接会被Nginx挡住,apache上就不会堆积过多的请求,这样就减少了相当多的资源占用。这点使用squid也有相同的作用,即使squid本身配置为不缓存,对apache还是有很大帮助的。
7
Nginx能支持httphttpsemailemail的功能比较少用),LVS所支持的应用在这点上会比Nginx更多。在使用上,一般最前端所采取的策略应是LVS,也就是DNS的指向应为LVS均衡器,LVS的优点令它非常适合做这个任务。重要的ip地址,最好交由LVS托管,比如数据库的ipwebservice服务器的ip等等,这些ip地址随着时间推移,使用面会越来越大,如果更换ip则故障会接踵而至。所以将这些重要ip交给 LVS托管是最为稳妥的,这样做的唯一缺点是需要的VIP数量会比较多。Nginx可作为LVS节点机器使用,一是可以利用Nginx的功能,二是可以利用Nginx的性能。当然这一层面也可以直接使用squidsquid的功能方面就比Nginx弱不少了,性能上也有所逊色于NginxNginx也可作为中层代理使用,这一层面Nginx基本上无对手,唯一可以撼动Nginx的就只有lighttpd了,不过lighttpd目前还没有能做到 Nginx完全的功能,配置也不那么清晰易读。另外,中层代理的IP也是重要的,所以中层代理也拥有一个VIPLVS是最完美的方案了。具体的应用还得具体分析,如果是比较小的网站(日PV小于1000万),用Nginx就完全可以了,如果机器也不少,可以用DNS轮询,LVS所耗费的机器还是比较多的;大型网站或者重要的服务,机器不发愁的时候,要多多考虑利用LVS

现在对网络负载均衡的使用是随着网站规模的提升根据不同的阶段来使用不同的技术:

第一阶段:利用NginxHAProxy进行单点的负载均衡,这一阶段服务器规模刚脱离开单服务器、单数据库的模式,需要一定的负载均衡,但是仍然规模较小没有专业的维护团队来进行维护,也没有需要进行大规模的网站部署。这样利用NginxHAproxy就是第一选择,此时这些东西上手快,配置容易,在七层之上利用HTTP协议就可以。这时是第一选择。

第二阶段:随着网络服务进一步扩大,这时单点的Nginx已经不能满足,这时使用LVS或者商用Array就是首要选择,Nginx此时就作为LVS或者Array的节点来使用,具体LVSArray的是选择是根据公司规模和预算来选择,Array的应用交付功能非常强大,本人在某项目中使用过,性价比也远高于F5,商用首选!但是一般来说这阶段相关人才跟不上业务的提升,所以购买商业负载均衡已经成为了必经之路。

第三阶段:这时网络服务已经成为主流产品,此时随着公司知名度也进一步扩展,相关人才的能力以及数量也随之提升,这时无论从开发适合自身产品的定制,以及降低成本来讲开源的LVS,已经成为首选,这时LVS会成为主流。
最终形成比较理想的基本架构为:Array/LVS — Nginx/Haproxy — Squid/Varnish — AppServer

 

五:keepalived

keepalived是一个类似于layer3, 4 & 5交换机制的软件,也就是我们平时说的第3层、第4层和第5层交换。Keepalived的作用是检测web服务器的状态,如果有一台web服务器死机,或工作出现故障,Keepalived将检测到,并将有故障的web服务器从系统中剔除,当web服务器工作正常后Keepalived自动将web服务器加入到服务器群中,这些工作全部自动完成,不需要人工干涉,需要人工做的只是修复故障的web服务器。

类似的HA工具还有heatbeatdrbd等,heatbeatdrbd配置都较为复杂。

Keepalived主要解决failover问题。即:主服务宕机了后,自动切换从从服务中选举新主服务,保证服务的正常运行。Haproxy/lvs主要解决负载均衡的问题。所有就有haproxy/lvs+keepalived的架构。

使用keepalived实现双机热备

通常说的双机热备是指两台机器都在运行,但并不是两台机器都同时在提供服务。
当提供服务的一台出现故障的时候,另外一台会马上自动接管并且提供服务,而且切换的时间非常短。
下面来以keepalived结合tomcat来实现一个web服务器的双机热备。
keepalived
的工作原理是VRRPVirtual Router RedundancyProtocol虚拟路由冗余协议
VRRP中有两组重要的概念:VRRP路由器和虚拟路由器,主控路由器和备份路由器。
VRRP
路由器是指运行VRRP的路由器,是物理实体,虚拟路由器是指VRRP协议创建的,是逻辑概念。一组VRRP路由器协同工作,共同构成一台虚拟路由器。 Vrrp中存在着一种选举机制,用以选出提供服务的路由即主控路由,其他的则成了备份路由。当主控路由失效后,备份路由中会重新选举出一个主控路由,来继续工作,来保障不间断服务。

6:zookeeper

 

7mybatis

 

7.1 datasource type

http://blog.csdn.net/crave_shy/article/details/46584803

 

 

7.2 transactionManager type

参考:http://www.tuicool.com/articles/iQNZvyf

 

两个取值

1:JDBC,mybatis自己管理事务

2:managed 自己不管理,交给web容器(weblogic,jboss)去管理。

MyBatis作为Java语言的数据库框架,对数据库的事务管理是其非常重要的一个方面。本文将讲述MyBatis的事务管理的实现机制。首先介绍MyBatis的事务Transaction的接口设计以及其不同实现 JdbcTransaction  ManagedTransaction ;接着,从MyBatisXML配置文件入手,讲解MyBatis事务工厂的创建和维护,进而阐述了MyBatis事务的创建和使用;最后分析 JdbcTransaction  ManagedTransaction 的实现和二者的不同特点。

以下是本文的组织结构:

一、概述

    对数据库的事务而言,应该具有以下几点:创建(create)、提交(commit)、回滚(rollback)、关闭(close。对应地,MyBatis将事务抽象成了Transaction接口:其接口定义如下:

MyBatis的事务管理分为两种形式:

一、使用JDBC的事务管理机制 :即利用java.sql.Connection对象完成对事务的提交(commit())、回滚(rollback())、关闭(close())等

二、使用MANAGED的事务管理机制: 这种机制MyBatis自身不会去实现事务管理,而是让程序的容器如(JBOSSWeblogic)来实现对事务的管理

这两者的类图如下所示:

二、事务的配置、创建和使用

1. 事务的配置

我们在使用MyBatis时,一般会在MyBatisXML配置文件中定义类似如下的信息:

environment >节点定义了连接某个数据库的信息,其子节点transactionManager > type 会决定我们用什么类型的事务管理机制。

2.事务工厂的创建

MyBatis事务的创建是交给TransactionFactory事务工厂来创建的,如果我们将<transactionManager>type 配置为"JDBC",那么,在MyBatis初始化解析<environment>节点时,会根据type="JDBC"创建一个JdbcTransactionFactory工厂,其源码如下:

/**
     * 解析<transactionManager>节点,创建对应的TransactionFactory
     * @param context
     * @return
     * @throws Exception
     */
  privateTransactionFactory transactionManagerElement(XNode context) throwsException {
    if (context != null) {
        Stringtype= context.getStringAttribute("type");
        Properties props = context.getChildrenAsProperties();
        /*
               Configuration初始化的时候,会通过以下语句,给JDBCMANAGED对应的工厂类
               typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
               typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
               下述的resolveClass(type).newInstance()会创建对应的工厂实例
         */
        TransactionFactory factory = (TransactionFactory) resolveClass(type).newInstance();
        factory.setProperties(props);
        return factory;
    }
    thrownewBuilderException("Environment declaration requires a TransactionFactory.");
  }

  

  如上述代码所示,如果type = "JDBC",MyBatis会创建一个 JdbcTransactionFactory.class 实例;如果type="MANAGED",则MyBatis会创建一个MangedTransactionFactory.class

实例。

 MyBatistransactionManager >节点的解析会生成TransactionFactory实例;而对dataSource >解析会生成datasouce实例(关于dataSource的解析和原理,读者可以参照我的另一篇博文: 《深入理解mybatis原理》 Mybatis数据源与连接池

  

),作为environment >节点,会根据TransactionFactoryDataSource实例创建一个Environment对象,代码如下所示:

private void environmentsElement(XNode context) throws Exception {
    if (context != null) {
      if (environment == null) {
        environment = context.getStringAttribute("default");
      }
      for (XNode child : context.getChildren()) {
        String id = child.getStringAttribute("id");
        //是和默认的环境相同时,解析之
        if (isSpecifiedEnvironment(id)) {
          //1.解析<transactionManager>节点,决定创建什么类型的TransactionFactory
          TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
          //2. 创建dataSource
          DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
          DataSource dataSource = dsFactory.getDataSource();
          //3. 使用了Environment内置的构造器Builder,传递id 事务工厂TransactionFactory和数据源DataSource
          Environment.Builder environmentBuilder = new Environment.Builder(id)
              .transactionFactory(txFactory)
              .dataSource(dataSource);
          configuration.setEnvironment(environmentBuilder.build());
        }
      }
    }
  }

  

  Environment 表示着一个数据库的连接,生成后的Environment对象会被设置到Configuration实例中,以供后续的使用。

上述一直在讲事务工厂TransactionFactory来创建的Transaction,现在让我们看一下MyBatis中的TransactionFactory的定义吧。

3. 事务工厂TransactionFactory

事务工厂Transaction定义了创建Transaction的两个方法:一个是通过指定的Connection对象创建Transaction,另外是通过数据源DataSource来创建Transaction。与JDBC MANAGED两种Transaction相对应,TransactionFactory有两个对应的实现的子类:如下所示:

4. 事务Transaction的创建

通过事务工厂TransactionFactory很容易获取到Transaction对象实例。我们以JdbcTransaction为例,看一下JdbcTransactionFactory是怎样生成JdbcTransaction的,代码如下:

publicclassJdbcTransactionFactoryimplementsTransactionFactory{
 
  publicvoidsetProperties(Properties props){
  }
 
    /**
     * 根据给定的数据库连接Connection创建Transaction
     * @param conn Existing database connection
     * @return
     */
  public Transaction newTransaction(Connection conn){
    returnnew JdbcTransaction(conn);
  }
 
    /**
     * 根据DataSource、隔离级别和是否自动提交创建Transacion
     *
     * @param ds
     * @param level Desired isolation level
     * @param autoCommit Desired autocommit
     * @return
     */
  public Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit){
    returnnew JdbcTransaction(ds, level, autoCommit);
  }
}

  如上说是,JdbcTransactionFactory会创建JDBC类型的Transaction,即JdbcTransaction。类似地,ManagedTransactionFactory也会创建ManagedTransaction。下面我们会分别深入JdbcTranaction ManagedTransaction,看它们到底是怎样实现事务管理的。

5. JdbcTransaction

   JdbcTransaction直接使用JDBC的提交和回滚事务管理机制。它依赖与从dataSource中取得的连接connection 来管理transaction 的作用域,connection对象的获取被延迟到调用getConnection()方法。如果autocommit设置为on,开启状态的话,它会忽略commitrollback

    直观地讲,就是JdbcTransaction是使用的java.sql.Connection 上的commitrollback功能,JdbcTransaction只是相当于对java.sql.Connection事务处理进行了一次包装(wrapper),Transaction的事务管理都是通过java.sql.Connection实现的。JdbcTransaction的代码实现如下:

/**
 * @see JdbcTransactionFactory
 */
/**
 * @author Clinton Begin
 */
publicclassJdbcTransactionimplementsTransaction{
 
  privatestaticfinal Log log = LogFactory.getLog(JdbcTransaction.class);
 
  //数据库连接
  protected Connection connection;
  //数据源
  protected DataSource dataSource;
  //隔离级别
  protected TransactionIsolationLevel level;
  //是否为自动提交
  protectedboolean autoCommmit;
 
  publicJdbcTransaction(DataSource ds, TransactionIsolationLevel desiredLevel, boolean desiredAutoCommit){
    dataSource = ds;
    level = desiredLevel;
    autoCommmit = desiredAutoCommit;
  }
 
  publicJdbcTransaction(Connection connection){
    this.connection = connection;
  }
 
  public Connection getConnection()throws SQLException {
    if (connection == null) {
      openConnection();
    }
    return connection;
  }
 
    /**
     * commit()功能使用connectioncommit()
     * @throws SQLException
     */
  publicvoidcommit()throws SQLException {
    if (connection != null && !connection.getAutoCommit()) {
      if (log.isDebugEnabled()) {
        log.debug("Committing JDBC Connection [" + connection + "]");
      }
      connection.commit();
    }
  }
 
    /**
     * rollback()功能使用connectionrollback()
     * @throws SQLException
     */
  publicvoidrollback()throws SQLException {
    if (connection != null && !connection.getAutoCommit()) {
      if (log.isDebugEnabled()) {
        log.debug("Rolling back JDBC Connection [" + connection + "]");
      }
      connection.rollback();
    }
  }
 
    /**
     * close()功能使用connectionclose()
     * @throws SQLException
     */
  publicvoidclose()throws SQLException {
    if (connection != null) {
      resetAutoCommit();
      if (log.isDebugEnabled()) {
        log.debug("Closing JDBC Connection [" + connection + "]");
      }
      connection.close();
    }
  }
 
  protectedvoidsetDesiredAutoCommit(boolean desiredAutoCommit){
    try {
      if (connection.getAutoCommit() != desiredAutoCommit) {
        if (log.isDebugEnabled()) {
          log.debug("Setting autocommit to " + desiredAutoCommit + " on JDBC Connection [" + connection + "]");
        }
        connection.setAutoCommit(desiredAutoCommit);
      }
    } catch (SQLException e) {
      // Only a very poorly implemented driver would fail here,
      // and there's not much we can do about that.
      thrownew TransactionException("Error configuring AutoCommit.  "
          + "Your driver may not support getAutoCommit() or setAutoCommit(). "
          + "Requested setting: " + desiredAutoCommit + ".  Cause: " + e, e);
    }
  }
 
  protectedvoidresetAutoCommit(){
    try {
      if (!connection.getAutoCommit()) {
        // MyBatis does not call commit/rollback on a connection if just selects were performed.
        // Some databases start transactions with select statements
        // and they mandate a commit/rollback before closing the connection.
        // A workaround is setting the autocommit to true before closing the connection.
        // Sybase throws an exception here.
        if (log.isDebugEnabled()) {
          log.debug("Resetting autocommit to true on JDBC Connection [" + connection + "]");
        }
        connection.setAutoCommit(true);
      }
    } catch (SQLException e) {
      log.debug("Error resetting autocommit to true "
          + "before closing the connection.  Cause: " + e);
    }
  }
 
  protectedvoidopenConnection()throws SQLException {
    if (log.isDebugEnabled()) {
      log.debug("Opening JDBC Connection");
    }
    connection = dataSource.getConnection();
    if (level != null) {
      connection.setTransactionIsolation(level.getLevel());
    }
    setDesiredAutoCommit(autoCommmit);
  }
 
}

6. ManagedTransaction

  ManagedTransaction让容器来管理事务Transaction的整个生命周期,意思就是说,使用ManagedTransactioncommitrollback功能不会对事务有任何的影响,它什么都不会做,它将事务管理的权利移交给了容器来实现。看如下Managed的实现代码大家就会一目了然:

/**
 * 
 * 让容器管理事务transaction的整个生命周期
 * connection的获取延迟到getConnection()方法的调用
 * 忽略所有的commitrollback操作
 * 默认情况下,可以关闭一个连接connection,也可以配置它不可以关闭一个连接
 * 让容器来管理transaction的整个生命周期
 * @see ManagedTransactionFactory
 */
/**
 * @author Clinton Begin
 */
publicclassManagedTransactionimplementsTransaction{
 
  privatestaticfinal Log log = LogFactory.getLog(ManagedTransaction.class);
 
  private DataSource dataSource;
  private TransactionIsolationLevel level;
  private Connection connection;
  privateboolean closeConnection;
 
  publicManagedTransaction(Connection connection, boolean closeConnection){
    this.connection = connection;
    this.closeConnection = closeConnection;
  }
 
  publicManagedTransaction(DataSource ds, TransactionIsolationLevel level, boolean closeConnection){
    this.dataSource = ds;
    this.level = level;
    this.closeConnection = closeConnection;
  }
 
  public Connection getConnection()throws SQLException {
    if (this.connection == null) {
      openConnection();
    }
    returnthis.connection;
  }
 
  publicvoidcommit()throws SQLException {
    // Does nothing
  }
 
  publicvoidrollback()throws SQLException {
    // Does nothing
  }
 
  publicvoidclose()throws SQLException {
    if (this.closeConnection && this.connection != null) {
      if (log.isDebugEnabled()) {
        log.debug("Closing JDBC Connection [" + this.connection + "]");
      }
      this.connection.close();
    }
  }
 
  protectedvoidopenConnection()throws SQLException {
    if (log.isDebugEnabled()) {
      log.debug("Opening JDBC Connection");
    }
    this.connection = this.dataSource.getConnection();
    if (this.level != null) {
      this.connection.setTransactionIsolation(this.level.getLevel());
    }
  }
 
}

注意:如果我们使用MyBatis构建本地程序,即不是WEB程序,若将type设置成"MANAGED",那么,我们执行的任何update操作,即使我们最后执行了commit操作,数据也不会保留,不会对数据库造成任何影响。因为我们将MyBatis配置成了“MANAGED”,即MyBatis自己不管理事务,而我们又是运行的本地程序,没有事务管理功能,所以对数据库的update操作都是无效的。

以上就是 《深入理解mybatis原理》 MyBatis事务管理机制 的全部内容,如有错误或者不准确的地方,请读者指正,共同进步!

-----------------------------------------------------------------------------------------------------------------------------------------

本文源自 http://blog.csdn.net/luanlouis/ ,如需转载,请注明出处,谢谢!

8:monogodb(3.0.3)

参考:http://kb.cnblogs.com/page/152296/

 

8.1:基本概念

数据库:数据库

集合:表

文档:行

8.2 bin文件下的命令

8.3:基本操作

插入insert()  :只支持单条插入,批量插入可以用for循环

查询find():

更新update({查询条件},{更新值},{没有是否插入},{批量更新:是否更新所有匹配条件的数据})

删除remove({条件})

8.4:高级操作

一:聚合

二:游标

8.5 索引

1:性能分析(插入10万数据)

仔细看红色区域,有几个我们关心的key

cursor:这里出现的是”BasicCursor",什么意思呢,就是说这里的查找采用的是表扫描,也就是顺序查找,很悲催啊。

nscanned:这里是10w,也就是说数据库浏览了10w个文档,很恐怖吧,这样玩的话让人受不了啊。

n:这里是1,也就是最终返回了1个文档。

millis:这个就是我们最最最....关心的东西,总共耗时114毫秒。 

2:建立索引

3:唯一索引

4:组合索引

实际上建立了两个不同的索引

5:删除索引

8.6:主从架构—集群1

1:好处

1.1:数据备份:从数据库负责备份主数据库

1.2:数据恢复:一旦数据丢失,可以从另一台服务器恢复

1.3:读写分离:主服务负责存储数据,从服务负责读取数据,因为读操作比存多,从服务比主服务多。

 

 

 

 

我们mongodb文件夹放在D盘和E盘,模拟放在多服务器上

 

2:设置主服务

启动D盘上的mongodb,把该数据库指定为主数据库,其实命令很简单:>mongodb --dbpath='XXX' --master,端口还是默认的27017.

3:设置从服务器

同样的方式启动E盘上的mongodb,指定该数据库为从属数据库,命令也很简单,当然我们要换一个端口,比如:8888

source 表示主数据库的地址。

>mongod--dbpath=xxxx --port=8888 --slave --source=127.0.0.1:27017

 

8.7:副本集—集群2

1:要点

1.1:没有待定的主从之分,即:启动的时候无需指导谁主谁从,由集群自己去选举。每个服务器对等,但也需要一主多从

1.2:需要一个副本集名称

1.3:动态故障恢复功能failover,即:主服务器坏了后,会自动从从服务器中选举新的主服务器。

  这点在单纯的“主从架构“中就无法实现,得手动去设置。

1.4:还需要一个仲裁服务器,在admin集合中使用rs.addArb(仲裁服务器ipport)追加即可。

1.5:可以使用rs.status()查询集群中各个服务器的状态

2:步骤

第一步:  既然我们要建立集群,就得取个集群名字,这里就取我们的公司名shopex, --replSet表示让服务器知道shopex还有其他数据库,这里就把D盘里面的mongodb程序打开,端口为2222。指定端口为3333shopex集群下的另一个数据库服务器。

  第二步:  既然上面说3333是另一个数据库服务器,不要急,现在就来开,这里把E盘的mongodb程序打开。

第三步:  ok,看看上面的日志红色区域,似乎我们还没有做完,是的,log信息告诉我们要初始化一下副本集,既然日志这么说,那我也就这么做,随便连接一下哪个服务器都行,不过一定要进入admin集合。

 

  第四步:开启成功后,我们要看看谁才能成为主数据库服务器,可以看到端口为2222的已经成为主数据库服务器。

  第五步:我们知道sql server里面有一个叫做仲裁服务器,那么mongodb中也是有的,跟sql server一样,仲裁只参与投票选举,这里我们把F盘的mongodb作为仲裁服务器,然后指定shopex集群中的任一个服务器端口,这里就指定2222

追加好了之后,我们使用rs.status()来查看下集群中的服务器状态,图中我们可以清楚的看到谁是主,还是从,还是仲裁。

  不是说该集群有自动故障恢复吗?那么我们就可以来试一下,在2222端口的cmd服务器按Ctrl+CKO掉该服务器,立马我们发现在3333端口的从属服务器即可顶上,最后大家也可以再次使用rs.status()来看下集群中服务器的状态。

 

8.8::分片技术—集群3

 在mongodb里面存在另一种集群,就是分片技术,跟sql server表分区类似,我们知道当数据量达到T级别的时候,我们的磁盘,内存就吃不消了,针对这样的场景我们该如何应对。

一:分片

mongodb采用将集合进行拆分,然后将拆分的数据均摊到几个片上的一种解决方案。

  下面我对这张图解释一下:

  人脸:代表客户端,客户端肯定说,你数据库分片不分片跟我没关系,我叫你干啥就干啥,没什么好商量的。

mongos:首先我们要了解片键的概念,也就是说拆分集合的依据是什么?按照什么键值进行拆分集合....好了,mongos就是一个路由服务器,它会根据管理员设置的片键将数据分摊到自己管理的mongod集群,数据和片的对应关系以及相应的配置信息保存在"config服务器"上。

mongod:一个普通的数据库实例,如果不分片的话,我们会直接连上mongod

二: 实战

  首先我们准备4mongodb程序,我这里是均摊在CDEF盘上,当然你也可以做多个文件夹的形式。

  1:开启config服务器

  先前也说了,mongos要把mongod之间的配置放到config服务器里面,理所当然首先开启它,我这里就建立2222端口。

  2: 开启mongos服务器

  这里要注意的是我们开启的是mongos,不是mongod同时指定下config服务器,这里我就开启D盘上的mongodb,端口3333

  3:启动mongod服务器

  对分片来说,也就是要添加了,这里开启EF盘的mongodb,端口为:44445555

  4:服务配置

  哈哈,是不是很兴奋,还差最后一点配置我们就可以大功告成。

 <1> 先前图中也可以看到,我们client直接跟mongos打交道,也就说明我们要连接mongos服务器,然后将44445555mongod交给mongos,添加分片也就是addshard()

  这里要注意的是,在addshard中,我们也可以添加副本集,这样能达到更高的稳定性。

<2>片已经集群了,但是mongos不知道该如何切分数据,也就是我们先前所说的片键,在mongodb中设置片键要做两步

开启数据库分片功能,命令很简单enablesharding(),这里我就开启test数据库。

指定集合中分片的片键,这里我就指定为person.name字段。

5: 查看效果

  好了,至此我们的分片操作全部结束,接下来我们通过mongosmongodb插入10w记录,然后通过printShardingStatus命令查看mongodb的数据分片情况。

 这里主要看三点信息:

  shards:我们清楚的看到已经别分为两个片了,shard0000shard0001

  databases:这里有个partitioned字段表示是否分区,这里清楚的看到test已经分区。

  chunks:这个很有意思,我们发现集合被砍成四段:无穷小 —— jack0jack0 ——jack234813jack234813——jack9999jack9999——无穷大。分区情况为:31,从后面的 on shardXXXX也能看得出。

8.9:运维技术

  这一篇我们以管理员的视角来看mongodb,作为一名管理员,我们经常接触到的主要有4个方面:

1、安装部署

2、状态监控

3、安全认证

4、备份和恢复,

  下面我们就一点一点的讲解

  一:安装部署

  我之前的文章都是采用console程序来承载,不过在生产环境中这并不是最佳实践,谁也不愿意在机器重启后满地找牙似找mongodb,在mongodb里面提供了一个叫做服务寄宿的模式,我想如果大家对wcf比较熟悉的话很容易听懂。好了,我们实践一下,这里我开一下D盘里面的mongodb

  这里要注意的有两点:

<1> logpath: 当我们使用服务寄宿的时候,用眼睛都能想明白肯定不会用console来承载日志信息了。

<2> install:   开启安装服务寄宿,很happy啊,把管理员的手工操作降低到最小,感谢mongodb

  好了,console程序叫我看log日志,那我就看看,发现mongodb已经提示我们如何开启mongodb,接着我照做就是了。

  还要提醒大家一点的就是,这些命令参数很多很复杂也就很容易忘,不过没关系,数据库给我们提供了一个help方法,我们可以拿mongodmongo说事。

mongod

mongo

二:状态监控

  监控可以让我们实时的了解数据库的健康状况以及性能调优,在mongodb里面给我们提供了三种方式。

1http监视器静态

  这个我在先前的文章中也提到了,这里就不赘述了。

2serverStatus()—静态

  这个函数可以获取到mongodb的服务器统计信息,其中包括全局锁,索引,用户操作行为等等这些统计信息,对管理员来说非常重要,具体的参数含义可以参考园友:http://www.cnblogs.com/xuegang/archive/2011/10/13/2210339.html

  这里还是截个图混个眼熟。

3mongostat—动态

  前面那些统计信息再牛X,那也是静态统计,不能让我观看实时数据变化,还好,mongodb里面提供了这里要说的mongostat监视器,这玩意会每秒刷新,在实际生产环境中大有用处,还是截张图,很有意思,是不是感觉大军压境了。

安全认证

  作为数据库软件,我们肯定不想谁都可以访问,为了确保数据的安全,mongodb也会像其他的数据库软件一样可以采用用户验证的方法,那么该怎么做呢?其实很简单,mongodb提供了addUser方法,还有一个注意点就是如果admin数据库中添加

将会被视为超级管理员

  上面的admin用户将会被视为超级管理员,“jack”用户追加的第三个参数表示是否是只读用户,好了,该添加的我们都添加了,我们第一次登录时不是采用验证模式,现在我们使用--reinstall重启服务并以--auth验证模式登录

  好了,我们进入test集合翻翻数据看看情况,我们发现jack用户始终都是没有写入的权限,不管是授权或者未授权。

四:备份和恢复

  这玩意的重要性我想都不需要我来说了吧,这玩意要是搞不好会死人的,mongodb里面常用的手段有3种。

1直接copy

  这个算是最简单的了,不过要注意一点,在服务器运行的情况下直接copy是很有风险的,可能copy出来时,数据已经遭到破坏,唯一能保证的就是要暂时关闭下服务器copy完后重开。

2mongodumpmongorestore

  这个是mongo给我们提供的内置工具,很好用,能保证在不关闭服务器的情况下copy数据。为了操作方便,我们先删除授权用户。

  好了,我们转入正题,这里我先在D盘建立一个backup文件夹用于存放test数据库。

  快看,数据已经备份过来了,太爽了,现在我们用mongorestore恢复过去,记住啊,它是不用关闭机器的。

  提一点的就是 drop选项,这里是说我将test数据恢复之前先删除原有数据库里面的数据,同样大家可以通过help查看。

3:主从复制

  这个我在上上篇有所介绍,这里也不赘述了。

  其实上面的12两点都不能保证获取数据的实时性,因为我们在备份的时候可能还有数据灌在内存中不出来,那么我们想说能不能把数据暴力的刷到硬盘上,当然是可以的,mongodb给我们提供了fsync+lock机制就能满足我们提的需求。fsync+lock首先会把缓冲区数据暴力刷入硬盘,然后给数据库一个写入锁,其他实例的写入操作全部被阻塞,直到fsync+lock释放锁为止。

  这里就不测试了。

  加锁:db.runCommand({"fsync":1,"lock":1})

  释放锁: db.$cmd.unlock.findOne()   

 

 

展开阅读全文

没有更多推荐了,返回首页