kafka项目--所遇问题总结

  • MySQL数据库
  • 系统默认系统时间
  • 批量插入(mybatis)
  • 1.mybatis中手写sql
  • 2.insertList
  • 数据去重(有主键约束或唯一索引)
  • insert into ignore:
  • upsert
  • json字符串->java对象->mysql(mybatis)
  • json字符串->java对象(fastjson)
  • 日期字符串的格式化
  • null值的流转
  • MySQL中字段类型--java数据类型
  • MySQL相关数据类型说明
  • MySQL其他
  • MySQL查看建表语句
  • MySQL查看表结构
  • MySQL建表语句 字段注释
  • Kafka相关
  • Kerberos权限验证(配置)
  • Kafka常规设置
  • Kafka取数逻辑
  • Kafka取数条数问题
  • 多线程问题(空指针异常)

MySQL数据库

系统默认系统时间

建表代码如下:

create table Y_Data(
Id int PRIMARY key auto_increment,
Position VARCHAR(200) not null DEFAULT '',
CorporateName VARCHAR(500) not null DEFAULT '',
WorkingPlace VARCHAR(1000) not NULL DEFAULT '',
Salary  VARCHAR(200) not null DEFAULT '',
ReleaseTime VARCHAR(300) not null DEFAULT '',
DataTime  timestamp not NULL default CURRENT_TIMESTAMP 
)

插入时,不指定DataTime的值,则会默认使用系统时间值。
如果指定???

批量插入(mybatis)

1.mybatis中手写sql

<insert id="addPerson" parameterType="java.util.List">
				<!--这里由于指定了传入的参数是java.util.List -->
        insert into person(id,name,age)
            values
        <foreach collection ="list" item="user" separator =",">
        		<!-- 这里使用","分割不同的value值  -->
            (#{user.id}, #{user.name}, #{user.age})
        </foreach>
    </insert>

以上代码的效果相当于

insert into person(id,name,age)  values  (1,'kai',20),(2,'lvbu',20),...;
//注意在MySQL中,sql语句有长度限制,默认大小为1M
//可以找到my.ini文件(window系统下),my.conf(Linux系统下),
//在配置里面加入max_allowed_packet=64M,可以把长度限制修改为64M

2.insertList

1)pom文件中导入依赖

<dependency>
	<groupId>tk.mybatis</groupId>
	<artifactId>mapper-spring-boot-starter</artifactId>
	<version>2.1.5</version>
</dependency>

2)创建一个BaseMapper类

import tk.mybatis.mapper.common.ConditionMapper;
import tk.mybatis.mapper.common.Mapper;
import tk.mybatis.mapper.common.MySqlMapper;

public interface BaseMapper<T> extends Mapper<T>, MySqlMapper<T>, ConditionMapper<T> {
}

3)要使用的Mapper接口继承BaseMapper接口

import tk.mybatis.mapper.common.BaseMapper;

public interface PersonMapper extends BaseMapper{
}

4)PersonMapper 的实例直接调用insertList(List list)方法

personMapper.insertList(list);

数据去重(有主键约束或唯一索引)

insert into ignore:

insert ignore  into person(id,name,age)  values  (1,'kai',20),(2,'lvbu',20) ;
//insert ignore	into:如果表中有主键约束或者唯一(联合)索引约束,在插入数据时,
//如果主键或唯一索引存在了,就不会在插入后面这条数据

upsert

upsert=update or insert
MySQL中使用on duplicate key update实现:

//原始写法
insert into person04(id,name,age) values (1,'kai',20),(2,'lvbu',20) 
		on duplicate key update
		id=values(id),
		name=values(name),
		age=values(age);
<insert id="addKafkaRecordToWC_OIL_GAS_T" parameterType="java.util.List">
        insert  into WC_OIL_GAS_T(period,rebate_type,custom_code,entity_code)
        values
        <foreach collection ="list" item="user" separator =",">
            (#{user.period},#{user.rebate_type},#{user.custom_code},#{user.entity_code})
        </foreach>
        on duplicate key update
        period=values(period),
        rebate_type=values(rebate_type),
        custom_code=values(custom_code),
        entity_code=values(entity_code)
    </insert>
    //WC_OIL_GAS_T表中有一个update_date字段,
    //`UPDATE_DATE` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间'
	//即设置为系统时间,在第一次单纯插入数据时,即使不指定这个字段的值,会使用当时插入
	//时的系统时间,而在upsert时,如果已经存在了该主键或者唯一索引,且没指定update_date
	//的更新后的值,那么不会更新update_date,如果指定了呢?

如果指定了update_date的值,就会更新:

<insert id="addKafkaRecordToWC_OIL_GAS_T" parameterType="java.util.List">
	    insert  into WC_OIL_GAS_T(period,rebate_type,custom_code,entity_code,update_date)
	      values
	      <foreach collection ="list" item="user" separator =",">
	          (#{user.period},#{user.rebate_type},#{user.custom_code},#{user.entity_code},now())
	      </foreach>
	      on duplicate key update
	      period=values(period),
	      rebate_type=values(rebate_type),
	      custom_code=values(custom_code),
	      entity_code=values(entity_code),
	      update_date=values(update_date)
	</insert>
	//这里使用now()更新update_date字段,和MySQL默认系统时间格式相同
	//如  "2019-08-15 10:14:39"

json字符串->java对象->mysql(mybatis)

json字符串->java对象(fastjson)

问题:json字符串中的key值为全大写,javaBean的属性名与之对应(也全大写)

//将json对应的String -->  对象
XXXObjest obj= JSONObject.parseObject(after,XXXObjest .class);
//解析不了,会报错,用Google的Gson可以解析,但是不建议
//因为如果javaBean的属性名全大写,mybatis端用不了

解决:将key值全转为小写,javaBean的属性名与之对应(也全小写)

//转换方法  由于该json字符串结构简单,k:v,k:v,...  所以采用以下方法
public class JUtil02 {
    public static void main(String[] args) {
        String before = "{\"NAME\":\"2017\",\"PERIOD\":\"001\",\"age\":17}";
        
        JSONObject parseObj = JSON.parseObject(before);
        JSONObject afterObj = new JSONObject();

        for(Map.Entry<String,Object> entry:parseObj.entrySet()){
            afterObj.put(entry.getKey().toLowerCase(),entry.getValue());
        }
        String after=afterObj.toJSONString();
        System.out.println(after);
    }
}

日期字符串的格式化

public class MyTimeUtil {
    /**
     * @param timeStr "Jun 27, 2019 3:36:16 PM"
     * @return
     */
    public static String TimeFormat(String timeStr){
        SimpleDateFormat sdf = new SimpleDateFormat ("MMM dd, yyyy h:mm:ss a", Locale.UK);
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String format1="";
        try
        {
            //"Jun 27, 2019 3:36:16 PM" -->"Thu Jun 27 15:36:16 CST 2019"
            Date parse = sdf.parse(timeStr);
            System.out.println (sdf.parse (timeStr));
            //"Thu Jun 27 15:36:16 CST 2019"-->"2019-06-27 15:36:16"
            format1 = format.format(parse);
            System.out.println(format1);
        }
        catch (ParseException e)
        {
            e.printStackTrace();
        }
        return format1;
    }
}

null值的流转

json字符串中某些key对应的value值为’null’,解析为javaBean中的属性时,其值为null(空),插入到MySQL中的字段值为null(空)

MySQL中字段类型–java数据类型

mysql

java

varchar

String

decimal

BigDecimal

timestamp

Timestamp

MySQL相关数据类型说明

decimal(15,4)表示的是最大可达15位(包含小数),小数四位,
java用java.math.BigDecimal (或double)就可以

MySQL其他

MySQL查看建表语句

show create table tb_name

MySQL查看表结构

//2者等效
desc tb_name;
show columns from tb_name;

MySQL建表语句 字段注释

//COMMENT 关键字
CREATE TABLE tb_name(
   id  VARCHAR(30) COMMENT '用户ID',
   name  VARCHAR(30) COMMENT  '用户名称'
)

Kafka相关

Kerberos权限验证(配置)

Kerberos ???
 Kerberos 是一种网络认证协议,其设计目标是通过密钥系统为客户机 / 服务器应用程序提供强大的认证服务。
 1.文件准备:krb5.conf,jaas.conf,username.keytab
 krb5.conf的存放路径:
 windows -> c:\windows\krb5.ini
 linux -> /etc/krb5.conf
 2.在jaas.conf文件中需指定username.keytab文件的绝对路径
 3.在hosts文件中配置Kafka服务器的地址映射:
 //即使代码中用ip指定Kafka的服务器,也必须配置hosts
 C:\Windows\System32\drivers\etc

Kafka常规设置

// 这个地方填写放kafka_krb5.conf的路径  windows server环境下后缀用.ini   linux环境下后缀用.conf
        System.setProperty("java.security.krb5.conf","c:\\windows\\krb5.ini");
        // 这个地方填写放kafka_jass.conf的路径
        System.setProperty("java.security.auth.login.config","c:\\windows\\jaas.ini");//jaas.ini

        Properties props = new Properties();

        //消费的主题所在的服务器 地址:端口
        props.put("bootstrap.servers", "kafka-1:9090,kafka-2:9090,kafka-3:9090,kafka-4:9090");
        props.put("acks", "1");
        
        //组织id
        props.put("group.id", "test");
//        props.put("auto.offset.reset","earliest");//重置偏移量,需将组织id+1

        props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
        props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
        props.put("key.deserializer",
                "org.apache.kafka.common.serialization.StringDeserializer");
        props.put("value.deserializer",
                "org.apache.kafka.common.serialization.StringDeserializer");
        props.put("security.protocol", "SASL_PLAINTEXT");
        props.put("sasl.kerberos.service.name", "kafka");

        KafkaConsumer<String, String> consumer = new KafkaConsumer<String, String>(
                props);

        //消费的主题名
        consumer.subscribe(Arrays.asList("topic_name"));

        System.out.println("登录成功连接__WC_OIL_GAS_T");

注意:
1.jaas.conf和krb5.conf文件的路径可以在虚拟机参数中设置
Java启动参数(JVM参数): //参数就之间空格隔开

-Djava.security.auth.login.config=/path/jaas.conf   //jaas文件路径
-Djava.security.krb5.conf=/path/krb5.conf    //krb5.conf文件路径

kafka 线上故障解析_kafka

2.手动重置偏移量:

//重置偏移量,需将组织id+1
	props.put("group.id", "test");//组织id+1
    props.put("auto.offset.reset","earliest");//重置偏移量

Kafka取数逻辑

/* 读取数据,读取超时时间为100ms */
ConsumerRecords<String, String> records = consumer.poll(100);

//遍历kafka记录
for (ConsumerRecord<String, String> record : records) {
	//records.count() 可以返回 某次读取的数据条数
}

Kafka取数条数问题

问题:数量少于应取数量
原因:每次限制了批量插入的数据为100条,不足100条时的判断条件有误
解决:每取到一批数据,由于数据量不会很大,直接一次全部插入

多线程问题(空指针异常)

在多线程时使用@Autowired总是获取不到bean,原因是:new thread不在spring
容器中,也就无法获得spring中的bean对象。
解决方法:手动获取

解决方案

/**
*核心代码如下
*/
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

//用于在线程中获取mapper对象
@Component
public class BeanContext implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        BeanContext.applicationContext = applicationContext;
    }

    public static ApplicationContext getApplicationContext(){
        return applicationContext;
    }

    @SuppressWarnings("unchecked")
    public static <T> T getBean(String name) throws BeansException {
        return (T)applicationContext.getBean(name);
    }

    public static <T> T getBean(Class<T> clz) throws BeansException {
        return (T)applicationContext.getBean(clz);
    }
}