标签: java

ElasticSearch使用script更新文档失败问题排查

起因

起因是一个批量根据es文档id更新指定字段的功能,经过上线后使用反馈,经常性的偶发文档无法更新的情况

处理

找到相关代码,自行写个demo代码批量跑下试下,原来的代码大概示意如下

public String len10() throws Exception{
    Random random = new Random();
    String[] ls = new String[500];
    for (int i = 0; i < 500; i++) {
        int finalI = i;
        Runnable callable = new Runnable() {
            @Override
            public void run() {
                String str = String.valueOf(random.nextInt(40)+10);
                String str2 = String.valueOf(random.nextInt(40)+10);
                String str3 = String.valueOf(random.nextInt(40)+10);
                String action = "/XXXXXXX_index/XXXXXXX_type/_update_by_query";
                String dateTime = "20"+str+"-01-12 23:"+str2+":"+str3;
                String script = "{\n" +
                        "  \"script\": {\n" +
                        "    \"inline\": \"ctx._source.modify_time='"+dateTime+"'\"\n" +
                        "  },\n" +
                        "  \"query\": {\n" +
                        "    \"bool\": {\n" +
                        "      \"filter\": [{\n" +
                        "        \"term\": {\n" +
                        "          \"id\": \"2\"\n" +
                        "        }\n" +
                        "      }]\n" +
                        "    }\n" +
                        "  }\n" +
                        "}"
                        ;
                try {
                    String post = esRestClient.performRequest("POST", action, script);
                    System.out.println(post);
                    ls[finalI] = post;
                } catch (IOException e) {
                    e.printStackTrace();
                    ls[finalI] = e.getMessage();
                }
            }
        };
        Thread thread = new Thread( callable);
        thread.start();
    }
    Thread.sleep(50000);
    return "";
}

大意就是起500个线程,更新索引中指定文档id为2的文档的modify_time字段,通过script来更新。

执行之后其实就可以看到大量异常信息了

Continue reading

基于AOP的Redis缓存注解功能开发设计[3],缓存更新、删除、过期

前文

在完成了前面两篇文章(基于AOP的Redis缓存注解功能开发设计[2],JexlEngine自定义缓存Key)中的基础功能的开发之后,我们可以再进一步完成缓存更新、缓存删除、缓存过期这几个接口。从而适应在具体的业务开发过程中遇到的各种对缓存数据操作的需求。

因为前面对具体的Redis的缓存的操作和实现逻辑已经做了详细说明,所以在这三个功能注解的开发过程中只做简单的逻辑阐述,不在细究每一个方法的具体含义

缓存更新

缓存更新的意思就是,当我们在更新数据库中的某条数据之后,我们需要另外也把Redis中作为缓存的数据也同步时更新下,这里其实就会涉及到我们之前提到的缓存一致性问题,这个话题要讲的话可以展开来讲很长一段,我们不在这里做过多讨论,可以自行再去搜索一番。

我在这里需要提供一个新的注解@RedisPut,和PointCut来实现我们的功能即可,代码直接加到原来的切面类中。切面类中需要实现的逻辑就是根据方法执行的结果,直接更新Redis中的缓存数据,和之前@RedisCache的区别就是这里不需要去Redis中判断是否已经存在缓存数据。

Continue reading

基于AOP的Redis缓存注解功能开发设计[2],JexlEngine自定义缓存Key

那么在经历了前面两片文章《基于AOP的Redis缓存注解功能开发设计》和《JexlEngine表达式引擎的简单使用》之后,我们便可以将JexlEngine表达式引擎在AOP缓存中使用起来,通过表达式引擎的定义来自定义每个缓存的Key或者每组类型的缓存的Key,从而达到在不同的代码逻辑中增删改有相同组别或者类型关联的缓存数据的需求

如何指定生成Key规则

在之前的Redis缓存注解一文中,我们定义的注解中有个Key变量

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface RedisCache {
    String value();

    String key() default "";
}

原本我们在使用的时候,value()用来指定在Redis中的Key,而hashKey则用注解作用在的方法的所有参数组合之后的Array来担当。现在我们把注解中的key()用上,用来填写对应的hashKey的生成规则,填写的内容则是一个JexlEngine的表达式。下面,写个Demo。

@RedisCache(value = "dict_cache", key = "paramStr+'_'+paramInteger+'_'+chars[0]+'_'+paramInt+'_'+paramDto.getCodeParam()")
public Result<DictionaryInfo> cacheParamExample(String paramStr, Integer paramInteger, ParamDto paramDto, char[] chars, int paramInt, Integer integer, double v){
    DictionaryInfo dictionaryInfo = new DictionaryInfo();
    dictionaryInfo.setDictName("DIC");
    dictionaryInfo.setDictCode("CODE");
    return new Result<>(dictionaryInfo);
}

表达式写得有点夸张,不过主要是做个展示,这里也不会用到太复杂的JexlEngine表达式,其根本在于用几个给予的对象拼接一个字符串,不会涉及到JexlEngine提供的各种复杂的功能。这边更新下之前文章中切面类RedisCacheAspect的代码

Continue reading

常用Java查看对象所占内存方法

业务开发需求中,在进行一些大批量数据处理的时候,会有很多对象驻留在内存中,此时可能会需要有一些对内存占用情况的考虑,以下几个方法能帮助快速判断出对象占用内存大小的情况,以帮助开发人员在后期参数设置上提供一定的参考依据和帮助

准备工作,新建几个用来存放数据的Java对象,可以稍微复杂点,实际上的这个学生信息对象会比这个更复杂很多,这里只是举例,如下

public class StudentInfo {

    private String name;
    private int age;
    private Address address;
    private EducationalExperience educationalExperience;



    //get、set方法省略
}
public class Address {

    private RegionInfo province;
    private RegionInfo city;
    private RegionInfo district;

    //get、set方法省略
}
public class RegionInfo {
    private String name;
    private int regionCode;
    private String abbreviation;

    
//get、set方法省略
}
Continue reading

Javassist结合JavaAgent修改Java.class文件字节码实践

续上一篇Javassist动态修改jar包内java类字节码,有了上一篇的基础之后我们再尝试结合JavaAgent来实现更加强大的AOP功能。

首先提一嘴JavaAgent。在 jdk 1.5 之后引入了 java.lang.instrument 包,该包提供了检测 java 程序的 Api,比如用于监控、收集性能信息、诊断问题,通过 java.lang.instrument 实现的工具我们称之为 Java Agent。

其基本原理可以理解为,当JVM在加载.class类文件的时候会触发ClassFileLoadHook回调JNI执行sun.instrument.InstrumentationImpl#transform方法,如果我们自己实现了ClassFileTransformer的transform方法,则会用transform方法返回的字节码内容替换掉原来读取到的.class文件的字节码。https://www.cnblogs.com/yichengtech/p/15854415.html

接下来我们来自己做一个简单的实现,顺便结合之前文章中的Javassist工具做一点“有意思”的事情。

创建Agent Jar包

创建一个新的工程,代码结构基本如下。一个MyAgent入口类、一个名为MyClassFileTransformer的ClassFileTransformer接口实现类

两个类的对应实现代码,首先是MyAgent类,在这里写一个premain方法,并在这里引入我们下面要实现的MyClassFileTransformer类

package com.myagent;

import java.lang.instrument.Instrumentation;

/**
 * MyAgent<br>
 *
 * @author CheungQ
 * @date 2023/3/15 16:43
 */
public class MyAgent {
    public static void premain(String agentArgs, Instrumentation inst) {
        System.out.println("premain begin");
        inst.addTransformer(new MyClassFileTransformer());
    }
}

而在MyClassFileTransformer类中实现了ClassFileTransformer接口,并在transform方法中实现我们自己的逻辑。先判断一下当前加载的类,如果不需要调整修改的则直接return null即可。此处我们假设需要修改com.cheungq.demo.service.RedisService这个类

Continue reading

Javassist动态修改jar包内java类字节码

之前在es聚合导出(不是),Map#merge方法使用一文里提到过调用jar包里的代码,但是jar包里的代码有问题,需要修改才行的问题。另外之前也跟朋友聊天的时候提到过他调用的jar包里的源码有bug,但是没法修改的事情。今天摸会儿<・)))><<,试用下Javassist(http://www.javassist.org/)的强大功能,可以动态的直接修改编译后的.class文件,这样的好处是在不修改源码的情况下即可修改现有逻辑。不光是修改,他还能动态创建新的类,并编写相应逻辑,实现对现有代码的零侵入。

同时结合上文Redis结合AOP实现限流注解的开发中提到的Aspect切面编程相关技术实现更加强大的方法增强效果。

下面就来做一个简单实现试试看,目标仍然是修改jar包里的代码逻辑,首先第一步自己先写个类,并打成jar包。

打包jar包

取一段现有项目里的代码,打成一个包。新建一个项目,按如下结构填入代码,并进行package打包操作

现有分库操作中常用的一个方法,根据订单号模12之后的值进行分库存取操作
public class Mod12 {
    public static int mod12(String number){
        return modBy(number,12);
    }

    public static int modBy(String number, int modNum){
        BigInteger numBig = new BigInteger(number);
        return numBig.mod(BigInteger.valueOf(modNum)).intValue();
    }
}

pom.xml文件内容也很干净,如下,分别定义groupId、artifactId、version即可

<?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>com.mymod</groupId>
    <artifactId>mod12</artifactId>
    <version>1.0-SNAPSHOT</version>
</project>

执行package后在项目的target目录下得到一个mod12-1.0-SNAPSHOT.jar文件。

题外话,这样的操作其实也是在日常工作中常用的一种方式。将一些通用的工具类,接口类等独立一个项目,并打包成jar包,再在其他各业务系统中引入这个对应的包调用即可。打一个比方,A系统在集群内提供了一系列rpc服务接口,此时A系统的开发人员将接口单独定义单独抽出来打成一个jar包,其他系统在需要调用A系统的接口的时候引入这个jar包,根据jar包内定义的方法调用A系统的接口。如果A系统某天新增了某个接口,而某个调用方需要用这个新增的接口,则更新下jar包的版本号即可。

Continue reading

Redis结合AOP实现限流注解的开发

有点长。本来应该分3个部分分别是AOP、Redis配置、限流原理及实现来写的,写的时候没收住,一股脑全顺着写下来了

什么是 AOP

(1)面向切面编程(方面),利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

(2)通俗描述:不通过修改源代码方式,在主干功能里面添加新功能

准备工作

详细的原理、底层实现的内容这里不多提,只谈实际使用。

添加maven依赖,具体version需要自己判断,我这边本地起的应用的SpringBoot2.4的

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>1.9.4</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.4</version>
</dependency>

创建一个我们需要使用的的注解

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface ControlStrategy {
    String level() default "6";
    String threshold() default "";
    String strategy() default "";
    String key() default "";
    String switchKey() default "";
}

以及对应的切面类

@Component
@Aspect
public class ControlStrategyAspect {
}

简单说下这两部分

首先,注解部分上的元注解@Target({ElementType.METHOD, ElementType.TYPE}),表示当前注解可以作用于方法和接口或类上,@Retention(RetentionPolicy.RUNTIME)表示作用于运行时,@Inherited表示该注解对添加了该注解的类的子类同样生效

Continue reading