宽带数据清洗
1 项目简介
每个用户接入宽带基于ADSL拨号拨号开始上网,当用户输入用户名密码之后验证通过,在AAA服务器就会生成一行日志记录,记录用户当前的操作信息,当用户下线的时候,AAA服务器判断用户断开连接会继续生存一行下线的日志信息,因每一个地区都会部署很多AAA服务器,所有需要把所有AAA服务器产生的日志信息整理成用户上网信息汇总到总的服务器,以便于后期分析决策和计费。
上网计费流程如下:
2 清洗流程
编程思路:读取各个AAA服务器中的日志数据,解析出用户的上网信息,把所有的用户上网信息发送给中央服务器,中央服务器接受各个AAA服务器中的数据,并把数据写入到数据库中;
3 原始数据
AAA服务器中的日志数据如下
#briup1660|037:wKgB1660A|7|1239110900|44.211.221.247
#briup4418|037:wKgB4418A|7|1239138480|251.196.223.191
#|037:wKgB1660A|8|1239203860|44.211.221.247
#briup1247|037:wKgB1247A|7|1239106770|22.7.202.75
#briup3288|037:wKgB3288A|7|1239127180|240.144.42.68
#|037:wKgB1247A|8|1239176602|22.7.202.75
#briup8258|037:wKgB8258A|7|1239176880|90.203.198.194
#briup2391|037:wKgB2391A|7|1239118210|161.43.86.232
数据基于|拆分,数据说明如下:
- 第一项 用户名
- 第三项 7表示用户上线,8表示用户下线
- 第四项 用户上线、下线时间
- 第五项 用户上网所分配的ip
4 模块划分
基于需求,模块划分如下:
- 采集模块
- 功能
采集原始计费Log(radwtmp)文件,整理成数据清单
- 讨论
存取方式的选择、对各种原始记录情况的处理、对跨时段原始记录情况的处理
注意:
采集模块每隔一个小时采集一次,保证采集整理的数据完整,采集过程需要考虑:
- 跨时间段的问题,如1点上线3点下线
- 家庭局域网中,网络接入ip是固定的,家庭内部可以同一个账号连续上线和下线
- AAA服务器网络模块
将采集模块中采集的数据清单发送给中央处理器
- 中央处理器网络模块
接受AAA服务器中发送过来的数据
- 入库模块
将中央处理器网络模块接受的数据写入到数据库
- 备份模块
在网络或数据库发生异常的情况下将本次采集或接收到的数据清单持久存储;取出上次持久存储的数据清单
- 日志模块
将系统运行过程信息记入日志文件,以备查验
- 配置模块
实例化其他模块,降低模块之间的耦合性
5 业务实现
具体实施
1.数据清洗实体BIDR
class BIDR {
var name: String = _ //用户名
var ip: String = _ //用户登录的ip
var login_date: Timestamp = _ //用户登录的时间
var logout_date: Timestamp = _ //用户下线的时间
var sum_time: Long = _ //用户在线总时长
var NAS_ip: String = _ //NAS服务器ip地址
def this(name: String, ip: String,
login_date: Timestamp, NAS_ip: String) {
this()
this.name = name
this.ip = ip
this.login_date = login_date
this.NAS_ip = NAS_ip
}
override def toString: String = {
s"name=$name,ip=$ip,login_date=$login_date,logout_date=$logout_date,sum_time=$sum_time,NAS_ip=$NAS_ip"
}
}
2.接口的定义
采集模块:
trait Gather {
def gather():ListBuffer[BIDR]
}
AAA服务器网络模块:
trait Client {
def send(bidrs:ListBuffer[BIDR]):Unit
}
中央处理器网络模块:
trait Server {
def receiver():Unit
}
入库模块:
trait Dbstore {
def saveToDb(bidrs:ListBuffer[BIDR])
}
3.各个模块的接口的相应的实现类
采集模块实现类:
/*
Gather接口规定了采集模块所应有的方法。
当Gather执行gather()方法时,开始对AAA服务器的计费信息进行采集。
将采集的数据封装成为一个BIDR的集合返回。
*/
class GatherImpl extends Gather with Serializable {
override def gather(): ListBuffer[BIDR] = {
//采集AAA服务器的计费信息,将数据封装为BIDR集合返回。
//@return 采集封装BIDR数据的集合
var file = new File("src/data/radwtmp1")
//key是ip 值遇到7的半个BIDR对象
var map = new HashMap[String, BIDR]()
//list 存放的是完整的BIDR对象
var list = ListBuffer[BIDR]()
//读取没有配对8的map集合
var mapfile = new File("src/data/savemap")
if (mapfile.exists()) {
var ois = new ObjectInputStream(new FileInputStream(mapfile))
map = ois.readObject().asInstanceOf[mutable.HashMap[String, BIDR]]
ois.close()
}
var poniter = 0L
//读取上一次记录的位置
var file1 = new File("/src/data/filePointer")
if (file1.exists()) {
var fr = new DataInputStream(new FileInputStream(file1))
poniter = fr.readLong()
}
var random = new RandomAccessFile("src/data/radwtmp1", "rw")
random.seek(poniter)
//random.readLine()每调用一次读取一行数据,读到文件末尾是null
var str: String = ""
breakable {
while ((str = random.readLine()) != null) {
if (str==null) break()
var strs: Array[String] = str.split("[|]")
if (strs(2).equals("7")) {
var bidr = new BIDR()
bidr.name = strs(0)
bidr.ip = strs(4)
bidr.login_date = new Timestamp(strs(3).toLong)
//bidr.NAS_ip = InetAddress.getLocalHost.getHostAddress //获取本机ip
bidr.NAS_ip = "127.0.0.1" //获取本机ip
map.put(strs(4), bidr)
} else if (strs(2).equals("8")) {
if (map.contains(strs(4))) {
//判断strs(4)在map中的键是否存在
var bidr = map(strs(4))
bidr.logout_date = new Timestamp(strs(3).toLong)
bidr.sum_time = bidr.logout_date.getTime - bidr.login_date.getTime
//移除map中刚刚处理的键值
map.remove(strs(4))
list += bidr
}
}
}
}
//将end_point写入文件
var end_pint: Long = random.getFilePointer
var fw = new DataOutputStream(new FileOutputStream("src/data/filePointer"))
fw.writeLong(end_pint)
fw.flush()
fw.close()
//保存map
var oop = new ObjectOutputStream(new FileOutputStream("src/data/savemap"))
oop.writeObject(map)
oop.flush()
oop.close()
list
}
}
AAA服务器网络模块实现类:
//编程思路:读取各个服务器中的日志数据,解析出用户的上网信息,
// 把所有的用户上网信息发送给中央服务器,中央服务器接收各个AAA是服务器中的数据,并把数据写入到数据库中
/*
Client接口是采集系统网络模块客户端的规范。
Client的作用就是与服务器端进行通信,传递数据。
*/
class ClientImpl extends Client{
//客户端发送数据
override def send(bidrs: ListBuffer[BIDR]): Unit = {
//send方法用于与服务器端进行交互,发送BIDR集合至服务器端。
var client = new Socket("127.0.0.1",10000)
var os = client.getOutputStream
//将bidr对象发送给服务器端
var oos = new ObjectOutputStream(os)
oos.writeObject(bidrs)
//接收服务器数据
var is = client.getInputStream
var dis = new DataInputStream(is)
oos.flush()
os.flush()
dis.close()
is.close()
oos.close()
os.close()
client.close()
}
}
中央处理器网络模块实现类:
/*
Server接口提供了采集系统网络模块服务器端的标准。
* Server的实现类需要实现与采集体统客户端进行交互,并调用DBStore将接收的数据持久化。
* 当Server的实现类执行revicer()方法时,Server开始监听端口。
* 当调用Server的shutdown方法时,Server将安全的停止服务。
*/
class ServerImpl extends Server {
override def receiver(): Unit = {
//该方法用于启动这个Server服务,开始接收客户端传递的信息,
// 并且调用DBStore将接收的数据持久化。
var server = new ServerSocket(10000)
while (true) {
var socket = server.accept()
var is = socket.getInputStream
var ois = new ObjectInputStream(is)
//接收到客户端发送过来的bidr对象
var list: ListBuffer[BIDR] = ois.readObject().asInstanceOf[ListBuffer[BIDR]]
//调用DBStore将接收的数据持久化。
val dbs = new DbstoreImpl
dbs.saveToDb(list)
//给客户端发送消息
var os = socket.getOutputStream
var dos = new DataOutputStream(os)
dos.writeInt(list.size)
dos.flush()
dos.close()
}
}
}
入库模块实现类:
//DBStore提供了入库模块的规范。
// 该接口的实现类将BIDR集合持久化。
class DbstoreImpl extends Dbstore {
override def saveToDb(bidrs: ListBuffer[BIDR]): Unit = {
//将BIDR集合进行持久化 。
//@param collection 需要储存的BIDR集合
//1.注册驱动
Class.forName("com.mysql.jdbc.Driver")
//2.基本信息
val url = "jdbc:mysql://localhost:3306/dbstore?useUnicode=true&characterEncoding=utf8"
val username = "root"
val password = "root"
//3.获取连接数据库
val conn = DriverManager.getConnection(url, username, password)
//4.将传入的bidr集合对象中的元素写入到数据库中
var sql = "insert into bidr(name,ip,login_date,logout_date,sum_time,NAS_ip) values(?,?,?,?,?,?) "
var ps = conn.prepareStatement(sql)
for( i <- bidrs){
ps.setString(1,i.name)
ps.setString(2,i.ip)
ps.setString(3,i.login_date.toString)
ps.setString(4,i.logout_date.toString)
ps.setLong(5,i.sum_time)
ps.setString(6,i.NAS_ip)
//将当前行数据添加到缓存中
ps.addBatch()
//执行缓存中所有数据的写入
ps.executeBatch()
}
ps.executeBatch()
//关闭资源
ps.close()
conn.close()
}
}
6 测试
中央处理器:(作为服务器接受数据,并把数据写入到数据库中)
object CenterServer {
def main(args: Array[String]): Unit = {
var center=new ServerImpl()
center.receiver()
}
}
AAA服务器:(作为客户端,将采集到的数据发送给服务器)
object AAA {
def main(args: Array[String]): Unit = {
//采集数据
var g=new GatherImpl()
var bidrs:ListBuffer[BIDR]=g.gather();
//bidrs.foreach(println(_))
//发送数据给中央处理器
var c=new ClientImpl()
c.send(bidrs)
}
}
结果展示:
入库总行数:
入库数据前10行:
该项目还可以继续补充完成其他模块,如备份模块、日志模块、配置模块。