Java Web Services面试问题集锦
Q. 应用集成方式有哪些?
A. 应用可以采用以下方式集成:
- 共享数据库
- 批量文件传输
- 远程过程调用(RPC)
- 通过消息中间件来交换异步信息(MOM)
Q. 应用集成可以采用的Web服务方式有什么?
A. SOAP WS(Simple Object Access Protocal) 和RESTful Web Service(REpresentational State Transfer)
Q. SOAP WS 和RESTful Web Service之间有什么不同呢?
A.
-
SOAP WS支持既远程过程调用(例如,RPC)又支持消息中间件(MOM)方式进行应用集成。而Restful Web Service仅支持RPC集成方式。
-
SOAP WS是传输协议无关的。它支持多种协议,比如,HTTP(S)、 Messaging、TCP、UDP SMTP等等。而REST是协议相关的,只支持HTTP或者HTTPS协议。
-
SOAP WS仅允许使用XML数据格式。定义的操作通过POST请求发送。其重点是通过操作名来获取服务,并将应用逻辑封装为服务。而REST方式则允许多种数据格式,例如,XML、JSON、文本、HTML等等。而且由于REST方式采用标准GET、PUT、PSOT和DELETE 方法,因此所有的浏览器都可以支持。其重点是通过资源名来获取服务,并将数据封装为服务。AJAX支持REST方式,它可以使用XMLHttpRequest对象。无状态CRUD操作(创建、读、更新和删除)更加适合这种方式。
GET – represent() POST – acceptRepresention() PUT – storeRepresention() DELETE – removeRepresention()
-
无法缓存SOAP方式读取的内容。而REST方式的则可以,而且性能和可扩展性都更好一些。 SOAP WS支持SSL和WS-security,针对企业级应用可以有更多的安全保障,例如按需提升安全指数、通过第三方来保证身份认证信息的安全性、除了点到点SSL(point to point SSL)之外,更针对消息的不同部分来提供不同的保密算法等等。而REST只支持点到点SSL。而且无论是不是敏感消息,SSL都会加密整条消息。
-
SOAP对于基于ACID的短寿命事务管理以及基于补偿事务管理的长寿命事务有深入的支持。同时,SOAP也支持分布式事务(译者:在一个分布式环境中涉及到多个资源管理器的事务)的两阶段提交(two-phase commit)方式。而REST由于基于HTTP协议,因此对于事务处理既不兼容ACID方式也不提供分布式事务的两阶段提交方式。
-
即便是要通过SOAP的第三方程序,SOAP通过内置的重试逻辑也可以提供端到端可靠性。REST没有一个标准的消息系统,因而寄希望于客户通过重连去解决通信失败问题。
Q.如何选择采用哪种Web service?SOAP WS还是REST?
A.一般而言,基于REST的Web service的优势在于其简单、性能不错、可扩展性好,并且也支持多种数据格式。而SOAP则适用于安全性和事务处理可靠性方面要求比较高的服务中。
对于这个问题的答案,更多的考虑依据是设计者对功能性和非功能性需求的要求。通过回答下列问题可以帮助你做出选择:
- 所提供的服务会暴露数据或者业务逻辑吗?(如果会暴露数据的话可以选择REST方式,如果会暴露业务逻辑的话可以选择SOAP WS)。客户或者服务提供商需要一个正式的契约(contract)吗?(SOAP可以通过WSDL(Web Service Description Language)提供一个正式契约)
- 需要支持多种数据格式吗?
- 需要进行AJAX调用吗?(REST可以采用XMLHttpRequest来发送AJAX调用)
- 同步调用还是异步调用?
- 有状态调用还是无状态调用?(REST适合无状态CRUD操作)
- 对于安全性的要求?(SOAP WS对于安全性的支持更好些)
- 对于事务处理的要求?(SOAP WS这方面更有优势)
- 有带宽限制吗?(SOAP消息比较冗长)
- 哪种方式更适合开发者呢呢?(REST更好实现,也更好测试和维护)
Q.有什么可以用来测试Web Service的工具吗?
A.测试SOAP WS可以使用SoapUI,测试RESTFul service可以采用Firefox的“poster”插件。
Q. SOA和Web service的区别是什么?
A. SOA是一种软件设计准则,一种实现松耦合,高可复用性和粗粒度的web服务的设计模式。开发者可以选择任意协议实现SOA,例如,HTTP、HTTPS、JMS、SMTP、RMI、IIOP(例如,采用IIOP的EJB)、RPC等。消息可以采用XML或者数据传输对象(Data Transfer Objects,DTOs)。
Web Service是实现SOA的技术之一。也可以不用Web service来实现SOA应用:例如,用一些传统的技术,像Java RMI,EJB,JMS消息等。但是Web service提供的是标准的平台无关的服务,这些服务采用HTTP、XML、SOAP、WSDL和UDDI技术,因此可以带来J2EE和.NET这些异构技术(heterogeneous technologies)之间的互操作性。
Q.如果可以使用传统的中间件方式,例如,RPC、CORBA、RMI和DCOM,为什么还要选择Web service?
A. 传统的中间件跟应用关系紧密,对应用的任何修改都可能会引起对应中间件的修改。因此这种方式下应用不好维护,复用性也比较差。一般情况下也无法支持异质系统(heterogeneity)。另外,采用这种方式应尽量避免互联网访问应用。还有,这种方式代价更高并且可用性差。
Web service采用松耦合连接,即在客户端和服务器端之间提供了一个抽象层。这种松耦合应用可以减少维护成本并增加可复用性。Web service提供了一种基于XML和Web的新的中间件形式。Web service是语言和平台无关的,任何语言都可以用来开发一个Web service,并且部署到任何平台上,包括小型设备到大型超级计算机。Web service采用语言无关协议,例如HTTP,并且通过Web API来在不同的应用程序之间传输XML消息。Web service可以通过互联网访问,开销少并且可用性好。
Q.开发基于SOAP的Web service有哪些方式呢?
A.有两种方式;
- 契约先行(也称为自顶向下,contract-first)方式:通过XSD和WSDL来定义contract,然后根据contract生成Java类;
- 契约后行(也称为自底向上,contract-last)方式:先定义Java类,然后生成约定,也就是从Java类得到WSDL文件。 注意:WSDL描述这样一些信息:服务所提供的所有用户操作、终端位置信息(例如,调用服务的URL),请求和响应中的简单或者复杂元素等。
Q. 上面两种方式各有什么优缺点吗?你更推荐哪种?
A.
契约先行方式的Web service
优点:
- 客户端程序和服务器端程序分离,因此重构服务器端代码不会影响到客户端。
- 由于遵守相同的规范,因此客户端和服务器端的开发可以并行进行。
- 开发者可以控制请求\响应消息的结构:例如,“status”应该是作为消息的一个元素还是一个属性?契约规定的非常明确。因此开发者可以大胆的去修改OXM(Object to XML Mapping)库,而不用担心是否会导致“status”从元素变成“属性”。不仅如此,甚至Web service框架和工具箱都可以更换,比如从Apache Axis变成Apache CXF等等。
缺点:
- 开发前期需要额外的一些搭建XSD和WSDL的工作。使用XML Spy、OxygenXM等工具可以简化这些工作。另外,还需要开发对象模型。
- 开发者需要除了Java之外,还需要去学习XSD和WSDL。
契约后行方式的Web service
优点:
- 开发者不用去学习XSD、WSDL和SOAP相关知识。可以通过框架或者工具集利用已有服务来快速构建新的服务。例如,通过基于IDE向导快速构建应用。
- 学习曲线和开发时间会比契约先行方式小。
缺点:
- 项目初始的开发时间会缩短,但是一旦contract发生变化或者需要加入新的元素,那么随之而来的维护和扩展应用所带来的开发时间会是怎样呢?采用这种方式,由于客户端和服务器端之间紧密耦合,因此未来潜在的变化可能破坏客户端的contract,导致所有的客户都会受到影响,为了避免这中情况的发生,项目开发时需要很小心的开发和管理未来要发布的服务。
- XML的有效负载(payload)无法控制。这也就是说修改应用的OXM库会导致某个元素错误的变成了属性。
那么,你会选择哪一种方式呢?
最好的方式是“契约先行”(contract-first),这里有篇文章(ontract-first versus contract-last web services )用一些例子来解释为什么。总的来说,契约先行方式比契约后行方式开发出来的应用更加强壮。当然,选择哪种方式是跟需求和工具等等有关的,需要综合这些因素考虑决定。
学会定制MapReduce里的partition,sort和grouping,Secondary Sort Made Easy_HadoopChina_新浪博客
通过初期的几个开发员培训班,我发现有不少学员容易“偏爱”缺省的MapReduce行为,而忽略如何在代码里根据自己应用的需要来定制不同于系统缺省的行为。这篇文章结合Secondary Sort来介绍“Shuffle & Sort”里涉及到的三个重要操作。
缺省情况下,MapReduce Framework的Shuffle & Sort过程将所有和某一个键相关联的值“组合”(group)在一起,传送到一个唯一确定的Reducer,而且传送到每个Reducer的键是“排序”的(sort)。这对应到三个操作:1)“组合”; 2)“排序”; 和 3)partition(确定哪个键及其值的组合送到哪个Reducer)。
这三个操作涉及到最基本的MapRedcue工作原理,好理解。但是初学者容易忽略的地方是他们很容易记住这三个操作的缺省行为,却不清楚其实其中每一个的行为都是可以在代码里进行定制的。所以,下面首先介绍如何控制这三个操作行为:
- 定义partitioner来告诉MapReduce framework如何将见到的键和值送到哪个Reducer
- 定义key comparator来告诉MapReduce framework如何排序键
- 定义grouping comparator来告诉MapReduce framework如何控制“组合”键值在一起
MapReduce里面的二次排序、组排序和Partitioner - FacingTheSunCN的专栏 - 博客频道 - CSDN.NET
在MapReduce程序中,我们常常需要对属于同一个key的value进行排序,即“二次排序”,将key和value进行组合,合并成一个新的key,给map去排序。在Hadoop 1.0.4中,利用setSortComparatorClass()对二次排序进行设定,但是sort comparator需要自己实现一个comparator,下面是一个自己实现的comparator的例子。
- public static class SortComparator extends WritableComparator {
- protected SortComparator() {
- super(Text.class, true);
- // TODO Auto-generated constructor stub
- }
- @Override
- public int compare(WritableComparable a, WritableComparable b) {
- // TODO Auto-generated method stub
- String[] strs_a = ((Text) a).toString().split(":");
- String[] strs_b = ((Text) b).toString().split(":");
- if ((strs_a.length != 3) || (strs_b.length != 3)) {
- log.error("Error: dimension error 1 in SortComparator!");
- System.exit(1);
- }
- if (Integer.parseInt(strs_a[0]) > Integer.parseInt(strs_b[0])) {
- return 1;
- } else if (Integer.parseInt(strs_a[0]) < Integer
- .parseInt(strs_b[0])) {
- return -1;
- } else {
- if (Double.parseDouble(strs_a[1]) > Double
- .parseDouble(strs_b[1])) {
- return 1;
- } else {
- return -1;
- }
- }
- }
- }
然后,在job中设置
- job.setSortComparatorClass(SortComparator)
由于我们使用了“二次排序”,因此现在的key是被合并过的key(上面说过,是将key与value合并成新的key),所以我们需要定义组比较器(grouping comparator),它的功能是在reducer中为我们需要的相同的key(即合并之前的key)送入到同一个reduce中(官方文档中的描述是“Define the comparator that controls which keys are grouped together for a single call to Reducer.reduce(Object, Iterable, org.apache.hadoop.mapreduce.Reducer.Context)”)。下面是一个grouping comparator的例子。
- public static class GroupComparator extends WritableComparator {
- protected GroupComparator() {
- super(Text.class, true);
- // TODO Auto-generated constructor stub
- }
- @Override
- public int compare(WritableComparable a, WritableComparable b) {
- // TODO Auto-generated method stub
- String[] strs_a = ((Text) a).toString().split(":");
- String[] strs_b = ((Text) b).toString().split(":");
- if ((strs_a.length != 3) || (strs_b.length != 3)) {
- log.error("Error: dimension error 1 in GroupComparator!");
- System.exit(1);
- }
- String new_key_a = strs_a[0] + strs_a[2];
- String new_key_b = strs_b[0] + strs_b[2];
- if (new_key_a.compareTo(new_key_b) == 0) {
- return 0;
- } else if (new_key_a.compareTo(new_key_b) > 0) {
- return 1;
- } else {
- return -1;
- }
- }
- }
然后,在job中设置
- job.setGroupingComparatorClass(GroupComparator.class);
此外,由于我们实际的key与我们所需要的key是不一样的,因此我们需要自己定义一个partitioner,以“欺骗”reducer,将我们所需的相同的key传到同一个reducer,下面是一个partitioner的例子。
- public static class Patitioner extends
- HashPartitioner<Text, IntWritable> {
- @Override
- public int getPartition(Text key, IntWritable value, int numReduceTasks) {
- // TODO Auto-generated method stub
- String[] new_key = key.toString().split(":");
- if (new_key.length != 3) {
- log.error("Error: dimension error in partitioner!");
- System.exit(1);
- }
- return super.getPartition(new Text(new_key[0]), value,
- numReduceTasks);
- }
- }
然后,在job中设置
- job.setPartitionerClass(Patitioner.class);
Partitioner和GroupingComparator有点饶人,功能好像重复了。
- Partitioner是将相同的key(用户虚拟的key)传到同一个reducer(到了reducer中,reducer只认map中实际输出的key,实际key中哪一部分作为key用一个单独的reduce来处理就是GroupingComparator的功能)
- GroupingComparator是让reducer用一个单独的reduce来处理同一个key
- Partitioner中的key和GroupingComparator中的key是可以不一样的(例如我的例子中)
mapreduce编程(二)- 大象书中求每一年的最高温度 - - 博客频道 - CSDN.NET
1 通过设置了partitioner来进行分区。因为分区是按照年份来进行,所以同年的数据就可以分区到一个reducer中。
2 自定义key比较器,按照年份升序,温度值降序。这样map输出的所有kv对就是按照年份升序,温度值降序排列的。
3 自定义分组比较器,所有同一年的数据属于同一个组,那么在reduce输出的时候,只需要取第一个value就能达到输出一年最高气温的目的。
Hadoop 中的两表join | Alex的个人Blog
Common Join
最为普通的join策略,不受数据量的大小影响,也可以叫做reduce side join ,最没效率的一种join 方式. 它由一个mapreduce job 完成.
首先将大表和小表分别进行map 操作, 在map shuffle 的阶段每一个map output key 变成了table_name_tag_prefix + join_column_value , 但是在进行partition 的时候它仍然只使用join_column_value 进行hash.
每一个reduce 接受所有的map 传过来的split , 在reducce 的shuffle 阶段,它将map output key 前面的table_name_tag_prefix 给舍弃掉进行比较. 因为reduce 的个数可以由小表的大小进行决定,所以对于每一个节点的reduce 一定可以将小表的split 放入内存变成hashtable. 然后将大表的每一条记录进行一条一条的比较.
Map Join
Map Join 的计算步骤分两步,将小表的数据变成hashtable广播到所有的map 端,将大表的数据进行合理的切分,然后在map 阶段的时候用大表的数据一行一行的去探测(probe) 小表的hashtable. 如果join key 相等,就写入HDFS.
map join 之所以叫做map join 是因为它所有的工作都在map 端进行计算.
10个SCRUM最佳实践
下面是对Scrum的一些最佳做法的列表:
- 燃尽图可以用来监测冲刺的状态。 用图形跟踪计划比用表格更好。
- 计划扑克是一种有用的评估完成Sprint 任务项花费时间的方法。 使用斐波那契数列做为规划扑克数字是一个很好的做法。
- ROI(投资回报率)可用于确定Sprint任务项目的优先级。 计划扑克也可以用来确定的ROI值。
- 使用任务板和简单的计划/报告工具(例如Excel, SPR intometer , projectsimple )跟踪过程和质量,这已经足够了。
- Scrum方法不对每件事做文档,但是,这并不意味着“没有文档”。 根据需要,必要的文档是可以做的。
- 每日早会的长度不得超过15分钟。 Scrum是一种灵活的方法,没有人需要听取其他成员问题的详细信息。 这些细节可能只需要团队成员的子集与Scrum Master在每日会议后进行讨论。
- 每日会议应该站立进行,为了尽量使会议时间短。 会议地点和时间建议每天都相同。
- 产品的需求 Backlog 可能包含不会开发的项。 根据ROI值,某些Backlog 项可能不被开发,这是正常的。 产品需求 Backlog,应该包含所有可能的需求项。为了 管理简单并给它们编号。
- Sprint 迭代的长度(以周为单位),不建议中途更改。 但根据冲刺回顾会议结果,如果有很重要的原因冲刺周期长度可以改变。
- 每天6小时,是一个现实的计划输入。 冲刺小时的总量,可以计算为:(团队成员)*(冲刺天数)* 6小时
原文:http://agile.dzone.com/articles/10-scrum-methodology-best
HBase MapReduce实例分析 - 新城主力唱好 - 博客园
跟Hadoop的无缝集成使得使用MapReduce对HBase的数据进行分布式计算非常方便,本文将介绍HBase下 MapReduce开发要点。很好理解本文前提是你对Hadoop MapReduce有一定的了解,如果你是初次接触Hadoop MapReduce编程,可以参考 "第一个MapReduce应用" 这篇文章来建立基本概念。
Ken Wu's Blog » HBase性能调优
因官方Book Performance Tuning部分章节没有按配置项进行索引,不能达到快速查阅的效果。所以我以配置项驱动,重新整理了原文,并补充一些自己的理解,如有错误,欢迎指正。
配置优化
zookeeper.session.timeout
默认值:3分钟(180000ms)
说明:RegionServer与Zookeeper间的连接超时时间。当超时时间到后,ReigonServer会被Zookeeper从RS集群清单中移除,HMaster收到移除通知后,会对这台server负责的regions重新balance,让其他存活的RegionServer接管.
调优:
这个timeout决定了RegionServer是否能够及时的failover。设置成1分钟或更低,可以减少因等待超时而被延长的failover时间。
不过需要注意的是,对于一些Online应用,RegionServer从宕机到恢复时间本身就很短的(网络闪断,crash等故障,运维可快速介入),如果调低timeout时间,反而会得不偿失。因为当ReigonServer被正式从RS集群中移除时,HMaster就开始做balance了(让其他RS根据故障机器记录的WAL日志进行恢复)。当故障的RS在人工介入恢复后,这个balance动作是毫无意义的,反而会使负载不均匀,给RS带来更多负担。特别是那些固定分配regions的场景。
hbase.regionserver.handler.count
默认值:10
说明:RegionServer的请求处理IO线程数。
调优:
这个参数的调优与内存息息相关。
较少的IO线程,适用于处理单次请求内存消耗较高的Big PUT场景(大容量单次PUT或设置了较大cache的scan,均属于Big PUT)或ReigonServer的内存比较紧张的场景。
较多的IO线程,适用于单次请求内存消耗低,TPS要求非常高的场景。设置该值的时候,以监控内存为主要参考。
这里需要注意的是如果server的region数量很少,大量的请求都落在一个region上,因快速充满memstore触发flush导致的读写锁会影响全局TPS,不是IO线程数越高越好。
压测时,开启Enabling RPC-level logging,可以同时监控每次请求的内存消耗和GC的状况,最后通过多次压测结果来合理调节IO线程数。
这里是一个案例?Hadoop and HBase Optimization for Read Intensive Search Applications,作者在SSD的机器上设置IO线程数为100,仅供参考。
hbase ( key 设计 ) 条件查询排序分页
Paging is a very common use-case for web sites and many other applications. In relational databases, this is easily implemented with LIMIT and OFFSET, or by selecting the row number in the query and adding conditionals based on it’s value. HBase 0.19.x, on the other hand, does not provide any queries or filters that support paging directly. After a quick example using SQL, I will show how to implement the same functionality in HBase.
Let’s assume that we have a large number of users. Each user has performed a number of actions. Each action has a unique identifier, a timestamp, and a name.
This is how you might get the third page of an individual users’ actions using SQL:
SELECT id, name, stamp FROM actions WHERE userid = 1 ORDER BY stamp DESC LIMIT 10 OFFSET 20;
This utilizes secondary indexes on both userid and stamp, meaning to accomplish this query you need at least three indexes on this table as id is the primary key. Though a simple query to write, you will run into problems as the actions table grows to millions of rows and beyond. Insertions would look like:
INSERT INTO actions (id, userid, name, stamp) VALUES (newid(), 1, 'Joe User', epoch());
HBase has no real indexes. Rows are stored in sorted order, and columns in a family are sorted. For more information, read the HBase Architecture page on the HBase Wiki.
Very conscious of the primary queries we will run on user-actions, we will design an HBase table to support paging queries on per-user, time-ordered lists of actions.
We will use the Java Client API for HBase, specifically the HTable class. What we are looking for are two methods:
public static List<Action> getUserActions(int userid, int offset, int limit) public static void putUserAction(Action action)
Please note, I am using a custom object, Action, for simplicity. It is a client-side holder for the four action fields (id, userid, name, stamp).
There are a number of ways to store your data in HBase that will allow the getUserActions query, but in this case we will go with a very tall table design (lots of rows with few columns in them) rather thanwide (lots of columns in each row). Specifically, the difference here would be whether you have a row-per-action or a row-per-user. We will do a row-per-action, but will be designing our row key (the primary key) to be a composite key to allow for grouping and sorting of actions, rather than just the action id. This means we will not have random-access to an action by it’s id, so rather than defining this as the actions table (which might also exist if you needed actionid random access) we will define it as the useractions table, and we will only store a single column in a single family,content:name.
The row key that we will use in our HBase useractions table is:
<userid><reverse_order_stamp><actionid>
It’s very important that each of these fields is fixed-length and binary so that the lexicographical/ascending byte-ordering of HBase will properly sort our rows.
The userid field will be a 4 byte, big endian integer. reverse_order_stamp is an 8 byte, big endian long with a value of (Long.MAX_VALUE - epoch). This is so the most recent stamp is at the top rather than the bottom. actionid is another 4 byte, big endian integer. Thankfully, HBase provides helpful utilties in the org.apache.hadoop.hbase.util.Bytes class to deal with this (unfortunately it lacked some key features in 0.19, so the code below makes use of the Bytes class available in 0.20/TRUNK). Before we get into HBase code, let’s define the helper methods makeActionRow and readActionRow to deal with the composite key:
public static byte [] makeActionRow(int userid, long stamp, int actionid)
throws Exception {
byte [] useridBytes = Bytes.toBytes(userid);
byte [] stampBytes = Bytes.toBytes(stamp);
byte [] actionidBytes = Bytes.toBytes(actionid);
return Bytes.add(useridBytes, stampBytes, actionidBytes);
}
public static Action readActionRow(byte [] row)
throws Exception {
// Bytes.toInt(byte [] buf, int offset, int length)
int userid = Bytes.toInt(row,0,4);
long stamp = Long.MAX_VALUE - Bytes.toLong(row,4,8);
int actionid = Bytes.toInt(row,12,4);
return new Action(userid,stamp,actionid);
}
Now that we can deal with the composite keys, insertion is very straightforward:
public static void putUserAction(Action action) throws Exception {
// Get the fields from the Action object
int userid = action.getUserID();
long stamp = Long.MAX_VALUE - action.getStamp();
int actionid = action.getID();
String name = action.getName();
// Build the composite row, column, and value
byte [] row = makeActionRow(userid,stamp,actionid);
byte [] column = Bytes.toBytes("content:name");
byte [] value = Bytes.toBytes(name);
// Insert to HBase
HTable ht = new HTable("useractions");
BatchUpdate bu = new BatchUpdate(row);
bu.put(column,value)
ht.commit(bu);
}
We just serialize the fields into the composite row, and write the single column to HBase in a BatchUpdate. Reading will deal with unserializing the fields and Scanners. In addition to matching for the content:name column, we will also specify a startRow and stopRow so that the Scanner only returns results from the user we are looking at. This way we do not have to worry about jumping to the next user in our code, the Scanner will just stop.
public static List<Action> getUserActions(int userid, int offset, int limit)
throws Exception {
// Initialize counter and List to return
int count = 0;
List<Action> actions = new ArrayList<Action>(limit);
// Initialize startRow, stopRow, and columns to match
byte [] startRow = makeActionRow(userid,0,0);
byte [] stopRow = makeActionRow(userid,Long.MAX_VALUE,Integer.MAX_VALUE);
byte [][] columns = {Bytes.toBytes("content:name")};
// Open Scanner
HTable ht = new HTable("useractions");
Scanner s = ht.getScanner(columns,startRow,stopRow);
RowResult res = null;
// Iterate over Scanner
while((res = s.next()) != null) {
// Check if past offset
if(++count <= offset) continue;
// Get data from RowResult
byte [] row = res.getRow();
byte [] value = res.get(columns[0]).getValue();
// Build Action
Action action = readActionRow(row);
String name = Bytes.toString(value);
action.setName(name);
actions.add(action);
// Check limit
if(count == offset + limit) break;
}
// Cleanup and return
s.close();
return actions;
}
The storage of your data must be tied to how you need to query it. Without a sophisticated query engine or indexing capabilities, you must design to take advantage of sorted rows and columns, potentially designing a table per query type. Denormalization is okay!
Ken Wu's Blog » HBase二级索引与Join
1,按索引建表
每一个索引建立一个表,然后依靠表的row key来实现范围检索。row key在HBase中是以B+ tree结构化有序存储的,所以scan起来会比较效率。
单表以row key存储索引,column value存储id值或其他数据 ,这就是Hbase索引表的结构。
如何Join?
多索引(多表)的join场景中,主要有两种参考方案:
1,按索引的种类扫描各自独立的单索引表,最后将扫描结果merge。
这个方案的特点是简单,但是如果多个索引扫描结果数据量比较大的话,merge就会遇到瓶颈。
比如,现在有一张1亿的用户信息表,建有出生地和年龄两个索引,我想得到一个条件是在杭州出生,年龄为20岁的按用户id正序排列前10个的用户列表。
有一种方案是,系统先扫描出生地为杭州的索引,得到一个用户id结果集,这个集合的规模假设是10万。
然后扫描年龄,规模是5万,最后merge这些用户id,去重,排序得到结果。
这明显有问题,如何改良?
保证出生地和年龄的结果是排过序的,可以减少merge的数据量?但Hbase是按row key排序,value是不能排序的。
变通一下 – 将用户id冗余到row key里?OK,这是一种解决方案了,这个方案的图示如下:

merge时提取交集就是所需要的列表,顺序是靠索引增加了_id,以字典序保证的。
2, 按索引查询种类建立组合索引。
在方案1的场景中,想象一下,如果单索引数量多达10个会怎么样?10个索引,就要merge 10次,性能可想而知。

解决这个问题需要参考RDBMS的组合索引实现。
比如出生地和年龄需要同时查询,此时如果建立一个出生地和年龄的组合索引,查询时效率会高出merge很多。
当然,这个索引也需要冗余用户id,目的是让结果自然有序。结构图示如下:

这个方案的优点是查询速度非常快,根据查询条件,只需要到一张表中检索即可得到结果list。缺点是如果有多个索引,就要建立多个与查询条件一一对应的组合索引,存储压力会增大。
在制定Schema设计方案时,设计人员需要充分考虑场景的特点,结合方案一和二来使用。下面是一个简单的对比:
| 单索引 | 组合索引 | |
| 检索性能 | 优异 | 优异 |
| 存储 | 数据不冗余,节省存储。 | 数据冗余,存储比较浪费。 |
| 事务性 | 多个索引保证事务性比较困难。 | 多个索引保证事务性比较困难。 |
| join | 性能较差 | 性能优异 |
| count,sum,avg,etc | 符合条件的结果集全表扫描 | 符合条件的结果集全表扫描 |
从上表中可以得知,方案1,2都存在更新时事务性保证比较困难的问题。如果业务系统可以接受最终一致性的话,事务性会稍微好做一些。否则只能借助于复杂的分布式事务,比如JTA,Chubby等技术。
count, sum, avg, max, min等聚合功能,Hbase只能通过硬扫的方式,并且很悲剧,你可能需要做一些hack操作(比如加一个CF,value为null),否则你在扫描时可能需要往客户端传回所有数据。
当然你可以在这个场景上做一些优化,比如增加状态表等,但复杂性带来的风险会更高。
还有一种终极解决方案就是在业务上只提供上一页和下一页,这或许是最简单有效的方案了。
HBase存储时间相关多列数据的两种方案 - 大圆那些事 - 博客园
多行单列
表结构设计
Row Key:用户标识ID + (Long.MAX_VALUE - timestamp)
Column Family:’cf’
Column Qualifier:’’
Value:宝贝、URL等
其中,使用(Long.MAX_VALUE – timestamp)作为Row Key的后半部分是为了便于获取最近插入的数据,一个用户标识ID下的数据存储在多个Row Key下,每个Row Key下仅有一个Column Qualifier,表示该用户的一次时间相关的访问数据(访问宝贝、URL等)。
查询方式
1)查询某个特定timestamp下的记录,则使用用户标识ID + (Long.MAX_VALUE - timestamp)进行Get查找;
2)查询某个用户标识ID下所有的记录,则通过Scan.setStartRow(uid)和Scan.setStopRow(uid+1)进行Scan查找;
3)查询某个用户标识ID下最近时间内的N条记录,则通过Scan.setStartRow(uid)和Scan.setStopRow(uid+1)进行Scan查找,但是为了只获取N条记录,可以设置Scan.setCaching(N)优化查询,同时做一次ResultScanner.next(N)得到结果。
单行多列
表结构设计
Row Key:用户标识ID
Column Family:’cf’
Column Qualifier:(Long.MAX_VALUE - timestamp)
Value:宝贝、URL等
其中,使用(Long.MAX_VALUE – timestamp)作为Column Qualifier是为了便于获取最近插入的数据,一个用户标识ID下的数据存储在一个Row Key下,每个Row Key下会有多个Column Qualifier,表示该用户的所有时间相关的访问数据(访问宝贝、URL等)。
查询方式
1)查询某个特定timestamp下的记录,则使用用户标识ID进行Get查找,同时通过Get.addColumn(‘cf’, (Long.MAX_VALUE – timestamp))方法限定要查询的Column Qualifier;
2)查询某个用户标识ID下所有的记录,则直接使用用户标识ID进行Get查找,通过Get.addFamily(‘cf’)方法添加整个Column Family;
3)查询某个用户标识ID下最近时间内的N条记录,则直接使用用户标识ID进行Get查找,通过Get.addFamily(‘cf’)方法添加整个Column Family,通过ColumnCountGetFilter(int N)限制最多要查询返回N条记录。
如何理解Hadoop-Hbase原理与应用小结 - leonarding技术博客 - ITPUB个人空间 - powered by X-Space
使用X-UA-Compatible来设置IE8兼容模式 - Libra - 博客园
每个主要版本IE新增的功能都是为了让浏览器更容易使用、增加安全性及更支持业界标准。以这些作为IE的特色,其中一个风险就是旧版本网站无法正确的显示。
为了将这个风险降到最低,IE6允许网页开发人员选择IE编译和显示他们网页的方式。"Quirks mode"为预设,这会使页面以旧版本浏览器的视点显示,"Standards mode"(也称为"strict mode")特点是支持业界标准最为完善。然而要利用这个增强的支持功能,网页必须包含恰当的<!DOCTYPE>指令。
若一个网页没有包含<!DOCTYPE>指令,IE6会将它以quirks mode显示。若网页包含有效的<!DOCTYPE>指令但浏览器无法辨识,IE6会将它以IE6 standards mode显示。因为少数网站已经包含<!DOCTYPE>指令,兼容性模式的切换相当成功。这使网页开发人员能选择将他们的网页转移为standards mode的最佳时机。
随著时间经过,更多网站开始使用standards mode。它们也开始使用IE6的特性和功能来检测IE。举例来说,IE6不支持universal selector(即css之全局选择器 * {}),一些网站便使用它来针对IE做特定的对应。
当 IE7增加了对全域选择器的支持,那些依赖IE6特点的网站便无法侦测出这个新版本的浏览器。因此那些针对IE的特定对应无法应用于IE7,造成这些网站便无法如他们预期的显示。由于<!DOCTYPE>只支持两种兼容性模式,受到影响的网站拥有者被迫更新他们的网站使其能支持IE7。
IE8 比之前的任何版本浏览器都更支持业界标准,因此针对旧版本浏览器设计的网页可能无法如预期般呈现。为了帮助减轻所有问题,IE8引入文件兼容性的概念,使你能选择你的网页设计要对应的特定IE版本。文件兼容性在IE8增加了一些新的模式,这些模式能告诉浏览器如何解析和编译一个网页。若你的网页无法在 ie8正确的显示,你可以更新你的网站使它支持最新的网页标准(优先选项)或在你的页面上新增一个meta元素用于告诉IE8如何依照旧版本浏览器编译你的页面。
这能让你选择将你的网站更新支持IE8新特点的时机。
认识文件兼容性模式
IE8支持几种文件兼容性模式,它们具有不同的特性并影响内容显示的方式。
•Emulate IE8 mode指示IE使用<!DOCTYPE>指令来决定如何编译内容。Standards mode指令会显示成IE8 Standards mode而quirks mode会显示成IE5 mode。不同于IE8 mode,Emulate IE8 mode重视<!DOCTYPE>指令。
•Emulate IE7 mode指示IE使用<!DOCTYPE>指令来决定如何编译内容。Standards mode指令会显示成IE7 Standards mode而quirks mode会显示成IE5 mode。不同于IE7 mode,Emulate IE7 mode重视<!DOCTYPE>指令。对于许多网页来说这是最推荐的兼容性模式。
•IE5 mode 编译内容如同IE7的quirks mode之显示状况,和IE5中显示的非常类似。
•IE7 mode编译内容如同IE7的standards mode之显示状况,无论网页是否含有<!DOCTYPE>指令。
•IE8 mode提供对业界标准的最高支持,包含 W3C Cascading Style Sheets Level 2.1 Specification和W3C Selectors API,并有限的支持 W3C Cascading Style Sheets Level 3 Specification (Working Draft)。
•Edge mode指示IE以目前可用的最高模式显示内容。当使用IE8时其等同于IE8 mode。若(假定)未来放出支持更高兼容性模式的IE,使用Edge mode的页面会使用该版本能支持的最高模式来显示内容。同样的那些页面在使用IE8浏览时仍会照常显示。
由于edge mode使用该IE版本所能支持的最高模式来显示所浏览的网页内容,建议仅使用于测试页及其他非商用页面。
直接拿来用!最火的Android开源项目(一)-CSDN.NET
GitHub在中国的火爆程度无需多言,越来越多的开源项目迁移到GitHub平台上。更何况,基于不要重复造轮子的原则,了解当下比较流行的Android与iOS开源项目很是必要。利用这些项目,有时能够让你达到事半功倍的效果。为此,CSDN特整理了在GitHub平台上最受欢迎的Android及iOS开源项目,以飨开发者。
下面,就让我们一起来看看,在GitHub平台上,究竟有哪些Android开源项目最火,也最受开发者欢迎。
ActionBarSherlock应该算得上是GitHub上最火的Android开源项目了,它是一个独立的库,通过一个API和主题,开发者就可以很方便地使用所有版本的Android动作栏的设计模式。
Hibernate 的 10 个常见面试问题及答案 - 博客 - 伯乐在线
Hibernate中get和load有什么不同之处? 把get和load放到一起进行对比是Hibernate面试时最常问到的问题,这是因为只有正确理解get()和load()这二者后才有可能高效地使用Hibernate。get和load的最大区别是,如果在缓存中没有找到相应的对象,get将会直接访问数据库并返回一个完全初始化好的对象,而这个过程有可能会涉及到多个数据库调用;而load方法在缓存中没有发现对象的情况下,只会返回一个代理对象,只有在对象getId()之外的其它方法被调用时才会真正去访问数据库,这样就能在某些情况下大幅度提高性能。你也可以参考 Hibernate中get和load的不同之处, 此链接给出了更多的不同之处并对该问题进行了更细致的讨论。
Hibernate中save、persist和saveOrUpdate这三个方法的不同之处? 除了get和load,这又是另外一个经常出现的Hibernate面试问题。 所有这三个方法,也就是save()、saveOrUpdate()和persist()都是用于将对象保存到数据库中的方法,但其中有些细微的差别。例如,save()只能INSERT记录,但是saveOrUpdate()可以进行 记录的INSERT和UPDATE。还有,save()的返回值是一个Serializable对象,而persist()方法返回值为void。你还可以访问 save、persist以及saveOrUpdate,找到它们所有的不同之处。
Hibernate中的命名SQL查询指的是什么? Hibernate的这个面试问题同Hibernate提供的查询功能相关。命名查询指的是用<sql-query>标签在影射文档中定义的SQL查询,可以通过使用Session.getNamedQuery()方法对它进行调用。命名查询使你可以使用你所指定的一个名字拿到某个特定的查询。 Hibernate中的命名查询可以使用注解来定义,也可以使用我前面提到的xml影射问句来定义。在Hibernate中,@NameQuery用来定义单个的命名查询,@NameQueries用来定义多个命名查询。
Hibernate中的SessionFactory有什么作用? SessionFactory是线程安全的吗? 这也是Hibernate框架的常见面试问题。顾名思义,SessionFactory就是一个用于创建Hibernate的Session对象的工厂。SessionFactory通常是在应用启动时创建好的,应用程序中的代码用它来获得Session对象。作为一个单个的数据存储,它也是 线程安全的,所以多个线程可同时使用同一个SessionFactory。Java JEE应用一般只有一个SessionFactory,服务于客户请求的各线程都通过这个工厂来获得Hibernate的Session实例,这也是为什么SessionFactory接口的实现必须是线程安全的原因。还有,SessionFactory的内部状态包含着同对象关系影射有关的所有元数据,它是 不可变的,一旦创建好后就不能对其进行修改了。
Hibernate中的Session指的是什么? 可否将单个的Session在多个线程间进行共享? 前面的问题问完之后,通常就会接着再问这两个问题。问完SessionFactory的问题后就该轮到Session了。Session代表着Hibernate所做的一小部分工作,它负责维护者同数据库的链接而且 不是线程安全的,也就是说,Hibernage中的Session不能在多个线程间进行共享。虽然Session会以主动滞后的方式获得数据库连接,但是Session最好还是在用完之后立即将其关闭。
hibernate中sorted collection和ordered collection有什么不同? T这个是你会碰到的所有Hibernate面试问题中比较容易的问题。sorted collection是通过使用 Java的Comparator在内存中进行排序的,ordered collection中的排序用的是数据库的order by子句。对于比较大的数据集,为了避免在内存中对它们进行排序而出现 Java中的OutOfMemoryError,最好使用ordered collection。
Hibernate中transient、persistent、detached对象三者之间有什么区别? 在Hibernate中,对象具有三种状态:transient、persistent和detached。同Hibernate的session有关联的对象是persistent对象。对这种对象进行的所有修改都会按照事先设定的刷新策略,反映到数据库之中,也即,可以在对象的任何一个属性发生改变时自动刷新,也可以通过调用Session.flush()方法显式地进行刷新。如果一个对象原来同Session有关联关系,但当下却没有关联关系了,这样的对象就是detached的对象。你可以通过调用任意一个session的update()或者saveOrUpdate()方法,重新将该detached对象同相应的seesion建立关联关系。Transient对象指的是新建的持久化类的实例,它还从未同Hibernate的任何Session有过关联关系。同样的,你可以调用persist()或者save()方法,将transient对象变成persistent对象。可要记住,这里所说的transient指的可不是 Java中的transient关键字,二者风马牛不相及。
Hibernate中Session的lock()方法有什么作用? 这是一个比较棘手的Hibernate面试问题,因为Session的lock()方法重建了关联关系却并没有同数据库进行同步和更新。因此,你在使用lock()方法时一定要多加小心。顺便说一下,在进行关联关系重建时,你可以随时使用Session的update()方法同数据库进行同步。有时这个问题也可以这么来问:Session的lock()方法和update()方法之间有什么区别?。这个小节中的关键点也可以拿来回答这个问题。
Hibernate中二级缓存指的是什么? 这是同Hibernate的缓存机制相关的第一个面试问题,不出意外后面还会有更多这方面的问题。二级缓存是在SessionFactory这个级别维护的缓存,它能够通过节省几番数据库调用往返来提高性能。还有一点值得注意,二级缓存是针对整个应用而不是某个特定的session的。
Hibernate中的查询缓存指的是什么? 这个问题有时是作为上个Hibernate面试问题的后继问题提出的。查询缓存实际上保存的是sql查询的结果,这样再进行相同的sql查询就可以之间从缓存中拿到结果了。为了改善性能,查询缓存可以同二级缓存一起来使用。Hibernate支持用多种不同的开源缓存方案,比如EhCache,来实现查询缓存。
为什么在Hibernate的实体类中要提供一个无参数的构造器这一点非常重要?
每个Hibernate实体类必须包含一个 无参数的构造器, 这是因为Hibernate框架要使用Reflection API,通过调用Class.newInstance()来创建这些实体类的实例。如果在实体类中找不到无参数的构造器,这个方法就会抛出一个InstantiationException异常。
可不可以将Hibernate的实体类定义为final类?
是的,你可以将Hibernate的实体类定义为final类,但这种做法并不好。因为Hibernate会使用代理模式在延迟关联的情况下提高性能,如果你把实体类定义成final类之后,因为 Java不允许对final类进行扩展,所以Hibernate就无法再使用代理了,如此一来就限制了使用可以提升性能的手段。不过,如果你的持久化类实现了一个接口而且在该接口中声明了所有定义于实体类中的所有public的方法轮到话,你就能够避免出现前面所说的不利后果。

