002-日常总结

1. 线程与性能相关

类别

总结

性能问题排查步骤

1. CPU占比过高:

1.1 top命令查看CPU占比高的进程,然后top -Hp pid查看异常线程;

    1.2 jstack pid查看进程的各线程栈信息,找到异常线程的堆栈信息,分析堆栈调用的类、方法;

2. GC相关信息:

    jstat 命令,查看新生代、老年代GC的次数、时间、以及内存使用等信息;

3. 内存数据分析:

    jmap + jhat 命令,dump内存文件到本地,然后MAT工具分析内存中对象占比,及产生的源头。

    jmap -dump : 生成Java堆转储快照

    jmap -heap:显示Java堆详细信息

    jmap -histo:like |more:显示堆中对象统计信息

  

定时任务与线程池

定时任务不适用线程池模型,定时任务触发时刻瞬时压力大,其他时间基本不执行;使用线程池模型则在触发时线程池瞬间被塞满任务,且容易出现拒绝的情况;但是处理完之后几乎全部空闲,浪费线程资源;

一般根据任务触发时预计处理数据量来估计使用方式,比如一万条数据以内,一次性取出全部数据,然后循环串行执行完;如果数据量较大,则考虑分批次每次取1000条数据处理;

druid与dubbo线程池

获取driud conn导致dubbo线程池被占满:查看jstack发现线程状态waiting,且卡在获取连接处。查看源码定位到breakAfterAcquireFailure设置为true,当获取连接失败时当前线程会退出,就会出现栈中没有其他线程的情况;

解决办法:1.设置获取连接的超时时间,2. breakAfterAcquireFailure不设置为true;

线程池中线程的执行

线程池在调度的时候执行的Runnable的run()方法,而不是start()方法。如果是start()方法,那么当往线程池仍过多的任务时,线程池会额外创建线程,同是每个任务都是异步执行的,所有的任务一下子就执行完了。如果是要通过线程池调度,那么只能使用同步阻塞的run()方法调用才满足这个要求。

start()方法与run()方法

1.  Thread.start() 方法用来异步启动一个线程,然后主线程立刻返回。该启动的线程不会马上运行,会放到等待队列中等待 CPU 调度,只有线程真正被 CPU 调度时才会调用 run() 方法执行。

2. 如果直接调用 run() 方法,那就等于调用了一个普通的同步方法,达不到多线程运行的异步执行。

线程池的hystrix隔离

 

信号量的hystrix隔离

 
  

 

2. 接口对接

类别

总结

对外SDK设计

1. SDK接口参数尽量设计成通用参数,不用频繁升级,如参数放在context中,业务方动态扩展;

 

2. SDK参数灵活设置,则后台接入层需要校验参数,包括key是否存在,value是否为null,类型是否匹配,能否有默认值等

 

3. SDK中所有入参、出参一定要设置序列号,否则容易出现新老包因序列化导致的不兼容问题,或使用jsom格式、context格式等;

 

4. SDK升级,老版本一定要回归测试,业务方比一定也升级SDK,确保SDK能向下兼容;

 

5. SDK中最好包含调用者信息,如token;便于后台服务统计和监控调用者请求量、QPS,甚至设计降级和熔断策略;

  

前后端接口对接

对外提供接口升级一定要确保兼容使用老版本的调用方,入参和出参均需要序列化,建议使用json格式传递参数;

 

1. 所有需要处理的数据(包括转换、赋值、判断等)一定要判断是否为空,避免出现空指针异常;

 

2. 数据转换时一定校验为空的情况,避免传入数据为空导致转换时报异常;

 

3. 枚举值空值处理,基本类型与包装类型的空处理,实体Bean的定义采用包装类型,避免基本类型的自动初始化赋值;

 

4. 时间、日期等在后端封装成String后返回至前端,避免传输时丢失UTC时间;

 

5. long型数据传至前端时最好转换成string(16位以上数据可能会丢失精度);

 

6. post请求以json格式传参,get请求以form格式传参,与前端规范保持一致;

 

7. 未查询到数据返回code值200,返回信息说明未查询到数据,注意与异常值-1区分开;

 

8. 返回前端的状态值最好带上desc,例如(0571,杭州),(1,已支付)等;

 

9. 对外提供服务要打印接口请求参数和返回结果,便于问题排查,参数map不建议用json格式打印,会丢失value为null的key;

  

定时任务相关

9. 定时任务预留手动触发接口,万一线上数据跑错后可以重新跑;另外统计数据一定要做到幂等,多次统计结果保持正确;

 

10. 文件操作(excel、txt等)一定要注意数据量的大小,必须有限定条件限制数据量,但数据量太大时可能会导致处理时间过长、内存被占满等问题,或者是当后台还未处理完,但http请求认为已经超时后重新请求,导致重复处理,使问题更严重。最好还能限制并发操作,避免同时请求导致内存被耗光。

 

props.load(new FileInputStream("db.properties")); 是读取当前目录的db.properties文件

 

getClass.getResourceAsStream("db.properties"); 是读取当前类所在位置一起的db.properties文件

 

getClass.getResourceAsStream("/db.properties"); 是读取ClassPath的根的db.properties文件,注意ClassPath如果是多个路径或者jar文件的,只要在任意一个路径目录下或者jar文件里的根下都可以,如果存在于多个路径下的话,按照ClassPath中的先后顺序,使用先找到的,其余忽略。

  

其他

1. SpringBoot支持动态的读取文件,扩展接口为:EnvironmentPostProcessor;

 

2.  MongoSocketOpenException异常,原因是springboot自动配置了支持mongodb。在启动springboot时会自动实例化一个mongo实例,需要禁用自动配置。

增加 @SpringBootApplication ( exclude = MongoAutoConfiguration.class)配置即可;

 

3. 手动指定外部logback加载路径,添加配置项:logging.config=D:/logs/mq-demo/logback.xml

 

4. String操作类的replace和replaceAll的区别(replace替换字符串命中,replaceAll替换正则表达式命中)

 

5.  如果在try语句里有return语句,finally语句还是会执行。它会在把控制权转移到该方法的调用者或者构造器前执行finally语句。也就是说,使用return语句把控制权转移给其他的方法前会执行finally语句。

es查询深度分页问题

1. es搜索原理:当执行查询10000~10100条记录时,es节点将请求广播到各分片,各分片查前10100条记录。然后查询结果返回给es节点,es节点都结果合并整合后,取相应数据返回给client;

2. 问题:如果此方式查1000000~1000100条记录时,cpu、内存、网络io可能都会出现问题;所以会有修改index.max_result_window 值的方式来限制from/size的执行范围,即不能超过设定值,否则报错;

3. 更高效的深度分页方式:Scroll 或者SearchAfter。scroll常用于离线的读取深度分页数据,SearchAfter用于实时和高并发场景,执行原理类似与传统数据库里的cursors(游标),如果是不关心顺序的场景,可以考虑使用scroll-scan。

4. Scroll方式在一次查询请求后维护一个索引快照的search context,然后每次再去批量的读取数据,效率比较高,缺点是维护search context需要占用很多资源;searchAfter的方式通过维护一个实时游标来避免scroll的缺点,可以用于实时请求和高并发场景,缺点是不能够随机跳转分页,只能是一页一页的向后翻,并且需要至少指定一个唯一不重复字段来排序。

  

线上空指针问题排查

1. 线上频繁报空指针异常,error日志打印过多,不再打印堆栈信息,不知道去查看最开始的日志信息,结果需要手动审查代码,难以定位问题;

2. 请求参数打印时,json打印map参数不打印值为null的key,导致本地测试map参数和线上不一致,无法重现问题;

3. map取值校验时,判断key是否存在后,取值要先做空判断再转类型,彻底避免空指针异常的出现;

 

 

3.  myBatis使用细节

描述

解释

错误:with a primitive return type (int).

返回值类型为int,但是查询出来的结果有空值(null),无法转换成基本类型。包括char,long,short都有可能。

解决方式:1. select ifnull(value, 0) from xxx;结果为空时默认返回返回值0。2. 如果where 条件命中不上,select语句则不会执行(where语句比select先执行),返回值仍为null。此情况一是使用 select case when then语句。二是修改Mapper接口中的返回值类型,改用包装类(Integer,Short,Long等),并在业务逻辑中处理null值。

 

select CASE

WHEN (select provinceID from kdmc_t_province where name =#{province})is null

THEN 0

ELSE (select provinceID from kdmc_t_province where name =#{province})

END;

resultType和resultMap区别

查询出来的结果集只有一行且一列,可以使用简单类型进行输出映射。通过resultType映射。

如果查询结果复杂,需要封装成bean,则适合使用resultMap,定义映射规则。

动态传入表名,例如数据记录日表

先区分#{}和${}的却别,简略来讲${}单纯填充字段,不做预编译;#{}有预编译过程,可以防止SQL注入。(#{} 是编译好SQL语句再取值,${} 这是取值以后再去编译SQL语句)

那么,动态传入表名则需要使用${}方式,例如select * from ${prefix}ACT_HI_PROCINST where PROC_INST_ID_ = #{processInstanceId},另外SQL语句配置为statementType="STATEMENT",即不进行预编译。

mySQL时间选择

TIMESTAMP 和时区相关,更能反映当前时间。当插入日期时,会先转换为本地时区后再存放;当查询日期时,会将日期转换为本地时区后再显示。所以不同时区的人看到的同一时间是不一样的。注:mySQL中timestamp范围从1970年~2038年。

多参数入参

单个参数配置文件映射时能自动识别,多参数则需要指定参数,一是通过Map传递,二是封装成bean,三是在接口通过注解标明。例如:

Integer getAccountPermissionValue(

@Param("publicId") long publicId,

@Param("prvCode") String prvCode

) throws Exception;

随机数生成

private String getFixLenthString(int strLength) {

Random rm = new Random();

    // 获得随机数

    double pross = (1 + rm.nextDouble()) * Math.pow(10, strLength);

    // 将获得的获得随机数转化为字符串

    String fixLenthString = String.valueOf(pross);

    // 返回固定的长度的随机数

    return fixLenthString.substring(2, strLength + 2);

}

insert返回主键id

1. xml文件中insert语句配置useGeneratedKeys="true" keyProperty="id" keyColumn="id"即可,对于insert ignore into,insert into on duplicate key update不生效。

2. param需以bean的方式传入,且在接口通过bean.getId()获取主键id,sql返回结果只表示执行的状态(插入成功的行数,即1)。

resultMap示例

<resultMap id="pubDownMaterial" type="com.cmb.zh.pms.mapper.pojo.message.PubDownImageText" autoMapping="true">       

<id column="id" property="id" jdbcType="INTEGER" />       

<result column="msg_id" property="msgId" jdbcType="INTEGER" />       

<result column="image_text_id" property="imageTextId" jdbcType="INTEGER" />       

<result column="material_id" property="materialId" jdbcType="INTEGER" />       

<result column="title" property="title" jdbcType="VARCHAR"/>       

<result column="content" property="content" jdbcType="VARCHAR"/>       

<result column="author" property="author" jdbcType="VARCHAR"/>       

<result column="summary" property="summary" jdbcType="VARCHAR"/>       

<result column="res_id" property="resId" jdbcType="INTEGER" />       

<result column="click_url" property="clickUrl" jdbcType="VARCHAR"/>       

<result column="show_order" property="showOrder" jdbcType="SMALLINT" />       

<result column="title_pic_show" property="showTitlePic" jdbcType="TINYINT" />       

<result column="link_url" property="linkUrl" jdbcType="VARCHAR"/>       

<result column="comment_flag" property="commentFlag" jdbcType="CHAR"/>      

<result column="res_create_time" property="resCreateTime" jdbcType="TIMESTAMP"/>   

</resultMap>

批量插入示例

<insert id ="batchSavePubDownMsg" parameterType="java.util.List" >

<selectKey resultType="java.lang.Integer" keyProperty="id" order="AFTER">

SELECT LAST_INSERT_ID()

</selectKey>

insert into redeem_code (

bach_id, code, type, facevalue, create_user, create_time

)

values

<foreach collection ="list" item="item" index= "index" separator =",">

(

#{item.batchId},

#{item.code},

#{item.type},

#{item.facevalue},

#{item.createUser},

#{item.createTime}

)           

</foreach >

</insert>

建表脚本示例

CREATE TABLE `risk_platform`.`rp_rule_center_rule` (

`id` bigint(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增主键',

`scene_no` bigint(10) NOT NULL COMMENT '场景No',

`active_type` tinyint(4) NOT NULL COMMENT '触发类别,1:实时触发,2:定时触发',

`event_no` bigint(10) NOT NULL COMMENT '事件No',

`rule_code` varchar(50) NOT NULL COMMENT '规则代号',

`rule_name` varchar(50) NOT NULL COMMENT '规则名称',

`rule_desc` varchar(2048) DEFAULT NULL COMMENT '规则描述',

`biz_lines` varchar(50) DEFAULT NULL COMMENT '适用业务线',

`run_type` tinyint(4) NOT NULL DEFAULT '0' COMMENT '运行状态, 0:未启用, 1:运行, 2:观察',

`start_active_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '有效时间',

`end_active_time` timestamp NOT NULL DEFAULT '2038-01-01 00:00:00' COMMENT '有效时间',

`condition_exps` varchar(2048) NOT NULL COMMENT '条件表达式',

`punish_exps` varchar(2048) NOT NULL COMMENT '处罚表达式',

`contents` varchar(2048) NOT NULL COMMENT '完整的规则表达式',

`related_features` varchar(2048) NOT NULL COMMENT '规则关联特征列表',

`level` tinyint(4) NOT NULL DEFAULT '0' COMMENT '优先级',

`deleted` tinyint(4) NOT NULL DEFAULT '0' COMMENT '是否删除标识, 0:未删除, 1:已删除',

`remark` varchar(2048) DEFAULT NULL COMMENT '备注',

`md5` varchar(50) NOT NULL COMMENT 'MD5签名',

`create_user` varchar(50) DEFAULT NULL COMMENT '创建者',

`update_user` varchar(50) DEFAULT NULL COMMENT '修改者',

`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',

`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',

PRIMARY KEY (`id`),

UNIQUE KEY `uk_rce_rule_code` (`rule_code`) USING BTREE COMMENT '特征编码唯一约束',

KEY `idx_create_time` (`create_time`) USING BTREE,

KEY `idx_update_time` (`update_time`) USING BTREE

) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin ROW_FORMAT=COMPACT COMMENT='规则中心_规则表';

表变更sql语句

 

ALTER TABLE risk_platform.rp_rule_center_rule ADD rule_type tinyint(4) NOT NULL DEFAULT '0' COMMENT '策略类型, 0:表达式策略, 1:积分策略';

ALTER TABLE risk_platform.rp_rule_center_rule_condition MODIFY true_value varchar(30) DEFAULT NULL COMMENT '正确结果集:true用1表示,其他数值表示符合条件时积分分数';

ALTER TABLE risk_platform.rp_rule_center_rule_condition MODIFY false_value varchar(30) DEFAULT NULL COMMENT '正确结果集:true用1表示,其他数值表示符合条件时积分分数';

 

ALTER TABLE risk_platform.rp_rule_center_rule_punish ADD left_threshold varchar(50) DEFAULT NULL COMMENT '左阈值';

ALTER TABLE risk_platform.rp_rule_center_rule_punish ADD right_threshold varchar(50) DEFAULT NULL COMMENT '左阈值';

 

ALTER TABLE risk_center.rc_risk_config MODIFY COLUMN value text COMMENT '参数值' AFTER cn_name;

插入数据脚本

INSERT INTO `user_system`.`ac_physics_system`

(`physics_system_name`, `host`, `gray_host`, `del_flag`)

VALUES ( 'risk-platform', 'https://center.caocaokeji.cn', '', 0);

 

INSERT INTO `risk_platform`.`rp_punish_action` (`code`, `name`, `type`, `description`, `create_time`, `update_time`, `deleted`, `remark`, `priority`) VALUES ( '701000', 'REGISTER_CLUSTER', NULL, '注册账号聚集', '2020-02-20 15:20:28', '2020-03-02 14:47:21', '0', NULL, '30');

INSERT INTO `risk_platform`.`rp_punish_action` (`code`, `name`, `type`, `description`, `create_time`, `update_time`, `deleted`, `remark`, `priority`) VALUES ( '702000', 'REGISTER_FREQUENTLY', NULL, '高频注册', '2020-02-20 15:20:59', '2020-03-02 14:47:40', '0', NULL, '20');

INSERT INTO `risk_platform`.`rp_punish_action` (`code`, `name`, `type`, `description`, `create_time`, `update_time`, `deleted`, `remark`, `priority`) VALUES ( '704000', 'REGISTER_CLIENT_ABNORMAL', NULL, '注册终端异常', '2020-02-20 15:21:43', '2020-03-02 14:47:42', '0', NULL, '10');

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值