项目我已上传到github中,感兴趣的可以下载下来看看,希望大神可以在以后多多指点。
github代码地址:https://github.com/sjctt/spartacus
昨天我完成了udp接收部分,今天来把入库部分做一下。
在这里,如果我把入库动作与udp做单线程处理,会发现,在高并发数据下,入库效率存在很大的延迟,并且udp的接收会有很多数据丢失的情况,这里我们需要采用多线程入库的方式处理数据。
当然,在做多线程时,无限制的开启线程操作显然是不可取的,这会造成严重的资源浪费,甚至内存溢出的情况,因为这里使用线程池来控制线程操作。
java的线程池有四种形式分别为:单线程池、缓存线程池、固定线程池、周期线程池。单线程池显然在处理效率上会存在问题,缓存线程池在处理较为复杂的业务时可能会因为单线程处理时间过长造成线程堆积内存溢出,周期线程池更适合定时任务、周期性的工作处理,相比较下,固定线程池在这个项目中最为适用。
数据接收类代码
package spartacus_services.syslog_service;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.helper.tools.iniHelper.iniOperate;
import com.data.operate.redis_dop;
import spartacus_public.method.spartacus_debug;
import spartacus_services.syslog_service.logic.syslog_logic;
/**
* @author Song
* @category spartacus 接收syslog协议的服务 数据采集模块
* @serial
*【2020年09月01日】 建立对象
*【2020年09月02日】 仅测试udp并发接收(未入库),每秒600无丢失
* 完成多线程入库模块,采用固定线程池实现
* redis采用长连接的形式,防止在多线程中重复开启连接造成的资源浪费
*/
public class syslog_service extends Thread
{
public void run()
{
final redis_dop redis = new redis_dop();
redis.Initialization();//初始化redis连接 长连接
int availProcessors = (Runtime.getRuntime().availableProcessors() * 2)+1;//计算cpu最大线程数
ExecutorService ThreadPool = Executors.newFixedThreadPool(availProcessors);;//创建一个固定线程池
spartacus_debug.writelog_txt("spartacus_datacollection[syslog_service]:service start......");
String bind_ip ="0.0.0.0"; //udp绑定的ip地址
int bind_port =514; //udp绑定的端口
DatagramChannel channel =null;
DatagramSocket socket =null;
Selector selector =null;
ByteBuffer byteBuffer = ByteBuffer.allocate (102400000) ;//设定udp缓存区大小
//#region 读取配置文件
try
{
URL sysconf = getClass().getClassLoader().getResource("config/sysconf.ini");//获取配置文件
iniOperate ini = new iniOperate(sysconf.getPath());
bind_ip = ini.getValue("syslog_config", "IP");
bind_port = Integer.parseInt(ini.getValue("syslog_config", "Port"));
}
catch (Exception e)
{
spartacus_debug.writelog_txt("spartacus_datacollection[syslog_service]:读取系统配置文件时触发catch异常,"+e.getMessage());
}
//#endregion
//#region 准备udp对象
try
{
channel = DatagramChannel.open();
channel.configureBlocking(false);//将通道设置为非阻塞模式,考虑高并发
socket = channel.socket();//创建socket监听器
InetSocketAddress bindAddress = new InetSocketAddress(bind_ip,bind_port);
socket.bind(bindAddress);
selector = Selector.open(); //采用nio选择器 可支持多通道监听,方便以后扩展
channel.register(selector, SelectionKey.OP_READ);
}
catch (Exception e)
{
spartacus_debug.writelog_txt("spartacus_datacollection[syslog_service]:绑定端口时触发catch异常,请检查端口是否已被占用,绑定ip:"+bind_ip+",端口号:"+bind_port+","+e.getMessage());
}
//#endregion
//#region 开始监听
int test_datacount =0;
while(true)
{
try
{
if(selector.select()>0)//如果存在0个以上的通道
{
Set selectedKeys = selector.selectedKeys();
Iterator iterator = selectedKeys.iterator();
while(iterator.hasNext())
{
test_datacount++;
SelectionKey sk = (SelectionKey) iterator.next();
if (sk.isReadable())
{
DatagramChannel datagramChannel = (DatagramChannel)sk.channel();
byteBuffer.clear();//清空缓冲区
final String sourceip= datagramChannel.receive(byteBuffer).toString().split(":")[0].replace("/", "");//获取发送者ip地址
byteBuffer.flip();
final String data = Charset.forName("UTF-8").decode(byteBuffer).toString();//获取日志内容
System.out.println(sourceip+" "+data+" "+test_datacount);
ThreadPool.execute(new Runnable()
{
//启用多线程入库
@Override
public void run()
{
syslog_logic sysloglogic = new syslog_logic();
sysloglogic.warehouse(sourceip,data,redis);
}
});
iterator.remove();
}
}
}
}
catch (Exception e)
{
spartacus_debug.writelog_txt("spartacus_datacollection[syslog_service]:数据接收时catch异常,"+e.getMessage());
}
}
//#endregion
}
}
多线程入库代码
package spartacus_services.syslog_service.logic;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import com.data.operate.redis_dop;
import net.sf.json.JSONObject;
import spartacus_public.method.spartacus_debug;
import spartacus_public.method.spartacus_hostinfo;
import spartacus_services.syslog_service.entity.spartacus_assets_discover;
import spartacus_services.syslog_service.entity.spartacus_receive_data;
/**
* @author Song
* @category syslog模块逻辑层
* @serial
*【2020年09月02日】 建立对象
*/
public class syslog_logic
{
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
//#region redis入库
/**
* @author Song
* @category redis入库函数
* @serial
*【2020年09月02日】 建立对象
* 首先检查资产是否存在,如果存在则将资产数据整理到入库数据中
* 如果不存在,则放入资产发现表 spartacus_assets_discover,
* 数据整理完毕后放入数据表spartacus_COMPANY_PC_data中
*/
public void warehouse(String sourceip,String data,redis_dop redis)
{
spartacus_receive_data receivedata = new spartacus_receive_data();
//#region 确定资产是否存在
String json = isexist(sourceip,redis);
if(json == null)
{
//资产不存在,需要写入资产发现列表
set_assetsdiscover(sourceip,redis);
}
else
{
//资产存在,整理到入库数据中
JSONObject assets = JSONObject.fromObject(json);//序列化资产对象
receivedata.setHostip(assets.getString("assetsip"));
receivedata.setReceivetime(System.currentTimeMillis());
receivedata.setReceivesource("syslog");
receivedata.setIdspartacus_assets(assets.getInt("idspartacus_assets"));
receivedata.setAssetsname(assets.getString("assetsname"));
receivedata.setSecuritydomain(assets.getString("securitydomain"));
receivedata.setBusinessdomain(assets.getString("businessdomain"));
receivedata.setPhysicalposition(assets.getString("physicalposition"));
receivedata.setSubnodename(assets.getString("subnodename"));
receivedata.setSubnodeip(assets.getString("subnodeip"));
}
//#endregion
//#region 组建数据并入库
receivedata.setDatacontent(data);
ArrayList<spartacus_receive_data> discoverlist = new ArrayList<spartacus_receive_data>();
discoverlist.add(receivedata);
spartacus_hostinfo hostinfo = new spartacus_hostinfo();
String host_name = hostinfo.get_hostname();
redis.redis_set_list("spartacus_"+host_name+"_data", discoverlist);
//#endregion
}
//#endregion
//#region 查询资产是否存在
/**
* @author Song
* @category 检测资产是否存在
* @serial
*【2020年09月02日】 建立对象
* 检测资产是否已存在,存在则返回资产对象,不存在则返回null
*/
private String isexist(String client_ip,redis_dop redis)
{
ArrayList<String> assetslist = redis.redis_get_list("spartacus_assets", -1);
String assetip = "\"assetsip\":\""+ client_ip +"\"";//查询条件
for (String asset : assetslist)
{
if(asset.contains(assetip))
{
return asset;
}
}
return null;
}
//#endregion
//#region 发现资产入库
/**
* @author Song
* @category 发现资产入库
* @serial
*【2020年09月02日】 建立对象
* 将clientip添加到发现列表,如果发现则表已存在则不添加
*/
private void set_assetsdiscover(String sourceip,redis_dop redis)
{
try
{
ArrayList<String> assetslist = redis.redis_get_list("spartacus_assets_discover", -1);
String assetip = "\"sourceip\":\""+ sourceip +"\"";//查询条件
for (String asset : assetslist)
{
if(asset.contains(assetip))
{
return;
}
}
spartacus_assets_discover discover = new spartacus_assets_discover();
discover.setNodeip(InetAddress.getLocalHost().getHostAddress());
discover.setSourceip(sourceip);
discover.setCreatetime(sdf.format(new Date()));
ArrayList<spartacus_assets_discover> discoverlist = new ArrayList<spartacus_assets_discover>();
discoverlist.add(discover);
redis.redis_set_list("spartacus_assets_discover", discoverlist);
}
catch (Exception e)
{
spartacus_debug.writelog_txt("spartacus_datacollection[syslog_service]:发现资产入库时catch异常,"+e.getMessage());
}
}
//#endregion
}
目前实测并发1000/秒无数据正常接收无丢失,后续考虑用loadrunner测试一下极限值。
=============================================分割线==============================================
发现一个小问题,在发现资产时,因为多线程的原因,出现了数据脏读现象,即B线程在查询发现资产中是否存在资产ip时,A线程尚未提交修改,因此 发现资产会同时添加两条同样的资产ip,解决这个问题,一般有几种选择:1. 使用数据库锁 2. 使用数据队列
考虑锁库可能带来的性能下降,这里我使用了任务队列,将发现资产由多线程运行转为单线程处理,有效避免数据脏读现象当然处理问题的方法有很多,需要根据具体问题具体分析。
package spartacus_services.syslog_service.threadclasslibrary;
import com.data.operate.redis_dop;
import spartacus_services.syslog_service.syslog_service;
import spartacus_services.syslog_service.logic.syslog_logic;
/**
* @author Song
* @category syslog模块队列处理工厂
* @serial
*【2020年09月02日】 建立对象
* 处理发现资产队列任务
*/
public class syslog_queuefactory extends Thread
{
public void run()
{
redis_dop redis = new redis_dop();
redis.Initialization();
try
{
while(true)
{
Object queue = syslog_service.syslog_queue.poll();
if(queue!=null)
{
syslog_logic logic = new syslog_logic();
logic.set_assetsdiscover(queue.toString(), redis);
}
Thread.sleep(1);
}
}
catch (Exception e)
{
}
}
}
项目我已上传到github中,感兴趣的可以下载下来看看,希望大神可以在以后多多指点。
github代码地址:https://github.com/sjctt/spartacus