Feb 28, 2018 - POP_part23

관점 - 프로그래머가 보는 시각

계약에 의한 설계

함수와 함수를 호출하는 쪽이 서로 계약을 맺고 있다고 간주하고 프로그래밍 하는것을 가리켜 계약에 의한 설계라고 한다. 계약한 내용을 미리 함수의 주석으로 알려주자 계약이행의 확인을 위한 코드는 단정문으로 표현하자.

주의점

  • 함수쪽에서는 파라미터를 조정하지 않는다
  • 함수쪽의 단정문을 사용자 입력확인에 사용하지 않는다.
  • 함수쪽에서는 상정은 엄격하고 확약은 관용적으로

클래스 불변 표명

클래스가 클래스를 사용하는쪽에 대해 항상 참이 된다는것을 보증하는 조건

트래시보다는 크래시

코드 실행중에 있을수 없는 일이 발생했을때는 재빨리 크래시가 정론이다.

방어적 프로그래밍

  • 외부 소스에서의 데이터 입력값을 확인한다.(상정 내의 오류 검출)
  • 함수의 입력 파라미터 값을 확인한다.(상정 외의 오류를 검출)

방어적 프로그래밍을 통해 안전해진다.

  • 개발중의 안전 운전
  • 운용 중의 안전 운전

바리케이트 전략을 취하자 데이터를 격리시키고 필터링해서 정확한데이터를 쓰는 전략이다.

다양한 오류처리 방법

  • 무해한 값을 반환한다.
  • 다음 데이터로 대신한다.
  • 이전과 같은 값을 반환한다.
  • 가장 가까운 유효값으로 대신한다.
  • 로그에 기록한다.
  • 오류를 반환한다
  • 오류 처리 함수를 호출한다.
  • 오류 메시지를 표시한다.
  • 처리를 중지한다.
  • 각 위치에서 최적인 오류 처리를 선택한다.

참조


Feb 23, 2018 - LockSupport.park()

LockSupport.park()

운영중인 시스템에서 아래처럼 에러 코드들이 발생하면서 장애상황이 발생되기 시작했다.

sun.misc.Unsafe.park(Native Method)
java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
org.apache.commons.pool2.impl.LinkedBlockingDeque.takeFirst(LinkedBlockingDeque.java:524)
org.apache.commons.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:438)
org.apache.commons.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:361)
redis.clients.util.Pool.getResource(Pool.java:49)
redis.clients.jedis.JedisPool.getResource(JedisPool.java:99)
com.mobon.dao.redis.RedisSingleFactory.getConnection(RedisSingleFactory.java:133)
com.mobon.dao.redis.RedisConnFactory.getLogConnection(RedisConnFactory.java:79)
xxx.util.RedisUtilSub.setErrorLog(RedisUtilSub.java:66)
xxx.util.ErrorLog.getStack(ErrorLog.java:160)
xxx.util.ErrorLog.getStack(ErrorLog.java:79)
xxx.servlet.Drc.doGet(Drc.java:221)
javax.servlet.http.HttpServlet.service(HttpServlet.java:624)
javax.servlet.http.HttpServlet.service(HttpServlet.java:731)
org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:303)
org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
xxx.filter.XssFilter.doFilter(XssFilter.java:87)
org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:220)
org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:122)
org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:505)
org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:170)
org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:103)
org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:956)
org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:116)
org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:423)
org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1079)
org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:625)
org.apache.tomcat.util.net.AprEndpoint$SocketWithOptionsProcessor.run(AprEndpoint.java:2459)
java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
java.lang.Thread.run(Thread.java:745)

위에 코드를 보면서 일단을 pool이 문제라고 생각했다. 찾아서 코드를 보니 pool 설정이 디폴트 설정으로 되어 있었다.


JedisPoolConfig poolConfig=new JedisPoolConfig();
this.pool = new JedisPool(poolConfig,hostIp, port, 1000, pwd);

그래서 해당 풀이 문제인지 찾아 볼려고 첫번째 덤프에서 나왔던 콜스텍을 기준으로 org.apache.commons.pool2.impl.GenericObjectPool.borrowObject의 코드를 찾아보았다.


 while (p == null) {
            create = false;
            if (blockWhenExhausted) {
                p = idleObjects.pollFirst();
                if (p == null) {
                    create = true;
                    p = create();
                }
                if (p == null) {
                    if (borrowMaxWaitMillis < 0) {
                        p = idleObjects.takeFirst();
                    } else {
                        waitTime = System.currentTimeMillis();
                        p = idleObjects.pollFirst(borrowMaxWaitMillis,
                                TimeUnit.MILLISECONDS);
                        waitTime = System.currentTimeMillis() - waitTime;
                    }
                }

쓰레드 상태를 WAINTING 걸고 park 메소드를 호출하는 로직에 콜스텍은 dleObjects.takeFirst(); 호출하는 순간 발생이 된다. 해당 상태는 borrowMaxWaitMillis에 의해서 결정이 되는데 해당 값은 유효한 커넥션 풀이 없을때 얼마나 대기할지 결정을 하므로써 그렇게 된다. 그래서 위에서 보았듯이 커넥션 풀이 디폴트로 설정이 되어 있기때문에 최대 8개만 생성이되고 더이상 되지 않아서 커넥션풀이 여유가 남았을때 사용이 가능하게 되는 경우이다.

그래서 설정을 커넥션 풀 갯수를 늘리고 체크하는 설정을 추가하였다.


    JedisPoolConfig poolConfig=new JedisPoolConfig();
    poolConfig.setTimeBetweenEvictionRunsMillis(60000);
    poolConfig.setMinEvictableIdleTimeMillis(1800000);
    poolConfig.setNumTestsPerEvictionRun(5);
    poolConfig.setMaxTotal(220);
    poolConfig.setMaxIdle(220);
    poolConfig.setTestWhileIdle(true);
    poolConfig.setTestOnBorrow(true);

쓰레드 상태

상태 코드
객체생성 NEW
실행대기 RUNNABLE
일시정지 WAITING
일시정지 TIMED_WAITING
일시정지 BLOCKED
종료 TERMINATED

참조


Feb 23, 2018 - json-lib

json-lib

팀원이 json-lib를 써서 json 문자열을 JSONObject로 변환하는 부분이 있었다. 하지만 하나의 특정 url에서 아래처럼 에러코드가 나기 시작했다.


JSON keys must be strings. 

그래서 json-lib를 보기 시작했는데 key값이 String 타입인줄 체크하고 있었다 api에서 보니 JsonConfig 객체의 AllowNonStringKeys 변수값을 수정하면 위에 에러를 피할수 있어서 아래 처럼 회피를 했다.


JsonConfig config = new JsonConfig();
config.setAllowNonStringKeys(true);
JSONObject json = JsonMapper.fromObject("{"123123":"xxxxx"}",config);

하지만 위처럼 수정을해도 다른 에러가 나기 시작했다.


JSON keys must not be null nor the 'null' string.

해당 에러을 회피할수가 없어서 소스를 확인했더니 무조건 키에 null이라는 값이 들어오면 에러를 발생시키게 만들었다. JSONObject 에 930 line


 String key = String.valueOf( k );
 if( "null".equals( key )){
    throw new NullPointerException("JSON keys must not be null nor the 'null' string.");
 }

그러면 json 표준에 키값이 null이라는 string을 사용하지 못하게 강제 했는지 확인 작업을 하였지만 그런 내용이 없어서 위에 에러발생 시키는 코드를 삭제 배포해서 해결했다.

참조