52.大数据之旅——java分布式项目13-购物车,Quartz使用,RabbitMQ(消息队列)

购物车拦截器获取用户信息

52.大数据之旅——java分布式项目13-购物车,Quartz使用,RabbitMQ(消息队列)_第1张图片
CartInterceptor 类代码:

//购物车拦截器
public class CartInterceptor implements HandlerInterceptor{
@Autowired
private HttpClientService httpClientService;
private static final ObjectMapper MAPPER = new ObjectMapper();
 
//在controller方法执行前触发
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
/*
 * 步骤:
 * 1、从cookie中获取ticket,如果获取不到转向登录页面;如果获取到继续
 * 2、利用sso系统提供业务访问接口,查询redis,如果有继续,如果没有转向登录页面
 * 3、把user.json转成User。和controller共享变量,线程访问安全。ThreadLocal
 */
 
String ticket=CookieUtils.getCookieValue(request,"JT_TICKET");
if(StringUtils.isNullOrEmpty(ticket)){
response.sendRedirect("/user/login.html");
return false;
}else{
String url="http://sso.jt.com/user/query/"+ticket;
String resultJson=httpClientService.doGet(url);
String userJson=MAPPER.readTree(resultJson).get("data").asText();
Long userId=MAPPER.readValue(userJson, User.class).getId();
 
UserThreadLocal.setUserid(userId);
System.err.println("%$%$^%$^$$");
System.out.println(userId);
return true;
       }
 
}
 
//在controller方法执行后触发
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
 
 
}
 
//在controller方法执行后,在转向页面之前
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
// TODO Auto-generated method stub
 
}
 
 
}
 

UserTheadLocal类代码:

//用户的ThreadLocal形成线程安全性保障
public class UserTheadLocal {
 
private static final ThreadLocal<String> USERID=new ThreadLocal<>();
 
public static Long getUserId(){
return Long.parseLong(USERID.get());
}
public static void setUserId(Long userId){
USERID.set(String.valueOf(userId));
}
 
}

CartController类代码:

//我的购物车
@RequestMapping("/show")
public String myCartList(Model model){
 
Long userId = UserTheadLocal.getUserId();
 
List<Cart> cartList = dubboCartRestService.myCartList(userId);
model.addAttribute("cartList", cartList);
 
return "cart";
}
 

SpringMVC拦截器配置:

  <mvc:interceptors>
    <mvc:interceptor>
    <mvc:mapping path="/cart/**"/>
    <bean class="com.jt.web.interceptor.CartInterceptor">bean>
    mvc:interceptor>
     
    <mvc:interceptor>
    <mvc:mapping path="/order/**"/>
    <bean class="com.jt.web.interceptor.OrderInterceptor">bean>
    mvc:interc

eptor>
 
mvc:interceptors>

Quartz定时任务


52.大数据之旅——java分布式项目13-购物车,Quartz使用,RabbitMQ(消息队列)_第2张图片

Quartz整合


实现步骤:
1.在订单系统中引入整合文件 applicationContext-scheduler.xml
配置示例:


<bean name="paymentOrderJobDetail" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">

<property name="jobClass" value="com.jt.order.job.PaymentOrderJob" />

<property name="name" value="paymentOrder" />

<property name="group" value="Order" />

<property name="durability" value="true"/>

<property name="applicationContextJobDataKey" value="applicationContext"/>
bean>
 

<bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<property name="jobDetail" ref="paymentOrderJobDetail" />

<property name="cronExpression" value="0 0/1 * * * ?" />
bean>
 

<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
    <property name="triggers">
        <list>
            <ref bean="cronTrigger" />
        list>
    property>
bean>
 
beans>

2.在jt-order工程下,建立自定义的定时业务类
代码示意:

public class PaymentOrderJob extends QuartzJobBean{
 
//底层调用时就会回调这个方法
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
//定期删除两天前的未支付订单
ApplicationContext applicationContext = (ApplicationContext) context.getJobDetail().getJobDataMap().get("applicationContext");
Date date = new DateTime().minusDays(2).toDate();        //joda工具类方便日期加减,2天前
applicationContext.getBean(OrderMapper.class).paymentOrder(date);
}
}

3.完成OrderMapper类代码:
代码示意:

public interface OrderMapper{
public Order queryByOrderId(String orderId);
public void orderAdd(Order order);
public void paymentOrder(Date date);
}
 

4.完成OrderMapper.xml语句:
OrderMapper.xml:


<update id="paymentOrder" parameterType="date">
UPDATE tb_order SET STATUS=6 
WHERE payment_type=1 AND STATUS=1 AND create_time < #{date}        
update>

RabbitMQ


介绍
官方网址:http://www.rabbitmq.com/
52.大数据之旅——java分布式项目13-购物车,Quartz使用,RabbitMQ(消息队列)_第3张图片
RabbitMQ是流行的开源消息队列系统,用erlang语言开发。RabbitMQ是AMQP(高级消息队列协议)的标准实现。

AMQP(Advanced Message Queue )协议的出现其实也是顺应需求。因为同步消息通讯的有很多公开标准(如 COBAR的 IIOP ,或者是 SOAP 等),但是在异步消息处理中却不是这样,只有大企业有一些商业实现(如微软的 MSMQ ,IBM 的 Websphere MQ 等)。因此,在 2006 年的 6 月,Cisco 、Redhat、iMatix 等联合制定了 AMQP 的公开标准。

RabbitMQ是由RabbitMQ Technologies Ltd开发并且提供商业支持的。该公司在2010年4月被SpringSource(VMWare的一个部门)收购。在2013年5月被并入Pivotal。其实VMWare,Pivotal和EMC本质上是一家的。不同的是VMWare是独立上市子公司,而Pivotal是整合了EMC的某些资源,现在并没有上市。

系统架构
52.大数据之旅——java分布式项目13-购物车,Quartz使用,RabbitMQ(消息队列)_第4张图片
1.Broker:简单来说就是消息队列服务器实体。作用就是维护一条从Producer到Consumer的路线,保证数据能够按照指定的方式进行传输。

2.producer:消息生产者,就是投递消息的程序。

3.Message有两个部分:payload(有效载荷)和label(标签)。payload顾名思义就是传输的数据。label是exchange的名字或者说是一个tag,它描述了payload。

4.Exchange:消息交换机,它指定消息按什么规则,路由到哪个队列。

4.Queue:消息队列载体,每个消息都会被投入到一个或多个队列。

6.Routing Key:路由关键字,exchange根据这个关键字进行消息投递。

7.Binding:绑定,它的作用就是把exchange和queue按照路由规则绑定起来。

8.consumer:消息消费者,就是接受消息的程序。当有Message到达某个队列后,订阅这个队列的Consumer会消费消息。当然,一个队列可能被多个Consumer订阅和消费。在这个Message中,只有payload,label已经被删掉了。即对于Consumer来说,它是不知道谁发送的这个信息的。

9.Connection: 就是一个TCP的连接。Producer和Consumer都是通过TCP连接到RabbitMQ Server的。

10.channel:消息通道,在客户端的每个连接里,可建立多个channel,每个channel代表一个会话任务。

11.vhost:虚拟主机,一个broker里可以开设多个vhost,用作不同用户的权限分离。可以类比于linux操作系统下的用户组概念。
应用场景
RabbitMQ消息队列的主要特点是异步处理,主要目的是减少请求响应时间和解耦。所以主要的使用场景就是将比较耗时而且不需要即时(同步)返回结果的操作作为消息放入消息队列。同时由于使用了消息队列,只要保证消息格式不变,消息的发送方和接收方并不需要彼此联系,也不需要受对方的影响,即解耦合。

使用场景的话,举个例子:

大型电商的订单系统

或者同时有大量用户注册你的软件,再高并发情况下注册请求开始出现一些问题,例如邮件接口承受不住,或是分析信息时的大量计算使cpu满载,这将会出现虽然用户数据记录很快的添加到数据库中了,但是却卡在发邮件或分析信息时的情况,导致请求的响应时间大幅增长,甚至出现超时,这就有点不划算了。面对这种情况一般也是将这些操作放入消息队列(生产者消费者模型),消息队列慢慢的进行处理,同时可以很快的完成注册请求,不会影响用户使用其他功能。

不适用场景
针对网游等业务不太适合。游戏消息量多而且同步要求高,消息一般不需要缓存,实时响应发出较多。另外,类似战斗或移动的消息,量大而且要求响应迅速用mq性能会有问题。

所以在软件的正常功能开发中,并不需要去刻意的寻找消息队列的使用场景,而是当出现性能瓶颈时,去查看业务逻辑是否存在可以异步处理的耗时操作,如果存在的话便可以引入消息队列来解决。否则盲目的使用消息队列可能会增加维护和开发的成本却无法得到可观的性能提升,那就得不偿失了。

RabbitMQ的安装


52.大数据之旅——java分布式项目13-购物车,Quartz使用,RabbitMQ(消息队列)_第5张图片

RabbitMQ添加用户


52.大数据之旅——java分布式项目13-购物车,Quartz使用,RabbitMQ(消息队列)_第6张图片

RabbitMQ各种模式介绍


52.大数据之旅——java分布式项目13-购物车,Quartz使用,RabbitMQ(消息队列)_第7张图片

Simple模式


实现步骤:
1.创建Maven工程
2.引入相关的pom文件,(本例中是在jt-web工程做的测试,因为已包含RabbitMQ的jar包)
3.通过API操作

测试连接代码:

import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
 
          /*
 * 测试连接
 */
@Test
public void testConnect() throws Exception{
ConnectionFactory factory=new ConnectionFactory();
factory.setHost("192.168.234.131");
factory.setPort(5672);
factory.setVirtualHost("/jt");
factory.setUsername("jtadmin");
factory.setPassword("123456");
 
Connection cn=factory.newConnection();
while(true);
 
}


52.大数据之旅——java分布式项目13-购物车,Quartz使用,RabbitMQ(消息队列)_第8张图片
发送消息到队列:

/*
 * 简单模式-生成者
 */
@Test
public void simple_produce() throws Exception{
ConnectionFactory factory=new ConnectionFactory();
factory.setHost("192.168.234.132");
factory.setPort(5672);
factory.setVirtualHost("/jt");
factory.setUsername("jtadmin");
factory.setPassword("123456");
 
Connection cn=factory.newConnection();
 
String QUEUE_NAME="q2";
String message="helloworld";
Channel channel=cn.createChannel();
//1.队列名
//2.队列的数据是否持久化-durable
//3.当前连接断开后,队列是否自动删除 -exclusive   仅创建者可以使用的私有队列,断开后自动删除。(如果为true则只有创建者线程才可以使用,如果线程停止则queue也会自动删除)
//4.没有consumer时,队列是否自动删除-aoto_delete 
//5.额外的扩展参数 x-ha-policy
channel.queueDeclare(QUEUE_NAME,false,true,false,null);
channel.basicPublish("",QUEUE_NAME, null, message.getBytes());
 
while(true);
 
}
 
测试消费消息:
 
        /*
 * 测试消费信息
 */
@Test
public void simple_consume() throws Exception{
ConnectionFactory factory=new ConnectionFactory();
factory.setHost("192.168.234.131");
factory.setPort(5672);
factory.setVirtualHost("/jt");
factory.setUsername("jtadmin");
factory.setPassword("123456");
 
Connection connection=factory.newConnection();
 
Channel channel=connection.createChannel();
String QUEUE_NAME="hello";
 
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
 
 
//定义消费者在消费信息时的处理逻辑,本例中是将消息进行控制台打印。
//注意,队列中有几条信息,handleDelivery方法就会被调用几次。
//在实际业务中,常见的处理方式是将信息持久化到文件中或数据库中。
Consumer consumer = new DefaultConsumer(channel) {
  @Override
  public void handleDelivery(String consumerTag, Envelope envelope,
                             AMQP.BasicProperties properties, byte[] body)
      throws IOException {
    String message = new String(body, "UTF-8");
    System.out.println( message );
  }
};
channel.basicConsume(QUEUE_NAME, true, consumer);
while(true);//让消费者线程一直开启,则消费者会监听队列,一旦有新的消息,就会消费。
}

Work模式


发送者代码:

public class Send {
 
    private final static String QUEUE_NAME = "jt_test_queue_work";
 
    public static void main(String[] argv) throws Exception {
        // 获取到连接以及mq通道
        Connection connection = ConnectionUtil.getConnection();
        Channel channel = connection.createChannel();
 
        // 声明队列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
 
        for (int i = 0; i < 100; i++) {
            // 消息内容
            String message = "hello-" + i;
            channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
            System.out.println(" [x] Sent '" + message + "'");
 
            //Thread.sleep(i * 10);        //越往后,停歇的时间越长
        }
 
        channel.close();
        connection.close();
    }
}
 

消费者1的代码:

public class Recv {
 
    private final static String QUEUE_NAME = "jt_test_queue_work";
 
    public static void main(String[] argv) throws Exception {
 
        // 获取到连接以及mq通道
        Connection connection = ConnectionUtil.getConnection();
        Channel channel = connection.createChannel();
 
        // 声明队列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
 
        // 保证在接收端一个消息没有处理完时不会接收另一个消息,即接收端发送了ack后才会接收下一个消息。举个例子,如果输入1,那如果接收一个消息,但是没有应答,则客户端不会收到下一个消息,消息只会在队列中阻塞。如果输入3,那么可以最多有3个消息不应答,如果到达了3个,则发送端发给这个接收方得消息只会在队列中,而接收方不会有接收到消息的事件产生。
        channel.basicQos(1);
 
        // 定义队列的消费者
        QueueingConsumer consumer = new QueueingConsumer(channel);
        // 接收消息时使autoAck为false,即不自动会发ack确认消息       channel.basicConsume(QUEUE_NAME, false, consumer);
 
        // 获取消息
        while (true) {
            QueueingConsumer.Delivery delivery = consumer.nextDelivery();
            String message = new String(delivery.getBody());
            System.out.println(" [x] Received '" + message + "'");
            //休眠0.01秒
            Thread.sleep(10);
            // 在消息处理完成后发送ack确认消息
            channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
        }
    }
}
 

消费者2的代码:

public class Recv2 {
 
    private final static String QUEUE_NAME = "jt_test_queue_work";
 
    public static void main(String[] argv) throws Exception {
 
        // 获取到连接以及mq通道
        Connection connection = ConnectionUtil.getConnection();
        Channel channel = connection.createChannel();
 
        // 声明队列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
 
        // 同一时刻服务器只会发一条消息给消费者,每一次服务器只会向客户端发送一条
        channel.basicQos(1);
 
        // 定义队列的消费者
        QueueingConsumer consumer = new QueueingConsumer(channel);
        // 监听队列,手动返回完成状态
        channel.basicConsume(QUEUE_NAME, false, consumer);
 
        // 获取消息
        while (true) {
            QueueingConsumer.Delivery delivery = consumer.nextDelivery();
            String message = new String(delivery.getBody());
            System.out.println(" [x] Received '" + message + "'");
            // 休眠1秒
            Thread.sleep(1000);
 
            channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
        }
    }
}

注:
work模式下,消费者线程之间是竞争关系
此外,在代码里,两个消费线程的不同点在于一个消费快,一个消费慢

在启动时,应该先启动消费线程,在启动生产线程

为了方便连接,我们写了一个连接的工具类
ConnectionUtil 类代码:

public class ConnectionUtil {
 
    public static Connection getConnection() throws Exception {
        //定义连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        //设置服务地址
        factory.setHost("192.168.234.233");
        //端口
        factory.setPort(5672);
        //设置账号信息,用户名、密码、vhost
        factory.setVirtualHost("/jt");
        factory.setUsername("jtadmin");
        factory.setPassword("123456");
        // 通过工程获取连接
        Connection connection = factory.newConnection();
        return connection;
    }
 
}

Publish/Subscribe模式


发送者代码:

public class Send {
 
    private final static String EXCHANGE_NAME = "jt_test_exchange_fanout";
 
    public static void main(String[] argv) throws Exception {
        // 获取到连接以及mq通道
        Connection connection = ConnectionUtil.getConnection();
        Channel channel = connection.createChannel();
 
        // 声明exchange
        channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
 
        // 消息内容
        for(int i=0;i<100;i++){
                String message = "Hello World!"+i;
                channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes());
                System.out.println(" [x] Sent '" + message + "'");
        }
        
        channel.close();
        connection.close();
    }
}

消费者1的代码:

public class Recv {
 
    private final static String QUEUE_NAME = "jt_test_queue_exchange";
 
    private final static String EXCHANGE_NAME = "jt_test_exchange_fanout";
 
    public static void main(String[] argv) throws Exception {
 
        // 获取到连接以及mq通道
        Connection connection = ConnectionUtil.getConnection();
        Channel channel = connection.createChannel();
 
        // 声明队列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
 
        // 声明exchange
        channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
        
        // 绑定队列到交换机
        channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "");
 
        // 同一时刻服务器只会发一条消息给消费者
        channel.basicQos(1);
 
        // 定义队列的消费者
        QueueingConsumer consumer = new QueueingConsumer(channel);
        // 监听队列,手动返回完成
        channel.basicConsume(QUEUE_NAME, false, consumer);
 
        // 获取消息
        while (true) {
            QueueingConsumer.Delivery delivery = consumer.nextDelivery();
            String message = new String(delivery.getBody());
            System.out.println(" [x] Received '" + message + "'");
            Thread.sleep(10);
            // 返回消息消费状态
            channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
        }
    }
}

消费者2的代码:

public class Recv2 {
 
    private final static String QUEUE_NAME = "jt_test_queue_exchange2";
 
    private final static String EXCHANGE_NAME = "jt_test_exchange_fanout";
 
    public static void main(String[] argv) throws Exception {
 
        // 获取到连接以及mq通道
        Connection connection = ConnectionUtil.getConnection();
        Channel channel = connection.createChannel();
 
        // 声明队列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
 
        // 绑定队列到交换机
        channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "");
 
        // 同一时刻服务器只会发一条消息给消费者
        channel.basicQos(1);
 
        // 定义队列的消费者
        QueueingConsumer consumer = new QueueingConsumer(channel);
        // 监听队列,手动返回完成
        channel.basicConsume(QUEUE_NAME, false, consumer);
 
        // 获取消息
        while (true) {
            QueueingConsumer.Delivery delivery = consumer.nextDelivery();
            String message = new String(delivery.getBody());
            System.out.println(" [x] Received '" + message + "'");
            Thread.sleep(10);
            
            int i = 0;
            //int j = i/0; 当程序出错时,会终止消费
 
            channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
        }
    }
}

Routing模式


发送端代码:

public class Send {
 
    private final static String EXCHANGE_NAME = "jtItemDirectExchange";
 
    public static void main(String[] argv) throws Exception {
        // 获取到连接以及mq通道
        Connection connection = ConnectionUtil.getConnection();
        Channel channel = connection.createChannel();
 
        // 声明exchange
        channel.exchangeDeclare(EXCHANGE_NAME, "direct");
 
        // 消息内容
        String message = "123456";
        String routingKey = "item_update";
        channel.basicPublish(EXCHANGE_NAME, routingKey, null, message.getBytes());
        System.out.println(" [x] Sent '" + message + "'");
 
        channel.close();
        connection.close();
    }
}

接收者1代码:

public class Recv {
 
    private final static String QUEUE_NAME = "jt-web.itemQueue1";
 
    private final static String EXCHANGE_NAME = "jtItemDirectExchange";
 
    public static void main(String[] argv) throws Exception {
 
        // 获取到连接以及mq通道
        Connection connection = ConnectionUtil.getConnection();
        Channel channel = connection.createChannel();
        
        // 声明exchange
        channel.exchangeDeclare(EXCHANGE_NAME, "direct");
 
        // 声明队列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
 
        // 绑定队列到交换机
        String routingKey = "item_update";
        channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, routingKey);
 
        // 同一时刻服务器只会发一条消息给消费者
        channel.basicQos(1);
 
        // 定义队列的消费者
        QueueingConsumer consumer = new QueueingConsumer(channel);
        // 监听队列,手动返回完成,第二个参数false关闭自动消息确认
        channel.basicConsume(QUEUE_NAME, false, consumer);
 
        // 获取消息
        while (true) {
            QueueingConsumer.Delivery delivery = consumer.nextDelivery();
            String message = new String(delivery.getBody());
            System.out.println(" [x] Received '" + message + "'");
            Thread.sleep(10);
 
            channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
        }
    }
}

消费者2代码:

public class Recv2 {
 
    private final static String QUEUE_NAME = "jt-web.productQueue";
 
    private final static String EXCHANGE_NAME = "jtItemDirectExchange";
 
    public static void main(String[] argv) throws Exception {
 
        // 获取到连接以及mq通道
        Connection connection = ConnectionUtil.getConnection();
        Channel channel = connection.createChannel();
        
        // 声明exchange
        channel.exchangeDeclare(EXCHANGE_NAME, "direct");
 
        // 声明队列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
 
        // 绑定队列到交换机
        channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "key2");
 
        // 同一时刻服务器只会发一条消息给消费者
        channel.basicQos(1);
 
        // 定义队列的消费者
        QueueingConsumer consumer = new QueueingConsumer(channel);
        // 监听队列,手动返回完成
        channel.basicConsume(QUEUE_NAME, false, consumer);
 
        // 获取消息
        while (true) {
            QueueingConsumer.Delivery delivery = consumer.nextDelivery();
            String message = new String(delivery.getBody());
            System.out.println(" [x] Received '" + message + "'");
            Thread.sleep(10);
 
            channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
        }
    }
}

注:因为消费者2的路由key不匹配,所以接收不到消息。

Topic模式


发送者代码:

public class Send {
 
    private final static String EXCHANGE_NAME = "jt_test_exchange_topic";
 
    public static void main(String[] argv) throws Exception {
        // 获取到连接以及mq通道
        Connection connection = ConnectionUtil.getConnection();
        Channel channel = connection.createChannel();
 
        // 声明exchange
        channel.exchangeDeclare(EXCHANGE_NAME, "topic");
 
        // 消息内容
        String message = "Hello World! topic";
        channel.basicPublish(EXCHANGE_NAME, "item.update", null, message.getBytes());
        System.out.println(" [x] Sent '" + message + "'");
 
        channel.close();
        connection.close();
    }
}
 

消费者1的代码:

public class Recv {
 
    private final static String QUEUE_NAME = "jt_test_queue_topic";
 
    private final static String EXCHANGE_NAME = "jt_test_exchange_topic";
 
    public static void main(String[] argv) throws Exception {
 
        // 获取到连接以及mq通道
        Connection connection = ConnectionUtil.getConnection();
        Channel channel = connection.createChannel();
        channel.exchangeDeclare(EXCHANGE_NAME, "topic");
        
        // 声明队列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
 
        // 绑定队列到交换机
        channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "item.#");
 
        // 同一时刻服务器只会发一条消息给消费者
        channel.basicQos(1);
 
        // 定义队列的消费者
        QueueingConsumer consumer = new QueueingConsumer(channel);
        // 监听队列,手动返回完成
        channel.basicConsume(QUEUE_NAME, false, consumer);
 
        // 获取消息
        while (true) {
            QueueingConsumer.Delivery delivery = consumer.nextDelivery();
            String message = new String(delivery.getBody());
            System.out.println(" [x] Received1 '" + message + "'");
            Thread.sleep(10);
 
            channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
        }
    }
}

消费者2的代码:

public class Recv2 {
 
    private final static String QUEUE_NAME = "jt_test_queue_topic_work2";
 
    private final static String EXCHANGE_NAME = "jt_test_exchange_topic";
 
    public static void main(String[] argv) throws Exception {
 
        // 获取到连接以及mq通道
        Connection connection = ConnectionUtil.getConnection();
        Channel channel = connection.createChannel();
        // 声明exchange
        channel.exchangeDeclare(EXCHANGE_NAME, "topic");
        
        // 声明队列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
 
        // 绑定队列到交换机
        channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "item.update");
 
        // 同一时刻服务器只会发一条消息给消费者
        channel.basicQos(1);
 
        // 定义队列的消费者
        QueueingConsumer consumer = new QueueingConsumer(channel);
        // 监听队列,手动返回完成
        channel.basicConsume(QUEUE_NAME, false, consumer);
 
        // 获取消息
        while (true) {
            QueueingConsumer.Delivery delivery = consumer.nextDelivery();
            String message = new String(delivery.getBody());
            System.out.println(" [x] Received2 '" + message + "'");
            Thread.sleep(10);
 
            channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
        }
    }
}

注:因为发送的是:Hello World! topic,消费者1的匹配规则是item.#,消费者2的匹配规则是item.update,所以只有消费者1能够收到消息。

引入RabbitMQ


52.大数据之旅——java分布式项目13-购物车,Quartz使用,RabbitMQ(消息队列)_第9张图片
实现步骤:
1.将整合文件拷贝到jt-web-controller工程下,一共有三个文件
分别是:

rabbitmq.properties 配置示例:

rabbit.ip=192.168.234.233
rabbit.port=5672
rabbit.username=jtadmin
rabbit.password=123456
rabbit.vhost=/jt

applicationContext-rabbitmq-send.xml配置示例:


<rabbit:connection-factory id="connectionFactory"
host="${rabbit.ip}" port="${rabbit.port}" username="${rabbit.username}" password="${rabbit.password}"
virtual-host="${rabbit.vhost}" />
 

<rabbit:admin connection-factory="connectionFactory" />
 

<rabbit:queue name="jt-order-Queue" auto-declare="true"/>
 

<rabbit:direct-exchange name="OrderDirectExchange" auto-declare="true">
<rabbit:bindings>

<rabbit:binding queue="jt-order-Queue" key="order.create"/>
rabbit:bindings>
rabbit:direct-exchange>
 

<rabbit:listener-container connection-factory="connectionFactory">

<rabbit:listener ref="rabbitOrderService" method="create" queue-names="jt-order-Queue"/>
rabbit:listener-container>
 
<bean id="rabbitOrderService" class="com.jt.order.mq.service.RabbitOrderService">bean>
 
beans>
 

applicationContext-rabbitmq-receive.xml配置示例:


<rabbit:connection-factory id="connectionFactory"
host="${rabbit.ip}" port="${rabbit.port}" username="${rabbit.username}" password="${rabbit.password}"
virtual-host="${rabbit.vhost}" />
 

<rabbit:template id="amqpTemplate" connection-factory="connectionFactory"
exchange="OrderDirectExchange" />
 

<rabbit:admin connection-factory="connectionFactory" />
 

<rabbit:direct-exchange name="OrderDirectExchange" auto-declare="true" durable="true">
rabbit:direct-exchange>
 
beans>

注意:标红的路由key要和代码里的key一致

RabbitOrderService类代码:

@Autowired
private OrderMapper orderMapper;
 
 
public void create(Order order){
 
orderMapper.createOrder(order);
 
}
 
DubboOrderRestServiceImpl类代码:
@Autowired
private RabbitTemplate rabbitTemplate; 
 
private static final ObjectMapper MAPPER=new ObjectMapper();
 
@Override
public String createOrder(String json) {
try {
Order order=MAPPER.readValue(json, Order.class);
//生成订单编号,用户id+当前的时间戳
String orderId=order.getUserId()+""+System.currentTimeMillis();
order.setOrderId(orderId);
order.setCreated(new Date());
order.setUpdated(order.getCreated());
 
 
rabbitTemplate.convertAndSend("order.create",order);
 
//向前台返回生成订单编号,因为前台要构造SysResult.ok(orderId)这种形式
return orderId;
 
} catch (Exception e) {
// TODO: handle exception
}
 
return null;
}
 

.Net下RabbitMQ的使用(7) – 消息的传输控制


消息的应答
RabbitMQ有两种应答模式,自动和手动。这也是AMQP协议所推荐的。这在point-to-point和broadcast都是一样的。
自动应答-当RabbitMQ把消息发送到接收端,接收端把消息出队列的时候就自动帮你发应答消息给服务。
手动应答-需要我们开发人员手动去调用ack方法去告诉服务已经收到。
文档推荐在大数据传输中,如果对个别消息的丢失不是很敏感的话选用自动应答比较理想,而对于那些一个消息都不能丢的场景,需要选用手动应答,也就是说在正确处理完以后才应答。如果选择了自动应答,那么消息重发这个功能就没有了。

消息的拒收
拒收,是接收端在收到消息的时候响应给RabbitMQ服务的一种命令,告诉服务器不应该由我处理,或者拒绝处理,扔掉。接收端在发送reject命令的时候可以选择是否要重新放回queue中。如果没有其他接收者监控这个queue的话,要注意一直无限循环发送的危险。
BasicDeliverEventArgs ea = (BasicDeliverEventArgs)consumer.Queue.Dequeue();
channel.BasicReject(ea.DeliveryTag, false);
BasicReject方法第一个参数是消息的DeliveryTag,对于每个Channel来说,每个消息都会有一个DeliveryTag,一般用接收消息的顺序来表示:1,2,3,4 等等。第二个参数是是否放回queue中,requeue。
BasicReject一次只能拒绝接收一个消息,而BasicNack方法可以支持一次0个或多个消息的拒收,并且也可以设置是否requeue。
channel.BasicNack(3, true, false);
在第一个参数DeliveryTag中如果输入3,则消息DeliveryTag小于等于3的,这个Channel的,都会被拒收。

消息的QoS
QoS = quality-of-service, 顾名思义,服务的质量。通常我们设计系统的时候不能完全排除故障或保证说没有故障,而应该设计有完善的异常处理机制。在出现错误的时候知道在哪里出现什么样子的错误,原因是什么,怎么去恢复或者处理才是真正应该去做的。在接收消息出现故障的时候我们可以通过RabbitMQ重发机制来处理。重发就有重发次数的限制,有些时候你不可能不限次数的重发,这取决于消息的大小,重要程度和处理方式。
甚至QoS是在接收端设置的。发送端没有任何变化,接收端的代码也比较简单,只需要加如下代码:
channel.BasicQos(0, 1, false);
代码第一个参数是可接收消息的大小的,但是似乎在客户端2.8.6版本中它必须为0,即使:不受限制。如果不输0,程序会在运行到这一行的时候报错,说还没有实现不为0的情况。第二个参数是处理消息最大的数量。举个例子,如果输入1,那如果接收一个消息,但是没有应答,则客户端不会收到下一个消息,消息只会在队列中阻塞。如果输入3,那么可以最多有3个消息不应答,如果到达了3个,则发送端发给这个接收方得消息只会在队列中,而接收方不会有接收到消息的事件产生。总结说,就是在下一次发送应答消息前,客户端可以收到的消息最大数量。第三个参数则设置了是不是针对整个Connection的,因为一个Connection可以有多个Channel,如果是false则说明只是针对于这个Channel的。
这种数量的设置,也为我们在多个客户端监控同一个queue的这种负载均衡环境下提供了更多的选择。

MQ其他产品概述


RabbitMQ是一个传统的messaging queue系统实现,基于Erlang,更多用在企业系统内,对数据一致性、稳定性和可靠性要求很高的场景。对性能和吞吐量还在其次。

Kafka
Kafka是linkedin开源的MQ系统,追求高吞吐量,一开始的目的就是用于日志收集和传输,适合产生大量数据的互联网服务的数据收集业务。同时对于数据一致性、可靠性也有不错的表现(通过Zookeeper来管理)

Kafka的特征如下:
高写入速度:Kafka能以超过1Gbps NIC的速度写这盘磁带(实际可以到SATA 3速度

高可靠性: 通过zookeeper做分布式一致性,同步到任意多块磁盘上,故障自动切换选主,自愈。

高容量:通过横向扩展,LinkedIn每日通过Kafka存储的新增数据高达175TB,8000亿条消息,可无限扩容,类似把两条磁带粘到一起。

其他产品,如ActiveMQ,MSMQ,NetMQ,ZMQ等若感兴趣,可在峰会中选择学习

使用MQ的理由


  1. 解耦
    在项目启动之初来预测将来项目会碰到什么需求,是极其困难的。消息队列在处理过程中间插入了一个隐含的、基于数据的接口层,两边的处理过程都要实现这一接口。这允许你独立的扩展或修改两边的处理过程,只要确保它们遵守同样的接口约束。

  2. 异步通信
    很多时候,你不想也不需要立即处理消息。消息队列提供了异步处理机制,允许你把一个消息放入队列,但并不立即处理它。你想向队列中放入多少消息就放多少,然后在你乐意的时候再去处理它们。

  3. 冗余
    消息队列支持数据的持久化,即把数据进行持久化直到它们已经被完全处理,通过这一方式规避了数据丢失风险。

  4. 峰值处理能力
    当你的应用访问量剧增的情况下,使用消息队列能够使关键组件顶住增长的访问压力,而不是因为超出负荷的请求而完全崩溃。

  5. 送达保证
    比如在Work模式下,每一个消息只能被处理一次。这之所以成为可能,是因为获取一个消息只是"预定"了这个消息,暂时把它移出了队列。除非客户端明确的表示已经处理完了这个消息,否则这个消息会被放回队列中去,在一段可配置的时间之后可再次被处理。

6.排序保证
在许多情况下,数据处理的顺序都很重要。消息队列本来就是排序的,FIFO(先进先出)

上一篇 51.大数据之旅——java分布式项目12-整合Dubbo

你可能感兴趣的:(大数据学习之旅)