我们曾在之前的文章(COBOL程序数据转JSON格式消息·III)提到过Kafka在CICS中的运用,今天我们将通过一个案例,更加全面地介绍Kafka和CICS的集成情况。

如何把CICS应用程序中的消息发送到各大流处理服务器,帮助CICS用户扩大业务范围,一直是CICS努力的方向之一。如今,Kafka已被全球众多企业广泛采用,成为最受欢迎的分布式流处理平台之一。

毫无疑问,CICS也支持和Kafka的友好集成。现在,CICS用户可以轻松地把消息发送到Kafka服务器。在这篇文章中,我们就将介绍运行在CICS Liberty中的Liberty Kafka客户端如何通过调用Kafka客户端API将消息发送到Kafka服务器。

1. Kafka和IBM Event Streams

Apache Kafka是一个分布式的流处理平台,用于发布和订阅流式记录。它起源于LinkedIn,并于2011年成为Apache的开源项目。Kafka快速、可扩展、持久且具有容错能力,多年来发展迅猛。如今,《财富》500强中已有超过三分之一的企业将其用于生产系统。

将Kafka与CICS集成的想法源于多个客户的需求,他们希望实现CICS与Kafka的连接。他们的业务运行在CICS上,但他们逐渐意识到需要将其数据发送到Kafka服务器。

我们的解决方案采用了IBM Event Streams。IBM Event Streams 基于 Apache Kafka构建,是一款高吞吐量且具有容错功能的事件管理平台,可帮助您构建极具响应性的智能化事件驱动型应用。

kafka传输json格式到mysql kafka命令发送json数据_kafka发送json数据

当前,有三种机制可用于将数据从主机传输到IBM Event Streams:

A.运行在CICS中的Liberty Kafka客户端

我们的项目就是这种情况,它需要起一个运行在CICS中的Liberty JVM服务器。我们开发了一个Liberty Kafka客户端,它实际上是运行在CICS Liberty中的Java应用程序。该客户端通过调用Kafka客户端API将消息发送到Kafka服务器。

B. MQ连接器

如果您拥有IBM MQ许可证,则可以使用MQ事件流连接器连接到IBM Event Streams。IBM创建了一对连接器。MQ源连接器用于将数据从IBM MQ复制到事件流或Apache Kafka,而MQ接收器用于将数据从事件流或Apache Kafka复制到IBM MQ。连接器本身可以运行在USS和zLinux下,也可以在分布式系统上运行。

C. Inbound RESTful APIs

RESTful API允许包括z/OS Connect在内的多个系统将数据发送到IBM Event Streams。这意味着我们可以使用RESTful API将事件从任何主机系统发送到IBM Event Streams。RESTful API是单向的,即从Z到IBM Event Streams。

2. 项目背景

一些供应商正在银行业中寻求新的渠道应用解决方案。我们的一家泰国客户与一家业务供应商合作,开发基于Kafka服务器的银行渠道应用程序。他们的解决方案依赖于主机数据,那要如何将消息从CICS传输到Kafka服务器呢?大家一致认为最好的解决方案是将消息从CICS直接发送到Kafka服务器。

3. 项目架构

我们的解决方案包括以下内容:

  1. 运行在CICS region中的COBOL程序负责发送应用程序数据。COBOL程序通过EXEC CICS LINK调用运行在CICS Liberty JVM服务器中的Java程序。数据通过CICS channel/container传输。
  2. 注解Java方法并配置Liberty JVM服务器以从CICS程序调用Java EE应用程序。

Liberty中的Java程序实现以下逻辑:

  1. 调用JCICS API从CICS channel/container获取数据。
  2. 调用DFHJSON程序将应用程序数据转换为JSON。
  3. 通过调用Kafka客户端API,使Kafka生产者客户端将JSON数据发送到Kafka服务器。

kafka传输json格式到mysql kafka命令发送json数据_数据_02

4. Link to Liberty (L2L)

使用L2L,我们可以将Liberty JVM服务器中的Java EE应用程序作为CICS交易的初始程序或通过LINK、START或START CHANNEL命令来调用它。L2L允许COBOL、PL/I甚至汇编程序应用程序以标准方式轻松使用Java EE功能。

我们使用L2L功能将Java应用程序暴露成CICS程序,需要执行以下步骤:

1. 配置Liberty JVM服务器

要配置Liberty JVM服务器以支持到Java EE应用程序的链接,要将cicsts:link-1.0功能添加到server.xml中。确保在部署Java应用程序之前添加此功能。

2. 准备Java EE应用程序

对于能被CICS程序调用Java EE应用程序,它必须是纯Java对象(POJO),并带有注解才能启用所需的Java方法。

1   public class LinkToLiberty {     
 2      @CICSProgram("PUTQ")
 3       public void putQ()   4   {               
 5             try 
 6             { 
 7                    //JCICS example, Get data from Channal/Container.
 8                   Channel currentChannel =Task.getTask().getCurrentChannel();
 9                   if (currentChannel != null) 
10                   {  
11                         Container conHead = currentChannel.getContainer("HEAD");
12                         if (conHead != null)
13                         {                 
14                               byte[] dataArry =conHead.get();                         
15                              Container  conPayload = currentChannel.getContainer("PAYLOAD");
16                               if(conPayload !=null)
17                               {
18                                    byte[]payloadData = conPayload.get();                           
19                               }
20                              // TODO: we will use the data in container here      
21                           }
22                        }
23                   }
24               }
25   }

左右滑动查看代码

在上面的代码中,我们用@CICSProgram对putQ()方法进行了注解,并指定其参数以匹配要创建的PROGRAM资源的名称,例如@CICSProgram("PUTQ")。当我们enable Java项目的bundle资源后,CICS会自动创建一个 PROGRAM 资源,PROGRAM的名称就是注解中的参数。

Channel/container是Java程序和COBOL程序之间实现数据通信的最主要方式。本例中我们可以看到程序在最开始获取到当前的channel。然后,我们从channel中通过getContainer 来获取COBOL程序传入的数据。

5.  DFHJSON转化应用程序数据为JSON

DFHJSON是CICS提供的一个程序,应用程序可以通过Link DFHJSON实现应用程序数据和JSON数据之间的转换。

在开始之前,JSONTRANSFRM bundle资源必须已经安装并已启用。数据转换是通过使用 JSONTRANSFRM 对象驱动的,该对象根据 JSON 模式绑定文件 .jsbind 创建,该文件包含应用程序数据与JSON 数据之间的映射。因为我们是从JSON模式开始,我们可以运行DFHJS2LS批处理作业以创建应用程序记录的数据映射。

具体步骤如下:

1. 应用程序必须创建一个channel(例如MyJSONChan),并将以下container放入channel中。

  • DFHJSON-DATA
  • DFHJSON-TRANSFRM

2. 使用LINK PROGRAM API命令链接到CICS提供的可链接接口DFHJSON,将COBOL应用程序数据转换成JSON数据。

EXEC CICS LINK PROGRAM('DFHJSON') CHANNEL('MyJSONChan')

左右滑动查看代码

3. 获取容器 DFHJSON-ERROR 并检查在转换期间是否发生任何错误。

4. 获取容器 DFHJSON-JSON。

代码片段如下:

1      //(1)Create a channel MyJSONChan.
 2    jsonChan = Task.getTask().createChannel("MyJSONChan");
 3    // Put the contents of the language structure into the DFHJSON-DATA container.
 4    Container conJsonData = jsonChan.createContainer("DFHJSON-DATA");
 5   conJsonData.put(dataArry);
 6    // Put the name of the BUNDLE resource into the DFHJSON-TRANSFRM container.
 7    Container conTransfrm = jsonChan.createContainer("DFHJSON-TRANSFRM");
 8   conTransfrm.putString("OUTP");                    
 9
10      //(2)Issues an EXEC CICS LINK request to the program DFHJSON to do the transformation.
11
12      Program p = new Program();
13     p.setName("DFHJSON");                                  
14     p.link(jsonChan);
15
16       //(3)Check the DFHJSON-ERROR container to see if there is any error.
17      Container bitconJsonErr = jsonChan.getContainer("DFHJSON-ERROR");                    
18      if (bitconJsonErr != null)
19      {
20        byte[] ba = bitconJsonErr.get();
21       ByteBuffer bb = ByteBuffer.wrap(ba);                              
22        int cicsrc = bb.getInt();
23       logger.error("error happens for CICS Json parser! DFHJSON-ERROR="+cicsrc);       
24
25        //Check the DFHJSON-ERROR container to get more errors messages.
26       Container conERRORMSG = jsonChan.getContainer("DFHJSON-ERRORMSG"); 
27        if (conERRORMSG != null)
28        {
29         String ERRORMSG = conERRORMSG.getString();
30         logger.error("DFHJSON-ERRORMSG="+ERRORMSG);
31        }                   
32       }
33       else
34       {
35          //(4)Get the JSON message from the DFHJSON-JSON container.
36         Container conJson = jsonChan.getContainer("DFHJSON-JSON"); 
37          if (conJson != null)
38            {
39             sJson = conJson.getString();
40            }
41        }

左右滑动查看代码

6. 性能测试

我们通过使用池对象来提高性能。如果不使用线程池,每当将数据发送到Kafka时都会创建多个对象。线程池可以复用创建成本太高的对象,从而大大提高了性能。

在我们的例子中,我们调用KafkaProducer API将JSON消息发送到Kafka主题 "ACCOUNT-BALANCES-INBOUND-TOPIC"。消息是异步发送的,使用CallBack类并实现onCompletion()方法:

1Properties properties = new Properties(); 
 2
 3properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, KAFKA_BROKER_LIST); properties.put(ProducerConfig.CLIENT_ID_CONFIG, CLIENT_ID); 
 4properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer"); properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer"); 
 5
 6producer = new KafkaProducer<>(properties);
 7
 8ProducerRecord data = new ProducerRecord(TOPIC, sJson);  910producer.send(data, new SimpleCallBack(startTime, 0, sJson));

左右滑动查看代码

创建新生产者(KafkaProducer)的成本比较高,每次将数据发送到Kafka时,过程都是新建KafkaProducer对象 > 打开 > 发送 > 关闭。当打开时,Kafka客户端会连接到Kafka服务器以读取元数据,组装消息并再次发送。这是一个成本比较高的过程,为了避免此类问题,我们配置了Kafka对象池。

对象池是一种设计模式,允许重用昂贵的对象。我们使用Apache Commons Pool(org.apache.commons.pool2)创建我们自己的对象池。

我们创建的是一个存储KafkaProducer对象的池。Liberty应用程序在发送数据时通过调用pool.borrowObject借用KafkaProducer,然后在完成发送数据后通过调用pool.returnObject将KafkaProducer返回到池中。

1pool = new GenericObjectPool>(new ProducerFactory(), config); 23producer = ProducerListener.pool.borrowObject(); 4producer.send(data, new SimpleCallBack(startTime, 0, sJson));5ProducerListener.pool.returnObject(producer);

左右滑动查看代码

我们将该项目部署到了客户的系统上,获得了极好的性能——一分钟内发送了3,000条记录,平均响应时间仅为0.0045秒!

如果您有类似需求,需要将消息从CICS发送到Kafka服务器,也可以尝试在系统中实现我们的解决方案哦。

今天是我们在年前的最后一次推送,祝大家新年快乐,我们年后见!



kafka传输json格式到mysql kafka命令发送json数据_服务器_03

作者:米爱莲

作者:董亚鹏

作者:王典