工作困难杂记

报警阈值范围标记

难点

  • 一个规则的阈值有四种:通常时段,分级报警,特殊时段,节假日时段。
  • 根据优先级来命中不同时段的阈值:节假日->特殊时段->分级报警->通常时段。
  • 时段设置是根据星期加时间的数据结构。需要要判断今天是否是节假日,再要判断今天是星期几。
      {
        "end": "09:29:59",
        "start": "09:00",
        "week": 1
      },
  • 返回给前端的数据需要与横坐标一致。对应不上范围就画不出来。
{
    "alarmLevel": 4,
    "data": [
        {
            "yAxis": "100",
            "xAxis": "00:00:20"
        },
        {
            "yAxis": "120",
            "xAxis": "00:29:20"
        }
    ]
}
  • 前端数据依据不同的时间跨度,比如一天,三天,一个月。返回的数据的横坐标还不同,需要做适配。
三种格式xdata: 2022-02-14   2022-02-14 00:15   23:59:15

解决思路

  • 以30分钟为跨度,将00:00:00到23:59:59划分为48块时间段。
  • 使用map,以LocalTime为键,将优先级越高的越晚put,来覆盖优先级低的。
  • 将得到的map转化为前端需要的结构。
  • 原始频率对应不上,他们的x轴时间刻度不确定不规则。
0: "00:00:20"
1: "00:01:25"
2: "00:02:35"
3: "00:03:40"
4: "00:04:45"
5: "00:05:55"
6: "00:07:00"
7: "00:08:05"
8: "00:09:15"
9: "00:10:20"
10: "00:11:30"
  • 第一个x不断加5s直到对应上,第二个x不断减去5s直到对应上,范围缩小所以阈值准确。
while (!xData.contains(x1.format(PipeConstants.LOCAL_TIME_PATTERN)) && x1.isBefore(x2)){
    x1 = x1.plusSeconds(5L);
}
while (!xData.contains(x2.format(PipeConstants.LOCAL_TIME_PATTERN)) && x2.isAfter(x1)){
    x2 = x2.plusSeconds(-5L);
}

总结

判断节假日,判断星期几来命中不同的阈值,时间切割,并且构造返回前端要求的横坐标格式。工作复杂麻烦。解决了就感觉挺简单。一步一步做的时候解决了不少问题,尤其是思路,一开始问了前端后画的范围能否覆盖前面画的,前端说是,然后做好了才发现不是覆盖是叠加,于是总想着在这一版代码上修改,作茧自缚极其复杂。于是重写了一版换了截然不同的思路,就是切割时间块用map存储优先级高的覆盖的思路。

亮点

阈值优先级命中,时间分段,横坐标匹配,构造返回数据。

间隔流量分析曲线

难点

  • 不同时间跨度调用获取数据的接口不同
  • 数据有缺失值,需要做数据拟合,估算缺失值
  • 四个维度:一站点一时段,多站点一时段,一站点多时段,多站点多时段
  • 目标:多站点多时段
  • 返回的数据都是当前值,要计算间隔值,两个点相减。
  • 不同站点的间隔值还需要根据配置的公式做加法或者减法运算

解决思路

  • 封装一个获取数据的方法,判断调用不同的接口获取数据
  • 完成多站点一时段的间隔数据计算
  • 之后的一站点多时段和多站点多时段都基于此方法进行增强
  • x轴坐标根据开始和结束时间以及时间频率来plus获取
for (LocalDateTime x = startTime; x.isBefore(endTime); x = x.plusMinutes(interval)) {
    xData.add(x.format(PipeConstants.LOCAL_DATE_TIME_PATTERN));
}
  • 数据拟合使用org.apache.commons.math3
SimpleRegression regression = new SimpleRegression();
  • 数据之间通过配置的公式进行运算,使用大学数据结构的知识,用到两个栈:数据栈和运算符栈。
  • 空数据,异常数据处理细致。
// 统计最值过滤掉异常数据
DoubleSummaryStatistics doubleSummaryStatistics = yData.stream().filter(t -> {
    try {
        Double.parseDouble(t);
        return true;
    } catch (Exception e) {
        return false;
    }
}).mapToDouble(Double::parseDouble).summaryStatistics();

// 异常数据填充null,保持数量与相对位置不变
Map<String, List<BigDecimal>> mapByIdService = v.stream().collect(Collectors.toMap(TableDataDTO::getIdService, t -> t.getData().stream()
    .map(s -> {
        BigDecimal b;
        try {
            b = new BigDecimal(s);
        } catch (Exception e) {
            b = null;
        }
        return b;
    }).collect(Collectors.toList()), (v1, v2) -> v2));

总结

依据公式来给前端生成间隔数据曲线,主后端的工作,前端只是曲线展示。此需求独立且综合,从设计到开发全靠自己。思路架构正确,最后的结果很不错,测试的bug修改少许代码即可,健壮性和扩展性不错。
两个主要方法均由一个核心方法扩展增强,代码不冗余,修改核心代码逻辑即可无感知的修改其他两个方法。

亮点

设计思路正确,代码结构清晰,方法封装合理不冗余。很多地方留有后手,可插拔可配置,各种计算数据转换取值为null等异常情况处理细致。健壮性、可扩展性和可读性令人满意。

例如:主要方法都由一个核心方法增强而来:说明代码封装好,不冗余
return mergeCurveLineDataVO(curveLineDataVOS, query.getType());
return computeFormula(lineDataVO, formula, wiseComputePoint.getName());
例如:热插拔,fittingYData方法即为缺失值估算拟合方法,传false即可屏蔽此功能,其他不受影响。
可以写个小方法来控制是否拟合,比如查当天数据,如果拟合就会估算未来值,此时判断时间为当天的话就返回false

List<String> yData = makeYData(fittingYData(historyData, curveLineDataVO.getXData(), true), curveLineDataVO.getXData());

导入导出excel

技术选型:悟耘信息easypoi

难点

  • 导出的exce结构不复杂,用easypoi注解导出即可
  • 只是有一点:列的数量需要根据数据库查询到的数据数量动态生成
  • 反射增强类后,会一直保持增强状态,直到服务重启,于是还要在导出前调用一个恢复原始类的方法,也通过反射来恢复
  • 由于easypoi留给我们用于生成excel下拉框的接口不丰富灵活,是在excel注解里写死。于是也应用反射来动态设置该写死属性以实现下拉框
@Excel(name = "*报警等级" ,orderNum = "60",width = 10, replace = {"一级报警_1","二级报警_2","三级报警_3","四级报警_4"},addressList = true)
private Integer alarmLevel = 4;

下拉框

  • 往往导出容易,导入才是难点,导入时还得通过反射来拿到相应动态生成的属性值。

解决思路

  • 用反射增强导出VO
  • 查找资料发现反射无法动态给类增加属性,或者给属性添加注解(或许是我孤陋寡闻)
  • 于是找到一个@Excel的isColumnHidden属性,可以隐藏列,提前写好足够数量的隐藏列,根据查询数量修改注解属性实现伪动态生成excel列

亮点

反射的实战

杂记

自动注入时,idea会提示有两个,一个是该接口,一个是其熔断实现类。

Could not autowire. There is more than one bean of…

autowire
因为确实实现了该接口,产生歧义正常。

@Component
public class IotDeviceClientFallback implements IotDeviceClient {
    public IotDeviceClientFallback() {
    }

fallback
一般情况,调用时不会有问题,只是idea提示碍眼,也有很多解决办法。
2. 当使用降级框架Hystrix时,就不会有如上问题,因为他实现的是FallbackFactory接口。

@Component
@Slf4j
public class AlarmClientFallback implements FallbackFactory<AlarmClient> {

fallbackfactory
3. openfeign官方也说到了这个问题。

将 Feign 与 Spring Cloud CircuitBreaker 回退一起使用时ApplicationContext,同一类型中有多个 bean。这将导致@Autowired无法工作,因为没有一个 bean 或一个标记为主要的。为了解决这个问题,Spring Cloud OpenFeign 将所有 Feign 实例标记为@Primary,因此 Spring Framework 将知道要注入哪个 bean。在某些情况下,这可能是不可取的。要关闭此行为,请将primary属性设置@FeignClient为 false。

@Primary

  1. openfeign也提供类似Hystrix的 FallbackFactory,@FeignClient中的属性。可以访问触发回退的原因,也可以避免注入冲突。
@FeignClient(name = "testClientWithFactory", url = "http://localhost:${server.port}/",
            fallbackFactory = TestFallbackFactory.class)
    protected interface TestClientWithFactory {

        @RequestMapping(method = RequestMethod.GET, value = "/hello")
        Hello getHello();

        @RequestMapping(method = RequestMethod.GET, value = "/hellonotfound")
        String getException();

    }

    @Component
    static class TestFallbackFactory implements FallbackFactory<FallbackWithFactory> {

        @Override
        public FallbackWithFactory create(Throwable cause) {
            return new FallbackWithFactory();
        }

    }
	// 注意这里没有@Component,不会被容器加载,所以不会被注入
    static class FallbackWithFactory implements TestClientWithFactory {

        @Override
        public Hello getHello() {
            throw new NoFallbackAvailableException("Boom!", new RuntimeException());
        }

        @Override
        public String getException() {
            return "Fixed response";
        }

    }

多线程调用feign

多线程时,token无法传递给其他线程,会报401.

RequestContextHolder.setRequestAttributes(RequestContextHolder.getRequestAttributes(), true);

这句并没有完全解决问题:用线程池时,新创建的线程能传入token,线程已经存在被再次利用时还是拿不到。这时需要结合threadLocal,Feign的拦截器,来解决问题。

异常堆栈不打印

OmitStackTraceInFastThrow, jdk 1.6开始,默认server模式下开启了这个参数,意为当jvm检测到程序在重复抛一个异常,在执行若干次后会将异常吞掉,通过指定OmitStackTraceInFastThrow,可禁用这功能。

动态代理之

package reflect;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class WorkHandler implements InvocationHandler{

    //代理类中的真实对象  
    private Object obj;

    public WorkHandler() {
        // TODO Auto-generated constructor stub
    }
    //构造函数,给我们的真实对象赋值
    public WorkHandler(Object obj) {
        this.obj = obj;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //在真实的对象执行之前我们可以添加自己的操作
        System.out.println("before invoke。。。");
        Object invoke = method.invoke(obj, args);
        //在真实的对象执行之后我们可以添加自己的操作
        System.out.println("after invoke。。。");
        return invoke;
    }

}

public class Test {

    public static void main(String[] args) {
        People people = new Student();
        InvocationHandler handler = new WorkHandler(people);

        People proxy = (People) Proxy.newProxyInstance(people.getClass().getClassLoader(), people.getClass().getInterfaces(), handler);
        People p = proxy.work("写代码").work("开会").work("上课");

        System.out.println("打印返回的对象");
        System.out.println(p.getClass());

        String time = proxy.time();
        System.out.println(time);
    }
}

invoke方法,第一个参数是被代理对象People的实际代理对象,即proxy

People proxy = (People) Proxy.newProxyInstance(people.getClass().getClassLoader(), people.getClass().getInterfaces(), handler);

。第二个参数是被代理对象调用的方法。但是不知道invoke方法这几个参数怎么来的,说白了就是被代理对象调用某个方法怎么就会调用到WorkHandler 的invoke方法了,这就是jdk动态代理的庇护了吧。

threadLocal内存泄漏的理解

ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal不存在外部强引用时,key(ThreadLocal)就会被GC回收,这样就会导致ThreadLocalMap中key为null,而value还存在强引用,只用thread线程退出,value的强引用链条才会短掉,但如果当前线程未结束,这些key为null的Entry的value就会一直存在一条强引用链(红色链条)。

public class Test {
    public static void main(String[] args) {
        ThreadLocal qiangyinyong = new ThreadLocal();
        qiangyinyong.set(new Object());
        qiangyinyong = null;
    }
}

变量qiangyinyong即为ThreadLocal对象的强引用,当qiangyinyong这个变量一直指向它,它不会被回收,当qiangyinyong = null,在下次GC时,在ThreadLocalMap里的Entry的key为ThreadLocal对象,是一个弱引用,会被回收,导致Entry的key为null,value存在且无法访问。

ThreadLocal正确的使用方法:
  1. 每次使用完ThreadLocal都要调用它的remove()清除数据。
  2. 将ThreadLocal变量定义成private static,这样就一直存在ThreadLocal的强引用,也能保证任何时候都能通过ThreadLocal的弱引用访问到Entry的value值,进而清除掉。

(持续更新)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值