paulwong

2010年9月15日 #

Maven打包时,环境变量替换,并解决spring-boot项目中${}无效的问题

https://www.jianshu.com/p/cf3bd9ddfe6f

posted @ 2019-11-20 16:12 paulwong 阅读(3) | 评论 (0)编辑 收藏

LOGBACK FOR SPRING

当程序中使用LOGGER.INFO("MESSAGE");要求打印日志时,LOGBACK会获取该行代码所在类的全名,和打印等级(INFO/DEBUG等),再在配置文件的<logger>中查找对应logger,使用其配置的appender组件打印日志,如无法找到对应的logger,则使用<root>对应的appender打印日志。

其中appender是用来输出日志,有file和console两个实现,console则是向控制台输出日志,而file则是向文件输出日志。
rolling file appender中,有rollingPolicy和triggerPolicy两个主要属性,rollingPolicy是确定如何处理日志文件,而triggerPolicy则是确定何时处理日志文件。

如果要使用SPRING针对LOGBACK的一些功能,如profile等,则要将logback.xml的配置文件命名为logback-spring.xml,并在SPRING中配置,logging.config= logback-spring.xml。

SPRING会将logging.file、logging.path这些配置转成系统变量LOG_FILE、LOG_PATH,可在配置文件中直接引用,如${LOG_FILE}。

如果logback配置文件要SPRING的其他属性,则要使用如下标签:
<springProperty scope="context" name="logLevel" source="log.level"/>

如果要使用LOGBACK的一些常用属性,可引入:
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
<include resource="org/springframework/boot/logging/logback/console-appender.xml"/>
如CONSOLE APPENDER,此resource在spring-boot-version.jar中。


=========================================
看完这个不会配置 logback ,请你吃瓜!
https://juejin.im/post/5b51f85c5188251af91a7525

logback解析——Appender
https://juejin.im/post/5a39c91cf265da4327185d10

SpringBoot中logback.xml使用application.yml中属性
https://www.cnblogs.com/jianliang-Wu/p/8945343.html

springboot使用logback-spring.xml配置讲解
https://blog.csdn.net/heguiliang_123/article/details/80296745

Logback配置
https://www.cnblogs.com/cjsblog/p/9113131.html

Spring Boot中的日志
http://loveshisong.cn/%E7%BC%96%E7%A8%8B%E6%8A%80%E6%9C%AF/2016-11-03-Spring-Boot%E4%B8%AD%E7%9A%84%E6%97%A5%E5%BF%97.html

Spring Boot与logback总结
https://blog.csdn.net/u014527058/article/details/79667458

SpringBoot Logback 配置参数迁移到配置中心 Apollo
https://blog.csdn.net/shuaizai88/article/details/83027262

posted @ 2019-11-19 15:14 paulwong 阅读(7) | 评论 (0)编辑 收藏

MONGODB安装


https://www.jianshu.com/p/d3b31b7aa182 

后续初始化用户及数据库
https://zocada.com/setting-mongodb-users-beginners-guide/

如果要以认证的方式登录,需加以下内容至/etc/mongod.conf
security:
  authorization: enabled





posted @ 2019-11-15 17:30 paulwong 阅读(18) | 评论 (0)编辑 收藏

Setting up ActiveMQ for HA-Load Balance


In a typical enterprise applications, we often need messaging and asynchronous processing.
To satisfy this need, we need a reliable as well as scalable messaging infrastructure. In currently available messaging infrastructures Apache ActiveMQ stands out in terms of features and simplicity.

Apache ActiveMQ comes with lot of features in built and also provides a way to configure or tweak as per the needs of an application.

In this post , we will explore how to enable network of activeMQ brokers so that we achieve HA(High Availability) as well as load balance between consumers & producers.

I carried out  my experiment on local machine with ACtiveMQ 5.8.0, but this can be easily upgraded to latest versions of ActiveMQ viz. 5.10.0

To have network of brokers, we need multiple brokers. So, I changed tcp and admin ports of brokers so that I can run multiple brokers on single machine.

To get brief background on network of broker, please visit this link

In this post we will setup below topology, we will mix failover and NOB to get work done,

1. Producer1 is configured to send messages to broker3 with failover to broker2
2. Producer2 is configured to send messages to broker2 with failover to broker3
3. Broker3, Broker2 are networked with Broker1as below


4. Broker1 is connected with broker4 with NOB.
5. Make sure you enable "advisorySupport" on the broker, which is essential for transparent routing of messages across brokers.
Dry Run:
1. Producer1 sends messages to queue "input.q" on broker3, where there are no active consumers, but it see subscriptions from broker1
2. Broker1 and broker 4 are has consumers which are looking at "input.q".
3. When broker3 receives a message it forwards it to broker1, as its in networked and has active consumers for "input.q" 
4. When broker1 receives a messages on "input.q", it gets load balanced between broker1 and broker4  as both has consumers looking for "input.q".
5. Whenever broker3 goes down, producer1 switches transparently to broker2, as its configured with failover.
6. I used prefetch size as 1, so that you can load balancing on consumers
Sample activemq configurations can be downloaded from here.

posted @ 2019-11-04 16:14 paulwong 阅读(29) | 评论 (0)编辑 收藏

ACTIVE MQ HA

组建ACTIVEMQ CLUSTER,使得其中一个ACTIVE MQ DOWN掉时,能自动切换到其他节点。

ACTIVEMQ 只有MASTER-SLAVE模式,集群中的多个节点共享消息的存储,多个节点同时启动时,竞争消息存储的锁,谁先取得,谁就是MASTER,当MASTER DOWN掉时,锁被释放,SALVE中马上又竞争锁,取得者成为MASTER。

方案:
  • 安装NFSV4
  • 修改消息存储路径
    <persistenceAdapter>
      <kahaDB directory="/sharedFileSystem/sharedBrokerData"/>
    </persistenceAdapter>
  • 客户端修改连接字符串
    failover://(tcp://master:61616,tcp://slave:61616)?randomize=false
--》


https://my.oschina.net/hzchenyh/blog/716424

https://www.iteye.com/blog/shift-alt-ctrl-2069250

https://stackoverflow.com/questions/53542928/activemq-ha-on-failover

https://activemq.apache.org/shared-file-system-master-slave

posted @ 2019-11-01 10:46 paulwong 阅读(25) | 评论 (0)编辑 收藏

ACTIVEMQ设置预取消息数目

当ACTIVEMQ的某个QUEUE有多个消费者,为避免某个消息者取了更多个消息处理,而造成其他消费者无消息可处理的情况,可以设置每个消费者不预取消息,即每个消费者消费完单个消息后,再去取消息,这样其他消费者就能平均的有消息可处理。


https://stackoverflow.com/questions/35928089/activemq-how-to-prevent-message-from-going-to-dispatched-queue


设置方法,在CONNECT STRING中设置:
tcp://localhost:61616?jms.prefetchPolicy.all=0 

tcp://localhost:61616?jms.prefetchPolicy.queuePrefetch=0 

queue = new ActiveMQQueue("TEST.QUEUE?consumer.prefetchSize=10"); 
consumer = session.createConsumer(queue);

http://activemq.apache.org/what-is-the-prefetch-limit-for.html

posted @ 2019-10-31 11:28 paulwong 阅读(33) | 评论 (0)编辑 收藏

EIP in SPRING INTEGRATION

  • idempotent receiver
幂等型,同一个MESSAGE,如MESSAGE ID都一样,在MESSAGING系统中不管运行多少次,结果都一样,为啥?因为重复的MESSAGE,都被忽略了。
方案:
消息被处理后,从消息中取出ID,放入META-DATA-STORE中,后续处理消息时,要从META-DATA-STORE中检查是否有值。

下面这个方案,ID的存储和判断是否重复消息都在一个INTERCEPTOR中搞定。
https://stackoverflow.com/questions/50401460/spring-integration-dsl-configure-idempotent-receiver-to-identify-duplicates
https://www.javacodegeeks.com/2015/09/monitoring-and-management.html


claim-check
将MESSAGE的PAYLOAD存在STORE中,返回一个ID,这个ID即claim-check,如果需要取MESSAGE的DETAIl时,可从STORE中取出MESSAGE。
https://github.com/spring-projects/spring-integration/blob/master/src/reference/asciidoc/claim-check.adoc


posted @ 2019-10-25 11:03 paulwong 阅读(135) | 评论 (0)编辑 收藏

SPRING INTEGRATION LESSONS

Lessons

Introduction to Enterprise Application Integration

In our first lesson, you will get introduced to the concepts of Enterprise Application Integration. You will learn about the and Enterprise integration patterns that can be applied to simplify integration between different platforms and the Integration strategies that can be followed for this purpose. Finally, we will discuss how and why to implement a Message driven architecture and how to achieve both Synchronous and asynchronous communication among nodes.

Spring Integration Fundamentals

In this lesson, you will get to understand how Spring Integration works under the hood. The core concepts of Spring Integration messaging system (like message channels and endpoints) will be introduced. Additionally, the components that build the framework will be discussed, including the channel adapters, transformers, filters, routers etc. Finally, the two distinct methods of communication (synchronous and asynchronous) are explained and the lesson ends with a discussion on error handling.

Spring Integration and Web Services

In this lesson, we will focus on the integration with external web services. Spring Integration comes with the necessary functionality (adapters, channels etc.) to support web services out of the box. A full example is built from scratch in order to better understand the topic.

Enterprise Messaging

In this lesson, we will focus on integrating our application with JMS messaging. For this purpose, we will use Active MQ, which will be our broker. We will show examples of sending and receiving JMS messages by using the Spring Integration JMS channel adapters. Following these examples, we will see some ways of customizing these invocations by configuring message conversion and destination resolution.

Spring Integration Full Example

In this lesson, we will wrap everything up by providing a complete application that uses several of the components provided by Spring Integration in order to provide a service to its users. We will discuss the system architecture, the actual implementation and the relevant error handling.

Monitoring and Management

In this lesson, we will examine different mechanisms of monitoring or gathering more information about what is going on within the messaging system. Some of these mechanisms consist of managing or monitoring the application through MBeans, which are part of the JMX specification. Another mechanism discussed in this chapter is how we will implement the EIP idempotent receiver pattern using a metadata store. Finally, the last mechanism described is the control bus. This will let us send messages that will invoke operations on components in the application context.

posted @ 2019-10-25 09:45 paulwong 阅读(18) | 评论 (0)编辑 收藏

SPRING INTEGRATION DSL DEMO

https://github.com/spring-projects/spring-integration/tree/master/src/reference/asciidoc

posted @ 2019-10-23 11:55 paulwong 阅读(18) | 评论 (0)编辑 收藏

Spring Integration 中文手册 - GOOD

Spring Integration 中文手册 (1)


Spring Integration 中文手册 (2)

posted @ 2019-10-11 10:28 paulwong 阅读(46) | 评论 (0)编辑 收藏

LINUX配置DNS

vi /etc/resolv.conf

nameserver 8.8.8.8

posted @ 2019-10-10 10:55 paulwong 阅读(34) | 评论 (0)编辑 收藏

在SPRING INTEGRATION中手动开始和停止JMS LISTENER

如果要对JMS BROKER生产和消费MESSAGE,一种方式是用JmsTemplate发送和消费消息,另一种方式是SPRING INTEGRATION。

SPRING INTEGRATION是实现了EIP模式的一种框架,即使用CHANNEL和JMS-INBOUND-ADAPTER、JMS-OUTBOUND-ADAPTER,完全脱离了JmsTemplate的API。

如果需要实现这种场景:从BROKER取一条消息,处理消息,且处理途中不要再从BROKER再取消息,处理完后再取消息,再处理。

这样要求手动开始和停止JMS LISTENER,即手动开始和停止JMS-INBOUND-ADAPTER、JMS-OUTBOUND-ADAPTER。

@Bean
@InboundChannelAdapter(value = "loaderResponseChannel")
public MessageSource loaderResponseSource() throws Exception {
    return Jms
            .inboundAdapter(oracleConnectionFactory())
            .configureJmsTemplate(
                    t -> t.deliveryPersistent(true)
                            .jmsMessageConverter(jacksonJmsMessageConverter())
            ).destination(jmsInbound).get();
}

当使用@InboundChannelAdapter时,会自动注册一个SourcePollingChannelAdapter ,但这个名字比较长:configrationName.loaderResponseSource.inboundChannelAdapter。

呼叫这个实例的start()和stop()方法即可。

@Bean
public IntegrationFlow controlBusFlow() {
    return IntegrationFlows.from("controlBus")
              .controlBus()
              .get();
}

Message operation = MessageBuilder.withPayload("@configrationName.loaderResponseSource.inboundChannelAdapter.start()").build();
operationChannel.send(operation)

https://stackoverflow.com/questions/45632469/shutdown-spring-integration-with-jms-inboundadapter

https://docs.spring.io/spring-integration/docs/5.0.7.RELEASE/reference/html/system-management-chapter.html#control-bus

https://github.com/spring-projects/spring-integration-java-dsl/blob/master/src/test/java/org/springframework/integration/dsl/test/jms/JmsTests.java

https://stackoverflow.com/questions/50428552/how-to-stop-or-suspend-polling-after-batch-job-fail

posted @ 2019-10-09 17:16 paulwong 阅读(24) | 评论 (0)编辑 收藏

CountDownLatch、CyclicBarrier和Semaphore

CountDownLatch、CyclicBarrier和Semaphore这三个并发辅助类,可以在线程中呼叫,使得线程暂停等,但各有不同。

  • CountDownLatch
1、初始化,并传入计数器
2、向不同的线程传入CountDownLatch实例
3、如果在某一线程中呼叫await(),则此线程被挂起,直到计数器为0,才往下执行
4、如果在某一线程中呼叫countDown(),计数器减1
5、最终如果计数器值为0时,则CountDownLatch实例不再起作用了,即为一次性的

  • CyclicBarrier
1、初始化,并传入计数器值,也可传入一个Runnable类,会在计数器为0时,被执行
2、向不同的线程传入CyclicBarrier实例
3、如果在某一线程中呼叫await(),则此线程被挂起,直到计数器为0,才往下执行
4、其他线程呼叫await(),则此线程被挂起,直到计数器为0,才往下执行
5、最终如果计数器值为0时,则CyclicBarrier实例会将计数器值恢复,又可重用

  • Semaphore
1、初始化,并传入计数器值
2、向不同的线程传入Semaphore实例
3、如果在某一线程中呼叫acquire(),则Semaphore实例会将计数器值减1,如果计数器值为-1,则将计数器值置为0,此线程被挂起,直到计数器值大于1时,才往下执行
4、此线程需呼叫release(),使得计数器值+1,以便其他线程在计数器值为0时不受阻


CountDownLatch 例子:
public class Test {
     public static void main(String[] args) {   
         final CountDownLatch latch = new CountDownLatch(2);
          
         new Thread(){
             public void run() {
                 try {
                     System.out.println("子线程"+Thread.currentThread().getName()+"正在执行");
                    Thread.sleep(3000);
                    System.out.println("子线程"+Thread.currentThread().getName()+"执行完毕");
                    latch.countDown();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
             };
         }.start();
          
         new Thread(){
             public void run() {
                 try {
                     System.out.println("子线程"+Thread.currentThread().getName()+"正在执行");
                     Thread.sleep(3000);
                     System.out.println("子线程"+Thread.currentThread().getName()+"执行完毕");
                     latch.countDown();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
             };
         }.start();
          
         try {
             System.out.println("等待2个子线程执行完毕");
            latch.await();
            System.out.println("2个子线程已经执行完毕");
            System.out.println("继续执行主线程");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
     }
}

结果:
线程Thread-0正在执行
线程Thread-1正在执行
等待2个子线程执行完毕
线程Thread-0执行完毕
线程Thread-1执行完毕
2个子线程已经执行完毕
继续执行主线程


CyclicBarrier例子:
public class Test {
    public static void main(String[] args) {
        int N = 4;
        CyclicBarrier barrier  = new CyclicBarrier(N,new Runnable() {
            @Override
            public void run() {
                System.out.println("当前线程"+Thread.currentThread().getName());   
            }
        });
         
        for(int i=0;i<N;i++)
            new Writer(barrier).start();
    }
    static class Writer extends Thread{
        private CyclicBarrier cyclicBarrier;
        public Writer(CyclicBarrier cyclicBarrier) {
            this.cyclicBarrier = cyclicBarrier;
        }
 
        @Override
        public void run() {
            System.out.println("线程"+Thread.currentThread().getName()+"正在写入数据");
            try {
                Thread.sleep(5000);      //以睡眠来模拟写入数据操作
                System.out.println("线程"+Thread.currentThread().getName()+"写入数据完毕,等待其他线程写入完毕");
                cyclicBarrier.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }catch(BrokenBarrierException e){
                e.printStackTrace();
            }
            System.out.println("所有线程写入完毕,继续处理其他任务");
        }
    }
}

执行结果:
线程Thread-0正在写入数据
线程Thread-1正在写入数据
线程Thread-2正在写入数据
线程Thread-3正在写入数据
线程Thread-0写入数据完毕,等待其他线程写入完毕
线程Thread-1写入数据完毕,等待其他线程写入完毕
线程Thread-2写入数据完毕,等待其他线程写入完毕
线程Thread-3写入数据完毕,等待其他线程写入完毕
当前线程Thread-3
所有线程写入完毕,继续处理其他任务
所有线程写入完毕,继续处理其他任务
所有线程写入完毕,继续处理其他任务
所有线程写入完毕,继续处理其他任务


Semaphore例子:
public class Test {
    public static void main(String[] args) {
        int N = 8;            //工人数
        Semaphore semaphore = new Semaphore(5); //机器数目
        for(int i=0;i<N;i++)
            new Worker(i,semaphore).start();
    }
     
    static class Worker extends Thread{
        private int num;
        private Semaphore semaphore;
        public Worker(int num,Semaphore semaphore){
            this.num = num;
            this.semaphore = semaphore;
        }
         
        @Override
        public void run() {
            try {
                semaphore.acquire();
                System.out.println("工人"+this.num+"占用一个机器在生产");
                Thread.sleep(2000);
                System.out.println("工人"+this.num+"释放出机器");
                semaphore.release();           
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

执行结果:
工人0占用一个机器在生产
工人1占用一个机器在生产
工人2占用一个机器在生产
工人4占用一个机器在生产
工人5占用一个机器在生产
工人0释放出机器
工人2释放出机器
工人3占用一个机器在生产
工人7占用一个机器在生产
工人4释放出机器
工人5释放出机器
工人1释放出机器
工人6占用一个机器在生产
工人3释放出机器
工人7释放出机器
工人6释放出机器

https://www.cnblogs.com/dolphin0520/p/3920397.html

https://juejin.im/post/5aeec3ebf265da0ba76fa327

posted @ 2019-09-24 10:18 paulwong 阅读(37) | 评论 (0)编辑 收藏

使用 Jenkins 部署 Spring Boot

https://mp.weixin.qq.com/s?__biz=MzI4NDY5Mjc1Mg==&mid=2247489278&idx=2&sn=a48342d706bfd1651e277e1c24e81e3e&chksm=ebf6ce81dc81479764d1e6ff7b207257a78d52bed5ef8c2f16c76f70660d1da9609167ed7bbb&mpshare=1&scene=1&srcid=&sharer_sharetime=1568861026830&sharer_shareid=24856bf403968a883e437b859be0a9b5&pass_ticket=qB9yWQbj%2FGo7PDZNogjBwishDCx5Suu%2BvBWnS1TpKmY%3D#rd

posted @ 2019-09-19 17:44 paulwong 阅读(33) | 评论 (0)编辑 收藏

CI/CD 资源

Continuous delivery tool landscape
http://www.jamesbowman.me/post/continuous-delivery-tool-landscape/

posted @ 2019-09-18 17:08 paulwong 阅读(30) | 评论 (0)编辑 收藏

ANSIBLE + SPRING BOOT

https://github.com/remyma/ansible-springboot

frontend+backend+jenkins+ansible
https://itnext.io/ci-cd-with-jenkins-and-ansible-f41ef2b33977

posted @ 2019-09-18 16:09 paulwong 阅读(33) | 评论 (0)编辑 收藏

Windows终端工具_MobaXterm

前言

  有人喜欢小而美的工具,有人喜欢大集成工具。这里推荐一款增强型的Windows终端工具MobaXterm,它提供所有重要的远程网络工具(SSH,X11,RDP,VNC,FTP,MOSH ......)和Unix命令(bash,ls,cat,sed,grep,awk,rsync等)。使用MobaXterm工具,可以替代SSH客户端工具(xshell、putty、securecrt等)、sftp/ftp工具(winscp、filezilla)、远程桌面访问工具(RDO等)等等,可以极大降低你windows系统上的软件安装数量。

MobaXterm使用体会&优点

  1、工具获取简单、免安装(绿色版)、免费(不用到处找license)、可个性化配置(字体、前景色、背景色、语法高亮等)。

  2、可以替代xshell、putty、securecrt等SSH客户端、winscp、filezilla等ftp传输工具、替代RDO远程桌面访问工具等。减少windows系统软件安装和资源占用。我的系统已经超负荷运转

  3、可以替代CMD窗口。CMD命令行字体太丑。。并且配置还麻烦。实在不想用。

  4、支持Unix/Linux常用命令使用。满足在windows上学习操作linux命令的需求以及利用linux命令快速处理文本文件。

  5、可以支持丰富的组件,减少部分软件的安装。如Cygwin。

  工具使用建议:每个人对工具使用的要求和场景不尽相同。需要了解MobaXterm特性可以进入MobaXterm官网或者阅读MobaXterm帮助手册(启动工具--> 菜单栏"help" -> "document")。

MobaXterm主要功能

  1、远程会话管理器:单个应用程序中的SSH,SFTP,telnet,VNC,Mosh,RDP连接

  2、Windows上的许多Unix/Linux命令:基本Cygwin命令(bash,grep,awk,sed,rsync,...)。

  3、丰富的组件和插件,可以自由选择。详情查看MobaXterm Plugins

  4、远程桌面:使用RDP,VNC或XDMCP在计算机上显示完整的远程桌面

  5、嵌入式Xserver:在Windows计算机上显示远程应用程序

  6、....

MobaXterm工具下载

  MobaXterm工具分为便携版(绿色免安装版,免费)和专业版(收费)。对于大部分开发测试人员,免费的绿色免安装版本就可以满足日常工作的需求。下载路径:MobaXterm

MobaXterm使用技巧

1、执行cmd命令快速切换执行DOS指令。可以执行exit退回原界面。

2、MobaXterm界面风格、主体、字体以及相关快捷方式设置。MobaXterm --> Settings --> Configuration

3、MobaXterm取消自动断开SSH会话。

posted @ 2019-09-11 13:19 paulwong 阅读(42) | 评论 (0)编辑 收藏

nohup命令不输出OUTPUT到实体文件

https://stackoverflow.com/questions/10408816/how-do-i-use-the-nohup-command-without-getting-nohup-out

posted @ 2019-09-06 10:15 paulwong 阅读(40) | 评论 (0)编辑 收藏

MONGODB拓展操作

MongoTemplate使用Cursor处理大数量的数据
https://blog.csdn.net/ClementAD/article/details/55210973

Spring Data MongoDB系列之三:数据库批量操作
https://blog.csdn.net/sinat_24044957/article/details/80646292

Distinct in Spring Data MongoDB
https://stackoverflow.com/questions/19203724/distinct-in-spring-data-mongodb

MONGODB SQL语句
http://www.runoob.com/mongodb/mongodb-indexing.html


posted @ 2019-09-03 15:52 paulwong 阅读(44) | 评论 (0)编辑 收藏

MAVEN中SCOPE为SYSTEM的JAR包如何打包?

https://stackoverflow.com/questions/2065928/maven-2-assembly-with-dependencies-jar-under-scope-system-not-included

在pom.xml中加入REPOSITRY:
<repositories>
  <repository>
    <id>my</id>
    <url>file://${basedir}/my-repo</url>
  </repository>
</repositories>

file://${basedir}/my-repo 中放JAR包的结构要和MAVEN库.m2中保持一致。

DEPENDENCY还是按正常的来,不加SYSTEM SCOPE:
<dependency>
  <groupId>sourceforge.jchart2d</groupId>
  <artifactId>jchart2d</artifactId>
  <version>3.1.0</version>
</dependency>


这里涉及到一个问题:如何将JAR比较优雅地放到file://${basedir}/my-repo 中,可以使用deploy,如果JAR包不是由MAVEN打出来的,命令如下:
mvn deploy:deploy-file 
-DgroupId={yourProject} \ 
-DartifactId={yourProject}\
-Dfile={yourFile}\ // jar 包路径
-Durl={URL} \// 私服URL
-DrepositoryId=releases\
-Dpackaging=jar\ // 指定格式,如果不写,一句 pom 文件中
-DpomFile=pom.xml // 指定该 jar 包的 pom 文件,如不指定,将生成一个默认的 pom——导致不可用

如果JAR包是由MAVEN打出来的,命令如下:
mvn deploy:deploy-file 
-Dfile={yourFile}\ // jar 包路径
-Durl=file://${basedir}/my-repo
-DpomFile=pom.xml // 指定该 jar 包的 pom 文件,如不指定,将生成一个默认的 pom——导致不可用

MAVEN DEPLOY PLUGIN:
<plugin>
   <groupId>org.apache.maven.plugins</groupId>
   <artifactId>maven-deploy-plugin</artifactId>
   <version>2.8.2</version>
   <executions>
      <execution>
         <id>default-cli</id>
         <phase>package</phase>
         <goals>
            <goal>deploy-file</goal>
         </goals>
         <configuration>
            <file>target/COMPOSANT-A-1.0.tar.gz</file>
            <repositoryId>nexus</repositoryId>
            <groupId>COMPOSANTS</groupId>
            <artifactId>COMPOSANT-A</artifactId>
            <version>1.0</version>
            <generatePom>false</generatePom>
            <packaging>tar.gz</packaging>
            <url>http://urlRepo:8080/nexus/content/repositories/snapshots</url>
         </configuration>
      </execution>
   </executions>
</plugin>

posted @ 2019-08-27 17:32 paulwong 阅读(73) | 评论 (0)编辑 收藏

淘宝千万级并发分布式架构的14次演进

一、概述

本文以淘宝作为例子,介绍从一百个并发到千万级并发情况下服务端的架构的演进过程,同时列举出每个演进阶段会遇到的相关技术,让大家对架构的演进有一个整体的认知,文章最后汇总了一些架构设计的原则。

二、基本概念

在介绍架构之前,为了避免部分读者对架构设计中的一些概念不了解,下面对几个最基础的概念进行介绍:

1)分布式

系统中的多个模块在不同服务器上部署,即可称为分布式系统,如Tomcat和数据库分别部署在不同的服务器上,或两个相同功能的Tomcat分别部署在不同服务器上。

2)高可用

系统中部分节点失效时,其他节点能够接替它继续提供服务,则可认为系统具有高可用性。

3)集群

一个特定领域的软件部署在多台服务器上并作为一个整体提供一类服务,这个整体称为集群。如Zookeeper中的Master和Slave分别部署在多台服务器上,共同组成一个整体提供集中配置服务。

在常见的集群中,客户端往往能够连接任意一个节点获得服务,并且当集群中一个节点掉线时,其他节点往往能够自动的接替它继续提供服务,这时候说明集群具有高可用性。

4)负载均衡

请求发送到系统时,通过某些方式把请求均匀分发到多个节点上,使系统中每个节点能够均匀的处理请求负载,则可认为系统是负载均衡的。

5)正向代理和反向代理

  • 系统内部要访问外部网络时,统一通过一个代理服务器把请求转发出去,在外部网络看来就是代理服务器发起的访问,此时代理服务器实现的是正向代理;
  • 当外部请求进入系统时,代理服务器把该请求转发到系统中的某台服务器上,对外部请求来说,与之交互的只有代理服务器,此时代理服务器实现的是反向代理。

简单来说,正向代理是代理服务器代替系统内部来访问外部网络的过程,反向代理是外部请求访问系统时通过代理服务器转发到内部服务器的过程。

三、架构演进

单机架构

淘宝千万级并发分布式架构的14次演进

以淘宝作为例子。在网站最初时,应用数量与用户数都较少,可以把Tomcat和数据库部署在同一台服务器上。浏览器往www.taobao.com发起请求时,首先经过DNS服务器(域名系统)把域名转换为实际IP地址10.102.4.1,浏览器转而访问该IP对应的Tomcat。

随着用户数的增长,Tomcat和数据库之间竞争资源,单机性能不足以支撑业务。

第一次演进:Tomcat与数据库分开部署

淘宝千万级并发分布式架构的14次演进

Tomcat和数据库分别独占服务器资源,显著提高两者各自性能。

随着用户数的增长,并发读写数据库成为瓶颈。

第二次演进:引入本地缓存和分布式缓存

淘宝千万级并发分布式架构的14次演进

在Tomcat同服务器上或同JVM中增加本地缓存,并在外部增加分布式缓存,缓存热门商品信息或热门商品的html页面等。通过缓存能把绝大多数请求在读写数据库前拦截掉,大大降低数据库压力。

其中涉及的技术包括:使用memcached作为本地缓存,使用Redis作为分布式缓存,还会涉及缓存一致性、缓存穿透/击穿、缓存雪崩、热点数据集中失效等问题。

缓存抗住了大部分的访问请求,随着用户数的增长,并发压力主要落在单机的Tomcat上,响应逐渐变慢。

第三次演进:引入反向代理实现负载均衡

淘宝千万级并发分布式架构的14次演进

在多台服务器上分别部署Tomcat,使用反向代理软件(Nginx)把请求均匀分发到每个Tomcat中。

此处假设Tomcat最多支持100个并发,Nginx最多支持50000个并发,那么理论上Nginx把请求分发到500个Tomcat上,就能抗住50000个并发。

其中涉及的技术包括:Nginx、HAProxy,两者都是工作在网络第七层的反向代理软件,主要支持http协议,还会涉及session共享、文件上传下载的问题。

反向代理使应用服务器可支持的并发量大大增加,但并发量的增长也意味着更多请求穿透到数据库,单机的数据库最终成为瓶颈。

第四次演进:数据库读写分离

淘宝千万级并发分布式架构的14次演进

把数据库划分为读库和写库,读库可以有多个,通过同步机制把写库的数据同步到读库,对于需要查询最新写入数据场景,可通过在缓存中多写一份,通过缓存获得最新数据。

其中涉及的技术包括:Mycat,它是数据库中间件,可通过它来组织数据库的分离读写和分库分表,客户端通过它来访问下层数据库,还会涉及数据同步,数据一致性的问题。

业务逐渐变多,不同业务之间的访问量差距较大,不同业务直接竞争数据库,相互影响性能。

第五次演进:数据库按业务分库

淘宝千万级并发分布式架构的14次演进

把不同业务的数据保存到不同的数据库中,使业务之间的资源竞争降低,对于访问量大的业务,可以部署更多的服务器来支撑。

这样同时导致跨业务的表无法直接做关联分析,需要通过其他途径来解决,但这不是本文讨论的重点,有兴趣的可以自行搜索解决方案。

随着用户数的增长,单机的写库会逐渐会达到性能瓶颈。

第六次演进:把大表拆分为小表

淘宝千万级并发分布式架构的14次演进

比如针对评论数据,可按照商品ID进行hash,路由到对应的表中存储;针对支付记录,可按照小时创建表,每个小时表继续拆分为小表,使用用户ID或记录编号来路由数据。

只要实时操作的表数据量足够小,请求能够足够均匀的分发到多台服务器上的小表,那数据库就能通过水平扩展的方式来提高性能。其中前面提到的Mycat也支持在大表拆分为小表情况下的访问控制。

这种做法显著的增加了数据库运维的难度,对DBA的要求较高。数据库设计到这种结构时,已经可以称为分布式数据库,但是这只是一个逻辑的数据库整体,数据库里不同的组成部分是由不同的组件单独来实现的。

如分库分表的管理和请求分发,由Mycat实现,SQL的解析由单机的数据库实现,读写分离可能由网关和消息队列来实现,查询结果的汇总可能由数据库接口层来实现等等,这种架构其实是MPP(大规模并行处理)架构的一类实现。

目前开源和商用都已经有不少MPP数据库,开源中比较流行的有Greenplum、TiDB、Postgresql XC、HAWQ等,商用的如南大通用的GBase、睿帆科技的雪球DB、华为的LibrA等等。

不同的MPP数据库的侧重点也不一样,如TiDB更侧重于分布式OLTP场景,Greenplum更侧重于分布式OLAP场景。

这些MPP数据库基本都提供了类似Postgresql、Oracle、MySQL那样的SQL标准支持能力,能把一个查询解析为分布式的执行计划分发到每台机器上并行执行,最终由数据库本身汇总数据进行返回。

也提供了诸如权限管理、分库分表、事务、数据副本等能力,并且大多能够支持100个节点以上的集群,大大降低了数据库运维的成本,并且使数据库也能够实现水平扩展。

数据库和Tomcat都能够水平扩展,可支撑的并发大幅提高,随着用户数的增长,最终单机的Nginx会成为瓶颈。

第七次演进:使用LVS或F5来使多个Nginx负载均衡

淘宝千万级并发分布式架构的14次演进

由于瓶颈在Nginx,因此无法通过两层的Nginx来实现多个Nginx的负载均衡。

图中的LVS和F5是工作在网络第四层的负载均衡解决方案,其中LVS是软件,运行在操作系统内核态,可对TCP请求或更高层级的网络协议进行转发,因此支持的协议更丰富,并且性能也远高于Nginx,可假设单机的LVS可支持几十万个并发的请求转发;F5是一种负载均衡硬件,与LVS提供的能力类似,性能比LVS更高,但价格昂贵。

由于LVS是单机版的软件,若LVS所在服务器宕机则会导致整个后端系统都无法访问,因此需要有备用节点。可使用keepalived软件模拟出虚拟IP,然后把虚拟IP绑定到多台LVS服务器上,浏览器访问虚拟IP时,会被路由器重定向到真实的LVS服务器,当主LVS服务器宕机时,keepalived软件会自动更新路由器中的路由表,把虚拟IP重定向到另外一台正常的LVS服务器,从而达到LVS服务器高可用的效果。

此处需要注意的是,上图中从Nginx层到Tomcat层这样画并不代表全部Nginx都转发请求到全部的Tomcat。

在实际使用时,可能会是几个Nginx下面接一部分的Tomcat,这些Nginx之间通过keepalived实现高可用,其他的Nginx接另外的Tomcat,这样可接入的Tomcat数量就能成倍的增加。

由于LVS也是单机的,随着并发数增长到几十万时,LVS服务器最终会达到瓶颈,此时用户数达到千万甚至上亿级别,用户分布在不同的地区,与服务器机房距离不同,导致了访问的延迟会明显不同。

第八次演进:通过DNS轮询实现机房间的负载均衡

淘宝千万级并发分布式架构的14次演进

在DNS服务器中可配置一个域名对应多个IP地址,每个IP地址对应到不同的机房里的虚拟IP。

当用户访问www.taobao.com时,DNS服务器会使用轮询策略或其他策略,来选择某个IP供用户访问。此方式能实现机房间的负载均衡,至此,系统可做到机房级别的水平扩展,千万级到亿级的并发量都可通过增加机房来解决,系统入口处的请求并发量不再是问题。

随着数据的丰富程度和业务的发展,检索、分析等需求越来越丰富,单单依靠数据库无法解决如此丰富的需求。

第九次演进:引入NoSQL数据库和搜索引擎等技术

淘宝千万级并发分布式架构的14次演进

当数据库中的数据多到一定规模时,数据库就不适用于复杂的查询了,往往只能满足普通查询的场景。

对于统计报表场景,在数据量大时不一定能跑出结果,而且在跑复杂查询时会导致其他查询变慢,对于全文检索、可变数据结构等场景,数据库天生不适用。

因此需要针对特定的场景,引入合适的解决方案。如对于海量文件存储,可通过分布式文件系统HDFS解决,对于key value类型的数据,可通过HBase和Redis等方案解决,对于全文检索场景,可通过搜索引擎如ElasticSearch解决,对于多维分析场景,可通过Kylin或Druid等方案解决。

当然,引入更多组件同时会提高系统的复杂度,不同的组件保存的数据需要同步,需要考虑一致性的问题,需要有更多的运维手段来管理这些组件等。

引入更多组件解决了丰富的需求,业务维度能够极大扩充,随之而来的是一个应用中包含了太多的业务代码,业务的升级迭代变得困难。

第十次演进:大应用拆分为小应用

淘宝千万级并发分布式架构的14次演进

按照业务板块来划分应用代码,使单个应用的职责更清晰,相互之间可以做到独立升级迭代。这时候应用之间可能会涉及到一些公共配置,可以通过分布式配置中心Zookeeper来解决。

不同应用之间存在共用的模块,由应用单独管理会导致相同代码存在多份,导致公共功能升级时全部应用代码都要跟着升级。

第十一次演进:复用的功能抽离成微服务

淘宝千万级并发分布式架构的14次演进

如用户管理、订单、支付、鉴权等功能在多个应用中都存在,那么可以把这些功能的代码单独抽取出来形成一个单独的服务来管理,这样的服务就是所谓的微服务。

应用和服务之间通过HTTP、TCP或RPC请求等多种方式来访问公共服务,每个单独的服务都可以由单独的团队来管理。此外,可以通过Dubbo、SpringCloud等框架实现服务治理、限流、熔断、降级等功能,提高服务的稳定性和可用性。

不同服务的接口访问方式不同,应用代码需要适配多种访问方式才能使用服务,此外,应用访问服务,服务之间也可能相互访问,调用链将会变得非常复杂,逻辑变得混乱。

第十二次演进:引入企业服务总线ESB屏蔽服务接口的访问差异

淘宝千万级并发分布式架构的14次演进

通过ESB统一进行访问协议转换,应用统一通过ESB来访问后端服务,服务与服务之间也通过ESB来相互调用,以此降低系统的耦合程度。这种单个应用拆分为多个应用,公共服务单独抽取出来来管理,并使用企业消息总线来解除服务之间耦合问题的架构,就是所谓的SOA(面向服务)架构,这种架构与微服务架构容易混淆,因为表现形式十分相似。

个人理解,微服务架构更多是指把系统里的公共服务抽取出来单独运维管理的思想,而SOA架构则是指一种拆分服务并使服务接口访问变得统一的架构思想,SOA架构中包含了微服务的思想。

业务不断发展,应用和服务都会不断变多,应用和服务的部署变得复杂,同一台服务器上部署多个服务还要解决运行环境冲突的问题,此外,对于如大促这类需要动态扩缩容的场景,需要水平扩展服务的性能,就需要在新增的服务上准备运行环境,部署服务等,运维将变得十分困难。

第十三次演进:引入容器化技术实现运行环境隔离与动态服务管理

淘宝千万级并发分布式架构的14次演进

目前最流行的容器化技术是Docker,最流行的容器管理服务是Kubernetes(K8S),应用/服务可以打包为Docker镜像,通过K8S来动态分发和部署镜像。

Docker镜像可理解为一个能运行你的应用/服务的最小的操作系统,里面放着应用/服务的运行代码,运行环境根据实际的需要设置好。把整个“操作系统”打包为一个镜像后,就可以分发到需要部署相关服务的机器上,直接启动Docker镜像就可以把服务起起来,使服务的部署和运维变得简单。

在大促的之前,可以在现有的机器集群上划分出服务器来启动Docker镜像,增强服务的性能,大促过后就可以关闭镜像,对机器上的其他服务不造成影响(在3.14节之前,服务运行在新增机器上需要修改系统配置来适配服务,这会导致机器上其他服务需要的运行环境被破坏)。

使用容器化技术后服务动态扩缩容问题得以解决,但是机器还是需要公司自身来管理,在非大促的时候,还是需要闲置着大量的机器资源来应对大促,机器自身成本和运维成本都极高,资源利用率低。

第十四次演进:以云平台承载系统

淘宝千万级并发分布式架构的14次演进

系统可部署到公有云上,利用公有云的海量机器资源,解决动态硬件资源的问题,在大促的时间段里,在云平台中临时申请更多的资源,结合Docker和K8S来快速部署服务,在大促结束后释放资源,真正做到按需付费,资源利用率大大提高,同时大大降低了运维成本。

所谓的云平台,就是把海量机器资源,通过统一的资源管理,抽象为一个资源整体。在之上可按需动态申请硬件资源(如CPU、内存、网络等),并且之上提供通用的操作系统,提供常用的技术组件(如Hadoop技术栈,MPP数据库等)供用户使用,甚至提供开发好的应用。用户不需要关系应用内部使用了什么技术,就能够解决需求(如音视频转码服务、邮件服务、个人博客等)。在云平台中会涉及如下几个概念:

  • IaaS:基础设施即服务。对应于上面所说的机器资源统一为资源整体,可动态申请硬件资源的层面;
  • PaaS:平台即服务。对应于上面所说的提供常用的技术组件方便系统的开发和维护;
  • SaaS:软件即服务。对应于上面所说的提供开发好的应用或服务,按功能或性能要求付费。

至此,以上所提到的从高并发访问问题,到服务的架构和系统实施的层面都有了各自的解决方案,但同时也应该意识到,在上面的介绍中,其实是有意忽略了诸如跨机房数据同步、分布式事务实现等等的实际问题,这些问题以后有机会再拿出来单独讨论。

四、 架构设计总结

架构的调整是否必须按照上述演变路径进行?

不是的,以上所说的架构演变顺序只是针对某个侧面进行单独的改进,在实际场景中,可能同一时间会有几个问题需要解决,或者可能先达到瓶颈的是另外的方面,这时候就应该按照实际问题实际解决。

如在政府类的并发量可能不大,但业务可能很丰富的场景,高并发就不是重点解决的问题,此时优先需要的可能会是丰富需求的解决方案。

对于将要实施的系统,架构应该设计到什么程度?

对于单次实施并且性能指标明确的系统,架构设计到能够支持系统的性能指标要求就足够了,但要留有扩展架构的接口以便不备之需。对于不断发展的系统,如电商平台,应设计到能满足下一阶段用户量和性能指标要求的程度,并根据业务的增长不断的迭代升级架构,以支持更高的并发和更丰富的业务。

服务端架构和大数据架构有什么区别?

所谓的“大数据”其实是海量数据采集清洗转换、数据存储、数据分析、数据服务等场景解决方案的一个统称。

在每一个场景都包含了多种可选的技术,如数据采集有Flume、Sqoop、Kettle等,数据存储有分布式文件系统HDFS、FastDFS,NoSQL数据库HBase、MongoDB等,数据分析有Spark技术栈、机器学习算法等。

总的来说大数据架构就是根据业务的需求,整合各种大数据组件组合而成的架构,一般会提供分布式存储、分布式计算、多维分析、数据仓库、机器学习算法等能力。而服务端架构更多指的是应用组织层面的架构,底层能力往往是由大数据架构来提供。

有没有一些架构设计的原则?

1)N+1设计

系统中的每个组件都应做到没有单点故障。

2)回滚设计

确保系统可以向前兼容,在系统升级时应能有办法回滚版本。

3)禁用设计

应该提供控制具体功能是否可用的配置,在系统出现故障时能够快速下线功能。

4)监控设计

在设计阶段就要考虑监控的手段。

5)多活数据中心设计

若系统需要极高的高可用,应考虑在多地实施数据中心进行多活,至少在一个机房断电的情况下系统依然可用

6)采用成熟的技术

刚开发的或开源的技术往往存在很多隐藏的bug,出了问题没有商业支持可能会是一个灾难。

7)资源隔离设计

应避免单一业务占用全部资源。

8)架构应能水平扩展

系统只有做到能水平扩展,才能有效避免瓶颈问题

9)非核心则购买

非核心功能若需要占用大量的研发资源才能解决,则考虑购买成熟的产品。

10)使用商用硬件

商用硬件能有效降低硬件故障的机率。

11)快速迭代

系统应该快速开发小功能模块,尽快上线进行验证,早日发现问题大大降低系统交付的风险。

12)无状态设计

服务接口应该做成无状态的,当前接口的访问不依赖于接口上次访问的状态。

设计到此结束,其实,解决方案有很多,但是这个只是我采用的,觉得最轻便的一个。

原创文章,转载请注明: 转载自并发编程网 – ifeve.com本文链接地址: 淘宝千万级并发分布式架构的14次演进

posted @ 2019-08-16 10:05 paulwong 阅读(143) | 评论 (0)编辑 收藏

Guide to the Most Important JVM Parameters

https://www.baeldung.com/jvm-parametersa

Optimising Your Minecraft: Jvm Arguments
https://xealgaming.net/threads/optimising-your-minecraft-jvm-arguments.4758/

posted @ 2019-08-01 16:55 paulwong 阅读(40) | 评论 (0)编辑 收藏

JVM内存配置

JVM内存主要分为两个部分,分别是PermanentSapce和HeapSpace。

PermantSpace主要负责存放加载的Class类级对象如class本身,method,field等反射对象,一般不用配置。

JVM的Heap区可以通过-X参数来设定。HeapSpace= {Old + NEW {= Eden , from, to } }

当一个URL被访问时,内存申请过程如下:

  1. JVM会试图为相关Java对象在Eden中初始化一块内存区域 
  2. 当Eden空间足够时,内存申请结束。否则到下一步 
  3. JVM试图释放在Eden中所有不活跃的对象(这属于1或更高级的垃圾回收), 释放后若Eden空间仍然不足以放入新对象,则试图将部分Eden中活跃对象放入Survivor区
  4.  Survivor区被用来作为Eden及OLD的中间交换区域,当OLD区空间足够时,Survivor区的对象会被移到Old区,否则会被保留在Survivor区 
  5. 当OLD区空间不够时,JVM会在OLD区进行完全的垃圾收集(0级) 
  6. 完全垃圾收集后,若Survivor及OLD区仍然无法存放从Eden复制过来的部分对象,导致JVM无法在Eden区为新对象创建内存区域,则出现”out of memory错误”

Xms/Xmx:定义NEW+OLD段的总尺寸,ms为JVM启动时NEW+OLD的内存大小;mx为最大可占用的NEW+OLD内存大小。。在用户生产环境上一般将这两个值设为相同,以减少运行期间系统在内存申请上所花的开销; 

NewSize/MaxNewSize:定义单独NEW段的尺寸,NewSize为JVM启动时NEW的内存大小;MaxNewSize为最大可占用的NEW的内存大小。在用户生产环境上一般将这两个值设为相同,以减少运行期间系统在内存申请上所花的开销;

Xms/Xmx和NewSize/MaxNewSize定义好后,OLD区间也自然定义完毕了,即OLD区初始大小=(Xms-NewSize),OLD区最大可占用大小=(Xmx-MaxNewSize); 

PermSize/MaxPermSize:定义Perm段的尺寸,PermSize为JVM启动时Perm的内存大小;MaxPermSize为最大可占用的Perm内存大小。在用户生产环境上一般将这两个值设为相同,以减少运行期间系统在内存申请上所花的开销。

posted @ 2019-08-01 16:44 paulwong 阅读(82) | 评论 (0)编辑 收藏

使用 Awaitility 测试异步代码

自动化工具 异步校验工具 awaitility 快速入门
https://testerhome.com/topics/7408


https://yanbin.blog/test-asyn-call-with-awaitility/

Introduction to Awaitlity
https://www.baeldung.com/awaitlity-testing




posted @ 2019-08-01 10:06 paulwong 阅读(65) | 评论 (0)编辑 收藏

Spring Batch JUnit test for multiple jobs


https://stackoverflow.com/questions/34217101/spring-batch-junit-test-for-multiple-jobs

@Configuration
public class TestBatchConfiguration implements MergedBeanDefinitionPostProcessor {

    @Autowired
    @Qualifier("JobA")
    private Job job;

    @Bean(name="jtestl")
    public JobLauncherTestUtils jobLauncherTestUtils() {
        JobLauncherTestUtils jobLauncherTestUtils = new JobLauncherTestUtils();
        jobLauncherTestUtils.setJob(job);
        return jobLauncherTestUtils;
    }

    /**
     * 
https://stackoverflow.com/questions/22416140/autowire-setter-override-with-java-config
     * This is needed to inject the correct job into JobLauncherTestUtils
     
*/
    @Override
    public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
        if(beanName.equals("jtestl")) {
            beanDefinition.getPropertyValues().add("job", getMyBeanFirstAImpl());
        }
    }

    private Object getMyBeanFirstAImpl() {
        return job;
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
}

posted @ 2019-07-31 10:48 paulwong 阅读(64) | 评论 (0)编辑 收藏

Keep SSH session alive

sshd (the server) closes the connection if it doesn't hear anything from the client for a while. You can tell your client to send a sign-of-life signal to the server once in a while.

The configuration for this is in the file "~/.ssh/config", create it if the configuration file does not exist. To send the signal every four minutes (240 seconds) to the remote host, put the following in your "~/.ssh/config" file.

Host remotehost:     HostName remotehost.com     ServerAliveInterval 240 

This is what I have in my "~/.ssh/config":

To enable it for all hosts use:

Host * ServerAliveInterval 240 

Also make sure to run:

chmod 600 ~/.ssh/config 

because the config file must not be world-readable.

posted @ 2019-07-30 13:46 paulwong 阅读(40) | 评论 (0)编辑 收藏

publish over ssh 实现 Jenkins 远程部署

Jenkins远程部署,一开始没有任何头绪,想了很多方案. 因为两台机器都是windows系统,所以想到publish over cifs, 但是这个网上资料太少,貌似只能内网使用。又想到了Jenkins 分布式构建,但是Jenkins构建的代码和产物最后自动拷贝到主节点。而远程机器其实是客户方的机器,所以这个分布式构建并不适用。最后还是选定publish over ssh来实现远程部署。 
请注意:在进行远程部署操作前,先要确保客户机能ssh 登录到远程机器。如果不知道SSH怎么登陆,请参考http://blog.csdn.net/flyingshuai/article/details/72897692 
1. 安装publish over ssh 插件,安装很简单,在此不表。 
2. 在Jenkins系统设置里找到Publish over SSH模块 
3. 用户名/密码方式登录的,系统设置里设置如下: 
4. 如果是证书登录的,系统设置里设置如下: 
5. Job设置,点击增加构建后操作步骤,选择send build artifacts over ssh, 设置如下: 
6. 文件上传到远程服务器后,还有一些后续操作,比如,替换数据库配置文件。可以把bat命令写到一个批处理文件中,存到服务器上。Exec command填写批处理文件的绝对路径。如上图所示。
关于bat脚本: 
如果每次都需要替换同样的文件,用copy /y 是无条件覆盖,不会询问。而xcopy可以实现批量拷贝文件和文件夹。如果文件较多可用此命令 
注意脚本运行失败,构建也会显示蓝色成功图标,所以一定要打开控制台输出,看是否真的成功。
--------------------- 
作者:flyingshuai 
来源:CSDN 
原文:https://blog.csdn.net/flyingshuai/article/details/72898665 
版权声明:本文为博主原创文章,转载请附上博文链接!

posted @ 2019-07-25 09:33 paulwong 阅读(85) | 评论 (0)编辑 收藏

How do I clear my Jenkins/Hudson build history?

     摘要: 问题:I recently updated the configuration of one of my hudson builds. The build history is out of sync. Is there a way to clear my build history?Please and thank you回答1:If you click Manage Hudson / Relo...  阅读全文

posted @ 2019-07-24 16:18 paulwong 阅读(52) | 评论 (0)编辑 收藏

Springboot ActiveMQ jmsTemplate配置

@Configuration
@DependsOn(value="cachingConnectionFactory")
public class JmsTemplateConfiguration {

@Value("${wechat.sendmessage.queue}")
private String queueName;

@Value("${wechat.sendmessage.topic}")
private String topicName;

@Value("${spring.jms.pub-sub-domain}")
private boolean isPubSubDomain;


/**
 * 定义点对点队列
 * 
@return
 
*/
@Bean
public Queue queue() {
    return new ActiveMQQueue(queueName);
}



/**
 * 定义一个主题
 * 
@return
 
*/
@Bean
public Topic topic() {
    return new ActiveMQTopic(topicName);
}

private final ObjectProvider<DestinationResolver> destinationResolver;
private final ObjectProvider<MessageConverter> messageConverter;
private final CachingConnectionFactory cachingConnectionFactory;

@Autowired
public JmsTemplateConfiguration(ObjectProvider<DestinationResolver> destinationResolver,
                                ObjectProvider<MessageConverter> messageConverter,
                                CachingConnectionFactory cachingConnectionFactory) {
    this.destinationResolver = destinationResolver;
    this.messageConverter = messageConverter;
    this.cachingConnectionFactory = cachingConnectionFactory;
}

/**
 * 配置队列生产者的JmsTemplate
 * 
@return JmsTemplate
 
*/
@Bean(name="jmsQueueTemplate")
public JmsTemplate jmsQueueTemplate() {
    //设置创建连接的工厂
    
//JmsTemplate jmsTemplate = new JmsTemplate(connectionFactory);
    
//优化连接工厂,这里应用缓存池 连接工厂就即可
    JmsTemplate jmsTemplate = new JmsTemplate(cachingConnectionFactory);
    //设置默认消费topic
   
//jmsTemplate.setDefaultDestination(topic());
    
//设置P2P队列消息类型
    jmsTemplate.setPubSubDomain(isPubSubDomain);

    DestinationResolver destinationResolver = (DestinationResolver) this.destinationResolver.getIfUnique();
    if (destinationResolver != null) {
        jmsTemplate.setDestinationResolver(destinationResolver);
    }
    MessageConverter messageConverter = (MessageConverter) this.messageConverter.getIfUnique();
    if (messageConverter != null) {
        jmsTemplate.setMessageConverter(messageConverter);
    }
    //deliveryMode, priority, timeToLive 的开关,要生效,必须配置为true,默认false
    jmsTemplate.setExplicitQosEnabled(true);
    //DeliveryMode.NON_PERSISTENT=1:非持久 ; DeliveryMode.PERSISTENT=2:持久
    
//定义持久化后节点挂掉以后,重启可以继续消费.
    jmsTemplate.setDeliveryMode(DeliveryMode.PERSISTENT);
    //默认不开启事务
    System.out.println("默认是否开启事务:"+jmsTemplate.isSessionTransacted());
    //如果不启用事务,则会导致XA事务失效;
    
//作为生产者如果需要支持事务,则需要配置SessionTransacted为true
  
//jmsTemplate.setSessionTransacted(true);
    
//消息的应答方式,需要手动确认,此时SessionTransacted必须被设置为false,且为Session.CLIENT_ACKNOWLEDGE模式
    
//Session.AUTO_ACKNOWLEDGE  消息自动签收
    
//Session.CLIENT_ACKNOWLEDGE  客户端调用acknowledge方法手动签收
    
//Session.DUPS_OK_ACKNOWLEDGE 不必必须签收,消息可能会重复发送
    jmsTemplate.setSessionAcknowledgeMode(Session.CLIENT_ACKNOWLEDGE);
    return jmsTemplate;
}

/**
 * 配置发布订阅生产者的JmsTemplate
 * 
@return JmsTemplate
 
*/
@Bean(name="jmsTopicTemplate")
public JmsTemplate jmsTopicTemplate() {
    //设置创建连接的工厂
   
//JmsTemplate jmsTemplate = new JmsTemplate(connectionFactory);
    
//优化连接工厂,这里应用缓存池 连接工厂就即可
    JmsTemplate jmsTemplate = new JmsTemplate(cachingConnectionFactory);
    //设置默认消费topic
  
//jmsTemplate.setDefaultDestination(topic());
    
//设置发布订阅消息类型
    jmsTemplate.setPubSubDomain(isPubSubDomain);


    //deliveryMode, priority, timeToLive 的开关,要生效,必须配置为true,默认false
    jmsTemplate.setExplicitQosEnabled(true);
    //DeliveryMode.NON_PERSISTENT=1:非持久 ; DeliveryMode.PERSISTENT=2:持久
    jmsTemplate.setDeliveryMode(DeliveryMode.PERSISTENT);

    //默认不开启事务
    System.out.println("是否开启事务"+jmsTemplate.isSessionTransacted());
    //如果session带有事务,并且事务成功提交,则消息被自动签收。如果事务回滚,则消息会被再次传送。
    
//jmsTemplate.setSessionTransacted(true);

    
//不带事务的session的签收方式,取决于session的配置。
    
//默认消息确认方式为1,即AUTO_ACKNOWLEDGE
    System.out.println("是否消息确认方式"+jmsTemplate.getSessionAcknowledgeMode());

    //消息的应答方式,需要手动确认,此时SessionTransacted必须被设置为false,且为Session.CLIENT_ACKNOWLEDGE模式
    
//Session.AUTO_ACKNOWLEDGE  消息自动签收
    
//Session.CLIENT_ACKNOWLEDGE  客户端调用acknowledge方法手动签收
    
//Session.DUPS_OK_ACKNOWLEDGE 不必必须签收,消息可能会重复发送
    jmsTemplate.setSessionAcknowledgeMode(Session.CLIENT_ACKNOWLEDGE);

    return jmsTemplate;
}

}

posted @ 2019-07-24 11:40 paulwong 阅读(213) | 评论 (0)编辑 收藏

Enterprise Integration Patterns

Why Enterprise Integration Patterns?

Enterprise integration is too complex to be solved with a simple 'cookbook' approach. Instead, patterns can provide guidance by documenting the kind of experience that usually lives only in architects' heads: they are accepted solutions to recurring problems within a given context. Patterns are abstract enough to apply to most integration technologies, but specific enough to provide hands-on guidance to designers and architects. Patterns also provide a vocabulary for developers to efficiently describe their solution.

Patterns are not 'invented'; they are harvested from repeated use in practice. If you have built integration solutions, it is likely that you have used some of these patterns, maybe in slight variations and maybe calling them by a different name. The purpose of this site is not to "invent" new approaches, but to present a coherent collection of relevant and proven patterns, which in total form an integration pattern language.

Despite the 700+ pages, our book covers only a fraction of patterns (and the problems to be solved) in the integration space. The current patterns focus on Messaging, which forms the basis of most other integration patterns. We have started to harvest more patterns but are realizing (once again) how much work documenting these patterns really is. So please stay tuned.

Messaging Patterns

We have documented 65 messaging patterns, organized as follows:

Message Construct.
Message
Command Message
Document Message
Event Message
Request-Reply
Return Address
Correlation Identifier
Message Sequence
Message Expiration
Format Indicator
Message Routing
Pipes-and-Filters
Message Router
Content-based Router
Message Filter
Dynamic Router
Recipient List
Splitter
Aggregator
Resequencer
Composed Msg. Processor
Scatter-Gather
Routing Slip
Process Manager
Message Broker
Message
Transformation
Message Translator
Envelope Wrapper
Content Enricher
Content Filter
Claim Check
Normalizer
Canonical Data Model
Messaging Endpoints
Message Endpoint
Messaging Gateway
Messaging Mapper
Transactional Client
Polling Consumer
Event-driven Consumer
Competing Consumers
Message Dispatcher
Selective Consumer
Durable Subscriber
Idempotent Receiver
Service Activator
Messaging Channels
Message Channel
Point-to-Point Channel
Publish-Subscr. Channel
Datatype Channel
Invalid Message Channel
Dead Letter Channel
Guaranteed Delivery
Channel Adapter
Messaging Bridge
Message Bus
Systems Mgmt.
Control Bus
Detour
Wire Tap
Message History
Message Store
Smart Proxy
Test Message
Channel Purger


https://www.enterpriseintegrationpatterns.com/patterns/messaging/index.html

posted @ 2019-07-18 14:11 paulwong 阅读(58) | 评论 (0)编辑 收藏

SPRING BATCH & SPRING INTEGRATION TUTORIAL

Spring JMS Artemis Example 6 minute read

A detailed step-by-step tutorial on how to connect to Apache ActiveMQ Artemis using Spring JMS and Spring Boot.

Spring JMS Topic Example 5 minute read

A detailed step-by-step tutorial on how to publish/subscribe to a JMS topic using Spring JMS and Spring Boot.

Spring JMS Integration Example12 minute read

A detailed step-by-step tutorial on how to connect to an ActiveMQ JMS broker using Spring Integration and Spring Boot.

Spring JMS Listener Example 7 minute read

A detailed step-by-step tutorial on how a Spring JMS listener works in combination with Spring Boot.

Spring JMS JmsTemplate Example 7 minute read

A detailed step-by-step tutorial on how to use JmsTemplate in combination with Spring JMS and Spring Boot.

Spring JMS Message Converter Example5 minute read

A detailed step-by-step tutorial on how to implement a message converter using Spring JMS and Spring Boot.

Spring Batch Admin Example 11 minute read

A detailed step-by-step tutorial on how to use a Spring Boot admin UI to manage Spring Batch jobs.

Spring Batch Example 11 minute read

A detailed step-by-step tutorial on how to implement a Hello World Spring Batch job using Spring Boot.

posted @ 2019-07-18 13:21 paulwong 阅读(60) | 评论 (0)编辑 收藏

Spring Integration Java DSL

This time I decided to play a little bit with Spring Integration Java DSL. Which has been merged directly into Spring Integration Core 5.0, which is smart and obvious move because:

  • Everyone starting the new Spring projects based on Java Config uses that
  • SI Java DSL enables you to use new powerfull Java 8 features like Lambdas
  • You can build your flow using the Builder pattern based on IntegrationFlowBuilder

Let's take a look on the samples howto use that based on ActiveMQ JMS.


https://bitbucket.org/tomask79/spring-integration-java-dsl/src/master/

posted @ 2019-07-18 13:16 paulwong 阅读(68) | 评论 (0)编辑 收藏

SPRING BATCH remote chunking模式下可同时处理多文件

SPRING BATCH remote chunking模式下,如果要同一时间处理多个文件,按DEMO的默认配置,是会报错的,这是由于多个文件的处理的MASTER方,是用同一个QUEUE名,这样SLAVE中处理多个JOB INSTANCE时,会返回不同的JOB-INSTANCE-ID,导致报错。

这时需更改SPRING BATCH使用SPRING INTEGRATION的模式中的GATEWAY组件。

GATEWAY组件是工作在REQUEST/RESPONSE模式下,即发一个MESSAGE到某一QUEUE时,要从REPLY QUEUE等到CONSUMER返回结果时,才往下继续。

OUTBOUND GATEWAY:从某一CHANNEL获取MESSAGE,发往REQUEST QUEUE,从REPLY QUEUE等到CONSUMER返回结果,将此MESSAGE发往下一CHANNEL。

INBOUND GATEWAY:从某一QUEUE获取MESSAGE,发往某一REQUEST CHANNEL,从REPLY CHANNEL等到返回结果,将此MESSAGE发往下一QUEUE。

详情参见此文:https://blog.csdn.net/alexlau8/article/details/78056064

    <!-- Master jms -->
    <int:channel id="MasterRequestChannel">
        <int:dispatcher task-executor="RequestPublishExecutor"/>
    </int:channel>
    <task:executor id="RequestPublishExecutor" pool-size="5-10" queue-capacity="0"/>
<!--    <int-jms:outbound-channel-adapter 
        connection-factory="connectionFactory" 
        destination-name="RequestQueue" 
        channel="MasterRequestChannel"/> 
-->

    <int:channel id="MasterReplyChannel"/>
<!--    <int-jms:message-driven-channel-adapter 
        connection-factory="connectionFactory" 
        destination-name="ReplyQueue"
        channel="MasterReplyChannel"/> 
-->

    <int-jms:outbound-gateway
        
connection-factory="connectionFactory"
        correlation-key
="JMSCorrelationID"
        request-channel
="MasterRequestChannel"
        request-destination-name
="RequestQueue"
        receive-timeout
="30000"
        reply-channel
="MasterReplyChannel"
        reply-destination-name
="ReplyQueue"
        async
="true">
        <int-jms:reply-listener />
    </int-jms:outbound-gateway>

    <!-- Slave jms -->
    <int:channel id="SlaveRequestChannel"/>
<!--    <int-jms:message-driven-channel-adapter
        connection-factory="connectionFactory" 
        destination-name="RequestQueue"
        channel="SlaveRequestChannel"/> 
-->

    <int:channel id="SlaveReplyChannel"/>
<!--    <int-jms:outbound-channel-adapter 
        connection-factory="connectionFactory" 
        destination-name="ReplyQueue"
        channel="SlaveReplyChannel"/> 
-->

    <int-jms:inbound-gateway
        
connection-factory="connectionFactory"
        correlation-key
="JMSCorrelationID"
        request-channel
="SlaveRequestChannel"
        request-destination-name
="RequestQueue"
        reply-channel
="SlaveReplyChannel"
        default-reply-queue-name
="ReplyQueue"/>

MASTER配置
package com.paul.testspringbatch.config.master;

import javax.jms.ConnectionFactory;

import org.springframework.beans.factory.config.CustomScopeConfigurer;
//import org.springframework.batch.core.configuration.annotation.StepScope;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.context.annotation.Scope;
import org.springframework.context.support.SimpleThreadScope;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.config.EnableIntegration;
import org.springframework.integration.dsl.IntegrationFlow;
import org.springframework.integration.dsl.IntegrationFlows;
import org.springframework.integration.jms.JmsOutboundGateway;

import com.paul.testspringbatch.common.constant.IntegrationConstant;

@Configuration
@EnableIntegration
@Profile("batch-master")
public class IntegrationMasterConfiguration {
    
//    @Value("${broker.url}")
//    private String brokerUrl;


//    @Bean
//    public ActiveMQConnectionFactory connectionFactory() {
//        ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory();
//        connectionFactory.setBrokerURL(this.brokerUrl);
//        connectionFactory.setTrustAllPackages(true);
//        return connectionFactory;
//    }

    /*
     * Configure outbound flow (requests going to workers)
     
*/
    @Bean
//    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public DirectChannel requests() {
        return new DirectChannel();
    }

//    @Bean
//    public IntegrationFlow outboundFlow(ConnectionFactory connectionFactory) {
//        return IntegrationFlows
//                .from(requests())
//                .handle(Jms.outboundAdapter(connectionFactory).destination(IntegrationConstant.MASTER_REQUEST_DESTINATION))
//                .get();
//    }
    
     @Bean
     public CustomScopeConfigurer customScopeConfigurer() {
         CustomScopeConfigurer customScopeConfigurer = new CustomScopeConfigurer();
         customScopeConfigurer.addScope("thread", new SimpleThreadScope());
         return customScopeConfigurer;
     }
     
//     @Bean
//     public static BeanFactoryPostProcessor beanFactoryPostProcessor() {
//         return new BeanFactoryPostProcessor() {
//                
//             @Override
//             public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
//                    beanFactory.registerScope("thread", new SimpleThreadScope());
//                }
//              };
//     }
    
    /*
     * Configure inbound flow (replies coming from workers)
     
*/
    @Bean
    @Scope(value = "thread"/* , proxyMode = ScopedProxyMode.NO */)
//    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public QueueChannel replies() {
        return new QueueChannel();
    }

//    @Bean
//    public IntegrationFlow inboundFlow(ConnectionFactory connectionFactory) {
//        return IntegrationFlows
//                .from(Jms.messageDrivenChannelAdapter(connectionFactory).destination(IntegrationConstant.MASTER_REPLY_DESTINATION))
//                .channel(replies())
//                .get();
//    }

    @Bean
    public JmsOutboundGateway jmsOutboundGateway(ConnectionFactory connectionFactory) {
        JmsOutboundGateway jmsOutboundGateway = new JmsOutboundGateway();
        jmsOutboundGateway.setConnectionFactory(connectionFactory);
        jmsOutboundGateway.setRequestDestinationName(IntegrationConstant.MASTER_REQUEST_DESTINATION);//2. send the message to this destination
        jmsOutboundGateway.setRequiresReply(true);
        jmsOutboundGateway.setCorrelationKey(IntegrationConstant.JMS_CORRELATION_KEY);//3. let the broker filter the message
        jmsOutboundGateway.setAsync(true);//must be async, so that JMS_CORRELATION_KEY work
        jmsOutboundGateway.setUseReplyContainer(true);
        jmsOutboundGateway.setReplyDestinationName(IntegrationConstant.MASTER_REPLY_DESTINATION);//4. waiting the response from this destination
        jmsOutboundGateway.setReceiveTimeout(30_000);
        return jmsOutboundGateway;
    }

    @Bean
    public IntegrationFlow jmsOutboundGatewayFlow(ConnectionFactory connectionFactory) {
        return IntegrationFlows
                        .from(requests())//1. receive message from this channel
                        .handle(jmsOutboundGateway(connectionFactory))
                        .channel(replies())//5. send back the response to this channel
                        .get();
    }

}


SLAVE配置:
package com.paul.testspringbatch.config.slave;

import javax.jms.ConnectionFactory;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.config.EnableIntegration;
import org.springframework.integration.dsl.IntegrationFlow;
import org.springframework.integration.dsl.IntegrationFlows;
import org.springframework.integration.jms.dsl.Jms;

import com.paul.testspringbatch.common.constant.IntegrationConstant;

@Configuration
@EnableIntegration
@Profile("batch-slave")
public class IntegrationSlaveConfiguration {
    

    /*
     * Configure inbound flow (requests coming from the master)
     
*/
    @Bean
    public DirectChannel requests() {
        return new DirectChannel();
    }

//    @Bean
//    public IntegrationFlow inboundFlow(ConnectionFactory connectionFactory) {
//        return IntegrationFlows
//                .from(Jms.messageDrivenChannelAdapter(connectionFactory).destination("requests"))
//                .channel(requests())
//                .get();
//    }

    /*
     * Configure outbound flow (replies going to the master)
     
*/
    @Bean
    public DirectChannel replies() {
        return new DirectChannel();
    }

//    @Bean
//    public IntegrationFlow outboundFlow(ConnectionFactory connectionFactory) {
//        return IntegrationFlows
//                .from(replies())
//                .handle(Jms.outboundAdapter(connectionFactory).destination("replies"))
//                .get();
//    }

    @Bean
    public IntegrationFlow inboundGatewayFlow(ConnectionFactory connectionFactory) {
        return IntegrationFlows
                    .from(Jms
                            .inboundGateway(connectionFactory)
                            .destination(IntegrationConstant.SLAVE_HANDLE_MASTER_REQUEST_DESTINATION)//1. receive message from this channel.
                            .correlationKey(IntegrationConstant.JMS_CORRELATION_KEY)//2. let the broker filter the message
                            .requestChannel(requests())//3. send the message to this channel
                            .replyChannel(replies())//4. waitting the result from this channel
                            .defaultReplyQueueName(IntegrationConstant.SLAVE_RETURN_RESULT_DESTINATION)//5.send back the result to this destination to the master.
                            )
                    .get();
    }

}

posted @ 2019-07-16 14:38 paulwong 阅读(112) | 评论 (0)编辑 收藏

Build Messaging Between Ruby/Rails Applications with ActiveMQ

https://dev.to/kirillshevch/build-messaging-between-ruby-rails-applications-with-activemq-4fin

posted @ 2019-07-12 17:12 paulwong 阅读(54) | 评论 (0)编辑 收藏

STEP范围内的ROUTER

在SPRING BATCH中,通常ROUTER是针对STEP的,但是如果在一个STEP中有多个WRITER,每个WRITER是写不同文件的,因此需要一个STEP内的ROUTER,以便能ROUTE到不同的WRITER中。


https://gist.github.com/benas/bfe2be7386b99ce496425fac9ff35fb8

posted @ 2019-07-11 11:45 paulwong 阅读(39) | 评论 (0)编辑 收藏

动态改变SPRING BATCH 的 CHUNKSIZE

 在SPRING BATCH REMOTE CHUNKING的模式下:
SPRING BATCH 读文件时,是按一行一行来读取数据,再按CHUNKSIZE提交到REMOTE操作,有时要整合当前行和下几行,再决定CHUNKSIZE,以便相关的数据能在远程同一个PROCESSOR中按顺序进行处理,因为相关的数据被拆成几个CHUNK来处理的话,就有可能不按顺序来处理。这样就需要动态调整CHUNKSIZE。

参照如下:
https://stackoverflow.com/questions/37390602/spring-batch-custom-completion-policy-for-dynamic-chunk-size

并结合SingleItemPeekableItemReader(装饰者,允许查看下一条数据,真正的操作委托给代理)。

posted @ 2019-07-02 11:13 paulwong 阅读(97) | 评论 (0)编辑 收藏

RABBITMQ资源

消息队列之 RabbitMQ
https://www.jianshu.com/p/79ca08116d57

Spring Boot 中使用 RabbitMQ
https://juejin.im/post/59f194e06fb9a0451329ec53




posted @ 2019-06-28 10:24 paulwong 阅读(46) | 评论 (0)编辑 收藏

How to implement JMS ReplyTo using SpringBoot

Request-Response is a message-exchange-pattern. In some cases, a message producer may want the consumers to reply to a message. The JMSReplyTo header indicates which destination, if any, a JMS consumer should reply to. The JMSReplyTo header is set explicitly by the JMS client; its contents will be a javax.jms.Destination object (either Topic or Queue).

In some cases, the JMS client will want the message consumers to reply to a temporary topic or queue set up by the JMS client. When a JMS message consumer receives a message that includes a JMSReplyTo destination, it can reply using that destination. A JMS consumer is not required to send a reply, but in some JMS applications, clients are programmed to do so.

For simplicity, this pattern is typically implemented in a purely synchronous fashion, as in web service calls over HTTP, which holds a connection open and waits until the response is delivered or the timeout period expires. However, request–response may also be implemented asynchronously, with a response being returned at some unknown later time.

For more information, check here.

Now, let’s jump into the code. In Spring, there are 2 ways to implement this (at least I know of).

  1. Using JMSTemplate
  2. Using Spring Integration

For demo purpose, I used ActiveMQ. However, you can implement this in other messaging systems like IBM MQ, Rabbit MQ, Tibco EMS, etc. In this demo, I send an ObjectMessage of type Order and reply with a Shipment object.

Using JMSTemplate

  1. First, we include the required dependencies. Replace the activemq dependency with your messaging system’s jars if not using ActiveMQ

     <dependencies>
         
    <dependency>
             
    <groupId>org.springframework.boot</groupId>
             
    <artifactId>spring-boot-starter-activemq</artifactId>
         
    </dependency>
         
    <dependency>
             
    <groupId>org.apache.activemq.tooling</groupId>
             
    <artifactId>activemq-junit</artifactId>
             
    <version>${activemq.version}</version>
             
    <scope>test</scope>
         
    </dependency>
         
    <dependency>
             
    <groupId>org.springframework.boot</groupId>
             
    <artifactId>spring-boot-starter-test</artifactId>
             
    <scope>test</scope>
         
    </dependency>
     
    </dependencies>
  2. Using the default spring.activemq. properties to configure the application with the ActiveMQ. However, you can do this inside a @Configuration class as well.

     spring:
       activemq:
         broker-url: tcp://localhost:
    61616
         non-blocking-redelivery: true
         packages:
           trust-all: true    
  3. Note in the above configuration spring.activemq.packages.trust-all can be changed to spring.activemq.packages.trusted with the appropriate packages.
  4. Now spring will do it’s magic and inject all the required Beans as usual :) However, in our code, we need to EnableJms

    import org.springframework.context.annotation.Configuration;
     
    import org.springframework.jms.annotation.EnableJms;

     @EnableJms
     @Configuration
     
    public class ActiveMQConfig {

         
    public static final String ORDER_QUEUE = "order-queue";
         
    public static final String ORDER_REPLY_2_QUEUE = "order-reply-2-queue";

     }
  5. First, we will configure the Producer

     @Slf4j
     @Service
     
    public class Producer {

         @Autowired
         JmsMessagingTemplate jmsMessagingTemplate;

         @Autowired
         JmsTemplate jmsTemplate;
          private Session session;

          @PostConstruct
           public void init(){
             jmsTemplate.setReceiveTimeout(1000L);
             jmsMessagingTemplate.setJmsTemplate(jmsTemplate);

             session = jmsMessagingTemplate.getConnectionFactory().createConnection()
                     .createSession(false, Session.AUTO_ACKNOWLEDGE);
           }

         
    public Shipment sendWithReply(Order order) throws JMSException {


             ObjectMessage objectMessage 
    = session.createObjectMessage(order);

             objectMessage.setJMSCorrelationID(UUID.randomUUID().toString());
             objectMessage.setJMSReplyTo(
    new ActiveMQQueue(ORDER_REPLY_2_QUEUE));
             objectMessage.setJMSCorrelationID(UUID.randomUUID().toString());
             objectMessage.setJMSExpiration(
    1000L);
             objectMessage.setJMSDeliveryMode(DeliveryMode.NON_PERSISTENT);

             
    return jmsMessagingTemplate.convertSendAndReceive(new ActiveMQQueue(ORDER_QUEUE),
                     objectMessage, Shipment.
    class); //this operation seems to be blocking + sync
         }
     }
  6. Note in the above code that, JmsMessagingTemplate is used instead of JmsTemplatebecause, we are interested in the method convertSendAndReceive. As seen in the method signature, it waits to receive the Shipment object from the consumer.
  7. Next, we can see the Receiver

     @Component
     
    public class Receiver implements SessionAwareMessageListener<Message> {

         @Override
         @JmsListener(destination 
    = ORDER_QUEUE)
         
    public void onMessage(Message message, Session session) throws JMSException {
             Order order 
    = (Order) ((ActiveMQObjectMessage) message).getObject();
             Shipment shipment 
    = new Shipment(order.getId(), UUID.randomUUID().toString());

             
    // done handling the request, now create a response message
             final ObjectMessage responseMessage = new ActiveMQObjectMessage();
             responseMessage.setJMSCorrelationID(message.getJMSCorrelationID());
             responseMessage.setObject(shipment);

             
    // Message sent back to the replyTo address of the income message.
             final MessageProducer producer = session.createProducer(message.getJMSReplyTo());
             producer.send(responseMessage);
         }
     }
  8. Using the javax.jms.Session the javax.jms.MessageProducer is created and used to send the reply message to the JMSReplyTo queue. In real life, this receiver could be a different application altogether.

Using Spring Integration

  1. First, we include the required dependencies in addition to the above dependencies

     <dependency>
       
    <groupId>org.springframework.integration</groupId>
       
    <artifactId>spring-integration-jms</artifactId>
     
    </dependency>
  2. Using the default spring.activemq. properties to configure the application with the ActiveMQ. However, you can do this inside a @Configuration class as well.

     spring:
       activemq
    :
         broker
    -url: tcp://localhost:61616
         non
    -blocking-redelivery: true
         packages
    :
           trust
    -all: true   
  3. Note in the above configuration spring.activemq.packages.trust-all can be changed to spring.activemq.packages.trusted with the appropriate packages.
  4. Next we create the required Beans for the Spring Integration.

     @EnableIntegration
     @IntegrationComponentScan
     @Configuration
     
    public class ActiveMQConfig {

         
    public static final String ORDER_QUEUE = "order-queue";
         
    public static final String ORDER_REPLY_2_QUEUE = "order-reply-2-queue";

         @Bean
         
    public MessageConverter messageConverter() {
             MappingJackson2MessageConverter converter 
    = new MappingJackson2MessageConverter();
             converter.setTargetType(MessageType.TEXT);
             converter.setTypeIdPropertyName(
    "_type");
             
    return converter;
         }

         @Bean
         
    public MessageChannel requests() {
             
    return new DirectChannel();
         }

         @Bean
         @ServiceActivator(inputChannel 
    = "requests")
         
    public JmsOutboundGateway jmsGateway(ActiveMQConnectionFactory activeMQConnectionFactory) {
             JmsOutboundGateway gateway 
    = new JmsOutboundGateway();
             gateway.setConnectionFactory(activeMQConnectionFactory);
             gateway.setRequestDestinationName(ORDER_QUEUE);
             gateway.setReplyDestinationName(ORDER_REPLY_2_QUEUE);
             gateway.setCorrelationKey(
    "JMSCorrelationID");
             gateway.setSendTimeout(
    100L);
             gateway.setReceiveTimeout(
    100L);
             
    return gateway;
         }

         @Autowired
         Receiver receiver;

         @Bean
         
    public DefaultMessageListenerContainer responder(ActiveMQConnectionFactory activeMQConnectionFactory) {
             DefaultMessageListenerContainer container 
    = new DefaultMessageListenerContainer();
             container.setConnectionFactory(activeMQConnectionFactory);
             container.setDestinationName(ORDER_QUEUE);
             MessageListenerAdapter adapter 
    = new MessageListenerAdapter(new Object() {

                 @SuppressWarnings(
    "unused")
                 
    public Shipment handleMessage(Order order) {
                     
    return receiver.receiveMessage(order);
                 }

             });
             container.setMessageListener(adapter);
             
    return container;
         }
     }
  5. Next, we will configure the MessagingGateway

     @MessagingGateway(defaultRequestChannel = "requests")
     
    public interface ClientGateway {
         Shipment sendAndReceive(Order order);
     }
  6. We then Autowire this gateway in our Component class when we want to send and receive the message. A sample is shown below.

     @Slf4j
     @Component
     
    public class Receiver {
         
    public Shipment receiveMessage(@Payload Order order) {
             Shipment shipment 
    = new Shipment(order.getId(), UUID.randomUUID().toString());
             
    return shipment;
         }
     }
  7. Next we configure the Componen to process the Order message. After successful execution, this component will send the Shipment message to the JMSReplyTo queue. In real life, this receiver could be a different application altogether.

For those, who just want to clone the code, head out to aniruthmp/jms

Written on June 5, 2018
https://aniruthmp.github.io/Spring-JMS-request-response/

posted @ 2019-06-27 09:20 paulwong 阅读(54) | 评论 (0)编辑 收藏

ACTIVE MQ高级特性

https://blog.51cto.com/1754966750/category17.html

posted @ 2019-06-26 14:13 paulwong 阅读(54) | 评论 (0)编辑 收藏

JMS消息消费的顺序性

现有的系统架构都是分布式的。有多个消息的发送者和多个消息的消费者。例如订单创建消息和订单支付消息,我们需要保证先消费订单创建消息,然后消费订单支付消息。

如何解决MQ消息消费顺序问题
https://segmentfault.com/a/1190000014512075

jms-如何保证消息的顺序
https://leokongwq.github.io/2017/01/23/jms-message-order.html







posted @ 2019-06-24 17:42 paulwong 阅读(53) | 评论 (0)编辑 收藏

Multiple MongoDB connectors with Spring Boot

http://blog.marcosbarbero.com/multiple-mongodb-connectors-in-spring-boot/

posted @ 2019-06-20 15:12 paulwong 阅读(49) | 评论 (0)编辑 收藏

NFS Server 架設

  1. 安裝 nfs-utils 套件。
    [root@kvm5 ~]# yum install -y nfs-utils
  2. 建立 NFS 分享目錄。
    [root@kvm5 ~]# mkdir /public /protected
  3. 修改 NFS 分享目錄的 SELinux 檔案 context。
    [root@kvm5 ~]# semanage fcontext -a -t public_content_t "/public(/.*)?"
    [root@kvm5 ~]# semanage fcontext -a -t public_content_t "/protected(/.*)?"
    [root@kvm5 ~]# restorecon -Rv /public /protected
  4. 考試時不用自行產生 kerberos keytab,只要依照指定的位置下載,存放在目錄 /etc/ 下,且檔名必須為 krb5.keytab。
    [root@kvm5 ~]# wget http://deyu.wang/kvm5.keytab -O /etc/krb5.keytab 
  5. kerberos keytab 的驗證跟時間有關,server 與 client 都必須校時。
    [root@kvm5 ~]# date
    Sun Jan  7 14:50:04 CST 2018
    [root@kvm5 ~]# chronyc -a makestep
    200 OK
    200 OK
    [root@kvm5 ~]# date
    Mon Nov 20 15:53:22 CST 2017
  6. 在 /protected 下建立次目錄 restricted,並將其擁有者設定為 deyu3,讓 deyu3 可以寫入資料。
    [root@kvm5 ~]# mkdir -p  /protected/restricted 
    [root@kvm5 ~]# chown deyu3 /protected/restricted 
  7. 編輯設定檔 /etc/exports,分享 /protected 及 /public 兩個目錄給網域 192.168.122.0/24。
    [root@kvm5 ~]# echo '/protected 192.168.122.0/24(rw,sync,sec=krb5p)' > /etc/exports
    [root@kvm5 ~]# echo '/public 192.168.122.0/24(ro,sync)' >> /etc/exports
    [root@kvm5 ~]# vim /etc/exports
    [root@kvm5 ~]# cat /etc/exports
    /protected 192.168.122.0/24(rw,sync,sec=krb5p)
    /public 192.168.122.0/24(ro,sync)

  8. NFS 掛載參數說明如下,詳細說明請參考 man 5 nfs 手冊。
    1. rw:read-write,可讀寫的權限;
    2. ro:read-only,唯讀的權限;
    3. sec=mode:安全認證模式;
      1. sec=sys 預設,使用本地 UNIX UIDs 及 GIDs 進行身份認證。
      2. sec=krb5 使用 Kerberos V5 取代本地 UNIX UIDs 及 GIDs 進行身份認證。
      3. sec=krb5i 使用 Kerberos V5 進行身份認證,資料完整性檢查,以防止數據被篡改。
      4. sec=krb5p 使用 Kerberos V5 進行身份認證,資料完整性檢查及 NFS 傳輸加密,以防止數據被篡改,這是最安全的方式。
    4. sync:資料同步寫入到記憶體與硬碟當中;
    [root@kvm5 ~]# man 5 nfs 
  9. 設定使用 4.2 版本,以匯出分享 SELinux context。無適合的版本 client 端掛載時會出現 mount.nfs: Protocol not supported 的訊息。
    [root@kvm5 ~]# vim /etc/sysconfig/nfs  sed -i 's/^\(RPCNFSDARGS=\).*$/\1\"-V 4.2\"/' /etc/sysconfig/nfs 
    [root@kvm5 ~]# grep ^RPCNFSDARGS /etc/sysconfig/nfs  RPCNFSDARGS="-V 4.2" 
  10. 設定開機啟動 nfs 服務,NFS server 端的服務為 nfs-server 及 nfs-secure-server,本版本只要啟動 nfs-server 就同時啟動 nfs-secure-server,而且使用 tab 鍵也不會出現 nfs-secure-server 服務,但有些版本則是兩者分開,必須確認是不是兩種服務都啟動。
    [root@kvm5 ~]# systemctl enable nfs-server.service nfs-secure-server.service 
  11. 啟動 nfs 服務
    [root@kvm5 ~]# systemctl start nfs-server.service nfs-secure-server.service 
  12. 查看目前啟動的 nfs 版本,因 server 指定使用 4.2,若出現 -4.2 表示 nfs server 沒有成功啟動。
    [root@kvm5 ~]# cat /proc/fs/nfsd/versions -2 +3 +4 +4.1 +4.2 
  13. 要確定 nfs-secure-server nfs-server 服務都正常運作。
    [root@kvm5 ~]# systemctl status nfs-secure-server.service nfs-server.service 
    nfs-secure-server.service - Secure NFS Server
       Loaded
    : loaded (/usr/lib/systemd/system/nfs-secure-server.service; enabled)
       Active
    : active (running) since Mon 2015-09-21 20:04:10 CST; 8s ago
      Process
    : 3075 ExecStart=/usr/sbin/rpc.svcgssd $RPCSVCGSSDARGS (code=exited, status=0/SUCCESS)
     Main PID
    : 3077 (rpc.svcgssd)
       CGroup
    : /system.slice/nfs-secure-server.service
               └─
    3077 /usr/sbin/rpc.svcgssd

    Sep 
    21 20:04:10 kvm5.deyu.wang systemd[1]: Starting Secure NFS Server
    Sep 
    21 20:04:10 kvm5.deyu.wang systemd[1]: Started Secure NFS Server.

    nfs
    -server.service - NFS Server
       Loaded
    : loaded (/usr/lib/systemd/system/nfs-server.service; enabled)
       Active
    : active (exited) since Mon 2015-09-21 20:04:10 CST; 8s ago
      Process
    : 3078 ExecStopPost=/usr/sbin/exportfs -f (code=exited, status=0/SUCCESS)
      Process
    : 3076 ExecStop=/usr/sbin/rpc.nfsd 0 (code=exited, status=0/SUCCESS)
      Process
    : 3087 ExecStart=/usr/sbin/rpc.nfsd $RPCNFSDARGS $RPCNFSDCOUNT (code=exited, status=0/SUCCESS)
      Process
    : 3084 ExecStartPre=/usr/sbin/exportfs -r (code=exited, status=0/SUCCESS)
      Process
    : 3083 ExecStartPre=/usr/libexec/nfs-utils/scripts/nfs-server.preconfig (code=exited, status=0/SUCCESS)
     Main PID
    : 3087 (code=exited, status=0/SUCCESS)
       CGroup
    : /system.slice/nfs-server.service

    Sep 
    21 20:04:10 kvm5.deyu.wang systemd[1]: Starting NFS Server
    Sep 
    21 20:04:10 kvm5.deyu.wang systemd[1]: Started NFS Server.
  14. 建議不論是否 TAB 有沒有出現提示,都同時啟動這兩個服務。CentOS 安裝版本 nfs-utils-1.3.0-8.el7.x86_64 啟動 nfs-secure-server 出現錯誤訊息,請執行 yum downgrade nfs-utils 換成 nfs-utils-1.3.0-0.el7.x86_64 套件。
    [root@kvm5 ~]# rpm -qa | grep nfs-utils
    nfs-utils-1.3.0-8.el7.x86_64
    [root
    @kvm5 ~]# yum downgrade nfs-utils -y
    [root@kvm5 ~]# rpm -qa | grep nfs-utils
    nfs-utils-1.3.0-0.el7.x86_64
  15. 再重新啟動 nfs 服務,並查看是否正常運作。
    [root@kvm5 ~]# systemctl restart nfs-server.service nfs-secure-server.service 
  16. 輸出所有設定的 nfs 分享目錄。
    [root@kvm5 ~]# exportfs -arv
    exporting 192.168.122.0/24:/public
    exporting 
    192.168.122.0/24:/protected

posted @ 2019-06-18 09:08 paulwong 阅读(61) | 评论 (0)编辑 收藏

centos7 mysql数据库安装和配置


https://www.cnblogs.com/starof/p/4680083.html 


http://www.runoob.com/mysql/mysql-install.html

posted @ 2019-06-14 10:24 paulwong 阅读(51) | 评论 (0)编辑 收藏

SPRING BOOT 打包部署指南


https://segmentfault.com/a/1190000017386408

posted @ 2019-06-13 15:22 paulwong 阅读(53) | 评论 (0)编辑 收藏

Spring batch 的高级特性--监听,异常处理,事务


https://my.oschina.net/u/2600078/blog/909346

posted @ 2019-06-12 17:03 paulwong 阅读(52) | 评论 (0)编辑 收藏

LINUX安装NFS


https://qizhanming.com/blog/2018/08/08/how-to-install-nfs-on-centos-7

How to fix "mountd: refused mount request: unmatched host"
https://www.golinuxhub.com/2016/09/how-to-fix-mountd-refused-mount-request.html

nfs设置固定端口并添加防火墙规则
https://www.centos.bz/2017/12/nfs%E8%AE%BE%E7%BD%AE%E5%9B%BA%E5%AE%9A%E7%AB%AF%E5%8F%A3%E5%B9%B6%E6%B7%BB%E5%8A%A0%E9%98%B2%E7%81%AB%E5%A2%99%E8%A7%84%E5%88%99/

a
NFS Server 架設

https://dywang.csie.cyut.edu.tw/dywang/rhce7/node60.html

posted @ 2019-06-12 15:24 paulwong 阅读(74) | 评论 (0)编辑 收藏

zip4j

缩解压ZIP之Zip4j
https://rensanning.iteye.com/blog/1836727

posted @ 2019-06-10 16:47 paulwong 阅读(56) | 评论 (0)编辑 收藏

Message Processing With Spring Integration

Full demo
https://dzone.com/articles/message-processing-spring

posted @ 2019-06-05 11:08 paulwong 阅读(58) | 评论 (0)编辑 收藏

Spring integration 基本概念

1.spring integration 's architecture

主要提供两个功能:

在系统内提供实现轻量级、事件驱动交互行为的框架

在系统间提供一种基于适配器的平台,以支持灵活的系统间交互

2.spring integration对于企业集成模式的支持

2.1Message:一个信息的单元,通常有消息头(header)和消息内容(payload)组成

2.2Message channel:消息处理节点之间的连接,负责将Message从生产者传输到消费者。

    根据消费者的多少,可分为point to point和publish-subscribe两种


    根据消息传输方式的不同,分为同步和异步两种

2.3Message Endpoint:消息处理节点,消息从节点进入通道,也是从节点离开通道

几个常见的Message EndPoint:

CHANNEL ADAPTER,用于连接该适配器的特点是单向消息流的,要么是消息通过该适配器进入通道,要么是消息通过该适配器离开通道


MESSAGING GATEWAY,处理的消息流和Channel Adapter不同,不是单向的,即有进入该节点的消息,也会从该节点发出消息。



SERVICE ACTIVATOR,该节点调用服务来处理输入的消息,并将服务返回的数据发送到输出通道。在spring integration中,调用的方法被限定为本地方法调用。


ROUTER,路由器,将输入的消息路由到某个输出通道中


SPLITTER,将输入的消息拆分成子消息


AGGREGATOR,将输入的多个消息合并为一个消息


3.观看书中例子hello-world思考

测试gateway时,下面代码向通道names内放入消息world?


然后service-activator从names通道中获得消息world,调用方法sayHello返回值到给gateway?

解释:gateway有一个service-interface的属性,这个属性指向一个interface。当我们用一个接口声明一个gateway时,spring integration会自动帮我们生成该接口的代理类,这样当我们往gateway发送消息时,spring integration会通过代理类把消息转发到default-request-channel中去



作者:马国标
链接:https://www.jianshu.com/p/bf1643539f99
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

posted @ 2019-06-05 10:36 paulwong 阅读(160) | 评论 (0)编辑 收藏

如何在SPRING INTEGRATION中使用事务

File Polling using the Spring Integration DSL
http://porterhead.blogspot.com/2016/07/file-polling-using-spring-integration.html

posted @ 2019-06-04 14:19 paulwong 阅读(60) | 评论 (0)编辑 收藏

SpringBoot使用MongoDB异常问题

https://www.cnblogs.com/linzhanfly/p/9674778.html

posted @ 2019-05-29 16:58 paulwong 阅读(76) | 评论 (0)编辑 收藏

MONGODB去除_class字段

加上此配置:

MongodbConfiguration.java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.core.convert.DbRefResolver;
import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver;
import org.springframework.data.mongodb.core.convert.DefaultMongoTypeMapper;
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
 
@Configuration
public class AppMongoConfig {
  @Autowired private MongoDbFactory mongoDbFactory;
 
  @Autowired private MongoMappingContext mongoMappingContext;
 
  @Bean
  public MappingMongoConverter mappingMongoConverter() {
 
    DbRefResolver dbRefResolver = new DefaultDbRefResolver(mongoDbFactory);
    MappingMongoConverter converter = new MappingMongoConverter(dbRefResolver, mongoMappingContext);
    converter.setTypeMapper(new DefaultMongoTypeMapper(null));
 
    return converter;
  }
}

posted @ 2019-05-29 14:18 paulwong 阅读(204) | 评论 (0)编辑 收藏

JAVA 8 TIME

Java 8新特性(四):新的时间和日期API
https://lw900925.github.io/java/java8-newtime-api.html
 

posted @ 2019-05-09 10:15 paulwong 阅读(66) | 评论 (0)编辑 收藏

SPRING BOOT BATCH资源

 Spring Boot下Spring Batch入门实例
https://www.jianshu.com/p/305192ea4cb1
 

SPRING BATCH + QUARTZ
https://examples.javacodegeeks.com/enterprise-java/spring/batch/quartz-spring-batch-example/
https://blog.kingbbode.com/posts/spring-batch-quartz

Spring Batch Tutorial: Batch Processing Made Easy with Spring
https://www.toptal.com/spring/spring-batch-tutorial

posted @ 2019-05-07 17:07 paulwong 阅读(75) | 评论 (0)编辑 收藏

以流的方式解释巨大JSON文件

https://www.ngdata.com/parsing-a-large-json-file-efficiently-and-easily/


https://sites.google.com/site/gson/streaming

http://www.acuriousanimal.com/2015/10/23/reading-json-file-in-stream-mode-with-gson.html

    public static void main(String [] args) throws IOException {
        String filePath = "C:big-data.json";
        FileInputStream in = new FileInputStream(new File(filePath));
        JsonReader reader = new JsonReader(new InputStreamReader(in, "UTF-8"));
        Gson gson = new GsonBuilder().create();
//        reader.beginObject();
//        reader.nextName();
        reader.beginObject();//跳过"{"
        while (reader.hasNext()) {
            // Read data into object model
            JsonToken jsonToken = reader.peek();
            if(jsonToken.equals(JsonToken.NAME)) {
                String name = reader.nextName();
                if(name.equalsIgnoreCase("SUMMARY")) {
//                    reader.beginObject();
                    Summary summary = gson.fromJson(reader, Summary.class);
                    logger.info(summary.toString());
                    break;
//                    reader.endObject();//跳过"}"
                }
            } /*else if(jsonToken.equals(JsonToken.BEGIN_OBJECT)) {
                reader.beginObject();
            } else if(jsonToken.equals(JsonToken.STRING)) {
                logger.info(reader.nextString());
            } else if(jsonToken.equals(JsonToken.NUMBER)) {
                logger.info(reader.nextInt() + "");
            } else if(jsonToken.equals(JsonToken.END_OBJECT)) {
                reader.endObject();
            } 
*/
//            Summary summary = gson.fromJson(reader, Summary.class);
//            break;
        }
        reader.close();
    }

posted @ 2019-05-03 15:22 paulwong 阅读(94) | 评论 (0)编辑 收藏

定时任务管理系统资源

SpringBoot 整合 Quartz 实现定时任务管理模块
https://juejin.im/post/5a7157f56fb9a01cb049a158

posted @ 2019-04-25 17:03 paulwong 阅读(47) | 评论 (0)编辑 收藏

2018预测

https://www.oschina.net/news/91924/10-open-source-technology-trends-2018 http://www.iteye.com/news/32843

posted @ 2018-01-15 10:51 paulwong 阅读(324) | 评论 (0)编辑 收藏

JAX-RS2资源

JAX-RS 2比JAX-RS 1增加了过滤器、拦截器、异步处理等特性。@import url(http://www.blogjava.net/CuteSoft_Client/CuteEditor/Load.ashx?type=style&file=SyntaxHighlighter.css);@import url(/css/cuteeditor.css);

JAXRSClientSpringBoot
http://cxf.apache.org/docs/jaxrsclientspringboot.html

CXFSpringBoot
http://cxf.apache.org/docs/springboot.html

Restfull Webservice书源码
https://github.com/feuyeux

JAX-RS 2.0 REST客户端编程实例
http://www.importnew.com/8939.html

本人的DEMO
https://git.oschina.net/paulwong/test-cxf

posted @ 2017-09-24 17:19 paulwong 阅读(920) | 评论 (0)编辑 收藏

SPRING CLOUD JWT资源


https://github.com/thomas-kendall/trivia-microservices


一个Spring Boot, JWT,AugularJS接口安全验证的简单例子
http://blog.csdn.net/offbye/article/details/47607711



https://github.com/tuanngda/spring-boot-oauth2-demo


Spring Oauth2 with JWT Sample
http://www.tuicool.com/articles/EjUFZj7


http://stackoverflow.com/questions/38156213/spring-cloud-zuul-jwt-for-value-reference-tokens

posted @ 2017-03-24 23:11 paulwong 阅读(356) | 评论 (0)编辑 收藏

Spring Boot Admin资源

Spring Boot Admin 的使用
http://blog.csdn.net/kinginblue/article/details/52132113

posted @ 2017-03-21 22:10 paulwong 阅读(242) | 评论 (0)编辑 收藏

DDD领域驱动设计 - 设计文档模板

设计文档模板:

  1. 系统背景和定位
  2. 业务需求描述
  3. 领域语言整理,主要是整理领域中的各种术语的定义,名词解释
  4. 领域划分(分析出子域、核心域、支撑域)
  5. 系统用例图
  6. 每个子域的领域模型设计(实体、值对象、聚合、领域事件,需要注意的是:领域模型是需要抽象的,要分析业务本质,而不是简单的直接对需求进行建模)
  7. 领域模型详细说明(如为什么这样设计的原因、模型内对象的关系、各种业务规则、数据一致性规则等)
  8. 领域服务、仓储、工厂设计
  9. Saga业务流程设计
  10. 关键聚合根的状态流转图
  11. 场景走查(讲述如何通过领域模型、领域服务、仓储、Saga流程等完成系统用例以及关键业务流程的)
  12. 架构设计(如传统三层架构、经典四层架构、CQRS/ES架构)

一些其他的思考:

  • 去除一切花俏的建模技巧,我觉得最重要的方向就是去努力分析问题和事物的本质,针对这个本质进行领域建模。这个领域建模,最主要的还是锻炼的人的事物抽象能力。10个人,建出来的领域模型都不同。本质原因就是大家对同一个问题的理解不同,对事物的本质的理解不同。虽然最终都能解决当前的问题,但是对适应未来需求变化的能力却是不同。
  • 所以,我们要把时间花在多理解业务上,让自己成为领域专家,只有这样,才能充分理解业务。多理解一点业务,你才能更好的抽象出业务本质背后的领域模型。很少有人能做到很快理解业务,并很快针对业务设计出正确的领域模型,至少我是不行。
  • 领域建模需要时间,是一个迭代的过程,人无完人。而时间很多时候也不会很充足,所以,不太可能一步到位把领域设计做的很完美。我们在整体项目规划的时候可能会有个大的架构设计、业务大图(边界思维),但是不可能达到领域设计的粒度,只能是一期一期的完善,到最后可能才会有完整的上面的目录内容。每一期都需要考虑支持的场景约束、上下文、系统边界、持续集成的相关设计。设计product, not project。

posted @ 2017-02-19 12:14 paulwong 阅读(357) | 评论 (0)编辑 收藏

浅谈我对DDD领域驱动设计的理解(转)

从遇到问题开始

当人们要做一个软件系统时,一般总是因为遇到了什么问题,然后希望通过一个软件系统来解决。

比如,我是一家企业,然后我觉得我现在线下销售自己的产品还不够,我希望能够在线上也能销售自己的产品。所以,自然而然就想到要做一个普通电商系统,用于实现在线销售自己企业产品的目的。

再比如,我是一家互联网公司,公司有很多系统对外提供服务,面向很多客户端设备。但是最近由于各种原因,导致服务经常出故障。所以,我们希望通过各种措施提高服务的质量和稳定性。其中的一个措施就是希望能做一个灰度发布的平台,这个平台可以提供灰度发布的服务。然后,当某个业务系统做了一些修改并需要发布时,可以使用我们的灰度发布平台来非常方便的实现灰度发布的功能。比如在灰度发布平台上方便的定制允许哪些特定的客户端才会访问新服务,哪些客户端继续使用老服务。灰度发布平台可以提供各种灰度的策略。有了这样的灰度发布机制,那即便系统的新逻辑有什么问题,受影响的面也不会很大,在可控范围内。所以,如果公司里的所有对外提供服务的系统都接入了灰度平台,那这些系统的发布环节就可以更加有保障了。

总之,我们做任何一个软件系统,都是有原因的,否则就没必要做这个系统,而这个原因就是我们遇到的问题。所以,通过问题,我们就知道了我们需要一个什么样的系统,这个系统解决什么样的问题。最后,我们就很自然的得出了一个目标,即知道了自己要什么。比如我要做一个论坛、一个博客系统、一个电商平台、一个灰度发布系统、一个IDE、一个分布式消息队列、一个通信框架,等等。

DDD切入点1 - 理解概念

DDD的全称为Domain-driven Design,即领域驱动设计。下面我从领域、问题域、领域模型、设计、驱动这几个词语的含义和联系的角度去阐述DDD是如何融入到我们平时的软件开发初期阶段的。要理解什么是领域驱动设计,首先要理解什么是领域,什么是设计,还有驱动是什么意思,什么驱动什么。

什么是领域(Domain)?

前面我们已经清楚的知道我们现在要做一个什么样的系统,这个系统需要解决什么问题。我认为任何一个系统都会属于某个特定的领域,比如论坛是一个领域,只要你想做一个论坛,那这个论坛的核心业务是确定的,比如都有用户发帖、回帖等核心基本功能。比如电商平台、普通电商系统,这种都属于网上电商领域,只要是这个领域的系统,那都有商品浏览、购物车、下单、减库存、付款交易等核心环节。所以,同一个领域的系统都具有相同的核心业务,因为他们要解决的问题的本质是类似的。

因此,我们可以推断出,一个领域本质上可以理解为就是一个问题域,只要是同一个领域,那问题域就相同。所以,只要我们确定了系统所属的领域,那这个系统的核心业务,即要解决的关键问题、问题的范围边界就基本确定了。通常我们说,要成为一个领域的专家,必须要在这个领域深入研究很多年才行。因为只有你研究了很多年,你才会遇到非常多的该领域的问题,同时你解决这个领域中的问题的经验也非常丰富。很多时候,领域专家比技术专家更加吃香,比如金融领域的专家。

什么是设计(Design)?

DDD中的设计主要指领域模型的设计。为什么是领域模型的设计而不是架构设计或其他的什么设计呢?因为DDD是一种基于模型驱动开发的软件开发思想,强调领域模型是整个系统的核心,领域模型也是整个系统的核心价值所在。每一个领域,都有一个对应的领域模型,领域模型能够很好的帮我们解决复杂的业务问题。

从领域和代码实现的角度来理解,领域模型绑定了领域和代码实现,确保了最终的代码实现就一定是解决了领域中的核心问题的。因为:1)领域驱动领域模型设计;2)领域模型驱动代码实现。我们只要保证领域模型的设计是正确的,就能确定领域模型可以解决领域中的核心问题;同理,我们只要保证代码实现是严格按照领域模型的意图来落地的,那就能保证最后出来的代码能够解决领域的核心问题的。这个思路,和传统的分析、设计、编码这几个阶段被割裂(并且每个阶段的产物也不同)的软件开发方法学形成鲜明的对比。

什么是驱动(Driven)?

上面其实已经提到了,就是:1)领域驱动领域模型设计;2)领域模型驱动代码实现。这个就和我们传统的数据库驱动开发的思路形成对比了。DDD中,我们总是以领域为边界,分析领域中的核心问题(核心关注点),然后设计对应的领域模型,再通过领域模型驱动代码实现。而像数据库设计、持久化技术等这些都不是DDD的核心,而是外围的东西。

领域驱动设计(DDD)告诉我们的最大价值我觉得是:当我们要开发一个系统时,应该尽量先把领域模型想清楚,然后再开始动手编码,这样的系统后期才会很好维护。但是,很多项目(尤其是互联网项目,为了赶工)都是一开始模型没想清楚,一上来就开始建表写代码,代码写的非常冗余,完全是过程是的思考方式,最后导致系统非常难以维护。而且更糟糕的是,出来混总是要还的,前期的领域模型设计的不好,不够抽象,如果你的系统会长期需要维护和适应业务变化,那后面你一定会遇到各种问题维护上的困难,比如数据结构设计不合理,代码到处冗余,改BUG到处引入新的BUG,新人对这种代码上手困难,等。而那时如果你再想重构模型,那要付出的代价会比一开始重新开发还要大,因为你还要考虑兼容历史的数据,数据迁移,如何平滑发布等各种头疼的问题。所以,就导致我们最后天天加班。

虽然,我们都知道这个道理,但是我也明白,人的习惯很难改变的,大部分人都很难从面向过程式的想到哪里写到哪里的思想转变为基于系统化的模型驱动的思维。我想,这或许是DDD很难在中国或国外流行起来的原因吧。但是,我想这不应该成为我们放弃学习DDD的原因,对吧!

概念总结:

  1. 领域就是问题域,有边界,领域中有很多问题;
  2. 任何一个系统要解决的那个大问题都对应一个领域;
  3. 通过建立领域模型来解决领域中的核心问题,模型驱动的思想;
  4. 领域建模的目标针对我们在领域中所关心的问题,即只针对核心关注点,而不是整个领域中的所有问题;
  5. 领域模型在设计时应考虑一定的抽象性、通用性,以及复用价值;
  6. 通过领域模型驱动代码的实现,确保代码让领域模型落地,代码最终能解决问题;
  7. 领域模型是系统的核心,是领域内的业务的直接沉淀,具有非常大的业务价值;
  8. 技术架构设计或数据存储等是在领域模型的外围,帮助领域模型进行落地;

DDD切入点2 - 理解领域、拆分领域、细化领域

理解领域知识是基础

上面我们通过第一步,虽然我们明确了要做一个什么样的系统,该系统主要解决什么问题,但是就这样我们还无法开始进行实际的需求分析和模型设计,我们还必须将我们的问题进行拆分,需求进行细化。有些时候,需求方,即提出问题的人,很可能自己不清楚具体想要什么。他只知道一个概念,一个大的目标。比如他只知道要做一个股票交易系统,一个灰度发布系统,一个电商平台,一个开发工具,等。但是他不清楚这些系统应该具体做成什么样子。这个时候,我认为领域专家就非常重要了,DDD也非常强调领域专家的重要性。因为领域专家对这个领域非常了解,对领域内的各种业务场景和各种业务规则也非常清楚,总之,对这个领域内的一切业务相关的知识都非常了解。所以,他们自然就有能力表达出系统该做成什么样子。所以,要知道一个系统到底该做成什么样子,到底哪些是核心业务关注点,只能靠沉淀领域内的各种知识,别无他法。因此,假设你现在打算做一个电商平台,但是你对这个领域没什么了解,那你一定得先去了解下该领域内主流的电商平台,比如淘宝、天猫、京东、亚马逊等。这个了解的过程就是你沉淀领域知识的过程。如果你不了解,就算你领域建模的能力再强,各种技术架构能力再强也是使不上力。领域专家不是某个固定的角色,而是某一类人,这类人对这个领域非常了解。比如,一个开发人员也可以是一个领域专家。假设你在一个公司开发和维护一个系统已经好几年了,但是这个系统的产品经理(PD)可能已经换过好几任了,这种情况下,我相信这几任产品经理都没有比你更熟悉这个领域。

拆分领域

上面我们明白了,领域建模的基础是要先理解领域,让自己成为领域专家。如果做到了这点,我们就打好了坚实的基础了。但是,有时一个领域往往太复杂,涉及到的领域概念、业务规则、交互流程太多,导致我们没办法直接针对这个大的领域进行领域建模。所以,我们需要将领域进行拆分,本质上就是把大问题拆分为小问题,然后各个击破的思路。然后既然把一个大的领域划分为了多个小的领域(子域),那最关键的就是要理清每个子域的边界;然后要搞清楚哪些子域是核心子域,哪些是非核心子域,哪些是公共支撑子域;然后,还要思考子域之间的联系是什么。那么,我们该如何划分子域呢?我的个人看法是从业务相关性的角度去思考,也就是我们平时说的按业务功能为出发点进行划分。还是拿经典的电商系统来分析,通常一个电商系统都会包含好几个大块,比如:

  • 会员中心:负责用户账号登录、用户信息的管理;
  • 商品中心:负责商品的展示、导航、维护;
  • 订单中心:负责订单的生成和生命周期管理;
  • 交易中心:负责交易相关的业务;
  • 库存中心:负责维护商品的库存;
  • 促销中心:负责各种促销活动的支持;

上面这些中心看起来很自然,因为大家对电子商务的这个领域都已经非常熟悉了,所以都没什么疑问,好像很自然的样子。所以,领域划分是不是就是没什么挑战了呢?显然不是。之所以我们觉得子域划分很简单,是因为我们对整个大领域非常了解了。如果我们遇到一个冷门的领域,就没办法这么容易的去划分子域了。这就需要我们先去努力理解领域内的知识。所以,我个人从来不相信什么子域划分的技巧什么的东西,因为我觉得这个工作没有任何诀窍可以使用。当我们不了解一个东西的时候,如何去拆解它?当我们对整个领域有一定的熟悉了,了解了领域内的相关业务的本质和关系,我们就自然而然的能划分出合理的子域了。不过并不是所有的系统都需要划分子域的,有些系统只是解决一个小问题,这个问题不复杂,可能只有一两个核心概念。所以,这种系统完全不需要再划分子域。但不是绝对的,当一个领域,我们的关注点越来越多,每个关注点我们关注的信息越来越多的时候,我们会不由自主的去进一步的划分子域。比如,也许我们一开始将商品和商品的库存都放在商品中心里,但是后来由于库存的维护越来越复杂,导致揉在一起对我们的系统维护带来一定的困难时,我们就会考虑将两者进行拆分,这个就是所谓的业务垂直分割。

细化子域

通过上面的两步,我们了解了领域里的知识,也对领域进行了子域划分。但这样还不够,凭这些我们还无法进行后续的领域模型设计。我们还必须再进一步细化每个子域,进一步明确每个子域的核心关注点,即需求细化。我觉得我们需要细化的方面有以下几点:

  1. 梳理领域概念:梳理出领域内我们关注的概念、概念的关系,并统一交流词汇,形成统一语言;
  2. 梳理业务规则:梳理出领域内我们关注的各种业务规则,DDD中叫不变性(invariants),比如唯一性规则,余额不能小于零等;
  3. 梳理业务场景:梳理出领域内的核心业务场景,比如电商平台中的加入购物车、提交订单、发起付款等核心业务场景;
  4. 梳理业务流程:梳理出领域内的关键业务流程,比如订单处理流程,退款流程等;

从上面这4个方面,我们从领域概念、业务规则、交互场景、业务流程等维度梳理了我们到底要什么,整理了整个系统应该具备的功能。这个工作我觉得是一个非常具有创造性和有难度的工作。我们一方面会主观的定义我们想要什么;另一方面,我们还会思考我们要的东西的合理性。我认为这个就是产品经理的工作,产品经理必须要负起职责,把他的产品充分设计好,从各个方面去考虑,如何设计一个产品,才能更好的解决用户的核心诉求,即领域内的核心问题。如果对领域不够了解,如果想不清楚用户到底要什么,如果思考问题不够全面,谈何设计出一个合理的产品呢?

关于领域概念的梳理,我觉得可以采用四色原型分析法,这个分析法通过系统的方法,将概念划分为不同的种类,为不同种类的概念标注不同的颜色。然后将这些概念有机的组合起来,从而让我们可以清晰的分析出概念和概念之间的关系。有兴趣的同学可以在网上搜索下四色原型

注意:上面我说的这四点,重点是梳理出我们要什么功能,而不是思考如何实现这些功能,如何实现是软件设计人员的职责。

DDD切入点3 - 领域模型设计

这部分内容,我想学习DDD的人都很熟悉了。DDD原著中提出了很多实用的建模工具:聚合、实体、值对象、工厂、仓储、领域服务、领域事件。我们可以使用这些工具,来设计每一个子域的领域模型。最终通过领域模型图将设计沉淀下来。要使用这些工具,首先就要理解每个工具的含义和使用场景。不要以为很简单哦,比如聚合的划分就是一个非常具有艺术的活。同一个系统,不同的人设计出来的聚合是完全不同的。而且很有可能高手之间的最后设计出来的差别反而更大,实际上我认为是世界观的相互碰撞,呵呵。所以,要领域建模,我觉得每个人都应该去学学哲学知识,这有助于我们更好的认识世界,更好的理解事物的本质。

关于这些建模工具的概念和如何运用我就不多展开了,我博客里也有很多这方面的介绍。下面我再讲一下我认为比较重要的东西,比如到底该如何领域建模?步骤应该是怎么样的?

领域建模的方法

通过上面我介绍的细化子域的内容,现在再来谈该如何领域建模,我觉得就方便很多了。我的主要方法是:

  1. 划分好边界上下文,通常每个子域(sub domain)对应一个边界上下文(bounded context),同一个边界上下文中的概念是明确的,没有任何歧义;
  2. 在每个边界上下文中设计领域模型,具体的领域模型设计方法有很多种,如以场景为出发点的四色原型分析法,或者我早期写的这篇文章;这个步骤最核心的就是找出聚合根,并找出每个聚合根包含的信息;关于如何设计聚合,可以看一下我写的这篇文章
  3. 画出领域模型图,圈出每个模型中的聚合边界;
  4. 设计领域模型时,要考虑该领域模型是否满足业务规则,同时还要综合考虑技术实现等问题,比如并发问题;领域模型不是概念模型,概念模型不关注技术实现,领域模型关心;所以领域模型才能直接指导编码实现;
  5. 思考领域模型是如何在业务场景中发挥作用的,以及是如何参与到业务流程的每个环节的;
  6. 场景走查,确认领域模型是否能满足领域中的业务场景和业务流程;
  7. 模型持续重构、完善、精炼;

领域模型的核心作用:

  1. 抽象了领域内的核心概念,并建立概念之间的关系;
  2. 领域模型承担了领域内的状态的维护;
  3. 领域模型维护了领域内的数据之间的业务规则,数据一致性;

下图是我最近做个一个普通电商系统的商品中心的领域模型图,给大家参考:

领域模型设计只是软件设计中的一小部分

需要特别注意的是,领域模型设计只是整个软件设计中的很小一部分。除了领域模型设计之外,要落地一个系统,我们还有非常多的其他设计要做,比如:

  • 容量规划
  • 架构设计
  • 数据库设计
  • 缓存设计
  • 框架选型
  • 发布方案
  • 数据迁移、同步方案
  • 分库分表方案
  • 回滚方案
  • 高并发解决方案
  • 一致性选型
  • 性能压测方案
  • 监控报警方案

等等。上面这些都需要我们平时的大量学习和积累。作为一个合格的开发人员或架构师,我觉得除了要会DDD领域驱动设计,还要会上面这么多的技术能力,确实是非常不容易的。所以,千万不要以为会DDD了就以为自己很牛逼,实际上你会的只是软件设计中的冰山一角而已。

总结

本文的重点是基于我个人对DDD的一些理解,希望能整理出一些自己总结出来的一些感悟和经验,并分享给大家。我相信很多人已经看过太多DDD书上的东西,我总是感觉书上的东西看似都太”正规“,很多时候我们读了之后很难消化,就算理解了书里的内容,当我们想要运用到实践中时,总是感觉无从下手。本文希望通过通俗易懂的文字,介绍了一部分我对DDD的学习感悟和实践心得,希望能给大家一些启发和帮助。

posted @ 2017-02-19 12:12 paulwong 阅读(316) | 评论 (0)编辑 收藏

DDD CQRS架构和传统架构的优缺点比较

最近几年,在DDD的领域,我们经常会看到CQRS架构的概念。我个人也写了一个ENode框架,专门用来实现这个架构。CQRS架构本身的思想其实非常简单,就是读写分离。是一个很好理解的思想。就像我们用MySQL数据库的主备,数据写到主,然后查询从备来查,主备数据的同步由MySQL数据库自己负责,这是一种数据库层面的读写分离。关于CQRS架构的介绍其实已经非常多了,大家可以自行百度或google。我今天主要想总结一下这个架构相对于传统架构(三层架构、DDD经典四层架构)在数据一致性、扩展性、可用性、伸缩性、性能这几个方面的异同,希望可以总结出一些优点和缺点,为大家在做架构选型时提供参考。

前言

CQRS架构由于本身只是一个读写分离的思想,实现方式多种多样。比如数据存储不分离,仅仅只是代码层面读写分离,也是CQRS的体现;然后数据存储的读写分离,C端负责数据存储,Q端负责数据查询,Q端的数据通过C端产生的Event来同步,这种也是CQRS架构的一种实现。今天我讨论的CQRS架构就是指这种实现。另外很重要的一点,C端我们还会引入Event Sourcing+In Memory这两种架构思想,我认为这两种思想和CQRS架构可以完美的结合,发挥CQRS这个架构的最大价值。

数据一致性

传统架构,数据一般是强一致性的,我们通常会使用数据库事务保证一次操作的所有数据修改都在一个数据库事务里,从而保证了数据的强一致性。在分布式的场景,我们也同样希望数据的强一致性,就是使用分布式事务。但是众所周知,分布式事务的难度、成本是非常高的,而且采用分布式事务的系统的吞吐量都会比较低,系统的可用性也会比较低。所以,很多时候,我们也会放弃数据的强一致性,而采用最终一致性;从CAP定理的角度来说,就是放弃一致性,选择可用性。

CQRS架构,则完全秉持最终一致性的理念。这种架构基于一个很重要的假设,就是用户看到的数据总是旧的。对于一个多用户操作的系统,这种现象很普遍。比如秒杀的场景,当你下单前,也许界面上你看到的商品数量是有的,但是当你下单的时候,系统提示商品卖完了。其实我们只要仔细想想,也确实如此。因为我们在界面上看到的数据是从数据库取出来的,一旦显示到界面上,就不会变了。但是很可能其他人已经修改了数据库中的数据。这种现象在大部分系统中,尤其是高并发的WEB系统,尤其常见。

所以,基于这样的假设,我们知道,即便我们的系统做到了数据的强一致性,用户还是很可能会看到旧的数据。所以,这就给我们设计架构提供了一个新的思路。我们能否这样做:我们只需要确保系统的一切添加、删除、修改操作所基于的数据是最新的,而查询的数据不必是最新的。这样就很自然的引出了CQRS架构了。C端数据保持最新、做到数据强一致;Q端数据不必最新,通过C端的事件异步更新即可。所以,基于这个思路,我们开始思考,如何具体的去实现CQ两端。看到这里,也许你还有一个疑问,就是为何C端的数据是必须要最新的?这个其实很容易理解,因为你要修改数据,那你可能会有一些修改的业务规则判断,如果你基于的数据不是最新的,那意味着判断就失去意义或者说不准确,所以基于老的数据所做的修改是没有意义的。

扩展性

传统架构,各个组件之间是强依赖,都是对象之间直接方法调用;而CQRS架构,则是事件驱动的思想;从微观的聚合根层面,传统架构是应用层通过过程式的代码协调多个聚合根一次性以事务的方式完成整个业务操作。而CQRS架构,则是以Saga的思想,通过事件驱动的方式,最终实现多个聚合根的交互。另外,CQRS架构的CQ两端也是通过事件的方式异步进行数据同步,也是事件驱动的一种体现。上升到架构层面,那前者就是SOA的思想,后者是EDA的思想。SOA是一个服务调用另一个服务完成服务之间的交互,服务之间紧耦合;EDA是一个组件订阅另一个组件的事件消息,根据事件信息更新组件自己的状态,所以EDA架构,每个组件都不会依赖其他的组件;组件之间仅仅通过topic产生关联,耦合性非常低。

上面说了两种架构的耦合性,显而易见,耦合性低的架构,扩展性必然好。因为SOA的思路,当我要加一个新功能时,需要修改原来的代码;比如原来A服务调用了B,C两个服务,后来我们想多调用一个服务D,则需要改A服务的逻辑;而EDA架构,我们不需要动现有的代码,原来有B,C两订阅者订阅A产生的消息,现在只需要增加一个新的消息订阅者D即可。

从CQRS的角度来说,也有一个非常明显的例子,就是Q端的扩展性。假设我们原来Q端只是使用数据库实现的,但是后来系统的访问量增大,数据库的更新太慢或者满足不了高并发的查询了,所以我们希望增加缓存来应对高并发的查询。那对CQRS架构来说很容易,我们只需要增加一个新的事件订阅者,用来更新缓存即可。应该说,我们可以随时方便的增加Q端的数据存储类型。数据库、缓存、搜索引擎、NoSQL、日志,等等。我们可以根据自己的业务场景,选择合适的Q端数据存储,实现快速查询的目的。这一切都归功于我们C端记录了所有模型变化的事件,当我们要增加一种新的View存储时,可以根据这些事件得到View存储的最新状态。这种扩展性在传统架构下是很难做到的。

可用性

可用性,无论是传统架构还是CQRS架构,都可以做到高可用,只要我们做到让我们的系统中每个节点都无单点即可。但是,相比之下,我觉得CQRS架构在可用性方面,我们可以有更多的回避余地和选择空间。

传统架构,因为读写没有分离,所以可用性要把读写合在一起综合考虑,难度会比较更大。因为传统架构,如果一个系统的高峰期的并发写入很大,比如为2W,并发读取也很大,比如为10W。那该系统必须优化到能同时支持这种高并发的写入和查询,否则系统就会在高峰时挂掉。这个就是基于同步调用思路的系统的缺点,没有一个东西去削峰填谷,保存瞬间多出来的请求,而必须让系统不管遇到多少请求,都必须能及时处理完,否则就会造成雪崩效应,造成系统瘫痪。但是一个系统,不会一直处在高峰,高峰可能只有半小时或1小时;但为了确保高峰时系统不挂掉,我们必须使用足够的硬件去支撑这个高峰。而大部分时候,都不需要这么高的硬件资源,所以会造成资源的浪费。所以,我们说基于同步调用、SOA思想的系统的实现成本是非常昂贵的。

而在CQRS架构下,因为CQRS架构把读和写分离了,所以可用性相当于被隔离在了两个部分去考虑。我们只需要考虑C端如何解决写的可用性,Q端如何解决读的可用性即可。C端解决可用性,我觉得是更加容易的,因为C端是消息驱动的。我们要做任何数据修改时,都会发送Command到分布式消息队列,然后后端消费者处理Command->产生领域事件->持久化事件->发布事件到分布式消息队列->最后事件被Q端消费。这个链路是消息驱动的。相比传统架构的直接服务方法调用,可用性要高很多。因为就算我们处理Command的后端消费者暂时挂了,也不会影响前端Controller发送Command,Controller依然可用。从这个角度来说,CQRS架构在数据修改上可用性要更高。不过你可能会说,要是分布式消息队列挂了呢?呵呵,对,这确实也是有可能的。但是一般分布式消息队列属于中间件,一般中间件都具有很高的可用性(支持集群和主备切换),所以相比我们的应用来说,可用性要高很多。另外,因为命令是先发送到分布式消息队列,这样就能充分利用分布式消息队列的优势:异步化、拉模式、削峰填谷、基于队列的水平扩展。这些特性可以保证即便前端Controller在高峰时瞬间发送大量的Command过来,也不会导致后端处理Command的应用挂掉,因为我们是根据自己的消费能力拉取Command。这点也是CQRS C端在可用性方面的优势,其实本质也是分布式消息队列带来的优势。所以,从这里我们可以体会到EDA架构(事件驱动架构)是非常有价值的,这个架构也体现了我们目前比较流行的Reactive Programming(响应式编程)的思想。

然后,对于Q端,应该说和传统架构没什么区别,因为都是要处理高并发的查询。这点以前怎么优化的,现在还是怎么优化。但是就像我上面可扩展性里强调的,CQRS架构可以更方便的提供更多的View存储,数据库、缓存、搜索引擎、NoSQL,而且这些存储的更新完全可以并行进行,互相不会拖累。理想的场景,我觉得应该是,如果你的应用要实现全文索引这种复杂查询,那可以在Q端使用搜索引擎,比如ElasticSearch;如果你的查询场景可以通过keyvalue这种数据结构满足,那我们可以在Q端使用Redis这种NoSql分布式缓存。总之,我认为CQRS架构,我们解决查询问题会比传统架构更加容易,因为我们选择更多了。但是你可能会说,我的场景只能用关系型数据库解决,且查询的并发也是非常高。那没办法了,唯一的办法就是分散查询IO,我们对数据库做分库分表,以及对数据库做一主多备,查询走备机。这点上,解决思路就是和传统架构一样了。

性能、伸缩性

本来想把性能和伸缩性分开写的,但是想想这两个其实有一定的关联,所以决定放在一起写。

伸缩性的意思是,当一个系统,在100人访问时,性能(吞吐量、响应时间)很不错,在100W人访问时性能也同样不错,这就是伸缩性。100人访问和100W人访问,对系统的压力显然是不同的。如果我们的系统,在架构上,能够做到通过简单的增加机器,就能提高系统的服务能力,那我们就可以说这种架构的伸缩性很强。那我们来想想传统架构和CQRS架构在性能和伸缩性上面的表现。

说到性能,大家一般会先思考一个系统的性能瓶颈在哪里。只要我们解决了性能瓶颈,那系统就意味着具有通过水平扩展来达到可伸缩的目的了(当然这里没有考虑数据存储的水平扩展)。所以,我们只要分析一下传统架构和CQRS架构的瓶颈点在哪里即可。

传统架构,瓶颈通常在底层数据库。然后我们一般的做法是,对于读:通常使用缓存就可以解决大部分查询问题;对于写:办法也有很多,比如分库分表,或者使用NoSQL,等等。比如阿里大量采用分库分表的方案,而且未来应该会全部使用高大上的OceanBase来替代分库分表的方案。通过分库分表,本来一台数据库服务器高峰时可能要承受10W的高并发写,如果我们把数据放到十台数据库服务器上,那每台机器只需要承担1W的写,相对于要承受10W的写,现在写1W就显得轻松很多了。所以,应该说数据存储对传统架构来说,也早已不再是瓶颈了。

传统架构一次数据修改的步骤是:1)从DB取出数据到内存;2)内存修改数据;3)更新数据回DB。总共涉及到2次数据库IO。

然后CQRS架构,CQ两端加起来所用的时间肯定比传统架构要多,因为CQRS架构最多有3次数据库IO,1)持久化命令;2)持久化事件;3)根据事件更新读库。为什么说最多?因为持久化命令这一步不是必须的,有一种场景是不需要持久化命令的。CQRS架构中持久化命令的目的是为了做幂等处理,即我们要防止同一个命令被处理两次。那哪一种场景下可以不需要持久化命令呢?就是当命令时在创建聚合根时,可以不需要持久化命令,因为创建聚合根所产生的事件的版本号总是为1,所以我们在持久化事件时根据事件版本号就能检测到这种重复。

所以,我们说,你要用CQRS架构,就必须要接受CQ数据的最终一致性,因为如果你以读库的更新完成为操作处理完成的话,那一次业务场景所用的时间很可能比传统架构要多。但是,如果我们以C端的处理为结束的话,则CQRS架构可能要快,因为C端可能只需要一次数据库IO。我觉得这里有一点很重要,对于CQRS架构,我们更加关注C端处理完成所用的时间;而Q端的处理稍微慢一点没关系,因为Q端只是供我们查看数据用的(最终一致性)。我们选择CQRS架构,就必须要接受Q端数据更新有一点点延迟的缺点,否则就不应该使用这种架构。所以,希望大家在根据你的业务场景做架构选型时一定要充分认识到这一点。

另外,上面再谈到数据一致性时提到,传统架构会使用事务来保证数据的强一致性;如果事务越复杂,那一次事务锁的表就越多,锁是系统伸缩性的大敌;而CQRS架构,一个命令只会修改一个聚合根,如果要修改多个聚合根,则通过Saga来实现。从而绕过了复杂事务的问题,通过最终一致性的思路做到了最大的并行和最少的并发,从而整体上提高系统的吞吐能力。

所以,总体来说,性能瓶颈方面,两种架构都能克服。而只要克服了性能瓶颈,那伸缩性就不是问题了(当然,这里我没有考虑数据丢失而带来的系统不可用的问题。这个问题是所有架构都无法回避的问题,唯一的解决办法就是数据冗余,这里不做展开了)。两者的瓶颈都在数据的持久化上,但是传统的架构因为大部分系统都是要存储数据到关系型数据库,所以只能自己采用分库分表的方案。而CQRS架构,如果我们只关注C端的瓶颈,由于C端要保存的东西很简单,就是命令和事件;如果你信的过一些成熟的NoSQL(我觉得使用文档性数据库如MongoDB这种比较适合存储命令和事件),且你也有足够的能力和经验去运维它们,那可以考虑使用NoSQL来持久化。如果你觉得NoSQL靠不住或者没办法完全掌控,那可以使用关系型数据库。但这样你也要付出努力,比如需要自己负责分库分表来保存命令和事件,因为命令和事件的数据量都是很大的。不过目前一些云服务如阿里云,已经提供了DRDS这种直接支持分库分表的数据库存储方案,极大的简化了我们存储命令和事件的成本。就我个人而言,我觉得我还是会采用分库分表的方案,原因很简单:确保数据可靠落地、成熟、可控,而且支持这种只读数据的落地,框架内置要支持分库分表也不是什么难事。所以,通过这个对比我们知道传统架构,我们必须使用分库分表(除非阿里这种高大上可以使用OceanBase);而CQRS架构,可以带给我们更多选择空间。因为持久化命令和事件是很简单的,它们都是不可修改的只读数据,且对kv存储友好,也可以选择文档型NoSQL,C端永远是新增数据,而没有修改或删除数据。最后,就是关于Q端的瓶颈,如果你Q端也是使用关系型数据库,那和传统架构一样,该怎么优化就怎么优化。而CQRS架构允许你使用其他的架构来实现Q,所以优化手段相对更多。

结束语

我觉得不论是传统架构还是CQRS架构,都是不错的架构。传统架构门槛低,懂的人也多,且因为大部分项目都没有什么大的并发写入量和数据量。所以应该说大部分项目,采用传统架构就OK了。但是通过本文的分析,大家也知道了,传统架构确实也有一些缺点,比如在扩展性、可用性、性能瓶颈的解决方案上,都比CQRS架构要弱一点。大家有其他意见,欢迎拍砖,交流才能进步,呵呵。所以,如果你的应用场景是高并发写、高并发读、大数据,且希望在扩展性、可用性、性能、可伸缩性上表现更优秀,我觉得可以尝试CQRS架构。但是还有一个问题,CQRS架构的门槛很高,我认为如果没有成熟的框架支持,很难使用。而目前据我了解,业界还没有很多成熟的CQRS框架,java平台有axon framework, jdon framework;.NET平台,ENode框架正在朝这个方向努力。所以,我想这也是为什么目前几乎没有使用CQRS架构的成熟案例的原因之一。另一个原因是使用CQRS架构,需要开发者对DDD有一定的了解,否则也很难实践,而DDD本身要理解没个几年也很难运用到实际。还有一个原因,CQRS架构的核心是非常依赖于高性能的分布式消息中间件,所以要选型一个高性能的分布式消息中间件也是一个门槛(java平台有RocketMQ),.NET平台我个人专门开发了一个分布式消息队列EQueue,呵呵。另外,如果没有成熟的CQRS框架的支持,那编码复杂度也会很复杂,比如Event Sourcing,消息重试,消息幂等处理,事件的顺序处理,并发控制,这些问题都不是那么容易搞定的。而如果有框架支持,由框架来帮我们搞定这些纯技术问题,开发人员只需要关注如何建模,实现领域模型,如何更新读库,如何实现查询,那使用CQRS架构才有可能,因为这样才可能比传统的架构开发更简单,且能获得很多CQRS架构所带来的好处。

posted @ 2017-02-19 08:39 paulwong 阅读(572) | 评论 (0)编辑 收藏

快速理解聚合根、实体、值对象的区别和联系

posted @ 2017-02-19 08:32 paulwong 阅读(294) | 评论 (0)编辑 收藏

基于Spring Boot, Axon CQRS/ES,和Docker构建微服务

这是一个使用Spring Boot和Axon以及Docker构建的Event Sorucing源码项目,技术特点:
1.使用Java 和Spring Boot实现微服务;
2.使用命令和查询职责分离 (CQRS) 和 Event Sourcing (ES) 的框架Axon Framework v2, MongoDB 和 RabbitMQ;
3.使用Docker构建 交付和运行;
4.集中配置和使用Spring Cloud服务注册;
5.使用Swagger 和 SpringFox 提供API文档

项目源码:GitHub

工作原理:
这个应用使用CQRS架构模式构建,在CQRS命令如ADD是和查询VIEW(where id=1)分离的,在这个案例中领域部分代码已经分离成两个组件:一个是属于命令这边的微服务和属性查询这边的微服务。

微服务是单个职责的功能,自己的数据存储,每个能彼此独立扩展部署。

属于命令这边的微服务和属性查询这边的微服务都是使用Spring Boot框架开发的,在命令微服务和查询微服务之间通讯是事件驱动,事件是通过RabbitMQ消息在微服务组件之间传递,消息提供了一种进程节点或微服务之间可扩展的事件载体,包括与传统遗留系统或其他系统的松耦合通讯都可以通过消息进行。

请注意,服务之间不能彼此共享数据库,这是很重要,因为微服务应该是高度自治自主的,这样反过来有助于服务能够彼此独立地扩展伸缩规模。

CQRS中命令是“改变状态的动作”。命令的微服务包含所有领域逻辑和业务规则,命令被用于增加新的产品或改变它们的状态,这些命令针对某个具体产品的执行会导致事件Event产生,这会通过Axon框架持久化到MongoDB中,然后通过RabbitMQ传播给其他节点进程或微服务。

在event-sourcing中,事件是状态改变的原始记录,它们用于系统来重新建立实体的当前状态(通过重新播放过去的事件到当前就可以构建当前的状态),这听上去会很慢,但是实际上,事件都很简单,执行非常快,也能采取‘快照’策略进行优化。

请注意,在DDD中,实体是指一个聚合根实体。

上面是命令这边的微服务,下面看看查询这边的微服务:
查询微服务一般扮演一种事件监听器和视图角色,它监听到命令那边发出的事件,然后处理它们以符合查询这边的要求。

在这个案例中,查询这边只是简单建立和维持了一个 ‘materialised view’或‘projection’ ,其中保留了产品的最新状态,也就是产品id和描述以及是否被卖出等等信息,查询这边能够被复制多次以方便扩展,消息可以保留在RabbitMQ队列中实现持久保存,这种临时保存消息方式可以防止查询这边微服务当机。

命令微服务和查询微服务两者都有REST API,提供外界客户端访问。

下面看看如何通过Docker运行这个案例,需要 Ubuntu 16.04:
1.Docker ( v1.8.2)
2.Docker-compose ( v1.7.1)

在一个空目录,执行下面命令下载docker-compose:

$ wget https://raw.githubusercontent.com/benwilcock/microservice-sampler/master/docker-compose.yml
注意:不要更改文件名称。

启动微服务:只是简单一个命令:

$ docker-compose up

你会看到许多下载信息和日志输出在屏幕上,这是Docker image将被下载和运行。一共有六个docker,分别是: ‘mongodb’, ‘rabbitmq’, ‘config’, ‘discovery’, ‘product-cmd-side’, 和 ‘product-qry-side’.

使用下面命令进行测试增加一个新产品:

$ curl -X POST -v --header "Content-Type: application/json" --header "Accept: */*" "http://localhost:9000/products/add/1?name=Everything%20Is%20Awesome"

查询这个新产品:

$ curl http://localhost:9001/products/1

Microservices With Spring Boot, Axon CQRS/ES, and Docker

posted @ 2017-02-18 22:00 paulwong 阅读(1256) | 评论 (0)编辑 收藏

DDD资源

微服务+DDD
https://github.com/benwilcock/microservice-sampler



https://github.com/AxonFramework/AxonBank

http://www.cnblogs.com/netfocus/p/4150084.html

posted @ 2017-02-18 21:53 paulwong 阅读(200) | 评论 (0)编辑 收藏

使用 Docker 搭建 Java Web 运行环境

     摘要: Docker 是 2014 年最为火爆的技术之一,几乎所有的程序员都听说过它。Docker 是一种“轻量级”容器技术,它几乎动摇了传统虚拟化技术的地位,现在国内外已经有越来越多的公司开始逐步使用 Docker 来替换现有的虚拟化平台了。作为一名 Java 程序员,我们是时候一起把 Docker 学起来了!本文会对虚拟化技术与 Docker 容器技术做一个对比,然后引出一些 ...  阅读全文

posted @ 2016-10-15 19:57 paulwong 阅读(1231) | 评论 (0)编辑 收藏

使用Spring Cloud Security OAuth2搭建授权服务

Spring Cloud Security OAuth2 是 Spring 对 OAuth2 的开源实现,优点是能与Spring Cloud技术线无缝集成,如果全部使用默认配置,开发者只需要添加注解就能完成 OAuth2 授权服务的搭建。

1. 添加依赖

授权服务是基于Spring Security的,因此需要在项目中引入两个依赖:

<dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-security</artifactId>
</dependency>

<dependency>
         <groupId>org.springframework.cloud</groupId>
         <artifactId>spring-cloud-starter-oauth2</artifactId>
 </dependency>


前者为 Security,后者为Security的OAuth2扩展。

2. 添加注解和配置

在启动类中添加@EnableAuthorizationServer注解:

@SpringBootApplication
@EnableAuthorizationServer
public class AlanOAuthApplication {
    public static void main(String[] args) {
        SpringApplication.run(AlanOAuthApplication.class, args);
    }
}


完成这些我们的授权服务最基本的骨架就已经搭建完成了。但是要想跑通整个流程,我们必须分配 client_idclient_secret才行。Spring Security OAuth2的配置方法是编写@Configuration类继承AuthorizationServerConfigurerAdapter,然后重写void configure(ClientDetailsServiceConfigurer clients)方法,如:

@Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory() // 使用in-memory存储
                .withClient("client") // client_id
                .secret("secret") // client_secret
                .authorizedGrantTypes("authorization_code") // 该client允许的授权类型
                .scopes("app"); // 允许的授权范围
    }


3. 授权流程

访问授权页面:

localhost:8080/oauth/authorize?client_id=client&response_type=code&redirect_uri=http://www.baidu.com


此时浏览器会让你输入用户名密码,这是因为 Spring Security 在默认情况下会对所有URL添加Basic Auth认证。默认的用户名为user, 密码是随机生成的,在控制台日志中可以看到。

oauth2

画风虽然很简陋,但是基本功能都具备了。点击Authorize后,浏览器就会重定向到百度,并带上code参数:

这里写图片描述

拿到code以后,就可以调用

POST/GET http://client:secret@localhost:8080/oauth/token
  • 1

来换取access_token了:

curl -X POST -H "Cache-Control: no-cache" -H "Content-Type: application/x-www-form-urlencoded" -d 'grant_type=authorization_code&code=Li4NZo&redirect_uri=http://www.baidu.com' "http://client:secret@localhost:8080/oauth/token"

返回如下:

{
  "access_token": "32a1ca28-bc7a-4147-88a1-c95abcc30556",
  "token_type": "bearer",
  "expires_in": 2591999,
  "scope": "app"
}

到此我们最最基本的授权服务就搭建完成了。然而,这仅仅是个demo,如果要在生产环境中使用,还需要做更多的工作。

4. 使用MySQL存储access_token和client信息

把授权服务器中的数据存储到数据库中并不难,因为 Spring Cloud Security OAuth 已经为我们设计好了一套Schema和对应的DAO对象。但在使用之前,我们需要先对相关的类有一定的了解。

4.1 相关接口

Spring Cloud Security OAuth2通过DefaultTokenServices类来完成token生成、过期等 OAuth2 标准规定的业务逻辑,而DefaultTokenServices又是通过TokenStore接口完成对生成数据的持久化。在上面的demo中,TokenStore的默认实现为InMemoryTokenStore,即内存存储。 对于Client信息,ClientDetailsService接口负责从存储仓库中读取数据,在上面的demo中默认使用的也是InMemoryClientDetialsService实现类。说到这里就能看出,要想使用数据库存储,只需要提供这些接口的实现类即可。庆幸的是,框架已经为我们写好JDBC实现了,即JdbcTokenStoreJdbcClientDetailsService

4.2 建表

要想使用这些JDBC实现,首先要建表。框架为我们提前设计好了schema, 在github上:https://github.com/spring-projects/spring-security-oauth/blob/master/spring-security-oauth2/src/test/resources/schema.sql

在使用这套表结构之前要注意的是,对于MySQL来说,默认建表语句中主键是varchar(255)类型,在mysql中执行会报错,原因是mysql对varchar主键长度有限制。所以这里改成128即可。其次,语句中会有某些字段为LONGVARBINARY类型,它对应mysql的blob类型,也需要修改一下。

4.3 配置

数据库建好后,下一步就是配置框架使用JDBC实现。方法还是编写@Configuration类继承AuthorizationServerConfigurerAdapter

@Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private DataSource dataSource;
    @Bean // 声明TokenStore实现
    public TokenStore tokenStore() {
        return new JdbcTokenStore(dataSource);
    }
    @Bean // 声明 ClientDetails实现
    public ClientDetailsService clientDetails() {
        return new JdbcClientDetailsService(dataSource);
    }
    @Override // 配置框架应用上述实现
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.authenticationManager(authenticationManager);
        endpoints.tokenStore(tokenStore());

        // 配置TokenServices参数
        DefaultTokenServices tokenServices = new DefaultTokenServices();
        tokenServices.setTokenStore(endpoints.getTokenStore());
        tokenServices.setSupportRefreshToken(false);
        tokenServices.setClientDetailsService(endpoints.getClientDetailsService());
        tokenServices.setTokenEnhancer(endpoints.getTokenEnhancer());
        tokenServices.setAccessTokenValiditySeconds( (int) TimeUnit.DAYS.toSeconds(30)); // 30天
        endpoints.tokenServices(tokenServices);
    }

完成这些后,框架就会将中间产生的数据写到mysql中了。oauth_client_details是client表,可以直接在该表中添加记录来添加client: 
这里写图片描述

4.4 需要注意的地方

这里不得不说 Spring 设计有一个奇葩地的方。注意看oauth_access_token表是存放访问令牌的,但是并没有直接在字段中存放token。Spring 使用OAuth2AccessToken来抽象与令牌有关的所有属性,在写入到数据库时,Spring将该对象通过JDK自带的序列化机制序列成字节直接保存到了该表的token字段中。也就是说,如果只看数据表你是看不出access_token的值是多少,过期时间等信息的。这就给资源服务器的实现带来了麻烦。我们的资源提供方并没有使用Spring Security,也不想引入 Spring Security 的任何依赖,这时候就只能将 DefaultOAuth2AccessToken的源码copy到资源提供方的项目中,然后读取token字段并反序列化还原对象来获取token信息。但是如果这样做还会遇到反序列化兼容性的问题,具体解决方法参考我另一篇博文:http://blog.csdn.net/neosmith/article/details/52539614

5. 总结

至此一个能在生产环境下使用的授权服务就搭建好了。其实我们在实际使用时应该适当定制JdbcTokenStoreClientDetailsService来实适应业务需要,甚至可以直接从0开始实现接口,完全不用框架提供的实现。另外,Spring 直接将DefaultOAuth2AccessToken序列化成字节保存到数据库中的设计,我认为是非常不合理的。或许设计者的初衷是保密access_token,但是通过加密的方法也可以实现,完全不应该直接扔字节。不过通过定制TokenStore接口,我们可以使用自己的表结构而不拘泥于默认实现。

http://blog.csdn.net/tracker_w/article/category/6360121

http://blog.csdn.net/neosmith/article/details/52539927

posted @ 2016-09-16 18:22 paulwong 阅读(7927) | 评论 (0)编辑 收藏

使用Spring Cloud Feign作为HTTP客户端调用远程HTTP服务

在Spring Cloud Netflix栈中,各个微服务都是以HTTP接口的形式暴露自身服务的,因此在调用远程服务时就必须使用HTTP客户端。我们可以使用JDK原生的URLConnection、Apache的Http Client、Netty的异步HTTP Client, Spring的RestTemplate。但是,用起来最方便、最优雅的还是要属Feign了。

Feign简介

Feign是一种声明式、模板化的HTTP客户端。在Spring Cloud中使用Feign, 我们可以做到使用HTTP请求远程服务时能与调用本地方法一样的编码体验,开发者完全感知不到这是远程方法,更感知不到这是个HTTP请求。比如:

@Autowired
private AdvertGropRemoteService service; // 远程服务

public AdvertGroupVO foo(Integer groupId) {
    return service.findByGroupId(groupId); // 通过HTTP调用远程服务
}

开发者通过service.findByGroupId()就能完成发送HTTP请求和解码HTTP返回结果并封装成对象的过程。

Feign的定义

为了让Feign知道在调用方法时应该向哪个地址发请求以及请求需要带哪些参数,我们需要定义一个接口:

@FeignClient(name = "ea")  //  [A]
public interface AdvertGroupRemoteService {

    @RequestMapping(value = "/group/{groupId}", method = RequestMethod.GET) // [B]
    AdvertGroupVO findByGroupId(@PathVariable("groupId") Integer adGroupId) // [C]

    @RequestMapping(value = "/group/{groupId}", method = RequestMethod.PUT)
    void update(@PathVariable("groupId") Integer groupId, @RequestParam("groupName") String groupName)

A: @FeignClient用于通知Feign组件对该接口进行代理(不需要编写接口实现),使用者可直接通过@Autowired注入。

B: @RequestMapping表示在调用该方法时需要向/group/{groupId}发送GET请求。

C: @PathVariableSpringMVC中对应注解含义相同。

Spring Cloud应用在启动时,Feign会扫描标有@FeignClient注解的接口,生成代理,并注册到Spring容器中。生成代理时Feign会为每个接口方法创建一个RequetTemplate对象,该对象封装了HTTP请求需要的全部信息,请求参数名、请求方法等信息都是在这个过程中确定的,Feign的模板化就体现在这里。

在本例中,我们将Feign与Eureka和Ribbon组合使用,@FeignClient(name = "ea")意为通知Feign在调用该接口方法时要向Eureka中查询名为ea的服务,从而得到服务URL。

Feign的Encoder、Decoder和ErrorDecoder

Feign将方法签名中方法参数对象序列化为请求参数放到HTTP请求中的过程,是由编码器(Encoder)完成的。同理,将HTTP响应数据反序列化为java对象是由解码器(Decoder)完成的。

默认情况下,Feign会将标有@RequestParam注解的参数转换成字符串添加到URL中,将没有注解的参数通过Jackson转换成json放到请求体中。注意,如果在@RequetMapping中的method将请求方式指定为POST,那么所有未标注解的参数将会被忽略,例如:

@FeignClient(name = "ea")  //  [A]
public interface AdvertGroupRemoteService {

    @RequestMapping(value = "/group/{groupId}", method = RequestMethod.GET) // [B]
    AdvertGroupVO findByGroupId(@PathVariable("groupId") Integer adGroupId) // [C]

    @RequestMapping(value = "/group/{groupId}", method = RequestMethod.PUT)
    void update(@PathVariable("groupId") Integer groupId, @RequestParam("groupName") String groupName)

此时因为声明的是GET请求没有请求体,所以obj参数就会被忽略。

在Spring Cloud环境下,Feign的Encoder*只会用来编码没有添加注解的参数*。如果你自定义了Encoder, 那么只有在编码obj参数时才会调用你的Encoder。对于Decoder, 默认会委托给SpringMVC中的MappingJackson2HttpMessageConverter类进行解码。只有当状态码不在200 ~ 300之间时ErrorDecoder才会被调用。ErrorDecoder的作用是可以根据HTTP响应信息返回一个异常,该异常可以在调用Feign接口的地方被捕获到。我们目前就通过ErrorDecoder来使Feign接口抛出业务异常以供调用者处理。

Feign的HTTP Client

Feign在默认情况下使用的是JDK原生的URLConnection发送HTTP请求,没有连接池,但是对每个地址会保持一个长连接,即利用HTTP的persistence connection 。我们可以用Apache的HTTP Client替换Feign原始的http client, 从而获取连接池、超时时间等与性能息息相关的控制能力。Spring Cloud从Brixtion.SR5版本开始支持这种替换,首先在项目中声明Apache HTTP Client和feign-httpclient依赖:

@RequestMapping(value = "/group/{groupId}", method = RequestMethod.GET)
void update(@PathVariable("groupId") Integer groupId, @RequestParam("groupName") String groupName, DataObject obj);

然后在application.properties中添加:

feign.httpclient.enabled=true

总结

通过Feign, 我们能把HTTP远程调用对开发者完全透明,得到与调用本地方法一致的编码体验。这一点与阿里Dubbo中暴露远程服务的方式类似,区别在于Dubbo是基于私有二进制协议,而Feign本质上还是个HTTP客户端。如果是在用Spring Cloud Netflix搭建微服务,那么Feign无疑是最佳选择。

http://blog.csdn.net/tracker_w/article/category/6360121
http://blog.csdn.net/neosmith/article/details/52449921

posted @ 2016-09-16 18:13 paulwong 阅读(2282) | 评论 (0)编辑 收藏

微服务框架Spring Cloud

2016

posted @ 2016-09-11 20:49 paulwong 阅读(654) | 评论 (0)编辑 收藏

JHipster

基于SPRING CLOUD的微服务框架
http://jhipster.cn/

posted @ 2016-09-11 16:40 paulwong 阅读(301) | 评论 (0)编辑 收藏

Spring Boot 性能优化

摘要
Spring 框架给企业软件开发者提供了常见问题的通用解决方案,包括那些在未来开发中没有意识到的问题。但是,它构建的 J2EE 项目变得越来越臃肿,逐渐被 Spring Boot 所替代。Spring Boot 让我们创建和运行项目变得更为迅速,现在已经有越来越多的人使用它。我们已经在几个项目中使用了 Spring Boot ,今天我们就来一起讨论一下如何改进 Spring Boot 应用的性能。

Spring 框架给企业软件开发者提供了常见问题的通用解决方案,包括那些在未来开发中没有意识到的问题。但是,它构建的 J2EE 项目变得越来越臃肿,逐渐被 Spring Boot 所替代。Spring Boot 让我们创建和运行项目变得更为迅速,现在已经有越来越多的人使用它。我们已经在几个项目中使用了 Spring Boot ,今天我们就来一起讨论一下如何改进 Spring Boot 应用的性能。

首先,从之前我在开发中遇到的一个问题说起。在一次查看项目运行日志的时候,我偶然发现了一个问题,日志里显示这个项目总是加载 Velocity 模板引擎,但实际上这个项目是一个没有 web 页面的 REST Service 项目。于是我花了一点时间去寻找产生这个问题的原因,以及如何改进 Spring Boot 应用的性能。在查找了相关的资料后,我得出的结论如下:

组件自动扫描带来的问题

默认情况下,我们会使用 @SpringBootApplication 注解来自动获取的应用的配置信息,但这样也会给应用带来一些副作用。使用这个注解后,会触发自动配置( auto-configuration )和 组件扫描 ( component scanning),这跟使用 @Configuration、@EnableAutoConfiguration 和 @ComponentScan 三个注解的作用是一样的。这样做给开发带来方便的同时,也会有两方面的影响:

1、会导致项目启动时间变长。当启动一个大的应用程序,或将做大量的集成测试启动应用程序时,影响会特别明显。

2、会加载一些不需要的多余的实例(beans)。

3、会增加 CPU 消耗。

针对以上两个情况,我们可以移除 @SpringBootApplication 和 @ComponentScan 两个注解来禁用组件自动扫描,然后在我们需要的 bean 上进行显式配置:

// 移除 @SpringBootApplication and @ComponentScan, 用 @EnableAutoConfiguration 来替代
@Configuration
@EnableAutoConfiguration
public class SampleWebUiApplication {

    
// 

    
// 用 @Bean 注解明确显式配置,以便被 Spring 扫描到
    @Bean
    
public MessageController messageController(MessageRepository messageRepository) {
        
return new MessageController(messageRepository);
    }

如何避免组件自动扫描带来的问题

我们在上面提到,@SpringBootApplication 注解的作用跟 @EnableAutoConfiguration 注解的作用是相当的,那就意味着它也能带来上述的三个问题。要避免这些问题,我们就要知道我们需要的组件列表是哪些,可以用 -Ddebug 的方式来帮助我们明确地定位:

mvn spring-boot:run -Ddebug … ========================= AUTO-CONFIGURATION REPORT =========================   Positive matches: -----------------     DispatcherServletAutoConfiguration       - @ConditionalOnClass classes found: org.springframework.web.servlet.DispatcherServlet (OnClassCondition)       - found web application StandardServletEnvironment (OnWebApplicationCondition)  ... 

接着拷贝 Positive matches 中列出的信息:

DispatcherServletAutoConfiguration 
EmbeddedServletContainerAutoConfiguration
ErrorMvcAutoConfiguration
HttpEncodingAutoConfiguration
HttpMessageConvertersAutoConfiguration
JacksonAutoConfiguration
JmxAutoConfiguration
MultipartAutoConfiguration
ServerPropertiesAutoConfiguration
PropertyPlaceholderAutoConfiguration
ThymeleafAutoConfiguration
WebMvcAutoConfiguration
WebSocketAutoConfiguration

然后来更新项目配置,显式地引入这些组件,引入之后,再运行一下应用确保没有错误发生:

@Configuration
@Import({
        DispatcherServletAutoConfiguration.
class,
        EmbeddedServletContainerAutoConfiguration.
class,
        ErrorMvcAutoConfiguration.
class,
        HttpEncodingAutoConfiguration.
class,
        HttpMessageConvertersAutoConfiguration.
class,
        JacksonAutoConfiguration.
class,
        JmxAutoConfiguration.
class,
        MultipartAutoConfiguration.
class,
        ServerPropertiesAutoConfiguration.
class,
        PropertyPlaceholderAutoConfiguration.
class,
        ThymeleafAutoConfiguration.
class,
        WebMvcAutoConfiguration.
class,
        WebSocketAutoConfiguration.
class,
})
public class SampleWebUiApplication {}


在上面的代码中,我们可以删掉我们不需要的组件信息,来提高应用的性能,比如在我的项目中,不需要 JMX 和 WebSocket 功能,我就删掉了它们。删掉之后,再次运行项目,确保一切正常。

将Servlet容器变成Undertow

默认情况下,Spring Boot 使用 Tomcat 来作为内嵌的 Servlet 容器。我们可以启动项目,然后用 VisualVM 或者 JConsole 来查看应用所占的内存情况:

Spring Boot 性能优化

以上是我使用 Spring Boot 的默认方式启动应用后,用 VisualVM 监控到的内存的占用情况:堆内存占用 110M,16 个线程被开启。

可以将 Web 服务器切换到 Undertow 来提高应用性能。Undertow 是一个采用 Java 开发的灵活的高性能 Web 服务器,提供包括阻塞和基于 NIO 的非堵塞机制。Undertow 是红帽公司的开源产品,是 Wildfly 默认的 Web 服务器。首先,从依赖信息里移除 Tomcat 配置:

<exclusions>
        
<exclusion>
                
<groupId>org.springframework.boot</groupId>
                
<artifactId>spring-boot-starter-tomcat</artifactId>
        
</exclusion>
</exclusions>


然后添加 Undertow:

<dependency>
        
<groupId>org.springframework.boot</groupId>
        
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>


启动项目后,用 VisualVM 监控到的信息显示:堆内存占用 90M,13个线程被开启。

Spring Boot 性能优化

总结

这些都是我们在项目开发中使用到的一些优化 Spring Boot 应用的小技巧,对于大的应用性能的提高还是很明显的。大家可以尝试一下,然后告诉我们你的测试结果。

最后,附上代码,大家可以去这里下载:spring-boot-performance

文中大部分内容参考英国一个架构师的博客 和 DZone 近期发布的文章,在此感谢两位大牛。参考文章及链接:

(1)Spring Boot 性能优化:Spring Boot Performance

(2)Spring Boot 内存优化:Spring Boot Memory Performance

(3)https://www.techempower.com/benchmarks/

(4)Spring 应用程序优化:Optimizing Spring Framework for App Engine Applications

posted @ 2016-09-11 16:37 paulwong 阅读(513) | 评论 (0)编辑 收藏

spring cloud项目读取配置管理

摘要
spring cloud config server配置好了数据库连接信息,这个项目读取config,获取连接信息。这里以mybtis作为列子。从服务器读取jdbc信息后,运行mybatis程序。

确认服务是否成功
http://localhost:8888/demo-config/test
{"name":"demo-config","profiles":["test"],"label":"master","version":"02d28ad4925aa9bd1bf8a48d2edbf04ce61aa45a","propertySources":[{"name":"https://git.oschina.net/penghaozhong/demo.git/demo-config-repo/demo-config-test.properties","source":{"jdbc.url":"jdbc:mysql://localhost:3306/demo?characterEncoding=UTF-8","jdbc.username":"root","jdbc.driver":"com.mysql.jdbc.Driver","jdbc.password":"xxxxxx","jdbc.type":"mysql"}}]}

    2. 配置读取配置文件

  



在bootstrap.properties中添加读取配置管理的地址。

3. 读取配置文件属性,这里采用@ConfigurationProperties
/**
 * 读取数据库配置文件
 * 
@author penghaozhong
 *
 
*/
@ConfigurationProperties(prefix = DataSourceProperties.PREFIX, ignoreUnknownFields = false)
public  class  DataSourceProperties {
    
        public DataSourceProperties() {
        super();
    }
        //对应配置文件里的配置键
        public final static String PREFIX="jdbc";    
        
        private String type; 
        private String driver; 
        private String url; 
        private String username; 
        private String password;
        
        public String getType() {
            return type;
        }
        public void setType(String type) {
            this.type = type;
        }
        public String getDriver() {
            return driver;
        }
        public void setDriver(String driver) {
            this.driver = driver;
        }
        public String getUrl() {
            return url;
        }
        public void setUrl(String url) {
            this.url = url;
        }
        public String getUsername() {
            return username;
        }
        public void setUsername(String username) {
            this.username = username;
        }
        public String getPassword() {
            return password;
        }
        public void setPassword(String password) {
            this.password = password;
        }

}


4. 配置mybatis
@Configuration
@MapperScan("com.phz.test.spring.cloud.demo")
@EnableConfigurationProperties(DataSourceProperties.class)
@EnableTransactionManagement
public class MybatisDataSource {

    // mybaits mapper xml搜索路径
    private final static String MAPPERLOCATIONS = "classpath:/mappings/**/*.xml";
    private final static String CONFIGLOCATION = "classpath:/mybatis-config.xml";

    @Autowired
    private  DataSourceProperties dataSourceProperties;
    private DruidDataSource datasource = null;

    @Bean(destroyMethod = "close")
    public  DataSource dataSource(){
        datasource = new DruidDataSource();  
        datasource.setUrl(dataSourceProperties.getUrl());
        datasource.setDbType(dataSourceProperties.getType());
        datasource.setDriverClassName(dataSourceProperties.getDriver());
        datasource.setUsername(dataSourceProperties.getUsername());
        datasource.setPassword(dataSourceProperties.getPassword());
        return datasource;
    }

    @PreDestroy
    public void close() {
        if(datasource != null){
            datasource.close();
        }
    }

    @Bean
    public SqlSessionFactory sqlSessionFactoryBean() throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSource());
        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        sqlSessionFactoryBean.setMapperLocations(resolver.getResources(MAPPERLOCATIONS));
        sqlSessionFactoryBean.setConfigLocation(resolver.getResource(CONFIGLOCATION));
        sqlSessionFactoryBean.setTypeAliasesPackage("com.phz.test.spring.cloud.demo.entity");
        return sqlSessionFactoryBean.getObject();
    }

    @Bean
    public PlatformTransactionManager transactionManager() {
        return new DataSourceTransactionManager(dataSource());
    }

}











posted @ 2016-09-11 16:26 paulwong 阅读(1646) | 评论 (0)编辑 收藏

spring-loaded热部署

什么是spring-loaded?

spring-loaded是一个对于jvm代理运行时期改变类文件的重载(重新加载),它转换类loadtime让他们服从后重新加载。不像“热代码替换”只允许一次简单的改变JVM运行(例如更改方法体)spring-loaded允许您添加/修改/删除/字段/方法构造函数。注释类型/方法/字段/构造函数也可以修改和可以添加/删除/修改值的枚举类型。

有什么好处?

开发测试阶段:能够在启动后动态更改代码调试,无需重启减少切换debug时间(ps:对于eclipse而言,在debug时期只能做到动态更新方法体不能增加)
对于线上测试发布阶段: 能够在出现问题后直接替换class文件而不重启应用(ps:对于外部提供的服务jar形式同样能做到)
怎么使用?

项目地址

https://github.com/spring-projects/spring-loaded

第一步:下载文件
http://repo.spring.io/release/org/springframework/springloaded/1.2.5.RELEASE/springloaded-1.2.5.RELEASE.jar

第二步:配置jvm启动参数

eclipse
eclipse:run as --> run configurations --> arguments -->> VM arguments
-javaagent:E:\repository\org\springframework\spring-load\springloaded-1.2.5.RELEASE.jar 
-noverify -Dspringloaded=verbose
详细描述:
-javaagent: 配置java代理使用下载后的jar包路径
-noverify: 禁用字节码验证
-Dspringloaded=verbose 显示springloaded时的详细信息


image

java命令启动

java -javaagent:E:\repository\org\springframework\spring-load\springloaded-1.2.5.RELEASE.jar -noverify Test  类似 

java jar包动态替换

1.打成runnable Jar
2.命令启动:

java -javaagent:E:\repository\org\springframework\spring-load\springloaded-1.2.5.RELEASE.jar -noverify -Dspringloaded=watchJars=main.jar main.jar

/**  * 类Test.java的实现描述:TODO 类实现描述   * @author Administrator 2016年7月4日 下午4:55:59  */ public class Test {     public static void main(String[] args) throws InterruptedException {          while(true) {             try {                 println();                 Thread.sleep(1000);             } catch (Throwable e) {                  e.printStackTrace();              }          }       }     public static void println() {          System.out.println("112222221222222");          }  } 

改变为

/**  * 类Test.java的实现描述:TODO 类实现描述   * @author Administrator 2016年7月4日 下午4:55:59  */ public class Test {     public static void main(String[] args) throws InterruptedException {          while(true) {             try {                 println();                 Thread.sleep(1000);             } catch (Throwable e) {                  e.printStackTrace();              }          }       }     public static void println() {          System.out.println("test replace jar");          }  } 
3.重新打包替换
PS:实测在window下无用 手上无linux机器待测试




1

posted @ 2016-09-11 10:40 paulwong 阅读(1482) | 评论 (0)编辑 收藏

MICROSERVICE 资源

Spring Cloud 为开发者提供了在分布式系统(如配置管理、服务发现、断路器、智能路由、微代理、控制总线、一次性 Token、全局锁、决策竞选、分布式会话和集群状态)操作的开发工具。使用 Spring Cloud 开发者可以快速实现上述这些模式。

主要有eureka做服务发现、config做分布式配置、zuul做api-gateway、feign做客户端负载均衡、hystrix做断路器、turbine做聚合的monitor、graphite做指标监控。

http://blog.csdn.net/liaokailin/article/category/6212338


MICROSERVICE WITH SPRING-CLOUD.
https://github.com/kennyk65/Microservices-With-Spring-Student-Files




!!!spring-cloud-study
http://git.oschina.net/itmuch/spring-cloud-study

posted @ 2016-09-01 21:46 paulwong 阅读(193) | 评论 (0)编辑 收藏

Disruptor

一个ring buffer实现多线程通信。由于他们之间通信不需要锁所以性能有很大的提高。

Disruptor更多的应用在高速事务中,利用JVM的伪内存,这也可能是它为LMAX而生的,和akka的应用场景不一样。

一般编写并发应用程序马上想到多线程或者多进程。但多线程需要处理资源竞争,共享访问等问题,搞不好容易出现死锁,当程序规模比较大时,排查难度很大。 Actor模型提供了另一种编写并发应用程序的思路。 有点类似Node.JS的基于事件异步处理. (其实我觉得基于消息异步和基于事件异步是一回事)

什么是基于消息异步呢?很简单,比如要开展一个项目,需要多人协作。作为项目经理的你,只需要像手下发出命令,个人各干各的,互不干扰。做完了就回送一个消息给项目经理,项目经理再分派新的任务. (可能比喻得不恰当,但Actor的基本思路就是这样,你不需要考虑资源共享和线程并发什么的, Actor库屏蔽了这些底层的实现细节 . 每个Actor就相当于一个人或者叫一个处理者,他们的职责很单一,就是响应对方发来的消息,做出响应,并回送一个响应消息。 每个Actor负责做自己的份内事,最后有一个调度角色的Actor将所有Actor管理起来,形成一个整体)

Akka 是针对Scala和Java的Actor库,JActor是一个用纯Java编写的Actor库。 

https://github.com/LMAX-Exchange/disruptor
http://www.oschina.net/p/disruptor

posted @ 2016-06-05 18:19 paulwong 阅读(397) | 评论 (0)编辑 收藏

JPA概要

1 JPA概述

JPA(Java Persistence API,Java持久化API),定义了对象-关系映射(ORM)以及实体对象持久化的标准接口。

JPA是JSR-220(EJB3.0)规范的一部分,在JSR-220中规定实体对象(EntityBean)由JPA进行支持。

所以JPA不局限于EJB3.0,而是作为POJO持久化的标准规范,可以脱离容器独立运行,开发和测试更加方便。

JPA在应用中的位置如下图所示:

 

JPA维护一个Persistence Context(持久化上下文),在持久化上下文中维护实体的生命周期。主要包含三个方面的内容:

  1. ORM元数据。JPA支持annotion或xml两种形式描述对象-关系映射。
  2. 实体操作API。实现对实体对象的CRUD操作。
  3. 查询语言。约定了面向对象的查询语言JPQL(Java Persistence Query Language)。

JPA的主要API都定义在javax.persistence包中。如果你熟悉Hibernate,可以很容易做出对应:

 
org.hibernatejavax.persistence说明
cfg.ConfigurationPersistence读取配置信息
SessionFactoryEntityManagerFactory用于创建会话/实体管理器的工厂类
SessionEntityManager提供实体操作API,管理事务,创建查询
TransactionEntityTransaction管理事务
QueryQuery执行查询

2 实体生命周期

实体生命周期是JPA中非常重要的概念,描述了实体对象从创建到受控、从删除到游离的状态变换。对实体的操作主要就是改变实体的状态。

JPA中实体的生命周期如下图:

  1. New,新创建的实体对象,没有主键(identity)值
  2. Managed,对象处于Persistence Context(持久化上下文)中,被EntityManager管理
  3. Detached,对象已经游离到Persistence Context之外,进入Application Domain
  4. Removed, 实体对象被删除

EntityManager提供一系列的方法管理实体对象的生命周期,包括:

  1. persist, 将新创建的或已删除的实体转变为Managed状态,数据存入数据库。
  2. remove,删除受控实体
  3. merge,将游离实体转变为Managed状态,数据存入数据库。

如果使用了事务管理,则事务的commit/rollback也会改变实体的状态。

3 实体关系映射(ORM)

3.1 基本映射

 
对象端数据库端annotion可选annotion
ClassTable@Entity@Table(name="tablename")
propertycolumn@Column(name = "columnname")
propertyprimary key@Id@GeneratedValue 详见ID生成策略
propertyNONE@Transient 

3.2 ID生成策略

ID对应数据库表的主键,是保证唯一性的重要属性。JPA提供了以下几种ID生成策略

  1. GeneratorType.AUTO ,由JPA自动生成
  2. GenerationType.IDENTITY,使用数据库的自增长字段,需要数据库的支持(如SQL Server、MySQL、DB2、Derby等)
  3. GenerationType.SEQUENCE,使用数据库的序列号,需要数据库的支持(如Oracle)
  4. GenerationType.TABLE,使用指定的数据库表记录ID的增长 需要定义一个TableGenerator,在@GeneratedValue中引用。例如:

    @TableGenerator( name="myGenerator", table="GENERATORTABLE", pkColumnName = "ENTITYNAME", pkColumnValue="MyEntity", valueColumnName = "PKVALUE", allocationSize=1 )

    @GeneratedValue(strategy = GenerationType.TABLE,generator="myGenerator")

3.3 关联关系

JPA定义了one-to-one、one-to-many、many-to-one、many-to-many 4种关系。

对于数据库来说,通常在一个表中记录对另一个表的外键关联;对应到实体对象,持有关联数据的一方称为owning-side,另一方称为inverse-side。

为了编程的方便,我们经常会希望在inverse-side也能引用到owning-side的对象,此时就构建了双向关联关系。 在双向关联中,需要在inverse-side定义mappedBy属性,以指明在owning-side是哪一个属性持有的关联数据。

对关联关系映射的要点如下:

 
关系类型Owning-SideInverse-Side
one-to-one@OneToOne@OneToOne(mappedBy="othersideName")
one-to-many / many-to-one@ManyToOne@OneToMany(mappedBy="xxx")
many-to-many@ManyToMany@ManyToMany(mappedBy ="xxx")

其中 many-to-many关系的owning-side可以使用@JoinTable声明自定义关联表,比如Book和Author之间的关联表:

@JoinTable(name = "BOOKAUTHOR", joinColumns = { @JoinColumn(name = "BOOKID", referencedColumnName = "id") }, inverseJoinColumns = { @JoinColumn(name = "AUTHORID", referencedColumnName = "id") })

关联关系还可以定制延迟加载和级联操作的行为(owning-side和inverse-side可以分别设置):

通过设置fetch=FetchType.LAZY 或 fetch=FetchType.EAGER来决定关联对象是延迟加载或立即加载。

通过设置cascade={options}可以设置级联操作的行为,其中options可以是以下组合:

  • CascadeType.MERGE 级联更新
  • CascadeType.PERSIST 级联保存
  • CascadeType.REFRESH 级联刷新
  • CascadeType.REMOVE 级联删除
  • CascadeType.ALL 级联上述4种操作

3.4 继承关系

JPA通过在父类增加@Inheritance(strategy=InheritanceType.xxx)来声明继承关系。A支持3种继承策略:

  1. 单表继承(InheritanceType.SINGLETABLE),所有继承树上的类共用一张表,在父类指定(@DiscriminatorColumn)声明并在每个类指定@DiscriminatorValue来区分类型。
  2. 类表继承(InheritanceType.JOINED),父子类共同的部分公用一张表,其余部分保存到各自的表,通过join进行关联。
  3. 具体表继承(InheritanceType.TABLEPERCLASS),每个具体类映射到自己的表。

其中1和2能够支持多态,但是1需要允许字段为NULL,2需要多个JOIN关系;3最适合关系数据库,对多态支持不好。具体应用时根据需要取舍。

4 事件及监听

通过在实体的方法上标注@PrePersist,@PostPersist等声明即可在事件发生时触发这些方法。

5 Query Language 查询语言

JPA提供两种查询方式,一种是根据主键查询,使用EntityManager的find方法:

T find(Class entityClass, Object primaryKey)

另一种就是使用JPQL查询语言。JPQL是完全面向对象的,具备继承、多态和关联等特性,和hibernate HQL很相似。

使用EntityManager的createQuery方法:

Query createQuery(String qlString)

5.1 使用参数

可以在JPQL语句中使用参数。JPQL支持命名参数和位置参数两种参数,但是在一条JPQL语句中所有的参数只能使用同一种类型。

举例如下:

  • 命令参数

Query query = em.createQuery("select p from Person p where p.personid=:Id"); query.setParameter("Id",new Integer(1));

  • 位置参数

Query query = em.createQuery("select p from Person p where p.personid=?1"); query.setParameter(1,new Integer(1));

5.2 命名查询

如果某个JPQL语句需要在多个地方使用,还可以使用@NamedQuery 或者 @NamedQueries在实体对象上预定义命名查询。

在需要调用的地方只要引用该查询的名字即可。

例如:

@NamedQuery(name="getPerson", query= "FROM Person WHERE personid=?1")

@NamedQueries({ @NamedQuery(name="getPerson1", query= "FROM Person WHERE personid=?1"), @NamedQuery(name="getPersonList", query= "FROM Person WHERE age>?1") })

Query query = em.createNamedQuery("getPerson");

5.3 排序

JPQL也支持排序,类似于SQL中的语法。例如: Query query = em.createQuery("select p from Person p order by p.age, p.birthday desc");

5.4 聚合查询

JPQL支持AVG、SUM、COUNT、MAX、MIN五个聚合函数。例如:

Query query = em.createQuery("select max(p.age) from Person p"); Object result = query.getSingleResult(); String maxAge = result.toString();

5.5 更新和删除

JPQL不仅用于查询,还可以用于批量更新和删除。

如:

Query query = em.createQuery("update Order as o set o.amount=o.amount+10"); //update 的记录数 int result = query.executeUpdate();

Query query = em.createQuery("delete from OrderItem item where item.order in(from Order as o where o.amount<100)"); query.executeUpdate();

query = em.createQuery("delete from Order as o where o.amount<100"); query.executeUpdate();//delete的记录数

5.6 更多

与SQL类似,JPQL还涉及到更多的语法,可以参考:http://docs.oracle.com/cd/E11035_01/kodo41/full/html/ejb3_langref.html

6 事务管理

JPA支持本地事务管理(RESOURCELOCAL)和容器事务管理(JTA),容器事务管理只能用在EJB/Web容器环境中。

事务管理的类型可以在persistence.xml文件中的“transaction-type”元素配置。

JPA中通过EntityManager的getTransaction()方法获取事务的实例(EntityTransaction),之后可以调用事务的begin()、commit()、rollback()方法。

Date: 2012-12-30 16:46:29 CST

Author: Holbrook

Org version 7.8.11 with Emacs version 24

posted @ 2016-06-04 16:08 paulwong 阅读(263) | 评论 (0)编辑 收藏

IM资源

kafka-examples视频上的例子
https://github.com/gwenshap/kafka-examples

zer0MQTTServer
https://github.com/zer0Black/zer0MQTTServer
http://www.w2bc.com/Article/85741

moquette
https://github.com/andsel/moquette

融云 Blog
http://www.rongcloud.cn/pricing

EMQTT
http://emqtt.com/docs

群聊天系统,高吞吐量,高转发量,后端什么架构?
http://www.v2ex.com/t/92125

用Java实现一个IM的server端需要熟悉哪些技术点或框架?
http://www.zhihu.com/question/37090498

使用Java API创建(create),查看(describe),列举(list),删除(delete)Kafka主题(Topic)--转载
http://www.cnblogs.com/davidwang456/p/4313784.html

kafka中文教程
http://orchome.com/3

Kafka入门经典教程
http://www.open-open.com/lib/view/open1455842076964.html

Kafka深度解析
http://www.open-open.com/lib/view/open1457055194765.html

大型网站架构系列:分布式消息队列(一)
http://www.open-open.com/lib/view/open1453645976605.html



posted @ 2016-05-23 22:03 paulwong 阅读(261) | 评论 (0)编辑 收藏

Eclipse使用git最简易流程

git有诸多好处,网上都说的很清楚了,在这里我不再赘述。对于我来说,私下里想做一些项目,而又不能很好的保存自己的代码和进行版本控制,这时候,就用到了git。下面,就以我个人为例讲讲git从0开始如何安装使用。

Step1 准备工作

msysgit,下载地址为http://msysgit.github.io/

Eclipse IDE for Java EE Developers(必须是这个,自带Egit,或者自行安装Egit插件),在Eclipse官网下载,地址http://www.eclipse.org/downloads/

github账号,github官网注册一个,地址https://github.com/

廖雪峰的git教程,地址为http://www.liaoxuefeng.com/wiki/0013739516305929606dd18361248578c67b8067c8c017b000,讲得很好,我大概花了3个晚上的时间看,如果仅仅是为了安装上,可以先暂时略过,后面再看。

Step2 msysgit安装

一路next就行,注意可能会遇到360的拦截(该死,点击允许本程序所有操作或者安装前就退出360即可)。

Step3 git用户名和邮箱配置

单击开始菜单,打开Git Bush,

教程1

 

输入以下命令

git config --global user.name "Your Name"

git config --global user.email email@example.com

用户名和邮箱替换为自己的,为本台机器所有仓库指定用户名和email地址。

 

Step4 创建SSH Key

因为本地git仓库和远程的github仓库之间是通过ssh加密的,所以,还需要创建一个SSH Key。

输入以下命令

ssh-keygen -t rsa -C youremail@example.com

同样将邮箱替换为自己的。在路径C盘->用户->用户名,找到.ssh文件夹,打开就可以看到两个文件id_rsa和id_rsa.pub,

打开id_rsa.pub,将其中的内容复制下来(这就相当于一把钥匙,持有钥匙的人可以访问你的内容,下一个小结讲解这把钥匙的用途),下面是我的

教程2

 

Step5 与github上的仓库相关联

注册一个github账号

注册完成之后,点击右上角的settings(就是那个齿轮,设置的图标)

教程3

 

进入设置页面

image

选SSH Keys,点击add SSH key,就可以添加了,title随便填写,将从id_rsa.pub中复制的内容粘贴到这里。从此,gitgub仓库就能够识别你本地的仓库了。也就是说,你离将本地代码推送到github上,或者将github上的代码克隆到本地,就只差一步了。(可参考廖雪峰前辈的教程先进行在非Eclipse下的操作,更有感觉)

 

Step6 Egit的使用

首先,登入你的github账号,点击加号,选择New repositrory,创建一个新的仓库,如图所示。

教程6

名字叫做test2,其他都默认就好。

点击绿色按钮创建,就创建好了一个github仓库。

教程7

将地址复制下来,保存好备用。

 

打开Eclipse,像平常一样,新建一个web工程,或者其他你常用的工程。我这里工程叫做scott

{4$7`FVS]283EW(~80FR(JT

右击项目,选择Team->Share Project,弹出如下窗口。

image

选择git,next,在弹出的界面上,①先勾选Use or create……,②createrepository,③勾选工程,④finish。

教程8

这时候,你就创建好了一个git工程,右击工程,选择Team,你可以像svn一样进行commit,synchronize等操作,但是,remote操作中,push是灰色的,表示还不能将代码推送到github仓库上去。

教程10

这时,打开Git Repository视图(具体怎么打开百度去,这都是基本常识了)

教程11

右键选择Create Remote,点OK

image

点击Change,弹出如下界面

image

URI输入之前在github上拷贝的地址,下面的内容会自动填上。你只需要再填上github上的用户名和密码即可。finish,save。

接下来的几步就简单了,在项目里面随便写点代码,然后先将新增的代码提交到本地,例如,将cat类,先add to index,然后commit。这时候,右键工程,Team->remote中就有了push选项,意味着你可以将本地代码保存在github中了。祝贺你!

教程12

点击Push,点击add All Branches Spec,将本地版本库添加,然后Finish,后面一路Next,再次查看github,可以看到代码已经成功推送。

最后,看看我们在github上的代码吧!

作者: 张万帆 
欢迎任何形式的转载,但请务必注明出处。

posted @ 2016-05-15 18:17 paulwong 阅读(921) | 评论 (0)编辑 收藏

Eclipse上安装GIT插件EGit及使用

一、Eclipse上安装GIT插件EGit

 

      Eclipse的版本eclipse-java-helios-SR2-win32.zip(在Eclipse3.3版本找不到对应的 EGit插件,无法安装)

 

     

 

 

 

EGit插件地址:http://download.eclipse.org/egit/updates

 

OK,随后连续下一步默认安装就可以,安装后进行重启Eclipse

 

二、在Eclipse中配置EGit

 

准备工作:需要在https://github.com 上注册账号

 

Preferences > Team > Git > Configuration

 

 

这里的user.name 是你在https://github.com上注册用户名

 

 

user.email是你在github上绑定的邮箱。在这里配置user.name即可

 

 

三、新建项目,并将代码提交到本地的GIT仓库中

 

1、新建项目 git_demo,并新建HelloWorld.java类

 

 

2、将git_demo项目提交到本地仓库,如下图

 

 

 

 

 

 

到此步,就成功创建GIT仓库。但文件夹处于untracked状态(文件夹中的符号”?”表示),下面我们需要提交代码到本地仓库,如下图

 

 

 

 

 

 

 

OK,这样代码提交到了本地仓库

 

 

四:将本地代码提交到远程的GIT仓库中

 

准备工作:在https://github.com上创建仓库

 

 

 

 

 

 

点击“Create repository” ,ok,这样在github上的仓库就创建好了。

 

注意创建好远程仓库后,点击进去,此时可以看到一个HTTP地址,如红线框,这个是你http协议的远程仓库地址

 

 

准备工作做好了,那开始将代码提交到远程仓库吧

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

OK,这样提交远程GIT就完成了,可以在https://github.com核对一下代码是否已经提交

 

 

注意的问题

   如果是首次提交会第一步:先在本地建立一个一样的仓库,称本地仓库。

          第二步:在本地进行commit操作将把更新提交到本地仓库;

          第三步: 将服务器端的更新pull到本地仓库进行合并,最后将合并好的本地仓库push到服务器端,这样就进行了一次远程提交。

 

  如果非首次提交同样的道理

          第一步:将修改的代码commit操作更新到本地仓库;

         第二步:第三步: 将服务器端的更新pull到本地仓库进行合并,最后将合并好的本地仓库push到服务器端

 

 

posted @ 2016-05-15 17:46 paulwong 阅读(238) | 评论 (0)编辑 收藏

排版六原则


作者: 阮一峰

日期: 2010年10月16日

上个月,我贴了《图形化简历》

几天后,就收到了秋叶老师的来信,希望与我探讨一些设计问题。他写过一本畅销书《说服力-让你的PPT会说话》,眼下正在写续集。

我看了新书的样章,觉得很不错,有些内容很值得分享。

====================================

首先,我们先看一个例子。良好的设计如何使得一个平庸的文档脱胎换骨。下面是一张大学生的求职简历,再普通不过了,想要引起招聘经理的注意,恐怕很难。

秋叶老师对它进行了简单的排版,还是一张表格,还是黑白配色,没有使用任何图形元素,效果却完全不一样了。

真是令人眼前一亮,不由自主地想多看几眼。这就是优秀设计的作用:它让你脱颖而出。

====================================

秋叶老师把他的排版心得,总结为六个原则:对齐,聚拢,重复,对比,强调,留白。我是这样理解的:

一、对齐原则

  相关内容必须对齐,次级标题必须缩进,方便读者视线快速移动,一眼看到最重要的信息。

二、聚拢原则

  将内容分成几个区域,相关内容都聚在一个区域中。段间距应该大于段内的行距。

三、留白原则

  千万不要把页面排得密密麻麻,要留出一定的空白,这本身就是对页面的分隔。这样既减少了页面的压迫感,又可以引导读者视线,突出重点内容。

四、降噪原则

  颜色过多、字数过多、图形过繁,都是分散读者注意力的"噪音"。

五、重复原则

  多页面排版时,注意各个页面设计上的一致性和连贯性。另外,在内容上,重要信息值得重复出现。

六、对比原则

  加大不同元素的视觉差异。这样既增加了页面的活泼,又方便读者集中注意力阅读某一个子区域。

====================================

下面用一个PPT的例子,演示排版六原则。

上面这张ppt有两个毛病。一是字数太多,抓不住重点;二是右边没有对齐,使得读者的视线只能一行行地从行首到行尾移动,不能直上直下。

现在进行修改。

第一步,根据"聚拢原则",将六点分成六个区域。

第二步,根据"降噪原则",将每一点分成"小标题"和"说明文字"两部分。

第三步,根据"对齐原则",将每一个部分、每一种元素对齐。

第四步,根据"对比原则",加大"小标题"和"说明文字"在字体和颜色上的差异。

第五步,根据"留白原则",留出一定的空白。

页面的可读性大大增加。

(完)

posted @ 2016-05-06 17:55 paulwong 阅读(154) | 评论 (0)编辑 收藏

MyThrift 0.0.1 发布,轻量级 RPC 服务框架

1)什么是MyThrift?

基于以下组件打造的一款轻量级RPC服务框架:thrift(facebook出品)、commons-pool(socket连接池)、自研服务治理框架(zookeeper).

2)为什么要写MyThrift?

2.1)提出问题:

    各种存储越来越多(redis,mysql,hdfs,hbase,mq),

    让web开发人员自己访问存储,并保证性能,是一件高要求的事情。

    造成的结果就是软件开发进度缓慢,性能低下,各种bug.

2.2)分析问题:

    RPC框架:考虑到本人看过thrift的源码,尤其是针对网络模块非常熟悉,并在实际生产环境中使用过,thrift性能不错。

    TCP/IP:有过2年的TCP/IP报文分析经验,从链路层到应用层都很熟悉。

    ZooKeeper:在MySQL-Binlog项目中攒出了一些使用经验,同时积极吸收Motan,JACK,HArpc等兄弟软件的优秀理念部分。

    连接池:使用commons-pool连接池组件。

2.3)解决问题:

    糅合thrift,zookeeper,commons-pool打造一款轻量级、性能高、上手容易的rpc调用框架,

    使得架构师和后台开发人员可以将各种复杂存储的IO访问对外暴露为服务(其实就是跨机器的普通函数调用)

    这样web开发人员可以专注于业务逻辑,加速产品迭代,对企业带来的好处不用多说!

本次版本内容:

1)server端进行网络模型参数调优

2)client端有连接池

3)支持server注册多个thrift的处理器Processor

可用于集中式负载的服务化组件,达到生产环境级别,经历过生产环境的考验,可放心使用。

http://git.oschina.net/qiangzigege/MyThrift 

内附非常详细+多图预警的《MyThrift在线使用文档.md》,0基础轻松上手!

posted @ 2016-05-05 17:02 paulwong 阅读(1332) | 评论 (0)编辑 收藏

Couchbase介绍,更好的Cache系统

在移动互联网时代,我们面对的是更多的客户端,更低的请求延迟,这当然需要对数据做大量的 Cache 以提高读写速度。

术语

  • 节点:指集群里的一台服务器。

现有 Cache 系统的特点

目前业界使用得最多的 Cache 系统主要是 memcached 和 redis。 这两个 Cache 系统都有都有很大的用户群,可以说是比较成熟的解决方案,也是很多系统当然的选择。 不过,在使用 memcached 和 redis 过程中,还是碰到了不少的问题和局限:

  • Cluster 支持不够。在扩容、负载均衡、高可用等方面存在明显不足。
  • 持久化支持不好,出现问题后恢复的代价大。memcached 完全不支持持久化,redis 的持久化会造成系统间歇性的负载很高。

我期待的理想 Cache 系统

良好的 cluster 支持

  • Key 可以动态分散(Auto Sharding)在不同的服务器上,可以通过动态添加服务器节点增加系统容量。
  • 没有单点失效,任何一个单点都不会造成数据不可访问。
  • 读写负载可以均匀分布在系统的不同节点上。

支持异步持久化支持

  • 方便快速恢复,甚至可以直接用作 key/value 数据库。 经常在跟业界朋友交流时,会提到用 key 分段的方法来做容量扩展以及负载均衡。但是用静态的 key 分段会有不少问题:
  • Cache 系统本身及使用 cache 的客户端都需要预设一个分段逻辑,这个逻辑后期如果需要调整将会非常困难。不能解决单点失效的问题,还需要额外的手段。运维需要更多的人为参与,避免 key 超出现有分区,一旦出现 key 找不到对应服务器,访问直接失败。

最接近需求的系统:Couchbase

基于这些想法,我花了几天时间在 google, stack overflow, quora 上看了很多大家关于 cache cluster 的讨论,找到一个比较新系统 Couchbase。

mem vs cbmemcached VS couchbase

Couchbase 的集群设计对等网

Couchbase 群集所有点都是对等的,只是在创建群或者加入集群时需要指定一个主节点,一旦结点成功加入集群,所有的结点对等。

high_level_architecture

图片来源:couchbase.com

对等网的优点是,集群中的任何节点失效,集群对外提供服务完全不会中断,只是集群的容量受影响。 Smart Client

由于 couchbase 是对等网集群,所有的节点都可以同时对客户端提供服务,这就需要有方法把集群的节点信息暴露给客户端,couchbase 提供了一套机制,客户端可以获取所有节点的状态以及节点的变动,由客户端根据集群的当前状态计算 key 所在的位置。 vBucket

vBucket 概念的引入,是 couchbase 实现 auto sharding,在线动态增减节点的重要基础。

简单的解释 vBucket 可以从静态分片开始说起,静态分片的做法一般是用 key 算出一个 hash,得到对应的服务器,这个算法很简单,也容易理解。如以下代码所示:

servers = ['server1:11211', 'server2:11211', 'server3:11211'] server_for_key(key) = servers[hash(key) % servers.length] 

但也有几个问题:

  • 如果一台服务器失效,会造成该分片的所有 key 失效。
  • 如果服务器容量不同,管理非常麻烦。
  • 前面提到过,运维、配置非常不方便。

为了把 key 跟服务器解耦合,couchbase 引入了 vBucket。可以说 vBucket 代表一个 cache 子集,主要特点:

  • key hash 对应一个 vBucket,不再直接对应服务器。
  • 集群维护一个全局的 vBucket 与服务器对应表。
  • 前面提到的 smart client 重要的功能就是同步 vBucket 表。

如以下代码所示:

servers = ['server1:11211', 'server2:11211', 'server3:11211'] vbuckets = [0, 0, 1, 1, 2, 2] server_for_key(key) = servers[vbuckets[hash(key) % vbuckets.length]] 

vBucket

图片来源:http://dustin.sallings.org/2010/06/29/memcached-vbuckets.html

由于 vBucket 把 key 跟服务器的静态对应关系解耦合,基于 vBucket 可以实现一些非常强大有趣的功能,例如:

  • Replica,以 vBucket 为单位的主从备份。如果某个节点失效,只需要更新 vBucket 映射表,马上启用备份数据。
  • 动态扩容。新增加一个节点后,可以把部分 vBucket 转移到新节点上,并更新 vBucket 映射表。

vBucket 非常重要,以后可以单独写一篇文章分享。

总结

  • Couchbase 的对等网设计,smart client 直接获取整的集群的信息,在客户端实现负载均衡,整个集群没有单点失效,并且完全支持平行扩展。
  • vBucket 的引入,完全实现了 auto sharding,可以方便灵活的把数据的子集在不同节点上移动,以实现集群动态管理。
  • Couchbase 有一个非常专业的 web 管理界面,并且支持通过 RESTful API 管理,这也是 memcached, redis 不能企及的。
  • 如果只是做 key/value 的 cache,Couchbase 可以完全取代 memcached。
  • Couchbase 已经被我们在生产环境中大量采用。

posted @ 2016-05-04 15:00 paulwong 阅读(274) | 评论 (0)编辑 收藏

[转]巅峰对决 - 框架的性能比较

转自:http://colobu.com/2016/04/25/performance-comparison-of-frameworks/
这句话也可以应用在软件开发上,"无快不破"强调的是软件的性能。我陆陆续续写了多篇各种框架的文章,也在github上开了多个性能比较的开源项目,本文做一个汇总,以备将来的查找。

  1. 最快的web服务器
  2. 最快的并发框架
  3. 最快的RPC服务器
  4. 最快的websocket框架
  5. 最快的RESTful框架
  6. 最快的Go序列化框架
  7. 最快的Go web框架
  8. 最快的Java序列化框架

posted @ 2016-05-03 14:36 paulwong 阅读(211) | 评论 (0)编辑 收藏

轻量级 RPC 框架新浪微博的 motan

概述

motan是一套高性能、易于使用的分布式远程服务调用(RPC)框架。

功能

  • 支持通过spring配置方式集成,无需额外编写代码即可为服务提供分布式调用能力。

  • 支持集成consul、zookeeper等配置服务组件,提供集群环境的服务发现及治理能力。

  • 支持动态自定义负载均衡、跨机房流量调整等高级服务调度能力。

  • 基于高并发、高负载场景进行优化,保障生产环境下RPC服务高可用。

文档索引

posted @ 2016-04-25 14:22 paulwong 阅读(1604) | 评论 (1)编辑 收藏

dubboss

HTTPCLIENT请求基于NETTY的HTTP服务器,NETTY生成相关的HTTPREQUEST、HTTPRESPONSE,调用SPRING 的DISPATCHSERVLET.SERVICE,将HTTPRESPONSE生成二进制字节,返回HTTPCLIENT。DISPATCHSERVLET同时会调用SPRING的CONTROLLER干活,即以前的SPRING MVC那套可以保留。

缺点:
nginx+tomcat与netty优缺点
http://www.oschina.net/question/1765708_158026

我的架构最开始是 nginx + [netty (tcp) ] 用nginx做负载均衡。后来分布式接口变成了http restful -> nginx + [netty(http) ]

于是参考了netty的官方http例子,开始移植代码,其实移植量不大,只需要把原来的tpc handler里面的逻辑放到http handler里面就可以了。本以为ok了,测试才发现了大量的问题,其实问题本身是由于对http基础掌握的还不够透彻,比如keep-alive属性,这个平时基本不用的东西,当你要自己开发http server时,变的很有用,你不能每次都close channel.你要化很多时间了解header里面每个字段的意思,然后去实现它。再比如一个post请求,你要自己写代码从body里面读数据,解码等。

最终我放弃了netty,回到了tomcat.应为tomcat把http的一切都帮你搞定了。

最终的架构是 nginx + [tomcat(http) ]

tomcat就是针对http层的,所以我建议http还是选择tomcat(或者其他成熟的http-server),并不是说netty不好,而是你的选择问题,netty是一个网络组件,tcp,udp,http都可以弄,但是官方文档都是些hello wolrd级别的。如果你非常了解http结构,完全可以基于netty搞出一个比tomcat牛的http server.

如果做tcp开发,netty不二之选!

dubboss是一个分布式REST服务的框架,实现了如下功能:

  • 基于dubbo的RPC
  • 基于netty的应用服务器
  • 基于springmvc的REST调用
  • 基于ngnix的负载均衡

https://github.com/leiguorui/dubboss

posted @ 2016-04-20 13:04 paulwong 阅读(1423) | 评论 (0)编辑 收藏

!!!架构网站内容不错

http://colobu.com/categories/%E6%9E%B6%E6%9E%84/page/2/

posted @ 2016-04-19 17:54 paulwong 阅读(184) | 评论 (0)编辑 收藏

SPRING MVC整合NETTY

http://wenku.baidu.com/view/4573aba4ce2f0066f53322e8.html

posted @ 2016-04-19 13:27 paulwong 阅读(1529) | 评论 (0)编辑 收藏

Tigase服务器安装以及集群

     摘要: 安装第一台tigase服务器安装tigase服务器所需环境。[root@tigase3~]# yum install java-1.7.0-openjdk.x86_64 java-1.7.0-openjdk-devel.x86_64下载tigase服务器软件包。[root@tigase3tmp]#wget https://projects.tigase.org/attachments/d...  阅读全文

posted @ 2016-04-19 10:17 paulwong 阅读(335) | 评论 (0)编辑 收藏

在 Java 应用程序中使用 Elasticsearch

http://www.ibm.com/developerworks/cn/java/j-use-elasticsearch-java-apps/index.html?ca=drs-

posted @ 2016-04-18 23:39 paulwong 阅读(247) | 评论 (0)编辑 收藏

集中式日志系统 ELK 协议栈详解


http://www.ibm.com/developerworks/cn/opensource/os-cn-elk/index.html?ca=drs-

posted @ 2016-04-18 23:33 paulwong 阅读(255) | 评论 (0)编辑 收藏

DUBBO简介

http://dubbo.io/User+Guide-zh.htm

随着互联网的发展,网站应用的规模不断扩大,常规的垂直应用架构已无法应对,分布式服务架构以及流动计算架构势在必行,亟需一个治理系统确保架构有条不紊的演进。

  • 单一应用架构
    • 当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本。
    • 此时,用于简化增删改查工作量的 数据访问框架(ORM) 是关键。
  • 垂直应用架构
    • 当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,将应用拆成互不相干的几个应用,以提升效率。
    • 此时,用于加速前端页面开发的 Web框架(MVC) 是关键。
  • 分布式服务架构
    • 当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。
    • 此时,用于提高业务复用及整合的 分布式服务框架(RPC) 是关键。
  • 流动计算架构
    • 当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。
    • 此时,用于提高机器利用率的 资源调度和治理中心(SOA) 是关键。

posted @ 2016-04-15 11:28 paulwong 阅读(216) | 评论 (0)编辑 收藏

Netty是什么?

本质:JBoss做的一个Jar包

 

目的:快速开发高性能、高可靠性的网络服务器和客户端程序

 

优点:提供异步的、事件驱动的网络应用程序框架和工具

 

通俗的说:一个好使的处理Socket的东东

 

 

如果没有Netty?

 

远古:java.net + java.io

 

近代:java.nio

 

其他:Mina,Grizzly

 

为什么不是Mina?

 

1、都是Trustin Lee的作品,Netty更晚;

2、Mina将内核和一些特性的联系过于紧密,使得用户在不需要这些特性的时候无法脱离,相比下性能会有所下降,Netty解决了这个设计问题;

3、Netty的文档更清晰,很多Mina的特性在Netty里都有;

4、Netty更新周期更短,新版本的发布比较快;

5、它们的架构差别不大,Mina靠apache生存,而Netty靠jboss,和jboss的结合度非常高,Netty有对google protocal buf的支持,有更完整的ioc容器支持(spring,guice,jbossmc和osgi);

6、Netty比Mina使用起来更简单,Netty里你可以自定义的处理upstream events 或/和 downstream events,可以使用decoder和encoder来解码和编码发送内容;

7、Netty和Mina在处理UDP时有一些不同,Netty将UDP无连接的特性暴露出来;而Mina对UDP进行了高级层次的抽象,可以把UDP当成"面向连接"的协议,而要Netty做到这一点比较困难。

 

 

Netty的特性

 

设计

统一的API,适用于不同的协议(阻塞和非阻塞)

基于灵活、可扩展的事件驱动模型

高度可定制的线程模型

可靠的无连接数据Socket支持(UDP)

 

性能

更好的吞吐量,低延迟

更省资源

尽量减少不必要的内存拷贝

 

安全

完整的SSL/TLS和STARTTLS的支持

能在Applet与Android的限制环境运行良好

 

健壮性

不再因过快、过慢或超负载连接导致OutOfMemoryError

不再有在高速网络环境下NIO读写频率不一致的问题

 

易用

完善的JavaDoc,用户指南和样例

简洁简单

仅信赖于JDK1.5

 

看例子吧!

 

Server端:

 

Java代码  收藏代码
  1. package me.hello.netty;  
  2.   
  3. import org.jboss.netty.bootstrap.ServerBootstrap;  
  4. import org.jboss.netty.channel.*;  
  5. import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;  
  6. import org.jboss.netty.handler.codec.string.StringDecoder;  
  7. import org.jboss.netty.handler.codec.string.StringEncoder;  
  8.   
  9. import java.net.InetSocketAddress;  
  10. import java.util.concurrent.Executors;  
  11.   
  12. /** 
  13.  * God Bless You! 
  14.  * Author: Fangniude 
  15.  * Date: 2013-07-15 
  16.  */  
  17. public class NettyServer {  
  18.     public static void main(String[] args) {  
  19.         ServerBootstrap bootstrap = new ServerBootstrap(new NioServerSocketChannelFactory(Executors.newCachedThreadPool(), Executors.newCachedThreadPool()));  
  20.   
  21.         // Set up the default event pipeline.  
  22.         bootstrap.setPipelineFactory(new ChannelPipelineFactory() {  
  23.             @Override  
  24.             public ChannelPipeline getPipeline() throws Exception {  
  25.                 return Channels.pipeline(new StringDecoder(), new StringEncoder(), new ServerHandler());  
  26.             }  
  27.         });  
  28.   
  29.         // Bind and start to accept incoming connections.  
  30.         Channel bind = bootstrap.bind(new InetSocketAddress(8000));  
  31.         System.out.println("Server已经启动,监听端口: " + bind.getLocalAddress() + ", 等待客户端注册。。。");  
  32.     }  
  33.   
  34.     private static class ServerHandler extends SimpleChannelHandler {  
  35.         @Override  
  36.         public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {  
  37.             if (e.getMessage() instanceof String) {  
  38.                 String message = (String) e.getMessage();  
  39.                 System.out.println("Client发来:" + message);  
  40.   
  41.                 e.getChannel().write("Server已收到刚发送的:" + message);  
  42.   
  43.                 System.out.println("\n等待客户端输入。。。");  
  44.             }  
  45.   
  46.             super.messageReceived(ctx, e);  
  47.         }  
  48.   
  49.         @Override  
  50.         public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception {  
  51.             super.exceptionCaught(ctx, e);  
  52.         }  
  53.   
  54.         @Override  
  55.         public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {  
  56.             System.out.println("有一个客户端注册上来了。。。");  
  57.             System.out.println("Client:" + e.getChannel().getRemoteAddress());  
  58.             System.out.println("Server:" + e.getChannel().getLocalAddress());  
  59.             System.out.println("\n等待客户端输入。。。");  
  60.             super.channelConnected(ctx, e);  
  61.         }  
  62.     }  
  63. }  

 

客户端:

 

Java代码  收藏代码
  1. package me.hello.netty;  
  2.   
  3. import org.jboss.netty.bootstrap.ClientBootstrap;  
  4. import org.jboss.netty.channel.*;  
  5. import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory;  
  6. import org.jboss.netty.handler.codec.string.StringDecoder;  
  7. import org.jboss.netty.handler.codec.string.StringEncoder;  
  8.   
  9. import java.io.BufferedReader;  
  10. import java.io.InputStreamReader;  
  11. import java.net.InetSocketAddress;  
  12. import java.util.concurrent.Executors;  
  13.   
  14. /** 
  15.  * God Bless You! 
  16.  * Author: Fangniude 
  17.  * Date: 2013-07-15 
  18.  */  
  19. public class NettyClient {  
  20.   
  21.     public static void main(String[] args) {  
  22.         // Configure the client.  
  23.         ClientBootstrap bootstrap = new ClientBootstrap(new NioClientSocketChannelFactory(Executors.newCachedThreadPool(), Executors.newCachedThreadPool()));  
  24.   
  25.         // Set up the default event pipeline.  
  26.         bootstrap.setPipelineFactory(new ChannelPipelineFactory() {  
  27.             @Override  
  28.             public ChannelPipeline getPipeline() throws Exception {  
  29.                 return Channels.pipeline(new StringDecoder(), new StringEncoder(), new ClientHandler());  
  30.             }  
  31.         });  
  32.   
  33.         // Start the connection attempt.  
  34.         ChannelFuture future = bootstrap.connect(new InetSocketAddress("localhost", 8000));  
  35.   
  36.         // Wait until the connection is closed or the connection attempt fails.  
  37.         future.getChannel().getCloseFuture().awaitUninterruptibly();  
  38.   
  39.         // Shut down thread pools to exit.  
  40.         bootstrap.releaseExternalResources();  
  41.     }  
  42.   
  43.     private static class ClientHandler extends SimpleChannelHandler {  
  44.         private BufferedReader sin = new BufferedReader(new InputStreamReader(System.in));  
  45.   
  46.         @Override  
  47.         public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {  
  48.             if (e.getMessage() instanceof String) {  
  49.                 String message = (String) e.getMessage();  
  50.                 System.out.println(message);  
  51.   
  52.                 e.getChannel().write(sin.readLine());  
  53.   
  54.                 System.out.println("\n等待客户端输入。。。");  
  55.             }  
  56.   
  57.             super.messageReceived(ctx, e);  
  58.         }  
  59.   
  60.         @Override  
  61.         public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {  
  62.             System.out.println("已经与Server建立连接。。。。");  
  63.             System.out.println("\n请输入要发送的信息:");  
  64.             super.channelConnected(ctx, e);  
  65.   
  66.             e.getChannel().write(sin.readLine());  
  67.         }  
  68.     }  
  69. }  

 

Netty整体架构



 

Netty组件

 

ChannelFactory

Boss

Worker

Channel

ChannelEvent

Pipeline

ChannelContext

Handler

Sink

 

Server端核心类

NioServerSocketChannelFactory

NioServerBossPool 

NioWorkerPool

NioServerBoss

NioWorker

NioServerSocketChannel

NioAcceptedSocketChannel

DefaultChannelPipeline

NioServerSocketPipelineSink

Channels

 

 

ChannelFactory

Channel工厂,很重要的类

保存启动的相关参数

 

NioServerSocketChannelFactory

NioClientSocketChannelFactory

NioDatagramChannelFactory

 

这是Nio的,还有Oio和Local的

 

 

SelectorPool

 

Selector的线程池

 

NioServerBossPool 默认线程数:1

NioClientBossPool      1

NioWorkerPool      2 * Processor

NioDatagramWorkerPool

 

 

Selector

 

选择器,很核心的组件

 

NioServerBoss

NioClientBoss

NioWorker

NioDatagramWorker

 

 

Channel

 

通道

 

NioServerSocketChannel

NioClientSocketChannel

NioAcceptedSocketChannel

NioDatagramChannel

 

Sink

负责和底层的交互

如bind,Write,Close等

 

NioServerSocketPipelineSink

NioClientSocketPipelineSink

NioDatagramPipelineSink

 

Pipeline

负责维护所有的Handler

 

ChannelContext

一个Channel一个,是Handler和Pipeline的中间件

 

Handler

对Channel事件的处理器

 

 

ChannelPipeline

 



 

 

 

优秀的设计----事件驱动



 

 

优秀的设计----线程模型



 

注意事项

 

解码时的Position

 

Channel的关闭

 

更多Handler

 

 

Channel的关闭

 

用完的Channel,可以直接关闭;

1、ChannelFuture加Listener

2、writeComplete

 

一段时间没用,也可以关闭

TimeoutHandler

posted @ 2016-04-12 15:19 paulwong 阅读(606) | 评论 (0)编辑 收藏

轻量级分布式 RPC 框架

     摘要: Code highlighting produced by Actipro CodeHighlighter (freeware)http://www.CodeHighlighter.com/-->1、每套服务需搭配一个RPCSERVER2、RPCSERVER作为SPRING 容器的一个BEAN3、在SPRING启动的时候,RPCSERVER会启动一个NETTY服务器端,然后将SPRING...  阅读全文

posted @ 2016-04-12 13:44 paulwong 阅读(639) | 评论 (0)编辑 收藏

为什么开发与测试老掐架呢

让我们思考几个常见的问题:

  • 软件测试的目的是什么?

  • 开发人员能否构建出没有Bug的完美软件?

  • 测人人员和开发人员是什么关系?

  • 软件测试能否保证软件质量?

先闭目冥想五分钟吧,然后可以尝试着回答上面的问题。

计算机先驱 Maurice Wikes 回忆起 1949 年他在英国剑桥工作的情形,在拖着打孔纸带上楼给雏形计算机 EDASC 装载程序时,他看到了自己的未来:

我强烈的意识到,生命中剩下的好日子,都将耗费在给自己的程序找错误上头。

Maurice Wikes告诉我们,没有完美的软件。

我曾经写过一篇荐书文,推荐了温伯格技术思想三部曲中的《颠覆完美软件:软件测试必须知道的几件事》。在这本书里,温伯格也告诉我们,没有完美的软件。所有的开发和测试人员都应该读读那本书。

温伯格在《颠覆完美软件》中几乎讨论所有常见的与软件测试相关的概念、问题和指导思想,所以,在这篇文章里,我只能来吐槽啦,我将从以下几方面列一些常见的现象,希望能引起大家的思考。

  • 测试和开发的关系

  • 流程与标准

  • 资源

  • 态度

测试和开发的关系

测试和开发是对立的吗?

从处理Bug的角度看,似乎可以这么说。开发人员既生产代码,也生产Bug。因为开发人员不可避免地会生产Bug,所以测试人员必须存在,以便在软件交付之前尽可能多地检出Bug,保证交付给客户的软件质量更好一些。一个产Bug,一个挑Bug,看起来似乎是对立的。

在现实中,很多测试团队和开发团队也正是因为这一点而搞得关系不和,甚至真的对立起来。请回想一下你周围发生的与开发和测试相关的事儿,看看有没有遇到过下面的情景:

  • 开发说,测试净找麻烦,客户跟本不可能像他们那样使用软件

  • 测试说,问题总是会在看似极端的条件下产生,用户总是会不经意触碰到看似极端的不可能出现的条件

  • 开发说,测试花在异常情况下的精力比测试主流程还多,不知道轻重缓急

  • 测试说,开发从来不考虑测试的感受,连测都不测就扔给我们

  • 开发说,我都测了,还要测试人员干什么

  • 测试说,这么明显的问题你们都不测一下,把我们测试当垃圾桶啊

  • ……

许许多多类似的问题,让开发和测试的关系从扑朔迷离、相爱相杀走向对立。我见过开发和测试搞冷战某人遇见某人侧脸而过,也见过测试经理和开发经理打架,还见过高层领导故意让测试团队和开发团队关系紧张以为这样可以提高测试效率也能给开发压力最终会产出更高质量的软件……

实际上,测试和开发拥有同一个目的:让软件更完美。测试和开发的关系,是一个问题的两面,应该是相辅相成和平共处的。测试不是为了挑刺儿,他提出的问题也不针对生产软件的开发人员,而仅仅是在努力想让开发人员的产出物看起来更好用。只要开发不将测试提Bug这个行为看成针对个人的行为,一切就有了美好的前提。

否定软件,并不是否定开发软件的人。这是开发和测试都需要明确的一个原则和前提。

还有的人认为开发和测试之关系类似皮与毛,皮之不存毛将焉附?所以有的开发也会因此而有优越感:没我们写软件,你们测试早下岗了!可是,开发不写软件,开发也下岗了耶!

感谢开发的不完美,让测试可以有事可做并练就慧眼。

感谢测试的认真细致和耐心体贴,让开发可以发现自己的不完美并有机会提升自己——那些说我软件不好的,都是为了我好。

资源

别动我们测试的服务器,你们自己搭一个!

我们没环境,不用你们的用谁的?

谁把我们的测试手机拿走了?你们申请一个嘛,老来占我们设备。

谁在用我们的账号?招呼都不打!我要用,赶紧退出来!

有时开发和测试之间也会有资源上的冲突,要有努力的有创造性的解决(我可以负责任地说,装黑苹果不是好办法),不要让大家伙的工作卡在环境上,这是管理者要解决的基本问题。我见过很多非常棒的一线经理,在现实制约下,主动把自己的手机、iPad都贡献出来当做测试设备。这也是解决资源问题的一种办法哦。

流程与标准

你身边的人员会这么抱怨吗:

  • 开发根本不看我们的测试用例,评审邮件从来就不回复

  • 我们一报Bug,开发就说用户根本不可能这么用,还说不知道我们怎么会这么测

  • 送测单里根本不写测试范围或者寥寥几句跟没写一样

  • 开发调整设计从来也不告诉我们

  • 为什么产品经理和UI只和开发讨论需求变更?

  • 为什么发布计划里不给测试预留测试时间?

  • 为什么开发写完代码测都不测就扔给我们?

  • 为什么客户那里发现了问题老问是谁测的、为什么没测出来?

  • 测试老是一声不吭就把Bug优先级设置为Major

  • 测试总是把大量时间花在用户根本不可能用到的功能上

  • 测试分不清哪些什么是重点,你给他说他还老是一堆道理这了那了

  • 测试提的Bug,现象描述也不准确,重现步骤也没有,有的根本就知道是不是误操作

  • 测试老来打断我,一会儿叫一下一会儿叫一下,根本没办法专注开发

  • jira上的Bug重复率太高,一个问题提N遍,难道就不能合并一下?

  • 测试发现Bug,一声招呼都不打就直接告诉老板了,搞得我很被动

  • 测试就是专门挑刺儿的,有劲不往正地儿使,你倒是测测用户常用的功能啊

  • 那么简单的Bug都能流出到用户那里,真不知道测试怎么测的

  • 开发老嫌测试报告数据不漂亮,逼着我们调整

Ok,如果你身边的开发和测试从来没有过类似的问题,那很好,恭喜你,看来你们的团队人nice协作也很顺畅,棒棒哒。

假如你身边充斥着这样嘈杂的抱怨,那说明什么呢?开发、测试、发布这一套流程有问题?还是团队缺乏明确的指向来引导大家向积极、有效的行为靠近?

流程和标准总是有待解释的,再好的规则,歪嘴和尚也能把它念斜……

我们随便挑一个问题吧:为什么开发写完代码测都不测就扔给我们?这个问题普遍存在,它反映出的是程序员和测试人员的工作边界难以界定的矛盾。

程序员会说,我都测一遍,还要你们测试做什么?

测试会说,你测都不测,冒烟都过不了,有没有责任心?

程序员说,要我写测试用例,搭各种环境,遍历各种正常、异常逻辑,我还有没有时间写代码了?

测试会说,我们测试是垃圾桶吗,什么烂玩意儿都直接扔给我们,我们的时间就那么不值钱?

开发会说,测试本来就是干这个的,你不测谁测?

……

像这样的问题,能制定一个标准,说明什么样的逻辑开发要自测覆盖什么样的逻辑可以交给测试来测?能画一条三八线吗?

不能。所以,这个时候,靠谱的一线管理者就显得很重要。如何创造性的发现适合团队的方法来让大家顺畅地协同工作,比标准、制度更重要,这往往依赖于技术管理者的能力和团队成员的意识。没有普适的方法,只有适合这个组织的、此时此地的策略,加油吧,在战斗中摸索出最适合当下的道路。

那什么是靠谱的一线管理者呢?

温伯格《成为技术领导者》一书中对领导职责的定义如下:

领导的职责就是创造这样一个环境,每个人都能在其中发挥出更多的能力。

如果一个技术领导带领的团队,大部分人都能专心做与其能力适配的事情而不用整天泡在与本节前面所列类似的问题里,那他基本上就算是比较靠谱了。

至于像给测试预留多长的测试周期、调整设计要不要通知测试、需求调整要不要测试参与等问题,合理的流程和标准可以起到很大的辅助作用,技术领导者只要依据合理的制度,引导大家有效参与,就可以化解。

态度

场景一:

测试MM对阿猿说发现了一个Bug。  阿猿矢口否认:不可能,绝对不可能!  MM:真的有Bug,你过来看一下!  阿猿:我都不用看,在我这儿好好儿的。  MM:你来看一下嘛……  阿猿:看什么看,肯定你环境问题,动什么东西了吗?重启了吗?

场景二:

测试MM想在jira上提个Bug,先在QQ上对阿猿说:有个Bug,你过来看下?  阿猿:忙着呢,焦头烂额的。  MM:一分钟都用不了,你来看下吧。  阿猿:思路一打断就不好恢复了,等会儿!  MM:你不看我提到jira上了啊。  阿猿:随便,你不就是爱提Bug嘛。

场景三:

测试MM呼叫阿猿:阿猿阿猿,程序又崩溃了,快来看看!  阿猿慢腾腾地起身过来,鼠标点几下:看不出来什么问题,你怎么操作的?  MM:这样点一下,那样,这样,……回车……。  阿猿:重现不了啊,你想办法重现,重现了再叫我,我忙着呢。  MM:……

我曾经画过一张暴漫,以“她发现了一个Bug”为题发布在微信订阅号“程序视界”里,再现类似的场景,感兴趣的可以在订阅号内回复10019查看(点击订阅号底部的帮助菜单里的“所有文章”子菜单也能找到)。

开发和测试的日常工作中,上面的情景不断上演,这其中有一部分原因来自态度。我们有时还能听到类似下面的话:

  • 你Bug里的现象描述根本没用

  • 你根本就没理解这个逻辑,给你说不清楚

  • 测试什么都不懂……

  • 你听我的,我让你怎么测你就怎么测

  • 你这种测法儿,再好的软件都经不起你折腾

  • 用户根本不可能这样用,你们整来整去净瞎耽误工夫

  • 一轮都没测完,你们就给老板说可以按期交付没问题?

  • 你们安排计划时根本不考虑测试,三天,三天怎么可能测得完!

  • ……

有时,有一些开发人员会用技术优势藐视测试,认为测试工作技术含量低,内心认为测试是附属没地位,说话就不太客气……测试会感觉到,反过来也会对开发有意见……就这么,从相敬如宾开始走向嫌怨丛生……

有个朋友的QQ签名档是:没有自我,只有大道。我琢磨,放在软件项目里,也挺适用的。

其实,开发和测试拥有共同的目的:生产高质量软件。具体说,每一个产品、项目、版本都有明确的目标,这些目标是属于开发和测试的,是大家的。我们把共同的目标牢记在心,摆在首位,我们还要想着别人所做的一切,都是针对软件本身,都是在为目标而努力,这样就心平气和多了,就容易从当下的泥沼中超脱出来,求同存异共同前进。

    作者:foruok 微信订阅号“程序视界”(programmer_sight)

    原文:CSDN

    posted @ 2016-04-12 11:01 paulwong 阅读(213) | 评论 (0)编辑 收藏

    Dubbos

         摘要: dubbos主要是基于dubbox的基础上,进行进一步的优化及拓展。Dubbos当前的主要功能支持REST风格远程调用(HTTP + JSON/XML):基于非常成熟的JBoss RestEasy框 架,在dubbo中实现了REST风格(HTTP + JSON/XML)的远程调用,以显著简化企业内部的跨语言交互,同时显著简化企业对外的Open API、无线API甚至AJAX服务端等等的开...  阅读全文

    posted @ 2016-04-12 10:59 paulwong 阅读(429) | 评论 (0)编辑 收藏

    Hack a Wifi Network WPA2/WPA/WEP

    First of all you will need a Linux operating system & a little education about Python
    programming.
    You can install Linux operating system on any Pc or Laptop.
    Note : This tutorial is only for eduactional purpose. The author of this pdf is not responsible for any illegal work. During installing and setting up you can loose all of your data, Do not do if you don’t know about programming.
    Device:
    Use Tp-link TL-WN722N Wifi Adapter for High gain.
    Start! Open Terminal:
    1. airmon-ng check kill
    2. airmon-ng
    3. airmon-ng start wlan0
    4. airodump-ng wlan0mon
    5. (control + c to stop)
    6. airodump-ng wlan0mon —bssid 5C:F9:6A:CD:8A:1D -c 1 -w WPA2
    7. wait for 1 minute, capture handshake
    8. aircrack-ng WPA2-01.cap -w /root/rockyou.txt
    /darkc0de
    /Wpa list 1,2,3

    darkc0de, Wpalist & rockyou.txt are the dictionaries files. You can download these from internet.
    This PDF is made by Malik Mubashir

    posted @ 2016-04-05 16:53 paulwong 阅读(197) | 评论 (0)编辑 收藏

    Walle --web部署系统工具

    Walle 一个web部署系统工具,配置简单、功能完善、界面流畅、开箱即用!支持git、svn版本管理,支持各种web代码发布,PHP,Python,JAVA等代码的发布、回滚,可以通过web来一键完成。

    官网主页 | Github主页

    功能列表

    • 用户分身份注册、登录
    • 开发者发起上线任务申请、部署
    • 管理者审核上线任务
    • 支持多项目部署
    • 支持多项目多任务并行
    • 快速回滚
    • 项目的用户权限管理
    • 部署前准备任务pre-deploy(前置检查)
    • 代码检出后处理任务post-deploy(如vendor)
    • 同步后更新软链前置任务pre-release
    • 发布完毕后收尾任务post-release(如重启)
    • 执行sql构建(不要担心忘记测试环境sql同步)
    • 线上文件指纹确认
    • 支持git、svn版本管理

    posted @ 2016-03-29 22:09 paulwong 阅读(287) | 评论 (0)编辑 收藏

    SHARDING-JDBC

    https://github.com/dangdangdotcom/sharding-jdbc/

    posted @ 2016-03-29 21:26 paulwong 阅读(208) | 评论 (0)编辑 收藏

    想知道吗?CTO 比普通程序员强在哪?

    互联网的蓬勃发展,让无数的程序员身价水涨船高,都变成了「香饽饽」,更有了不少「创业」,「当上 CTO,迎娶白富美的传说」。都说不想当元帅的士兵不是好士兵,我觉得这件事见仁见智,但提升自己的价值,让自己变得更优秀更有竞争力,一定是一线城市的大部分 IT 人内心的追求。

    诚然,并不是所有程序员都会变成 CTO,程序员——>CTO 的路径像是一个漏斗,极少数人沉淀下来,在业界掀起一阵阵飓风。这些 CTO 比起普通的程序员,强在哪?丰富的技术知识只是基础,更重要的是战略眼光,管理把控能力。那么 CTO 所思所想,和普通程序员究竟有什么不同?

     普通的程序员往往只负责模块的开发,代码的优化,和新技术的钻研,哦对我说的是普通程序员,而不是只会 fork 的小白程序员;而走向管理领域的高级程序员也许已经开始负责团队,背负团队进度和效率。而 CTO,往往不仅要考虑优化团队的开发工具、流程,肩负起把控整体技术方向的重任,要具有前瞻性,同时还要对企业绩效负责。尤其是技术驱动型公司,你问这样的公司 CTO 好招么,答案通常是「很难招」。技术选型其实是创业公司最纠结的问题,很多团队往往一上来基于已有的程序员的个人习惯和爱好,选择了一个技术方案,然后到某一天一看,我靠,全是坑(当然,也可能与执行者的能力有关)。

    图为通常来说程序员的发展路线:

    影响企业绩效的因素在方方面面,核心因素却往往集中在产品上。不夸张地说,应用程序的性能对于企业绩效有着非常巨大的影响。互联网产品遍地开花,SDK 层出不穷,用户对于一种新产品的尝试时间与互联网产品更新的速度成反比。用户体验这个已经被讲烂的概念依然还是提升产品价值的关键按钮,无论是 2C 还是 2B。

    一旦用户未在你所负责的产品中获得最佳体验,或者直接解决痛点,他们会毫不犹豫的选择其他平台。

    这个问题普通程序员通常解决不了,而一名优秀的 CTO 就需要下点功夫了。如何成为一名优秀的 CTO,这是一个问题,而一个问题往往是另一个问题的解决方案。为什么一个团队需要优秀的 CTO?是因为需要有人来带领技术团队优化应用性能——解决用户体验的难题,提升开发、运维,把控技术团队的战略方向。那么,优化应用性能,获得好的用户体验,提升开发、运维效率,又该怎么做呢?

    为了确保应用程序能够达到甚至超越用户的高期望,需要不断优化底层 IT 基础设施的性能。然而,随着基础设施变得越来越动态化,混合化和复杂化,一波波新的挑战随之而生,让不少 CTO 多了几根白头发。

    但是一个问题的产生,往往意味着相应的解决方法正在路上。为了优化应用程序的性能,优秀的 CTO 需要足够主动和敏捷。

    主动优化包括物理和虚拟服务器,网络,存储设备,数据库,终端用户服务,云,和大数据环境在内的所有基础设施。需要将 IT 团队带领成为不仅能够迅速识别和解决问题,同时具有强大的反脆弱性,在问题对用户体验产生不利影响之前,先发制人的组织。以下五大关键措施或许可以帮助我们实现一点。

    1. 捕捉和报告性能指标

    鉴于良好性能的重要性,对于 IT 团队来说只在基础设施组件出现问题时产生告警是不足够的。CTO 需要让团队能够提前发现潜在的性能问题,并主动解决。例如,通过免费或付费的第三方工具及一些开源工具,配置告警,在问题出现之前解决。不同的团队,往往有最为适合自己的基础设施监控手段,优秀的 CTO 需要能够综合衡量团队大小,开发、运维水平,与人力和资金成本,选择最符合公司当下情况的监控方式。对于变动型较大或者高速发展的公司,盲目增加人力和花费时间去进行自主开发系统监控解决方案往往造成时间的浪费,得不偿失。

    2. 统一视图和工具来增加可视性,并加快问题解决

    由于开源工具与第三方解决方案层出不穷,不少 IT 团队也勇于尝试新工具、新方法。虽然有很多新的工具,解决不同方面的问题,但当问题出现时,团队成员仍然花费许多时间开会讨论,不断地开会浪费了许多时间。而与此同时,用户却经历着槽糕的体验。为什么明明有许多工具却依然采取本办法沟通呢?原因有两个,一个是很多 IT 团队内部在使用不同的协作、监控等工具,另一个是其实团队内部并没有养成利用监控平台或者协作工具的习惯。这种时候 CTO 就需要发挥作用,采用一个统一且功能强大的视图和架构来监测关键的 IT 服务,无论是虚拟机,物理主机,云主机,或者其他组件,同时采取深刻理解DevOps,掌握提升协作、沟通效率,优化开发流程,节省运维成本,提前发现问题的方法。

    想知道吗?CTO 比普通程序员强在哪?

    3. 跟踪用户体验

    IT 团队可能拥有大量的性能指标,但是如果不知道用户的真实体验,就还是无法真正了解性能表现。什么是真实的体验?就是用户在实际操作中,是如何使用我们的产品的,在某个界面停留多久,对哪个环节不满意,诸如此类。IT 团队需要分析端到端的基础设施的响应时间,并借助虚拟交易功能,持续跟踪交易响应时间,即使在用户不使用应用程序的情况下。

    想知道吗?CTO 比普通程序员强在哪?

    4. 采用严格的 SLA 管理

    一旦企业的全面监测到位, IT 团队针对服务水平协议(SLAs)跟踪性能和体验是至关重要的。IT 团队需要能够跟踪 SLA 合规性,当潜在问题出现时,立即识别和解决。通过跟踪 SLAs,IT 企业可以评估他们在管理用户体验和基础设施性能上的有效性。 这一评估对于准确计量团队绩效,设定目标和跟踪进展也是至关重要的。

    5. 将 IT 和非 IT 数据相关联,进行高效的容量规划

    满足用户不断提高的期望,并不仅仅是跟踪 IT 数据。通过关联 IT 和业务数据,团队可以主动识别瓶颈,提高终端用户体验。比如,将服务器 CPU 利用率指标和简单的历史数据相关联;比如,将用户登录或交易的数量与 IT 数据一起进行展示,可以为适应未来发展的容量规划,提供有意义的见解。下图为某团队将 PHP 请求、响应时间等数据和系统性能数据一起导入 Cloud Insight 仪表盘进行展示的例子。

    想知道吗?CTO 比普通程序员强在哪?想知道吗?CTO 比普通程序员强在哪?

    插播一个好玩的,下图为某团队成员别出心裁将键盘使用记录导入仪表盘进行展示,也许键盘记录只是一种出于好玩的别出心裁,但同理,也可以将运营数据、业务数据、系统性能数据一起导入仪表盘进行展示,这对一个快速增长的 IT 团队来说,就很有价值了。

    想知道吗?CTO 比普通程序员强在哪?

    总结

    数据驱动互联网高速发展的时代,技术团队 Leader 除了技术过硬,眼光独到,还要将紧跟 DevOps 的步伐,放眼国内外,快速、敏捷、尽可能多的优化团队开发手段和流程,减少开发、运维、运营之间的沟通壁垒,将数据化融入到技术推进的方方面面。而当你在这些方面有了核心竞争力,就不再只是一名普通的程序员了。

    posted @ 2016-03-29 20:44 paulwong 阅读(151) | 评论 (0)编辑 收藏

    MongoDB健壮集群——用副本集做分片

         摘要: 1.    MongoDB分片+副本集健壮的集群方案多个配置服务器 多个mongos服务器  每个片都是副本集 正确设置w架构图说明:1.   此实验环境在一台机器上通过不同port和dbpath实现启动不同的mongod实例2.   总的9个mongod实例,分别做成shard...  阅读全文

    posted @ 2015-12-18 14:03 paulwong 阅读(536) | 评论 (0)编辑 收藏

    利用Mongodb的复制集搭建高可用分片,Replica Sets + Sharding的搭建过程

         摘要: 参考资料 reference:  http://mongodb.blog.51cto.com/1071559/740131  http://docs.mongodb.org/manual/tutorial/deploy-shard-cluster/#sharding-setup-shard-collection感谢网友Mr.Sharp,他给了我很多...  阅读全文

    posted @ 2015-12-18 13:54 paulwong 阅读(509) | 评论 (0)编辑 收藏

    MONGODB的复制与分片

    复制:为了防止单点故障,会有几个实例在运行,保持相同的数据。

    • 一般主从:一主多从,主作读写数据,从作从主备份数据用,如果主宕机,则整个MONGODB无法工作。
    • 复制式主从:一动态主多从,主由选举产生,当中一个主宕机,其他的从会选出一个主。

    适用场景:高负荷的读多写少。

    分片:SHARDING,一般数据库中的分库分表,一个表分成几个表用。每个片再做复制。

    适用场景:高负荷的写多读少。即如果发现MONGODB写不能支撑了,则要转此模式。

    安装配置服务器,安装ROUTER:MONGOS,安装分片服务器,通知MONGOS挂载SHARD。

    如果只启用数据库的分片,则不同的表放在不同的分片上,即一个表只占一个分片,另一个表占另一个分片,如果做了表的分片,则此表会分布在所有分片上。











    posted @ 2015-12-18 13:21 paulwong 阅读(270) | 评论 (0)编辑 收藏

    Android Application Architecture 安卓APP架构[译]

    本文介绍了文章作者从事了几年android应用的开发,经历2次架构变革,第一次集成了RxJava第二次集成了MVP,并将RxJava与MVP完美结合,实现了低耦合,代码简单,测试方便的架构。

    其实我们在开发中也遇到过,Android入门门槛较低,如果前期对APP规划不清晰,Coder们对未来变化把握不准,技术架构经验不够强大,最终导致就是一个Activity几千行,里面写了大量的Private方法,拆成几个Fragment、封装出来几个类都是无法解决,结果就是看Activity难受的要死,纠结,看了不爽改也不是不改也不是,严重影响看的人的心情。并且怨天尤人这个是产品人员规划App不好,没有前瞻性,改来改去。。。

    这篇文章就是使用新的结构解决该问题。

    安卓APP架构

    Android Application Architecture

    Our journey from standard Activities and AsyncTasks to a modern MVP-based architecture powered by RxJava.

    这篇文章主要目的是讲述如何将传统的Activities 与 AsyncTasks 模式向目前主流的MVP架构基础的响应式编程框架过度。

    1*HrE2lljEfsCu1X_OUDfHYA

    Different parts of a software codebase should be independent, yet perfectly work together like a well-oiled machine — photo by Chester Alvarez.

    先畅享一下:~~~如果松耦合架构,分工明确,然后完美的组合在一起工作是一个很吊的事情。
    (转个图片还要写明白谁拍的,版权意识真强)

    The Android dev ecosystem moves very quickly. Every week new tools are created, libraries are updated, blog posts are written and talks are given. If you go on holiday for a month, by the time you come back there will be a new version of the support library and/or Play Services.

    最近几年Android的生态链变化非常迅速,从底层的Android Api到应用层的各种开源的类库、工具更新非常迅速。一不留神就落后了。

    I’ve been making Android apps with the ribot team for over three years. During this time, the architecture and technologies we’ve used to build Android apps have been continuously evolving. This article will take you through this journey by explaining our learnings, mistakes and the reasoning behind these architectural changes.

    我在Ribot团队从事Android应用开发工作三年多,伴随着公司技术的不断创新,积累了很多经验、错误以及在技术选型背后的故事。

    旧的应用架构

    The old times
    Back in 2012 our codebases used to follow a basic structure. We didn’t use any networking library and AsyncTasks were still our friends. The diagram below shows approximately how the architecture was.

    2012年那个时候,我们的代码都是用的原生Android,没有使用任何的网络请求框架,而是基于AsyncTasks书写。
    1*TTtpcT4H80THBofnCtQ_L>The code was structured in two layers: the data layer that was in charge of retrieving/saving data from REST APIs and persistent data stores; and the view layer, whose responsibility was handling and displaying the data on the UI.
    The APIProvider provides methods to enable Activities and Fragments to easily interact with the REST API. These methods use URLConnection and AsyncTasks to perform network calls in a separate thread and return the result to the Activities via callbacks.

    代码分为两层,Data与View,Data层主要是用来从API获取数据,保存到持久化的db当中。View层主要就是把Data的数据显示到UI上。APIProvider提供方法出来,用于在Activity或者Fragment中方便的进行控制与交互。技术上将,使用URLConnection与AsyncTasks实现了一个异步的网络请求并将结果返回到调用的回调方法里面。

    In a similar way, the CacheProvider contains methods that retrieve and store data from SharedPreferences or a SQLite database. It also uses callbacks to pass the result back to the Activities.

    相同的原理CacheProvider提供一系列方法,将SharedPreferences或者SQLite的数据取出来,并且返回给到Activity

    问题

    The problems
    The main issue with this approach was that the View layer had too many responsibilities. Imagine a simple common scenario where the application has to load a list of blog posts, cache them in a SQLite database and finally display them on a ListView. The Activity would have to do the following:

    主要问题是View层有太多的累赘,以一个博客列表为例来讲述,比如博客需要显示一个ListView,从SQLite读取数据,Activity需要做到以下几点:

    1. Call a method loadPosts(callback) in the APIProvider
    2. Wait for the APIProvider success callback and then call savePosts(callback) in the CacheProvider.
    3. Wait for the CacheProvider success callback and then display the posts on the ListView.
    4. Separately handle the two potential errors callback from the APIProvider and CacheProvider.
    1. 执行APIProvider里面的loadPosts的方法,里面传入回调参数内容。
    2. 等待loadPosts执行成功后,执行回调里面的CacheProvider中的savePosts方法,savePosts也要传入回调参数。
    3. 等待savePosts执行成功后,执行回调里面的方法刷新ListView
    4. 分别书写代码处理2 3 两步的错误回调内容。

    This is a very simple example. In a real case scenario the REST API will probably not return the data like the view needs it. Therefore, the Activity will have to somehow transform or filter the data before showing it. Another common case is when the loadPosts() method takes a parameter that needs to be fetched from somewhere else, for example an email address provided by the Play Services SDK. It’s likely that the SDK will return the email asynchronously using a callback, meaning that we now have three levels of nested callbacks. If we keep adding complexity, this approach will result into what is known as callback hell.

    这还是一个比较简单的例子,在一些真实的场景中,远程的API可能没有返回程序的必须值,但是activity必须把数据处理完成之后才能显示结果。再一个例子就是如果loadPosts方法需要借助一些其他地方的返回参数时,类似用多线程去实现同步请求,为保证数据正常请求,意味着必须做一个三层的回调,如果再复杂一些,想理清楚这些回调就是很蛋疼的事情。

    In summary:
    Activities and Fragments become very large and difficult to maintain
    Too many nested callbacks means the code is ugly and difficult to understand so painful to make changes or add new features.
    Unit testing becomes challenging, if not impossible, because a lot of the logic lives within the Activities or Fragments that are arduous to unit test.

    总之,回调多了之后,Activity与Fragment会乱的要死,并且一般人无法直视。

    牛逼的新架构出来了

    A new architecture driven by RxJava
    We followed the previous approach for about two years. During that time, we made several improvements that slightly mitigated the problems described above. For example, we added several helper classes to reduce the code in Activities and Fragments and we started using Volley in the APIProvider. Despite these changes, our application code wasn’t yet test-friendly and the callback hell issue was still happening too often.

    我们在蛋疼的架构中煎熬了2年,当然也尝试过很多方式,最终也只能是缓和一下乱的问题。我们在APIProvider使用了Volley,代替了AsyncHttpClient,但是其实是一个吊样。

    It wasn’t until 2014 when we started reading about RxJava. After trying it on a few sample projects, we realised that this could finally be the solution to the nested callback problem. If you are not familiar with reactive programming you can read this introduction. In short, RxJava allows you to manage data via asynchronous streams and gives you many operators that you can apply to the stream in order to transform, filter or combine the data.

    不到2014年我们就开始进行RxJava的预研,然后尝试了一批简单的项目,感觉RxJava的方式是解决我们嵌套回调的终极解决办法。简单的说,RxJava允许你通过异步流的方式管理你的数据,并且还可以通过操作符(Operators)对Observable对象的变换

    Taking into account the pains we experienced in previous years, we started to think about how the architecture of a new app would look. So we came up with this.

    我们用了几年的经验痛定思痛,搞了下面这么个东西,新的APP的架构图

    1*kCynNIa5PscRl41V2scosA-200

    Similar to the first approach, this architecture can be separated into a data and view layer. The data layer contains the DataManager and a set of helpers. The view layer is formed by Android framework components like Fragments, Activities, ViewGroups, etc.

    与第一种方法相似,这个架构也是分为Data层与View层,Data层包含DataManager与一堆Helper;View层是包含Fragments, Activities, ViewGroups等。

    Helper classes (third column on diagram) have very specific responsibilities and implement them in a concise manner. For example, most projects have helpers for accessing REST APIs, reading data from databases or interacting with third party SDKs. Different applications will have a different number of helpers but the most common ones are:

    Helper主要是集成第三方的类库,以便于在代码中几行代码就可以清晰的实现某个功能,比如请求API,访问数据库等,虽然不同的应用程序都有不同的类库,但是他们无非就是以下这些内容:

    • PreferencesHelper: reads and saves data in SharedPreferences.
    • DatabaseHelper: handles accessing SQLite databases.
    • Retrofit services: perform calls to REST APIs. We started using Retrofit instead of Volley because it provides support for RxJava. It’s also nicer to use.
    • 从SharedPreferences中读取或者写入数据
    • 读写SQLite数据库
    • 类似与square的Retrofit服务,也就是Http Client,我们用Restrofit替代了Volley因为他支持Rxjava,并且更吊。

    Most of the public methods inside helper classes will return RxJava Observables.
    The DataManager is the brain of the architecture. It extensively uses RxJava operators to combine, filter and transform data retrieved from helper classes. The aim of the DataManager is to reduce the amount of work that Activities and Fragments have to do by providing data that is ready to display and won’t usually need any transformation.

    RxJava最核心的两个东西是Observables(被观察者,事件源)和Subscribers(观察者),在Helper类中的Public方法,一般都会返回一个RxJava的Observables;DataManager是整个架构的大脑,他大量的使用Rxjava的operators对Helper返回来的数据进行的整合过滤、二次处理。

    The code below shows what a DataManager method would look like. This sample method works as follows:

    下面用一个例子来说明DataManager是做什么的:

    1. Call the Retrofit service to load a list of blog posts from a REST API
    2. Save the posts in a local database for caching purposes using the DatabaseHelper.
    3. Filter the blog posts written today because those are the only ones the view layer wants to display.
    1. 调用Retrofit的服务,去请求一个博客列表的API
    2. 用DatabaseHelper保存这些数据到数据库
    3. 过滤出这些BLOG哪些是今天写的,然后显示到UI界面上。

    Components in the view layer such as Activities or Fragments would simply call this method and subscribe to the returned Observable. Once the subscription finishes, the different Posts emitted by the Observable can be directly added to an Adapter in order to be displayed on a RecyclerView or similar.

    Observables发出一系列事件,Subscribers(例如 Activities or Fragments)处理这些事件,可以直接将数据显示到一些可以回收、重用的View上面。
    【BTW:如果一个Observerble没有任何的的Subscriber,那么这个Observable是不会发出任何事件的】

    The last element of this architecture is the event bus. The event bus allows us to broadcast events that happen in the data layer, so that multiple components in the view layer can subscribe to these events. For example, a signOut() method in the DataManager can post an event when the Observable completes so that multiple Activities that are subscribed to this event can change their UI to show a signed out state.

    这个架构的另外一个模块是event bus,event bus可以让我们在Data层发出广播(不是Android的Broadcast)然后不同的模块去注册并接收不同的广播事件

    Why was this approach better?
    RxJava Observables and operators remove the need for having nested callbacks.
    1*BIsOCzJnc-SSU8fPXTiP1A

    为什么这个方式这么牛逼,是因为Observables与operators可以去掉那一堆必须的回调方法

    The DataManager takes over responsibilities that were previously part of the view layer. Hence, it makes Activities and Fragments more lightweight.
    Moving code from Activities and Fragments to the DataManager and helpers means that writing unit tests becomes easier.

    DataManager替代了传统架构中很多代码,从而使得Activity与Fragment变得更加轻量级。并且使得单元测试变得更加简单。

    Clear separation of responsibilities and having the DataManager as the only point of interaction with the data layer, makes this architecture test-friendly. Helper classes or the DataManager can be easily mocked.

    DataManager成为了唯一的数据交互部分,这样清晰的架构使得更方便进行代码自测。

    What problems did we still have?
    For large and very complex projects the DataManager can become too bloated and difficult to maintain.
    Although view layer components such as Activities and Fragments became more lightweight, they still have to handle a considerable amount of logic around managing RxJava subscriptions, analysing errors, etc.

    我们还有什么问题?
    - 如果对于非常庞大并且复杂的项目来说,DataManger也会变得非常臃肿并且难以维护。
    - 尽管Activity与Fragment已经变得更加轻量级,但是对于错误异常的处理还是要在subscriptions的地方去书写。

    一体化的MVP模式

    Integrating Model View Presenter
    In the past year, several architectural patterns such as MVP or MVVM have been gaining popularity within the Android community. After exploring these patterns on a sample project and article, we found that MVP could bring very valuable improvements to our existing approach. Because our current architecture was divided in two layers (view and data), adding MVP felt natural. We simply had to add a new layer of presenters and move part of the code from the view to presenters.

    前几年开始,很多类似MVP与MVVM在Android的一些社区比较流行,经过研究之后,我们发现MVP模式是对我们目前的方案最有价值的改动。我们的两层架构View-Data与MVP的 Model-View架构天然融合,理念一致。我们只需要增加一个presenters层,然后把之前在view实现的代码移到上面就可以了。
    1*NonRJ0uzzN9o1ygT6J421g

    The data layer remains as it was but it’s now called model to be more consistent with the name of the pattern.
    Presenters are in charge of loading data from the model and calling the right method in the view when the result is ready. They subscribe to Observables returned by the data manager. Therefore, they have to handle things like schedulers and subscriptions. Moreover, they can analyse error codes or apply extra operations to the data stream if needed. For example, if we need to filter some data and this same filter is not likely to be reused anywhere else, it may make more sense to implement it in the presenter rather than in the data manager.

    之前的Data层就是现在的MVP中的Model,Presenter现在负责从Model中加载数据,加载完成后后再去调用左边的在Activity、ViewGroup中的方法。Presenters的subscribe去接收data manager中的Observables广播出来的数据。
    举例说明,如果我们需要增加数据的过滤操作但是并不是所有地方都需要的那种,那就可以在presenter里面写这些代码,而不用写在公共的datamanager里面。

    Below you can see what a public method in the presenter would look like. This code subscribes to the Observable returned by the dataManager.loadTodayPosts() method we defined in the previous section.

    我们定义的dataManager.loadTodayPosts()会广播出数据给到对应的subscribes

    The mMvpView is the view component that this presenter is assisting. Usually the MVP view is an instance of an Activity, Fragment or ViewGroup.

    MVP的View并不是指的Android的View,而是一个界面组件的的实例,例如Activity, Fragment , ViewGroup 在注册presenter的时候,需要把自己当前的实例传递进去。

    // Activity onCreate 中的代码段  if (presenter == null)         presenter = new Presenter1();         presenter.onTakeView(this);  

    Like the previous architecture, the view layer contains standard framework components like ViewGroups, Fragments or Activities. The main difference is that these components don’t subscribe directly to Observables. They instead implement an MvpView interface and provide a list of concise methods such as showError() or showProgressIndicator(). The view components are also in charge of handling user interactions such as click events and act accordingly by calling the right method in the presenter. For example, if we have a button that loads the list of posts, our Activity would call presenter.loadTodayPosts() from the onClick listener.

    这个架构与上一个架构不同的是,ViewLayer 也就是Activity这些,不会直接去订阅接收Observables发出的这些事件。而是只在Activity实现几个简单的显示错误、显示进度的方法(用接口interface来规范统一),然后把当前实例以参数形式传递给到对应事件的Presenter,由Presenter去执行这些显示错误、显示进度的方法。
    当然对于用户交互部分的按钮点击事件还是要在Activity中进行处理。

    If you want to see a full working sample of this MVP-based architecture, you can check out our Android Boilerplate project on GitHub. You can also read more about it in the ribot’s architecture guidelines.

    关于MVP的文章可以自行百度一下,MVP Android 关键词

    Why is this approach better?

    为什么这个又最吊

    • Activities and Fragments become very lightweight. Their only responsibilities are to set up/update the UI and handle user events. Therefore, they become easier to maintain.
    • We can now easily write unit tests for the presenters by mocking the view layer. Before, this code was part of the view layer so we couldn’t unit test it. The whole architecture becomes very test-friendly.
    • If the data manager is becoming bloated, we can mitigate this problem by moving some code to the presenters.
    • Activity与Fragment代码量大大降低,逻辑代码全部都丢给了Presenter,结果就是Activity只需要负责UI交互的按钮等代码。
    • 对于Presenter可以写单独的单元测试代码,只需要对Presenter提供的方法测试即可
    • 如果DataManager变得臃肿庞大了,我们可以分离这些代码到各自的Presenter中去。

    What problems do we still have?
    现在还有遗留什么问题

    Having a single data manager can still be an issue when the codebase becomes very large and complex. We haven’t reached the point where this is a real problem but we are aware that it could happen.

    只有一个DataManager仍旧是一个问题,尤其是当代码项目比较庞大的时候,当然我们还没有到达这个庞大的地步,尽管我们知道这个将来某天会发生。

    It’s important to mention that this is not the perfect architecture. In fact, it’d be naive to think there is a unique and perfect one that will solve all your problems forever. The Android ecosystem will keep evolving at a fast pace and we have to keep up by exploring, reading and experimenting so that we can find better ways to continue building excellent Android apps.

    如果想有个完美的架构解决你所有问题是不可能的。TMD Android的整个生态圈变化太快,又TM的不标准,就导致我们不断的去探索探索。。。以致于去找到更吊的方法去做Android apps。

    I hope you enjoyed this article and you found it useful. If so, don’t forget to click the recommend button. Also, I’d love to hear your thoughts about our latest approach.

    希望读了之后对我们的最新解决方案能有些建议想法。

    【本文翻译的目的是在闲暇时间,研究新技术,用通俗技术语言写给自己看,便于日后方便查阅为目】
    原文:https://medium.com/ribot-labs/android-application-architecture-8b6e34acda65
    MVP介绍:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0425/2782.html
    RxAndroid:https://github.com/ReactiveX/RxAndroid
    Eventbus:https://github.com/greenrobot/EventBus

    posted @ 2015-12-18 13:07 paulwong 阅读(383) | 评论 (0)编辑 收藏

    mongodb的监控与性能优化

         摘要: .mongodb的监控 mongodb可以通过profile来监控数据,进行优化。查看当前是否开启profile功能用命令db.getProfilingLevel()  返回level等级,值为0|1|2,分别代表意思:0代表关闭,1代表记录慢命令,2代表全部开始profile功能为db.setProfilingLevel(level);  #level等级,值同上l...  阅读全文

    posted @ 2015-12-16 18:50 paulwong 阅读(399) | 评论 (0)编辑 收藏

    高压锅内部烧黑,如何去掉?

    http://www.360doc.com/content/11/0415/13/117643_109815383.shtml

    http://wenda.tianya.cn/question/4b4edcf687745412

    http://zhidao.baidu.com/question/456177515446176485.html

    http://iask.sina.com.cn/b/6262165.html

    http://baike.pcbaby.com.cn/qzbd/5691.html#ldjc4ta=baby_tbody2

    http://www.xiaoqiaomen.cc/qingjieweisheng/258.html

    http://home.19lou.com/forum-106-thread-6901352097188270-1-1.html

    posted @ 2015-12-14 18:25 paulwong 阅读(245) | 评论 (0)编辑 收藏

    MONGODB删除/新增/更改大量记录的方法

    MONGODB中,由于删除大量记录会十分耗时,一般推荐由MONGODB自己在后台处理,只需在某个字段设一个索引的标签即可。

    @Indexed(expireAfterSeconds=180)
    private Date deletedAt;

    以上代码,如果字段deletedAt有值,那么将在180秒后被MONGODB删除,如果没值不会被删除。

    批量新增,小批量更新,防止读取超时
    private <T> void insertAll(List<T> list) {
            if (null != list) {
                int total = list.size();
                int count = (total + 50000 -1) / 50000;
                for (int i = 0; i < count; i++) {
                    int toIndex = ((i +1) * 50000 > total) ? total : ((i +1) * 50000);
                    log.info("toIndex = " + toIndex);
                    mongoTemplate1.insertAll(list.subList(i * 50000, toIndex));
                }
            }
        }

    批量更改
    import java.util.Date;

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.mongodb.core.MongoTemplate;
    import org.springframework.data.mongodb.core.query.Criteria;
    import org.springframework.data.mongodb.core.query.Query;
    import org.springframework.data.mongodb.core.query.Update;

    import com.tcl.project7.boss.gameapplication.yearendactivities.bigwheelgame.valueobject.SingleUseRedeemCode;

    public class SingleUseRedeemCodeRepositoryImpl implements SingleUseRedeemCodeRepositoryCustom{
        
        @Autowired
        private MongoTemplate mongoTemplate1;
        
        public void batchUpdateSingleUseRedeemCodeList(String bigWheelGameAwardId) {
            
            Query query = new Query();
            query.addCriteria(Criteria.where("bigwheelgameawardid").is(bigWheelGameAwardId));
            mongoTemplate1.updateMulti(
                                        query, 
                                        new Update().set("bigwheelgameawardid", "-1")
                                            .set("deletedat", new Date()), 
                                        SingleUseRedeemCode.class);
        }

    }


    Expire Data from Collections by Setting TTL

    New in version 2.2.

    This document provides an introduction to MongoDB’s “time to live” or TTL collection feature. TTL collections make it possible to store data in MongoDB and have the mongod automatically remove data after a specified number of seconds or at a specific clock time.

    Data expiration is useful for some classes of information, including machine generated event data, logs, and session information that only need to persist for a limited period of time.

    A special TTL index property supports the implementation of TTL collections. The TTL feature relies on a background thread in mongod that reads the date-typed values in the index and removes expired documentsfrom the collection.

    Procedures

    To create a TTL index, use the db.collection.createIndex() method with theexpireAfterSeconds option on a field whose value is either a date or an array that contains date values.

    NOTE

    The TTL index is a single field index. Compound indexes do not support the TTL property. For more information on TTL indexes, see TTL Indexes.

    Expire Documents after a Specified Number of Seconds

    To expire data after a specified number of seconds has passed since the indexed field, create a TTL index on a field that holds values of BSON date type or an array of BSON date-typed objects and specify a positive non-zero value in the expireAfterSeconds field. A document will expire when the number of seconds in the expireAfterSeconds field has passed since the time specified in its indexed field. [1]

    For example, the following operation creates an index on the log_events collection’s createdAt field and specifies the expireAfterSeconds value of 3600 to set the expiration time to be one hour after the time specified by createdAt.

    db.log_events.createIndex( { "createdAt": 1 }, { expireAfterSeconds: 3600 } ) 

    When adding documents to the log_events collection, set the createdAt field to the current time:

    db.log_events.insert( {    "createdAt": new Date(),    "logEvent": 2,    "logMessage": "Success!" } ) 

    MongoDB will automatically delete documents from the log_events collection when the document’screatedAt value [1] is older than the number of seconds specified in expireAfterSeconds.

    [1](12) If the field contains an array of BSON date-typed objects, data expires if at least one of BSON date-typed object is older than the number of seconds specified in expireAfterSeconds.

    SEE ALSO

    $currentDate operator

    Expire Documents at a Specific Clock Time

    To expire documents at a specific clock time, begin by creating a TTL index on a field that holds values of BSON date type or an array of BSON date-typed objects and specify an expireAfterSeconds value of0. For each document in the collection, set the indexed date field to a value corresponding to the time the document should expire. If the indexed date field contains a date in the past, MongoDB considers the document expired.

    For example, the following operation creates an index on the log_events collection’s expireAt field and specifies the expireAfterSeconds value of 0:

    db.log_events.createIndex( { "expireAt": 1 }, { expireAfterSeconds: 0 } ) 

    For each document, set the value of expireAt to correspond to the time the document should expire. For instance, the following insert() operation adds a document that should expire at July 22, 201314:00:00.

    db.log_events.insert( {    "expireAt": new Date('July 22, 2013 14:00:00'),    "logEvent": 2,    "logMessage": "Success!" } ) 

    MongoDB will automatically delete documents from the log_events collection when the documents’expireAt value is older than the number of seconds specified in expireAfterSeconds, i.e. 0 seconds older in this case. As such, the data expires at the specified expireAt value.

    posted @ 2015-12-11 15:03 paulwong 阅读(710) | 评论 (0)编辑 收藏

    How to delete large amount of data of a MongoDB collection “quickly”

    We have a db collection that is around 30 million documents, and I need to trim it down, to only keeping the documents created on the last month. 

    One approach would be use the remove command with a condition on the created_at field (the collection already have an index on this field):

    db.my_collection.remove({created_at: {$lte: new Date("11/01/2012")}});

    But this approach will be very slow, instead of that, a better way to do it is rename the current collection (for instance to “old_collection”) using renameCollection. Then performing a query-and-insert from the “old_collection” into “my_collection”:

    db.my_collection.renameCollection("old_collection");  
    db.createCollection("my_collection");
    db.my_collection.createIndex(...); // recreate the indexes for the collection
    // copy docs from old collection into the new collection
    db.old_collection.find(
    {created_at: {$gte: new Date("11/01/2012")}} ).sort({_id: -1}).forEach(
    function(row) { db.my_collection.insert(row); } ); // drop old collection db.old_collection.drop();

    This approach is typically faster than running a bunch of removes on your data

    posted @ 2015-12-10 20:09 paulwong 阅读(299) | 评论 (0)编辑 收藏

    MongoDB 固定集合(Capped Collections)

    MongoDB 固定集合(Capped Collections)是性能出色且有着固定大小的集合,对于大小固定,我们可以想象其就像一个环形队列,当集合空间用完后,再插入的元素就会覆盖最初始的头部的元素!


    创建固定集合

    我们通过createCollection来创建一个固定集合,且capped选项设置为true:

    >db.createCollection("cappedLogCollection",{capped:true,size:10000})

    还可以指定文档个数,加上max:1000属性:

    >db.createCollection("cappedLogCollection",{capped:true,size:10000,max:1000})

    判断集合是否为固定集合:

    >db.cappedLogCollection.isCapped()

    如果需要将已存在的集合转换为固定集合可以使用以下命令:

    >db.runCommand({"convertToCapped":"posts",size:10000})

    以上代码将我们已存在的 posts 集合转换为固定集合。


    固定集合查询

    固定集合文档按照插入顺序储存的,默认情况下查询就是按照插入顺序返回的,也可以使用$natural调整返回顺序。

    >db.cappedLogCollection.find().sort({$natural:-1})

    固定集合的功能特点

    可以插入及更新,但更新不能超出collection的大小,否则更新失败,不允许删除,但是可以调用drop()删除集合中的所有行,但是drop后需要显式地重建集合。

    在32位机子上一个cappped collection的最大值约为482.5M,64位上只受系统文件大小的限制。


    固定集合属性及用法

    属性

    • 属性1:对固定集合进行插入速度极快
    • 属性2:按照插入顺序的查询输出速度极快
    • 属性3:能够在插入最新数据时,淘汰最早的数据

    用法

    • 用法1:储存日志信息
    • 用法2:缓存一些少量的文档

    posted @ 2015-12-09 14:41 paulwong 阅读(242) | 评论 (0)编辑 收藏

    MongoDB 聚合

    http://www.runoob.com/mongodb/mongodb-aggregate.html
    MongoDB中聚合(aggregate)主要用于处理数据(诸如统计平均值,求和等),并返回计算后的数据结果。有点类似sql语句中的 count(*)。


    aggregate() 方法

    MongoDB中聚合的方法使用aggregate()。

    语法

    aggregate() 方法的基本语法格式如下所示:

    >db.COLLECTION_NAME.aggregate(AGGREGATE_OPERATION)

    实例

    集合中的数据如下:

    {    _id: ObjectId(7df78ad8902c)    title: 'MongoDB Overview',     description: 'MongoDB is no sql database',    by_user: 'w3cschool.cc',    url: 'http://www.w3cschool.cc',    tags: ['mongodb', 'database', 'NoSQL'],    likes: 100 }, {    _id: ObjectId(7df78ad8902d)    title: 'NoSQL Overview',     description: 'No sql database is very fast',    by_user: 'w3cschool.cc',    url: 'http://www.w3cschool.cc',    tags: ['mongodb', 'database', 'NoSQL'],    likes: 10 }, {    _id: ObjectId(7df78ad8902e)    title: 'Neo4j Overview',     description: 'Neo4j is no sql database',    by_user: 'Neo4j',    url: 'http://www.neo4j.com',    tags: ['neo4j', 'database', 'NoSQL'],    likes: 750 },

    现在我们通过以上集合计算每个作者所写的文章数,使用aggregate()计算结果如下:

    > db.mycol.aggregate([{$group : {_id : "$by_user", num_tutorial : {$sum : 1}}}]) { "result" : [ { "_id" : "w3cschool.cc", "num_tutorial" : 2 }, { "_id" : "Neo4j", "num_tutorial" : 1 } ], "ok" : 1 } >

    以上实例类似sql语句: select by_user, count(*) from mycol group by by_user

    在上面的例子中,我们通过字段by_user字段对数据进行分组,并计算by_user字段相同值的总和。

    下表展示了一些聚合的表达式:

    表达式描述实例
    $sum计算总和。db.mycol.aggregate([{$group : {_id : "$by_user", num_tutorial : {$sum : "$likes"}}}])
    $avg计算平均值db.mycol.aggregate([{$group : {_id : "$by_user", num_tutorial : {$avg : "$likes"}}}])
    $min获取集合中所有文档对应值得最小值。db.mycol.aggregate([{$group : {_id : "$by_user", num_tutorial : {$min : "$likes"}}}])
    $max获取集合中所有文档对应值得最大值。db.mycol.aggregate([{$group : {_id : "$by_user", num_tutorial : {$max : "$likes"}}}])
    $push在结果文档中插入值到一个数组中。db.mycol.aggregate([{$group : {_id : "$by_user", url : {$push: "$url"}}}])
    $addToSet在结果文档中插入值到一个数组中,但不创建副本。db.mycol.aggregate([{$group : {_id : "$by_user", url : {$addToSet : "$url"}}}])
    $first根据资源文档的排序获取第一个文档数据。db.mycol.aggregate([{$group : {_id : "$by_user", first_url : {$first : "$url"}}}])
    $last根据资源文档的排序获取最后一个文档数据db.mycol.aggregate([{$group : {_id : "$by_user", last_url : {$last : "$url"}}}])

    管道的概念

    管道在Unix和Linux中一般用于将当前命令的输出结果作为下一个命令的参数。

    MongoDB的聚合管道将MongoDB文档在一个管道处理完毕后将结果传递给下一个管道处理。管道操作是可以重复的。

    表达式:处理输入文档并输出。表达式是无状态的,只能用于计算当前聚合管道的文档,不能处理其它的文档。

    这里我们介绍一下聚合框架中常用的几个操作:

    • $project:修改输入文档的结构。可以用来重命名、增加或删除域,也可以用于创建计算结果以及嵌套文档。
    • $match:用于过滤数据,只输出符合条件的文档。$match使用MongoDB的标准查询操作。
    • $limit:用来限制MongoDB聚合管道返回的文档数。
    • $skip:在聚合管道中跳过指定数量的文档,并返回余下的文档。
    • $unwind:将文档中的某一个数组类型字段拆分成多条,每条包含数组中的一个值。
    • $group:将集合中的文档分组,可用于统计结果。
    • $sort:将输入文档排序后输出。
    • $geoNear:输出接近某一地理位置的有序文档。

    管道操作符实例

    1、$project实例

    db.article.aggregate( { $project : {         title : 1 ,