高并发推送数据之Kafka(.Net、Java)消息队列
.NET VS工具添加程序包源 在NuGet包管理中选择程序包源为上面添加的私有仓库。 搜索Data.Pipelines并安装。 在app.congif或者web.config中添加Kafka配置
<appSettings> <add key="kafka.ip" value="172.20.105.205"/> <add key="kafka.prot" value="9092"/> </appSettings>
/// <summary> /// 实时数据综合推送 /// </summary> /// <param name="listTemp"></param> public void RealTimeDataPush(List<T_SJZX_SZZDJC_CYXXInfo> listTemp, List<T_SJZX_SZZDJC_JCJGInfo> listTempDetail) { var listData = new List<Dictionary<string, object>>(); foreach (var item in listTemp) { var d = new Dictionary<string, object>(); d.Add("XH", item.XH);//序号 d.Add("DXMC", item.DXMC);//对象名称 d.Add("DXBH", item.DXBH);//对象编号 d.Add("CDBH", item.CDBH);//测点编号 d.Add("CDMC", item.CDMC);//测点名称 d.Add("XZQDM", item.XZQDM);//行政区代码 d.Add("SSXZQ", item.SSXZQ);//所属行政区 d.Add("JCSJ", item.JCSJ.Value.ToString("yyyy-MM-dd HH:mm:ss"));//监测时间 //d.Add("JCN", item.JCN);//监测年 //d.Add("JCY", item.JCY);//监测月 //d.Add("JCR", item.JCR);//监测日 //d.Add("JCXS", item.JCXS);//监测小时 d.Add("SFZX", item.SFZX);//是否在线 if (!string.IsNullOrEmpty(item.ZXBZBH)) d.Add("ZXBZBH", item.ZXBZBH);//执行标准编号 else d.Add("ZXBZBH", ""); if (!string.IsNullOrEmpty(item.ZXBZMC)) d.Add("ZXBZMC", item.ZXBZMC);//执行标准名称 else d.Add("ZXBZMC", ""); if (!string.IsNullOrEmpty(item.SZLB)) d.Add("SZLB", item.SZLB);//水质类别 else d.Add("SZLB", ""); if (!string.IsNullOrEmpty(item.SFCB)) d.Add("SFCB", item.SFCB);//是否超标 else d.Add("SFCB", ""); if (!string.IsNullOrEmpty(item.CBXM)) d.Add("CBXM", item.CBXM);//超标项目 else d.Add("CBXM", ""); //d.Add("YQBM", item.YQBM);//仪器编码 //d.Add("YQMC", item.YQMC);//仪器名称 //d.Add("YQXH", item.YQXH);//仪器型号 //d.Add("YQZT", item.YQZT);//仪器状态 d.Add("ORGID", item.ORGID);//机构代码 d.Add("CJR", item.CJR);//创建人 d.Add("CJSJ", item.CJSJ.Value.ToString("yyyy-MM-dd HH:mm:ss"));//创建时间 d.Add("GXR", item.GXR);//更新人 d.Add("SJLY", item.SJLY);//数据来源 d.Add("SJZT", item.SJZT);//数据状态 d.Add("GXSJ", item.GXSJ.Value.ToString("yyyy-MM-dd HH:mm:ss"));//更新时间 #region 明细 var listData2 = new List<Dictionary<string, object>>(); var listTempDetail2 = listTempDetail.Where(p => p.CYXH == item.XH); foreach (var item2 in listTempDetail2) { var d2 = new Dictionary<string, object>(); d2.Add("XH", item2.XH);//序号 d2.Add("CYXH", item2.CYXH);//采样序号 if (!string.IsNullOrEmpty(item2.FXXMDM)) d2.Add("FXXMDM", item2.FXXMDM);//分析项目编号 else d2.Add("FXXMDM", ""); if (!string.IsNullOrEmpty(item2.FXXMMC)) d2.Add("FXXMMC", item2.FXXMMC);//分析项目名称 else d2.Add("FXXMMC", ""); d2.Add("BCJG", item2.BCJG);//报出结果 d2.Add("BCJGBS", item2.BCJGBS);//报出结果表示 d2.Add("BCJGDW", item2.BCJGDW);//报出结果单位 if (!string.IsNullOrEmpty(item2.BZSX)) d2.Add("BZSX", item2.BZSX);//标准上限 else d2.Add("BZSX", ""); if (!string.IsNullOrEmpty(item2.BZXX)) d2.Add("BZXX", item2.BZXX);//标准下限 else d2.Add("BZXX", ""); if (!string.IsNullOrEmpty(item2.SFCB)) d2.Add("SFCB", item2.SFCB);//是否超标 else d2.Add("SFCB", ""); if (!string.IsNullOrEmpty(item2.CBBS)) d2.Add("CBBS", item2.CBBS);//超标倍数 else d2.Add("CBBS", ""); if (!string.IsNullOrEmpty(item2.SZLB)) d2.Add("SZLB", item2.SZLB);//水质类别 else d2.Add("SZLB", ""); if (!string.IsNullOrEmpty(item2.SZLB)) d2.Add("YQSBXH", item2.YQSBXH);//仪器编码 else d2.Add("YQSBXH", ""); d2.Add("ORGID", item2.ORGID);//机构代码 d2.Add("CJR", item2.CJR);//创建人 d2.Add("CJSJ", item2.CJSJ.Value.ToString("yyyy-MM-dd HH:mm:ss"));//创建时间 d2.Add("GXR", item2.GXR);//更新人 d2.Add("SJLY", item2.SJLY);//数据来源 d2.Add("SJZT", item2.SJZT);//数据状态 d2.Add("GXSJ", item2.GXSJ.Value.ToString("yyyy-MM-dd HH:mm:ss"));//更新时间 listData2.Add(d2); } #endregion d.Add("JCJG", listData2); listData.Add(d); } //SZZDZ_CYXX var product = new DataProducer("dfb426e321fc417981858b0927c21016", "XH"); //分页推送 int PageSize = 100; int PageTotal = (int)Math.Ceiling((decimal)listData.Count / PageSize); for (int PageIndex = 0; PageIndex < PageTotal; PageIndex++) { var data = listData.Skip(PageIndex * PageSize).Take(PageSize).ToList(); product.Send(data, r => Console.WriteLine(!r.Error.IsError ? $"Delivered message to {r.TopicPartitionOffset}" : $"Delivery Error: {r.Error.Reason}")); } Assert.IsTrue(true); //Thread.Sleep(500); }
using System; using System.Collections.Generic; using System.Linq; using System.Text; using Confluent.Kafka; //using Newtonsoft.Json; using System.Configuration; using Newtonsoft.Json; namespace Common { class DataProducer { private readonly string _key; private readonly string _dataKeyField; // 从配置读取kafka配置 static string ip = ConfigurationManager.AppSettings["kafka.ip"]; static string port = ConfigurationManager.AppSettings["kafka.port"]; /// <summary> /// 构造方法 /// </summary> /// <param name="key">业务数据队列key</param> /// <param name="dataKeyField">数据主键</param> public DataProducer(string key, string dataKeyField) { this._key = key; this._dataKeyField = dataKeyField; } /// <summary> /// 发送数据 /// </summary> /// <param name="data">数据集</param> /// <param name="callback">发送成功后回调,在发送成功前,不要杀死相关线程,否则可能导致数据丢失</param> public void Send(List<Dictionary<string, object>> data, Action<DeliveryReport<string, string>> callback) { foreach (var datum in data) { var keys = new List<string>(datum.Count); // 找出时候类型的字段 foreach (var entry in datum) { if (entry.Value is DateTime) { keys.Add(entry.Key); } } // 把时间转成long,与Java的getTime()兼容 foreach (var key in keys) { var eeee = (DateTime)datum[key]; // TODO 日期转换 datum[key] = "日期字符串"; } } var topic = this._key; var d = new Dictionary<string, object> { { "data", data }, { "keyField", this._dataKeyField } }; var jsonString = JsonConvert.SerializeObject(d, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }); // TODO 生成key Send(topic, jsonString.GetHashCode().ToString(), jsonString, callback); } /// <summary> /// 发送消息 /// </summary> /// <param name="topic">KafkaTopic</param> /// <param name="key">消息key</param> /// <param name="value">数据</param> /// <param name="callback">发送完成回调</param> private void Send(string topic, string key, string value, Action<DeliveryReport<string, string>> callback) { if (string.IsNullOrEmpty(ip)) { ip = "172.20.105.205"; } if (string.IsNullOrEmpty(port)) { port = "9092"; } var conf = new ProducerConfig { BootstrapServers = $"{ip}:{port}" }; using (var p = new ProducerBuilder<string, string>(conf).Build()) { // TODO 异常处理,添加日志 p.Produce(topic, new Message<string, string> { Key = key, Value = value }, callback); //Console.WriteLine($"topic: {topic}. key: k, value: " + value); // TODO 异常处理,添加日志 // wait for up to 1 seconds for any inflight messages to be delivered. p.Flush(TimeSpan.FromSeconds(1)); } } } }
JAVA
Maven镜像配置
修改本地maven的settings文件
在mirros下增加mirror:
<mirror> <id>bigdata-center</id> <mirrorOf>*</mirrorOf> <url>http://172.20.105.205:8081/repository/maven-public/</url> </mirror>
集成SDK
Maven引包
<dependency> <groupId>com.tencent.guangdong-eco-environment</groupId> <artifactId>data-pipelines</artifactId> <version>1.0-SNAPSHOT</version> </dependency>
配置
新建kafka配置文件
resources/conf/kafka.properties
配置内容如下:
kafka.ip=172.20.105.205
kafka.port=9092
package com.tencent.eco.data.product; import com.szboanda.platform.common.utils.DateUtils; import org.testng.annotations.Test; import java.text.ParseException; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; public class DataProducerTest { @Test public void send() throws ParseException { List<Map<String, Object>> data = new ArrayList<>(); Map<String, Object> d = new HashMap<>(); d.put("XH", "XH1"); d.put("CDBH", "CDBH1"); d.put("JCSJ", DateUtils.parseDate("2019-05-12 12:00","yyyy-MM-dd HH:mm")); d.put("JCXMDH", "w23234"); d.put("JCZ", 12.4); d.put("ORGID", "440000"); d.put("CJR", "tencent"); d.put("CJSJ", new Date()); d.put("XGR", "tencent"); d.put("GXSJ", new Date()); d.put("SJLY", "testQueue"); d.put("SJZT", "I"); data.add(d); Map<String, Object> d1 = new HashMap<>(); d1.put("XH", "XH2"); d1.put("CDBH", "CDBH1"); d1.put("JCSJ", DateUtils.parseDate("2019-05-12 12:00","yyyy-MM-dd HH:mm")); d1.put("JCXMDH", "w23234"); d1.put("JCZ", 12.45); d1.put("ORGID", "440000"); d1.put("CJR", "tencent"); d1.put("CJSJ", new Date()); d1.put("XGR", "tencent"); d1.put("GXSJ", new Date()); d1.put("SJLY", "testQueue"); d1.put("SJZT", "I"); data.add(d1); // 用来控制当前线程等待发送消息后才退出 final AtomicBoolean isSanded = new AtomicBoolean(false); DataProducer producer = new DataProducer("74ef6a7b7adc4e5cb6ee3d741d0e3210", "XH"); // 发送消息 producer.send(data, (metadata, exception) -> { isSanded.set(true); }); // 由于消息是异步发送,在跑单元测试时,需要暂停线程等待消息发送,如果不暂停,消息可能会丢失 try { while (!isSanded.get()) { Thread.sleep(1000); } } catch (InterruptedException e) { e.printStackTrace(); } } }
Maven镜像配置
修改本地maven的settings文件
在mirros下增加mirror: