Quantcast
Channel: IT社区推荐资讯 - ITIndex.net
Viewing all 15843 articles
Browse latest View live

余额宝不行了?支付宝要把定期理财做成“活期生意”

$
0
0

余额宝的收益率从今年1月的6.6%左右的7日年化收益率,一直下降到如今的4.1%;净资产增长率从一季度192%,下降到二季度的6%,净资产在二季度仅增长了300亿元。这个曾经引发“互联网金融”争议的产品,已经沦为一众货币基金产品。

支付宝也意识到这个问题,在2014年4月,支付宝就在实验收益率更高的定期理财产品——招财宝,并在近期的发布会上,包装成可随时“变现”的定期理财概念打出。同时,投资门槛也被设定在100元到1000元起。

招财宝CEO袁雷鸣说,传统定期理财模式,最大的一个不足就是产品到期前没法赎回,中间要用钱时就傻眼了。或者有些产品类型比如定期存款虽然允许提前支取,但是收益率直接降成活期,打成了一折。

以往的定期理财产品,不能在急用时变现,招财宝有自己的解决方案:用户在招财宝购买了高收益定期理财产品后,中间任何时间需要急用钱,都可以立即进行变现,快速获得现金使用,并且原产品收益率完全保持不变,仅需向平台支付按交易金额乘以千分之二的手续费。

不考虑其他因素的影响,我们可以简单的算笔账。假设你在招财宝平台上,用10000元买入一个年化收益率预计为6.7%,需要定存2年的定期理财产品。一年后,这笔钱应该是10670元。但是,因为你急着用钱,实际到手的钱需要扣除交易金额的千分之二,实际将收到10648元。如果按现在余额宝4.1%的7日年化收益率计算,一年后你能到手的金额是10410元,这其中相差了238元。

与在货基市场上,支付宝需要完全控股天弘基金管理有限公司不同,招财宝是一个对外开放的平台。在4个月的时间中,已经有40家金融机构入驻,用户规模超过50万,交易规模达110亿元,覆盖20万家小微企业。

由于招财宝中有为中小企业或个人融资者发布的借款产品,在形式上会被认定为P2P业务。 另据证券时报报道,上海招财宝金融信息服务有限公司的监管部门,是上海市黄浦区金融办公室,也可能是因为招财宝目前的模式类似于p2p。但袁雷鸣强调,实际上招财宝与p2p有很大不同,未来招财宝有望受到中国银监会的监管。

问及招财宝什么时候能够在支付宝有移动端入口?支付宝方面告诉PingWest:有类似的计划,但还没有具体的时间表。

 

相关阅读:

     阿里巴巴能从“不上市”的支付宝身上得到什么?93.75亿美元、37.5%的分成,以及…

     【PW晨报】成立六个月,Secret估值1亿美元

     【PW晚报】广电总局再“严打”:视频网站要在电视盒子上“隐姓埋名”

     阿里巴巴上市前的未结悬案:支付宝事件盖棺定论了吗?


八种主流NoSQL数据库对比

$
0
0

摘要:虽然SQL数据库是非常有用的工具,但经历了15年的一支独秀之后垄断即将被打破。这只是时间问题:被迫使用关系数据库,但最终发现不能适应需求的情况不胜枚举。详见我的IT-Homer博客:  八种主流NoSQL数据库对比

 

简介

NoSQL,是一项全新的数据库革命性运动,NoSQL的拥护者们提倡运用非关系型的数据存储。现今的计算机体系结构在数据存储方面要求具备庞大的水平扩展性,而NoSQL致力于改变这一现状。目前Google的 BigTable 和Amazon 的Dynamo使用的就是NoSQL型数据库。 

但是NoSQL数据库之间的不同,远超过两 SQL数据库之间的差别。这意味着软件架构师更应该在项目开始时就选择好一个适合的 NoSQL数据库。

针对这种情况,这里对 Cassandra、 Mongodb、CouchDB、Redis、 Riak、 Membase、Neo4j、HBase进行了比较:

 

1. CouchDB

  • 所用语言: Erlang
  • 特点:DB一致性,易于使用
  • 使用许可: Apache
  • 协议: HTTP/REST
  • 双向数据复制
  • 持续进行或临时处理
  • 处理时带冲突检查
  • 因此,采用的是master-master复制(见编注2)
  • MVCC – 写操作不阻塞读操作
  • 可保存文件之前的版本
  • Crash-only(可靠的)设计
  • 需要不时地进行数据压缩
  • 视图:嵌入式 映射/减少
  • 格式化视图:列表显示
  • 支持进行服务器端文档验证
  • 支持认证
  • 根据变化实时更新
  • 支持附件处理
  • 因此,CouchApps(独立的 js应用程序)
  • 需要 jQuery程序库
  • master-master复制是一种数据库同步方法,允许数据在一组计算机之间共享数据,并且可以通过小组中任意成员在组内进行数据更新。

最佳应用场景:适用于数据变化较少,执行预定义查询,进行数据统计的应用程序。适用于需要提供数据版本支持的应用程序。

例如: CRM、CMS系统。 master-master复制对于多站点部署是非常有用的。

 

 

2. Redis

  • 所用语言:C/C++
  • 特点:运行异常快
  • 使用许可: BSD
  • 协议:类 Telnet
  • 有硬盘存储支持的内存数据库,
  • 但自2.0版本以后可以将数据交换到硬盘(注意, 2.4以后版本不支持该特性!)
  • Master-slave复制(见编注3)
  • 虽然采用简单数据或以键值索引的哈希表,但也支持复杂操作,例如 ZREVRANGEBYSCORE。
  • INCR & co (适合计算极限值或统计数据)
  • 支持 sets(同时也支持 union/diff/inter)
  • 支持列表(同时也支持队列;阻塞式 pop操作)
  • 支持哈希表(带有多个域的对象)
  • 支持排序 sets(高得分表,适用于范围查询)
  • Redis支持事务
  • 支持将数据设置成过期数据(类似快速缓冲区设计)
  • Pub/Sub允许用户实现消息机制
  • Master-slave复制,如果同一时刻只有一台服务器处理所有的复制请求,通常应用在需要提供高可用性的服务器集群。

最佳应用场景:适用于数据变化快且数据库大小可遇见(适合内存容量)的应用程序。

例如:股票价格、数据分析、实时数据搜集、实时通讯。

 

3. MongoDB

  • 所用语言:C++
  • 特点:保留了SQL一些友好的特性(查询,索引)。
  • 使用许可: AGPL(发起者: Apache)
  • 协议: Custom, binary( BSON)
  • Master/slave复制(支持自动错误恢复,使用 sets 复制)
  • 内建分片机制
  • 支持 javascript表达式查询
  • 可在服务器端执行任意的 javascript函数
  • update-in-place支持比CouchDB更好
  • 在数据存储时采用内存到文件映射
  • 对性能的关注超过对功能的要求
  • 建议最好打开日志功能(参数 –journal)
  • 在32位操作系统上,数据库大小限制在约2.5Gb
  • 空数据库大约占 192Mb
  • 采用 GridFS存储大数据或元数据(不是真正的文件系统)

最佳应用场景:适用于需要动态查询支持;需要使用索引而不是 map/reduce功能;需要对大数据库有性能要求;需要使用 CouchDB但因为数据改变太频繁而占满内存的应用程序。

例如:你本打算采用 MySQL或 PostgreSQL,但因为它们本身自带的预定义栏让你望而却步。

 

4. Riak

所用语言:Erlang和C,以及一些Javascript

  • 特点:具备容错能力
  • 使用许可: Apache
  • 协议: HTTP/REST或者 custom binary
  • 可调节的分发及复制(N, R, W)
  • 用 JavaScript or Erlang在操作前或操作后进行验证和安全支持。
  • 使用JavaScript或Erlang进行 Map/reduce
  • 连接及连接遍历:可作为图形数据库使用
  • 索引:输入元数据进行搜索(1.0版本即将支持)
  • 大数据对象支持( Luwak)
  • 提供“开源”和“企业”两个版本
  • 全文本搜索,索引,通过 Riak搜索服务器查询( beta版)
  • 支持Masterless多站点复制及商业许可的 SNMP监控

最佳应用场景:适用于想使用类似 Cassandra(类似Dynamo)数据库但无法处理 bloat及复杂性的情况。适用于你打算做多站点复制,但又需要对单个站点的扩展性,可用性及出错处理有要求的情况。

例如:销售数据搜集,工厂控制系统;对宕机时间有严格要求;可以作为易于更新的 web服务器使用。

 

5. Membase

  • 所用语言: Erlang和C
  • 特点:兼容 Memcache,但同时兼具持久化和支持集群
  • 使用许可: Apache 2.0
  • 协议:分布式缓存及扩展
  • 非常快速(200k+/秒),通过键值索引数据
  • 可持久化存储到硬盘
  • 所有节点都是唯一的( master-master复制)
  • 在内存中同样支持类似分布式缓存的缓存单元
  • 写数据时通过去除重复数据来减少 IO
  • 提供非常好的集群管理 web界面
  • 更新软件时软无需停止数据库服务
  • 支持连接池和多路复用的连接代理

最佳应用场景:适用于需要低延迟数据访问,高并发支持以及高可用性的应用程序

例如:低延迟数据访问比如以广告为目标的应用,高并发的 web 应用比如网络游戏(例如 Zynga)

 

6. Neo4j

  • 所用语言: Java
  • 特点:基于关系的图形数据库
  • 使用许可: GPL,其中一些特性使用 AGPL/商业许可
  • 协议: HTTP/REST(或嵌入在 Java中)
  • 可独立使用或嵌入到 Java应用程序
  • 图形的节点和边都可以带有元数据
  • 很好的自带web管理功能
  • 使用多种算法支持路径搜索
  • 使用键值和关系进行索引
  • 为读操作进行优化
  • 支持事务(用 Java api)
  • 使用 Gremlin图形遍历语言
  • 支持 Groovy脚本
  • 支持在线备份,高级监控及高可靠性支持使用 AGPL/商业许可

最佳应用场景:适用于图形一类数据。这是 Neo4j与其他nosql数据库的最显著区别

例如:社会关系,公共交通网络,地图及网络拓谱

 

7. Cassandra

  • 所用语言: Java
  • 特点:对大型表格和 Dynamo支持得最好
  • 使用许可: Apache
  • 协议: Custom, binary (节约型)
  • 可调节的分发及复制(N, R, W)
  • 支持以某个范围的键值通过列查询
  • 类似大表格的功能:列,某个特性的列集合
  • 写操作比读操作更快
  • 基于 Apache分布式平台尽可能地 Map/reduce
  • 对 Cassandra有偏见,一部分是因为它本身的臃肿和复杂性,也因为 Java的问题(配置,出现异常,等等)

最佳应用场景:当使用写操作多过读操作(记录日志)如果每个系统组建都必须用 Java编写(没有人因为选用 Apache的软件被解雇)

例如:银行业,金融业(虽然对于金融交易不是必须的,但这些产业对数据库的要求会比它们更大)写比读更快,所以一个自然的特性就是实时数据分析

 

8. HBase(配合 ghshephard使用)

  • 所用语言: Java
  • 特点:支持数十亿行X上百万列
  • 使用许可: Apache
  • 协议:HTTP/REST (支持 Thrift,见编注4)
  • 在 BigTable之后建模
  • 采用分布式架构 Map/reduce
  • 对实时查询进行优化
  • 高性能 Thrift网关
  • 通过在server端扫描及过滤实现对查询操作预判
  • 支持 XML, Protobuf, 和binary的HTTP
  • Cascading, hive, and pig source and sink modules
  • 基于 Jruby( JIRB)的shell
  • 对配置改变和较小的升级都会重新回滚
  • 不会出现单点故障
  • 堪比MySQL的随机访问性能

最佳应用场景:适用于偏好BigTable:)并且需要对大数据进行随机、实时访问的场合。

例如: Facebook消息数据库(更多通用的用例即将出现)

Thrift 是一种接口定义语言,为多种其他语言提供定义和创建服务,由Facebook开发并开源。

 

当然,所有的系统都不只具有上面列出的这些特性。这里仅仅根据自己的观点列出一些认为的重要特性。与此同时,技术进步是飞速的,所以上述的内容肯定需要不断更新。



作者:sunboy_2050 发表于2014-8-25 22:35:32 原文链接
阅读:4 评论:0 查看评论

zookeeper( 转)

$
0
0

转自:http://qindongliang.iteye.com/category/299318

分布式助手Zookeeper(一)

Zookeeper最早是Hadoop的一个子项目,主要为Hadoop生态系统中一些列组件提供统一的分布式协作服务,在2010年10月升级成Apache Software 
Foundation(ASF)顶级项目,它主要提供以下的四个功能: 

功能名
组管理服务
分布式配置服务
分布式同步服务
分布式命名服务


Zookeeper的目标就是封装好复杂易出错的关键服务,将简单易用的接口和性能高效、功能稳定的系统提供给用户; 
Zookeeper的架构图如下: 




Zookeeper的特点如下: 

特点说明
最终一致性为客户端展示同一个视图,这是zookeeper里面一个非常重要的功能
可靠性如果消息被到一台服务器接受,那么它将被所有的服务器接受。
实时性Zookeeper不能保证两个客户端能同时得到刚更新的数据,如果需要最新数据,应该在读数据之前调用sync()接口。
独立性各个Client之间互不干预
原子性更新只能成功或者失败,没有中间状态。
顺序性所有Server,同一消息发布顺序一致。




zookeeper的工作原理, 
1.每个Server在内存中存储了一份数据; 
2.Zookeeper启动时,将从实例中选举一个leader(Paxos协议) 
3.Leader负责处理数据更新等操作(Zab协议); 
4.一个更新操作成功,当且仅当大多数Server在内存中成功修改数据。 


zookeeper中的几个重要角色: 

角色名描述
领导者(Leader)领导者负责进行投票的发起和决议,更新系统状态,处理写请求
跟随者(Follwer)Follower用于接收客户端的读写请求并向客户端返回结果,在选主过程中参与投票
观察者(Observer)观察者可以接收客户端的读写请求,并将写请求转发给Leader,但Observer节点不参与投票过程,只同步leader状态,Observer的目的是为了,扩展系统,提高读取速度。
客户端(Client)执行读写请求的发起方




为什么,在3.3.0版本之后,引入Observer角色? 

Zookeeper需保证高可用和强一致性; 
为了支持更多的客户端,需要增加更多Server; 
Server增多,投票阶段延迟增大,影响性能; 
权衡伸缩性和高吞吐率,引入Observer 
Observer不参与投票; 
Observers接受客户端的连接,并将写请求转发给leader节点; 
加入更多Observer节点,提高伸缩性,同时不影响吞吐率。 




为什么zookeeper集群的数目,一般为奇数个? 

Leader选举算法采用了Paxos协议; 
Paxos核心思想:当多数Server写成功,则任务数据写成功 
如果有3个Server,则两个写成功即可; 
如果有4或5个Server,则三个写成功即可。 
Server数目一般为奇数(3、5、7) 
如果有3个Server,则最多允许1个Server挂掉; 
如果有4个Server,则同样最多允许1个Server挂掉 
由此,我们看出3台服务器和4台服务器的的容灾能力是一样的,所以 
为了节省服务器资源,一般我们采用奇数个数,作为服务器部署个数。 


zookeeper的数据模型: 
基于树形结构的命名空间,与文件系统类似 
节点(znode)都可以存数据,可以有子节点 
节点不支持重命名 
数据大小不超过1MB(可配置) 
数据读写要保证完整性 
层次化的目录结构,命名符合常规文件系统规范; 
每个节点在zookeeper中叫做znode,并且其有一个唯一的路径标识; 
节点Znode可以包含数据和子节点(EPHEMERAL类型的节点不能有子节点); 
Znode中的数据可以有多个版本,比如某一个路径下存有多个数据版本,那么查询这个路径下的数据需带上版本; 
客户端应用可以在节点上设置监视器(Watcher); 
节点不支持部分读写,而是一次性完整读写。 

Znode有两种类型,短暂的(ephemeral)和持久的(persistent); 
Znode的类型在创建时确定并且之后不能再修改; 
短暂znode的客户端会话结束时,zookeeper会将该短暂znode删除,短暂znode不可以有子节点; 
持久znode不依赖于客户端会话,只有当客户端明确要删除该持久znode时才会被删除; 
Znode有四种形式的目录节点,PERSISTENT、PERSISTENT_SEQUENTIAL、EPHEMERAL、EPHEMERAL_SEQUENTIAL。 





Zookeeper的应用场景一(统一命名服务) 
分布式环境下,经常需要对应用/服务进行统一命名,便于识别不同服务; 
类似于域名与ip之间对应关系,域名容易记住; 
通过名称来获取资源或服务的地址,提供者等信息 
按照层次结构组织服务/应用名称 
可将服务名称以及地址信息写到Zookeeper上,客户端通过Zookeeper获取可用服务列表类 




Zookeeper的应用场景二(配置管理) 
分布式环境下,配置文件管理和同步是一个常见问题; 
一个集群中,所有节点的配置信息是一致的,比如Hadoop; 
对配置文件修改后,希望能够快速同步到各个节点上 
配置管理可交由Zookeeper实现; 
可将配置信息写入Zookeeper的一个znode上; 
各个节点监听这个znode 
一旦znode中的数据被修改,zookeeper将通知各个节点 



Zookeeper的应用场景三(集群管理) 

分布式环境中,实时掌握每个节点的状态是必要的; 
可根据节点实时状态作出一些调整; 
可交由Zookeeper实现; 
可将节点信息写入Zookeeper的一个znode上; 
监听这个znode可获取它的实时状态变化 
典型应用 
Hbase中Master状态监控与选举 

Zookeeper的应用场景四(分布式通知和协调) 
分布式环境中,经常存在一个服务需要知道它所管理的子服务的状态; 
NameNode须知道各DataNode的状态 
JobTracker须知道各TaskTracker的状态 
心跳检测机制可通过Zookeeper实现; 
信息推送可由Zookeeper实现(发布/订阅模式) 


Zookeeper的应用场景五(分布式锁) 
Zookeeper是强一致的; 
多个客户端同时在Zookeeper上创建相同znode,只有一个创建成功。 
实现锁的独占性 
多个客户端同时在Zookeeper上创建相同znode ,创建成功的那个客户端得到锁,其他客户端等待。 
控制锁的时序 
各个客户端在某个znode下创建临时znode (类型为CreateMode.EPHEMERAL_SEQUENTIAL),这样,该znode可掌握全局访问时序。 



Zookeeper的应用场景六(分布式队列) 
两种队列; 
当一个队列的成员都聚齐时,这个队列才可用,否则一直等待所有成员到达,这种是同步队列。 
队列按照 FIFO 方式进行入队和出队操作,例如实现生产者和消费者模型。(可通过分布式锁实现) 
同步队列 
一个job由多个task组成,只有所有任务完成后,job才运行完成。 
可为job创建一个/job目录,然后在该目录下,为每个完成的task创建一个临时znode,一旦临时节点数目达到task总数,则job运行完成。 

分布式助手Zookeeper(二)

zookeeper的安装模式也有三种,分别是单机模式,伪分布模式,和完全分布式模式, 
本篇,散仙要介绍的是完全分布式模式。 


我们需要先下载好zookeeper的安装包,然后解压后,配置zookeeper,修改其config目录下zoo_simple.cfg重命名为zoo.cfg,并在其data目录(自己手动创建)下,新建一个myid文件,server.x后面的x数字一直即可,修改其内容如下: 

Java代码   收藏代码
  1.     
  2. //tickTime:这个时间是作为 Zookeeper 服务器之间或客户端与服务器之间维持心跳的时间间隔,也就是每个 tickTime 毫秒时间就会发送一个心跳。    
  3.    tickTime=2000    
  4. //initLimit:这个配置项是用来配置 Zookeeper 接受客户端(这里所说的客户端不是用户连接 Zookeeper 服务器的客户端,而是 Zookeeper 服务器集群中连接到 Leader 的 Follower 服务器)初始化连接时最长能忍受多少个心跳时间间隔数。  
  5.   
  6. //当已经超过 10 个心跳的时间(也就是 tickTime)长度后 Zookeeper 服务器还没有收到客户端的返回信息,那么表明这个客户端连接失败。总的时间长度就是 10*2000=20 秒。  
  7.     initLimit=10    
  8. //syncLimit:这个配置项标识 Leader 与 Follower 之间发送消息,请求和应答时间长度,最长不能超过多少个 tickTime 的时间长度,总的时间长度就是 5*2000=10 秒  
  9.     syncLimit=5    
  10. //dataLogDir:日志文件保存的位置  
  11.     dataDir=/root/zookeeper/data    
  12. //客户端的端口号    
  13.   clientPort=2181    
  14.       //server.A=B:C:D:其中 A 是一个数字,表示这个是第几号服务器;B 是这个服务器的 ip 地址;C 表示的是这个服务器与集群中的 Leader 服务器交换信息的端口;D 表示的是万一集群中的 Leader 服务器挂了,需要一个端口来重新进行选举,选出一个新的 Leader,而这个端口就是用来执行选举时服务器相互通信的端口。  
  15.   
  16. //如果是伪集群的配置方式,由于 B 都是一样,所以不同的 Zookeeper 实例通信端口号不能一样,所以要给它们分配不同的端口号。  
  17.     server.1=10.2.143.5:2887:3887    
  18.     server.2=10.2.143.36:2888:3888    
  19.     server.3=10.2.143.37:2889:3889  


注意,如上的配置是在3台节点非观察者角色的配置,下面我们来看下,观察者角色的配置内容: 

Java代码   收藏代码
  1. //tickTime:这个时间是作为 Zookeeper 服务器之间或客户端与服务器之间维持心跳的时间间隔,也就是每个 tickTime 毫秒时间就会发送一个心跳。      
  2.    tickTime=2000      
  3. //initLimit:这个配置项是用来配置 Zookeeper 接受客户端(这里所说的客户端不是用户连接 Zookeeper 服务器的客户端,而是 Zookeeper 服务器集群中连接到 Leader 的 Follower 服务器)初始化连接时最长能忍受多少个心跳时间间隔数。    
  4.     
  5. //当已经超过 10 个心跳的时间(也就是 tickTime)长度后 Zookeeper 服务器还没有收到客户端的返回信息,那么表明这个客户端连接失败。总的时间长度就是 10*2000=20 秒。    
  6.     initLimit=10      
  7. //syncLimit:这个配置项标识 Leader 与 Follower 之间发送消息,请求和应答时间长度,最长不能超过多少个 tickTime 的时间长度,总的时间长度就是 5*2000=10 秒    
  8.     syncLimit=5      
  9. //dataLogDir:日志文件保存的位置    
  10.     dataDir=/root/zookeeper/data      
  11. //客户端的端口号      
  12.   clientPort=2181      
  13.       //server.A=B:C:D:其中 A 是一个数字,表示这个是第几号服务器;B 是这个服务器的 ip 地址;C 表示的是这个服务器与集群中的 Leader 服务器交换信息的端口;D 表示的是万一集群中的 Leader 服务器挂了,需要一个端口来重新进行选举,选出一个新的 Leader,而这个端口就是用来执行选举时服务器相互通信的端口。    
  14.     
  15. //如果是伪集群的配置方式,由于 B 都是一样,所以不同的 Zookeeper 实例通信端口号不能一样,所以要给它们分配不同的端口号。  
  16.   
  17. //此处声明表示作为一个观察者角色存在   
  18. peerType=observer  
  19.    
  20.     server.1=10.2.143.5:2887:3887      
  21.     server.2=10.2.143.36:2888:3888      
  22.     server.3=10.2.143.37:2889:3889    
  23. //注意观察者角色的末尾,需要拼接上observer  
  24.     server.4=10.2.143.38:2886:3886:observer   



通过,以上信息,我们发现,观察者角色的配置还是比较简单的,只在原来的配置中增加了,两处改动,然后我们就可可以启动集群信息,通过查看节点状态,我们可以发现观察者角色的状态为observer,观察者角色,并不会参入投票过程,所以在高并发的情况下,大大的增强了系统的可扩展性和吞吐率。另外需要注意的在启动zookeeper集群的时候,需要把系统的防火墙给关闭,除非你已经对外开放了zookeeper的客户端端口号,否则将有可能导致启动失败。 

 

分布式助手Zookeeper(三)

Zookeeper的Session: 
(1)客户端和server间采用长连接 
(2)连接建立后,server产生session ID(64位)返还给客户端 
(3)客户端定期发送ping包来检查和保持和server的连接 
(4)一旦session结束或超时,所有ephemeral节点会被删除 
(5)客户端可根据情况设置合适的session超时时间 

Zookeeper的Watchs: 
Watch是客户端安装在server的事件侦听方法 
(1) 当侦听的变化发生时,server发消息给客户端进行通知 
(2) 客户端使用单线程对所有事件按顺序同步回调 
(3) 触发回调条件: 
• 客户端连接、断开连接 
• 节点数据发生改变 
• 节点本身发生变化 
(4)Watch是单发的,每次触发后会被自动删除 
(5)如果需要再次侦听事件,必须重新安装watch 
(6)无法保证跟踪到每一个变化 
(7)避免安装大量watches侦听在同一个节点 

Zookeeper的一些注意事项: 
在客户端事件回调实现有阻塞调用 
• 试图跟踪每个状态变化 
• 大量watch侦听同一个znode的状态变化 
• 客户端会有需要长时间处理的GC(garbage collection) 
• Session超时后上层应用不进行恢复处理 


可以把zookper看成一个文件系统,文件系统中的所有文件形成一个数状结构,zookeeper维护着这样的树形层次结构,树中的节点称为znode。每个znode有一个与之相关联的ACL(Access Control List) 
znode通过路径被引用,而且要采用绝对路径,即必须以/开头。znode存储的数据要小于1M,这个可以配置,建议不要存储太大的东西,避免同步操作时间过长。 
znode类型 
短暂znode:回话结束,zookeeper就会把短暂znode删除,短暂znode不可以有子节点。 
持久znode:回话结束也不会被删除,除非客户端明确要删除此znode,持久znode可以有子节点。 
对于在特定时刻需要知道有哪些分布式资源可用的应用来说,使用短暂znode比较合适。 

znode的观察机制 
znode以某种方式发生变化时,“观察”(watch)机制可以让客户端得到通知。可以针对ZooKeeper服务的“操作”来设置观察,该服务的其他操作可以触发观察。比如,客户端可以对某个客户端调用exists操作,同时在它上面设置一个观察,如果此时这个znode不存在,则exists返回false,如果一段时间之后,这个znode被其他客户端创建,则这个观察会被触发,之前的那个客户端就会得到通知。 

zookeeper的一些基本操作如下: 

操作描述
create创建一个znode(必须有父节点)
delete删除一个znode(该znode不能有任何子节点)
exists测试一个znode是否存在,并且查询它的元数据
getACL,setACL获取/设置一个znode的ACL
getChildren获取一个znode的子节点名字列表
getData,setData获取/设置一个znode所保存的数据
sync将客户端的znode视图与ZooKeeper服务端同步



Zookeeper中的更新操作是有条件的。在使用delete或者setData操作时必须提供被更新znode的版本号,如果版本号不匹配,则更新操作失败。一般情况下设置-1即可。 


API 
目前主要有java和C两种客户端,每种操作都有同步和异步两种执行方式。 
观察触发器 
可以设置观察的操作:exists,getChildren,getData 
可以触发观察的操作:create,delete,setData 
下面给出一些事件截图: 









NodeCreated:节点创建事件 
NodeDeleted:节点被删除事件 
NodeDataChanged:节点数据改变事件 
NodeChildrenChanged:节点的子节点改变事件 

下面我们再来看下ACL(zookeeper的访问控制列表),每个znode被创建时都会带有一个ACL列表,用于决定谁可以对它执行何种操作。 

ACL权限允许的操作
CREATE创建节点create("name")
READgetChildren()  getData()
WRITEsetData
DELETEdelete("name")删除节点
ADMINsetACL()设置权限


每个ACL都是身份验证模式、符合该模式的一个身份和一组权限的组合。身份验证模式有三种: 
digest:用户名,密码 

host:通过客户端的主机名来识别客户端 

ip: 通过客户端的ip来识别客户端 

所以我们可以类似这样构建一个ACL类: 
new ACL(Perms.READ,new Id("host","example.com")); 
这个ACL对应的身份验证模式是host 
符合该模式的身份是example.com 
权限的组合是:READ 

下面给出一个API连接zookeeper的示例: 

Java代码   收藏代码
  1. package com.util;  
  2.   
  3. import java.util.concurrent.CountDownLatch;  
  4.   
  5. import org.apache.zookeeper.WatchedEvent;  
  6. import org.apache.zookeeper.Watcher;  
  7. import org.apache.zookeeper.ZooKeeper;  
  8.   
  9. /** 
  10.  * 测试zookeeper的连接 
  11.  * @author 三劫散仙 
  12.  *  
  13.  * ***/  
  14. public class Test {  
  15.       
  16.       
  17.       
  18.     public static void main(String[] args)throws Exception {  
  19.           
  20.           
  21.           
  22.         ZooKeeper  zk=new ZooKeeper("10.2.143.5:2181", 5000, new Watcher() {  
  23.             CountDownLatch down=new CountDownLatch(1);//同步阻塞状态  
  24.             @Override  
  25.             public void process(WatchedEvent event) {  
  26.              if(event.getState()==Event.KeeperState.SyncConnected){  
  27.                  down.countDown();//连接上之后,释放计数器  
  28.              }  
  29.                   
  30.             }  
  31.         });  
  32.           
  33.         System.out.println("连接成功:"+zk.toString());  
  34.         zk.close();//关闭连接  
  35.     }  
  36.   
  37. }  


打印效果如下: 

Java代码   收藏代码
  1. 连接成功:State:CONNECTING sessionid:0x0 local:null remoteserver:null lastZxid:0 xid:1 sent:0 recv:0 queuedpkts:0 pendingresp:0 queuedevents:0  

分布式助手Zookeeper(四)

Zookeeper是分布式环境下一个重要的组件,因为它能在分布式环境下,给我带来很多便利,大大简化了分布式编程的复杂性,本篇散仙将给出一个模拟例子,来演示下如何使用Zookeeper的API编程,来完成分布式环境下配置的同步。大家都知道在一个中大型的规模的集群中,配置文件通常是必不可少的的东西,很多时候,我都需要将在Master上配置好的配置文件,给分发到各个Slave上,以确保整体配置的一致性,在集群规模小的时候我们可能简单的使用远程拷贝或复制即可完成,但是,当集群规模越来越大的时候,我们发现这种方式不仅繁琐,而且容易出错,最要命的是,以后如果改动配置文件的很少一部分的东西,都得需要把所有的配置文件,给重新远程拷贝覆盖一次,那么,怎样才能避免这种牵一发而动全身的事情呢? 


事实上,利用Zookeeper,就能够很容易的,高可靠的帮我们完成这件事,我们只需要把配置文件保存在Zookeeper的znode里,然后通过Watch来监听数据变化,进而帮我们实现同步。一个简单的工作图如下所示: 



总结流程如下: 

序号实现
1启动ZK集群
2客户端在ZK创建一个znode,并写入数据
3启动各个Server上的Watcher,无限休眠
4客户端更新znode里数据
5Watcher的read方法发现数据更新,下拉至本地,更新本地数据



代码如下: 

Java代码   收藏代码
  1. package com.sanjiesanxian;  
  2.   
  3. import java.util.concurrent.CountDownLatch;  
  4.   
  5. import org.apache.zookeeper.CreateMode;  
  6. import org.apache.zookeeper.WatchedEvent;  
  7. import org.apache.zookeeper.Watcher;  
  8. import org.apache.zookeeper.ZooKeeper;  
  9. import org.apache.zookeeper.ZooDefs.Ids;  
  10. import org.apache.zookeeper.data.Stat;  
  11.   
  12.   
  13. /*** 
  14.  * Zookeeper实现分布式配置同步 
  15.  *  
  16.  * @author 秦东亮 
  17.  *  
  18.  * ***/  
  19. public class SyscConfig   implements Watcher{  
  20.       
  21.     //Zookeeper实例  
  22.     private ZooKeeper zk;  
  23.     private CountDownLatch countDown=new CountDownLatch(1);//同步工具  
  24.     private static final int TIMIOUT=5000;//超时时间  
  25.     private static final String PATH="/sanxian";  
  26.     public SyscConfig(String hosts) {  
  27.            
  28.     try{  
  29.         zk=new ZooKeeper(hosts, TIMIOUT, new Watcher() {  
  30.               
  31.             @Override  
  32.             public void process(WatchedEvent event) {  
  33.                    
  34.                 if(event.getState().SyncConnected==Event.KeeperState.SyncConnected){  
  35.                     //防止在未连接Zookeeper服务器前,执行相关的CURD操作  
  36.                     countDown.countDown();//连接初始化,完成,清空计数器  
  37.                 }  
  38.                   
  39.             }  
  40.         });  
  41.           
  42.     }catch(Exception e){  
  43.         e.printStackTrace();  
  44.     }  
  45.     }  
  46.       
  47.       
  48.       
  49.     /*** 
  50.      * 写入或更新 
  51.      * 数据 
  52.      * @param path 写入路径 
  53.      * @param value 写入的值 
  54.      * **/  
  55.   public void addOrUpdateData(String path,String data)throws Exception {  
  56.         
  57.         
  58.       Stat stat=zk.exists(path, false);  
  59.       if(stat==null){  
  60.             //没有就创建,并写入       
  61.           zk.create(path, data.getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);  
  62.       System.out.println("新建,并写入数据成功.. ");  
  63.       }else{    
  64.           //存在,就更新  
  65.           zk.setData(path, data.getBytes(), -1);  
  66.           System.out.println("更新成功!");  
  67.       }  
  68.   }  
  69.     
  70.   /** 
  71.    * 读取数据 
  72.    * @param path 读取的路径 
  73.    * @return 读取数据的内容 
  74.    *  
  75.    * **/  
  76.   public String readData()throws Exception{  
  77.         
  78.       String s=new String(zk.getData(PATH, this, null));  
  79.         
  80.     return s;    
  81.   }  
  82.       
  83.       
  84.     /** 
  85.      * 关闭zookeeper连接 
  86.      * 释放资源 
  87.      *  
  88.      * **/  
  89.     public void close(){  
  90.           
  91.         try{  
  92.               
  93.             zk.close();  
  94.         }catch(Exception e){  
  95.             e.printStackTrace();  
  96.         }  
  97.           
  98.     }  
  99.   
  100.    
  101. public static void main(String[] args)throws Exception {  
  102.       
  103.     SyscConfig conf=new SyscConfig("10.2.143.5:2181");  
  104.        
  105.       conf.addOrUpdateData(PATH, "修真天劫,九死一生。");  
  106.       conf.addOrUpdateData(PATH, "圣人之下,皆为蝼蚁,就算再大的蝼蚁,还是蝼蚁.");  
  107.      conf.addOrUpdateData(PATH, "努力奋斗,实力才是王道! ");  
  108.       
  109.     //System.out.println("监听器开始监听........");  
  110.     // conf.readData();  
  111.     // Thread.sleep(Long.MAX_VALUE);  
  112.     //conf.readData();  
  113.     conf.close();  
  114.       
  115. }  
  116.   
  117.     @Override  
  118.     public void process(WatchedEvent event){  
  119.          try{  
  120.         if(event.getType()==Event.EventType.NodeDataChanged){  
  121.             System.out.println("变化数据:  "+readData());  
  122.         }  
  123.          }catch(Exception e){  
  124.              e.printStackTrace();  
  125.          }  
  126.           
  127.     }  
  128. }  


模拟客户端输出如下: 

Java代码   收藏代码
  1.       
  2. //客户端监听代码  
  3. SyscConfig conf=new SyscConfig("10.2.143.5:2181");  
  4.        
  5.       conf.addOrUpdateData(PATH, "修真天劫,九死一生。");  
  6.       conf.addOrUpdateData(PATH, "圣人之下,皆为蝼蚁,就算再大的蝼蚁,还是蝼蚁.");  
  7.      conf.addOrUpdateData(PATH, "努力奋斗,实力才是王道! ");  
  8.       
  9.     //System.out.println("监听器开始监听........");  
  10.     // conf.readData();  
  11.     // Thread.sleep(Long.MAX_VALUE);  
  12.     //conf.readData();  
  13.     conf.close();  

 

Java代码   收藏代码
  1. 更新成功!  
  2. 更新成功!  
  3. 更新成功!  


模拟服务端输出如下: 

Java代码   收藏代码
  1. public static void main(String[] args)throws Exception {  
  2.     //服务端监听代码  
  3.     SyscConfig conf=new SyscConfig("10.2.143.36:2181");  
  4.     //conf.addOrUpdateData(PATH, "");  
  5.     System.out.println("模拟服务监听器开始监听........");  
  6.      conf.readData();  
  7.      Thread.sleep(Long.MAX_VALUE);  
  8.     conf.close();  
  9.       
  10. }  

 

Java代码   收藏代码
  1. 模拟服务监听器开始监听........  
  2. 数据更新了:  修真天劫,九死一生。  
  3. 数据更新了:  圣人之下,皆为蝼蚁,就算再大的蝼蚁,还是蝼蚁.  
  4. 数据更新了:  努力奋斗,实力才是王道!   




至此,使用zookeeper来完成配置同步的服务就完成了,我们可以发现,使用zookeeper来编写分布式程序是非常简单可靠的。 

分布式助手Zookeeper(五)

单点故障问题,在分布式系统中是一个很有可能发生的场景,比如说在Hadoop2.x之前的HDFS的NameNode和MapReduce的JobTracker的单点故障,当然这个问题已经在Hadoop2.x中得到解决,解决的方式,大部分是基于Zookeeper来实现的。另外一个例子,在Hbase中的Hmaster的单点问题,也是使用Zookeeper解决的。 

下面,我们先来看下,简单的实现图: 




总结流程如下: 

序号描述
1创捷父节点类型为Persistent
2创捷子节点类型为ephemeral + sequential
3客户端启动时创建子节点
4序列号最小的子节点选为master,其他子节点都是slave
5每个slave侦听序列号比它小的子节点中最大的子节点的NodeDeleted事件
6一旦NodeDeleted事件被触发,该slave客户端会重新选定侦听对象,如果不存在可侦听对象,该slave自动晋升成master




代码,如下: 

Java代码   收藏代码
  1. package com.automicswitch;  
  2.   
  3. import java.nio.charset.Charset;  
  4. import java.nio.charset.StandardCharsets;  
  5. import java.text.SimpleDateFormat;  
  6. import java.util.Collections;  
  7. import java.util.Date;  
  8. import java.util.List;  
  9. import java.util.concurrent.CountDownLatch;  
  10.   
  11. import org.apache.zookeeper.CreateMode;  
  12. import org.apache.zookeeper.WatchedEvent;  
  13. import org.apache.zookeeper.Watcher;  
  14. import org.apache.zookeeper.ZooDefs.Ids;  
  15. import org.apache.zookeeper.ZooKeeper;  
  16. import org.apache.zookeeper.data.Stat;  
  17.   
  18. import com.util.ConnectionWatcher;  
  19.   
  20. /** 
  21.  * 模拟Zookeeper实现单点故障 
  22.  * 自动切换 
  23.  * @author  秦东亮 
  24.  *  
  25.  * ***/  
  26. public class Slave  implements  Watcher{  
  27.       
  28.     /** 
  29.      * zk实例 
  30.      * **/  
  31.     public ZooKeeper zk;  
  32.       
  33.     /** 
  34.      *  同步工具 
  35.      *  
  36.      * **/  
  37.     private CountDownLatch count=new CountDownLatch(1);  
  38.       
  39.     private static final Charset CHARSET=StandardCharsets.UTF_8;  
  40.      public Slave() {  
  41.         // TODO Auto-generated constructor stub  
  42.     }  
  43.      /** 
  44.       * hosts, 
  45.       * zookeeper的访问地址 
  46.       *  
  47.       * **/  
  48.     public Slave(String hosts) {  
  49.         try{  
  50.          zk=new ZooKeeper(hosts, 7000, new Watcher() {  
  51.               
  52.             @Override  
  53.             public void process(WatchedEvent event) {  
  54.                 // TODO Auto-generated method stub  
  55.                 if(event.getState()==Event.KeeperState.SyncConnected){  
  56.                     count.countDown();  
  57.                       
  58.                 }  
  59.                   
  60.             }  
  61.         });  
  62.         }catch(Exception e){  
  63.             e.printStackTrace();  
  64.         }  
  65.     }  
  66.     /*** 
  67.      *  
  68.      * 此方法是写入数据 
  69.      * 如果不存在此节点 
  70.      * 就会新建,已存在就是 
  71.      * 更新 
  72.      *  
  73.      * **/  
  74.         public void write(String path,String value)throws Exception{  
  75.               
  76.             Stat stat=zk.exists(path, false);  
  77.             if(stat==null){  
  78.                 zk.create(path, value.getBytes(CHARSET), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);  
  79.             }else{  
  80.                   
  81.                 zk.setData(path, value.getBytes(CHARSET), -1);  
  82.             }  
  83.               
  84.         }  
  85.   
  86.          public String read(String path,Watcher watch)throws Exception{  
  87.                
  88.              byte[] data=zk.getData(path, watch, null);  
  89.                
  90.                
  91.              return new String(data,CHARSET);  
  92.          }  
  93.   
  94.           SimpleDateFormat f=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");  
  95.          public void automicSwitch()throws Exception{  
  96.                
  97.              System.out.println("Master故障,Slave自动切换.......,  时间  "+f.format(new Date()));  
  98.                
  99.          }  
  100.            
  101.          public void startMaster(){  
  102.                
  103.              System.out.println("A的Master 启动了........");  
  104.          }  
  105.            
  106. public void createPersist()throws Exception{  
  107.                
  108.              zk.create("/a", "主节点".getBytes(), Ids.OPEN_ACL_UNSAFE  , CreateMode.PERSISTENT);  
  109.                
  110.              System.out.println("创建主节点成功........");  
  111.                
  112.                
  113.          }  
  114.          public void createTemp()throws Exception{  
  115.                
  116.              zk.create("/a/b", "a".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);  
  117.                
  118.           System.out.println("a创建子节点成功...........");       
  119.                
  120.          }  
  121.            
  122.          public void check()throws Exception{  
  123.              List<String> list=zk.getChildren("/a", null);  
  124.               Collections.sort(list);  
  125.               if(list.isEmpty()){  
  126.                   System.out.println("此父路径下面没有节点");  
  127.               }else{  
  128.                     
  129.                   String start=list.get(0);  
  130.                     
  131.                   String data=new String(zk.getData("/a/"+start, false,null));  
  132.                   if(data.equals("a")){//等于本身就启动作为Master  
  133.                         
  134.                       if(list.size()==1){  
  135.                           startMaster();//作为Master启动  
  136.                           }else{  
  137.                               automicSwitch();  
  138.                           }  
  139.                   }else{  
  140.                       //非当前节点  
  141.                       for(int i=0;i<list.size();i++){  
  142.                           //获取那个节点存的此客户端的模拟IP  
  143.                           String temp=new String(zk.getData("/a/"+list.get(i), false, null));  
  144.                             
  145.                           if(temp.equals("a")){  
  146.                               //因为前面作为首位判断,所以这个出现的位置不可能是首位  
  147.                               //需要监听小节点里面的最大的一个节点  
  148.                               String watchPath=list.get(i-1);  
  149.                               System.out.println("a监听的是:  "+watchPath);  
  150.                                 
  151.                               zk.exists("/a/"+watchPath, this);//监听此节点的详细情况  
  152.                               break;//结束循环  
  153.                           }  
  154.                             
  155.                       }  
  156.                         
  157.                   }  
  158.                     
  159.                     
  160.               }  
  161.                
  162.          }  
  163.          public void close()throws Exception{  
  164.              zk.close();  
  165.          }  
  166.            
  167.         @Override  
  168.         public void process(WatchedEvent event) {  
  169.                
  170.             if(event.getType()==Event.EventType.NodeDeleted){  
  171.                   
  172.                 //如果发现,监听的节点,挂掉了,那么就重新,进行监听   
  173.                 try{  
  174.                 System.out.println("注意有节点挂掉,重新调整监听策略........");  
  175.                 check();  
  176.                 }catch(Exception e){  
  177.                     e.printStackTrace();  
  178.                       
  179.                 }  
  180.             }  
  181.               
  182.               
  183.               
  184.         }  
  185.       
  186.       
  187.         public static void main(String[] args)throws Exception {  
  188.               
  189.             Slave s=new Slave("10.2.143.5:2181");  
  190.             //s.createPersist();//创建主节点  
  191.             s.createTemp();  
  192.             s.check();  
  193.             Thread.sleep(Long.MAX_VALUE);  
  194.             s.close();  
  195.               
  196.         }  
  197.       
  198.   
  199. }  



散仙起了,3个客户端,作为模拟Slave,本机上的一个eclipse,一个Myeclipse,和服务器上的一个Myeclipse来实现,模拟单点故障,自动切换的功能。 
初始状态截图如下: 




散仙停掉,2的监听后发生的变化,如下: 


最后,散仙停掉A节点的Master,模拟Master宕机。 





到此,散仙已经模拟实现了主从单点故障的自动切换,使用Zookeeper可以非常简单可靠的来完成这个功能,当然,我们在这里只是模拟的简单实现,真正的单点问题的实现,肯定要比散仙的这个要复杂的多,在这里只是提供给大家一个解决的思路。 

分布式助手Zookeeper(六)

散仙,在关于zookeeper的前几篇文章中,除了记录了zookeeper的一些基础知识,也介绍了怎么使用zookeeper来完成,配置文件同步,和主从自动切换的功能,那么,本篇散仙将会介绍下如何使用,zookeeper来完成分布式锁的功能,其实本质上是与主从切换的实现代码是非常类似的,但是功能上强调的重点不一样。 

至于,为什么需要分布式锁(公平锁)?为什么不使用JAVA 自带的锁的应用? 

1,为什么需要分布式锁? 因为在分布式环境下,可能会出现一些事务,这时候我们除了可以在存储层的数据库进行控制,也可以在应用层控制,举个例子来讲,中国的飞机路线,我们都知道任何时候,都只能由一架飞机通过,而这个控制这个由谁通过,什么时候通过,是由一个信号控制台来决定的,分布式的环境下由于节点分散在各个地方,各个区域,所以控制起来比较麻烦,这时候我们就可以使用zookeeper来轻松的完成,分布式锁的功能。 

2,为什么不使用JAVA自带的锁?JAVA JDK提供了公平锁,与非公平锁,但这种实现是基于同一个JVM来说的,如果同一台机器上,不同的JVM,则可以使用文件锁,来实现,但是这些并不是分布式的模式,虽然可以通过RMI的方式来实现,但比较繁琐。 

使用zookeeper来完成分布式锁的步骤如下: 

序号内容
1创建一个持久znode
2多个程序并发的去zk服务上,创建一个短暂有时序性的节点路径。
3各个节点监听,比它小的里面,最大的节点的动态。
4如果发现,比它小的里面,最大的节点发生锁释放或退出,就自动接替为独占锁
5没发生改变的节点,继续重复步骤,2,3,4



拓扑图如下所示: 


注意上图中的master指的就是,获取锁的实例,这其实跟集群环境里只能有一个master的道理一样。 

代码如下: 


Java代码   收藏代码
  1. package com.test;  
  2.   
  3. import java.nio.charset.Charset;  
  4. import java.nio.charset.StandardCharsets;  
  5. import java.text.SimpleDateFormat;  
  6. import java.util.Collections;  
  7. import java.util.Date;  
  8. import java.util.List;  
  9. import java.util.concurrent.CountDownLatch;  
  10.   
  11. import org.apache.zookeeper.CreateMode;  
  12. import org.apache.zookeeper.WatchedEvent;  
  13. import org.apache.zookeeper.Watcher;  
  14. import org.apache.zookeeper.ZooDefs.Ids;  
  15. import org.apache.zookeeper.ZooKeeper;  
  16. import org.apache.zookeeper.Watcher.Event.KeeperState;  
  17. import org.apache.zookeeper.data.Stat;  
  18. /*** 
  19.  * 基于zookeeper实现的 
  20.  * 分布式公平锁 
  21.  *  
  22.  * @author qin dong liang  
  23.  * QQ技术群交流:324714439 
  24.  *  
  25.  * */  
  26. public class Lock1  implements Watcher {  
  27.       
  28.       
  29.     /** 
  30.      * ZK实例 
  31.      * */  
  32.     private ZooKeeper zk;  
  33.       
  34.     /**原子计数锁,防止在zk没有连上前,执行CURD操作*/  
  35.     private CountDownLatch down=new CountDownLatch(1);  
  36.       
  37.     public Lock1() {  
  38.         // TODO Auto-generated constructor stub  
  39.     }  
  40.       
  41.       
  42.       
  43.     public Lock1(String host)throws Exception {  
  44.          this.zk=new ZooKeeper(host, 5000   , new Watcher() {  
  45.               
  46.             @Override  
  47.             public void process(WatchedEvent event) {  
  48.                 // TODO Auto-generated method stub  
  49.                 /**链接上zk服务,岂可取消阻塞计数**/  
  50.                 if(event.getState()==KeeperState.SyncConnected){  
  51.                     down.countDown();  
  52.                 }  
  53.                   
  54.             }  
  55.         });  
  56.     }  
  57.     /** 
  58.      * 字符编码 
  59.      *  
  60.      * **/  
  61.      private static final Charset CHARSET=StandardCharsets.UTF_8;  
  62.     /*** 
  63.      *  
  64.      * 此方法是写入数据 
  65.      * 如果不存在此节点 
  66.      * 就会新建,已存在就是 
  67.      * 更新 
  68.      *  
  69.      * **/  
  70.         public void write(String path,String value)throws Exception{  
  71.               
  72.             Stat stat=zk.exists(path, false);  
  73.             if(stat==null){  
  74.                 zk.create(path, value.getBytes(CHARSET), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);  
  75.             }else{  
  76.                 zk.setData(path, value.getBytes(CHARSET), -1);  
  77.             }  
  78.               
  79.         }  
  80.           
  81.           
  82.         /** 
  83.          *  
  84.          * 切换锁 
  85.          *  
  86.          * **/  
  87.          public void check()throws Exception{  
  88.              List<String> list=zk.getChildren("/a", null);  
  89.               Collections.sort(list);//排序使得节点有次序  
  90.               if(list.isEmpty()){  
  91.                   System.out.println("此父路径下面没有节点,分布式锁任务完成或还没启动!");  
  92.               }else{  
  93.                     
  94.                   String start=list.get(0);//获取第一个节点  
  95.                     
  96.                   String data=new String(zk.getData("/a/"+start, false,null));  
  97.                   if(data.equals("a")){//等于本身就启动作为Master  
  98.                         
  99.                       if(list.size()==1){  
  100.                           startMaster();//作为Master启动  
  101.                           }else{  
  102.                               automicSwitch();//对于非第一个启动的节点,会调用此方法,因为他的第一个挂了  
  103.                               //或释放锁了,所以它是抢占的  
  104.                           }  
  105.                   }else{  
  106.                       //非当前节点,就打印当前节点,监控的节点  
  107.                       for(int i=0;i<list.size();i++){  
  108.                           //获取那个节点存的此客户端的模拟IP  
  109.                           String temp=new String(zk.getData("/a/"+list.get(i), false, null));  
  110.                           if(temp.equals("a")){  
  111.                               //因为前面作为首位判断,所以这个出现的位置不可能是首位  
  112.                               //需要监听小节点里面的最大的一个节点  
  113.                               String watchPath=list.get(i-1);  
  114.                               System.out.println("Lock1监听的是:  "+watchPath);  
  115.                                 
  116.                               zk.exists("/a/"+watchPath, this);//监听此节点的详细情况,如果发生节点注销事件  
  117.                               //则会触发自身的process方法  
  118.                               break;//结束循环  
  119.                           }  
  120.                             
  121.                       }  
  122.                         
  123.                   }  
  124.                     
  125.                     
  126.               }  
  127.                
  128.          }  
  129.           
  130.           
  131.   
  132.     @Override  
  133.     public void process(WatchedEvent event) {  
  134.         // TODO Auto-generated method stub  
  135.            
  136.         if(event.getType()==Event.EventType.NodeDeleted){  
  137.               
  138.             //如果发现,监听的节点,挂掉了,那么就重新,进行监听   
  139.             try{  
  140.            System.out.println("注意有锁退出或释放,公平锁开始抢占........");  
  141.             check();  
  142.             }catch(Exception e){  
  143.                 e.printStackTrace();  
  144.                   
  145.             }  
  146.         }  
  147.     }  
  148.     /** 
  149.      *  
  150.      * 读取数据,给定一个路径和 
  151.      * 监听事件 
  152.      *  
  153.      * ***/  
  154.      public String read(String path,Watcher watch)throws Exception{  
  155.            
  156.          byte[] data=zk.getData(path, watch, null);  
  157.            
  158.            
  159.          return new String(data,CHARSET);  
  160.      }  
  161.        
  162.      SimpleDateFormat f=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");  
  163.   
  164.        
  165.       /** 
  166.        * 关闭zk连接 
  167.        *  
  168.        * **/  
  169.      public void close()throws Exception{  
  170.          zk.close();  
  171.      }  
  172.        
  173.        
  174.        
  175.      /** 
  176.       * 释放锁 
  177.       * @throws Exception 
  178.       */  
  179.      public void automicSwitch()throws Exception{  
  180.            
  181.         // System.out.println("有节点释放锁,Lock1锁占入.......,  时间  "+f.format(new Date()));  
  182.          System.out.println("Lock1的上级锁节点退出或释放锁了,Lock1锁占入.......,  时间  "+f.format(new Date()));  
  183.      }  
  184.        
  185.      /** 
  186.       * 创建一个持久node, 
  187.       *  
  188.       * **/  
  189.      public void createPersist()throws Exception{  
  190.            
  191.          zk.create("/a", "主节点".getBytes(), Ids.OPEN_ACL_UNSAFE  , CreateMode.PERSISTENT);  
  192.            
  193.          System.out.println("创建主节点成功........");  
  194.            
  195.            
  196.      }  
  197.        
  198.      /*** 
  199.       * 创建锁node,注意是抢占 的 
  200.       *  
  201.       *  
  202.       * */  
  203.      public void createTemp()throws Exception{  
  204.            
  205.          zk.create("/a/b", "a".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);  
  206.            
  207.       System.out.println("Lock1注册锁成功,进入公平队列...........");      
  208.            
  209.      }  
  210.        
  211.     public static void main(String[] args)throws Exception {  
  212.               
  213.             //Slave s=new Slave("192.168.120.128:2181");  
  214.             Lock1 lock=new Lock1("192.168.120.128:2181");  
  215.             //  lock.createPersist();//创建主节点  
  216.              lock.createTemp();//注册临时有序节点  
  217.              lock.check();  
  218.               Thread.sleep(Long.MAX_VALUE);  
  219.              //lock.close();  
  220.               
  221.     }  
  222.        
  223.      /*** 
  224.       * 获取锁成功 
  225.       *  
  226.       * */  
  227.      public void startMaster(){  
  228.            
  229.          System.out.println("Lock1节点获取锁了,其他节点等待........");  
  230.      }  
  231.        
  232. }  



代码如上,所示,测试的时候,需要搭建一个3个节点的zookeeper集群,关于怎么搭建zookeeper集群,散仙前面的文章里有介绍,需要注意的是myid文件不要漏掉。 

上面这个类,需要拷贝多份,并改变里面的节点的值,放在不同的eclipse中,进行模拟测试。 

分布式助手Zookeeper(七)

上篇文章,散仙介绍了,分布式环境下,基于zookeeper实现的公平的锁,这篇,我们来看下,如何使用zookeeper来完成非公平锁的模拟,在这之前,我们先来,了解下公平锁和非公平锁的区别。 

  JAVA JDK提供了公平锁,与非公平锁,但这种实现是基于同一个JVM来说的, 
  如果同一台机器上,不同的JVM,则可以使用文件锁,来实现,但是这些并不是分布式的模式,虽然可以通过RMI的方式来实现, 
  但比较繁琐。在分布式的场景里,我们可以轻松的使用zookeeper来实现公平锁与非公平锁 
  
基于zookeeper实现的公平锁与非公平锁的区别 
  
先来通俗的看下二者的区别 

公平锁,即先来者先得,只有一个厕所的卫生间,想进去只能是按排队顺序来的,比较公平,first挂掉或释放后,会由secend得到锁,依次类推。 

非公平锁,比较暴力,只有一个厕所的卫生间,不用排队,外面围了一堆人等着上厕所,当里面的人出来时,外面的人谁强势,而且力气大,谁就能进去, 
极端情况下,如果两个人一样力气大,这时候就该厕所门发挥作用了,一次只能挤进去一个人,反映到我们的程序中,这时候就需要代码同步了,保证 
任何时候,只有一个人可以拿到锁。 

二者的相同点,都保证了,任何情况下,都只能一个人得到某种资源。但实现的方式不同。 


实现简述:分布式非公平锁的创建,除了得到锁外,其他的多个监听器,监听同一个锁的情况 

实现的流程步骤如下: 

序号介绍
1创建一个持久znode
2多个程序并发的去zk服务上,创建同一个短暂无时序性的节点路径,当一个程序,得到锁时,其他程序,只能监听,不能再次创建,创建时需要同步策略
3同一时刻只能有一个创建成功者,能得到锁
4没成功者,统一监视得到锁的节点
5如果中间得到锁的节点,释放了,或者出意外挂掉了,则重复步骤1,2,3,4


拓扑图如下: 


代码如下: 

Java代码   收藏代码
  1. /*** 
  2.  * @author qin dong liang 
  3.  *  
  4.  * */  
  5. public class LockUnFair3  implements Watcher {  
  6.       
  7.       
  8.     /** 
  9.      * ZK实例 
  10.      * */  
  11.     private ZooKeeper zk;  
  12.       
  13.     /**原子计数锁,防止在zk没有连上前,执行CURD操作*/  
  14.     private CountDownLatch down=new CountDownLatch(1);  
  15.       
  16.     public LockUnFair3() {  
  17.         // TODO Auto-generated constructor stub  
  18.     }  
  19.       
  20.       
  21.       
  22.     public LockUnFair3(String host)throws Exception {  
  23.          this.zk=new ZooKeeper(host, 5000   , new Watcher() {  
  24.               
  25.             @Override  
  26.             public void process(WatchedEvent event) {  
  27.                 // TODO Auto-generated method stub  
  28.                 /**链接上zk服务,岂可取消阻塞计数**/  
  29.                 if(event.getState()==KeeperState.SyncConnected){  
  30.                     down.countDown();  
  31.                 }  
  32.                   
  33.             }  
  34.         });  
  35.     }  
  36.     /** 
  37.      * 字符编码 
  38.      *  
  39.      * **/  
  40.      private static final Charset CHARSET=StandardCharsets.UTF_8;  
  41.        
  42.     @Override  
  43.     public void process(WatchedEvent event) {  
  44.         // TODO Auto-generated method stub  
  45.            
  46.         if(event.getType()==Event.EventType.NodeDeleted){  
  47.               
  48.             //如果发现,监听的节点,挂掉了,那么就重新,进行监听   
  49.             try{  
  50.           // System.out.println("注意有锁退出或释放,公平锁开始抢占........");  
  51.                 System.out.println("3我可以去抢占了");  
  52.                 createTemp();  
  53.         //  check();  
  54.             }catch(Exception e){  
  55.                 e.printStackTrace();  
  56.                   
  57.             }  
  58.         }  
  59.     }  
  60.        
  61.      SimpleDateFormat f=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");  
  62.   
  63.        
  64.       /** 
  65.        * 关闭zk连接 
  66.        *  
  67.        * **/  
  68.      public void close()throws Exception{  
  69.          zk.close();  
  70.      }  
  71.         
  72.      Random random=new Random();  
  73.      /*** 
  74.       * 创建锁node,注意是抢占 的 
  75.       *  
  76.       *  
  77.       * */  
  78.      public void createTemp()throws Exception{  
  79.        
  80.           
  81.          Thread.sleep(random.nextInt(2500));//加个线程休眠,实现模拟同步功能  
  82.            
  83.        if(zk.exists("/a/b", this) != null){  
  84.            System.out.println("锁被占用,监听进行中......");  
  85.        }else{  
  86.              
  87.              
  88.              
  89.           String data=zk.create("/a/b", "a".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);  
  90.           System.out.println("Lock3创建锁成功,节点路径:    "+data);  
  91.             
  92.        }  
  93.            
  94.         // System.out.println("2"+data);  
  95.            
  96.      }  
  97.        
  98.     public static void main(String[] args)throws Exception {  
  99.               
  100.             //Slave s=new Slave("192.168.120.128:2181");  
  101.             LockUnFair3 lock=new LockUnFair3("192.168.120.128:2181");  
  102.             //  lock.createPersist();//创建主节点  
  103.              lock.createTemp();  
  104.             // lock.check();  
  105.               Thread.sleep(Long.MAX_VALUE);  
  106.              lock.close();  
  107.               
  108.     }  
  109.        
  110.        
  111.        
  112. }  



以上是实现的代码,需要注意的是,在最后抢占锁时,可能会一下多个节点同时去建立名字一样的节点,由于zookeeper的特点,只能由一个建立成功,其他的会抛出异常,为了避免这种情况,散仙,目前的想到的是,在创建一个节点时,通过线程随机休眠,来达到一个同步情况,但这扔有极端情况,虽然几率很小,就是分布式环境下可能有多个节点随机休眠的时间是一样的,所以第二种做法,可以在zk节点维持一个有序的分布式队列,每次只能第一个得到锁,其他的继续等待,下一次的抢占,如此一来,就能保证任何时刻只有一个节点得到锁。 



                  如有什么不足之处,欢迎指正!   ^_^ 

分布式助手Zookeeper(八)

散仙,在前几篇关于zookeeper的文章中,介绍了基于zookeeper实现的分布式公平锁,以及非公平锁,那么本篇呢,散仙就来介绍下关于使用zookeeper如何模拟实现一个分布式队列。 

那么为什么需要分布式队列呢?,我们都知道队列,在我们的编程开发中,是一种比不可少的数据结构,最典型莫过于,生产者与消费者的例子了,我们在程序过经常使用的队列是基于非分布式的环境,JAVA JDK也自带了非常多的队列的实现,有基于阻塞模式的,也有基于非阻塞模式的,这些我们可以在不同的场景下使用。 

要想把JDK自带的队列给设计成分布式的队列,是一件非常繁琐的事,这时候我就可以轻而易举的使用zookeeper来完成这个功能,zookeeper由于其特殊的文件系统,所以在分布式环境下,能做许多有用的事。 



例如在Hadoop中,map任务完成后,我们启动reduce,我们都知道Hadoop的MapReduce是一种分布式的计算框架,所以这时候我们就可以使用zookeeper来完成这里的分布式队列的运用,虽然,hadoop本身用的并不是zookeeper来实现的。 

代码如下: 

Java代码   收藏代码
  1. package com.qin.queue;  
  2.   
  3. import java.util.Collections;  
  4. import java.util.List;  
  5. import java.util.concurrent.CountDownLatch;  
  6.   
  7. import org.apache.zookeeper.CreateMode;  
  8. import org.apache.zookeeper.WatchedEvent;  
  9. import org.apache.zookeeper.Watcher;  
  10. import org.apache.zookeeper.ZooKeeper;  
  11. import org.apache.zookeeper.Watcher.Event.EventType;  
  12. import org.apache.zookeeper.ZooDefs.Ids;  
  13.   
  14. import com.test.Lock1;  
  15.   
  16. /** 
  17.  *  
  18.  * 基于zookeeper实现的 
  19.  * 分布式队列 
  20.  * @author qindongliang 
  21.  *  
  22.  * **/  
  23. public class DistributeQueue1 {  
  24.       
  25.       
  26.      private ZooKeeper zk;  
  27.      private CountDownLatch down=new CountDownLatch(1);  
  28.        
  29.        
  30.      public DistributeQueue1(String zkHost) {  
  31.         try{  
  32.          zk=new ZooKeeper(zkHost, 5000, new Watcher(){  
  33.   
  34.             @Override  
  35.             public void process(WatchedEvent event) {  
  36.                   
  37.                 if(event.getState()==Event.KeeperState.SyncConnected){  
  38.                     down.countDown();  
  39.                 }  
  40.                   
  41.             }  
  42.                
  43.                
  44.          });  
  45.            
  46.         }catch(Exception e){  
  47.             e.printStackTrace();  
  48.         }  
  49.     }  
  50.       
  51.       
  52.        
  53.      /** 
  54.       * 创建一个持久node, 
  55.       *  
  56.       * **/  
  57.      public void createPersist()throws Exception{  
  58.            
  59.          zk.create("/a", "主节点".getBytes(), Ids.OPEN_ACL_UNSAFE  , CreateMode.PERSISTENT);  
  60.            
  61.          System.out.println("创建主节点成功........");  
  62.            
  63.            
  64.      }  
  65.        
  66.      /** 
  67.          *  
  68.          * 校验队列情况 
  69.          *  
  70.          * **/  
  71.          public void check()throws Exception{  
  72.              List<String> list=zk.getChildren("/a", null);  
  73.                
  74.              if(list.size()==3){  
  75.                  execute();  
  76.              }else{  
  77.                  System.out.println("阻塞中......,队列成员数量:"+list.size());  
  78.              }  
  79.                
  80.                
  81.                 
  82.                     
  83.                     
  84.               }  
  85.                
  86.            
  87.        
  88.        
  89.   public void execute(){  
  90.         
  91.       System.out.println("队列已满,开始执行任务......");  
  92.         
  93.   }  
  94.   
  95.   /*** 
  96.       *  
  97.       * 注册队列 
  98.       *  
  99.       *  
  100.       * */  
  101.      public void createTemp()throws Exception{  
  102.            
  103.          zk.create("/a/b", "a".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);  
  104.          System.out.println("有任务注册队列成功");  
  105.         check();   
  106.            
  107.      }  
  108.     
  109.   public static void main(String[] args)throws Exception {  
  110.           
  111.      DistributeQueue1 dq=new DistributeQueue1("192.168.120.128:2181");  
  112.      //dq.createPersist();  
  113.      dq.createTemp();  
  114.      Thread.sleep(Integer.MAX_VALUE);  
  115. }  
  116.       
  117.   
  118. }  



代码如上,散仙在代码里设定队列的总数为3,就执行干某一件事,我可以去zk的主节点上注册临时有序的节点,每注册一次都会校验,当前队列是不是以及满队,如果满队的话,就开始执行某个任务,从而达到分布式队列的模拟,这与JDK自带的CountDownLatch和CyclicBarrier是非常类似的,当然我们也可也使用zk模拟,这两个非分布式的功能,从而为我们的应用程序在分布式的环境下提供便利。 



已有 0人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



开源MySQL数据仓库解决方案:Infobright

$
0
0

Infobright是一款基于独特的专利知识网格技术的列式数据库。Infobright是开源的MySQL数据仓库解决方案,引入了列存储方案,高强度的数据压缩,优化的统计计算(类似sum/avg/group by之类),infobright 是基于mysql的,但不装mysql亦可,因为它本身就自带了一个。mysql可以粗分为逻辑层和物理存储引擎,infobright主要实现的就是一个存储引擎,但因为它自身存储逻辑跟关系型数据库根本不同,所以,它不能像InnoDB那样直接作为插件挂接到mysql,它的逻辑层是mysql的逻辑层加上它自身的优化器。

Infobright特征

优点:

  1. 大数据量查询性能强劲、稳定:百万、千万、亿级记录数条件下,同等的SELECT查询语句,速度比MyISAM、InnoDB等普通的MySQL存储引擎快5~60倍。高效查询主要依赖特殊设计的存储结构对查询的优化,但这里优化的效果还取决于数据库结构和查询语句的设计。
  2. 存储数据量大:TB级数据大小,几十亿条记录。数据量存储主要依赖自己提供的高速数据加载工具(百G/小时)和高数据压缩比(>10:1)
  3. 高数据压缩比:号称平均能够达到 10:1 以上的数据压缩率。甚至可以达到40:1,极大地节省了数据存储空间。高数据压缩比主要依赖列式存储和 patent-pending 的灵活压缩算法.
  4. 基于列存储:无需建索引,无需分区。即使数据量十分巨大,查询速度也很快。用于数据仓库,处理海量数据没一套可不行。不需要建索引,就避免了维护索引及索引随着数据膨胀的问题。把每列数据分块压缩存放,每块有知识网格节点记录块内的统计信息,代替索引,加速搜 索。
  5. 快速响应复杂的聚合类查询:适合复杂的分析性SQL查询,如SUM, COUNT, AVG, GROUP BY

Infobright的价值

  1. 节约设计开销。没有复杂的数据仓库模型设计要求(比如星状模型、雪花模型),无需要物化视图、数据分区、索引建立
  2. 节省存储资源。高压缩比率通常是10:1,某些应用可能达到40:1
  3. 集成利用广泛。和众多的BI套件相容,比如Pentaho、Cognos、Jaspersof
  4. 降低运维成本。随着数据库的逐渐增大,查询和装载性能持续保持稳定,实施和管理简单,需要极少的管理
  5. 商业保证。第一个商业支持的开源仓储分析数据库,是Oracle/MySQL 官方推荐的仓储集成架构

Infobright的适用场景

  1. 大数据量的分析应用。网页/在线分析、移动分析、客户行为分析、分析营销和广告
  2. 日志/事件管理系统。电信详单分析和报告、系统/网络 安全认证记录
  3. 数据集市。企事业单位特定数据仓库、为中小企业提供数据仓库
  4. 嵌入式分析。为独立软件供应商/ SaaS供应商提供嵌入式分析应用

限制:

  1. 不支持数据更新:社区版Infobright只能使用“LOAD DATA INFILE”的方式导入数据,不支持INSERT、UPDATE、DELETE。这使对数据的修改变得很困难,这样就限制了它作为实时数据服务的数据仓库来使用。
  2. 不支持高并发:只能支持10多个并发查询,虽然单库 10 多个并发对一般的应用来说也足够了,但较低的机器利用率对投资者来说总是一件不爽的事情,特别是在并发小请求较多的情况下。
  3. 没有提供主从备份和横向扩展的功能。如果没有主从备份,想做备份的话,也可以主从同时加载数据,但只能校验最终的数据一致性,使得从机在数据加载时停服务的时间较长;横向扩展方面,它本身就不是分布式的存储系统。

与MySQL对比

  1. infobright适用于数据仓库场合:即非事务、非实时、非多并发;分析为主;存放既定的事实,例如日志,或汇总的大量的数据。所以它并不适合于应对来自网站用户的请求。实际上它取一条记录比mysql要慢很多,但它取100W条记录会比mysql快。
  2. mysql的总数据文件占用空间通常会比实际数据多,因为它还有索引。infobright的压缩能力很强大,按列按不同类型的数据来压缩。
  3. 服务形式与接口跟mysql一致,可以用类似mysql的方式启用infobright服务,然后原来连接mysql的应用程序都可以以类似的方式连接与查询infobright。这对熟练mysql者来说是个福音,学习成本基本为0。

infobright有两个发布版:开源的ICE及闭源商用的IEE。ICE提供了足够用的功能,但不能 INSERT,DELETE,UPDATE,只能LOAD DATA INFILE。IEE除提供更充分的功能外,据说查询速度也要更快。

社区ICE版,国内各大企业均有测试,投入生成系统的较少,主要有以下原因:

  1. 对DML、alter语句限制
  2. 需定时增量load导出导入
  3. 自带的MyISAM难以支持高并发,若想充分利用服务器资源,需开启另外的MySQL实例
  4. 对中文等多字节文字支持不好
  5. 仅支持单核调度
  6. 缺少原厂的支持

ICE与IEE版本区别

IEE包含针对大多数企业工作需求的附加特性,如:更好的查询性能、DML语句支持、分布式导入等。另外,IEE版本还包含了一定级别的Infobright原厂或代理商的支持救援服务、产品培训等。

  1. 明显的查询性能差异。虽然IEE和ICE版本均具有明显超出例如Oracle、SQL Server、MySQL等行式数据库的查询性能,但IEE还要比ICE版本快50-500%。这个明显差距来自于IEE核心引擎中特有的——多线程调度模块(自IEE3.5引入).而在ICE中,一个独立的查询只能使用单个CPU核心,其他的查询进程只能使用其他核心。对于需要筛选和区分大量数据的复杂查询,使用IEE多线程调度模块可以显著地节约查询时间。
  2. 支持DML语句。IEE支持标准的SQL 数据操作语言,使用insert、update、delete操控数据。而ICE只支持Load data infile进行数据导入,任何数据的变化都需要重新导入全部数据。DML语句的使用会降低数据查询性能,随次数递增。
  3. 支持DDL语句。包括alter table rename,add column,drop column(但是列操作只能对最后列生效)
  4. 支持Hadoop接口(通过DLP)
  5. 高级复制和高可用。IEE版本包含主从功能,基于SQL statement
  6. 更简易的导入和更快的导入速度。IEE支持分布式导入工具-DLP;且包含标准的MySQL原生loader,用于处理一些复杂数据的导入,另一方面也说明IBloader的容错性较差
  7. Load或DML同时的一致性查询
  8. 支持临时表
  9. 其他商业授权,售后支持等

架构

基于MySQL的内部架构 – Infobright采取与MySQL相似的内部架构,下面是Infobright的架构图:

Infobright

灰色部分是mysql原有的模块,白色与蓝色部分则是 infobright自身的。

Infobright跟mysql一样的两层结构:

  • 逻辑层:处理查询逻辑(服务及应用管理),逻辑层右端的loader与unloader是infobright的数据导入导出模块,也即处理SQL语句里LOAD DATA INFILE … 与SELECT … INTO FILE任务,由于infobright面向的是海量数据环境,所以这个数据导入导出模块是一个独立的服务,并非直接使用mysql的模块。逻辑层的infobright优化器包在mysql查询优化器的外面,如下面将会提到的,因为它的存储层有一些特殊结构,所以查询优化方式也跟 mysql有很大差异。
  • 存储引擎:Infobright的默认存储引擎是brighthouse,但是Infobright还可以支持其他的存储引擎,比如MyISAM、MRG_MyISAM、Memory、CSV。Infobright通过三层来组织数据,分别是DP(Data Pack)、DPN(Data Pack Node)、KN(Knowledge Node)。而在这三层之上就是无比强大的知识网络(Knowledge Grid)。

Infobright的模块

  1. Optimizer优化器。最小化的解压缩数据,有效提高执行计划。
  2. Knowledge Grid知识网格。存储元数据、列信息、表关系,数据块分布状态统计信息,同等查询状态缓存信息
  3. Data Pack数据块。真实数据压缩存放位置,按照数据存储块保存

Data Pack(数据块)压缩层

存储引擎最底层是一个个的Data Pack(数据块)。每一个Pack装着某一列的64K个元素,所有数据按照这样的形式打包存储,每一个数据块进行类型相关的压缩(即根据不同数据类型采用不同的压缩算法),压缩比很高。它上层的压缩器与解压缩器就做了这个事情。

Infobright号称数据压缩比率是10:1到40:1。前面我们已经说过了Infobright的压缩是根据DP里面的数据类型,系统自动选择压缩算法,并且自适应地调节算法的参数以达到最优的压缩比。先看看在实验环境下的压缩比率,如下图所示:

data-pack

整体的压缩比率是20.302。但是这里有一个误区,这里的压缩比率指的是数据库中的原始数据大小/压缩后的数据大小,而不是文本文件的物理数据大小/压缩后的数据大小。很明显前者会比后者大出不少。在我的实验环境下,后者是7:1左右。一般来说文本数据存入数据库之后大小会比原来的文本大不少,因为有些字段被设置了固定长度,占用了比实际更多的空间。还有就是数据库里面会有很多的统计信息数据,其中就包括索引,这些统计信息数据占据的空间绝对不小。Infobright虽然没有索引,但是它有KN数据,通常情况下KN数据大小占数据总大小的1%左右。

既然Infobright会根据具体的数据类型进行压缩,那我们就看看不同的数据类型具有什么样的压缩比率。如下表所示:

zip

首先看看Int类型的压缩比率,结果是压缩比率上Int<mediumint<smallint。细心地读者会很容易发现tinyint的压缩比率怎么会比int还小。数据压缩比率除了和数据类型有关之外,还和数据的差异性有特别大关系,这是显而易见。posFlag只有0,1,-1三种可能,这种数据显然不可能取得很好的压缩比率。

再看看act字段,act字段使用了comment lookup,比简单的char类型具有更佳的压缩比率和查询性能。comment lookup的原理其实比较像位图索引。对于comment lookup的使用下一章节将细细讲述。在所有的字段当中date字段的压缩比率是最高的,最后数据的大小只有0.1M。varchar的压缩比率就比较差了,所以除非必要,不然不建议使用varchar。

上面的数据很清楚地展示了Infobright强大的压缩性能。在此再次强调,数据的压缩不只是和数据类型有关,数据的差异程度起了特别大的作用。在选择字段数据类型的时候,个人觉得性能方面的考虑应该摆在第一位。比如上面表中一些字段的选择就可以优化,ip可以改为bigint类型,date甚至可以根据需要拆分成year/month/day三列。

Knowledge Grid(知识网格)

压缩层再向上就是infobright最重要的概念:Knowledge Grid(知识网格)这也是infobright放弃索引却能应用于大量数据查询的基础。Knowledge Grid构架是Infobright高性能的重要原因。它包含两类结点:

  1. Data Pack Node(数据块节点):Data Pack Node和Data Pack是一一对应的关系。DPN记录着每一个DP里面存储和压缩的一些统计数据,包括最大值(max)、最小值(min)、null的个数、单元总数count、sum。avg等等。至不同值的量等等;Knowledge Node则存储了一些更高级的统计信息,以及与其它表的连接信息,这里面的信息有些是数据载入时已经算好的,有些是随着查询进行而计算的,所以说是具备一 定的“智能”的。
  2. Knowledge Node里面存储着指向DP之间或者列之间关系的一些元数据集合,比如值发生的范围(MIin_Max)、列数据之间的关联。大部分的KN数据是装载数据的时候产生的,另外一些事是查询的时候产生。

knowledge-grid

Knowledge Grid可分为四部分,DPN、Histogram、CMAP、P-2-P。

DPN如上所述。

Histogram用来提高数字类型(比如date,time,decimal)的查询的性能。Histogram是装载数据的时候就产生的。DPN中有mix、max,Histogram中把Min-Max分成1024段,如果Mix_Max范围小于1024的话,每一段就是就是一个单独的值。这个时候KN就是一个数值是否在当前段的二进制表示。

Histogram

Histogram的作用就是快速判断当前DP是否满足查询条件。如上图所示,比如select id from customerInfo where id>50 and id<70。那么很容易就可以得到当前DP不满足条件。所以Histogram对于那种数字限定的查询能够很有效地减少查询DP的数量。

CMAP是针对于文本类型的查询,也是装载数据的时候就产生的。CMAP是统计当前DP内,ASCII在1-64位置出现的情况。如下图所示

cmap

比如上面的图说明了A在文本的第二个、第三个、第四个位置从来没有出现过。0表示没有出现,1表示出现过。查询中文本的比较归根究底还是按照字节进行比较,所以根据CMAP能够很好地提高文本查询的性能。

Pack-To-Pack是Join操作的时候产生的,它是表示join的两个DP中操作的两个列之间关系的位图,也就是二进制表示的矩阵。

join

  • 存储在memory中,作用域在一个Sission中
  • 提高JOIN查询性能,无论是新建还是复用的

粗糙集(Rough Sets)是Infobright的核心技术之一。Infobright在执行查询的时候会根据知识网络(Knowledge Grid)把DP分成三类:

  1. 相关的DP(Relevant Packs),满足查询条件限制的DP
  2. 不相关的DP(Irrelevant Packs),不满足查询条件限制的DP
  3. 可疑的DP(Suspect Packs),DP里面的数据部分满足查询条件的限制

案例:

SELECT COUNT(*) FROM employees WHERE salary > 100000 AND age < 35 AND job = ‘IT’ AND city = ‘San Mateo’;

rough-sets

  1. 查找包含salary > 100000的数据包
  2. 查找包含age < 35的数据包
  3. 查找包含job = ’IT’的数据包
  4. 查找包含city = ‘San Mateo’的数据包
  5. 去除所有与检索条件不相干的标记
  6. 最后在确定的数据包内解压缩相关数据
  7. 执行检索

从上面的分析可以知道,Infobright能够很高效地执行一些查询,而且执行的时候where语句的区分度越高越好。where区分度高可以更精确地确认是否是相关DP或者是不相关DP亦或是可以DP,尽可能减少DP的数量、减少解压缩带来的性能损耗。在做条件判断的使用,一般会用到上一章所讲到的Histogram和CMAP,它们能够有效地提高查询性能。多表连接的时候原理也是相似的。先是利用Pack-To-Pack产生join的那两列的DP之间的关系。比如:SELECT MAX(X.D) FROM T JOIN X ON T.B = X.C WHERE T.A > 6。Pack-To-Pack产生T.B和X.C的DP之间的关系矩阵M。假设T.B的第一个DP和X.C的第一个DP之间有元素交叉,那么M[1,1]=1,否则M[1,1]=0。这样就有效地减少了join操作时DP的数量。前面降到了解压缩,顺便提一提DP的压缩。每个DP中的64K个元素被当成是一个序列,其中所有的null的位置都会被单独存储,然后其余的non-null的数据会被压缩。数据的压缩跟数据的类型有关,infobright会根据数据的类型选择压缩算法。infobright会自适应地调节算法的参数以达到最优的压缩比。

Knowledge Grid还是比较复杂的,里面还有很多细节的东西,可以参考官方的白皮书和 Brighthouse: an analytic data warehouse for ad-hoc queries这篇论文。

comment lookup的使用

前面已经分析了Infobright的构架,简要介绍了Infobright的压缩过程和工作原理。现在来讨论查询优化的问题。

comment-lookup

1)配置环境:在Linux下面,Infobright环境的配置可以根据README里的要求,配置brighthouse.ini文件。

2)选取高效的数据类型

Infobright里面支持所有的MySQL原有的数据类型。其中Integer类型比其他数据类型更加高效。尽可能使用以下的数据类型:

  • TINYINT,SMALLINT,MEDIUMINT,INT,BIGINT
  • DECIMAL(尽量减少小数点位数)
  • DATE ,TIME

效率比较低的、不推荐使用的数据类型有:

  • BINARY VARBINARY
  • FLOAT
  • DOUBLE
  • VARCHAR
  • TINYTEXT TEXT

Infobright数据类型使用的一些经验和注意点:

  1. Infobright的数值类型的范围和MySQL有点不一样,比如Infobright的Int的最小值是-2147483647,而MySQl的Int最小值应该是-2147483648。其他的数值类型都存在这样的问题。
  2. 能够使用小数据类型就使用小数据类型,比如能够使用SMALLINT就不适用INT,这一点上Infobright和MySQL保持一致。
  3. 避免效率低的数据类型,像TEXT之类能不用就不用,像FLOAT尽量用DECIMAL代替,但是需要权衡毕竟DECIMAL会损失精度。
  4. 尽量少用VARCHAR,在MySQL里面动态的Varchar性能就不强,所以尽量避免VARCHAR。如果适合的话可以选择把VARCHAR改成CHAR存储甚至专程INTEGER类型。VARCHAR的优势在于分配空间的长度可变,既然Infobright具有那么优秀的压缩性能,个人认为完全可以把VARCHAR转成CHAR。CHAR会具有更好的查询和压缩性能。
  5. 能够使用INT的情况尽量使用INT,很多时候甚至可以把一些CHAR类型的数据往整型转化。比如搜索日志里面的客户永久id、客户id等等数据就可以用BIGINT存储而不用CHAR存储。其实把时间分割成year、month、day三列存储也是很好的选择。在我能见到的系统里面时间基本上是使用频率最高的字段,提高时间字段的查询性能显然是非常重要的。当然这个还是要根据系统的具体情况,做数据分析时有时候很需要MySQL的那些时间函数。
  6. varchar和char字段还可以使用comment lookup,comment lookup能够显著地提高压缩比率和查询性能。

3)使用comment lookup

comment lookup只能显式地使用在char或者varchar上面。Comment Lookup可以减少存储空间,提高压缩率,对char和varchar字段采用comment lookup可以提高查询效率。Comment Lookup实现机制很像位图索引,实现上利用简短的数值类型替代char字段已取得更好的查询性能和压缩比率。Comment Lookup的使用除了对数据类型有要求,对数据也有一定的要求。一般要求数据类别的总数小于10000并且当前列的单元数量/类别数量大于10。Comment Lookup比较适合年龄,性别,省份这一类型的字段。

comment lookup使用很简单,在创建数据库表的时候如下定义即可:

act   char(15)   comment 'lookup',
part  char(4) comment 'lookup',

4)尽量有序地导入数据

前面分析过Infobright的构架,每一列分成n个DP,每个DPN列面存储着DP的一些统计信息。有序地导入数据能够使不同的DP的DPN内的数据差异化更明显。比如按时间date顺序导入数据,那么前一个DP的max(date)<=下一个DP的min(date),查询的时候就能够减少可疑DP,提高查询性能。换句话说,有序地导入数据就是使DP内部数据更加集中,而不再那么分散。

5)使用高效的查询语句。

这里涉及的内容比较多了,总结如下:

  • 尽量不适用or,可以采用in或者union取而代之
  • 减少IO操作,原因是infobright里面数据是压缩的,解压缩的过程要消耗很多的时间。
  • 查询的时候尽量条件选择差异化更明显的语句
  • Select中尽量使用where中出现的字段。原因是Infobright按照列处理的,每一列都是单独处理的。所以避免使用where中未出现的字段可以得到较好的性能。
  • 限制在结果中的表的数量,也就是限制select中出现表的数量。
  • 尽量使用独立的子查询和join操作代替非独立的子查询
  • 尽量不在where里面使用MySQL函数和类型转换符
  • 尽量避免会使用MySQL优化器的查询操作
  • 使用跨越Infobright表和MySQL表的查询操作
  • 尽量不在group by 里或者子查询里面使用数学操作,如sum(a*b)。
  • select里面尽量剔除不要的字段。
  • 避免使用select * from table
  • 避免使用union all
  • 尽量使用系统提供的函数

Infobright执行查询语句的时候,大部分的时间都是花在优化阶段。Infobright优化器虽然已经很强大,但是编写查询语句的时候很多的细节问题还是需要程序员注意。

Infobright导入工具

  • Insert
  • MySQL 导入工具 (@bh_dataformat=’mysql’)
  • ETL工具:http://www.infobright.org/Downloads/Contributed‐Software/
  • Infobright 自身的导入工:CSV格式(@bh_dataformat=’txt_variable’),二进制格式(@bh_dataformat=’binary’)
  • DLP 分布式导入工具(1.6TB/小时)

参考链接:

【翻译】优秀的RESTful API的设计原则

$
0
0
原文地址: http://codeplanet.io/principles-good-restful-api-design/
原文作者:Thomas Hunter Ii
第一次翻译,若有不对的地方,敬请谅解,若转载请注明本文地址。

目录
1. 定义(Definitions)
2. 数据的设计与抽象化(Data Design and Abstraction)
3. HTTP动词 (Verbs)
4. 版本(Versioning)
5. 分析 (Analytics)
6. API根路径 (API Root URL)
7. 路径 (Endpoint)
8. 信息过滤 (Filtering)
9. 状态码 (Status Codes)
10. 文档返回值 (Expected Return Documents)
11. 身份认证 (Authentication)
12. 内容形式 (Content Type)
13. 超媒体 (Hypermedia APIs)
14. 文档 (Documentation)
15. 其它:HTTP包文


引言

众所周知,优秀的API设计是相当困难的!API建立起服务器和那些希望调用数据的客户之间的联系。打破了这个约定会导致开发者收到大量的报怨邮件,大量的客户因此要修改他们不能继续使用的APP。当然API文档可以有效降低这些问题,但是大多数程序员都不爱写文档。

建立起API机制是提高你的服务的重要途径之一。通过建立API,你的服务器/请求中心会变成其它请求服务的一个平台。看看那些拥有大数据的公司:Facebook,Twitter,谷歌,GitHub,亚马逊,Netfilx,无一不是因为他们开放了数据中心的API,使得他们变得很强大。事实上,这个产业生存的最重要的原因就是那些平台所提供的消费数据。

越早建立起你的API,越多的人们会使用你的服务。

在你设计API时,及时的更新文档,这些文档的设计的目的是为了确保调用你API的客户能更容易的理解,这样你可以大幅的减少收到关于困惑或是抱怨的邮件。我将会整理设计原则方面的篇章,你可以根据你的需要读其中的一段或几段。

1. 定义 (Definitions)

以下是一些我会在这篇文章中用到的名词:

•    资源(Resource): 一个对象的记录。如:一个动物。
•    集合(Collection): 很多对象的集合。如:很多动物。
•    HTTP: 互联网交互的协议。
•    客户(Consumer): 通过电脑会做出HTTP请求的客户。
•    第三方开发人员(Third Party Developer): 需要获取服务器数据的第三方开发人员。
•    服务器(Server): 一个可以处理客户请求的HTTP的服务或应用。
•   路径(Endpoint): 一个服务器API的路径,代表了一个资源或一个集合。


2. 数据的设计与抽象化 (Data Design and Abstraction)

设计API比你想象的要简单。首先你需要考虑如何设计你的数据以及你的服务哭/请求中心将如何工作。如果你在开始写程序的时候就应该考虑API的设计,这样会使得一切变得简单起来。但是如果你想要在已经完成的程序中添加API,你可能需要提供更多的抽象化服务。

这里,我们临时将一个集合(Collection)看做是一个数据库表,资源(Resource)看作是数据表中的一条记录。当然这是一个特殊的例子,事实上,你的API必须将你的数据和业务逻辑尽可能的剥离开来。这点是非常重要的,这样可以消除第三方调用API的开发者的困惑,否则导致的结果就是他们不想使用你的API。

当然,有些重要的服务是不能通过API暴露给第三方用户的,一个简单的例子就是很多API不允许第三方使用者调用API来新增用户。

3. HTTP动词 (Verbs)

毫无疑问,你肯定知道GET和POST请求。它们是浏览器中最常用的请求类型。POST请求非常流行以致于都快成了日常用语。很多对互联网的原理一无所知的人们却可能知道他们可以通过Facebook的友好请求,‘POST‘一些信息上去。

有关于HTTP的动作,你需要掌握四个半。其中的半个是因为‘PATCH‘ 和’PUT‘这个动作很像。对于API开发者来说,其它四个HTTP动作都是有两两联系的。下面是具体的作动,以及解释(在这里我假设大部分的读者都懂得数据库方面的知识)。

GET(select):从服务器中取出资源。(一个或是多个)
POST(create):在服务器上新增一个资源。
PUT(update):在服务器上修改资源(需要提供整个资源信息)。
PATCH(update):在服务器上修改资源(只需要提供待修改的资源信息)。
Delete(delete):从服务器上删除资源。


以下是两个比较少用到的HTTP动作:
HEAD:从服务器上取回单个元数据的资源,例如数据的哈希值或数据的最后更新时间等。
OPTIONS:从服务器上取回客户允许得到的资源信息。

一个好的RESTful API会使用以上“四个半”动作来供第三方使用者获得或上传数据。并且URL不会包含任何的ACTION或是其它的动作。

浏览器通常会缓存GET请求(大多数如此!),例如一个被缓存了的GET请求(需要看缓存的HEADERS)要比POST请求快一些。一个HEAD请求一般是没有回应BODY的GET请求,当然也能被缓存。

4. 版本(Versioning)

无论你建立的是怎样的服务,在开始写程序前做了大量的设计,只要你的服务器代码有变动,那么你的数据关系就可能发生变化,比如你的数据资源可能会新增或删除一些属性。这是开发必不可少的工作,尤其是你的程序已经上线并且被很多人所使用。

请记住API是服务提供方和客户之间的联系。如果你需要改变你的服务器API,这将会破坏兼容性问题。如果你要破坏原有的API,你的客户将会痛恨你。如果你持续的修改你的API,你将会失去很多客户。所以需要你确保你的应用是逐步的更新,这样才会使得调用你的API的客户满意。你需要建立API的版本并且在有需要的时候介绍新的版本给客户,并且保持老的版本可持续访问。

另外一个建议是如果你的API只是简单的新增一些特性,例如在你的资源中新增一些属性(这些属性不是必填项,资源没有这些属性也能照常运行),或者你新增一些新的路径(Endpoints),这个时候你不必更新你的API版本号,因为你所做的这些改变并不会打破现有的兼容性。当然你需要更新你的API文档。

过段时间后,你可以不建议使用你的API的旧版本。不过不建议使用并不意味着关闭它或降低那个版本的质量,而只是告诉客户旧版本将会在未来的某一天不能使用,需要他们尽快更新到最新的版本。

好的RESTful API会在URL上显示版本号。最常见的解决办法就是把版本号放在请求的头部中。但经过我跟很多不同的开发人员的讨论,得出的结论是将版本号放在HTTP头信息中不如放在URL上来的方便和直观。例:将API的版本号放入URL: https://api.example.com/v1/

5. 分析 (Analytics)

优秀的程序会经常追踪客户调用你的API的版本/路径。这个可以在你的数据库中用整数型字段统计下,当每次请求过来的时候,就增加一次。这样做有很多优点,最常见的好处是我们就可以知道哪个API请求是最高效的。

为了创建第三方开发人员喜欢的API,最重要的事情是当你不赞成使用旧的API版本时,你需要联系那些还在使用旧版本的开发人员。这可能是你升级并彻底放弃一个API旧版本最好的方法。

通知第三方开发人员可以是自动的,例如当超过10,000的不赞成使用的请求被调用时,就可以自动发邮件给那些开发人员。

6. API根路径 (API Root URL)

你的API根路径(域名)的设计非常重要。当一个开发人员(可看作是作古派风格的开发者)在一个很老的程序上使用你的API的新的特性时,他们可能不知道你服务器,也许他们可能只是知道一系列的后缀请求的URL。所以尽量简化你的API根路径,因为一个复杂的URL会使得一些开发人员放弃你的API。

以下是两个常见的根路径URL:
•   https://example.org/api/v1/*
•   https://api.example.com/v1/*


如果你的应用很大或是你预期你的应用会很大,那么把你的API放到单独的子域里是个很不错的选择,这样可以使得你的API更加的灵活容易维护。例如:https://api.example.com/v1/*

如果你觉得你的API不是很复杂,或者是你只想要个小应用程序(比如你希望你的网站和API放在同一个框架下),那么就把你的API放在根域名的URL后面。例如:https://example.org/api/v1/*

有个可以存放API路径列表的页面是个很好的主意。比如点击GitHub的API的根路径就可以得到一个路径列表。就我个人而言,我是非常喜欢这种点击根路径就得到所有API URL或者得到API的开发文档的形式,这样有助于开发者的开发。

另外,作为好的RESTful API, API与用户的通信协议,总是使用HTTPs协议。

7. 路径 (Endpoint)

路径就是你API的URL,它指出了资源的详情或是一堆资源的集合。

举例说明:
如果你想要建立一个虚构的API来代表不同的动物园,每个动物园包含很多动物(每个动物只属于一个动物园),员工(可以在多个动物园里工作)照顾每个动物,那么你建的路径如下:

•    https://api.example.com/v1/zoos
•    https://api.example.com/v1/animals
•    https://api.example.com/v1/animal_types
•    https://api.example.com/v1/employees


关于每个路径的作用,你需要列举出一些HTTP动作的路径。以下列举了一些可行的动作来代表一个可以运作的API,值得注意的是我已经把每个HTTP动作放到了路径前,并写上了一些注释。
•    GET /zoos: 列出所有的动物园(返回ID和名称,没有其它更详细的资料。)
•    POST /zoos: 新建一个动物园
•    GET /zoos/ZID: 根据ZID取出一整个动物园的对象
•    PUT /zoos/ZID: 更新一个动物园(传入全部属性以及ZID)
•    PATCH /zoos/ZID: 更新一个动物园(传入要更新的属性以及ZID)
•    DELETE /zoos/ZID: 删除一个动物园
•    GET /zoos/ZID/animals: 根据ZID取出动物的列表(返回动物的ID和名称)
•    GET /animals: 列举出所有的动物。(返回动物ID和名称)
•    POST /animals: 新建一个动物。
•    GET /animals/AID: 根据动物的ID取出相应的动物的对象。
•    PUT /animals/AID: 新增一个动物(传入全部属性以及AID)
•    PATCH /animals/AID: 新增一个动物(传入要更新的属性以及AID)
•    GET /animal_types: 根据传入的动物园类型获取动物园的列表(返回动物园ID和名称)
•    GET /animal_types/ATID: 根据传入的动物园类型ID获取一个动物园的信息
•    GET /employees: 获取所有的员工列表
•    GET /employees/EID: 根据员工的ID获取员工信息
•    GET /zoos/ZID/employees: 获取在指定动物园里工作的员工列表(返回员工ID和姓名)
•    POST /employees: 新增一个员工
•    POST /zoos/ZID/employees: 在指定的动物园里雇佣一个员工
•    DELETE /zoos/ZID/employees/EID: 在指定的动画园里解雇一个员工


上述列表中,ZID表示动物园ID,AID表示动物ID,EID表示员工ID,ATID表示动物类型ID。在你的文档中标明是简写意思一个很好的习惯。

我简化了以上这些常用的API URL前缀。这个形式有利于开发时的交流,当然在你正式的API文档中,你需要补齐全部URL的路径。(例如:GET http://api.example.com/v1/animal_type/ATID).

值得注意的是关于员工和动物园的数据间,可以有很多不同的展示及联系,下面我会列举一个URL,来说明下它们之间特殊的相互影响。比如当我们想解雇一个员工时,我们没有一个HTTP动作叫‘FIRE’,但是我们可以通过HTTP动作‘DELETE’来表示员工的解雇,这样也达到了相同的目的。

8. 信息过滤 (Filtering)

当一个客户发来获取一个对象的列表请求时,服务器所给他的每一个对象是否与请求的标准相匹配是非常重要的。如果这个列表的记录数量很多。那么很重要的一点就是服务器不能将它们都返回给用户。不然这么庞大的数据量会使得第三方开发人员很难去分析获得的数据。如果他们请求的是一个确定的集合,然后迭代得到结果,那么他们就不会想要再看到另外多出来的那100条数据。否则他们就要花额外的工作量去分析那多出来的数据。问题是他们的程序去要去做一些限制呢?还是任由这样的事情发生而不去管呢?

最好的办法是API提供一些参数,让第三方开发人员传参去限制。

通过传参的模式,服务器可以为客户提供一些对结果集的排序等功能。当然最重要的是限定参数的办法使得网络负担变小了,同时客户也能准确的得到他们想要的数据。此外,客户也可能是比较懒惰的,如果客户端能为他们提供过滤或是分页的数据,那何乐而不为呢?

信息过滤最常见是存在于HTTP动作的GET中,既然他们是GET请求,那么过滤参数就必须写在URL里。下面是一些你可以参考的关于过滤参数的例子:

•    ?limit=10: 指定返回记录的数量 (有分页)
•    ?offset=10: 返回记录的开始位置 (有分页)
•    ?animal_type_id=1:  指定筛选条件。(例如WHERE animal_type_id = 1)
•    ?sortby=name&order=asc: 指定返回结果按照哪个属性排序,以及排序顺序。 (例如ORDER BY name ASC)


对于API的路径和URL的参数允许存在冗余(即重复)。比如我之前的例子 GET/zoo/ZID/animals和GET /animals?zoo_id=ZID的含义就是相同的。有些专用的路径会方便客户的调用,特别是那些你预计他们会经常使用的路径。在你的API文档中,需要写清楚哪些是冗余的,这样可以消除第三方开发人员的疑惑。

另外还要提一下,当参数可以指定按照某一属性进行排序时,你需要指出哪些属性是可以排序的,即需要整理出可以进行排序的属性的白名单,因为我们不需要发送错误的数据给客户。

9. 状态码 (Status Codes)

作为一个RESTful的API,你需要正确使用HTTP的响应码。他们是一套标准,许多网络技术、设备都能读懂。例如负载均衡(load balancers)就能分辩出这些状态码并且避免了当错误为50开头时发送给浏览器的很多回应。HTTP有很多很状态码,查看全部请点击这里,下面我简单列了一些重要的状态码:

•    200 OK - [GET]:服务器成功返回用户请求的数据,该操作是幂等的(Idempotent)。
•    201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功。
•    204 NO CONTENT - [DELETE]:用户删除数据成功。
•    400 INVALID REQUEST - [POST/PUT/PATCH]:用户发出的请求有错误,服务器没有进行新建或修改数据的操作,该操作是幂等的。
•    404 NOT FOUND -
  • :用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。
  • •    500 INTERNAL SERVER ERROR - [*]:服务器发生错误,用户将无法判断发出的请求是否成功。

    状态码边界:

    1XX状态码是HTTP比较低等级的状态码,在HTTP/1.0协议中没有定议任何1XX状态码,所以最好不要向客户发送任何1XX的状态码。
    2XX状态码说明了客户端接收了正确的消息。这种状态码是极好的,所以确保你的服务器发送的都是这类的状态码。
    3XX状态码用来重定向。很多API通常不会使用这些请求(SEO优化人员会使用它们)。值得一提的是,新出来的Hypermedia API还是会使用3XX的状态码的。
    4XX状态码表示客户端的请求错误。比如客户端提供了错误的参数或请求要获取的不存在的对象。对于客户端和服务器端来说这些请求是等幂的。
    5XX状态码一般是服务器在处理请求过程中的异常。一般来说这些错误被低等级的方法所抛出,当然也不排除开发人员手动发送,来确保某个客户得到某种回应。当服务器发送了5XX状态码,这个客户可能都不会收到任何回应,所以这些错误也是不可避免的。

    10. 文档返回值 (Expected Return Documents)

    当使用不同的HTTP动作来获取资源时,一个用户需要获取一些有排序的数据,以下列举了典型的RESTful APIs:

    •    GET /collection: 返回一个资源集合。
    •    GET /collection/resource: 返回单个资源对象。
    •    POST /collection: 返回最新创建的资源对象。
    •    PUT /collection/resource: 返回更新完毕的资源对象。
    •    PATCH /collection/resource: 返回更新完毕的资源对象。
    •    DELETE /collection/resource: 返回一个空的文档。


    当客户新增一个资源时,他们通常不知道新增的资源的ID(或最新更新的时间等属性),这些额外的属性需要通过POST同时返回给客户。

    11. 身份认证 (Authentication)

    大多数情况下服务器会对请求方的身份进行验证。当然有些API提供了可以被普通用户调用的路径,但多数的API都需要认证的过程。

    OAuth2.0提供了一个很好的解决方案。在每次请求时,服务器需要知道哪个第三方程序发来想要获取系统的某个用户信息的请求,这时候,就需要用户授权给那个第三方程序,允许它获得用户的信息。

    除此之外,还有OAuth 1.0 和 xAuth。无论你用的是哪种认证技术,你都需要确保你所使用的是常见技术,然后编辑好文 档和供不同平台使用的类库。

    就我的经验来看,关于以安全著称的OAuth1.0a,对它对接接口是非常痛苦的事。我最近惊奇的发现由于它没有提供很多语言的类型,很多的第三方开发人员都开始拥有了自己的类库。我曾经为了它用了好几个小时来调试一个叫“无效签名”的错误,这真是太糟糕了。

    12. 内容形式 (Content Type)

    关于服务器返回的数据格式,应该尽量使用JSON格式。包括Facebook, Twitter, GitHub都是使用了这种格式。其它的格式还有XML、SOAP。但是XML格式容易引起很多纠纷(特别是大型交互环境)。SOAP在当今几乎没人用了。

    开发人员使用流行的语言我框架可以对数据进行有效的转换。如果服务器想提供一个很普通的回应对象,那么它可以很容易的提供上述的数据格式(不包括SOAP)。值得注意的是,你需要正确使用响应文档中的头部内容。

    一些API创建者在响应内容中提供了响应格式的参数,包括json\xml\html。虽然本人很少这样干,但仍然觉得这是个不错的形式。

    13. 超媒体 (Hypermedia APIs)

    Hypermedia APIs是RESTful API设计的发展趋势,即在返回结果中提供链接。这样可以使用户在不查API文 档的前提下,也可能知道下一步应该要做什么。

    当使用一个不是Hypermedia的RESTful API时,路径是服务器和客户之间的联系。但这些路径必须是被客户知道的,一旦服务器改变了这些路径的作用,那么客户将不能跟服务器进行通信,这就是传统RESTful API的一大限制。

    对于现在来说,API客户不再是发起HTTP的唯一请求方,用着浏览器的人们都是发起HTTP的请求方。但是这些人并不会局限于使用预先定定好的RESTful API。这是为什么呢?因为他们读的是真正的内容,他们点击一些想要读的标题,然后一个网页就会打开来了,他们就可以知道他们真正想要的内容。如果一个URL改变了,网友们是不会受影响的(除非他们定阅了一个页面,而不是在首页是新打开一个标题去阅读)

    Hypermedia API的概念跟现实人类的世界很像。请求API的根目录得到可以请求每个集合信息以及描述这些集合的用途的URL列表。另外,提供给每个资原的ID不是必须的,因为URL里已经提供。

    当一个用户使用Hypemedia风格的API的链接来收集信息时,响应里一般都会包含最新的URL,这些URL没必要提前写在文档里。如果一个URL被缓存了,那么可能就会返回404错误,客户可以很容易的回到根目录来找到正确的URL。

    当要获取一个资源列表的集合,可以在响应的资源里放上一个关于URL的属性。当需要调用POST/PATCH/PUT,服务器回应的时候可以直接用3XX的响应码来做重定向。

    14. 文档 (Documentation)

    说实话,如果你不是100%确定你的标准,那么你的API也不是最糟糕的。但如果你的API没有文 档,这将会是很糟糕的API,那么没有人将会使用你的API。

    所以撰写API文档是十分有必要的。

    不要使用自动生成的文档,但如果你真的这么做了,那一定要保证你检查和校验过这个文档。
    不要缩减请求和响应的内容,最好就全部展求。在你的文档中有些重点的要加粗。

    在文档中,要加上每个路径的响应的内容和可能出现的信息错误,并尽可能的写上在什么情况下可能导致这些错误。

    如果你有时间的话,就建立一个开发者API的终端机,这样开发者就可以更方便 的体验你的API。这对你来说其实并不困难,并且所有的开发者(你公司内部的或第三方开发者)都会很喜欢这种形式。

    必须得确保你的文档可以被打印。CSS是个很强大的技术。当要打印的时候不必要隐藏你的侧边栏,因为很多开发者都喜欢离线复印去阅读API的文档。

    15. 其它:HTTP包文

    既然我们所做的所有事都是基于HTTP,那么我们接下来要讨论下HTTP包文。如果一个从事这个工作的人不知道HTTP包文,那是很糟糕的事情。当一个客户发送给服务器发送请求,会提供一个包含键/值(Key/Value)的集合,叫头部(Header),再过几行就是请求的内容(Body)了。头部和内容是放在同一个包里进行发送的。

    服务器的响应格式也是一对对的键/值(Key/Value)形式,HTTP是一个请求/响应的协议,它不支持服务器无缘无故的主动推送给客户端,除非你使用另一个叫Websockets的协议。
    当设计API时,一般需要用一些工具来帮助你看HTTP的包。比如Wireshark,当然你也可以用一些框架/WEB的服务自带的工具来确保

    例子: HTTP的请求
    POST /v1/animal HTTP/1.1
    Host: api.example.org
    Accept: application/json
    Content-Type: application/json
    Content-Length: 24
    {"name": "Gir","animal_type": 12
    }


    例子: HTTP的响应
    HTTP/1.1 200 OK
    Date: Wed, 18 Dec 2013 06:08:22 GMT
    Content-Type: application/json
    Access-Control-Max-Age: 1728000
    Cache-Control: no-cache
    {"id": 12,"created": 1386363036,"modified": 1386363036,"name": "Gir","animal_type": 12
    }









    已有 0人发表留言,猛击->> 这里<<-参与讨论


    ITeye推荐



    非关系性分布式数据库:HBase

    $
    0
    0

    HBase是一个分布式的、面向列的开源数据库,该技术来源于 Fay Chang 所撰写的Google论文“Bigtable:一个结构化数据的分布式存储系统”。就像Bigtable利用了Google文件系统(File System)所提供的分布式数据存储一样,HBase在Hadoop之上提供了类似于Bigtable的能力。HBase在列上实现了Bigtable论文提到的压缩算法、内存操作和布隆过滤器。Base是Apache Hadoop的数据库,能够对大型数据提供随机、实时的读写访问。HBase的目标是存储并处理大型的数据。HBase的表能够作为MapReduce任务的输入和输出,可以通过Java API来存取数据,也可以通过REST、Avro或者Thrift的API来访问。HBase不同于一般的关系数据库,它是一个适合于非结构化数据存储的数据库。另一个不同的是HBase基于列的而不是基于行的模式。

    google-hbase

    HBase 不同于一般的关系数据库,它是一个适合于非结构化数据存储的数据库。所谓非结构化数据存储就是说HBase是基于列的而不是基于行的模式,这样方便读写大数据内容。HBase是介于Map Entry(key & value)和DB Row之间的一种数据存储方式。有点类似于现在流行的Memcache,但不仅仅是简单的一个key对应一个 value,你很可能需要存储多个属性的数据结构,但没有传统数据库表中那么多的关联关系,这就是所谓的松散数据。简单来说,你在HBase中的表创建的可以看作是一张很大的表,而这个表的属性可以根据需求去动态增加,在HBase中没有表与表之间关联查询。你只需要告诉你的数据存储到HBase的那个column families 就可以了,不需要指定它的具体类型:char,varchar,int,tinyint,text等等。但是你需要注意HBase中不包含事务此类的功能。与hadoop一样,HBase目标主要依靠横向扩展,通过不断增加廉价的商用服务器,来增加计算和存储能力。HBase中的表一般有这样的特点:

    • 大:一个表可以有上亿行,上百万列
    • 面向列:面向列(族)的存储和权限控制,列(族)独立检索。
    • 稀疏:对于为空(null)的列,并不占用存储空间,因此,表可以设计的非常稀疏。

    HBase的优点:

    1. 列的可以动态增加,并且列为空就不存储数据,节省存储空间
    2. HBase自动切分数据,使得数据存储自动具有水平scalability
    3. HBase可以提供高并发读写操作的支持

    HBase的缺点:

    1. 不能支持条件查询,只支持按照Row key来查询
    2. 暂时不能支持Master server的故障切换,当Master宕机后,整个存储系统就会挂掉

    下图是HBase在Hadoop Ecosystem中的位置:

    hadoop

    HBase位于结构化存储层,围绕HBase,各部件对HBase的支持情况:

    • HDFS:高可靠的底层存储支持
    • MapReduce:高性能的计算能力
    • Zookeeper:稳定服务和failover机制
    • Pig&Hive:高层语言支持,便于数据统计
    • Sqoop:提供RDBMS数据导入,便于传统数据库向HBase迁移

    HBase 的数据模型

    HBase以表的形式存储数据。表有行和列组成。列划分为若干个列族(row family)

    • Row Key: Table主键 行键 Table中记录按照Row Key排序
    • Timestamp:每次对数据操作对应的时间戳,也即数据的version number
    • Column Family:列簇,一个table在水平方向有一个或者多个列簇,列簇可由任意多个Column组成,列簇支持动态扩展,无须预定义数量及类型,二进制存储,用户需自行进行类型转换

    table

    Row Key

    与nosql数据库们一样,row key是用来检索记录的主键。访问HBase table中的行,只有三种方式:

    1. 通过单个row key访问
    2. 通过row key的range
    3. 全表扫描

    Row key行键 (Row key)可以是任意字符串(最大长度是 64KB,实际应用中长度一般为 10-100bytes),在HBase内部,row key保存为字节数组。存储时,数据按照Row key的字典序(byte order)排序存储。设计key时,要充分排序存储这个特性,将经常一起读取的行存储放到一起。(位置相关性)。需要注意的:字典序对int排序的结果是1,10,100,11,12,13,14,15,16,17,18,19,2,20,21,…,9,91,92,93,94,95,96,97,98,99。要保持整形的自然序,行键必须用0作左填充。行的一次读写是原子操作 (不论一次读写多少列)。这个设计决策能够使用户很容易的理解程序在对同一个行进行并发更新操作时的行为。

    列族

    HBase表中的每个列,都归属与某个列族。列族是表的schema的一部分(而列不是),必须在使用表之前定义。列名都以列族作为前缀。例如courses:history,courses:math都属于courses 这个列族。

    访问控制、磁盘和内存的使用统计都是在列族层面进行的。实际应用中,列族上的控制权限能帮助我们管理不同类型的应用:我们允许一些应用可以添加新的基本数据、一些应用可以读取基本数据并创建继承的列族、一些应用则只允许浏览数据(甚至可能因为隐私的原因不能浏览所有数据)。

    时间戳

    HBase中通过row和columns确定的为一个存贮单元称为cell。每个 cell都保存着同一份数据的多个版本。版本通过时间戳来索引。时间戳的类型是 64位整型。时间戳可以由HBase(在数据写入时自动 )赋值,此时时间戳是精确到毫秒的当前系统时间。时间戳也可以由客户显式赋值。如果应用程序要避免数据版本冲突,就必须自己生成具有唯一性的时间戳。每个 cell中,不同版本的数据按照时间倒序排序,即最新的数据排在最前面。为了避免数据存在过多版本造成的的管理 (包括存贮和索引)负担,HBase提供了两种数据版本回收方式。一是保存数据的最后n个版本,二是保存最近一段时间内的版本(比如最近七天)。用户可以针对每个列族进行设置。Cell由{row key, column(=<family> + <label>), version} 唯一确定的单元。cell中的数据是没有类型的,全部是字节码形式存贮。

    HBase的物理存储

    HBase中的所有数据文件都存储在Hadoop HDFS文件系统上,格式主要有两种:

    1. HFile HBase中KeyValue数据的存储格式,HFile是Hadoop的二进制格式文件,实际上StoreFile就是对HFile做了轻量级包装,即StoreFile底层就是HFile
    2. HLog File,HBase中WAL(Write Ahead Log) 的存储格式,物理上是Hadoop的Sequence File

    HFile

    HFile的格式为:

    hfile

    HFile分为六个部分:

    1. Data Block 段–保存表中的数据,这部分可以被压缩
    2. Meta Block 段 (可选的)–保存用户自定义的kv对,可以被压缩。
    3. File Info 段–Hfile的元信息,不被压缩,用户也可以在这一部分添加自己的元信息。
    4. Data Block Index 段–Data Block的索引。每条索引的key是被索引的block的第一条记录的key。
    5. Meta Block Index段 (可选的)–Meta Block的索引。
    6. Trailer–这一段是定长的。保存了每一段的偏移量,读取一个HFile时,会首先读取Trailer,Trailer保存了每个段的起始位置(段的Magic Number用来做安全check),然后,DataBlock Index会被读取到内存中,这样,当检索某个key时,不需要扫描整个HFile,而只需从内存中找到key所在的block,通过一次磁盘io将整个block读取到内存中,再找到需要的key。DataBlock Index采用LRU机制淘汰。

    HFile文件不定长,长度固定的块只有两个:Trailer和FileInfo

    1. Trailer中指针指向其他数据块的起始点
    2. File Info中记录了文件的一些Meta信息,例如:AVG_KEY_LEN, AVG_VALUE_LEN, LAST_KEY, COMPARATOR, MAX_SEQ_ID_KEY等

    HFile的Data Block,Meta Block通常采用压缩方式存储,压缩之后可以大大减少网络IO和磁盘IO,随之而来的开销当然是需要花费cpu进行压缩和解压缩。目标Hfile的压缩支持两种方式:Gzip,Lzo。

    Data Index和Meta Index块记录了每个Data块和Meta块的起始点。Data Block是HBase I/O的基本单元,为了提高效率,HRegionServer中有基于LRU的Block Cache机制。每个Data块的大小可以在创建一个Table的时候通过参数指定,大号的Block有利于顺序Scan,小号Block利于随机查询。每个Data块除了开头的Magic以外就是一个个KeyValue对拼接而成, Magic内容就是一些随机数字,目的是防止数据损坏。

    HFile里面的每个KeyValue对就是一个简单的byte数组。这个byte数组里面包含了很多项,并且有固定的结构。

    keyvalue

    1. KeyLength和ValueLength:两个固定的长度,分别代表Key和Value的长度
    2. Key部分:Row Length是固定长度的数值,表示RowKey的长度,Row 就是RowKey,Column Family Length是固定长度的数值,表示Family的长度,接着就是Column Family,再接着是Qualifier,然后是两个固定长度的数值,表示Time Stamp和Key Type(Put/Delete)
    3. Value部分没有这么复杂的结构,就是纯粹的二进制数据

    HLog File

    hlog-file

    HLog文件就是一个普通的Hadoop Sequence File,Sequence File 的Key是HLogKey对象,HLogKey中记录了写入数据的归属信息,除了table和region名字外,同时还包括 sequence number和timestamp,timestamp是“写入时间”,sequence number的起始值为0,或者是最近一次存入文件系统中sequence number。HLog Sequece File的Value是HBase的KeyValue对象,即对应HFile中的KeyValue。

    HBase 的系统架构

    hbase

    Client

    包含访问HBase的接口,client维护着一些cache来加快对HBase的访问,比如regione的位置信息。

    1. 使用HBase RPC机制与HMaster和HRegionServer进行通信
    2. Client与HMaster进行通信进行管理类操作
    3. Client与HRegionServer进行数据读写类操作

    Zookeeper

    1. 保证任何时候,集群中只有一个master,避免HMaster单点问题
    2. 存贮所有Region的寻址入口。
    3. 实时监控Region Server的状态,将Region server的上线和下线信息实时通知给Master,HRegionServer把自己以Ephedral方式注册到Zookeeper中,HMaster随时感知各个HRegionServer的健康状况
    4. 存储HBase的schema,包括有哪些table,每个table有哪些column family,Zookeeper Quorum存储-ROOT-表地址、HMaster地址

    HMaster

    HMaster没有单点问题,HBase中可以启动多个HMaster,通过Zookeeper的Master Election机制保证总有一个Master在运行,主要负责Table和Region的管理工作:

    1. 管理用户对表的增删改查操作
    2. 管理HRegionServer的负载均衡,调整Region分布
    3. Region Split后,负责新Region的分布,发现失效的region server并重新分配其上的region。
    4. 在HRegionServer停机后,负责失效HRegionServer上Region迁移
    5. 负责GFS上的垃圾文件回收

    HRegionServer

    HBase中最核心的模块,主要负责响应用户I/O请求,向HDFS文件系统中读写数据

    1. HRegionServer管理一些列HRegion对象
    2. 每个HRegion对应Table中一个Region,HRegion由多个HStore组成
    3. 每个HStore对应Table中一个Column Family的存储
    4. Column Family就是一个集中的存储单元,故将具有相同IO特性的Column放在一个Column Family会更高效
    5. HRegionServer负责切分在运行过程中变得过大的region

    region

    可以看到,client访问HBase上数据的过程并不需要HMaster参与(寻址访问zookeeper和HRegionServer,数据读写访问HRegioneServer),HMaster仅仅维护者table和region的元数据信息,负载很低。

    HStore

    HBase存储的核心。由MemStore和StoreFile组成。MemStore是Sorted Memory Buffer。用户写入数据的流程:

    hstore

    Client写入 -> 存入MemStore,一直到MemStore满 -> Flush成一个StoreFile,直至增长到一定阈值 -> 出发Compact合并操作 -> 多个StoreFile合并成一个StoreFile,同时进行版本合并和数据删除 -> 当StoreFiles Compact后,逐步形成越来越大的StoreFile -> 单个StoreFile大小超过一定阈值后,触发Split操作,把当前Region Split成2个Region,Region会下线,新Split出的2个孩子Region会被HMaster分配到相应的HRegionServer上,使得原先1个Region的压力得以分流到2个Region上。由此过程可知,HBase只是增加数据,有所得更新和删除操作,都是在Compact阶段做的,所以,用户写操作只需要进入到内存即可立即返回,从而保证I/O高性能。

    HLog

    在分布式系统环境中,无法避免系统出错或者宕机,一旦HRegionServer以外退出,MemStore中的内存数据就会丢失,引入HLog就是防止这种情况。每个HRegionServer中都会有一个HLog对象,HLog是一个实现Write Ahead Log的类,每次用户操作写入Memstore的同时,也会写一份数据到HLog文件,HLog文件定期会滚动出新,并删除旧的文件(已持久化到StoreFile中的数据)。当HRegionServer意外终止后,HMaster会通过Zookeeper感知,HMaster首先处理遗留的HLog文件,将不同region的log数据拆分,分别放到相应region目录下,然后再将失效的region重新分配,领取到这些region的HRegionServer在Load Region的过程中,会发现有历史HLog需要处理,因此会Replay HLog中的数据到MemStore中,然后flush到StoreFiles,完成数据恢复。

    HBase的访问

    1. Native Java API,最常规和高效的访问方式,适合Hadoop MapReduce Job并行批处理HBase表数据
    2. HBase Shell,HBase的命令行工具,最简单的接口,适合HBase管理使用
    3. Thrift Gateway,利用Thrift序列化技术,支持C++,PHP,Python等多种语言,适合其他异构系统在线访问HBase表数据
    4. REST Gateway,支持REST 风格的Http API访问HBase, 解除了语言限制
    5. Pig,可以使用Pig Latin流式编程语言来操作HBase中的数据,和Hive类似,本质最终也是编译成MapReduce Job来处理HBase表数据,适合做数据统计
    6. Hive,当前Hive的Release版本尚没有加入对HBase的支持,但在下一个版本Hive 0.7.0中将会支持HBase,可以使用类似SQL语言来访问HBase

    参考链接:

    HTML无害化和Sanitize模块

    $
    0
    0

    一.ng-bind-html、ng-bind-html-unsafe

            AngularJS非常注重安全方面的问题,它会尽一切可能把大多数攻击手段最小化。其中一个攻击手段是向你的web页面里注入不安全的HTML,然后利用它触发跨站攻击或者注入攻击。

            考虑这样一个例子,假设我们有一个变量存在于myUnsafeHTMLContent作用域中。当HTML的内容如下时,OnMouseOver事件将会把元素中的内容修改成"PWN3D!"。

    $scope.myUnsafeHTMLContent = '<p style="color:blue">an html' + '<em onmouseover="this.textContent = 'PWN3D!'">click here</em>' + 'snippet</p>';

            如果你在变量中存了一些HTML内容,并尝试把这个变量进行数据绑定,这时AngularJS默认的行为是:先把HTML内容进行转义,然后再显示它。所以,HTML标签最终会被当成普通的文本来处理。

            所以对于下面这行内容:

    <div ng-bind='myUnsafeHTMLContent'></div>

            最终看到的结果将会是:

    <p style="color:blue">an html<em onmouseover="this.textContent='PWN3D!'">click here</em>
    snippet</p>

            标签在web页面上被渲染成了普通字符串。

            但是,如果在你的应用中,你想把myUnsafeHTMLContents中的内容作为HTML渲染应该怎么办呢?在这种情况下,AngularJS有另外一个指令(以及一个$sanitize服务用来启动指令),它让你能够以安全或者非安全的方式渲染HTML。

            我们先来看一个以安全模式渲染的例子(一般来说你应该用这种模式),使用安全模式可以避免HTML中的绝大多数攻击手段。这种情况下需要使用ng-bind-html指令。

            ng-bind-html、ng-bind-html-unsafe以及linky过滤器都属于ngSanitize模块。为了让以上内容能够运行,需要导入angular-sanitize.js(或者.min.js),同时还用导入一个对ngSanitize的模块依赖。

            那么,当我们在同样的myUnsafeHTMLContent作用域中使用ng-bind-html指令时会发生什么呢?例如对于下面这行HTML:

    <div ng-bind-html="myUnsafeHTMLContent"></div>

            在这种情况下,浏览器中将会输出这样的内容:

    an html _click here_ snippet

            有一个很重要的东西需要注意,style标签(蓝色)以及<em>标签上的onmouseover处理器都被AngulsrJS删除掉了,这是因为AngularJS认为它们是不安全的。

            最后,如果真的决定需要把myUnsafeHTMContent中的内容按照原样渲染出来(这可能是因为你的确信任内容的来源,或者其他一些原因),那么可以使用ng-bind-html-unsafe指令:

    <div ng-bind-html-unsafe="myUnsafeHTMLContent"></div>

            在这种情况下浏览器将会输出以下内容:

    an html _click here_ snippet

            文本的颜色是蓝色的(因为样式绑定到了p标签上),同时click here上面还注册了一个onmouseover事件。所以,当你的鼠标移动到click here这块文本附近的时候,浏览器中输出的内容将会变成:

    an html PWN3D! snippet

            如你所见,这种方式实际上很不安全,所以,如果你决定使用ng-bind-html-unsafe指令,那么你要完全确定它就是你要的东西。某些人可以利用这种方式轻松地读取用户信息然后发送到他自已的服务器上去。

     

    二.Linky

            Linky过滤器也属于ngSanitize模块,你可以在已经渲染好的HTML内容中添加这个过滤器,然后把HTML中存在的超链接转换成anchor标签。linky过滤器使用起来非常简单,我们来看一个例子:

    $scope.contents = 'Text with links:http://angularjs.org/ & mailto:us@there.org';

            如果使用以下绑定方式:

    <div ng-bind-html="contents"></div>

            HTML中的内容最终会被显示成:

    Text with links:http://angularjs.org/ & mailto:us@there.org

            现在我们来看看,当使用linky过滤器时到底会发生些什么:

    <div ng-bind-html="contents | linky"></div>

            linky过滤器将会遍历文本内容,然后在所有找到的URL和mailto链接上加<a>标签,这样就给用户提供了可交互的HTML内容:

    Text with links: http://angularjs.org/ & us@there.org

            这里的实际内容会被linky过滤器自动改成超链接的形式,即<a href="http://angularjs.org/">http://angularjs.org/</a> & <a href="us@there.org</a>。



    已有 0人发表留言,猛击->> 这里<<-参与讨论


    ITeye推荐



    java socket参数详解:BackLog

    $
    0
    0

     

     java socket参数详解:BackLog

     

    输入连接指示(对连接的请求)的最大队列长度被设置为 backlog 参数。如果队列满时收到连接指示,则拒绝该连接。
    注意:
    1. backlog参数必须是大于 0 的正值。如果传递的值等于或小于 0,则假定为默认值。
    2. 经过测试这个队列是按照 FIFO(先进先出)的原则。
    3. 如果将accept这个函数放在一个循环体中时,backlog参数也不会有什么作用。或者简单的讲运行ServerSocket的这个线程会阻塞时,无论是在accept,还是在read处阻塞,这个backlog参数才生效。
    建一个ServerSocket实例,绑定到端口10000,backlog设置为2

     

    package socket;
    
    import java.io.*;
    import java.net.*;
    import org.apache.log4j.Logger;
    
    public class Test_backlog {
    	private static Logger logger = Logger.getLogger(Test_backlog.class);
    
    	public static void main(String[] args) throws Exception {
    		BufferedReader in = null;
    		PrintWriter out = null;
    		int backlog = 2;
    
    		ServerSocket serversocket = new ServerSocket(10000, backlog);
    		while (true) {
    			logger.debug("启动服务端......");
    			int i;
    			Socket socket = serversocket.accept();
    			logger.debug("有客户端连上服务端, 客户端信息如下:" + socket.getInetAddress() + " : " + socket.getPort() + ".");
    			in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
    			out = new PrintWriter(socket.getOutputStream(), true);
    			do {
    				char[] c = new char[1024];
    				i = in.read(c);
    				logger.debug("服务端收到信息: " + new String(c, 0, i));
    			} while (i == -1);
    			out.close();
    			in.close();
    			socket.close();
    			logger.debug("关闭服务端......");
    		}
    	}
    }

     

     

    服务端日志:

    0    [main] DEBUG socket.Test_backlog - 启动服务端......
    3871 [main] DEBUG socket.Test_backlog - 有客户端连上服务端, 客户端信息如下:/127.0.0.1 : 4176.
    18888 [main] DEBUG socket.Test_backlog - 服务端收到信息: 发送完信息我就断掉。
    18888 [main] DEBUG socket.Test_backlog - 关闭服务端......
    18889 [main] DEBUG socket.Test_backlog - 启动服务端......
    18890 [main] DEBUG socket.Test_backlog - 有客户端连上服务端, 客户端信息如下:/127.0.0.1 : 4177.
    45316 [main] DEBUG socket.Test_backlog - 服务端收到信息: 我是第二个客户端,发完后我也断掉。
    45316 [main] DEBUG socket.Test_backlog - 关闭服务端......
    45316 [main] DEBUG socket.Test_backlog - 启动服务端......
    45317 [main] DEBUG socket.Test_backlog - 有客户端连上服务端, 客户端信息如下:/127.0.0.1 : 4178.
    52501 [main] DEBUG socket.Test_backlog - 服务端收到信息: 最后一个拉。
    52501 [main] DEBUG socket.Test_backlog - 关闭服务端......

     

    使用TCP工具连接这个服务端
    1. 当第一个客户端连接时,服务端打出如下信息:
    6629 [main] DEBUG socket.Test_backlog - 有客户端连上服务端, 客户端信息如下:/127.0.0.1 : 4110.
    2. 当第二个客户端连接时,服务端没有打出任何东东。因为这时客户端如果虽然显示成功了,但是被阻在连接队列中。
    3. 当第三个客户端连接时,情况与2相同。
    4. 当第四个客户端连接时,连接不上并报错。因为backlog参数设置成2,队列中只有阻塞两个。
    总结:
    管理客户连接请求的任务是由操作系统来完成的。操作系统把这些连接请求存储在一个先进先出的队列中。许多操作系统限定了队列的最大长度,一般为50。当队列中的连接请求达到了队列的最大容量时,服务器进程所在的主机会拒绝新的连接请求。只有当服务器进程通过ServerSocket的accept()方法从队列中取出连接请求,使队列腾出空位时,队列才能继续加入新的连接请求。
    对于客户进程,如果它发出的连接请求被加入到服务器的队列中,就意味着客户与服务器的连接建立成功,客户进程从Socket构造方法中正常返回。如果客户进程发出的连接请求被服务器拒绝,Socket构造方法就会抛出ConnectionException。

     

     

     

     

     



    已有 0人发表留言,猛击->> 这里<<-参与讨论


    ITeye推荐




    Quartz实现动态定时任务

    $
    0
    0

    转http://my.oschina.net/u/1177710/blog/284608

    一、 说明

         由于最近工作要实现定时任务的执行,而且要求定时周期是不固定的,所以就用到了quartz来实现这个功能;

         spring3.1以下的版本必须使用quartz1.x系列,3.1以上的版本才支持quartz 2.x,不然会出错。至于原因,则是spring对于quartz的支持实现,org.springframework.scheduling.quartz.CronTriggerBean继承了org.quartz.CronTrigger,在quartz1.x系列中org.quartz.CronTrigger是个类,而在quartz2.x系列中org.quartz.CronTrigger变成了接口,从而造成无法用spring的方式配置quartz的触发器(trigger)。

         我使用的quartz版本是2.2.1 。

         最终实现的功能:

          1) 项目启动时,可执行的定时任务启动,按时执行相应的逻辑 ;

         2)  可添加新任务,删除任务,更新任务,暂停任务,恢复任务 ;

    二、 添加quartz包

        我使用Gradle构建项目,加包时只需下面一行即可:

       compile "org.quartz-scheduler:quartz:2.2.1"

    三、 配置及使用

          1.  配置任务调度器 (对应的文件名为quartz-task.xml)  

    ?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
     <?xml  version="1.0"  encoding="UTF-8"?>
    <beans  xmlns="http://www.springframework.org/schema/beans"  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"
     default-lazy-init="false">
     <!-- 调度器 -->
        <bean  name="schedulerFactoryBean"  class="org.springframework.scheduling.quartz.SchedulerFactoryBean"
           <!-- 通过applicationContextSchedulerContextKey属性配置spring上下文 -->    
            <property  name="applicationContextSchedulerContextKey">    
                <value>applicationContext</value>    
            </property>   
        </bean>  
        <!--加载可执行的任务-->
        <bean  id="loadTask"  class="com.quartz.LoadTask"  init-method="initTask"  />
     
    </beans>

        2. 服务器启动时加载,在web.xml文件里配置

    ?
    1
    2
    3
    4
    <context-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:quartz-task.xml</param-value>
     </context-param>

       3. 加载可执行任务的类LoadTask.java

    ?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
     public  class  LoadTask {
     public  void  initTask() throws  Exception {
      Scheduler scheduler = schedulerFactoryBean.getScheduler();
      // 可执行的任务列表
      Collection<Task> taskList = taskService.findTask();
      for  (Task task : taskList) {
       // 任务名称和任务组设置规则:
       // 名称:task_1 ..
       // 组 :group_1 ..
       TriggerKey triggerKey = TriggerKey.triggerKey(
         "task_"  + task.getId(), "group_"  + task.getId());
       CronTrigger trigger = (CronTrigger) scheduler
         .getTrigger(triggerKey);
       // 不存在,创建一个
       if  (null  == trigger) {
        JobDetail jobDetail = JobBuilder
          .newJob(QuartzJobFactory.class)
          .withIdentity("task_"  + task.getId(),
            "group_"  + task.getId()).build();
        jobDetail.getJobDataMap().put("scheduleJob", task);
        // 表达式调度构建器
        CronScheduleBuilder scheduleBuilder = CronScheduleBuilder
          .cronSchedule(getCronExpression());
        // 按新的表达式构建一个新的trigger
        trigger = TriggerBuilder
          .newTrigger()
          .withIdentity("task_"  + task.getId(),
            "group_"  + task.getId())
          .withSchedule(scheduleBuilder).build();
        scheduler.scheduleJob(jobDetail, trigger);
       else  {
        // trigger已存在,则更新相应的定时设置
        CronScheduleBuilder scheduleBuilder = CronScheduleBuilder
          .cronSchedule(taskService.getCronExpression());
        // 按新的cronExpression表达式重新构建trigger
        trigger = trigger.getTriggerBuilder().withIdentity(triggerKey)
          .withSchedule(scheduleBuilder).build();
        // 按新的trigger重新设置job执行
        scheduler.rescheduleJob(triggerKey, trigger);
       }
      }
     }
     @Autowired
     private  SchedulerFactoryBean schedulerFactoryBean;
     @Autowired
     private   TaskService taskService;
    }

       4. 调度任务的入口

    ?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
     public  class  QuartzTaskFactory implements  Job {
      
     @Override
     public  void  execute(JobExecutionContext context)
       throws  JobExecutionException {
      // TODO Auto-generated method stub
      try  {
       System.out.println("任务运行...");
       Task task = (Task) context.getMergedJobDataMap().get(
         "scheduleJob");
       System.out.println("任务名称: ["  + task.getTaskName() + "]");
       //在这里执行你的任务...
       catch  (Exception e) {
       e.printStackTrace();
      }
     }
    }

       5. 暂停任务

    ?
    1
    2
    3
    Scheduler scheduler = schedulerFactoryBean.getScheduler();
    JobKey jobKey = JobKey.jobKey(scheduleJob.getJobName(), scheduleJob.getJobGroup());
    scheduler.pauseJob(jobKey);

       6. 恢复任务

    ?
    1
    2
    3
    Scheduler scheduler = schedulerFactoryBean.getScheduler();
    JobKey jobKey = JobKey.jobKey(scheduleJob.getJobName(), scheduleJob.getJobGroup());
    scheduler.resumeJob(jobKey);

      7. 删除任务

    ?
    1
    2
    3
    Scheduler scheduler = schedulerFactoryBean.getScheduler();
    JobKey jobKey = JobKey.jobKey(scheduleJob.getJobName(), scheduleJob.getJobGroup());
    scheduler.deleteJob(jobKey);

       8. 立即运行任务

    ?
    1
    2
    3
    Scheduler scheduler = schedulerFactoryBean.getScheduler();
    JobKey jobKey = JobKey.jobKey(scheduleJob.getJobName(), scheduleJob.getJobGroup());
    scheduler.triggerJob(jobKey);

       9. 更新任务(时间表达式)

    ?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    Scheduler scheduler = schedulerFactoryBean.getScheduler();
      
    TriggerKey triggerKey = TriggerKey.triggerKey(scheduleJob.getJobName(),
    scheduleJob.getJobGroup());
      
    //获取trigger,即在spring配置文件中定义的 bean id="myTrigger"
    CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
      
    //表达式调度构建器
    CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(scheduleJob
    .getCronExpression());
      
    //按新的cronExpression表达式重新构建trigger
    trigger = trigger.getTriggerBuilder().withIdentity(triggerKey)
    .withSchedule(scheduleBuilder).build();
      
    //按新的trigger重新设置job执行
    scheduler.rescheduleJob(triggerKey, trigger);

    四、时间表达式说明

      

    字段 允许值 允许的特殊字符

    秒 0-59 , – * /

    分 0-59 , – * /

    小时 0-23 , – * /

    日期 1-31 , – * ? / L W C

    月份 1-12 或者 JAN-DEC , – * /

    星期 1-7 或者 SUN-SAT , – * ? / L C #

    年(可选) 留空, 1970-2099 , – * /

    表达式意义

    "0 0 12 * * ?" 每天中午12点触发

    "0 15 10 ? * *" 每天上午10:15触发

    "0 15 10 * * ?" 每天上午10:15触发

    "0 15 10 * * ? *" 每天上午10:15触发

    "0 15 10 * * ? 2005" 2005年的每天上午10:15触发

    "0 * 14 * * ?" 在每天下午2点到下午2:59期间的每1分钟触发

    "0 0/5 14 * * ?" 在每天下午2点到下午2:55期间的每5分钟触发

    "0 0/5 14,18 * * ?" 在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发

    "0 0-5 14 * * ?" 在每天下午2点到下午2:05期间的每1分钟触发

    "0 10,44 14 ? 3 WED" 每年三月的星期三的下午2:10和2:44触发

    "0 15 10 ? * MON-FRI" 周一至周五的上午10:15触发

    "0 15 10 15 * ?" 每月15日上午10:15触发

    "0 15 10 L * ?" 每月最后一日的上午10:15触发

    "0 15 10 ? * 6L" 每月的最后一个星期五上午10:15触发

    "0 15 10 ? * 6L 2002-2005" 2002年至2005年的每月的最后一个星期五上午10:15触发

    "0 15 10 ? * 6#3" 每月的第三个星期五上午10:15触发

    每天早上6点

    0 6 * * *

    每两个小时

    0 */2 * * *

    晚上11点到早上8点之间每两个小时,早上八点

    0 23-7/2,8 * * *

    每个月的4号和每个礼拜的礼拜一到礼拜三的早上11点

    0 11 4 * 1-3

    1月1日早上4点

    0 4 1 1 *

     

    ok,定时任务已经正确执行....

    我是看了这篇文章,http://www.meiriyouke.net/?p=140 ,写的很好。



    已有 0人发表留言,猛击->> 这里<<-参与讨论


    ITeye推荐



    Java程序员在Google是如何工作的

    $
    0
    0
    在 Quora看到一个有意思的问题, Working at Google: What is it like to be a Java programmer at Google?,笔者对原文做了简单的编译,虽不能至,心向往之。以下是正文:
    1. 严格遵守“Google Style”,这份“Google Style”会有详细的文档清楚地告诉你如何编码(一开始你会觉得很痛苦,但是以后你会享受它带来的好处)
    2. 非常严格的代码审查(同样一开始会觉得很烦人,但以后你会感激它的),它能确保代码稳定的运行。(我从其他公司小伙伴那听闻Google对代码要求让人汗颜)
    3. 使用内部定制化的 Eclipse IDE(真正的勇士在用VIM)
    4. 自定义一切(构建系统、测试系统、java、版本控制、问题追踪系统、OS、等等),很多外面的东西放在Google的规模基本不能用
    5. Google Collection(你不需要“new ArrayList()”,取而代之的是“Lists.newArrayList()”或者类似的操作)
    6. 巨大的Java文件(我曾经见过一个.java文件超过9万行),通常这是自动生成的代码
    7. 忘记标准的序列化相关的东西,欢迎使用protocol buffers,以Google的量级来说这很重要
    8. 有各种机会来提升你的技能(技术讲座、代码实验室、专题讨论等等)
    9. 大量的会议
    10. 可升降的桌子
    11. 两个大的显示器
    12. 每周五的免费啤酒
    13. 附近的小厨房能填充肚子

    14. Slides 和 firepoles (就是那个弯弯的东东)能直达自助餐厅,因此你可以吃的更胖

     15.  厕所墙壁上的”编程纸“能使你的“方便”之旅更具有“码农范儿”

    笔者整理了文章中可能出现过的资源(如不能打开,请自备梯子):

    相关文章

    spring与mybatis四种整合方法

    $
    0
    0


      1、采用数据映射器(MapperFactoryBean)的方式,不用写mybatis映射文件,采用注解方式提供相应的sql语句和输入参数。
      (1)Spring配置文件:

         <!-- 引入jdbc配置文件 -->
         <context:property-placeholder location="jdbc.properties"/>

          <!--创建jdbc数据源 -->
          <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
            <property name="driverClassName" value="${driver}"/>
            <property name="url" value="${url}"/>
            <property name="username" value="${username}"/>
            <property name="password" value="${password}"/>
            <property name="initialSize" value="${initialSize}"/>
            <property name="maxActive" value="${maxActive}"/>
            <property name="maxIdle" value="${maxIdle}"/>
            <property name="minIdle" value="${minIdle}"/>
          </bean>

          <!-- 创建SqlSessionFactory,同时指定数据源-->
          <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
          <property name="dataSource" ref="dataSource" />
          </bean>

          <!--创建数据映射器,数据映射器必须为接口-->
          <bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
          <property name="mapperInterface" value="com.xxt.ibatis.dbcp.dao.UserMapper" />
          <property name="sqlSessionFactory" ref="sqlSessionFactory" />
          </bean>

          <bean id="userDaoImpl2" class="com.xxt.ibatis.dbcp.dao.impl.UserDaoImpl2">
          <property name="userMapper" ref="userMapper"/>
     </bean>

     
    (2)数据映射器UserMapper,代码如下:
      public interface UserMapper {
            @Select("SELECT * FROM user WHERE id = #{userId}")
            User getUser(@Param("userId") long id);
      }
     (3) dao接口类UserDao,代码如下:
       public interface UserDao {
           public User getUserById(User user);
       }
    (4)dao实现类UserDaoImpl2,,代码如下:

      public class UserDaoImpl2 implements UserDao {
           private UserMapper userMapper;

           public void setUserMapper(UserMapper userMapper) {
               this.userMapper = userMapper;
           } 

           public User getUserById(User user) {
              return userMapper.getUser(user.getId());
           }
       }

     

     2、采用接口org.apache.ibatis.session.SqlSession的实现类org.mybatis.spring.SqlSessionTemplate
        mybatis中, sessionFactory可由SqlSessionFactoryBuilder.来创建。MyBatis- Spring 中,使用了SqlSessionFactoryBean来替代。SqlSessionFactoryBean有一个必须属性 dataSource,另外其还有一个通用属性configLocation(用来指定mybatis的xml配置文件路径)。
       (1)Spring配置文件:
        <!-- 创建SqlSessionFactory,同时指定数据源-->
       <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
          <property name="dataSource" ref="dataSource" />
          <!-- 指定sqlMapConfig总配置文件,订制的environment在spring容器中不在生效-->
          <property  name="configLocation"  value="classpath:sqlMapConfig.xml"/>
          <!--指定实体类映射文件,可以指定同时指定某一包以及子包下面的所有配置文件,mapperLocations和configLocation 有一个即可,当需要为实体类指定别名时,可指定configLocation属性,再在mybatis总配置文件中采用mapper引入实体类映射文件 -->
          <!- - <property  name="mapperLocations"  value="classpath*:com/xxt/ibatis/dbcp/**/*.xml"/>  -->
       </bean>
    <bean id="sqlSession"     class="org.mybatis.spring.SqlSessionTemplate">
          <constructor-arg index="0" ref="sqlSessionFactory" />
    </bean>

    <bean id="UserDaoImpl " class="com.xxt.ibatis.dbcp.dao.impl.UserDaoImpl">
       <!--注入SqlSessionTemplate实例 -->
       <property name="sqlSessionTemplate" ref="sqlSession" />
    -->
    </bean>

        (2)mybatis总配置文件sqlMapConfig.xml:
      <configuration>
       <typeAliases>
         <typeAlias type="com.xxt.ibatis.dbcp.domain.User" alias="User" />
      </typeAliases>
       <mappers>
          <mapper resource="com/xxt/ibatis/dbcp/domain/user.map.xml" />
         </mappers>
     </configuration>
    (3)实体类映射文件user.map.xml:
    <mapper namespace="com.xxt.ibatis.dbcp.domain.User">
         <resultMap type="User" id="userMap">
            <id property="id" column="id" />
            <result property="name" column="name" />
            <result property="password" column="password" />
            <result property="createTime" column="createtime" />
         </resultMap>
         <select id="getUser" parameterType="User" resultMap="userMap">
           select * from user where id = #{id}
         </select>
    <mapper/>
    (4)dao层接口实现类UserDaoImpl:
      public class UserDaoImpl implements  UserDao  {
         public SqlSessionTemplate sqlSession;
         public User getUserById(User user) {
             return (User)sqlSession.selectOne("com.xxt.ibatis.dbcp.domain.User.getUser", user);
         }
         public void setSqlSession(SqlSessionTemplate sqlSession) {
              this.sqlSession = sqlSession;
         }
       }
     3、采用抽象类org.mybatis.spring.support.SqlSessionDaoSupport提供SqlSession。
       (1)spring配置文件:

    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
       <property name="dataSource" ref="dataSource" />
       <property  name="configLocation"  value="classpath:sqlMapConfig.xml"/>
       <!-- <property  name="mapperLocations"  value="classpath*:com/xxt/ibatis/dbcp/domain/user.map.xml"/   >  -->
    </bean>

     <bean id="sqlSession"     class="org.mybatis.spring.SqlSessionTemplate">
          <constructor-arg index="0" ref="sqlSessionFactory" />
    </bean>

    <bean id="userDaoImpl3" class="com.xxt.ibatis.dbcp.dao.impl.UserDaoImpl3">
       <!--注入SqlSessionTemplate实例 -->
       <property name="sqlSessionTemplate" ref="sqlSession" />
       <!--也可直接注入SqlSessionFactory实例,二者都指定时,SqlSessionFactory失效 -->
       <!-- <property name="sqlSessionFactory" ref="sqlSessionFactory" />
    -->
    </bean>

     (2) dao层接口实现类UserDaoImpl3:
    public class UserDaoImpl3 extends SqlSessionDaoSupport implements UserDao {  
      public User getUserById(User user) {  
         return (User) getSqlSession().selectOne("com.xxt.ibatis.dbcp.domain.User.getUser", user);  
      }  
    }
     3、采用org.mybatis.spring.mapper.MapperFactoryBean或者org.mybatis.spring.mapper.MapperScannerConfigurer
    <beans:bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
            <beans:property name="dataSource" ref="simpleDataSource" />
            <beans:property name="configLocation"
                value="classpath:conf/core/mybatis-config.xml" />
        </beans:bean>

        <beans:bean id="sqlSessionFactory_contact" class="org.mybatis.spring.SqlSessionFactoryBean">
            <beans:property name="dataSource" ref="simpleDataSource_contact" />
            <beans:property name="configLocation"
                value="classpath:conf/core/mybatis-config-contact.xml" />
        </beans:bean>

        <beans:bean id="transactionManager"
            class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <beans:property name="dataSource" ref="simpleDataSource" />
        </beans:bean>

        <beans:bean id="transactionManager_contact"
            class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <beans:property name="dataSource" ref="simpleDataSource_contact" />
        </beans:bean>
       

        <!--  <tx:annotation-driven transaction-manager="transactionManager" />
        <tx:annotation-driven transaction-manager="transactionManager_contact" />
        -->   
    <bean id="userDao" class="org.mybatis.spring.mapper.MapperFactoryBean">
            <property name="mapperInterface" value="com.mybatis.UserDao"></property>
            <property name="sqlSessionFactory" ref="sqlSessionFactory"></property>
        </bean>

        <bean id="userService" class="com.mybatis.UserServiceImpl">
            <property name="userDao" ref="userDao"></property>
        </bean>

        <!-- 加载配置文件 -->
        <beans:bean name="mapperScannerConfigurer"
            class="org.mybatis.spring.mapper.MapperScannerConfigurer">
            <beans:property name="basePackage" value="com.elong.hotel.crm.data.mapper" />
            <beans:property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></beans:property>
        </beans:bean>
        <beans:bean name="mapperScannerConfigurer_contact"
            class="org.mybatis.spring.mapper.MapperScannerConfigurer">
            <beans:property name="basePackage"
                value="com.elong.hotel.crm.data.contact.mapper" />
            <beans:property name="sqlSessionFactoryBeanName" value="sqlSessionFactory_contact"></beans:property>
        </beans:bean>

        <tx:advice id="txAdvice" transaction-manager="transactionManager">
            <tx:attributes>
                <!-- name表示以什么开始的方法名,比如 add*表示add开头的方法 propagation表示事务传播属性,不写默认有 -->
                <tx:method name="save*" propagation="REQUIRED" />
                <tx:method name="insert*" propagation="REQUIRED" />
                <tx:method name="add*" propagation="REQUIRED" />
                <tx:method name="del*" />
                <tx:method name="update*" />
                <tx:method name="find*" read-only="true" />
                <tx:method name="get*" read-only="true" />
                <tx:method name="search*" read-only="true" />
            </tx:attributes>
        </tx:advice>
       
        <tx:advice id="txAdvice_contact" transaction-manager="transactionManager_contact">
            <tx:attributes>
                <!-- name表示以什么开始的方法名,比如 add*表示add开头的方法 propagation表示事务传播属性,不写默认有 -->
                <tx:method name="save*" propagation="REQUIRED" />
                <tx:method name="insert*" propagation="REQUIRED" />
                <tx:method name="add*" propagation="REQUIRED" />
                <tx:method name="del*" />
                <tx:method name="update*" />
                <tx:method name="find*" read-only="true" />
                <tx:method name="get*" read-only="true" />
                <tx:method name="search*" read-only="true" />
            </tx:attributes>
        </tx:advice>
        <!-- 配置事务切面 -->
        <aop:config>
            <aop:pointcut expression="execution(* com.elong.hotel.crm.service..*.*(..))"
                id="pointcut" />
            <aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut" />
            <aop:advisor advice-ref="txAdvice_contact" pointcut-ref="pointcut" />
        </aop:config>


    已有 0人发表留言,猛击->> 这里<<-参与讨论


    ITeye推荐



    手机小屏幕界面设计原则

    $
    0
    0

    1997年,那是一个春天,有一位老人在南海边画了一个圈,这个圈就画在咱大深圳!画圈的人是小平同志。

    2014年,还是这个春天,移动设备的普及成为当下互联网发展的又一次变革,我们在各种小屏幕上画着一个又一个的圈,如今,用户的移动体验无意正在变得更好,因为越来越多的网站已经开始为移动设备做了设计,设计师们也明白什么更适合小屏幕。然而,在手机上执行任务操作仍然存在着许多障碍。

    亲爱的设基师朋友们如果你正在为此困扰,请关注如下招式:

    首先即使是4G下载时间问题依然存在,其次用户讨厌需要大量下载网站因为他们需要为此付费,再次即使是经验丰富的移动用户他们在通过移动端访问完整版网站时和普通人没什么区别。这意味着用户在整个移动体验中依然有着很高的失败存在。

    如果从设备看,目前市场主要移动设备可分为:功能型手机,智能手机,全屏手机。

    (备注:功能型手机:是基本功能都具有,不具有功能扩展或少有扩展的手机,其最大的区别是智能手机。功能手机一般待机时间会长点,较稳定。

    智能型手机:是指像个人电脑一样,具有独立的操作系统,独立的运行空间,可以由用户自行安装软件、游戏、导航等第三方服务商提供的程序,并可以通过移动通讯网络来实现无线网络接入的这样一类手机的总称。

    全屏手机:也可以称为触屏手机,最大的特点在于它那超大的屏幕,利用触摸屏的技术,将该技术应用到手机屏幕上面的一种智能型手机。是时下最热门的手机类。)

    为了实现更高的用户性能,应该为每一种移动设备类型设计不同的体验:屏幕越小,功能就越少,设计也应该越简洁。

    如何为小屏做设计?

    为移动设备设计时,主要注意两点:

    1.内容和导航是否足够明显,用户不用花费学习成本。

    2.为小屏幕和缓慢的下载速度做设计。

    这两点诠释了为什么所有移动端网站都必须根据目标网站的特点作出。

    寸土寸金的空间——移动屏幕是这么的小,你这么浪费空间你妈妈知道吗?

    图二的显示方式可以呈现更多的新闻,这才是人们的实际使用。图一相比更增加了用户一步操作,虽然好看但华而不实。 为什么用户在日常使用中更偏爱普通且有点无趣的设计? 这是因为这样的设计能让用户一眼看到更多内容。

    第二个案例则是成功浪费ipad的屏幕空间,它让大多数的天气信息密集的排布在底部,而将大部分屏幕的空间用一张纯粹的图片做装饰,这样的默认设计让人乏味。

    一个内容两种呈现多些选择

    同样的数据如果有着两种不同的呈现显得有点多余,通常是设计者并不清楚哪个更适合用户,所以给出两个方案,让用户自己去选择。为数据使用两种界面,唯一合理的情况是这两种数据需要强调本身具有不同的两个方向。

    美团的界面有列表视图和地图视图,两种界面能够让用户查看不同的方向,列表包括详细的信息内容和价格,而地图则显示了餐厅的地理位置。

    在ibook中也同样的表现了两种显示,但你会发现一种界面明显优于另一种。事实表明,列表界面能让用户更加轻松的辨认出图书和作者,或用书名做快速搜索,列表中按照字母快速排序而书架中的并不是。大量图书存在情况下,通过图书封面快速查找可能性也随之降底。

    别让虚拟元素抢了地盘

    所谓虚拟设计元素是指像用户提供关于屏幕内容的信息或者操作该内容的命令。这些虚拟元素通常用底层的系统提供。例如PC端他的虚拟元素就是PC的各种指令如任务栏,系统托盘区等。而软件PS工具中的菜单栏,工具栏等等。

    在移动端使用这些虚拟元素的代价很明显,他们会占据空间,而压缩了内容的区域。

    图一中你会发现这些虚拟的元素,让有效内容显得更少,然而也会随着系统自带的一些元素不断叠加,经过堆积的虚拟元素通常会占据一半的屏幕空间。那如果我们将这些元素暂时隐藏其实也是很危险的,因为用户对看不见的部分通常是忽略的。那该如何是好?!!!

    其实只要你善待他们他们自然会用更好的姿态回报你:

    1. 考虑将这些虚拟元素设计的更简单有效

    2. 提供自始至终的一致性,让用户记住你这些虚拟操作正在处于隐藏状态,而他的操作可以不断重复这样的模式。3. 通过内容的错落差,上下文提示和渐进式暴露方法降低隐藏带来的复杂性。

    Path从早期即将菜单做隐藏处理,长期的使用培养了用户对这个虚拟元素的认知。并且在使用过程中你需要重复这样的操作。

    阅读器默认状态工具隐藏,用户能通过文字错落了解翻页,点击屏幕呼出工具。

    时间都去哪儿——下载时间的秘密

    试想如果你已经把空间都利用的很好了,但等待时间却很长,用户可不会原谅你。因此,在为移动设备设计的时候,将交互流程成本降到最低是关键。

    1. 让操作流线话,所需要下载的界面越少越好。举个例子,如果内容都已经存储在手机中,这样操作并不需要从服务器下载信息。

    2. 只加载需要的信息,即那些对用户有用的价值信息。

    3. 不要滥用图片。图片越多,下载速度也越慢。(ps:也许这也是为什么在移动端扁平化盛行的原因之一)

    4. 给用户提供反馈。比如你的下载进行到什么样的一个状态,需要让用户有一个认知,这样虽然不能完全解决他们中断操作,但可以稍微缓解一下。

    解决了如上问题是不是就能留住用户,提高成功率?非也~~

    请看最后一招!

    必须避免过早注册及高门槛

    移动应用的最重要的原则之一就是避免让用户在第一步做注册。因为初期用户对这样的产品并未建立认知和好感,在初期就要求用户注册是错误的。需要记住的是,用户在一开始对你的应用投入程度是很低的。除非你真的太牛逼,太受欢迎了,值得他们去注册。

    下面我们一起来看一下几个案例:

    图一中在开始第一步即要求用户填写信息,造成很大流失率,因为初期用户并未对你的信息有所认知,你凭什么要求别人填写。图二强迫用户注册,你长这样凭什么要我注册?!许多人会就此退出应用,而且从此碰都不碰。这样你便失去了给别人带来良好的第一印象。

    上图所示的案例即为合理的体验:

    1. 展示用户需要的信息

    2. 让用户制定自己的订单

    3. 显示价格信息,以及相应订单时间。

    4. 接受订单,此时要求填写注册等相关信息,恰到好处。

    在移动端的设计中如果能对以上几个观点有所考虑,相信你的移动端设计会是变的更有诚意,其实用户更多的时候只是希望看到你为他们量身定制的服务。这就好比当年小平同志将深圳划为改革开放经济特区,享有经济特权,如此的有诚意的一个圈造就了今天的深圳。

    感情深不深 酒水一口闷

    诚意足不足 为你做衣服

    haproxy负载均衡 xtracluster

    $
    0
    0
    上一篇为xtracluster的安装(http://xmarker.blog.163.com/blog/static/226484057201472610520306/),本篇将记录下使用haproxy在三个数据节点做负载均衡,haproxy可以安装在数据节点,也可以安装在别的节点,如果安装在数据节点,需要注意端口号不能和mysql的3306冲突


    1.haproxy安装(仅一个节点即可

    下载地址:http://www.haproxy.org/,选择最新稳定版。这里我选择数据节点ddb169作为haproxy的负载均衡服务器

    [root@db169 soft]# tar -zxvf haproxy-1.5.3.tar.gz

    [root@db169 soft]# cd haproxy-1.5.3

    [root@db169 haproxy-1.5.3]# make TARGET=linux2628

    [root@db169 haproxy-1.5.3]# make install

    [root@db169 haproxy-1.5.3]# mkdir /etc/haproxy;cp examples/haproxy.cfg /etc/haproxy/

    [root@db169 haproxy-1.5.3]# groupadd -g 1001 haproxy

    [root@db169 haproxy-1.5.3]# useradd -g haproxy haproxy


    2.配置haproxy(仅一个节点即可)

    配置haproxy.cnf文件

    [root@db169 haproxy-1.5.3]# mkdir /etc/haproxy/

    [root@db169 haproxy-1.5.3]# cat /etc/haproxy/haproxy.cfg

    # this config needs haproxy-1.1.28 or haproxy-1.2.1


    global

    log 127.0.0.1 local0

    log 127.0.0.1 local1 notice

    #log loghost local0 info

    maxconn 4096

    chroot /usr/share/haproxy

    uid haproxy

    gid haproxy

    daemon

    #debug

    #quiet

    pidfile  /var/run/haproxy.pid


    defaults

    log global

    mode http

    #option httplog

    option dontlognull

    retries 3

    redispatch

    maxconn 2000

    contimeout 5000

    clitimeout 50000

    srvtimeout 50000


    frontend pxc-front  #描述允许客户端连接的监听套接字

            bind    *:3307

    mode    tcp

    default_backend pxc-back #当没有匹配use_backend时,默认的backend

    frontend stats-front

      bind    *:8099

    mode    http

    default_backend stats-back

    backend pxc-back #描述进来的连接将转发到哪些后端服务器

    mode    tcp

    balance leastconn    #负载均衡算法,使用最少连接算法,适合长连接应用

    option httpchk #启用HTTP协议检查服务器监控状态,通过调用脚本检查节点的状态

    server db169 192.168.1.169:3306 check port 9200 inter 12000 rise 3 fall 3 #fall连续3次检查错误后,将表明服务器死亡,默认为3;inter连续两次检查的间隔时间值,单位为毫秒默认为2s;rise连续3次检查成功,表明服务可用

    server db172 192.168.1.172:3306 check port 9200 inter 12000 rise 3 fall 3

    server db173 192.168.1.173:3306 check port 9200 inter 12000 rise 3 fall 3


    backend stats-back  #开启haproxy的状态页面

    mode http

    balance roundrobin

    stats   uri /haproxy/stats #定义访问统计信息的URI

    stats   auth    admin:admin #设置查看统计信息的用户名和密码


    3.安装检测脚本(每个节点都需要)

    [root@db169 haproxy-1.5.3]# cp /opt/pxc/bin/clustercheck /usr/bin/

    [root@db169 haproxy-1.5.3]# cp /opt/pxc/xinetd.d/mysqlchk /etc/xinetd.d/

    [root@db169 haproxy-1.5.3]# echo 'mysqlchk 9200/tcp # mysqlchk' >> /etc/services

    安装xinetd服务

    [root@db169 haproxy-1.5.3]# yum -y install xinetd

    [root@db169 haproxy-1.5.3]# /etc/init.d/xinetd restart

    停止 xinetd:                                              [失败]

    正在启动 xinetd:                                          [确定]

    [root@db169 haproxy-1.5.3]# chkconfig --level 2345 xinetd on


    创建检查脚本的用户(在任一节点即可):

    grant process on *.* to 'clustercheckuser'@'localhost' identified by 'clustercheckpassword!';

    测试检测脚本

    [root@db169 haproxy-1.5.3]# clustercheck

    HTTP/1.1 200 OK

    Content-Type: text/plain

    Connection: close

    Content-Length: 40


    [root@db169 haproxy-1.5.3]# curl -I 127.0.0.1:9200

    HTTP/1.1 200 OK

    Content-Type: text/plain

    Connection: close

    Content-Length: 40


    要保证状态为200,否则检测不通过,可能是mysql服务不正常,或者环境不对致使haproxy无法使用mysql,

    如果不为200,可以手工修改下检测脚本,时期输出到日志,然后排错,比如我的检测就有个错误,一直报503:

    [root@db169 examples]# curl -I 192.168.1.172:9200

    HTTP/1.1 503 Service Unavailable

    Content-Type: text/plain

    Connection: close

    Content-Length: 44


    我修改db172后的检测脚本如下:

    [root@db172 data]# cat /usr/bin/clustercheck 

    #!/bin/bash 

    #

    # Script to make a proxy (ie HAProxy) capable of monitoring Percona XtraDB Cluster nodes properly

    #

    # Authors:

    # Raghavendra Prabhu <raghavendra.prabhu@percona.com>

    # Olaf van Zandwijk <olaf.vanzandwijk@nedap.com>

    #

    # Based on the original script from Unai Rodriguez and Olaf (https://github.com/olafz/percona-clustercheck)

    #

    # Grant privileges required:

    # GRANT PROCESS ON *.* TO 'clustercheckuser'@'localhost' IDENTIFIED BY 'clustercheckpassword!';

    if [[ $1 == '-h' || $1 == '--help' ]];then

        echo "Usage: $0 <user> <pass> <available_when_donor=0|1> <log_file> <available_when_readonly=0|1> <defaults_extra_file>"

        exit

    fi


    MYSQL_USERNAME="${1-clustercheckuser}" 

    MYSQL_PASSWORD="${2-clustercheckpassword!}" 

    AVAILABLE_WHEN_DONOR=${3:-0}

    ERR_FILE="${4:-/tmp/mcl2.log}" 

    AVAILABLE_WHEN_READONLY=${5:-1}

    DEFAULTS_EXTRA_FILE=${6:-/etc/my.cnf}

    #Timeout exists for instances where mysqld may be hung

    TIMEOUT=10

    echo "MYSQL_USERNAME : $MYSQL_USERNAME" >>/tmp/mcl.log

    echo "MYSQL_PASSWORD : $MYSQL_PASSWORD" >>/tmp/mcl.log

    echo "AVAILABLE_WHEN_DONOR : $AVAILABLE_WHEN_DONOR" >>/tmp/mcl.log

    echo "ERR_FILE : $ERR_FILE" >>/tmp/mcl.log

    echo "AVAILABLE_WHEN_READONLY : $AVAILABLE_WHEN_READONLY" >>/tmp/mcl.log

    echo "DEFAULTS_EXTRA_FILE : $DEFAULTS_EXTRA_FILE" >>/tmp/mcl.log

    然后再测试:

    [root@db172 data]# more /tmp/mcl.log

    /usr/bin/clustercheck

    /usr/bin/clustercheck

    MYSQL_USERNAME : clustercheckuser

    MYSQL_PASSWORD : clustercheckpassword!

    AVAILABLE_WHEN_DONOR : 0

    ERR_FILE : /dev/null

    AVAILABLE_WHEN_READONLY : 1

    DEFAULTS_EXTRA_FILE : /etc/my.cnf

    [root@db172 data]# more /tmp/mcl2.log

    /usr/bin/clustercheck: line 44: mysql: command not found

    看到问题了,原来是找不到mysql这个可执行文件

    [root@db172 data]# which mysql

    /opt/pxc/bin/mysql

    明明是有的,但这个脚本可能找不到,所以把它复制到/usr/bin下面,在测试就通过了


    4.启动haproxy:

    [root@db169 ~]# haproxy -f /etc/haproxy/haproxy.cfg

    [WARNING] 236/190345 (21350) : parsing [/etc/haproxy/haproxy.cfg:22]: keyword 'redispatch' is deprecated in favor of 'option redispatch', and will not be supported by future versions.

    [WARNING] 236/190345 (21350) : parsing [/etc/haproxy/haproxy.cfg:24] : the 'contimeout' directive is now deprecated in favor of 'timeout connect', and will not be supported in future versions.

    [WARNING] 236/190345 (21350) : parsing [/etc/haproxy/haproxy.cfg:25] : the 'clitimeout' directive is now deprecated in favor of 'timeout client', and will not be supported in future versions.

    [WARNING] 236/190345 (21350) : parsing [/etc/haproxy/haproxy.cfg:26] : the 'srvtimeout' directive is now deprecated in favor of 'timeout server', and will not be supported in future versions.

    [ALERT] 236/190345 (21350) : [haproxy.main()] Cannot chroot(/usr/share/haproxy).

    报错了,解决方法:

    [root@db169 ~]# mkdir /usr/share/haproxy

    再次启动:

    [root@db169 ~]# haproxy -f /etc/haproxy/haproxy.cfg

    [WARNING] 236/190744 (21400) : parsing [/etc/haproxy/haproxy.cfg:22]: keyword 'redispatch' is deprecated in favor of 'option redispatch', and will not be supported by future versions.

    [WARNING] 236/190744 (21400) : parsing [/etc/haproxy/haproxy.cfg:24] : the 'contimeout' directive is now deprecated in favor of 'timeout connect', and will not be supported in future versions.

    [WARNING] 236/190744 (21400) : parsing [/etc/haproxy/haproxy.cfg:25] : the 'clitimeout' directive is now deprecated in favor of 'timeout client', and will not be supported in future versions.

    [WARNING] 236/190744 (21400) : parsing [/etc/haproxy/haproxy.cfg:26] : the 'srvtimeout' directive is now deprecated in favor of 'timeout server', and will not be supported in future versions.

    [root@db169 ~]# netstat -anp|grep 3307

    tcp        0      0 0.0.0.0:3307                0.0.0.0:*                   LISTEN      21401/haproxy

    已经在3307端口监听了


    5.添加开机自启动脚本:

    [root@db169 examples]# cp /usr/local/sbin/haproxy /usr/sbin/haproxy
    cd /opt/soft/haproxy-1.5.3/examples
    [root@db169 examples]# cp haproxy.init /etc/init.d/haproxy
    [root@db169 examples]# chmod +x /etc/init.d/haproxy 

    6.测试

    [root@db169 ~]# mysql -p123456 -P3307 -h 192.168.1.169

    Welcome to the MySQL monitor.  Commands end with ; or \g.

    Your MySQL connection id is 47

    Server version: 5.6.19-67.0-25.6-log Percona XtraDB Cluster binary (GPL) 5.6.19-25.6, Revision 824, wsrep_25.6.r4111


    Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.


    Oracle is a registered trademark of Oracle Corporation and/or its

    affiliates. Other names may be trademarks of their respective

    owners.


    Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.


    mysql>

    关闭任意一个节点不影响haproxy的连接。


    6.haproxy web端:

    http://192.168.1.169:8099/haproxy/stats

    输入用户名密码分别为配置文件里的配置即可登录查看负载等情况。

    Spring AOP监控SQL执行

    $
    0
    0

             对数据库连接池Proxool比较熟悉的读者,都知道Proxool可以记录SQL执行内容和时间等信息日志。我们可以将该日志记录专门的SQL日志文件,对于查找执行特别耗时的SQL起了不小的作用。对于一些其他连接池,没有该特性时,本文介绍Spring AOP切面方法来记录SQL日志。

    当然也可以通过数据库提供的特性来查询执行效率较低的SQL,本文不做探讨。

     

             本文介绍使用SpringJdbcTemplate执行SQL,使用其他方法或者ORM思路类似(Hibernate提供了日志记录功能)。

     

             使用AOP,可以使用Around通知,在JdbcTemplate执行方法前,记录当前时间,在方法执行完后,计算SQL耗时,并记录日志。思路很简单,不过多介绍,代码如下。


    package org.enyes.sql.util;
    import org.apache.log4j.Logger;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    
    /**
     * 对Spring JdbcTemplate进行切面,记录每个sql执行时间。
     */
    @Aspect
    public class SqlExecutionTimeAspect {
    	/**
    	 * logger
    	 */
    	private static final Logger LOG = Logger
    			.getLogger(SqlExecutionTimeAspect.class);
    
    	/**
    	 * 当Sql执行时间超过该值时,则进行log warn级别题型,否则记录INFO日志。
    	 */
    	private long warnWhenOverTime = 2 * 60 * 1000L;
    
    	@Around("execution(* org.springframework.jdbc.core.JdbcTemplate.*(..))")
    	public Object logSqlExecutionTime(ProceedingJoinPoint joinPoint)
    			throws Throwable {
    		long startTime = System.currentTimeMillis();
    		Object result = joinPoint.proceed();
    		long costTime = System.currentTimeMillis() - startTime;
    		if (costTime > warnWhenOverTime) {
    			StringBuilder sb = new StringBuilder();
    			sb.append("execute method :").append(joinPoint.getSignature());
    			sb.append("args: ").append(arrayToString(joinPoint.getArgs()));
    			sb.append(" cost time[").append(costTime).append("]ms");
    			LOG.warn(sb);
    		} else if (LOG.isInfoEnabled()) {
    			StringBuilder sb = new StringBuilder();
    			sb.append("execute method :").append(joinPoint.getSignature());
    			sb.append("args: ").append(arrayToString(joinPoint.getArgs()));
    			sb.append(" cost time[").append(costTime).append("]ms");
    			LOG.info(sb);
    		}
    		return result;
    	}
    
    	private static String arrayToString(Object[] a) {
    		if (a == null)
    			return "null";
    
    		int iMax = a.length - 1;
    		if (iMax == -1)
    			return "[]";
    
    		StringBuilder b = new StringBuilder();
    		b.append('[');
    		for (int i = 0;; i++) {
    			if (a[i] instanceof Object[]) {
    				b.append(arrayToString((Object[]) a[i]));
    			} else {
    				b.append(String.valueOf(a[i]));
    			}
    			if (i == iMax)
    				return b.append(']').toString();
    			b.append(", ");
    		}
    	}
    }

    Springxml配置如下:

    <?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"
    	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
    	xmlns:context="http://www.springframework.org/schema/context"
    	xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:tx="http://www.springframework.org/schema/tx"
    	xmlns:aop="http://www.springframework.org/schema/aop"
    	xmlns:task="http://www.springframework.org/schema/task" 
    	xsi:schemaLocation="  
        http://www.springframework.org/schema/beans   
        http://www.springframework.org/schema/beans/spring-beans-3.1.xsd  
        http://www.springframework.org/schema/context  
        http://www.springframework.org/schema/context/spring-context-3.1.xsd  
        http://www.springframework.org/schema/mvc  
        http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd 
         http://www.springframework.org/schema/tx 
        http://www.springframework.org/schema/tx/spring-tx-3.1.xsd 
         http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-3.1.xsd  
        http://www.springframework.org/schema/task 
        http://www.springframework.org/schema/task/spring-task-3.1.xsd "><beans><aop:aspectj-autoproxy proxy-target-class="true"/><bean class="org.enyes.sql.util.SqlExecutionTimeAspect"/></beans>

    可以使用Log4J将SqlExecutionTimeAspect类的日志打印到专门的日志中,并且warnWhenOverTime提供setter方法,可以通过Spring xml来具体配置。

    完毕。






    作者:fangchao2061 发表于2014-8-26 16:20:46 原文链接
    阅读:65 评论:1 查看评论

    为什么Android手机总是越用越慢?

    $
    0
    0

    根据第三方的调研数据显示,有 77% 的 Android 手机用户承认自己曾遭遇过手机变慢的影响,百度搜索“Android+ 卡慢”,也有超过 460 万条结果。在业内,Android 手机一直有着“越用越慢”的口碑,这个现象甚至超出了硬件范畴——很多中高端 Android 手机在硬件参数上都优于同一代 iPhone,但是它们仍然会在使用半年到一年的时间后进入“欠流畅”的状态——这无疑是一件令人困扰的事情。

    然而,若是要回答这个问题,我们需要追溯到上个世纪,去寻找智能手机的起源。

    西方历史及奇幻文学作品十分热衷于表达“血统”的设定,其传统文化认为血统可以决定天赋,并引出“命运是否被注定”的哲学思考。比如大家比较熟知的《哈利波特》系列,解构之后就不难发现,这实际上是一部讲述格兰芬多与斯莱特林两支血统及其传人的厮杀史(哈利波特是格兰芬多的后代,继承了其勇气,伏地魔是斯莱特林的后代,拥有着其野心),而无处不在的预言(一个终将杀死另一个),也贯彻了西方惯有的宿命论情结。

    到了科技行业,“血统”的定义被“基因”所取代,一个公司有着什么样的基因,决定了它的擅长领域,这种评价也被广泛接受,成为唯物时代独树一帜的唯心理念,并经受住了事实考验——当我们试图解释微软失落于互联网、Google 败退于社交网络、百度止步于电子商务的原因时,都会由衷的感慨“原来剧本早在多年以前就已经写好了”。

    同样,为什么 Android 手机的“卡慢”问题永远比 iPhone 要更加严重,它的答案也从一开始就注定了。

    1965 年,贝尔实验室、通用电气和麻省理工学院开始合作开发一套能够兼顾易用性和强大性的操作系统,经过六年时间的通力协作,贝尔实验室的一名软件工程师 Ken Thompson 在休假期间完成了一个名为 Unix 的系统编写,并最终成为贝尔实验室的母公司、美国电信巨头 AT&T的商业产品,并启动了长达数十年的版权运作。尽管后来有着许多变种,但是从严格意义上来讲,Unix 不是一个开源的操作系统。

    1991 年,一个芬兰的大学生、同时也是计算机黑客的 Linus Torvalds,他对 Unix 十分着迷,但是买不起运行 Unix 需要的工作站,所以他就尝试自己以同样的编程方式写了一个名为 Linux 的操作系统,并在自由软件之父 Richard Stallman 的精神鼓舞之下,将 Linux 加入到了自由软件基金(FSF)当中,允许所有人使用、拷贝、修改甚至销售 Linux 系统,同时承担开源义务,禁止把 Linux 封闭化的企图。

    之所以要如此大费周章的讲述 Unix 和 Linux 两个操作系统的故事,是因为 iOS 和 Android,正是分别基于 Unix 和 Linux 而衍生出来的作品。也就是说,是 Unix 和 Linux 的两种特性,造成了 iPhone 与 Android 手机在使用体验上的巨大差异。

    乔布斯曾经邀请 Linux 的创始者 Linus Torvalds 到苹果工作,放弃 Linux 的开源,协助开发 Mac OS 封闭式的 Mach 内核,后者与乔布斯大吵一架之后明确表示拒绝。而从 Mac OS 开始,苹果就将操作系统的私有化视为企业战略,用乔布斯的话来讲,他是将 iOS 装进了 iPhone 这个盒子里,然后卖给了用户。所以,iPhone 之所以不会出现“越用越卡”的情况,是因为苹果公司对它的手机从硬件到软件拥有最高的管理权限,在封闭式的环境中,来自第三方的应用程序无法调用超过 iPhone 承受限度的指令,自然也不可能造成持续性的系统损伤。

    反观 Android 手机,由于开源的公开条件,Google 无法从代码这一端口约束第三方的应用程序,同时,由于 Linux 核心设定应用在调取系统功能时一定要取得 ROOT 权限,这也导致大量应用因为单一功能的实现需求而获得整个 ROOT 层面的支配,可以在 Android 手机的任意储存位置进行读写,这种高自由度无异于开启了潘多拉魔盒,让 Android 手机无法对恶意 App 事先设防。这也是开源软件备受争议、且在商用领域遭到抵触的原因:它只关心是否授予了用户自由——这个自由也包括逾越边界的自由——而没有从最坏的出发点去考虑如何规避被滥用的风险。尽管 Google 作为巨头,一直在尝试对产业链进行统一管理,但是当这条产业链日益庞大、连 Google 也只能扮演其中之一的角色时,Android 的失控也就在情理之中了。比如,Android 的最新版本通常需要花费超过一年半的时间,才能使激活它的 Android 手机占比超过 50%,但是 iOS 7 只用了两个月,就让半数以上的 iPhone 都更新完毕。另外,一款应用程序如果被苹果从 App Store 中惩罚出去,它就再也无法被安装到任何一款合法的 iPhone 里面,但是如果一款应用程序被 Google 驱逐出 Google Play,但是它还是可以登录各种第三方应用市场,提供正常的下载和安装。

    所以,Android 的这种天生短板,又催生出了一个“手机调校”的市场,并带动了新的产业链。

    “手机调校”的第一级,在于系统层。在 Android 4.4 以及之后的 Android L 的规划中,它将应用程序的运行模式由 Dalvik 换成了 ART,其原理简单来说是“预编译”效果,即当一款应用程序在第一次被安装到 Android 时,它的字节码就已经被编译成为了本地的机器码,减少后续运行应用程序时的启动和执行时间。

    根据 Google 自己公布的结果,在不同的性能测试 App 中,ART 的速度对比 Dalvik 的平均提升幅度达到了 80%,在某些项目中,ART 的提升幅度甚至超过了 1.5 倍,这个结果可谓非常喜人。

    这是 Google 希望从源头解决 Android 卡慢问题的努力,但是这只是对性能优化有着作用,无法解决因为应用程序违规调用资源而产生的问题。同时,由于在安装应用程序时进行了“预编译”,整个安装时间将会变长,安装完毕后生成的文件也会变大,比如最新的 Google+ 安装包只有 6.9M,但是它安装后的 APK 大小达到了 28.3M,这对 Android 手机储存空间又存在过多占用的问题。

    “手机调校”的第二级,在于 ROM 层。作为全球最大的 Android 市场,中国的许多手机厂商都以开发专用 ROM 来为销售产品添彩,大多数的 ROM,也都会考虑对 Android 系统进行优化,比如 MIUI V6 就宣称“引入多种 Linux 系统内核内存优化技术,提高应用运行效率”。

    也就是说,与 Google 做的事情一样,ROM 厂商主要的优化工作,也是对 Linux 动刀,打上各种补丁,使其底层语言能够更好的适配到各种手机终端上。还是以 MIUI V6 为例,在介绍新特性时,其有这么一条:“ZRAM 调度优化技术”,其实 ZARM 就是 Linux 内核里的一个内存模块,作用就是在内存中划出一个部分出来充当虚拟盘,来承载 Linux 的交换分区,将一些任务压缩容纳进去,使内存的使用率提高,让 CPU 来为内存服务(因为目前的智能手机普遍 CPU 过剩、而内存才是瓶颈)。

    不过,ROM 也是一把双刃剑,它对于 Android 底层系统的修改,以及它对于内存空间的占用,又都有增加手机负载的风险。

    “手机调校”的第三级,在于应用层。大量应用程序在手机中发生的意外或故意占用事件,是造成 Android 手机越来越慢的最核心原因。过多的应用程序热衷于滞留在内存空间里、以及将大量碎片留在储存空间里,是带来麻烦的罪魁祸首。这也是为什么即时清理类应用得以逐渐成为 Android 手机标配。

    Android 系统有七类进程,分别是前台进程、可见进程、主要服务、次要服务、后台进程、内容供应节点、空进程,在没有安装清理类应用的时候,一部 Android 手机只能依赖系统默认的分配机制来自动调节内存使用,只要应用程序提出请求,大部分进程只要打开后都会被保留在内存当中,这原本是为了让用户在再度激活这些进程时不需要重新载入、节省时间的初衷考虑,但是 Android 没有料到激烈的市场竞争会驱使应用程序产生“劣币驱良币”的趋势,很多开发者出于商业目的,在不需要留存在内存的情况下也想方设法的让应用程序保持潜在运行状态,一个两个还好说,但是一旦数量更多,Anrdoid 手机就会频频卡顿和发热。

    以目前全球用户规模最大的 Android 手机清理类应用“猎豹清理大师”为例,它清理的进程类型,主要放在后台进程、次要服务、内容供应节点和空进程:

    后台进程(Hidden)——这个是最优先被扫描和识别出来的进程,因为大部分 Android 用户在切换应用程序时都不会使用返回键退出,而是直接按下 Home 键,前者会让应用进入空进程(占用资源相对较小),而后者则会保留为后台进程(占用资源相对更大),尤其是当游戏类 App 在后台运行时,它会和其他 App 争抢资源,而不会在乎那款 App 是不是用户正在使用。根据猎豹清理大师的统计,约有 20% 的常用 App 即使不运行时也在后台启动联网,主要是提交产品及用户使用信息、获取广告信息、查询是否升级等。

    次要服务(Secondary Server)——比如某些企业套件、邮箱联系人、触控接口等,这些进程很多都是系统自带的,有些用户会使用,但是有些用户也可能不会使用或已经有了替代应用,所以猎豹清理大师的清理逻辑是基于用户行为和授权来建立(分为建议清理和深度清理两类);

    内容供应节点(Content Provider)——这部分进程没有程序实体,仅仅提供内容给其他应用使用,比如日历供应节点、邮件供应节点等,除了占用内存资源之外,它还会占用网络,所以也会给 Android 手机造成不必要的负担;

    空进程(Empty)——如果是通过返回键退出应用,大部分的应用也会在 Android 手机的内存里遗留一个空的进程,这个进程没有数据运行,但是会记录应用的历史信息,几乎没有任何价值,同样,这部分进程内容被干掉的优先级也很高。

    除了对内存的过度消耗之外,Android 手机也容易在储存中积累大量冗余数据,包括无法卸载的预装应用、卸载之后的残存文件以及使用应用的过程中产生的缓存,由于 Android 本身没有提供管理工具,即使将手机连接电脑之后也是如同 Windows 树状结构一样的文件夹包,用户很难独立判断哪些文件夹可以删除、哪些文件夹是系统必备的,最后也会导致手机尺寸空间愈来愈窄的情况。

    “手机调校”的问题,可能又回带来用户操作的负担增加,其心理压力甚于行为压力,玩着手机还不忘隔三差五的使用清理功能,这种与 iPhone 相比“别具特色”的操作习惯,也是 Android 手机永远像一个半成品或工程机的原因。

    本文链接


    java UTF-8 和 UTF-8 without BOM工具处理类

    $
    0
    0
    package org.shefron.fc.utfwithbom;
    
    import java.io.FileInputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.PushbackInputStream;
    
    public class UTFFileHandler {
    
    	/**
    	 *
    	 * @param file the filePath
    	 * @return the FileInputStream
    	 * @throws Exception
    	 */
    	public static InputStream getInputStream(String file) throws Exception{
    		FileInputStream fis = null;
    		try{
    			fis = new FileInputStream(file);
    		}catch(Exception e){
    			System.out.println(e.getMessage());
    			throw new Exception("IO Stream Error!");
    		}
    		return fis;
    
    	}
    
    	/**
    	 *
    	 * @param file the filePath
    	 * @param enc  the default encoding
    	 * @return the UTFFileHandler.UnicodeInputStream
    	 * @throws Exception
    	 */
    	public static InputStream getInputStreamWithoutBom(String file,String enc) throws Exception{
    
    		UnicodeInputStream stream = null;
    		try{
    			FileInputStream fis = new FileInputStream(file);
    			stream = new UnicodeInputStream(fis,null);
    			System.out.println("encoding:"+stream.getEncoding() );
    		}catch(Exception e){
    			System.out.println(e.getMessage());
    			throw new Exception("IO Stream Error!");
    		}
    		return stream;
    
    	}
    
    	/**
    	 * This inputstream will recognize unicode BOM marks and will skip bytes if
    	 * getEncoding() method is called before any of the read(...) methods.
    	 *
    	 * Usage pattern: String enc = "ISO-8859-1"; // or NULL to use systemdefault
    	 * FileInputStream fis = new FileInputStream(file); UnicodeInputStream uin = new
    	 * UnicodeInputStream(fis, enc); enc = uin.getEncoding(); // check and skip
    	 * possible BOM bytes InputStreamReader in; if (enc == null) in = new
    	 * InputStreamReader(uin); else in = new InputStreamReader(uin, enc);
    	 */
    	public static class UnicodeInputStream extends InputStream {
    	    PushbackInputStream internalIn;
    	    boolean isInited = false;
    	    String defaultEnc;
    	    String encoding;
    
    	    private static final int BOM_SIZE = 4;
    
    	    public UnicodeInputStream(InputStream in, String defaultEnc) {
    	        internalIn = new PushbackInputStream(in, BOM_SIZE);
    	        this.defaultEnc = defaultEnc;
    	    }
    
    	    public String getDefaultEncoding() {
    	        return defaultEnc;
    	    }
    
    	    public String getEncoding() {
    	        if (!isInited) {
    	            try {
    	                init();
    	            } catch (IOException ex) {
    	                IllegalStateException ise = new IllegalStateException(
    	                        "Init method failed.");
    	                ise.initCause(ise);
    	                throw ise;
    	            }
    	        }
    	        return encoding;
    	    }
    
    	    /**
    	     * Read-ahead four bytes and check for BOM marks. Extra bytes are unread
    	     * back to the stream, only BOM bytes are skipped.
    	     */
    	    protected void init() throws IOException {
    	        if (isInited)
    	            return;
    
    	        byte bom[] = new byte[BOM_SIZE];
    	        int n, unread;
    	        n = internalIn.read(bom, 0, bom.length);
    
    	        if ((bom[0] == (byte) 0x00) && (bom[1] == (byte) 0x00)&& (bom[2] == (byte) 0xFE) && (bom[3] == (byte) 0xFF)) {
    	            encoding = "UTF-32BE";
    	            unread = n - 4;
    	        } else if ((bom[0] == (byte) 0xFF) && (bom[1] == (byte) 0xFE)&& (bom[2] == (byte) 0x00) && (bom[3] == (byte) 0x00)) {
    	            encoding = "UTF-32LE";
    	            unread = n - 4;
    	        } else if ((bom[0] == (byte) 0xEF) && (bom[1] == (byte) 0xBB)&& (bom[2] == (byte) 0xBF)) {
    	            encoding = "UTF-8";
    	            unread = n - 3;
    	        } else if ((bom[0] == (byte) 0xFE) && (bom[1] == (byte) 0xFF)) {
    	            encoding = "UTF-16BE";
    	            unread = n - 2;
    	        } else if ((bom[0] == (byte) 0xFF) && (bom[1] == (byte) 0xFE)) {
    	            encoding = "UTF-16LE";
    	            unread = n - 2;
    	        } else {
    	            // Unicode BOM mark not found, unread all bytes
    	            encoding = defaultEnc;
    	            unread = n;
    	        }
    	        // System.out.println("read=" + n + ", unread=" + unread);
    
    	        if (unread > 0)
    	            internalIn.unread(bom, (n - unread), unread);
    
    	        isInited = true;
    	    }
    
    	    public void close() throws IOException {
    	        // init();
    //	        isInited = true;
    	        internalIn.close();
    	    }
    
    	    public int read() throws IOException {
    	        // init();
    //	        isInited = true;
    	        return internalIn.read();
    	    }
    	}
    
    }
    


     

    作者:shefron 发表于2014-8-27 15:52:22 原文链接
    阅读:6 评论:0 查看评论

    Web 前端攻防(2014版)

    $
    0
    0

    禁止一切外链资源

    外链会产生站外请求,因此可以被利用实施 CSRF 攻击。

    目前国内有大量路由器存在 CSRF 漏洞,其中相当部分用户使用默认的管理账号。通过外链图片,即可发起对路由器 DNS 配置的修改,这将成为国内互联网最大的安全隐患。

    案例演示

    百度旅游在富文本过滤时,未考虑标签的 style 属性,导致允许用户自定义的 CSS。因此可以插入站外资源:

    所有浏览该页面的用户,都能发起任意 URL 的请求:

    由于站外服务器完全不受控制,攻击者可以控制返回内容:

    • 如果检测到是管理员,或者外链检查服务器,可以返回正常图片;
    • 如果是普通用户,可以返回 302 重定向到其他 URL,发起 CSRF 攻击。例如修改路由器 DNS:
    http://admin:admin@192.168.1.1/userRpm/PPPoECfgAdvRpm.htm?wan=0&lcpMru=1480&ServiceName=&AcName=&EchoReq=0&manual=2&dnsserver=黑客服务器&dnsserver2=4.4.4.4&downBandwidth=0&upBandwidth=0&Save=%B1%A3+%B4%E6&Advanced=Advanced

    演示中,随机测试了几个帖子,在两天时间里收到图片请求 500 多次,已有近 10 个不同的 IP 开始向我们发起 DNS 查询。

    通过中间人代理,用户的所有隐私都能被捕捉到。还有更严重的后果,查考 流量劫持危害探讨

    要是在热帖里『火前留名』,那么数量远不止这些。

    如果使用发帖脚本批量回复,将有数以万计的用户网络被劫持。

    防范措施

    杜绝用户的一切外链资源。需要站外图片,可以抓回后保存在站内服务器里。

    对于富文本内容,使用白名单策略,只允许特定的 CSS 属性。

    尽可能开启 Content Security Policy 配置,让浏览器底层来实现站外资源的拦截。

    富文本前端扫描

    富文本是 XSS 的重灾区。

    富文本的实质是一段 HTML 字符。由于历史原因,HTML 兼容众多不规范的用法,导致过滤起来较复杂。几乎所有使用富文本的产品,都曾出现过 XSS 注入。

    案例演示

    旅游发帖支持富文本,我们继续刚才的演示。

    由于之前已修复过几次,目前只能注入 embed 标签和 src 属性。

    但即使这样,仍然可以嵌入一个框架页面:

    因为是非同源执行的 XSS,所以无法获取主页面的信息。但是可以修改 top.location,将页面跳转到第三方站点。

    将原页面嵌入到全屏的 iframe 里,伪造出相同的界面。然后通过浮层登录框,进行钓鱼。

    总之,富文本中出现可执行的元素,页面安全性就大打折扣了。

    防范措施

    这里不考虑后端的过滤方法,讲解使用前端预防方案:

    无论攻击者使用各种取巧的手段,绕过后端过滤,但这些 HTML 字符最终都要在前端构造成元素,并渲染出来。

    因此可以在 DOM 构造之后、渲染之前,对离屏的元素进行风险扫描。将可执行的元素(script,iframe,frame,object,embed,applet)从缓存中移除。

    或者给存在风险的元素,加上沙箱隔离属性。

    • 例如 iframe 加上  sandbox 属性,即可只显示框架内容而不运行脚本
    • 例如 Flash 加上  allowScriptAccess 及  allowNetworking,也能起到一定的隔离作用。

    DOM 仅仅被构造是不会执行的,只有添加到主节点被渲染时才会执行。所以这个过程中间,可以实施安全扫描。

    实现细节可以参考: http://www.etherdream.com/FunnyScript/richtext saferender.html

    如果富文本是直接输入到静态页面里的,可以考虑使用 MutationEvent 进行防御。详细参考: http://fex.baidu.com/blog/2014/06/xss-frontend-firewall-2/

    但推荐使用动态方式进行渲染,可扩展性更强,并且性能消耗最小。

    跳转 opener 钓鱼

    浏览器提供了一个 opener 属性,供弹出的窗口访问来源页。但该规范设计的并不合理,导致通过超链接打开的页面,也能使用 opener。

    因此,用户点了网站里的超链接,导致原页面被打开的第三方页面控制。

    虽然两者受到同源策略的限制,第三方无法访问原页面内容,但可以跳转原页面。

    由于用户的焦点在新打开的页面上,所以原页面被悄悄跳转,用户难以觉察到。当用户切回原页面时,其实已经在另一个钓鱼网站上了。

    案例演示

    百度贴吧目前使用的超链接,就是在新窗口中弹出,因此同样存在该缺陷。

    攻击者发一个吸引用户的帖子。当用户进来时,引诱他们点击超链接。

    通常故意放少部分的图片,或者是不会动的动画,先让用户预览一下。要是用户想看完整的,就得点下面的超链接:

    由于扩展名是 gif 等图片格式,大多用户就毫无顾虑的点了。

    事实上,真正的类型是由服务器返回的 MIME 决定的。所以这个站外资源完全有可能是一个网页:

    当用户停留在新页面里看动画时,隐匿其中的脚本已悄悄跳转原页面了。

    用户切回原页面时,其实已在一个钓鱼网站上:

    在此之上,加些浮层登录框等特效,很有可能钓到用户的一些账号信息。

    防范措施

    该缺陷是因为 opener 这个属性引起的,所以得屏蔽掉新页面的这个属性。

    但通过超链接打开的网页,无法被脚本访问到。只有通过 window.open 弹出的窗口,才能获得其对象。

    所以,对页面中的用户发布的超链接,监听其点击事件,阻止默认的弹窗行为,而是用 window.open 代替,并将返回窗体的 opener 设置为 null,即可避免第三方页面篡改了。

    详细实现参考: http://www.etherdream.com/FunnyScript/opener_protect.html

    当然,实现中无需上述 Demo 那样复杂。根据实际产品线,只要监听用户区域的超链接就可以。

    用户内容权限

    支持自定义装扮的场合,往往是钓鱼的高发区。

    一些别有用心者,利用装扮来模仿系统界面,引诱用户上钩。

    案例演示 – 空间越界

    百度空间允许用户撰写自定格式的内容。

    其本质是一个富文本编辑器,这里不演示 XSS 漏洞,而是利用样式装扮,伪装一个钓鱼界面。

    百度空间富文本过滤元素、部分属性及 CSS 样式,但未对 class 属性启用白名单,因此可以将页面上所有的 CSS 类样式,应用到自己的内容上来。

    防范措施

    规定用户内容尺寸限制,可以在提交时由用户自己确定。

    不应该为用户的内容分配无限的尺寸空间,以免恶意用户设置超大字体,破坏整个页面的浏览。

    最好将用户自定义的内容嵌套在 iframe 里,以免影响到页面其他部位。

    如果必须在同页面,应将用户内容所在的容器,设置超过部分不可见。以免因不可预测的 BUG,导致用户能将内容越界到产品界面上。

    案例演示 – 功能越界

    自定义装扮通常支持站外超链接。

    相比贴吧这类简单纯文字,富文本可以将超链接设置在其他元素上,例如图片。

    因此这类链接非常具有迷惑性,用户不经意间就点击到。很容易触发之前提到的修改 opener 钓鱼。

    如果在图片内容上进行伪装,更容易让用户触发一些危险操作。

    要是和之前的区域越界配合使用,迷惑性则更强:

    防范措施

    和之前一样,对于用户提供的超链接,在点击时进行扫描。如果是站外地址,则通过后台跳转进入,以便后端对 URL 进行安全性扫描。

    如果服务器检测到是一个恶意网站,或者目标资源是可执行文件,应给予用户强烈的警告,告知其风险。

    点击劫持检测

    点击劫持算是比较老的攻击方式了,基本原理大家也都听说过。就是在用户不知情的前提下,点击隐藏框架页面里的按钮,触发一些重要操作。

    但目前在点击劫持上做防御的并不多,包括百度绝大多数产品线目前都未考虑。

    案例演示

    能直接通过点击完成的操作,比较有意义的就是关注某用户。例如百度贴吧加关注的按钮:

    攻击者事先算出目标按钮的尺寸和坐标,将页面嵌套在自己框架里,并设置框架的偏移,最终只显示按钮:

    接着通过 CSS 样式,将目标按钮放大,占据整个页面空间,并设置全透明。

    这时虽看不到按钮,但点击页面任意位置,都能触发框架页中加关注按钮的点击:

    防范措施

    事实上,点击劫持是很好防御的。

    因为自身页面被嵌套在第三方页面里,只需判断 self == top 即可获知是否被嵌套。

    对一些重要的操作,例如加关注、删帖等,应先验证是否被嵌套。如果处于第三方页面的框架里,应弹出确认框提醒用户。

    确认框的坐标位置最好有一定的随机偏移,从而使攻击者构造的点击区域失效。

    Web 前端攻防(2014版),首发于 博客 - 伯乐在线

    保护云数据安全的8个实用性技巧

    $
    0
    0
    概述:云服务为我们的工作和生活带来了很大的方便,但是数据安全的问题也接踵而来.本文介绍了保护云数据安全的8个实用性技巧 ,帮助云服务的用户提高对云数据安全的认识.

    云存储服务的高速发展让你终于告别了去哪儿都必须随身携带U盘或移动硬盘的日子。从Google Drive到Dropbox, 在网络连接可用的情况下,这些云解决方案为你提供在线存储数据的服务并可 让你能够在任何地方和时间里进行访问

    云数据安全

    云存储服务的方便确实令我们着迷,但是,从将个人数据上传到云提供商之后所带来的 相关的安全问题却不得不让我们提高警惕,我们无法确定到底有哪些人能够触碰到这些敏感的信息。

    我们迫切地希望能够做一些措施,尽力 保护我们的云数据免遭未经授权的访问。以下的8个保护云数据安全的实用性技巧应该会对我们有所帮助。

    1.本地数据备份

    数据管理最重要的一条就是 保持对数据的备份。一般来说,有一个很好的做法就是对你的任何数据创建电子拷贝。这样做的好处就是,如果你正在使用的数据遗失或者损坏了,你依然可以访问它们。市场上有多种云存储服务,因此你应当 设置一些云备份

    当你在云端存放数据后,你还应该将这些数据 手动的备份到外部物理存储驱动器或设备上,比如硬盘和U盘。这能够帮助你在网络不畅或者无网络的情况下对数据进行访问。

    2.不要存储敏感信息

    互联网上没有绝对的隐私,因此,对我个人而言,我不会在云端存储自己的绝密文件。你或许可以把这种处理方式归结于“偏执”二字,但是,网上盗 窃的事情屡见不鲜,这样做可以杜绝此类的风险。而且,我们最敏感的数据是不需要挂在云端进行随时无间断访问的。

    我的建议是, 只把你需要频繁访问的文件放在云端,而那些敏感的信息,比如各种在线账户密码、个人身份信息(PII)等则不要存放在那里。当然,如果你的这些信息必须包含在文件中时,你应当对这些敏感信息进行加密后再上传。

    3.使用加密数据的云服务

    你需要在使用云存储服务时让你的隐私得到保护,其中一个最简单的方法就是寻找一个为你的数据 提供局部加密的云服务。这相当于为你的数据又额外提供了一个安全层—— 所有对数据进行访问的行为都必须先解密这个安全层

    这个安全层在密码学以零知识证明(zero-knowledge proof)而著称,它能够有效的保护服务供应商和管理者自身的数据安全。除了在云端保持数据加密,我们还希望能够在 云服务中数据的上传和下载阶段也提供数据加密的功能。而DrivePop等一类云服务商已经通过军用级高级加密标准(AES)(256位)实现了这些功能。

    由于额外的加解密步骤,当你同步文件云驱动器时,会需要等待一段时间。也就是说,你想要文档只对你一个人开放的话,必须经历痛苦的等待。

    4.上传云端之前对数据加密

    如果你选择不使用云服务来帮助你加密数据,你可以选择使用第三方工具来执行加密。你可以 下载一个云数据保护的应用程序,该应用程序可在你上传文件到云端之前 通过密码和密钥序列对文件进行加密保护

    如果你已经选择了加密的云服务,上传前的加密工作也 不会与之冲突,而是额外增加一层保护措施而已。

    5.关注云服务提供商的协议细则

    除了存储你的数据,某些云服务还允许你与他人分享你的照片和文件。这个功能听起来很诱人,但是有时这些服务会是陷阱。有一些细则,云服务提供商并不会宣传出来引起你的注意,而是默默地将其放在他们的服务条款(TOS)里使之合法化。

    例如,在2011年,Twitpic将“在他们的云服务里,他们有权使用或分发你所共享的图片。”写进了他们的服务条款里。后来他们对这一项细则声称抱歉但是进一步说明:这些图片虽然 最终版权仍属于图片主人,但是 Twitpic和其合作伙伴有分发这些图片的权利

    作为一个不是纯粹的云存储服务提供商,Twitpic的案例向你很好地解释了为什么要关注这些云服务提供商对你提供的东西,尤其是关于他们的安全和隐私政策。在使用他们的服务之前先尝试在网上查询其是否有你所担心的负面的评论和警告,这将有助于你作出一个更为明智的选择。

    6.使用强密码或提供二次验证

    作为防御恶意黑客的第一线,你必须确保你的密码能够经得住黑客的破解。互联网上有成千上万种帮助你拟定一个好密码的技巧。除了使用一个强大而独特的密码,你应当 经常更换它,同时 不要使用与其它网络帐户相同的密码

    另外,如果你的云服务提供了两步验证的支持,你也可以选择这种更为安全的方法。在Google Drive,用户要使用云存储服务的话必须先登录他们的谷歌帐户。而打开谷歌账户需要两步验证—— 从手机上获取验证码可以大幅增加安全性,而只有输入你得到的密码才能访问云数据

    7.对你的在线行为提高警惕

    云数据的安全 与你平时的网上行为息息相关,尤其是你在使用公共电脑和网络的时候。当你使用公共电脑时,切忌选择保存你的密码,并关注在你的帐户注销之后都做了些什么。在公共场合保存你的密码和忘记帐户注销 会增加陌生人访问你的数据的风险

    你经常在在公共场所使用开放和无安全担保的Wi-Fi 来登陆你的云帐户么?通常这些连接都是未加密的,也就是说不管你在做什么,使用同一网络的黑客都可以“嗅探”你的连接行为,这甚至包含 你的云帐户的登陆凭证!你可以看看这篇来自于无线安全网的 文章 ,它向你介绍了黑客如何查看未加密的无线网络。

    8.使用反病毒和反侦察保护你的系统

    你可能选择了一个让你绝对信任的安全的云服务提供商,但是却有可能会忽略云数据安全 最薄弱的一环——你登陆的计算机系统。如果你没有你的系统进行有效地保护,你会提供给 黑客通过病毒和BUG来访问你的帐户的机会。

    例如,键盘木马会追踪你所有的按键。如果你的系统无法监测出来,如果你的登陆没有经过保护和加密,通过嵌入看似合法文件的恶意软件, 黑客就能拿到你的用户ID和密码

     

    本文翻译自 8 Practical Tips To Secure Your Cloud Data



    已有 0人发表留言,猛击->> 这里<<-参与讨论


    ITeye推荐



    [MySQL优化案例]系列 — 分页优化

    $
    0
    0

    通常,我们会采用ORDER BY LIMIT start, offset 的方式来进行分页查询。例如下面这个SQL:

    SELECT * FROM `t1` WHERE ftype=1 ORDER BY id DESC LIMIT 100, 10;

    或者像下面这个不带任何条件的分页SQL:

    SELECT * FROM `t1` ORDER BY id DESC LIMIT 100, 10;

    一般而言,分页SQL的耗时随着 start 值的增加而急剧增加,我们来看下面这2个不同起始值的分页SQL执行耗时:

    yejr@imysql.com> SELECT * FROM `t1` WHERE ftype=1 ORDER BY id DESC LIMIT 500, 10;
    …
    
    10 rows in set (0.05 sec)
    
    
    yejr@imysql.com> SELECT * FROM `t1` WHERE ftype=6 ORDER BY id DESC LIMIT 935500, 10;
    …
    
    10 rows in set (2.39 sec)
    

    可以看到,随着分页数量的增加,SQL查询耗时也有数十倍增加,显然不科学。今天我们就来分析下,如何能优化这个分页方案。 一般滴,想要优化分页的终极方案就是:没有分页,哈哈哈~~~,不要说我讲废话,确实如此,可以把分页算法交给Sphinx、Lucence等第三方解决方案,没必要让MySQL来做它不擅长的事情。 当然了,有小伙伴说,用第三方太麻烦了,我们就想用MySQL来做这个分页,咋办呢?莫急,且待我们慢慢分析,先看下表DDL、数据量、查询SQL的执行计划等信息:

    yejr@imysql.com> SHOW CREATE TABLE `t1`;
    CREATE TABLE `t1` (
     `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
    ...
     `ftype` tinyint(3) unsigned NOT NULL,
    ...
     PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
    yejr@imysql.com> select count(*) from t1;
    +----------+
    | count(*) |
    +----------+
    | 994584 |
    +----------+
    
    yejr@imysql.com> EXPLAIN SELECT * FROM `t1` WHERE ftype=1 ORDER BY id DESC LIMIT 500, 10\G
    *************************** 1. row ***************************
     id: 1
     select_type: SIMPLE
     table: t1
     type: index
    possible_keys: NULL
     key: PRIMARY
     key_len: 4
     ref: NULL
       rows: 510 Extra: Using where
    
    yejr@imysql.com> EXPLAIN SELECT * FROM `t1` WHERE ftype=1 ORDER BY id DESC LIMIT 935500, 10\G
    *************************** 1. row ***************************
     id: 1
     select_type: SIMPLE
     table: t1
     type: index
    possible_keys: NULL
     key: PRIMARY
     key_len: 4
     ref: NULL rows: 935510 Extra: Using where

    可以看到,虽然通过主键索引进行扫描了,但第二个SQL需要扫描的记录数太大了,而且需要先扫描约935510条记录,然后再根据排序结果取10条记录,这肯定是非常慢了。 针对这种情况,我们的优化思路就比较清晰了,有两点:

    1、尽可能从索引中直接获取数据,避免或减少直接扫描行数据的频率
    2、尽可能减少扫描的记录数,也就是先确定起始的范围,再往后取N条记录即可
    

    据此,我们有两种相应的改写方法:子查询、表连接,即下面这样的:

    #采用子查询的方式优化,在子查询里先从索引获取到最大id,然后倒序排,再取10行结果集
    #注意这里采用了2次倒序排,因此在取LIMIT的start值时,比原来的值加了10,即935510,否则结果将和原来的不一致
    yejr@imysql.com> EXPLAIN SELECT * FROM (SELECT * FROM `t1` WHERE id > ( SELECT id FROM `t1` WHERE ftype=1 ORDER BY id DESC LIMIT 935510, 1) LIMIT 10) t ORDER BY id DESC\G
    *************************** 1. row ***************************
     id: 1
     select_type: PRIMARY
     table: <derived2>
     type: ALL
    possible_keys: NULL
     key: NULL
     key_len: NULL
     ref: NULL
     rows: 10
     Extra: Using filesort
    *************************** 2. row ***************************
     id: 2
     select_type: DERIVED
     table: t1
     type: ALL
    possible_keys: PRIMARY
     key: NULL
     key_len: NULL
     ref: NULL
     rows: 973192
     Extra: Using where
    *************************** 3. row ***************************
     id: 3
     select_type: SUBQUERY
     table: t1
     type: index
    possible_keys: NULL
     key: PRIMARY
     key_len: 4
     ref: NULL
     rows: 935511
     Extra: Using where
    
    #采用INNER JOIN优化,JOIN子句里也优先从索引获取ID列表,然后直接关联查询获得最终结果,这里不需要加10
    yejr@imysql.com> EXPLAIN SELECT * FROM `t1` INNER JOIN ( SELECT id FROM `t1` WHERE ftype=1 ORDER BY id DESC LIMIT 935500,10) t2 USING (id)\G
    *************************** 1. row ***************************
     id: 1
     select_type: PRIMARY
     table: <derived2>
     type: ALL
    possible_keys: NULL
     key: NULL
     key_len: NULL
     ref: NULL
     rows: 935510
     Extra: NULL
    *************************** 2. row ***************************
     id: 1
     select_type: PRIMARY
     table: t1
     type: eq_ref
    possible_keys: PRIMARY
     key: PRIMARY
     key_len: 4
     ref: t2.id
     rows: 1
     Extra: NULL
    *************************** 3. row ***************************
     id: 2
     select_type: DERIVED
     table: t1
     type: index
    possible_keys: NULL
     key: PRIMARY
     key_len: 4
     ref: NULL
     rows: 973192
     Extra: Using where

    然后我们来对比下这2个优化后的新SQL执行时间:

    yejr@imysql.com> SELECT * FROM (SELECT * FROM `t1` WHERE id > ( SELECT id FROM `t1` WHERE ftype=1 ORDER BY id DESC LIMIT 935510, 1) LIMIT 10) T ORDER BY id DESC;
    ...
    rows in set (1.86 sec)
    #采用子查询优化,从profiling的结果来看,相比原来的那个SQL快了:28.2%
    
    yejr@imysql.com> SELECT * FROM `t1` INNER JOIN ( SELECT id FROM `t1` WHERE ftype=1 ORDER BY id DESC LIMIT 935500,10) t2 USING (id);
    ...
    10 rows in set (1.83 sec)
    #采用INNER JOIN优化,从profiling的结果来看,相比原来的那个SQL快了:30.8%
    

    我们再来看一个不带过滤条件的分页SQL对比:

    #原始SQL
    yejr@imysql.com> EXPLAIN SELECT * FROM `t1` ORDER BY id DESC LIMIT 935500, 10\G
    *************************** 1. row ***************************
               id: 1
      select_type: SIMPLE
            table: t1
             type: index
    possible_keys: NULL
              key: PRIMARY
          key_len: 4          ref: NULL         rows: 935510
            Extra: NULL
    
    yejr@imysql.com> SELECT * FROM `t1` ORDER BY id DESC LIMIT 935500, 10;
    ...
    10 rows in set (2.22 sec)
    
    #采用子查询优化
    yejr@imysql.com> EXPLAIN SELECT * FROM (SELECT * FROM `t1` WHERE id > ( SELECT id FROM `t1` ORDER BY id DESC LIMIT 935510, 1) LIMIT 10) t ORDER BY id DESC;
    *************************** 1. row ***************************
               id: 1
      select_type: PRIMARY
            table: <derived2>
             type: ALL
    possible_keys: NULL
              key: NULL
          key_len: NULL
              ref: NULL
             rows: 10
            Extra: Using filesort
    *************************** 2. row ***************************
               id: 2
      select_type: DERIVED
            table: t1         type: ALLpossible_keys: PRIMARY
              key: NULL
          key_len: NULL
              ref: NULL
             rows: 973192
            Extra: Using where
    *************************** 3. row ***************************
               id: 3
      select_type: SUBQUERY
            table: t1
             type: index
    possible_keys: NULL
              key: PRIMARY
          key_len: 4
              ref: NULL
             rows: 935511        Extra: Using index
    yejr@imysql.com> SELECT * FROM (SELECT * FROM `t1` WHERE id > ( SELECT id FROM `t1` ORDER BY id DESC LIMIT 935510, 1) LIMIT 10) t ORDER BY id DESC;
    …
    10 rows in set (2.01 sec)
    #采用子查询优化,从profiling的结果来看,相比原来的那个SQL快了:10.6%
    
    
    #采用INNER JOIN优化
    yejr@imysql.com> EXPLAIN SELECT * FROM `t1` INNER JOIN ( SELECT id FROM `t1`ORDER BY id DESC LIMIT 935500,10) t2 USING (id)\G
    *************************** 1. row ***************************
               id: 1
      select_type: PRIMARY
            table: 
             type: ALL
    possible_keys: NULL
              key: NULL
          key_len: NULL
              ref: NULL
             rows: 935510
            Extra: NULL
    *************************** 2. row ***************************
               id: 1
      select_type: PRIMARY
            table: t1         type: eq_refpossible_keys: PRIMARY
              key: PRIMARY
          key_len: 4
              ref: t1.id
             rows: 1
            Extra: NULL
    *************************** 3. row ***************************
               id: 2
      select_type: DERIVED
            table: t1
             type: index
    possible_keys: NULL
              key: PRIMARY
          key_len: 4
              ref: NULL
             rows: 973192        Extra: Using index
    yejr@imysql.com> SELECT * FROM `t1` INNER JOIN ( SELECT id FROM `t1`ORDER BY id DESC LIMIT 935500,10) t2 USING (id);
    …
    10 rows in set (1.70 sec)
    #采用INNER JOIN优化,从profiling的结果来看,相比原来的那个SQL快了:30.2%
    

    至此,我们看到采用子查询或者INNER JOIN进行优化后,都有大幅度的提升,这个方法也同样适用于较小的分页,虽然LIMIT开始的 start 位置小了很多,SQL执行时间也快了很多,但采用这种方法后,带WHERE条件的分页分别能提高查询效率:24.9%、156.5%,不带WHERE条件的分页分别提高查询效率:554.5%、11.7%,各位可以自行进行测试验证。单从提升比例说,还是挺可观的,确保这些优化方法可以适用于各种分页模式,就可以从一开始就是用。 我们来看下各种场景相应的提升比例是多少:

    大分页,带WHERE大分页,不带WHERE大分页平均提升比例小分页,带WHERE小分页,不带WHERE总体平均提升比例
    子查询优化28.20%10.60%19.40%24.90%554.40%154.53%
    INNER JOIN优化30.80%30.20%30.50%156.50%11.70%57.30%

    结论:这样看就和明显了,尤其是针对大分页的情况,因此我们优先推荐使用INNER JOIN方式优化分页算法。

    上述每次测试都重启mysqld实例,并且加了SQL_NO_CACHE,以保证每次都是直接数据文件或索引文件中读取。如果数据经过预热后,查询效率会一定程度提升,但但上述相应的效率提升比例还是基本一致的。

    2014/07/28后记更新:

    其实如果是不带任何条件的分页,就没必要用这么麻烦的方法了,可以采用对主键采用范围检索的方法,例如参考这篇: Advance for MySQL Pagination

    转自: http://imysql.com/2014/07/26/mysql-optimization-case-paging-optimize.shtml

    Java中关于String类型的10个问题

    $
    0
    0

    1. 如何比较两个字符串?用“=”还是equals

    简单来说,“==”是用来检测俩引用是不是指向内存中的同一个对象,而equals()方法则检测的是两个对象的值是否相等。只要你项检测俩字符串是不是相等的,你就必须得用equals()方法。

    如果你知道“字符串保留(string intern)”的概念那就更好了。

    2. 为什么安全敏感的字符串信息用char[]会比String对象更好?

    String对象是不可变的就意味着直到垃圾回收器过来清扫之前它们都不会发生变化的。用数组的话,就可以很明确的修改它任何位置的字符元素。这样的话,如密码等安全敏感的信息就不会出现在系统的任何地方。

    3. 字符串对象能否用在switch表达式中?

    从JDK7开始的话,我们就可以在switch条件表达式中使用字符串了,也就是说7之前的版本是不可以的。

    // java 7 only!
    switch (str.toLowerCase()) {
          case "a":
               value = 1;
               break;
          case "b":
               value = 2;
               break;
    }

    4. 如何将字符串转换为整型数值?

    int n = Integer.parseInt("10");

    如此简单,经常使用有偶尔也会被遗忘。

    5. 如何用空格去分隔字符串?

    我们可以很便捷的使用正则表达式来进行分隔。“\s”就表示空格,还有如”",”\t”,”\r”,”\n”.

    String[] strArray = aString.split("\\s+");

    6. substring()方法具体是都干了些啥?

    在JDK6中,这个方法只会在标识现有字符串的字符数组上 给一个窗口来表示结果字符串,但是不会创建一个新的字符串对象。如果需要创建个新字符串对象,可以这样在结果后面+一个空的字符串:

    str.substring(m, n) + ""

    这么写的话就会创建一个新的字符数组来表示结果字符串。同时,这么写也有一定的几率让你的代码跑的更快,因为垃圾回收器会吧没有在使用的大字符串回收而留下子字符串。

    Oracle JDK7中的substring()方法会创建一个新的字符数组,而不用之前存在的。看看 这张图就会明白substring()方法在JDK6和JDK7中的区别。

    7. String&StringBuilder&StringBuffer

    String vs StringBuilder:StringBuilder是可变的,这就意味你在创建对象之后还可以去修改它的值。StringBuilder vs StringBuffer:StringBuffer是同步的,意味着它是线程安全的,但是就会比StringBuilder慢些。

    8. 如何快速重复构造一段字符串?

    在Python编程中,只需要用字符串去乘以一个数字就可以 搞定了,那在Java编程中,我们可以使用来自Apache Commons Lang包中的StringUtils类的repeat()方法。

    String str = "abcd";
    String repeated = StringUtils.repeat(str,3);
    //abcdabcdabcd

    9. 如何将时间格式的字符串转换成date对象?

    String str = "Sep 17, 2013";
    Date date = new SimpleDateFormat("MMMM d, yy", Locale.ENGLISH).parse(str);
    System.out.println(date);
    //Tue Sep 17 00:00:00 EDT 2013

    10. 如何计数一个字符在某个字符串中出现的次数?

    使用Apache Commons Lang包中的 StringUtils类就可以完成这个工作。

    int n = StringUtils.countMatches("11112222", "1");
    System.out.printl```  n(n);

    相关文章

    Viewing all 15843 articles
    Browse latest View live