首页 本文章均为作者原创,未经作者同意,严禁转发

 

 

2012-2-14 假如互联网公司做铁道部12306订票网站


看到很多帖子都在吹捧让某些互联网公司来做,我就觉得有点恶心,他们来也肯定搞不定。

 

首先,这些帖子支持者明显没有接触过传统企业系统。

 

     在网站订票前已经有电话订票,还有传统的窗口订票,这些系统是依赖于他们的核心票系统,网站只是个外围系统而已。至于为什么能这样说,只要做过比较大的企 业系统的人,都应该明白。因此,网站系统能定到票否,不能仅仅取决于网站本身,而且还取决于核心系统,因此性能啊,并发量等等,网站本身并没有决定性作 用。就好比好的系统用了一个糟糕的数据库系统一样。订票网站这次八成是背了黑锅。

    再看看这些顶尖互联网电商系统,数据都是自己玩,想怎么玩都这么玩,况且也是磕磕盼盼的玩了这么多年。他们的架构师能站出来对订票网站哼一声自己的见解么?我目前没有看到,说明他们这些顶尖的电商架构师还是很理智的

 

 

其次,我看到有帖子吹捧互联网技术,什么云,什么nosql,这对互联网还真是管用。但到了“票”,哪怕没有我提到的所谓“核心系统”,订票网站自己玩票,这些系统都是浮云。因为这么有限的资源,这么人都再短时间抢购,这应用场景明显不是云计算,nosql技术应用场景 ,让铁道部订票网站玩这些技术,明显是推他们入火坑。

 

 

再次,对那些吹捧排队技术来实现,明显这些支持者少有人跟国企政府打交道。先不说好坏,假设这真的能实现,铁道部领导一句话就能让你起鸡皮疙瘩:“要你这排队系统有什么用,都得排队,我还不如多开几个订票点,还能解决就业”。互联网公司如果提这个方案,很可能出局。

 

 

最后,还是替订票网站系统打个抱不平。据网上说这个系统建设调研了数年,我非常能理解其中的谨慎和辛苦,因为我之前为移动,电信集团服务,客户决定 升级他们的核心系统之前,都做了多年的调研,我参与其中,深知他们非常谨慎认真。我也相信铁道部也是这样的。至于为什么不用IBM方案,我觉得了解IBM 的人都觉得这很正常,并非由什么内幕

 

如果让互联网公司参与,可以从用户体验,支持大量用户登录等外围次要功能入手。至于深入到核心,那是要动摇铁道部IT整个架构,这不是短时间能改善的。

鼓励一下网站定票系统,做到这程度,已经不容易了,留此贴为证,他们来年会更好,每个人都能有愉快的购票体验

 

2012-4-14 警告:通过调用InjectBeanSelfProcessor并不一定能注入一个被代理的Service


在Spring 里,所有的标注为@Service的类如果想获取对本身的代理的引用,通常有四种办法

1 是在需要使用的地方通过context.getBean("serviceName")获得proxy的引用,

2 可以以上面的为基础,用@PostContstructor标注一方法,在方法体里调用 context.getBean("serviceName") 初始化self

3 是通过 AopContext.currentProxy(),在调用出获取当前proxy,但因为操作通过ThreadLocal来完成,有性能问题,官方并不推荐此用法

4 是我们项目最常用的方法 ,通过实现BeanPostProcessor的 postProcessAfterInitialization(Object bean, String beanName) 的方法,将代理好的类注入到本身Service(要求Service实现BeanSelfAware里。类似如下用法

public class InjectBeanSelfProcessor implements BeanPostProcessor

{

 public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException
 {
  if (bean instanceof BeanSelfAware)
  {
   BeanSelfAware myBean = (BeanSelfAware) bean;
   myBean.setSelf(bean);
   return myBean;
  }
  return bean;
 }

}

 

需要警告的是,InjectBeanSelfProcessor 在我们的多个Service里出现互相引用的时候,将不一定把代理过的类传入到自身,即bean 不是Proxy的一个实例,而是bean本身。这是因为在AService,BService循环引用过程,

 

当BService需要AService的时候,Spring会暂时创建一个EarlyBeanReference,以解决循环引用问题。 Spring没有按照我们预期的注入一个被代理的类。因此我们项目中这将导致事物不一致问题。

 

为了保证使用方法InjectBeanSelfProcessor 仍然能获取到一个被代理的类,建议InjectBeanSelfProcessor 使用如下方式获取

 

public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException
 {
  if (bean instanceof BeanSelfAware)
  {
   BeanSelfAware myBean = (BeanSelfAware) bean;
   Class cls = bean.getClass();
   
   
   if(!AopUtils.isAopProxy(bean)){
    //if @Service
    Class c = bean.getClass();
    Service serviceAnnotation = (Service)c.getAnnotation(Service.class);
    if(serviceAnnotation!=null){
     try
     {
      log.info("No Proxy,retrive from context "+bean.getClass());
      bean = context.getBean(beanName);
      if(!AopUtils.isAopProxy(bean)){
       //仍然不是Proxy
       log.info("No Proxy Bean for service "+bean.getClass());
      }
     }
     catch (BeanCurrentlyInCreationException ex)
     {
      //告警,但仍然在一个No Proxy的情况正常运行
      log.info(ex.getMessage());
     }
     catch (Exception ex)
     {
      //告警,但仍然在一个No Proxy的情况正常运行
      log.fatal(ex.getMessage(), ex);
     }
    }
    
   }
   
   myBean.setSelf(bean);
   return myBean;
  }
  return bean;
 }

 

 

 

 

建议每个采用InjectBeanSelfProcessor 方式的项目,最好在注入前判断一下此类是否是被代理过的,以做一下评估曾有哪些Service受过影响,可以通过

AopUtils.isAopProxy(bean)) 来判断。然后再使用我建议的InjectBeanSelfProcessor 写法

 

 

2012-4-14 学习架构三本书


我认为学习架构进阶的三本书:

1 《操作系统概念》,或者《操作系统设计与实现》

推荐原因:还有什么系统比操作系统要处理的问题更复杂么?

2 《计算机网络》 或者《计算机网络系统方法》

推荐原因:多个系统交互,要考虑什么,这俩书都说的很清楚了

3 事务处理概念与技术

推荐原因:企业系统,所有的问题和思路,你知道的,你不知道的,都可以在这里找到。

估计现在大学都开的有这样的课程,但学生时代只是学了知识,并没有学为什么有这样的知识。从架构的角度再再回头看看,是非常有好处的,主要看的是,依照重要性,依次是

1 为什么出现了这些问题

2 如何简化或者模型话这些问题

3 如何取舍解决方案的

4 解决方案是怎么样的

 

2012-5-23 Spring-dumpling


这是一个在公司做的东西,用于为程序提供协作服务,正如spring里的@Service,@Transactional提供事物服务那样.做完了可以开源出来

一:协作服务
1 @CooperationService,类似于Spring的@Service
所有标记@CooperationService的类都将检查类方法里是否有如下annotation
@Publish / @Subscribe
@RemotePublish / @RemoteSubscriber
@ClusterSync
@RemoteNotify / @RemoteWait
@Process / @Task

2 @Publish,有如下属性
path: 一个逻辑路径,必须要
ruleExp: 根据输入值,输出值判断是否需要publish,如rule="returnValue!=null&&arg[0]!=null"
argExp:一个参数表达式列表,如果没有,则按照输入参数和输出参数作为参数列表,传递个sub。参数格式如:argExp="args[0].orderId,args[1],returnValue;"

@Subscribe
path: 一个逻辑路径,必须要
sameTrnasaction: 与标记为同样path的类是否处于同一个事物,默认false

3 @ClusterSync
path:一个逻辑路径,一旦机器(JVM)获取锁,将永久占有锁,机器宕机或者失去连接,将导致其他机器中的某一个占用

4 @RemotePublish,同Publish,但是发布到远程
path 一个路径
ruleEx: 根据输入值,输出值判断是否需要Notify,默认是returnValue!=null ,否则,总是通知其他机器.如规则rule="returnValue==true"
argExp:一个参数表达式列表,如果没有,则按照输入参数和输出参数作为参数列表,传递个sub。参数格式如:argExp="arg0=input[0].orderId;arg1=input[0].cash;arg2=returnValue;"

@RemoteSubscriber
path:一个路径。

5 @RemoteNotify,远程只能有一个被执行
path 一个路径
rule: 根据输入值,输出值判断是否需要Notify,默认是returnValue!=null ,否则,总是通知其他机器. 如规则rule="returnValue==true"
argExp:一个参数表达式列表,如果没有,则按照输入参数和输出参数作为参数列表,传递个sub。参数格式如:argExp="arg0=input[0].orderId;arg1=input[0].cash;arg2=returnValue;"
@RemoteWait ,
path:一个路径。

6 @Process
@Task待定

二:协作服务提供者

每组annotation可以有自己的协作服务提供者,或者共用一种协作服务提供者(如果服务提供者都支持)。
协作服务提供时机应该是系统启动成功后(也包括系统各个组件初始化成功后)
Remote的协作服务实现可以通过JMS,ZK,甚至数据库表共享来实现。推荐使用ZK,但ZK 对RemotePublish 支持并不好。不适合线上业务,只适合一些数据同步和管理功能。
@Publish,@Process是基于Local的,则不需一个第三方协作服务提供者。

三:协作服务发现:

通过Spring的机制PostProcessor,首先找到注解为@CooperationService的类,然后依次遍历方法,可以发现这些需要服务的方法和类
也能适合别的框架,如没有spring的情况

四 协作者
分为发起方和接收方,通过申明annotation,可以实现协作。也可以直接在代码里调用协作服务API以满足发起方的灵活性要求.如一个方法体里需要俩次Publish到不同地方
PublicService pub = ctx.get("CooperationService-Pub");pub.send(path1, arglist),pub.send(path2,arglist2)

五 应用场景说明

1 多台机器上只有一台能执行某个job,则使用@ClusterSync
2 主业务调用后会调用一些次要业务,不希望次要业务影响主业务的性能和牺牲可维护性 主业务使用@Publish,多个次业务使用@Subscribe。
3 数据需要同步到多台机器上,使用@RemotePublish和 @RemoteSubscriber标签
4 数据需要交给远程的任一台机器处理,使用@RemoteNotify然后结合@RemoteWait 标签一起用
5 主业务和次要业务处理后,还要求交给远端一个机器处理 可以在使用@Publish,@Subscrbie后,可以结合@RemoteNotify,@RemoteWait 来处理

 



 

2012-5-23 Struts2 漏洞解决办法


Struts2官方已经发布了多次漏洞补丁,但根源在于OGNL能调用静态办法,所以彻底解决漏洞的办法是底层禁止OGNL调用一些特定的java类,如System,Runtime类

我们底层入手,调用OGNLRuntime静态方法,设置我们自己的MethodAccessor类,能禁止OGNL在表达式中调用Runtime,System等类。这已经验证通过。
 
如下图从左到右,是action的参数赋值过程,为了堵住漏洞,通常是在ParameterInteceptor做过滤,但几年来,都有漏洞,如果我们在OGNLRuntime那做过滤,因为这是调用java类方法最底层的方法,所以很保险

我们目前推荐在原有过滤的基础上,增加一个patch包以对底层调用过滤,随后,公司内部已经布一个ongl_patch.jar,基本上引入后,在项目的servlet/struts/spring 任何一个框架的listener里调用里面的方法初始化一下,就可以了

其实质就是做如下初始化:

OgnlRuntime.setMethodAccessor(OgnlTest.class, new NoMethodAccessor());
OgnlRuntime.setMethodAccessor(Runtime.class, new NoMethodAccessor());
OgnlRuntime.setMethodAccessor(System.class, new NoMethodAccessor());
OgnlRuntime.setMethodAccessor(ProcessBuilder.class,
new NoMethodAccessor());
OgnlRuntime
.setMethodAccessor(OgnlRuntime.class, new NoMethodAccessor());

 

NoMethodAccessor实现了OGNL的 MethodAccessor,

如下:

public class NoMethodAccessor implements MethodAccessor {

public NoMethodAccessor() {
int i = 1;
}

@Override
public Object callStaticMethod(Map context, Class targetClass,
String methodName, Object[] args) throws MethodFailedException {
throw new MethodFailedException("do not run", "123");
}

@Override
public Object callMethod(Map context, Object target, String methodName,
Object[] args) throws MethodFailedException {
// TODO Auto-generated method stub
throw new MethodFailedException("do not run", "123");
}

}

 

 

2012-6-3 Spring Scheduler 引起的死锁解决办法


 

现在Spring配置内容越来越多,初始化需要的时间也越来越多。这时候quartz启动,Job在另外一个线程去试图获取Bean,有一定死锁的机率。

这个是找到的一个官方认可的bug,但尚未修复
https://jira.springsource.org/browse/SPR-9199


一个改造方法是是的job采用@autowired 尽可能通过注入bean,我觉得这是最好的方式,但有可能不灵活
另外一个临时解决方法是将job启动尽可能延迟多一点,以等待spring主线程初完毕
还有一个方法是将quartz启动延迟到spring初始化完毕后,即监听spring 的 ContextRefreshedEvent事件,然后启动schedule。此实现方法稍微有点复杂,参考附件,说明如下:

public class MySchedulerFactoryBean extends SchedulerFactoryBean implements ApplicationListener, Ordered
{
public MySchedulerFactoryBean()
{
super();
}

// public void start() throws SchedulingException
// {
// super.start();
// System.out.println("start ");
// }

@Override
public void onApplicationEvent(ApplicationEvent event)
{
if (event instanceof ContextRefreshedEvent)
{
System.out.println(" spring init finsihed");
super.start();
}

}

@Override
public int getOrder()
{
return 100;
}

}

所有的配置文件都做如下相应改动

<bean class="com.netease.XXXX.MySchedulerFactoryBean">
<property name="autoStartup" value="false" />
<property name="triggers">
<list>
<ref bean="jobTestDetailTrigger" />
</list>
</property>
</bean>

即将spring提供的SchedulerFactoryBean 改成我们自己的

另外有一处改动就是,但可以保证spring按照我们定义的顺序调用Linstener从而初始化系统各种数据
按照所有Lisener的优先级(通过实现ordered方法)来顺序调用这些Liseneer

<bean id="applicationEventMulticaster" class="com.netease.lottery.util.MyApplicationEventMulticaster"/>

 

MyApplicationEventMulticaster实现可以参考spring提供的SimpleApplicationEventMulticaster

代码如下:

package com.netease.lottery.util;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ApplicationEventMulticaster;
import org.springframework.core.Ordered;

public class MyApplicationEventMulticaster implements ApplicationEventMulticaster
{

Set<ApplicationListener> list = new HashSet<ApplicationListener>();

@Override
public void addApplicationListener(ApplicationListener listener)
{
list.add(listener);

}

@Override
public void removeApplicationListener(ApplicationListener listener)
{
list.remove(listener);

}

@Override
public void removeAllListeners()
{
list = new HashSet<ApplicationListener>();

}

@Override
public void multicastEvent(ApplicationEvent event)
{
List target = new ArrayList(list);
Collections.sort(target, new Comparator() {

@Override
public int compare(Object o1, Object o2)
{
ApplicationListener l1 = (ApplicationListener) o1;
ApplicationListener l2 = (ApplicationListener) o2;
int p1 = Ordered.LOWEST_PRECEDENCE;
int p2 = Ordered.LOWEST_PRECEDENCE;
if (l1 instanceof Ordered)
{
p1 = ((Ordered) l1).getOrder();
}
if (l2 instanceof Ordered)
{
p2 = ((Ordered) l2).getOrder();
}

return p1 - p2;

}

});

for (int i = 0; i < target.size(); i++)
{
ApplicationListener l = (ApplicationListener) target.get(i);
l.onApplicationEvent(event);
}

}

}



总共有这三种方法可以解决,个人觉得第三种是最周全和灵活的方式

 

 

 

2012-7-6 OQL 的几个例子


解决内存泄露的一个方法是分许heap dump文件,可以参考 http://visualvm.java.net/oqlhelp.html

我自己总结了一下以后可能用到的一些OQL,如下:

查找所有包含指定类的list

heap.objects(heap.findClass("java.util.ArrayList"),true, function(it){
if(it.size<=0){
return false ;
}
var i=0;
var data = it.elementData[0];
var className = classof(data).name;
if(isClass(className)){
return true
}else{
return false;
}

} )

function isClass(name){
var pattern = /com.netease/ ;
var result = pattern.exec(name);
return result!=null;
}

查找业务类直接或者间接引用的list

select filter(heap.livepaths(s),function(it){

var array = it ;
var i= 0;
var size = array.length;
for(;i<size;i++){
var className = classof(array[i]).name;
if(isClass(className)){
return true
}else{
return false;
}
}
return true ;


})
from java.util.ArrayList s

 

查找包含内容最多的List,这个应该是查找内存泄露的好语句
map(top(heap.objects('java.util.ArrayList'), 'rhs.size - lhs.size', 5),"toHtml(it)+'='+it.size")

查找当前系统属性
map(heap.objects(heap.findClass("com.netease.Main")),"it.size")

查找同样内容最多的string
var counts={};
var alreadyReturned={};

filter(
sort(
map(heap.objects("java.lang.String"),
function(heapString){
if( ! counts[heapString.toString()]){
counts[heapString.toString()] = 1;
} else {
counts[heapString.toString()] = counts[heapString.toString()] + 1;
}
return { string:heapString.toString(), count:counts[heapString.toString()]};
}),
'lhs.count < rhs.count'),
function(countObject) {
if( ! alreadyReturned[countObject.string]){
alreadyReturned[countObject.string] = true;
return true;
} else {
return false;
}
}
);

2012-7-19 Resin3 JSP预编译改善办法


一、预编译需求

通过预先编译JSP,而不是在线上编译,节省了线上编译时间,降低系统上线初期超高的CPU使用率,以及用户请求等待事件

默认方式,Resin3提供了俩种预编译(http://www.caucho.com/resin-3.0/jsp/compile.xtp),但在实际使用的时候,无论哪种方法,都只能编译部分JSP,导致优化很不明显,据运维的说,还发生了log4j日志无法输出的情况(这个我也很莫名其妙,至今不知道原因)

二、预编译改善原理

幸好Resin是公开源码的,通过分析预编译代码,其默认编译方式是每64个jsp文件为一组,好处是这样编译速度快,但如果其中有一个jsp没有编译通过,则这64个文件都奖编译失败。在我们系统中,有很多子jsp,如果一些JSP不幸与这些子JSP分在一组,则编译不通过。

目前解决办法是每组JSP只包含一个jJSP文件。这样就能保证全部编译,但缺点是需要较长编译时间,在测试系统上大概需要1分钟才能编译好200多个JSP文件

代码如下:

package com.netease.common.util;

import java.lang.reflect.Method;

import com.caucho.java.JavacConfig;

public class ResinJSPCompileTask {

/**

 * @param args

 */

public static void main(String[] args) throws Exception {

JavacConfig config = JavacConfig.getLocalConfig();

if (config == null) {

config = new JavacConfig();

}

config.setMaxBatch(1);

config.setArgs("-nowarn");

config.init();

//String appDir = "D:\\workspace\\20111221_data\\resin\\webRoot";

//String classDir = "D:\\workspace\\20111221_data\\resin\\webRoot\\WEB-INF\\work\\";

 String appDir = args[0];

 String classDir = args[1];

String clsName = "com.caucho.jsp.JspCompiler";

Class jsp = Class.forName(clsName);

Method main = jsp.getMethod("main", new String[0].getClass());

main.invoke(null, new Object[] { new String[] { "-app-dir", appDir,

"-class-dir", classDir } });

}

}

核心代码就是config.setMaxBatch(1);

另外 appDir 是web根目录,classDir 是jsp编译后的保存的目录,其实就是${appDir}/WEB-INF/work.

针对编译速度慢的情况,可以做如下调整

1 换到线上机器进行预编译,速度能提高

2 setMatBatch(2),能节约一半时间。但会有少数JSP(< 子JSP个数)无法预编译。