Flink测试程序以及发布到服务器

测试程序:从kafka中读取车辆定位数据,进行计算过滤,将超出指定电子围栏区域的定位数据保存到mysql,以便于后续进一步处理。

该测试程序源自业务部门的需求:对监控的车辆行驶轨迹进行实时分析,如果超出电子围栏区域则需要及时报警处理。由于这个场景的实时性要求高,因此比较适合用flink来直接处理。如果先将车辆定位数据保存到doris,再进行定时计算,则在时效性上可能就无法保证了。

由于flink目前只支持java跟scala,为了写程序,被迫学起了java。本例使用idea来编写(题外话:java跟c#的很多概念、甚至语法都已经非常接近了,写起代码来并不费劲;费劲是需要理解idea的配置管理、包引用、编译发布环境等这些概念,对于习惯了使用宇宙第一ide的vs新手来说,确实非常头疼(╥﹏╥))。

 

项目的pom配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>catsti.flink</groupId>
    <artifactId>kafka-compute-mysql</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <flink.version>1.13.2</flink.version>
        <java.version>1.8</java.version>
        <scala.binary.version>2.11</scala.binary.version>
        <maven.compiler.source>${java.version}</maven.compiler.source>
        <maven.compiler.target>${java.version}</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-java</artifactId>
            <version>${flink.version}</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-streaming-java_${scala.binary.version}</artifactId>
            <version>${flink.version}</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-clients_${scala.binary.version}</artifactId>
            <version>${flink.version}</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.34</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-dbcp2</artifactId>
            <version>2.1.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-connector-kafka_2.11</artifactId>
            <version>1.11.3</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.28</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-simple</artifactId>
            <version>1.7.25</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>
</project>

 

主类代码如下:

import comm.JsonHelper;
import comm.PolygonUtil;
import entity.p_cargis;
import org.apache.flink.api.common.functions.FilterFunction;
import org.apache.flink.api.common.restartstrategy.RestartStrategies;
import org.apache.flink.api.common.serialization.SimpleStringSchema;
import org.apache.flink.api.common.time.Time;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumer;

import java.awt.geom.Point2D;
import java.util.Properties;
import java.util.concurrent.TimeUnit;

public class DataCompute {
    public static void main(String[] args) throws Exception{

        final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

        //设置重启机制
        env.setRestartStrategy(RestartStrategies.fixedDelayRestart(3, Time.of(10, TimeUnit.SECONDS)));//每10秒重启一次,尝试3次

        //设置kafka属性
        Properties props = new Properties();
        props.setProperty("bootstrap.servers", "172.18.88.33:9092");
        props.setProperty("group.id","g1");
        props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
        props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
        props.put("auto.offset.reset", "latest");

        //读取kafka消息
        SingleOutputStreamOperator<p_cargis> pgis = env.addSource(new FlinkKafkaConsumer<>(
                "flinktest1",        //需要读取的kafka消息主题
                new SimpleStringSchema(),   //
                props)).map(string -> JsonHelper.getJsonToBean(string, p_cargis.class));//将kafka消息(json格式)转化为p_cargis实体

        //按照车牌号分组
        pgis.keyBy(p_cargis::getCph);

        //对数据进行过滤
        SingleOutputStreamOperator<p_cargis> filter = pgis.filter(new FilterFunction<p_cargis>() {
            @Override
            public boolean filter(p_cargis p_cargis) throws Exception {
                //判断数据是否在指定区域内
                Point2D.Double point = new Point2D.Double();
                point.x = p_cargis.lat;
                point.y = p_cargis.lon;
                return !PolygonUtil.isInPolygon(point,GetPolygonRule.getTestPolygonRule());
            }
        });

        filter.print();

        //将过滤出来的数据保存到mysql
        filter.addSink(new SinkToMySql()).name("Save to Mysql");

        //启动执行
        env.execute("测试程序(读kafka,计算车辆定位数据是否超出范围,将超出数据写入mysql)");
    }
}

多边形工具类(用于判定指定点位是否在指定的区域内):

package comm;

import java.awt.geom.Point2D;
import java.util.List;

public class PolygonUtil {
    /**
     * 地球半径
     */
    private static double EARTH_RADIUS = 6378138.0;

    private static double rad(double d)
    {
        return d * Math.PI / 180.0;
    }

    /**
     * 计算是否在圆上(单位/千米)
     *
     * @Title: GetDistance
     * @Description: TODO()
     * @param radius 半径
     * @param lat1  纬度
     * @param lng1  经度
     * @return
     * @return double
     * @throws
     */
     public static boolean isInCircle(double radius,double lat1, double lng1, double lat2, double lng2)
     {
         double radLat1 = rad(lat1);
         double radLat2 = rad(lat2);
         double a = radLat1 - radLat2;
         double b = rad(lng1) - rad(lng2);
         double s = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a/2),2) + Math.cos(radLat1)*Math.cos(radLat2)*Math.pow(Math.sin(b/2),2)));
         s = s * EARTH_RADIUS;
         s = Math.round(s * 10000) / 10000;
         if(s > radius) {//不在圆上
             return false;
         }else {
             return true;
         }
     }

    /**
     * 是否在矩形区域内
     * @Title: isInArea
     * @Description: TODO()
     * @param lat 测试点经度
     * @param lng 测试点纬度
     * @param minLat 纬度范围限制1
     * @param maxLat 纬度范围限制2
     * @param minLng 经度限制范围1
     * @param maxLng 经度范围限制2
     * @return
     * @return boolean
     * @throws
     */
    public static boolean isInRectangleArea(double lat,double lng,double minLat,
                                            double maxLat,double minLng,double maxLng){
        if(isInRange(lat, minLat, maxLat)){//如果在纬度的范围内
            if(minLng*maxLng>0){
                if(isInRange(lng, minLng, maxLng)){
                    return true;
                }else {
                    return false;
                }
            }else {
                if(Math.abs(minLng)+Math.abs(maxLng)<180){
                    if(isInRange(lng, minLng, maxLng)){
                        return true;
                    }else {
                        return false;
                    }
                }else{
                    double left = Math.max(minLng, maxLng);
                    double right = Math.min(minLng, maxLng);
                    if(isInRange(lng, left, 180)||isInRange(lng, right,-180)){
                        return true;
                    }else {
                        return false;
                    }
                }
            }
        }else{
            return false;
        }
    }

    /**
     * 判断是否在经纬度范围内
     * @Title: isInRange
     * @Description: TODO()
     * @param point
     * @param left
     * @param right
     * @return
     * @return boolean
     * @throws
     */
    public static boolean isInRange(double point, double left,double right){
        if(point>=Math.min(left, right)&&point<=Math.max(left, right)){
            return true;
        }else {
            return false;
        }
    }

    /**
     * 判断点是否在多边形内
     * @Title: IsPointInPoly
     * @Description: TODO()
     * @param point 测试点
     * @param pts 多边形的点
     * @return
     * @return boolean
     * @throws
     */
    public static boolean isInPolygon(Point2D.Double point, List<Point2D.Double> pts){

        int N = pts.size();
        boolean boundOrVertex = true;
        int intersectCount = 0;//交叉点数量
        double precision = 2e-10; //浮点类型计算时候与0比较时候的容差
        Point2D.Double p1, p2;//临近顶点
        Point2D.Double p = point; //当前点

        p1 = pts.get(0);
        for(int i = 1; i <= N; ++i){
            if(p.equals(p1)){
                return boundOrVertex;
            }

            p2 = pts.get(i % N);
            if(p.x < Math.min(p1.x, p2.x) || p.x > Math.max(p1.x, p2.x)){
                p1 = p2;
                continue;
            }

            //射线穿过算法
            if(p.x > Math.min(p1.x, p2.x) && p.x < Math.max(p1.x, p2.x)){
                if(p.y <= Math.max(p1.y, p2.y)){
                    if(p1.x == p2.x && p.y >= Math.min(p1.y, p2.y)){
                        return boundOrVertex;
                    }

                    if(p1.y == p2.y){
                        if(p1.y == p.y){
                            return boundOrVertex;
                        }else{
                            ++intersectCount;
                        }
                    }else{
                        double xinters = (p.x - p1.x) * (p2.y - p1.y) / (p2.x - p1.x) + p1.y;
                        if(Math.abs(p.y - xinters) < precision){
                            return boundOrVertex;
                        }

                        if(p.y < xinters){
                            ++intersectCount;
                        }
                    }
                }
            }else{
                if(p.x == p2.x && p.y <= p2.y){
                    Point2D.Double p3 = pts.get((i+1) % N);
                    if(p.x >= Math.min(p1.x, p3.x) && p.x <= Math.max(p1.x, p3.x)){
                        ++intersectCount;
                    }else{
                        intersectCount += 2;
                    }
                }
            }
            p1 = p2;
        }
        if(intersectCount % 2 == 0){//偶数在多边形外
            return false;
        } else { //奇数在多边形内
            return true;
        }
    }
}

车辆定位数据实体类:

package entity;

public class p_cargis {
    public String cph;//车牌号
    public int cpys;//车牌颜色
    public String dt;//定位数据生成时间
    public double lat;//经度
    public double lon;//纬度
    public int xssd;//行驶速度


    public p_cargis() {
    }

    public String getCph(){
        return cph;
    }

    public int getCpys(){
        return cpys;
    }

    public String getDt() {
        return dt;
    }

    public double getLat() {
        return lat;
    }

    public double getLon() {
        return lon;
    }

    public int getXssd() {
        return xssd;
    }

    public void setCph(String cph) {
        this.cph = cph;
    }

    public void setCpys(int cpys) {
        this.cpys = cpys;
    }

    public void setDt(String dt) {
        this.dt = dt;
    }

    public void setLat(double lat) {
        this.lat = lat;
    }

    public void setLon(double lon) {
        this.lon = lon;
    }

    public void setXssd(int xssd) {
        this.xssd = xssd;
    }
}

 

将过滤出来的数据写入mysql,通过SinkToMySql类完成:

import entity.p_cargis;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.streaming.api.functions.sink.RichSinkFunction;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;


/**
 * 将结果保存到数据库
 * */
public class SinkToMySql extends RichSinkFunction<p_cargis> {
    PreparedStatement ps;
    private Connection connection;

    /**
     * open() 方法中建立连接,这样不用每次 invoke 的时候都要建立连接和释放连接
     *
     * @param parameters
     * @throws Exception
     */
    @Override
    public void open(Configuration parameters) throws Exception {
        super.open(parameters);
        connection = getConnection();
        String sql = "insert into T_ResultData(datatype, dataname, datavalue) values(?, ?, ?);";
        ps = this.connection.prepareStatement(sql);
    }

    @Override
    public void close() throws Exception {
        super.close();
        //关闭连接和释放资源
        if (connection != null) {
            connection.close();
        }
        if (ps != null) {
            ps.close();
        }
    }

    /**
     * 每条数据的插入都要调用一次 invoke() 方法
     *
     * @param value
     * @param context
     * @throws Exception
     */
    @Override
    public void invoke(p_cargis value, Context context) throws Exception {
        insertDB();
    }

    private void insertDB(p_cargis value) throws Exception {
        //组装数据,执行插入操作
        ps.setInt(1, value.getCpys());
        ps.setString(2, value.getCph());
        ps.setString(3, String.valueOf(value.getLat()) + "-" + String.valueOf(value.getLon()));
        ps.executeUpdate();
    }

    private static Connection getConnection() {
        Connection con = null;
        try {
            Class.forName("com.mysql.jdbc.Driver");
            con = DriverManager.getConnection("jdbc:mysql://172.16.170.74:3306/source_data?useUnicode=true&characterEncoding=UTF-8", "username", "password");
        } catch (Exception e) {
            System.out.println("-----------mysql get connection has exception , msg = "+ e.getMessage());
        }
        return con;
    }
}

 

整个项目结构如下:

 

 

编译发布生成jar包,然后到flink服务器上add上去:

 

 之后submit就可以运行起来了:

 

 使用一个测试程序,向kafka里写测试数据:

 

 很快mysql里产生了经过flink代码过滤出来的预警数据(不在指定区域内的车辆定位数据):

 

posted @ 2021-09-30 12:05  寒冰之光  阅读(514)  评论(0编辑  收藏  举报