posted in JavaWeb 

最近工作中涉及到两个对批量用户进行离线处理的工作:

  1. 对若干用户进行打标记。
  2. 对若干用户进行消息推送。

联想到之前也有很多这种类似的工作,索性把其中共用的部分抽离出来做成了框架——取名叫绿萝。

通过使用该容器,使用者编写处理业务相关的代码,业务无关的部分交给容器来解决。好比 Servlet和Tomcat之间的关系,绿萝作为一个容器来运行业务代码。

1、有什么用

具体来说,绿萝主要为使用者处理以下逻辑:

  1. 从文件中读取用户列表进行后续处理
  2. 把读取的用户列表分发给若干现成进行进行处理,线程数可指定
  3. 可以通过参数对消费的QPS进行限制,防止影响线上系统
  4. 对所有用户的处理结果进行返回码的统计
  5. 把业务参数解析成Map供使用者进行使用

2、怎么用

绿萝使用起来很简单,需要以下几步:

2.1 安装lvluo到本地repo

在lvluo目录下执行

mvn clean install -Dmaven.test.skip

2.2、引入依赖

        <dependency>
            <groupId>com.netease</groupId>
            <artifactId>lvluo-worker</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

2.3、实现接口

写一个类继承AbstractConsumerTask,并添加@Component注解。
(注:该类需要放置在com.netease路径下)

例如,要对1亿用户进行消息推送,那么编写以下类即可:

@Component
public class PushConsumerTask extends AbstractConsumerTask {

    private static final Logger LOGGER = LoggerFactory.getLogger(OneConsumerTask.class);
    private static final Logger SUCCESS_LOGGER = LoggerFactory.getLogger("successLogger");
    private static final Logger FAIL_LOGGER = LoggerFactory.getLogger("failLogger");

    private AtomicLong successCount = new AtomicLong(0);
    private AtomicLong failCount = new AtomicLong(0);

    @Override
    public int doService(String username, Map<String, String> requestMap) {

        //获取请求参数,进行参数校验
        String message = requestMap.get("message");
        if (StringUtils.isEmpty(indexStr)) {
            System.out.println("message 参数为空");
            System.exit(-1)
        }

        //业务逻辑处理
        int retCode = pushMessage(username, message);

        //打印需要的结果到日志
        if (retCode == 0) {
            SUCCESS_LOGGER.info(username);
            LOGGER.info(MessageFormat.format("成功:第{0}个逻辑处理完成, 用户名为{1}", successCount.addAndGet(1), username));
        } else {
            FAIL_LOGGER.info(username);
            LOGGER.info(MessageFormat.format("失败:第{0}个逻辑处理完成, 用户名为{1}", failCount.addAndGet(1), username));
        }

        return retCode;
    }

2.4、打包

pom.xml中加入以下代码:
其中finalName要指定成想要的名字。


    <build>
        <finalName>demo</finalName>
        <sourceDirectory>src/main/java</sourceDirectory>

        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>1.4</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <transformers>
                                <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
                                    <resource>META-INF/spring.handlers</resource>
                                </transformer>
                                <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                                    <mainClass>com.netease.Main</mainClass>
                                </transformer>
                                <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
                                    <resource>META-INF/spring.schemas</resource>
                                </transformer>
                            </transformers>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

2.5、运行

java -jar demo.jar 消费任务 文件路径 业务参数 系统参数
  • 消费任务 - 编写的业务类的名称,首字母替换成小写
  • 文件路径 - 用户名文件对应的文件路径
  • 业务参数 - 你的业务中需要用的参数 (如果有特殊字符或者中文需要进行urlencode)
  • 系统参数 - 如threadCount 指定线程数,qps指定限制的频率

如,

java -jar demo.jar pushConsumerTask usernameList.txt message=xx threadCount=10&qps=100

2.6、输出结果

控制台输出:

10:32:05,096  INFO DemoConsumerTask:51 - 失败:第504个逻辑处理完成, 用户名为urstest_czz994
10:32:05,096  INFO DemoConsumerTask:48 - 成功:第492个逻辑处理完成, 用户名为urstest_czz995
10:32:05,096  INFO AbstractConsumerTask:92 - 本次请求返回码1,用户名为urstest_czz994。总共请求995次,当前返回码统计结果:{0:491,1:504}
10:32:05,097  INFO AbstractConsumerTask:92 - 本次请求返回码0,用户名为urstest_czz995。总共请求996次,当前返回码统计结果:{0:492,1:504}

logs/success.log:

urstest_czz3
urstest_czz4
urstest_czz6
urstest_czz9
urstest_czz10
urstest_czz11

logs/fail.log

urstest_czz0
urstest_czz1
urstest_czz2
urstest_czz5
urstest_czz7

3、注意事项

  1. ConsumerTask类放在com.netease路径下
  2. Spring版本用4及以上版本,推荐4.2.9.RELEASE

4、代码

如果对代码有兴趣欢迎围观吐槽~
https://github.com/czzshr/lvluo

工程中包含了两个module:
- lvluo-demo 是lvluo的使用demo
- lvluo-worker 是lvluo的具体实现

posted in JavaWeb 

项目组有两个老项目需要从resin迁移到tomcat,因为这两个项目的调用方比较多,所以要保证从resin迁移到tomcat过程中对调用方完全透明才行。这就需要对resin和tomcat的不同之处进行处理。

在修改过程中踩了若干坑,最终相关的Resin2Tomcat处理代码有了一个相对稳定的版本。于是把这部分相关处理代码抽成了一个maven工程,上传到公司仓库,方便以后其他同学使用。

1. 问题

目前遇到的问题主要是resin和tomcat对编码方式的不同,

  1. resin用gbk进行urlDecode,tomcat用utf-8进行urlDecode。
  2. 部分调用方的调用参数没有进行urlEncode,所以tomcat需要做iso8859-1到utf-8的转换。
  3. 调用方可能调用不规范,比如post请求的部分参数放在url中,部分放在body中。

2. 处理思路

做一个filter进行编码的转换,这样业务代码就不需要进行变动了,可以直接Resin切换到Tomcat。

3. 如何使用

  1. pom.xml中添加依赖
    <dependencies>
        <dependency>
            <groupId>com.netease.urs</groupId>
            <artifactId>resin2tomcat</artifactId>
            <version>0.1.0</version>
        </dependency>
    </dependencies>
  1. web.xml中添加filter
    <filter>
        <display-name>EncodingFilter</display-name>
        <filter-name>EncodingFilter</filter-name>
        <filter-class>com.netease.urs.resin2tomcat.filter.EncodingFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>EncodingFilter</filter-name>
        <url-pattern>/servlet/*</url-pattern>
    </filter-mapping>

4. 其他

删除lib下的Resin.jar,因为发现这个jar在tomcat中会报错

posted in JavaWeb 

2016-09-06_21:20:28.jpg

1. 设置方式

通常有以下几种设置方式:

1.1. 方法一 放入特定的文件夹中。

  • 修改为自定义ContextPath

把Web应用放到<catalina_home>/webapps下对应的文件夹中。

比如,Web应用放在<catalina_home>/webapps/bookstore目录下,则项目的对应的url为http://localhost:8080/bookstore/

  • 修改为根路径

特别的,如果Web应用在<catalina_home>/webapps/ROOT目录下,则该Web应用的url为http://localhost:8080/

这种方法Tomcat官方并不推荐。

1.2. 方法二 修改配置文件

  • 修改为自定义ContextPath

修改<catalina_home>/conf/server.xml

比如,在<catalina_home>/conf/server.xml<Host>元素下添加如下内容:

<Context path="/bookstore" docBase="/absolute/path/to/webapp"/>

则项目的对应的url为http://localhost:8080/bookstore/

  • 修改为根路径

特别的,在<catalina_home>/conf/server.xml<Host>元素下添加如下内容:

<Context path="/" docBase="/absolute/path/to/webapp"/>

则该Web应用的url为http://localhost:8080/

1.3. 方法三 添加配置文件

  • 修改为自定义ContextPath

/conf/Catalina/localhost下添加配置文件。

比如在<catalina_home>/conf/Catalina/localhost目录下添加一个配置文件bookstore.xml,配置文件中中填入以下内容:

<Context path="something" docBase="/absolute/path/to/webapp" />

则项目的对应的url为http://localhost:8080/bookstore/,服务器端运行的项目名称为something。这个方法很方便的隐藏了项目的名称,对一些项目名称被固定不能更换,但外部访问时又想换个路径,非常有效。不过通常文件名path相同。

  • 修改为根路径

特别的,比如在<catalina_home>/conf/Catalina/localhost目录下添加一个配置文件ROOT.xml,配置文件中中填入以下内容:

<Context path="/" docBase="/absolute/path/to/webapp" />

则该Web应用的url为http://localhost:8080/

这样就没问题了!这种方法被认为是最好的办法。

2. 参考

HOWTO set the context path of a web application in Tomcat 7.0
Tomcat中三种部署项目的方法

posted in JavaWeb 

前言

好久没有更新博客了,自从实习之后就没更新过,忙成狗了。。

前些日子工作中使用到了Linux下的JNI编程,找到网上一篇文章,进行了一些改写,贴在这里以作记录。

JNI介绍

JNI其实是Java Native Interface的简称,也就是java本地接口。它提供了若干的API实现了和Java和其他语言的通信(主要是C&C++)。也许不少人觉得Java已经足够强大,为什么要需要JNI这种东西呢?我们知道Java是一种平台无关性的语言,平台对于上层的java代码来说是透明的,所以在多数时间我们是不需要JNI的,但是假如你遇到了如下的三种情况之一呢?

  • 你的Java代码,需要得到一个文件的属性。但是你找遍了JDK帮助文档也找不到相关的API。
    在本地还有一个别的系统,不过他不是Java语言实现的,这个时候你的老板要求你把两套系统整合到一起。
  • 你的Java代码,需要得到一个文件的属性。但是你找遍了JDK帮助文档也找不到相关的API。
    在本地还有一个别的系统,不过他不是Java语言实现的,这个时候你的老板要求你把两套系统整合到一起。
  • 你的Java代码中需要用到某种算法,不过算法是用C实现并封装在动态链接库文件(so文件)当中的。

对于上述的三种情况,如果没有JNI的话,那就会变得异常棘手了。就算找到解决方案了,也是费时费力。其实说到底还是会增加开发和维护的成本。

JNI示例程序

说了那么多一通废话,现在进入正题。看过JDK源代码的人肯定会注意到在源码里有很多标记成native的方法。这些个方法只有方法签名但是没有方法体。其实这些naive方法就是我们说的 java native interface。他提供了一个调用(invoke)的接口,然后用C或者C++去实现。我们首先来编写这个“桥梁”.我自己的开发环境是 JDK1.6 + GCC + VIM + Makefile,先用VIM编写下面的代码。

// Java代码
// com/jni/JniTest.java
package com.jni;  

public class JniTest {  
    public JniTest(){  
    }  
    public native void sayHello(String name);  
}

我的native本地方法有一个String的参数。会传递一个name到后台去。本地方法已经完成,现在来介绍下javah这个命令,接下来就要用javah敏玲来生成一个相对应的.h头文件。

javah是一个专门为JNI生成头文件的一个命令。打开Shell之后输入javah回车就能看到javah的一些参数。在这里就不多介绍我们要用的是 -jni这个参数,这个参数也是默认的参数,他会生成一个JNI式的.h头文件。在控制台进入到工程的根目录,然后输入命令。

javac com/jni/JniTest.java
javah -jni com.jni.JniTest

命令执行完之后在工程的根目录就会发现com_jni_JniTest.h 这个头文件。在这里有必要多句嘴,在执行javah的时候,要输入完整的包名+类名。否则在以后的测试调用过程中会发生java.lang.UnsatisfiedLinkError这个异常。到这里java部分算是基本完成了,接下来我们来编写后端的C代码(C++也可以)。

打开com_jni_JniTest.h 这个头文件,仔细观察一下这个方法,

//Cpp代码
/* * Class: com_jni_JniTest * Method: sayHello * Signature: (Ljava/lang/String;)V */  
JNIEXPORT void JNICALL Java_com_jni_JniTest_sayHello  
  (JNIEnv *, jobject, jstring);

在注释上标注类名、方法名、签名,至于这个签名是做什么用的,我们以后再说。在这里最重要的是 Java_com_ni_JniTest_sayHello这个方法。在Java端我们执行 sayHello(String name)这个方法之后,JVM就会帮我们唤醒在so文件里的Java_com_jni_JniTest_sayHello这个方法。因此我们新建一个C++ source file来实现这个方法。

//Cpp代码
// com_jni_JniTest.c
#include <iostream> 
#include "com_jni_JniTest.h" 

JNIEXPORT void JNICALL Java_jni_JniTest_sayHello 
    (JNIEnv* env, jobject obj, jstring name)  
{  
    const char* pname = env->GetStringUTFChars(name, NULL);  
    cout << "Hello, " << pname << endl;  
}

对应的Makefile如下:

# Makefile
libcom_jni_JniTest.so: com_jni_JniTest.o
    g++ -shared -fpic -O2 -o $@ $^
com_jni_JniTest.o: com_jni_JniTest.c
    g++ -fpic -O2 -c $^ -I.
clean:
    rm *.o
    rm *.so

make编译会提示找不到jni.h和jni_md.h,这两个文件在 $JDK_HOME/include 目录下,复制到本文件夹即可。

这个时候后端的C++代码也已经完成,接下来的任务就是怎么把他们连接在一起了,要让前端的java程序“认识并找到”这个动态链接库。加入System.loadLibrary(“Hello”);这句到静态初始化块里。

//Java代码
// com/jni/JniTest.java
package com.jni;  

public class JniTest {  

    static{  
        System.loadLibrary("com_jni_JniTest");  
    }  
    public JniTest(){  
    }  
    public native void sayHello(String name);        
}

这样我们的代码就能认识并加载这个动态链接库文件了。万事俱备,只欠测试代码了,接下来在JniTest.java中添加测试代码。

//Java代码
// com/jni/JniTest.java
package com.jni;  

public class JniTest {  

    static{  
        System.loadLibrary("com_jni_JniTest");  
    }  
    public JniTest(){  
    }  
    public native void sayHello(String name);      
    public static void main(String[] args) {
        JniTest jt = new JniTest();
        jt.sayHello("World");
}

执行代码

java -Djava.library.path=. com.jni.JniTest

发现控制台打印出来Hello, World这句话。就此一个最简单的JNI程序已经开发完成。

参考

本文章迁移自http://blog.csdn.net/timberwolf_2012/article/details/43639159