确认一下眼神
你是寿险研发部的人
九月月刊
寿险研发部
技术分享(一)
敏捷分享
专利挖掘
单元测试
本月记事
不忘初心
INSIST
坚持
这个世界
只有坚持不了的人
没有做不成的事
百日行动 继续前行
技术分享(二)
spring_boot 多线程及线程池配置技术及经验分享
区拓团队
1.@Scheduled配置定时任务
a.使用@Scheduled注解配置定时任务。
关于@Scheduled的参数有多种方式,可以根据自己的需求来进行选择。
@Scheduled(fixedRate=1000):上一次开始执行时间点后1秒再次执行。
@Scheduled(fixedDelay=1000):上一次执行完毕时间点后1秒再次执行。
@Scheduled(initialDelay=1000,fixedDelay=1000):第一次延迟1秒执行,然后在上一次执行完毕时间点后1秒再次执行。
@Scheduled(cron="* * * * * ?"):根据书写的cron规则执行。
关于cron表达式:
cron一共有7位,但是最后一位是年,可以留空,所以我们可以写6位:
* 第一位,表示秒,取值0-59
* 第二位,表示分,取值0-59
* 第三位,表示小时,取值0-23
* 第四位,日期天/日,取值1-31
* 第五位,日期月份,取值1-12
* 第六位,星期,取值1-7,星期一,星期二…,注:不是第1周,第二周的意思
* 另外:1表示星期天,2表示星期一。
* 第7为,年份,可以留空,取值1970-2099
* (*)星号:可以理解为每的意思,每秒,每分,每天,每月,每年…
* (?)问号:问号只能出现在日期和星期这两个位置,表示这个位置的值不确定,每天3点执行,所以第六位星期的位置,我们是不需要关注的,就是不确定的值。同时:日期和星期是两个相互排斥的元素,通过问号来表明不指定值。比如,1月10日,比如是星期1,如果在星期的位置是另指定星期二,就前后冲突矛盾了。
* (-)减号:表达一个范围,如在小时字段中使用“10-12”,则表示从10到12点,即10,11,12
* (,)逗号:表达一个列表值,如在星期字段中使用“1,2,4”,则表示星期一,星期二,星期四
* (/)斜杠:如:x/y,x是开始值,y是步长,比如在第一位(秒) 0/15就是,从0秒开始,每15秒,最后就是0,15,30,45,60 另:* /y,等同于0/y
* 0 0 3 * * ? 每天3点执行
* 0 5 3 * * ? 每天3点5分执行
* 0 5 3 ? * * 每天3点5分执行,与上面作用相同
* 0 5/10 3 * * ? 每天3点的 5分,15分,25分,35分,45分,55分这几个时间点执行
技术分享(一)
b.根据不同的业务找到分组依据。用来分线程数,多线程的分组依据。比如现在按照company分组。不采用chdrnum保单号来进行分组,因为按保单号分组,线程池太多,容易造成死锁。
c.用Future获取线程异步执行之后的结果。get是阻塞式,等待当前线程完成才返回值。用Future来判断线程池有没有满,Futere有返回值可以捕获异常之后处理异常。
d.在第7点中ThreadTaskConfig使用自定义的线程池执行异步任务,当配置了最小线程数2,最大线程数为5,等待数列为3,那么最多有8个任务在里面,如果根据分组条件需要开启12个线程,此时会报TaskRejectedException异常,抛出该异常之后,剩余的4个定时任务不再触发。所以在这里捕获这个异常,直到有线程释放出来。
@Scheduled(cron = "0 0/10 * * * ?")
public void executeAsyncTask(){
boolean areYouOk = covrpfTempService.isBeginOrEnd();
if(areYouOk){
logger.info("<=======保单险种批处理增量跑批有正在处理的任务,不做处理=====>");
return;
}
logger.info("<=======保单险种批处理增量定时任务开始=====>");
try {
List<Future<String>> listFuture = new ArrayList<Future<String>>();// 存放所有的线程,用于获取结果
List<String> list = findCompanyList();
if(list.size()!=0){
for(int i = 0; i < list.size(); i++){
String company = list.get(i);
while(true){
try {
//Future获取线程异步执行之后的结果
Future<String> future = insuranceThreadTask.doTask(company);
listFuture.add(future);
break;
} catch (TaskRejectedException e) {
logger.error("------------------------------------------------");
logger.error("----------线程池满,等待1s----------"+e);
logger.error("------------------------------------------------");
Thread.sleep(1000);
}
}
logger.info("保单险种批处理增量分公司为,{}",company);
}
//获取值。get是阻塞式,等待当前线程完成才返回值
for (Future<String> future : listFuture) {
logger.info("保单险种批处理增量future内容:"+future.get());
}
}
}catch (Exception e) {
logger.error(TaskInsurance.class+"保单险种批处理增量定时任务失败,错误原因:"+e);
}
logger.info("<=======保单险种批处理增量定时任务结束=====>");
}
2.ScheduledConfig设置定时任务的异步处理:
由于@Scheduled是同步的,如果开启了多个定时任务,下一个定时任务会等第一个定时任务处理完才开始。所以要配置@Scheduled的异步。配置如下:
@Configuration
@EnableAsync
@EnableScheduling
public class ScheduledConfig implements SchedulingConfigurer{
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
TaskScheduler taskScheduler = taskScheduler();
taskRegistrar.setTaskScheduler(taskScheduler);
}
//并行任务使用策略:多线程处理
@Bean(destroyMethod="shutdown")
public ThreadPoolTaskScheduler taskScheduler(){
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
//setPoolSize:定义一个线程池大小
scheduler.setPoolSize(10);
//setThreadNamePrefix:线程池名的前缀
scheduler.setThreadNamePrefix("scheduler-");
//setAwaitTerminationSeconds:设置线程池中任务的等待时间,如果超过这个时间还没有销毁就强制销毁,以确保应用最后能够被关闭,而不是阻塞住。
scheduler.setAwaitTerminationSeconds(60);
//setWaitForTasksToCompleteOnShutdown:设置线程池关闭的时候等待所有任务都完成再继续销毁其他的Bean
scheduler.setWaitForTasksToCompleteOnShutdown(true);
return scheduler;
}
}
3.@Async
使用@Async配置多线程。myInsurancePool即配置线程池的方法名,此处如果不写自定义线程池的方法名,会使用默认的线程池。见第3点。
4.ThreadTaskConfig使用自定义的线程池执行异步任务:
每多加一个定时任务,可以多加一个@Bean,使得每个定时任务配置不同的线程池配置,不使用同一个线程池设置。
TaskExecutor接口的实现类:
(1)SimpleAsyncTaskExecutor:这个实现不重用任何线程,或者说它每次调用都启动一个新线程。但是,它还是支持对并发总数设限,当超过线程并发总数限制时,阻塞新的调用,直到有位置被释放。不是真的线程池。
(2)SyncTaskExecutor:这个实现不会异步执行。相反,每次调用都在发起调用的线程中执行。它的主要用处是在不需要多线程的时候,比如简单的test case。
(3)ConcurrentTaskExecutor:Executor的适配类,不推荐使用。如果ThreadPoolTaskExecutor不满足要求时,才会考虑使用这个类。
(4)SimpleThreadPoolTaskExecutor:实际上是Quartz的SimpleThreadPool类的子类,它会监听Spring的生命周期回调。当你有线程池,需要在Quartz和非Quartz组件中共用时,这是它的典型用处。
(5)ThreadPoolTaskExecutor:最常用,要求jdk版本大于等于5。
(6)TimerTaskExecutor:这个实现使用一个TimerTask作为其背后的实现。它和SyncTaskExecutor的不同在于,方法调用是在一个独立的线程中进行的,虽然在那个线程中是同步的。
(7)WorkManagerTaskExecutor:这个实现使用了CommonJWorkManager作为其底层实现,是在Spring context中配置CommonJWorkManager应用的最重要的类。和SimpleThreadPoolTaskExecutor类似,这个类实现了WorkManager接口,因此可以直接作为WorkManager使用。
@Configuration
@ComponentScan("com.taikang.udp.xqchdr.*.service")
@EnableAsync
public class ThreadTaskConfig{
@Autowired
private ThreadPoolProperties threadPoolProperties;
@Bean
public Executor myInsurancePool() {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setCorePoolSize(threadPoolProperties.getCorePoolSize());//最小线程数
taskExecutor.setMaxPoolSize(threadPoolProperties.getMaxPoolSize());//最大线程数
taskExecutor.setQueueCapacity(threadPoolProperties.getQueueCapacity());//等待队列
taskExecutor.setThreadNamePrefix("TaskInsurance-");
taskExecutor.initialize();
return taskExecutor;
}
@Bean
public Executor myIntercomChdrPool() {
。。。。。。
}
。。。。。。
}
5.@Transactional
在业务层事务使用注解@Transactional。
spring事务回滚规则
指示spring事务管理器回滚一个事务的推荐方法是在当前事务的上下文内抛出异常。spring事务管理器会捕捉任何未处理的异常,然后依据规则决定是否回滚抛出异常的事务。
默认配置下,spring只有在抛出的异常为运行时unchecked异常时才回滚该事务,也就是抛出的异常为RuntimeException的子类(Errors也会导致事务回滚),而抛出checked异常则不会导致事务回滚。
可以明确的配置在抛出那些异常时回滚事务,包括checked异常。也可以明确定义那些异常抛出时不回滚事务。
还可以编程性的通过setRollbackOnly()方法来指示一个事务必须回滚,在调用完setRollbackOnly()后你所能执行的唯一操作就是回滚。
@Transactional用法
@Transactional 可以作用于接口、接口方法、类以及类方法上。当作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。
虽然 @Transactional 注解可以作用于接口、接口方法、类以及类方法上,但是 Spring 建议不要在接口或者接口方法上使用该注解,因为这只有在使用基于接口的代理时它才会生效。另外, @Transactional 注解应该只被应用到 public 方法上,这是由 Spring AOP 的本质决定的。如果你在 protected、private 或者默认可见性的方法上使用 @Transactional 注解,这将被忽略,也不会抛出任何异常。
默认情况下,只有来自外部的方法调用才会被AOP代理捕获,也就是,类内部方法调用本类内部的其他方法并不会引起事务行为,即使被调用方法使用@Transactional注解进行修饰。
注意:事务具有传递性,在某一个方法上添加@Transactional注解后,在该方法内调用的其他方法上无需再加@Transactional事务。
@Transactional
public void executeAsyncTask(List<Map> listM) {
。。。。。。业务处理
}
spring-cloud-eureka 服务注册介绍及服务下线的优化
沈仁文
Eureka Server作为一个独立的部署单元,以REST API的形式为服务实例提供了注册、管理和查询等操作。同时,Eureka Server也为我们提供了可视化的监控页面,可以直观地看到
各个Eureka Server
当前的运行状态和所有
已注册服务的情况。
技术分享(二)
1 服务提供方Service Provider
1.1 服务注册
Service Provider本质上是一个Eureka Client。它启动时,会调用服务注册方法,向Eureka Server注册自己的信息。Eureka Server会维护一个已注册服务的列表,这个列表为一个嵌套的hash map:
第一层,application name和对应的服务实例。
第二层,服务实例及其对应的注册信息,包括IP,端口号等。
当实例状态发生变化时(如自身检测认为Down的时候),也会向Eureka Server更新自己的服务状态,同时用replicateToPeers()向其它Eureka Server节点做状态同步。
1.2 续约与剔除
前面提到过,服务实例启动后,会周期性(eureka.instance.lease-renewal-interval-in-seconds)地向Eureka Server发送心跳(EurekaHttpClient.sendHeartBeat)以续约自己的信息,避免自己的注册信息被剔除。续约的方式与服务注册基本一致:首先更新自身状态,再同步到其它Peer。
如果Eureka Server在一段时间内(eureka.instance.lease-expiration-duration-in-seconds)没有接收到某个微服务节点的心跳,Eureka Server将会注销该微服务节点(自我保护模式除外)
2 服务消费方Service Consumer
Service Consumer本质上也是
一个Eureka Client(它也会向
Eureka Server注册,只是这个注册
信息无关紧要罢了)。它启动后,
会从Eureka Server上获取所有实例
的注册信息,包括IP地址、端口等,并缓存到本地。这些信息默认每30秒(eureka.client.registry-fetch-interval-seconds)更新一次。前文提到过,如果与Eureka Server通信中断,Service Consumer仍然可以通过本地缓存与Service Provider通信。
实际开发Eureka的过程中,有时会遇见Service Consumer获取到Server Provider的信息有延迟,在Eureka Wiki中有这么一段话:
All operations from Eureka client may take some time to reflect in the Eureka servers and subsequently in other Eureka clients. This is because of the caching of the payload on the eureka server which is refreshed periodically to reflect new information. Eureka clients also fetch deltas periodically. Hence, it may take up to 2 mins for changes to propagate to all Eureka clients.
最后一句话提到,服务端的更改可能需要2分钟才能传播到所有客户端,至于原因并没有介绍。这是因为Eureka有三处缓存和一处延迟造成的。
Eureka Server对注册列表进行缓存,默认时间为30s。
Eureka Client对获取到的注册信息进行缓存,默认时间为30s。
Ribbon会从上面提到的Eureka Client获取服务列表,将负载均衡后的结果缓存30s(ribbon.ServerListRefreshInterval)。
如果不是在Spring Cloud环境下使用这些组件(Eureka, Ribbon),服务启动后并不会马上向Eureka注册,而是需要等到第一次发送心跳请求时才会注册。心跳请求的发送间隔默认是30s。Spring Cloud对此做了修改,服务启动后会马上注册。
服务关闭优化
服务方正常关闭(curl -X POST http://ip:port/admin/shutdown)时,首先会调用DiscoveryClient.shutdown()从Eureka注册中心下线服务,执行中的线程会等待执行完毕。
但从上面服务消费方的说明来看,存在这样一种可能:客户端缓存的注册信息并不会立即感知服务已经下线,服务仍然会按照缓存的服务方信息进行服务的调用,此时服务方下线,会导致部分客户端调用失败,直到服务端删除错误服务方信息,或者重新从Eureka获取服务列表。最好的方式是服务方先向Eureka通知下线,等待客户端缓存失效重新获取服务列表后(2分钟),后再进行关闭。
简单的处理方法是将下线
前先将服务注册下线
(DiscoveryClient.shutdown()
开放为Restfull接口),
等等2分钟后,
再执行服务的shutdown。
package com.taikang.eba.common.rest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import com.netflix.discovery.EurekaClient;
@RestController
public class ManageController {
@Autowired(required = false)
private EurekaClient eurekaClient;
@RequestMapping(method = { RequestMethod.GET, RequestMethod.POST }, value = "/api/v1/eureka/remove")
public String removeFromEureka() {
if (eurekaClient != null) {
eurekaClient.shutdown();
}
return "remove from eureka success...";
}
}
厉不厉害!
指令与交易流水自动对账性能优化方法、装置 焦晓玉
发明背景:
年金划款指令和交易流水自动对账,由于指令类型种类多,每天网银流水量大,目前技术实现为工作人员手动导入网银,触发自动对账功能。
由于数据量级较大,自动处理耗时较长,与日常运营业务并行,导致系统压力过大,出现性能问题。
有益效果:
1、解决通知文件与交易流水自动对账性能问题,缓解系统压力;
2、避免影响正常运营业务,提高客户满意度;
专利挖掘
技术方案:
采用以下几种方法,减缓性能问题:
1、触发自动对账时点调整。
采用批次处理的方式,选择非运营高峰时点,触发指令与交易流水自动对账,一定程度上缓解系统压力。
2、去除特定指令的自动对账功能。
自动对账时要同时处理历史滞留的指令,遍历次数量大,系统压力过大。
与业务伙伴沟通,去除历史上滞留的优先级较低、对业务影响较小的指令,如个人所得税划款指令。优先将重要较高的指令加入自动对账序列,如缴费收款指令、支付指令。自动对账池子里的指令减少,一定程度上减小系统压力。
3、设置自动对账的时间区间。
系统自动对账时,当前处理的历史上所有的未对账成功的网银流水和指令,系统运行高达10年之久,历史滞留数据较多,多年前的数据对账业务运营意义较小。
设置自动对账的时间区间,预期采用仅自动对账近1年的业务运营数据,时间区间大大缩短,减轻系统受压。
4、代码层面分批处理提交
当前系统自动对账逻辑基本为交易网银按对账指令类型依次对账,当某一个指令类型完成后,但是对账未成功,则继续在同一个代码事务,与下一个指令类型的文件对账,一直继续该事务,终止条件为①流水交易与某指令对账成功;或者②遍历完所有类型的指令,仍未对账成功。单个事务耗时较长,引起系统压力。见下图左侧部分。
见右图右侧部分:代码层面按照指令类型分批次处理提交,逻辑基本如下,交易网银按照对账指令类型依次对账,当某一个指令类型遍历完成后,对账成功、或者对账不成功,结束该事务。重新发起事务,处理第二类指令类型与交易流水的对账,对账成功、或者对账不成功,结束该事务。如此循环,单个事务耗时相对减少,减轻系统压力。
敏捷开发之Scrum框架
洪梓尧
1.Scrum框架
a)Scrum是一个敏捷开发框架,是一个增量的、迭代的开发过程.。在这个框架中,整个开发周期包括若干个小的迭代周期,每个小的的迭代周期称为一个Sprint,每个Sprint的建议长度2到4周。
b)在Scrum中,使用Product Backlog来管理产品或项目的需求,product backlog是一个按照优先级的需求列表,列表条目的体现形式通常为Story。
c)Scrum的开发团队总是先开发的是对客户具有较高价值的需求。在每个Sprint中,Scrum开发团队从product Backlog中挑选最有价值的需求进行开发。Sprint中挑选的需求经过Sprint Plan Meeting上的分析、讨论和估算得到一个Sprint的task列表,我们称它为Sprint backlog 。 在每个迭代结束时,Scrum团队将交付潜在可交付的产品增量。
2.Scrum框架优势
a)专注于如何在最短的时间内实现最有价值的部分。
b)每隔一两周或者一个月,我们就可以看到实实在在的可以上线的产品。
c)团队按照优先级的高低先完成高优先级的产品功能,并自主管理,凝结了团队智慧创造出最好的方法因而提高效率。
d)能够在开发进程中不断检查,并做出相应调整,便于快速发现问题,促使团队和组织。
敏捷分享
3.Scrum组成
a)Roles:产品经理(Product Owner)、项目经理(Scrum Master)、团队成员(Team)
b)Meetings:冲刺规划会议(Sprint Plan Meeting)、每日站立会议(Scrum Standup Meeting)、冲刺评审会议(Sprint Review Meeting)、冲刺回顾会议(Sprint Retrospective Meeting)
c)工作成果:产品积压订单(Product Backlog)
d)冲刺积压订单: (Sprint Backlog)
e)燃尽图: (BurndownChart)
4.Scrum角色及职责
a)Product Owner职责:
确定产品的功能;决定发布的日期和发布内容;为产品的profitability of the product (ROI)负责;根据市场价值确定功能优先级;每个Sprint,根据需要调整功能和优先级(每个Sprint开始前调整);接受或拒绝接受开发团队的工作成果;Product Owner参与Scrum planning。
b)Scrum Master职责:
作为Team Leader和Product owner紧密地工作在一起,他可以及时地为团队成员提供帮助;保证团队资源完全可被利用并且全部是高产出的;保证各个角色及职责的良好协作;解决团队开发中的障碍;作为团队和外部的接口,屏蔽外界对团队成员的干扰;保证开发过程按计划进行,组织 Stand-up, Sprint Review and Sprint Planning meetings。
6.Scrum 物件
a)Product Backlog:
一个需求的列表;一般情况使用User Story来表示Backlog条目;理想情况每个需求项都对产品的客户或用户有价值;Backlog条目按照商业价值排列优先级;优先级由产品负责人来排列;在每个Sprint结束的时候要更新优先级的排列。
b)Sprint backlog:
Sprint backlog定义了Sprint的目标,明确了Sprint过程中具体需要完成的任务:团队成员自己挑选任务,而不是指派任务;对每一个任务,每天要更新剩余的工作量估算;每个团队成员都可以修改Sprint backlog,增加、删除或者修改任务。
c)Burn Down Chart:
燃尽图直观的反映了Sprint过程中,剩余的工作量情况,Y轴表示剩余的工作,X轴表示Sprint的时间。随着时间的消耗工作量逐渐减少,在开始的时候,由于估算上的误差或者遗漏工作量有可能呈上升态势。
c)Team职责:
一般情况人数在5-9个左右;团队要跨职能(包括开发人员、测试人员、用户界面设计师等);团队成员需要全职。(有些情况例外,比如数据库管理员);在项目向导范围内有权利做任何事情已确保达到Sprint的目标;高度的自我组织能力;向Product Owner演示产品功能;团队成员构成在sprint内不允许变化。
5.Scrum 活动
Scrum是由一个一个Sprint组成的。每个Sprint有以下活动组成:
a)召开Sprint Plan Meeting,确定这个Sprint的目标、演示日期、要完成的Backlog、Backlog的优先级等;
b)进入冲刺开发周期,在这个周期内,每天要召开Daily Stand-up Meeting;
c)整个冲刺周期结束,召开Sprint Review Meeting,将成果演示给Product Owner;
d)团队成员最后召开Sprint Retrospective Meeting,总结问题和经验.
单元测试
一、厚积
增长
短险核心、再保、新加倍保、BPM共4个团队用例数增加很多,分别为358 个、161个、102 个、99个;FE运维、规则引擎、团险外围、区拓项目、产品库、团险核心、调查系统共7个团队用例数增加较多;年金、长护、养老商城、新电销、UCM共5个团队增加用例较少。
保持
电销主数据、电销外围、NTMS、MSCC、FE新理赔、AFCS共6个团队本月用例数没有增长。
减少
无。
二、薄发
提高
再保、新加倍保、短险核心、长护、规则引擎、调查系统、产品库、FE运维、UCM、BPM、团险核心共11个团队覆盖率提升,其中再保和新加倍保两个团队覆盖率提升大于5%。
保持
电销主数据、NTMS、MSCC共3个团队覆盖率保持不变。
降低
养老商城、区拓项目、FE新理赔、AFCS、团险外围、电销外围、新电销、年金共8个团队覆盖率降低。
三、总结
1.本月总用例数为22194个,环比新增987个(上月21207个),16个团队用例数增长,6个团队用例数无增长。
2.本月覆盖率总体呈上升趋势,11个团队覆盖率提高,3个团队覆盖率无变化,8个团队覆盖率降低。
4.年金用例执行大量失败导致覆盖率降低较多;养老商城、区拓项目、FE新理赔、AFCS、团险外围、电销外围、新电销共12个团队覆盖率略微降低。
本月记事
汤总召开部门会议,宣导合规事宜,对历史需求的清理给与高度关注,并协调各部门密切合作,通力解决业务部门高度重视问题,为大个险正增长提供了强力支持。
更多精彩内容,下月继续!
本期编辑:
仔尧
红岩