盛付通储备架构面试总结

上个月底,经朋友介绍,到盛付通做了一次面试,但面的不是很好,现总结一下一些问题。

 

1:关于NIO,实际上,我之前已经看过NIO的东西,并且一直在用,但也就是个一知半解,之前的理解是,通过多个线程的切换来处理同一个Socket连接,这个理解是不对的。

  准确的说,应该是通过注册状态监听,NIO引擎维护这些状态,并通过轮询状态,进行数据处理。

  从TCP/IP的角度来看,应用状态监听与TCP/IP通讯无关,一旦有数据流动,TCP/IP协议栈都会对应用进行通知,底层有线程负责数据发送接收和通知,即TCP/IP协议栈已经处理好了数据的发送接收,无所谓的阻塞和非阻塞,留出的操作选项只是一个状态反馈;

  对于我们应用层的操作来说,无非就是阻塞接收或监听两种方式,具体到JAVA的API上来说,就是read阻塞和Selector注册监听器两种方式,查API可知,read方法由SocketInputStream的native socketRead0方法提供,即JAVA Socket的依赖于JAVA底层实现,而查sun.nio可知JAVA中NIO的实现主要通过线程锁操作实现,但也依赖于底层实现。

  进一步理解,可以认为,JAVA的NIO是通过线程锁来操作阻塞,而JAVA Socket则把阻塞交给你自己来操作;鉴于JDK1.4之后JAVA Socket用NIO重写过,则我们可以理解为read操作是基于底层线程锁操作来实现阻塞,即read/writer阻塞和NIO异步读写都是基于线程锁阻塞操作的。

  那么在此结论的基础之上,不难理解,JAVA底层已经实现了NIO,对于应用层NIO来说,可以做的就是轮询状态进行数据操作。

  阻塞操作和NIO异步读写的差别就在于,阻塞操作时N个连接需要同时分配对应的N个线程,可能造成M(M<=N)个线程等待;而NIO只需要通过一个线程不断的轮询N个Socket的状态按需分配M(M<=N)个线程,且不会造成线程等待。

  理解了底层代码实现的区别之后,回到实际的应用中,NIO通过注册SelectionKey.OP_READ/OP_WRITE/OP_CONNECT/OP_ACCEPT事件来过滤掉不必要的操作,通过事件通知,使用一个线程监视状态,减少了多线程操作耗费的CPU,但需要注意的是,事件触发后可能导致死循环,可能需要在accept之后将监听remove掉。

  例子代码与讨论:

  http://my.oschina.net/leoson/blog/106385

  http://bofang.iteye.com/blog/1672451

  http://www.iteye.com/topic/262231

  http://ifeve.com/overview/

 

2:关于ServiceBus/JMS,这个纯粹是自己表达的问题,当初在设计ServiceBus的时候,主要站在架构和业务的角度考量,该组件并非单纯的消息传递,是以设计类ESB组件为目标的,但其实际上就是个类JMS组件,其中的消息传递是由我自行设计的,事后发现JMS也能满足我的需求,只是报文会膨胀得很大很大,经过考虑,我想下一个版本会将消息传递改为JMS。

 

3:数据库的四个?题目我都忘了,当时也没怎么明白到底是问题什么的,

  五个范式?常用的是三个范式。

    NF1:无重复列,列唯一;

    NF2:属性完全依赖于主键(主属性),一张表只有唯一一个主键;

    NF3:属性不依赖于其他非主属性(非主键,但可为唯一字段),属性只能依赖于主键,不能依赖于另外的字段;2张表通过主外键关联,外键应该依赖于另一张表的主键;

    BCNF:任一属性不能依赖于另外一非主属性。在第三范式的基础上更加严格的限制了字段的选取,由可作为主键的字段(唯一索引)扩展到所有字段。

    NF4:禁止主键列和非主键列一对多的关系不受约束。表内不应该存在多对多的关系,即只允许一对多的主键和非主键的映射关系。如,一个订单被提交,之后被结算,不能用两行数据来表示,应该分解到两张表,一张表保存订单的基本状态,另一张表保存表的每一步的操作状态。

    NF5:尽可能的消除表中的任何冗余。NF4只是解开了主键和非主键列之间的冗余,而NF5要求全不字段都不得冗余。所以该范式是一个不断优化的过程。

   事务的四个基本要素:

    1:原子性,事务中各个操作应该是一个整体性,原子性的部分。

    2:一致性,事务提交后,各个操作的修改前和之后的结果应该全部一致性的成功或失败。

    3:隔离性,事务之间不得互相干扰。

    4:持久性,即事务提交后,应该持久保存。

  事务并发时可能导致的几个问题以及隔壁级别之间的差异,当时简单性的题了一下,似乎对不上题;

    1:脏读:事务A修改了某值,被事务B读出,但恰巧事务A回滚了,事务B此时持有的值即为脏读数据。

    2:不可重复读:事务A读取了某值,之后事务B读取该值并进行了更改,此时事务A/B持有的值是不同的。

    3:幻读:事务A修改某值并提交,之后事务B也修改了该值并提交,事务A提交的值被事务B提交的值覆盖掉了。

    4:串行读:事务A执行时,事务B必须等待事务A执行完毕才可执行,即事务要按顺序执行,而不能同步执行。

    据此划分出四个隔离级别:

    1:未提交读(read uncommitted): 脏读,也就是可能读取到其他会话中未提交事务修改的数据。

    2:提交读(read committed): 只能读取到已经提交的数据,多数数据库(除开MYSQL)默认都是该级别。会有不可重复读的问题。

    3:可重复读(repeated read): 在同一个事务内的查询都是事务开始时刻一致的,InnoDB的默认级别,可能导致幻读,但InnoDB解决了这个问题。

    4:串行读(serializable): 即锁表或锁行,每次读都需要获得表(行)级共享锁,读写相互阻塞。

 

4: 关于系统故障的解决方案.这个问题没回答好,主要是不熟悉这一块,干脆回答不知道了,恰恰这是最关键的一个问题,是作为一个架构师必须要知道的部分。

    1:对于系统故障,公司是有对应的解决方案的。主备、灾备机房,另外,行业内也有对应的规范,双路独立变电站,UPS电池组,应急电源,双路路由交换,DNS冗余,LVS/NGINX集群,这属于运维的范畴,作为架构师是必须要了解并熟悉的。

    2:实际上,针对系统故障的解决的基本手段就是冗余,在不同的系统层级都可以通过冗余多套系统分摊风险和压力。而对于架构师来说,设计出适配冗余的系统架构和应用,是非常重要的。结合到公司目前的架构来说,还做不到无缝拆分架构,甚至于要拆分冗余的话,要做系统性的改动。

    3:冗余通过功能划分,可分为数据备份冗余和架构性能冗余,

      1、数据备份冗余,用于系统崩溃后的挽救措施,主要以数据库和文件备份为主,如数据库主从备份,异地灾备。系统崩溃时,主要工作是恢复数据,而正在进行备份的数据的丢失,只能手工调帐,或基于已有的资料(如访问日志)恢复。这是运维的工作。

      2、架构性能冗余,用于架构规划和运营时压力性能指标的解决方案,主要以应用结构拆分和增量部署为主,如集群,读写分离。系统崩溃时,主要工作则是迅速启动预案(如:备机,备份前置系统),保证业务的连续性和完整性;系统面对压力时,主要工作则是根据架构进行压力分摊,保证业务进程的效率,维持良好的客户体验。这是架构师的工作。而这里架构师需要关注的点,则是在进行结构拆分和增量部署时,不应该对现有业务造成任何负面影响。

       如,目前我们有些系统系统的流水号主要由一个synchronized自增函数生成,并定时2秒将该数字存储到数据库,如果分拆应用,则会直接性的影响现有业务运营。之所以将流水号放到自增函数,而不是通过更新数据库来实现,是想极大的提高性能,避免改应用成为瓶颈。目前我的考虑是,使用NOSQL来解决这类,至于实际效果,还有待验证。

    4:关于系统无缝升级的问题,之前线上系统一直采用,停止->修改配置/更新DB->启动的流程进行升级,现在考虑使用ClassLoader热部署来实现。

 

5:其他的问题我不太记得了,基本上就这几个问题感觉没有回答好,也是自己积累的不多,当然也与前一周整个一周都在忙,加通宵班有关,总之准备不足,状态也不好。

 

6:记录一下平常遇到的一些知识点。

  游标:是在查询之后的结果集中的指针(光标),可以滑动以操作指定行的数据。

  存储过程:是一组预编译SQL的集合,可以认为是自定义的SQL函数,如(left)等。

  触发器:是由事务触发的,不可以在触发器中执行commit/rollback等事务相关语句。

  索引:创建复合索引时,应以差异最大的字段且查询频率高的字段在前,可提高索引效率;而重建索引的意义在于,当数据执行删除后,索引并未随之删除,仅标识为已删除,重建可去掉这些已删除索引的占位,从而提高效率;

      用作索引的字段不允许NULL值,否则不会使用该字段作为索引。

      使用ORDER时,使用的列应该是索引(联合索引),不应该包含非索引列。

      使用索引时应该避免转换,如索引字段是char,不可以xx=2323。

      使用索引,是用来查询是的关系,所以不能使用!=/<>/字符串连接/+(数学运算符)/自身,

      当使用索引可查出的数据达到总数据量的30%之后,索引将没有显著的性能提升。

  事务:事务一般以start transaction或begain开始,以commit/rollback结束。其中savepoint存储点,只能由rollback发起。

  tuncate/delete差异:tuncate清空表,会重置索引和自增主键等一些属性,是DDL语句,没有事务。

  在对SQL进行优化时,可通过慢–log-slow-queries[=file_name]和long_query_time来记录慢查询的SQL;通过show processlist实时观察线程与锁表状态;通过EXPLAIN分析SQL执行的效率和状态;使用GROUP时先过滤掉一些行;尽量使用预编译SQL,尽量赋予初始值,使用定长字段。

  使用EXSIST替代IN/NOT IN/DISTINCT;使用>替代>=、UNION/IN替代OR;避免使用NOT/IS NULL/IS NOT NULL关键字,

 

7:关于SSH/I的一些面试。

  Spring:事务,有编程式事务和声明式事务,编程式事务主要是在代码里,自行开启/提交事务(txManager.getTransaction/txManager.commit)。

      重点一般在声明式事务:

      1:使用org.springframework.transaction.interceptor.TransactionProxyFactoryBean为每个目标接口设定一个transactionManager,事务属性和目标。

      2:使用org.springframework.transaction.interceptor.TransactionProxyFactoryBean为目标的基类设定一个transactionManager和事务属性,继承自该基类的目标自然会被纳入Spring的事务范畴。

      3:使用org.springframework.transaction.interceptor.TransactionInterceptor来声明事务属性,拦截由org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator.beanNames/interceptorNames定义的清单里的目标。

      4:使用tx:advice标签指定transactionManager和事务属性,并使用aop:config/aop:pointcut.expression(execution(* com.spring.service.*.*(..)))/aop:advisor切入点来匹配可拦截的目标。http://pandonix.iteye.com/blog/336873

      5:使用全注解的方式声明拦截属性和目标,<tx:annotation-driven transaction-manager="transactionManager"/>。

      IOC/AOP,使用IOC,可以自由装配BEAN,而AOP是对IOC的补充。两者都是建立于JAVA的反射/代理/CGLIB的基础之上。

posted on 2013-05-06 12:58  過眼云煙  阅读(537)  评论(0)    收藏  举报

导航