반응형

문자열로 된 수식 계산하기

예를 들어 데이터에 "1+1" 이라는 스트링이 넘어올 경우 이 값의 결과값 2 가 필요한 경우가 있다.

이런 경우에 스크립트 엔진을 이용해 문자열을 계산하자 

파싱안하고 스크립트 엔진으로 연산하기

import javax.script.ScriptEngineManager;
import javax.script.ScriptEngine;

public class Test {
  public static void main(String[] args) throws Exception{
      ScriptEngineManager mgr = new ScriptEngineManager();
      ScriptEngine engine = mgr.getEngineByName("JavaScript");
      String foo = "30+2";
      System.out.println(engine.eval(foo));
    }
}

 

반응형
반응형

스프링 EL(Expression Language) 란 객체 그래프를 조회하고 조작하는 기능을 제공하는 언어를 말한다.

spEL은 모든 스프링 프로젝트에서 사용하는 expression language로 만들었다.

문법이나 규칙은 배우기가 쉽다.

  • #{"표현식"}

  • ${"프로퍼티"}

  • 이런식으로 특정 객체를 가져와서 문자열처럼 사용할 수 있고, 계산도 할 수 있다. 표현식은 프로퍼티를 포함할 수 있지만, 반대로는 불가능하다.

사용처

  • @Value 애노테이션 안에 spEL을 쓰면, 아래 필드값에 결과가 주입된다.
  • 스프링 시큐리티의 경우 메소드 시큐리티, @PreAuthorize, @PostAuthorize, @PreFilter, @PostFilter, XML 인터셉터 URL 설정 등에 사용된다.
  • 스프렝 데이터에서 @Query 에 사용된다.
  • 화면을 만드는 템플릿엔진인 타임리프(thymeleaf) 등에서도 사용된다.

예시

@Value("#{1 + 1}")
int value;
 
@Value("#{'안녕 ' + 'red'}")
String greeting;

@Value("#{1 eq 1}")
boolean yn;

@Value("red")
String red;

// application.properties (프로퍼티) 에서 blue.value 변수를 가져온다.
@Value("${blue.value}")
int blueValue;

// Sample 객체의 data 필드 값을 가져온다.
@Value("#{sample.data}")
int sampleData;

 

사용 종류

Default Value

@Value("${car.type:Sedan}")
private String type; // yml 에 값이 없으면 Sedan 로 default value 설정

System Variables

@Value("${user.name}")
// Or
@Value("${username}")
private String userName;

@Value("${number.of.processors}")
// Or
@Value("${number_of_processors}")
private int numberOfProcessors;

@Value("${java.home}")
private String java;

 

Parameter Method Value

@Value("${car.brand}")
public CarData setCarData(@Value("${car.color}") String color, String brand) {
    carData.setCarColor(color);
    carData.setCarBrand(brand);
}

systemProperties

@Value("#{systemProperties['user.name']}")
private String userName;
@Value("#{systemProperties}")
private Map<String, String> properties;
public Driver(@Value("#{systemProperties['user.name']}") String name, String location) {
    this.name = name;
    this.location = location;
}

Injecting into Maps

student.hobbies={indoor: 'reading, drawing', outdoor: 'fishing, hiking, bushcraft'}
@Value("#{${student.hobbies}}")
private Map<String, List<String>> hobbies;
반응형
반응형

몽고디비 조회 속도가 너무 느려져서 인덱스를 추가했다.

 

추가하는데 엄청난 시간이 들지 예상하지 못해, 생성문을 실행했는데, 이게 실행이 되지 않아 당연히 클라이언트에서 실행중 수행시간이 길어져 프로세스가 꺼진줄 알고 그냥 뒀다.

robo 3T 라는 툴에서는 프로세스가 15초 이상 실행되어 실행되지 않는다~ 뭐 이런 식의 문구가 떠서 끄면 작업이 끝난줄 알았다.

 

그런데 이게 최초에 foreground 로 돌리던게 돌아갔던건지 background 로 돌리던게 돌아가는건지 몽기DB 쓰기 수행작업이 lock 이 걸렸습다.

백그라운드로 돌렸어도 전체에 락이 걸렸을거 같은데 확실치 않다.

 

인덱스 생성 명령어를 수행한지 2시간이 지나도 lock 이 안풀려있엇어서 서비스 병목 현상때문에 알게 되었다. 

 

나중이 되서야 인덱스 생성되는 프로세스를 끌 수 있다는걸 그 뒤에 알았다 ㅠㅠ.

안일하게 생성명령어만 돌리고 생성안되었다고 방치하지만 않았어도 이슈가 발생하지 않았는데 말이다.

 

최초에 금방 생성될 줄 알고 실수로 포그라운드에서 인덱스 생성을 날렸어서,

클라이언트에서 중지됐다는 alert 문구와 별개로 계속 백그라운드에서 실행중이였나 보다.

 

백그라운드로 돌려도 그런 형상이 나오는지는 잘 모르겠다.

 

해서 현재 만들어지고 있는 인덱스 생성 오퍼레이션을 취소해줘야 한다.

 

아래와 같은 방법이 있는지 알았으면.....

killIndexMaking = function(a) {
    currOp = db.currentOp({"op": "command", 'query.createIndexes':'인덱스를 건 컬렉션명'})['inprog']
    //print(currOp) // 만들고있는 인덱스가 맞는지 check
    print("Killing opId: " + currOp[0].opid);
    //db.killOp(currOp[0].opid); // 죽인다.
};
 
killIndexMaking(1)

db.currentOp명령어로 'query.createIndexes'컬럼을 필터로 걸어 해당 컬렉션에 걸리고있는 인덱스 목록을 가져온다. 위에서는 인덱스를 하나만 만들고 있다고 가정하고 첫번째(currOp[0].opid)를 죽인다. 

일단 실행전에 인덱스 생성 리스트를 확인해본다.

 

리스트를 확인해보는 방법은 여러개가 있다.

db.currentOp(true).inprog.forEach(function(op){ if(op.msg!==undefined) print(op.msg) })
db
  .currentOp({"command.createIndexes": { $exists : true } })
  .inprog
  .forEach(function(op){ print(op.msg) })
db.currentOp({ 
    'msg' :{ $exists: true },
    'command': { $exists: true },
    $or: [ 
        { 'command.createIndexes': { $exists: true } }, 
        { 'command.reIndex': { $exists: true } }
    ]
}).inprog.forEach(function(op) { 
    print(op.msg); 
});

 

op 를 찾아서 해당 오퍼레이션을 지워줘야 하는데 문서를 보다 보니

Use the dropIndexes command or its shell helpers dropIndex() or dropIndexes() to terminate an in-progress index build. See Abort In-Progress Index Builds for more information.
Do not use killOp to terminate an in-progress index builds in replica sets or sharded clusters.

 

이런 말이 나왔다. 

killOp 를 쓰지말고 dropIndex() 를 사용해라. 혹시나 이런 일이 생길 경우에 dropIndex 를 써봐야겠다.

 

docs.mongodb.com/manual/core/index-creation/ [공식문서]
qkqhxla1.tistory.com/998 [인덱스 생성 취소 스크립트]
https://stackoverflow.com/questions/22309268/mongodb-status-of-index-creation-job [인덱스 생성 중인 상태보기]

 

 

반응형

'DB > MongoDB' 카테고리의 다른 글

[MongoDB] 몽고디비 인덱스 설명 및 생성  (0) 2020.12.17
반응형

gson 을 사용할 떄 json 구조에 맞춰서 fromJson 메소드를 호출하면 자동으로 파싱을 해주지만,

종종 커스텀을 해서 파싱을 해야 할떄가 있다.

 

1. 해야하는 순간은 해당 데이터가 모두 동일한 포맷으로 파싱이 되어야 한다든지,

EX) 공백 "" 스트링이 있으면 항상 null 로 치환, 또는 날짜가 포함된 경우 동일한 포맷으로 파싱해야 하는 경우.

2. 일반적인 방식으로 파싱이 어려운 경우

EX) json 객체의 키값이 가변적인 경우여서 객체명으로 파싱이 불가능할 경우

{ 
 "1234": {
	"name" : "cat"
 },
 "5594": {
 	"name": "dog" 
 }
}

EX) 같은 json 키 이지만 값에 value 에 따라 파싱되는 객체 구조가 다를 경우

 

 

여기서 registerTypeAdapter 에 넣는 class 에 따라 해당 클래스가 있는 json 이 gson 으로 파싱될떄 deserializer 가 호출된다.

String.class 를 넣으면 파싱되는 데이터타입중에 String 인 데이터는 모두 deserialize 를 호출하게 된다.

CustomDeserializer 클래스

public class CustomDeserializer implements JsonDeserializer<ReturnVo> {

	/**
	 * JsonElement json parameter 은 parsing 하려는 전체 json 데이터
	 */
	@Override
	public ReturnVo deserialize(JsonElement json, java.lang.reflect.Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
	
		ReturnVo returnVo = new ReturnVo();
      	JsonObject jsonObject = json.getAsJsonObject();
        /**
          파싱 
        */
    return returnVo;
	}
}

사용 예시

GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(ReturnVo.class, new CustomDeserializer()); 
Gson gson = builder.create();
ReturnVo response = gson.fromJson(jsonStr, ReturnVo.class); /** fromJson 호출시 CustomDeserializer 가 호출됨*/
return response;

 

 

참고문헌

> https://stackoverflow.com/questions/28319473/gson-same-field-name-different-types [Same field name, different types] 

반응형
반응형

스프링에서 설정파일 값을 외부에 노출하고 싶지 않을떄, Jasypt 를 사용하면 된다.

 

라이브러리

spring boot starter 용

3.0.3 이 작성기준 2021년 1월 28일 기준 최신버젼이다.

3,0.3 이 출시된 날짜는 2020년 5월 31일 이다.

<dependency>
	<groupId>com.github.ulisesbocchio</groupId>
	<artifactId>jasypt-spring-boot-starter</artifactId>
	<version>3.0.3</version>
</dependency>

위와 같이 라이브러리를 추가하면

@SpringBootApplication or @EnableAutoConfiguration 어노테이션을 메인에 추가해 주어야 한다.

이렇게 해주면 환경설정 파일 command line argument, application.properties, yaml properties 들을 암호화 할 수 있다.

 

@SpringBootApplication or @EnableAutoConfiguration 을 추가해주지 않으면 pom.xml 과 다른 어노테이션을 추가해주어야 한다.

<dependency>
        <groupId>com.github.ulisesbocchio</groupId>
        <artifactId>jasypt-spring-boot</artifactId>
        <version>3.0.3</version>
</dependency>
@Configuration
@EncryptablePropertySources({@EncryptablePropertySource("classpath:encrypted.properties"),
							@EncryptablePropertySource("classpath:encrypted2.properties")})
public class MyApplication {
...
}

위 작업이 필요하다. 그래서 그냥 @SpringBootApplication or @EnableAutoConfiguration 어노테이션 을 추가하자.

 

 

속성값에 대한 정의

Key Required Default Value
jasypt.encryptor.password True -
jasypt.encryptor.algorithm False PBEWITHHMACSHA512ANDAES_256
jasypt.encryptor.key-obtention-iterations False 1000
jasypt.encryptor.pool-size False 1
jasypt.encryptor.provider-name False SunJCE
jasypt.encryptor.provider-class-name False null
jasypt.encryptor.salt-generator-classname False org.jasypt.salt.RandomSaltGenerator
jasypt.encryptor.iv-generator-classname False org.jasypt.iv.RandomIvGenerator
jasypt.encryptor.string-output-type False base64
jasypt.encryptor.proxy-property-sources False false
jasypt.encryptor.skip-property-sources False empty list

 

configuration 설정파일

@Configuration
public class JasyptConfig {

    @Bean("jasyptStringEncryptor")
    public StringEncryptor stringEncryptor() {
        PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor();
        SimpleStringPBEConfig config = new SimpleStringPBEConfig();
        config.setPassword("password");
        config.setAlgorithm("PBEWITHHMACSHA512ANDAES_256");
        config.setKeyObtentionIterations("1000");
        config.setPoolSize("1");
        config.setProviderName("SunJCE");
        config.setSaltGeneratorClassName("org.jasypt.salt.RandomSaltGenerator");
        config.setIvGeneratorClassName("org.jasypt.iv.RandomIvGenerator");
        config.setStringOutputType("base64");
        encryptor.setConfig(config);
        return encryptor;
    }
}

password 를 제외하고 기본 값들로 작성되어 있다.

 

 

application.yml 설정 추가

jasypt:
  encryptor:
    bean: jasyptStringEncrptor

설정으로 등록한 빈의 명을 명시해줘야 한다.

 

암호화 및 복호화 예시

위에서 암호화를 위한 모든 작업은 끝났다. 아래와 같이 설정파일의 값을 암호화 복호화 해가면서 사용하면 된다.

@SpringBootApplication
public class Application implements CommandLineRunner {

	public static void main(String [] args) {
        SpringApplication.run(Application.class, args);
        System.out.println("=========== Server Start ===========");
	}
	
	@Override
   public void run(String... args) throws Exception {
		StandardPBEStringEncryptor pbeEnc = new StandardPBEStringEncryptor();
		pbeEnc.setAlgorithm("PBEWithMD5AndDES");
		pbeEnc.setPassword("test"); //2번 설정의 암호화 키를 입력
		
		String enc = pbeEnc.encrypt("1234"); //암호화 할 내용
		System.out.println("enc = " + enc); //암호화 한 내용을 출력
		
		//테스트용 복호화
		String des = pbeEnc.decrypt(enc);
		System.out.println("des = " + des);
   }
}

 

application.yml 사용예시

datasource:
  url: ENC(인크립트된dburl)
  username: ENC(인크립트된유저명)
  password: ENC(인크립트된패스워드)

 

 

더 많은 사용방법 및 기능들이 아래 참고 주소에 있어서 제대로 사용할거면 아래 문서를 참고하자

https://github.com/ulisesbocchio/jasypt-spring-boot [깃허브 jasypt-spring-boot 업데이트 문서]
반응형
반응형

Gson casting 오류

 

gson 을 사용하다 보면

__com.google.gson.internal.LinkedTreeMap cannot be cast to my class__

이런 오류가 나는데, casting 을 사용하려고 하면 나는 오류다.

Gson gson = new Gson();
list = gson.fromJson(jsonString, new TypeToken<List<T>>(){}.getType());

jsonString list 로 fromJson 하는 경우에 생기는 오류이다.

 

 

결론은 아래 메소드로 다시 json 변환 후 object 로 변환 하던지, 아니면 gson 파싱 객체를 casting하지 않도록 만들어야 한다.

//array 를 json String 으로 변환
public static <T> String arrayToString(ArrayList<T> list) {
    Gson g = new Gson();
    return g.toJson(list);
}
// json String 을 list Object 로 변환
public static <T> List<T> stringToArray(String s, Class<T[]> clazz) {
    T[] arr = new Gson().fromJson(s, clazz);
    return Arrays.asList(arr); //or return Arrays.asList(new Gson().fromJson(s, clazz)); for a one-liner
}

참고문헌

https://stackoverflow.com/questions/27253555/com-google-gson-internal-linkedtreemap-cannot-be-cast-to-my-class  
www.programmersought.com/article/46923204627/

 

반응형
반응형

SFTP 접속하는 라이브러리는 jsch 다.

sftp: <http://www.jcraft.com/jsch/>

 

내서버에서 다른 서버 22번 sftp 접속하는 방법이다.

 

public class FTPUtil {

	private static Log logger = LogFactory.getLog(FTPUtil.class);

    private Session session = null;
    private Channel channel = null;
    private ChannelSftp channelSftp = null;

   // SFTP 서버연결
    public void connect(String user, String url, String password) throws Exception{

        logger.info(url);
        //JSch 객체 생성
        JSch jsch = new JSch();
        try {
            //세션객체 생성 ( user , host, port )
            logger.info("session");
            session = jsch.getSession(user, url);
            //password 설정
            session.setPassword(password);
            //세션관련 설정정보 설정
            java.util.Properties config = new java.util.Properties();
            //호스트 정보 검사하지 않는다.
            config.put("StrictHostKeyChecking", "no");
            session.setConfig(config);
            //접속
            session.connect();
            //sftp 채널 접속
            channel = session.openChannel("sftp");
            channel.connect();

        } catch (JSchException e) {
            logger.error("connect JSchException error : " +e.toString());
            throw new Exception("JSchException");
        } catch (Exception e) {
        	 logger.error("connect error : " +e.toString());
             throw new Exception(e.toString());
		}
        channelSftp = (ChannelSftp) channel;
    }

    // 단일 파일 업로드
    public void upload( String dir , File file){
        FileInputStream in = null;
        try{ //파일을 가져와서 inputStream에 넣고 저장경로를 찾아 put
            in = new FileInputStream(file);
            channelSftp.cd(dir);
            channelSftp.put(in,file.getName());
        }catch(SftpException se){
            se.printStackTrace();
        }catch(FileNotFoundException fe){
            fe.printStackTrace();
        }finally{
            try{
                in.close();
            } catch(IOException ioe){
                ioe.printStackTrace();
            }
        }
    }


    public ChannelSftp moveDirectory(String dir){
    	 try{ //경로탐색후 inputStream에 데이터를 넣음
             channelSftp.cd(dir); // 결로 이동
         }catch(SftpException se){
             se.printStackTrace();
         }
		return channelSftp;
    }

    /**
     *
     * @param dir  서버의 저장되어 있는 경로
     * @param fileNm 다운받으려는 파일명
     * @param saveDir 현 서버의 다운받으려고 하는 경로
     */
    public void download(String dir, String fileNm, String saveDir){
        InputStream in = null;
        FileOutputStream out = null;
        //String path = "...";
        try{ //경로탐색후 inputStream에 데이터를 넣음
            channelSftp.cd(dir); // 결로 이동
            in = channelSftp.get(fileNm);

        }catch(SftpException se){
        	logger.error("download " +se.toString());
        }
        try {
            out = new FileOutputStream(new File(saveDir));
            int i;
            while ((i = in.read()) != -1) {
                out.write(i);
            }

        } catch (IOException e) {
        	logger.error("download io error : " +e.toString());
        } finally {
            try {
                out.close();
                in.close();
            } catch (IOException e) {
            	logger.error("download close error : " +e.toString());
            }
        }

    }

    /**
     * 인자로 받은 경로의 파일 리스트를 리턴한다.
     * @param path
     * @return
     */
    public Vector<ChannelSftp.LsEntry> getFileList(String path, String lsCmd) {

    	Vector<ChannelSftp.LsEntry> list = null;
    	try {
    		channelSftp.cd(path);
    		list = channelSftp.ls(lsCmd);
		} catch (SftpException e) {
			e.printStackTrace();
			return null;
		}
    	return list;
    }

    // 파일서버와 세션 종료
    public void disconnect(){
        channelSftp.quit();
        session.disconnect();
    }
}
반응형
반응형

CPU Usage

CPU 사용량은 시스템 사용률과 사용자 사용률 등을 합친값이다.

시스템 사용률은 운영체제가 사용한 CPU 사용률을 의미하며 사용자 사용률은 응용프로그램이 사용하는 CPU 사용률을 의미한다.

 

System 사용률이 높다면 시스템 사양을 높여야 한다.

USer 사용률이 높다면 시스템 업그레이드 또는 애플리케이션의 분배를 고려해야 한다.

 

CPU Idle

CPU Idle 은 CPU 가 모든 일을 끝내고 쉬는 시간을 의미한다. 일반적으로 CPU Usage 가 높다면 CPU Idle 은 낮을 것이다. 

하지만 I/O Wait 또는 Steal 등의 값으로 인해 이 비율이 항상 일정치 않다.

 

Idle 값이 항상 낮다면 시스템을 업그레이드 해야한다.

 

CPU I/O Wait

CPU가 입출력을 대기하는데 사용한 시간의 비율을 보여준다. 프로세스에 바로 접근 할 수 없는 상황인 경우 I/O Wait 비율은 늘어난다.

iowait 은 cpu 본연의 job이 아닌 다른 장치와의 통신 때문에 cpu job이 일시적으로 waiting 된 상태를 말한다. 예를 들어서 cpu와 hdd간의 테이터 통신이 많다면 (hard disk에 writing 부하가 심하게 올라간다면) iowait이 높아지게 된다.

 

I/O Wait 값이 높다면 하드 디스크를 SSD로 교체하거나 Raid 유형을 바꿔야 한다.

 

CPU Steal %

다른 OS 에 의해서 빼앗긴 CPU 시간의 비율, 가상화되어 있지 않다면 Steal 값은 사용되지 않으므로 항상 0으로 표시된다.

가상머신이 많아지는 경우, 동일한 물리 장비에서 제공되는 환경이다보니,

특정 가상머신이 CPU를 많이 차지하게 되면, 다른 머신들도 따라서 느려지게 되는데,

이 현상을 CPU Steal이라고 한다.

 

 

CPU Load(부하)

CPU Load 는 CPU 에 실행중이거나 대기중인 작업(프로세스) 의 개수를 평균으로 보여주는 값이다.

CPU에 실행중이거나 대기중인 작업이 있는지 100번 확인할 때 2개의 작업이 있다면 CPU 로드는 0.02 이다.

CPU 가 항상 실행중이고 대기중인 작업없이 효율적으로 정확히 일한다면 CPU Load 는 1이다. 코어가 2개라면 대기중인 작업이 없는 상태일 떄 CPU Load 는 2가 된다.

코어가 4개면 4 이다.

부하가 클 수록 CPU Load 의 값이 커지게 된다. CPU Load 는 남아있는 작업까지 표시해 주는 지표이다.

 

코어가 하나인 경우 CPU Load 의 임계값은 0.7 정로도 둔다. 적당한 평균치

CPU Load 가 지속적으로 0.7 을 넘어간다면 시스템업그레이드를 고려해야 한다.

 

참고문헌

brunch.co.kr/@leedongins/75?fbclid=IwAR3Vm-UPfMb3AFqhymelLPphePD8qtZl_wK57_K89YeOuomCeQ0pZZQC2jM [CPU 지표 정리]

 

반응형

+ Recent posts