├── .ebextensions ├── 00-makeFiles.config ├── 00-system-tuning.config ├── 01-timezone.config ├── 02-pinpoint.config └── nginx │ └── nginx.conf ├── .gitignore ├── .travis.yml ├── Procfile ├── README.md ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── posts ├── keys │ └── README.md └── lettuce-than-jedis │ ├── README.md │ └── images │ ├── count.png │ ├── ec2.png │ ├── jedis-agent1.png │ ├── jedis-connection.png │ ├── jedis-cpu.png │ ├── jedis-pinpoint.png │ ├── jedis-pool-connection.png │ ├── jedis-pool-cpu.png │ ├── jedis-pool-pinpoint.png │ ├── jedis-pool-result.png │ ├── jedis-result.png │ ├── lettuce-agent1.png │ ├── lettuce-connection.png │ ├── lettuce-cpu.png │ ├── lettuce-pinpoint.png │ ├── lettuce-result.png │ ├── lettuce-time.png │ └── spec.png ├── settings.gradle └── src ├── main ├── java │ └── com │ │ └── jojoldu │ │ └── blogcode │ │ └── springbootredistip │ │ ├── Application.java │ │ ├── config │ │ ├── EmbeddedRedisConfig.java │ │ ├── RedisProperties.java │ │ └── RedisRepositoryConfig.java │ │ ├── controller │ │ └── ApiController.java │ │ └── point │ │ ├── AvailablePoint.java │ │ └── AvailablePointRedisRepository.java └── resources │ ├── application.yml │ ├── logback-spring.xml │ └── logback │ ├── application-appender.xml │ └── error-appender.xml └── test └── java └── com └── jojoldu └── blogcode └── springbootredistip └── ApplicationTest.java /.ebextensions/00-makeFiles.config: -------------------------------------------------------------------------------- 1 | files: 2 | "/sbin/appstart" : 3 | mode: "000755" 4 | owner: webapp 5 | group: webapp 6 | content: | 7 | #!/usr/bin/env bash 8 | # Pinpoint Conifg 9 | AGENT_ID=`hostname` 10 | APP_NAME=dwlee-redis-test 11 | JAR_PATH=/var/app/current/application.jar 12 | PINPOINT_OPTS="-javaagent:/etc/pinpoint/pinpoint-bootstrap-1.6.2.jar -Dpinpoint.agentId=$AGENT_ID -Dpinpoint.applicationName=$APP_NAME" 13 | JVM_OPTS="-XX:+UseG1GC -verbose:gc -Xloggc:/var/app/current/logs/$APP_NAME/gc.log -Xms2G -Xmx2G" 14 | 15 | # config Redis 16 | REDIS_OPTS="--spring.redis.host=${SPRING_REDIS_HOST} --spring.redis.port=6379" 17 | 18 | # run app 19 | killall java 20 | java $PINPOINT_OPTS $JVM_OPTS -Dfile.encoding=UTF-8 -jar $JAR_PATH $REDIS_OPTS 21 | -------------------------------------------------------------------------------- /.ebextensions/00-system-tuning.config: -------------------------------------------------------------------------------- 1 | files: 2 | "/etc/security/limits.conf": 3 | content: | 4 | * soft nofile 65535 5 | * hard nofile 65535 6 | 7 | commands: 8 | 01: 9 | command: "echo \"10240 65535\" > /proc/sys/net/ipv4/ip_local_port_range" 10 | 02: 11 | command: "sysctl -w \"net.ipv4.tcp_timestamps=1\"" 12 | 03: 13 | command: "sysctl -w \"net.ipv4.tcp_tw_reuse=1\"" 14 | 04: 15 | command: "echo \"net.ipv4.tcp_max_tw_buckets=2000000\" >> /etc/sysctl.conf" 16 | 10: 17 | command: "sysctl -p" -------------------------------------------------------------------------------- /.ebextensions/01-timezone.config: -------------------------------------------------------------------------------- 1 | commands: 2 | 01remove_local: 3 | command: "rm -rf /etc/localtime" 4 | 02link_seoul_zone: 5 | command: "ln -s /usr/share/zoneinfo/Asia/Seoul /etc/localtime" -------------------------------------------------------------------------------- /.ebextensions/02-pinpoint.config: -------------------------------------------------------------------------------- 1 | files: 2 | "/tmp/pinpoint-install.sh": 3 | mode: "000744" 4 | owner: webapp 5 | group: webapp 6 | content: | 7 | #!/bin/bash 8 | wget -P /etc/pinpoint $1 9 | cd /etc/pinpoint 10 | tar -zxvf pinpoint-agent*.tar.gz 11 | 12 | container_commands: 13 | 001-command: 14 | command: "./pinpoint-install.sh ${PINPOINT_AGENT_URL}" 15 | cwd: /tmp 16 | 002-command: 17 | command: mv pinpoint-real-env-lowoverhead-sample.config pinpoint.config 18 | cwd: /etc/pinpoint 19 | 003-command: 20 | command: sed -i "s/127.0.0.1/${PINPOINT_COLLECTOR_URL}/g" pinpoint.config 21 | cwd: /etc/pinpoint 22 | 004-command: 23 | command: sed -i "s/profiler.sampling.rate=20/profiler.sampling.rate=1/g" pinpoint.config 24 | cwd: /etc/pinpoint 25 | 005-command: 26 | command: sed -i "s/#profiler.applicationservertype=TOMCAT/profiler.applicationservertype=TOMCAT/g" pinpoint.config 27 | cwd: /etc/pinpoint 28 | 006-command: 29 | command: sed -i "s/INFO/DEBUG/g" log4j.xml 30 | cwd: /etc/pinpoint/lib -------------------------------------------------------------------------------- /.ebextensions/nginx/nginx.conf: -------------------------------------------------------------------------------- 1 | user nginx; 2 | error_log /var/log/nginx/error.log warn; 3 | pid /var/run/nginx.pid; 4 | worker_processes auto; 5 | worker_rlimit_nofile 65535; 6 | 7 | events { 8 | use epoll; 9 | worker_connections 4096; 10 | } 11 | 12 | http { 13 | include /etc/nginx/mime.types; 14 | default_type application/octet-stream; 15 | 16 | log_format main '$remote_addr - $remote_user [$time_local] "$request" ' 17 | '$status $body_bytes_sent "$http_referer" ' 18 | '"$http_user_agent" "$http_x_forwarded_for"'; 19 | 20 | include conf.d/*.conf; 21 | 22 | map $http_upgrade $connection_upgrade { 23 | default "upgrade"; 24 | } 25 | 26 | upstream springboot { 27 | server 127.0.0.1:8080; 28 | keepalive 4096; 29 | } 30 | 31 | server { 32 | listen 80 default_server; 33 | 34 | location / { 35 | proxy_pass http://springboot; 36 | proxy_http_version 1.1; 37 | proxy_set_header Connection $connection_upgrade; 38 | proxy_set_header Upgrade $http_upgrade; 39 | 40 | proxy_set_header Host $host; 41 | proxy_set_header X-Real-IP $remote_addr; 42 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 43 | } 44 | 45 | access_log /var/log/nginx/access.log main; 46 | 47 | client_header_timeout 60; 48 | client_body_timeout 60; 49 | keepalive_timeout 60; 50 | gzip off; 51 | gzip_comp_level 4; 52 | 53 | # Include the Elastic Beanstalk generated locations 54 | include conf.d/elasticbeanstalk/01_static.conf; 55 | include conf.d/elasticbeanstalk/healthd.conf; 56 | } 57 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | .gradle 3 | /build/ 4 | !gradle/wrapper/gradle-wrapper.jar 5 | bin 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | /out/ 22 | 23 | ### NetBeans ### 24 | /nbproject/private/ 25 | /nbbuild/ 26 | /dist/ 27 | /nbdist/ 28 | /.nb-gradle/ 29 | logs 30 | 31 | ### VS Code ### 32 | .vscode/ 33 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - openjdk8 4 | 5 | branches: 6 | only: 7 | - master 8 | 9 | # Travis CI 서버의 Home 10 | cache: 11 | directories: 12 | - '$HOME/.m2/repository' 13 | - '$HOME/.gradle' 14 | 15 | script: "./gradlew clean build" 16 | 17 | # 배포에 필요한 파일들만 archive 에 옮겨서 archive/archive.zip 파일로 만든다. 18 | before_deploy: 19 | - mkdir -p archive 20 | - cp Procfile archive/Procfile 21 | - cp build/libs/*.jar archive/application.jar 22 | - cp -r ./.ebextensions archive/.ebextensions 23 | - cd archive 24 | - ls -al 25 | - zip -r archive.zip application.jar .ebextensions Procfile 26 | 27 | deploy: 28 | provider: elasticbeanstalk 29 | zip_file: archive.zip # before_deploy에서 이미 archive로 이동한 상태(cd archive)라 현재 위치에서 archive.zip 전송 30 | skip_cleanup: true 31 | access_key_id: $AWS_ACCESS_KEY # declared in Travis repo settings 32 | secret_access_key: 33 | secure: $AWS_SECRET_KEY 34 | region: "ap-northeast-2" 35 | app: "dwlee-test-eb" 36 | env: "dwlee-redis-test" 37 | bucket_name: "dwlee-beanstalk" # S3 bucket name 38 | on: 39 | branch: master 40 | 41 | after_deploy: 42 | - echo "Elastic Beanstalk 배포 진행중입니다." 43 | 44 | notifications: 45 | webhooks: https://fathomless-fjord-24024.herokuapp.com/notify -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: appstart -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Spring Boot & Redis Tip 모음 2 | 3 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'org.springframework.boot' version '2.1.5.RELEASE' 3 | id 'java' 4 | } 5 | 6 | apply plugin: 'io.spring.dependency-management' 7 | 8 | group = 'com.jojoldu.blogcode' 9 | version = '0.0.1-SNAPSHOT' 10 | sourceCompatibility = '1.8' 11 | 12 | configurations { 13 | compileOnly { 14 | extendsFrom annotationProcessor 15 | } 16 | } 17 | 18 | repositories { 19 | mavenCentral() 20 | } 21 | 22 | dependencies { 23 | compile group: 'it.ozimov', name: 'embedded-redis', version: '0.7.2' 24 | // lettuce 25 | // compile('org.springframework.boot:spring-boot-starter-data-redis') 26 | 27 | // jedis 28 | compile group: 'redis.clients', name: 'jedis' 29 | compile group: 'org.apache.commons', name: 'commons-pool2', version: '2.6.2' 30 | compile ('org.springframework.boot:spring-boot-starter-data-redis') { 31 | exclude group: 'io.lettuce', module: 'lettuce-core' 32 | } 33 | 34 | implementation 'org.springframework.boot:spring-boot-starter-actuator' 35 | implementation 'org.springframework.boot:spring-boot-starter-web' 36 | compileOnly 'org.projectlombok:lombok' 37 | annotationProcessor 'org.projectlombok:lombok' 38 | testImplementation 'org.springframework.boot:spring-boot-starter-test' 39 | } 40 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jojoldu/spring-boot-redis-tip/20ebc165aa00b2364b11b157f6228e9545e3044a/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.2.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS='"-Xmx64m"' 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS="-Xmx64m" 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /posts/keys/README.md: -------------------------------------------------------------------------------- 1 | # keys는 몇건부터 문제가 있을까? 2 | 3 | 아래는 [강대명님의 Redis 운영 관리](https://coupa.ng/bhcBtj) 라는 책의 한 구절입니다. 4 | 5 | > Redis 운영 관리 P.12 6 | 일례로 Redis를 사용하는 한 회사에서 필자에게 문의해온적이 있다. 7 | 데이터양이 적을때는 Redis가 굉장히 빠르다가, **데이터가 10만개에서 20만개가 되면서는 속도가 느려지기 시작했고**, 데이터 양이 늘어날 수록 속도가 점점 느려진다는 것이였다. 8 | 해당 서비스를 살펴보니, 서비스에서 필요한 Key 목록을 keys 명령을 통해서 가져오고 있었다. 9 | 10 | Redis가 장애나면 무조건 keys 쓴 코드가 잘못됐다고 할때가 있습니다. 11 | 무조건 keys쓴게 잘못된것이긴 합니다만 **실제 성능 이슈가 key가 아닌 다른 이슈일수도 있는데**, 무조건 keys 탓으로 해버리면 **또 같은 문제가 발생할수 있다**는게 문제입니다. 12 | 정확한 원인 파악이 제일 중요합니다. 13 | 14 | 만약 100건 이하의 key만 있다면? 15 | 1만건 이하라면? 16 | keys가 문제가 될까요? 17 | 18 | 그래서 이번 시간에는 Redis의 **keys는 몇건부터 이슈가 발생할까** 확인해보겠습니다. 19 | 20 | ## 0. 프로젝트 환경 21 | 22 | 테스트 환경은 앞서 [테스트한 글](https://jojoldu.tistory.com/418)과 같습니다. 23 | 24 | * Spring Boot 2.1.5 25 | * Spring Boot Data Redis 2.1.5 26 | * Jedis 2.9.0 27 | * Lettuce 5.1.6 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /posts/lettuce-than-jedis/README.md: -------------------------------------------------------------------------------- 1 | # Jedis 보다 Lettuce 를 쓰자 2 | 3 | Java의 Redis Client는 크게 2가지가 있습니다. 4 | 5 | * [Jedis](https://github.com/xetorthio/jedis) 6 | * [Lettuce](https://github.com/lettuce-io/lettuce-core) 7 | 8 | 둘 모두 몇천개의 Star를 가질만큼 유명한 오픈소스입니다. 9 | 이번 시간에는 둘 중 어떤것을 사용해야할지에 대해 **성능 테스트 결과**를 공유하고자 합니다. 10 | 11 | > 모든 코드와 Beanstalk 설정값은 [Github](https://github.com/jojoldu/spring-boot-redis-tip)에 있으니 참고하세요. 12 | 13 | > 레디스외 병목현상을 방지하기 위해 Nginx, 커널 파라미터 등은 모두 적절하게 [튜닝](https://github.com/jojoldu/spring-boot-redis-tip/tree/master/.ebextensions)된 상태입니다. 14 | 15 | ## 0. 프로젝트 환경 16 | 17 | 의존성 환경은 아래와 같습니다. 18 | 19 | * Spring Boot 2.1.4 20 | * Spring Boot Data Redis 2.1.4 21 | * Jedis 2.9.0 22 | * Lettuce 5.1.6 23 | 24 | 그리고 테스트에 사용될 Redis Entity 코드는 아래와 같습니다. 25 | 26 | ```java 27 | @ToString 28 | @Getter 29 | @RedisHash("availablePoint") 30 | public class AvailablePoint implements Serializable { 31 | 32 | @Id 33 | private String id; // userId 34 | private Long point; 35 | private LocalDateTime refreshTime; 36 | 37 | @Builder 38 | public AvailablePoint(String id, Long point, LocalDateTime refreshTime) { 39 | this.id = id; 40 | this.point = point; 41 | this.refreshTime = refreshTime; 42 | } 43 | } 44 | ``` 45 | 46 | ```java 47 | public interface AvailablePointRedisRepository extends CrudRepository { 48 | } 49 | ``` 50 | 51 | 임의의 데이터를 저장하고, 가져올 ```Controller``` 코드는 아래와 같습니다. 52 | 53 | ```java 54 | @Slf4j 55 | @RequiredArgsConstructor 56 | @RestController 57 | public class ApiController { 58 | private final AvailablePointRedisRepository availablePointRedisRepository; 59 | 60 | @GetMapping("/") 61 | public String ok () { 62 | return "ok"; 63 | } 64 | 65 | @GetMapping("/save") 66 | public String save(){ 67 | String randomId = createId(); 68 | LocalDateTime now = LocalDateTime.now(); 69 | 70 | AvailablePoint availablePoint = AvailablePoint.builder() 71 | .id(randomId) 72 | .point(1L) 73 | .refreshTime(now) 74 | .build(); 75 | 76 | log.info(">>>>>>> [save] availablePoint={}", availablePoint); 77 | 78 | availablePointRedisRepository.save(availablePoint); 79 | 80 | return "save"; 81 | } 82 | 83 | 84 | @GetMapping("/get") 85 | public long get () { 86 | String id = createId(); 87 | return availablePointRedisRepository.findById(id) 88 | .map(AvailablePoint::getPoint) 89 | .orElse(0L); 90 | } 91 | 92 | // 임의의 키를 생성하기 위해 1 ~ 1_000_000_000 사이 랜덤값 생성 93 | private String createId() { 94 | SplittableRandom random = new SplittableRandom(); 95 | return String.valueOf(random.nextInt(1, 1_000_000_000)); 96 | } 97 | } 98 | ``` 99 | 100 | 위 ```/save``` API를 통해 테스트할 데이터를 Redis에 적재해서 사용합니다. 101 | 성능 테스트는 ```/get``` API를 통해 진행합니다. 102 | 103 | ### 0-1. EC2 사양 104 | 105 | Spring Boot가 실행되고 Redis로 요청할 EC2의 사양은 아래와 같습니다. 106 | 107 | ![ec2](./images/ec2.png) 108 | 109 | (최대한 Redis 자원을 사용하기 위해 높은 사양을 선택했습니다.) 110 | 111 | * r5d.4xLarge X 4대 112 | 113 | ### 0-2. Redis 사양 114 | 115 | 테스트에 사용될 Redis (Elastic Cache) 의 사양은 아래와 같습니다. 116 | 117 | ![spec](images/spec.png) 118 | 119 | * R5.large 120 | * Redis 5.0.3 121 | 122 | 테스트용 데이터는 대략 **1천만건**을 적재하였습니다. 123 | 124 | ![count](./images/count.png) 125 | 126 | 그리고 성능 테스트와 모니터링은 네이버의 두 제품을 사용합니다. 127 | 128 | * [Pinpoint](https://github.com/naver/pinpoint) 129 | * [Ngrinder](https://naver.github.io/ngrinder/) 130 | 131 | 자 그럼 먼저 Jedis를 테스트해보겠습니다. 132 | 133 | ## 1. Jedis 134 | 135 | Jedis는 예전부터 Java의 표준 Redis Client로 사용되었습니다. 136 | 그래서 대부분의 Java & Redis 예제가 Jedis로 되어 있는데요. 137 | Spring Boot에서는 다음과 같이 **기본 의존성인 lettuce를 제거**하고 Jedis를 등록합니다. 138 | 139 | > Spring Boot 2.0이 되고 lettuce가 기본 클라이언트가 되어서 아래와 같이 제거해야만 합니다. 140 | 141 | ```groovy 142 | dependencies { 143 | compile group: 'it.ozimov', name: 'embedded-redis', version: '0.7.2' 144 | 145 | // jedis 146 | compile group: 'redis.clients', name: 'jedis' 147 | compile group: 'org.apache.commons', name: 'commons-pool2', version: '2.6.2' 148 | compile ('org.springframework.boot:spring-boot-starter-data-redis') { 149 | exclude group: 'io.lettuce', module: 'lettuce-core' 150 | } 151 | 152 | implementation 'org.springframework.boot:spring-boot-starter-actuator' 153 | implementation 'org.springframework.boot:spring-boot-starter-web' 154 | compileOnly 'org.projectlombok:lombok' 155 | annotationProcessor 'org.projectlombok:lombok' 156 | testImplementation 'org.springframework.boot:spring-boot-starter-test' 157 | } 158 | 159 | ``` 160 | 161 | 정상적으로 수행하셨다면 첫번째 테스트인 **Connection Pool 설정 없이** 테스트 해보겠습니다. 162 | 163 | ### 1-1. Not Connection Pool 164 | 165 | Connection Pool 세팅 없이 기본적인 Jedis 설정만 사용하겠습니다. 166 | 167 | ```java 168 | @RequiredArgsConstructor 169 | @Configuration 170 | @EnableRedisRepositories 171 | public class RedisRepositoryConfig { 172 | private final RedisProperties redisProperties; 173 | 174 | //jedis 175 | @Bean 176 | public RedisConnectionFactory redisConnectionFactory() { 177 | return new JedisConnectionFactory(new RedisStandaloneConfiguration(redisProperties.getHost(), redisProperties.getPort())); 178 | } 179 | 180 | @Bean 181 | public RedisTemplate redisTemplate() { 182 | RedisTemplate redisTemplate = new RedisTemplate<>(); 183 | redisTemplate.setConnectionFactory(redisConnectionFactory()); 184 | return redisTemplate; 185 | } 186 | } 187 | ``` 188 | 189 | 다 설정하셨다면 성능 테스트를 시작해보겠습니다. 190 | 191 | > 참고로 모든 테스트는 [JVM의 Warm up Time](https://dzone.com/articles/why-many-java-performance-test)을 고려하여 2번이상 테스트하였습니다. 192 | 193 | VUser 740명으로 시도합니다. 194 | 195 | * agent는 5대 196 | * 각 agent 별 148명을 지정했습니다. 197 | * 부하 테스트 시간은 3분 198 | 199 | #### 성능 테스트 결과 200 | 201 | 부하를 주는 Ngrinder의 Agent는 평균 CPU가 15 ~ 17%를 유지했습니다. 202 | 203 | ![jedis-agent1](./images/jedis-agent1.png) 204 | 205 | > 높을수록 **단기간에 많은 요청을 주고 받았다**를 의미합니다. 206 | 207 | **TPS**는 **3만**이 나왔습니다. 208 | 209 | ![jedis-result](./images/jedis-result.png) 210 | 211 | 핀포인트로 측정한 **응답속도** 는 **100ms**가 나왔습니다. 212 | 213 | ![jedis-pinpoint](./images/jedis-pinpoint.png) 214 | 215 | 그리고 Redis의 Connection 개수는 35개를 유지했습니다. 216 | 217 | ![jedis-connection](./images/jedis-connection.png) 218 | 219 | 마지막으로 Redis의 CPU는 **20**%가 최고치였습니다. 220 | 221 | ![jedis-cpu](./images/jedis-cpu.png) 222 | 223 | 224 | 생각보다 성능이 잘나오지 않은것 같죠? 225 | 226 | 부하를 주는 Ngrinder의 Agent CPU가 20%를 넘지 못했습니다. 227 | 즉, **Redis 응답속도가 좀 더 높다면 훨씬 더 높은 TPS**가 나올수 있다는 이야기입니다. 228 | 229 | 자 그럼 같은 사양으로 **Connection Pool**을 적용해보겠습니다. 230 | 231 | ### 1-2. Use Connection Pool 232 | 233 | 첫번째 테스트로 Connection Pool 없이 진행하니 다음과 같은 결과를 얻었습니다. 234 | 235 | * TPS가 낮게 나왔습니다. 236 | * 응답속도가 높습니다. 237 | * Redis Connection과 EC2 서버의 CPU가 여유로웠습니다. 238 | * Ngrinder의 Agnet CPU가 여유로웠습니다. 239 | 240 | 241 | 그래서 두번째 테스트로 **Jedis에서 Connection Pool 설정**을 추가하고 진행해보겠습니다. 242 | 243 | 첫번째 테스트에서 만든 ```RedisRepositoryConfig.java```에 아래와 같은 설정을 추가하겠습니다. 244 | 245 | ```java 246 | @Bean 247 | public RedisConnectionFactory redisConnectionFactory() { 248 | RedisStandaloneConfiguration config = new RedisStandaloneConfiguration(redisProperties.getHost(), redisProperties.getPort()); 249 | JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(config); 250 | jedisConnectionFactory.setPoolConfig(jedisPoolConfig()); 251 | return jedisConnectionFactory; 252 | } 253 | 254 | private JedisPoolConfig jedisPoolConfig() { 255 | final JedisPoolConfig poolConfig = new JedisPoolConfig(); 256 | poolConfig.setMaxTotal(128); 257 | poolConfig.setMaxIdle(128); 258 | poolConfig.setMinIdle(36); 259 | poolConfig.setTestOnBorrow(true); 260 | poolConfig.setTestOnReturn(true); 261 | poolConfig.setTestWhileIdle(true); 262 | poolConfig.setMinEvictableIdleTimeMillis(Duration.ofSeconds(60).toMillis()); 263 | poolConfig.setTimeBetweenEvictionRunsMillis(Duration.ofSeconds(30).toMillis()); 264 | poolConfig.setNumTestsPerEvictionRun(3); 265 | poolConfig.setBlockWhenExhausted(true); 266 | return poolConfig; 267 | } 268 | ``` 269 | 270 | Connection Pool 옵션을 넣고, 다시 성능 테스트를 시작해보면! 271 | 272 | TPS는 **5.2만**이 나왔습니다. 273 | 274 | ![jedis-pool-result](./images/jedis-pool-result.png) 275 | 276 | 핀포인트 결과는 평균 **50ms**가 나왔습니다. 277 | 278 | ![jedis-pool-pinpoint](./images/jedis-pool-pinpoint.png) 279 | 280 | 그리고 Redis의 Connection 수가 **515**개로 첫번째 대비 10배이상 높아졌습니다. 281 | 282 | ![jedis-pool-connection](./images/jedis-pool-connection.png) 283 | 284 | Redis의 CPU 역시 3배이상 올라 **69.5**%를 유지했습니다. 285 | 286 | ![jedis-pool-pu](./images/jedis-pool-cpu.png) 287 | 288 | Connection Pool을 적용하고 TPS가 전보다 50%이상 향상되었습니다. 289 | 그렇지만! 290 | 291 | 문제는 **이보다 더 높은 TPS를 맞추려면 Redis의 CPU가 90%를 넘을수도** 있습니다. 292 | 293 | 이건 충분히 문제가 되겠죠? 294 | 295 | Jedis를 **꼭** 써야한다면 성능 테스트를 통해 **적절한 Pool Size**를 찾아보셔야만 합니다. 296 | 297 | 298 | > 제가 사내 시스템에 Redis를 적용하던 시점에는 Jedis가 더이상 업데이트되지 않고 있었습니다. 299 | 2016년 9월이후로 더이상 릴리즈 되지 않다가, 2018년 11월부터 다시 릴리즈가 되고 있습니다. 300 | 혹시나 jedis를 사용하실 분들은 [릴리즈 노트](https://github.com/xetorthio/jedis/releases)를 참고해보세요. 301 | 302 | 마지막으로 Lettuce를 적용해서 테스트해보겠습니다. 303 | 304 | ## 2. Lettuce 305 | 306 | 307 | Lettuce는 Netty (**비동기 이벤트 기반 고성능 네트워크 프레임워크**) 기반의 Redis 클라이언트입니다. 308 | 비동기로 요청을 처리하기 때문에 **고성능을 자랑**합니다. 309 | 310 | 자 그럼 Lettuce 로 코드를 변경해서 다시 테스트를 진행해보겠습니다. 311 | 312 | RedisRepositoryConfig 설정 코드는 아래와 같습니다. 313 | 314 | 315 | ```java 316 | @RequiredArgsConstructor 317 | @Configuration 318 | @EnableRedisRepositories 319 | public class RedisRepositoryConfig { 320 | private final RedisProperties redisProperties; 321 | 322 | // lettuce 323 | @Bean 324 | public RedisConnectionFactory redisConnectionFactory() { 325 | return new LettuceConnectionFactory(redisProperties.getHost(), redisProperties.getPort()); 326 | } 327 | 328 | @Bean 329 | public RedisTemplate redisTemplate() { 330 | RedisTemplate redisTemplate = new RedisTemplate<>(); 331 | redisTemplate.setConnectionFactory(redisConnectionFactory()); 332 | return redisTemplate; 333 | } 334 | } 335 | ``` 336 | 337 | build.gradle 에서는 편하게 spring-data-redis만 추가하면 기본의존성으로 사용됩니다. 338 | 339 | ```groovy 340 | dependencies { 341 | compile group: 'it.ozimov', name: 'embedded-redis', version: '0.7.2' 342 | // lettuce 343 | compile ('org.springframework.boot:spring-boot-starter-data-redis') 344 | 345 | implementation 'org.springframework.boot:spring-boot-starter-actuator' 346 | implementation 'org.springframework.boot:spring-boot-starter-web' 347 | compileOnly 'org.projectlombok:lombok' 348 | annotationProcessor 'org.projectlombok:lombok' 349 | testImplementation 'org.springframework.boot:spring-boot-starter-test' 350 | } 351 | ``` 352 | 353 | 이제 테스트를 해보면! 354 | 355 | TPS는 무려 **10만**을 처리합니다. 356 | 357 | ![lettuce-result](./images/lettuce-result.png) 358 | 359 | 실제로 워낙 빠르게 처리하다보니 **대량의 요청을 해야하는 agent CPU가 70%까지** 올라갔습니다 360 | 361 | ![lettuce-agent1](./images/lettuce-agent1.png) 362 | 363 | 핀포인트의 응답속도는 **7.5**ms 로 아주 고속으로 응답을 주고 있습니다. 364 | 365 | ![lettuce-pinpoint](./images/lettuce-pinpoint.png) 366 | 367 | 그리고 Redis의 Connection은 **6-7**개를 유지합니다. 368 | 369 | ![lettuce-connection](./images/lettuce-connection.png) 370 | 371 | CPU 역시 **7**%밖에 되지 않습니다. 372 | 373 | ![lettuce-cpu](./images/lettuce-cpu.png) 374 | 375 | 앞에서 진행된 Jedis에 비해 압도적인 성능 차이가 보이시죠? 376 | 377 | ## 3. 결론 378 | 379 | 위에서 진행한 3개 테스트에 대한 결과입니다. 380 | 381 | | | TPS | Redis CPU | Redis Connection | 응답 속도 | 382 | |---------------------------|---------|-----------|------------------|-----------| 383 | | jedis **no** connection pool | 31,000 | 20% | 35 | 100ms | 384 | | jedis **use** connection pool | 55,000 | 69.5% | 515 | 50ms | 385 | | lettuce | **100,000** | **7**% | **6** | **7.5**ms | 386 | 387 | Lettuce는 TPS/CPU/Connection 개수/응답속도 등 **전 분야에서 우위에 있습니다**. 388 | 389 | **Lettuce를 사용**합시다. 390 | 391 | Jedis에 비해 몇배 이상의 성능과 하드웨어 자원 절약이 가능합니다. 392 | 이외에도 몇가지 이유가 더 있는데요. 393 | 394 | * 잘 만들어진 문서 395 | * [공식 문서](https://lettuce.io/core/release/reference/) 396 | * 깔끔하게 디자인된 코드 397 | * Jedis의 JedisPool, JedisCluster 등의 여러 생성자들을 보면 어떤걸 써야할지 종잡을수가 없습니다. 398 | * 빠른 피드백 399 | * Lettuce의 경우 Issue 제기시 당일 혹은 2일안에 피드백을 남깁니다. 400 | * 반면 Jedis의 경우 **작년까지는 피드백을 거의 주지 않고 있었습니다** 401 | * 최근엔 피드백 주기가 빨라졌습니다. 402 | 403 | ## 참고 404 | 405 | * [Azure-Redis-Java-Best-Practices.md](https://gist.github.com/warrenzhu25/1beb02a09b6afd41dff2c27c53918ce7#why-lettuce) 406 | * [Why is Lettuce the default Redis client used in Spring Session Redis](https://github.com/spring-projects/spring-session/issues/789) -------------------------------------------------------------------------------- /posts/lettuce-than-jedis/images/count.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jojoldu/spring-boot-redis-tip/20ebc165aa00b2364b11b157f6228e9545e3044a/posts/lettuce-than-jedis/images/count.png -------------------------------------------------------------------------------- /posts/lettuce-than-jedis/images/ec2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jojoldu/spring-boot-redis-tip/20ebc165aa00b2364b11b157f6228e9545e3044a/posts/lettuce-than-jedis/images/ec2.png -------------------------------------------------------------------------------- /posts/lettuce-than-jedis/images/jedis-agent1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jojoldu/spring-boot-redis-tip/20ebc165aa00b2364b11b157f6228e9545e3044a/posts/lettuce-than-jedis/images/jedis-agent1.png -------------------------------------------------------------------------------- /posts/lettuce-than-jedis/images/jedis-connection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jojoldu/spring-boot-redis-tip/20ebc165aa00b2364b11b157f6228e9545e3044a/posts/lettuce-than-jedis/images/jedis-connection.png -------------------------------------------------------------------------------- /posts/lettuce-than-jedis/images/jedis-cpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jojoldu/spring-boot-redis-tip/20ebc165aa00b2364b11b157f6228e9545e3044a/posts/lettuce-than-jedis/images/jedis-cpu.png -------------------------------------------------------------------------------- /posts/lettuce-than-jedis/images/jedis-pinpoint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jojoldu/spring-boot-redis-tip/20ebc165aa00b2364b11b157f6228e9545e3044a/posts/lettuce-than-jedis/images/jedis-pinpoint.png -------------------------------------------------------------------------------- /posts/lettuce-than-jedis/images/jedis-pool-connection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jojoldu/spring-boot-redis-tip/20ebc165aa00b2364b11b157f6228e9545e3044a/posts/lettuce-than-jedis/images/jedis-pool-connection.png -------------------------------------------------------------------------------- /posts/lettuce-than-jedis/images/jedis-pool-cpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jojoldu/spring-boot-redis-tip/20ebc165aa00b2364b11b157f6228e9545e3044a/posts/lettuce-than-jedis/images/jedis-pool-cpu.png -------------------------------------------------------------------------------- /posts/lettuce-than-jedis/images/jedis-pool-pinpoint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jojoldu/spring-boot-redis-tip/20ebc165aa00b2364b11b157f6228e9545e3044a/posts/lettuce-than-jedis/images/jedis-pool-pinpoint.png -------------------------------------------------------------------------------- /posts/lettuce-than-jedis/images/jedis-pool-result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jojoldu/spring-boot-redis-tip/20ebc165aa00b2364b11b157f6228e9545e3044a/posts/lettuce-than-jedis/images/jedis-pool-result.png -------------------------------------------------------------------------------- /posts/lettuce-than-jedis/images/jedis-result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jojoldu/spring-boot-redis-tip/20ebc165aa00b2364b11b157f6228e9545e3044a/posts/lettuce-than-jedis/images/jedis-result.png -------------------------------------------------------------------------------- /posts/lettuce-than-jedis/images/lettuce-agent1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jojoldu/spring-boot-redis-tip/20ebc165aa00b2364b11b157f6228e9545e3044a/posts/lettuce-than-jedis/images/lettuce-agent1.png -------------------------------------------------------------------------------- /posts/lettuce-than-jedis/images/lettuce-connection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jojoldu/spring-boot-redis-tip/20ebc165aa00b2364b11b157f6228e9545e3044a/posts/lettuce-than-jedis/images/lettuce-connection.png -------------------------------------------------------------------------------- /posts/lettuce-than-jedis/images/lettuce-cpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jojoldu/spring-boot-redis-tip/20ebc165aa00b2364b11b157f6228e9545e3044a/posts/lettuce-than-jedis/images/lettuce-cpu.png -------------------------------------------------------------------------------- /posts/lettuce-than-jedis/images/lettuce-pinpoint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jojoldu/spring-boot-redis-tip/20ebc165aa00b2364b11b157f6228e9545e3044a/posts/lettuce-than-jedis/images/lettuce-pinpoint.png -------------------------------------------------------------------------------- /posts/lettuce-than-jedis/images/lettuce-result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jojoldu/spring-boot-redis-tip/20ebc165aa00b2364b11b157f6228e9545e3044a/posts/lettuce-than-jedis/images/lettuce-result.png -------------------------------------------------------------------------------- /posts/lettuce-than-jedis/images/lettuce-time.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jojoldu/spring-boot-redis-tip/20ebc165aa00b2364b11b157f6228e9545e3044a/posts/lettuce-than-jedis/images/lettuce-time.png -------------------------------------------------------------------------------- /posts/lettuce-than-jedis/images/spec.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jojoldu/spring-boot-redis-tip/20ebc165aa00b2364b11b157f6228e9545e3044a/posts/lettuce-than-jedis/images/spec.png -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | } 5 | } 6 | rootProject.name = 'spring-boot-redis-tip' 7 | -------------------------------------------------------------------------------- /src/main/java/com/jojoldu/blogcode/springbootredistip/Application.java: -------------------------------------------------------------------------------- 1 | package com.jojoldu.blogcode.springbootredistip; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class Application { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(Application.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/jojoldu/blogcode/springbootredistip/config/EmbeddedRedisConfig.java: -------------------------------------------------------------------------------- 1 | package com.jojoldu.blogcode.springbootredistip.config; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.beans.factory.annotation.Value; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.context.annotation.Profile; 8 | import redis.embedded.RedisServer; 9 | 10 | import javax.annotation.PostConstruct; 11 | import javax.annotation.PreDestroy; 12 | import java.io.IOException; 13 | 14 | @Slf4j //lombok 15 | @RequiredArgsConstructor 16 | @Profile("local") // profile이 local일때만 활성화 17 | @Configuration 18 | public class EmbeddedRedisConfig { 19 | 20 | private final RedisProperties redisProperties; 21 | 22 | private RedisServer redisServer; 23 | 24 | @PostConstruct 25 | public void redisServer() { 26 | redisServer = new RedisServer(redisProperties.getPort()); 27 | redisServer.start(); 28 | } 29 | 30 | @PreDestroy 31 | public void stopRedis() { 32 | if (redisServer != null) { 33 | redisServer.stop(); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/jojoldu/blogcode/springbootredistip/config/RedisProperties.java: -------------------------------------------------------------------------------- 1 | package com.jojoldu.blogcode.springbootredistip.config; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | import org.springframework.boot.context.properties.ConfigurationProperties; 6 | import org.springframework.stereotype.Component; 7 | 8 | @Getter 9 | @Setter 10 | @ConfigurationProperties("spring.redis") 11 | @Component 12 | public class RedisProperties { 13 | private String host; 14 | private int port; 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/jojoldu/blogcode/springbootredistip/config/RedisRepositoryConfig.java: -------------------------------------------------------------------------------- 1 | package com.jojoldu.blogcode.springbootredistip.config; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.data.redis.connection.RedisConnectionFactory; 7 | import org.springframework.data.redis.connection.RedisStandaloneConfiguration; 8 | import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; 9 | import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; 10 | import org.springframework.data.redis.core.RedisTemplate; 11 | import org.springframework.data.redis.repository.configuration.EnableRedisRepositories; 12 | import redis.clients.jedis.JedisPool; 13 | import redis.clients.jedis.JedisPoolConfig; 14 | 15 | import java.time.Duration; 16 | 17 | @RequiredArgsConstructor 18 | @Configuration 19 | @EnableRedisRepositories 20 | public class RedisRepositoryConfig { 21 | private final RedisProperties redisProperties; 22 | 23 | // jedis 24 | @Bean 25 | public RedisConnectionFactory redisConnectionFactory() { 26 | RedisStandaloneConfiguration config = new RedisStandaloneConfiguration(redisProperties.getHost(), redisProperties.getPort()); 27 | return new JedisConnectionFactory(config); 28 | } 29 | 30 | // jedis connection pool 31 | // @Bean 32 | // public RedisConnectionFactory redisConnectionFactory() { 33 | // RedisStandaloneConfiguration config = new RedisStandaloneConfiguration(redisProperties.getHost(), redisProperties.getPort()); 34 | // JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(config); 35 | // jedisConnectionFactory.setPoolConfig(jedisPoolConfig()); 36 | // return jedisConnectionFactory; 37 | // } 38 | // 39 | // private JedisPoolConfig jedisPoolConfig() { 40 | // final JedisPoolConfig poolConfig = new JedisPoolConfig(); 41 | // poolConfig.setMaxTotal(128); 42 | // poolConfig.setMaxIdle(128); 43 | // poolConfig.setMinIdle(36); 44 | // poolConfig.setTestOnBorrow(true); 45 | // poolConfig.setTestOnReturn(true); 46 | // poolConfig.setTestWhileIdle(true); 47 | // poolConfig.setMinEvictableIdleTimeMillis(Duration.ofSeconds(60).toMillis()); 48 | // poolConfig.setTimeBetweenEvictionRunsMillis(Duration.ofSeconds(30).toMillis()); 49 | // poolConfig.setNumTestsPerEvictionRun(3); 50 | // poolConfig.setBlockWhenExhausted(true); 51 | // return poolConfig; 52 | // } 53 | 54 | // lettuce 55 | // @Bean 56 | // public RedisConnectionFactory redisConnectionFactory() { 57 | // return new LettuceConnectionFactory(redisProperties.getHost(), redisProperties.getPort()); 58 | // } 59 | 60 | @Bean 61 | public RedisTemplate redisTemplate() { 62 | RedisTemplate redisTemplate = new RedisTemplate<>(); 63 | redisTemplate.setConnectionFactory(redisConnectionFactory()); 64 | return redisTemplate; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/com/jojoldu/blogcode/springbootredistip/controller/ApiController.java: -------------------------------------------------------------------------------- 1 | package com.jojoldu.blogcode.springbootredistip.controller; 2 | 3 | import com.jojoldu.blogcode.springbootredistip.point.AvailablePoint; 4 | import com.jojoldu.blogcode.springbootredistip.point.AvailablePointRedisRepository; 5 | import lombok.RequiredArgsConstructor; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.springframework.data.redis.core.RedisTemplate; 8 | import org.springframework.web.bind.annotation.GetMapping; 9 | import org.springframework.web.bind.annotation.RestController; 10 | 11 | import java.time.LocalDateTime; 12 | import java.util.Set; 13 | import java.util.SplittableRandom; 14 | 15 | @Slf4j 16 | @RequiredArgsConstructor 17 | @RestController 18 | public class ApiController { 19 | private final AvailablePointRedisRepository availablePointRedisRepository; 20 | private final RedisTemplate redisTemplate; 21 | 22 | @GetMapping("/") 23 | public String ok () { 24 | return "ok"; 25 | } 26 | 27 | @GetMapping("/keys") 28 | public String keys() { 29 | Set keys = redisTemplate.keys("*"); 30 | return "keys"; 31 | } 32 | 33 | @GetMapping("/save") 34 | public String save(){ 35 | String randomId = createId(); 36 | LocalDateTime now = LocalDateTime.now(); 37 | 38 | AvailablePoint availablePoint = AvailablePoint.builder() 39 | .id(randomId) 40 | .point(1L) 41 | .refreshTime(now) 42 | .build(); 43 | 44 | log.info(">>>>>>> [save] availablePoint={}", availablePoint); 45 | 46 | availablePointRedisRepository.save(availablePoint); 47 | 48 | return "save"; 49 | } 50 | 51 | @GetMapping("/get") 52 | public long get () { 53 | String id = createId(); 54 | return availablePointRedisRepository.findById(id) 55 | .map(AvailablePoint::getPoint) 56 | .orElse(0L); 57 | } 58 | 59 | @GetMapping("/get2") 60 | public long get2 () { 61 | String id = createId(); 62 | return availablePointRedisRepository.findById(id) 63 | .map(AvailablePoint::getPoint) 64 | .orElse(0L); 65 | } 66 | 67 | private String createId() { 68 | SplittableRandom random = new SplittableRandom(); 69 | return String.valueOf(random.nextInt(1, 1_000_000_000)); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/com/jojoldu/blogcode/springbootredistip/point/AvailablePoint.java: -------------------------------------------------------------------------------- 1 | package com.jojoldu.blogcode.springbootredistip.point; 2 | 3 | import lombok.Builder; 4 | import lombok.Getter; 5 | import lombok.ToString; 6 | import org.springframework.data.annotation.Id; 7 | import org.springframework.data.redis.core.RedisHash; 8 | 9 | import java.io.Serializable; 10 | import java.time.LocalDateTime; 11 | 12 | @ToString 13 | @Getter 14 | @RedisHash("availablePoint") 15 | public class AvailablePoint implements Serializable { 16 | 17 | @Id 18 | private String id; // userId 19 | private Long point; 20 | private LocalDateTime refreshTime; 21 | 22 | @Builder 23 | public AvailablePoint(String id, Long point, LocalDateTime refreshTime) { 24 | this.id = id; 25 | this.point = point; 26 | this.refreshTime = refreshTime; 27 | } 28 | } -------------------------------------------------------------------------------- /src/main/java/com/jojoldu/blogcode/springbootredistip/point/AvailablePointRedisRepository.java: -------------------------------------------------------------------------------- 1 | package com.jojoldu.blogcode.springbootredistip.point; 2 | 3 | import org.springframework.data.repository.CrudRepository; 4 | 5 | public interface AvailablePointRedisRepository extends CrudRepository { 6 | } 7 | -------------------------------------------------------------------------------- /src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | redis: 3 | host: localhost 4 | port: 6379 5 | profiles: 6 | active: local 7 | 8 | logging.path: logs/spring-redis-tip/ 9 | 10 | --- 11 | spring: 12 | profiles: local 13 | 14 | --- 15 | spring: 16 | profiles: real 17 | -------------------------------------------------------------------------------- /src/main/resources/logback-spring.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/main/resources/logback/application-appender.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ${LOG_PATH}app.log 5 | 6 | ${LOG_PATH}backup/app-%d{yyyy-MM-dd}.%i.log.zip 7 | 60 8 | 9 | 100MB 10 | 11 | 12 | 13 | utf8 14 | %d{yyyy-MM-dd HH:mm:ss} %-5level --- [%thread] %logger{35} : %msg %n 15 | 16 | -------------------------------------------------------------------------------- /src/main/resources/logback/error-appender.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ${LOG_PATH}error.log 5 | 6 | ${LOG_PATH}backup/error-%d{yyyy-MM-dd}.%i.log.zip 7 | 60 8 | 9 | 100MB 10 | 11 | 12 | 13 | utf8 14 | %d{yyyy-MM-dd HH:mm:ss} %-5level --- [%thread] %logger{35} : %msg %n 15 | 16 | 17 | ERROR 18 | 19 | -------------------------------------------------------------------------------- /src/test/java/com/jojoldu/blogcode/springbootredistip/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.jojoldu.blogcode.springbootredistip; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | @RunWith(SpringRunner.class) 9 | @SpringBootTest 10 | public class ApplicationTest { 11 | 12 | @Test 13 | public void contextLoads() { 14 | } 15 | 16 | } 17 | --------------------------------------------------------------------------------