ABOUT ME

Today
Yesterday
Total
  • Redis 설치부터 사용까지
    DB 2024. 2. 19. 21:44

    Redis 란?

    https://redis.io/

     

    Redis

    Redis is an open source (BSD licensed), in-memory data structure store, used as a database, cache, and message broker

    redis.io

     

    위의 본문에 나온데로, 인 메모리 데이터 저장소로서

    캐싱, 벡터 데이터, 문서 데이터, 스트리밍 엔진, 그리고 메시지 브로커로서 사용되는 오픈소스

     

    추가적으로 key-value 쌍의 데이터베이스이다.

     

    여기서 말하는 인 메모리 데이터 저장소란?

     

    데이터 저장에 내부 메모리를 주로 사용하는 목적별 데이터베이스

     

    즉, 우리 컴퓨터를 구성하는 개체 중 메인 메모리(RAM)에 해당된다.

     

    따라서 보통의 데이터베이스는 하드디스크에 접근하여 데이터를 들고와야 하여 응답시간이 좀 걸리지만

     

    인메모리 방식은 RAM에서 바로 들고오기에 응답시간이 매우 빠르다.

     

    하지만 RAM은 휘발성 메모리기 때문에, 전원이 손실되어 꺼지게 되면 데이터가 손실된다.

     

    따라서 특수 목적의 데이터로 사용하며, 주로 캐싱, 실시간 유저 랭킹 등에 사용된다.

     

    Redis는 String, hash, list, set, sorted set 등의 자료구조를 제공한다.

     

    필자가 주로 사용했던 자료구조는 sorted set으로서 가중치에 따라 정렬된 상태를 유지하는 자료구조이다.

     

    또한 Redis는 싱글 스레드 형태를 유지한다.

     

    따라서 데이터의 원자성을 보장하게 된다.

     

    https://redis.io/docs/management/optimization/latency/ 에서 Single threaded nature of Redis를 보면

     

    " Redis는 대부분 싱글 스레드 기반으로 설계 되어있습니다.

     

    이것은 멀티플렉싱이라 불리는 기술을 사용함으로서 모든 클라이언트의 요청들을 단일 프로세스에서 처리합니다.

     

    레디스가 한번에 하나의 요청을 처리하기 때문에 모든 요청들은 순차적으로 처리됩니다.

     

    이러한 처리를 위해 Redis는 대부분 싱글스레드로 이루어져 있고, 모든 시스템이 싱글 스레드 기반인 것은 아닙니다.

     

    Redis는 느린 I/O 작업을 백그라운드에서 실행하기 위해 스레드를 사용합니다. "

     

    여기서 알아 볼 수 있는것은 기본적으로 클라이언트의 요청은 Single Thread로 처리하여 원자성이 보장되고

     

    DB I/O를 처리하기위한 백그라운드 추가 Thread가 존재하게 된다.

     

    따라서 클라이언트의 요청을 처리하는것은 단일 스레드가 맞고, Redis 자체가 단일스레드로만 이루어진것은 틀리게 된다.

     

    I/O 멀티플렉싱에 대한 설명은 아래의 링크를 참조

     

    https://blog.naver.com/n_cloudplatform/222189669084

     

    [네이버클라우드 기술&경험] IO Multiplexing (IO 멀티플렉싱) 기본 개념부터 심화까지 -1부-

    안녕하세요, 네이버 클라우드 플랫폼입니다. 이번 포스팅을 통해 다양한 Multiplexing 기법을 알려드리려 ...

    blog.naver.com

     

     

     

    우선 Redis를 설치하고 사용하는 기법 및 GUI 연결까지 해보자.

     

    Redis 설치하기

    필자가 설치했던 Redis 설치환경은 아래와 같다.

    • OS : Ubuntu 22.04.3 LTS
    • Build : Gradle
    • Docker

    우선 Docker 와 Linux 명령에 대한 기본 사용법을 알고있다는 가정아래에 기술한다.

     

    Docker 명령어로 redis 이미지를 다운받는다.

    docker pull redis

     

    그리고 다운받은 이미지를 기준으로 run 시키기전 기본적으로 Redis는 6379 포트를 사용하므로

     

    그대로 사용할 것 같으면 6379번 포트가 누가 사용중인지 알아야한다.

    sudo netstat -lntp

     

    위의 명령을 Linux에서 쳤을 때, 6379번이 보이면 다른 소프트웨어가 사용중인 것

     

    만약 다른 프로그램이 사용중이지 않는다면

    docker run -d -p 6379:6379 --name redis redis:latest --requirepass "<password>"

     

    다른 프로그램이 사용중이라면 

    docker run -d -p <open-port>:6379 --name redis redis:latest --requirepass "<password>"

     

    여기서 말하는 open-port는 외부에서 접근 가능한 포트여야 함을 의미하며

     

    password는 redis가 설치된 bash에서 커멘드라인으로 redis에 접속하기위한 redis-cli를 사용할 때 필요한 비밀번호이다.

     

    정상적으로 실행중인지 확인하고

    docker ps -a

     

    redis-cli로 접근 해보자

    docker exec -it redis redis-cli -a <password>

     

    잘 접속되었다면 127.0.0.1> 이런식으로 표기되어 있을 것이다.

     

    이제 Redis GUI툴인 Redis insight를 설치하고 연결 해 보자.

     

    https://redis.com/redis-enterprise/redis-insight/

     

    위의 링크를 타고 들어가면 Download RedisInsight 가 있고 이걸 누르면 자동으로 스크롤이 내려간다.

     

    여기서 OS 선택하고 Company에다가 그냥 아무거나 입력하면 다운받을 수 있다.

     

    그리고 Redis Insight를 켜면 된다.

     

    그리고 아래쪽에 Connect Your Databases  > Add connection details manually를 클릭한다.

     

     

    저걸 타고 들어가면 현재 Docker 상에서 돌아가고 있는 Redis의 접속정보를 적는 곳이 나온다.

     

     

    여기서 접속정보를 적는다.

     

    Host 는 접속 주소, Port의 경우 docker 로 redis를 실행할 때 콜론( : ) 기준으로 왼쪽 포트번호와 동일하게 적는다.

     

    Database Alias 는 이 Redis insight에서 사용할 별칭을 정해두는 것이며

     

    Username은 따로 공란으로 두어도 되고, Password는 아까 redis를 실행할 때 --requirepass 옵션으로 추가하였던 비밀번호를 적는다.

     

    그리고 정상적으로 기입하였다면 Test Connection을 눌러보자

     

    정상적이라면 위와 같은 푸쉬알림이 발생한다.

     

    이제 Test Connection을 통과했다면, Add Redis Database를 눌러서 추가하자.

     

    그러면 Redis 목록으로 넘어가게 되는데, 방금 추가한 녀석을 누르고 들어가면

     

     

    위와 같이 창이 뜨게되고, 가려놓은곳은 아까 기입한 alias가 보일 것이다.

     

    왼쪽과 오른쪽으로 구분되어있는데, 왼쪽이 key 오른쪽이 해당 key에 들어있는 value들이다.

     

    이제 Springboot와 Redis를 연결해보자.

     

    Springboot 와 Redis 연결하기

    현재 포스팅에서 사용한 Springboot 버전은 다음과 같다.

    • Springboot : 3.2.2
    • Build : Gradle

    Java에서 Redis를 설치하고 운용하는 주요 라이브러리에는 Jedis, Lettuce가 있다.

     

    과거에는 Jedis를 주로 사용하였으며, 지금은 성능상 뛰어난 Lettuce를 권장하고 있다.

     

    왜 그런지에 대해서는 https://jojoldu.tistory.com/418 여기를 참고하자

     

     

    기본적으로 start.spring.io 에서 프로젝트를 생성했다는 가정아래에 Springboot 에서 Redis로 연결하는 디펜던시를 추가한다.

     

    implementation 'org.springframework.boot:spring-boot-starter-data-redis'

     

    그리고 application.properties에서 redis접속정보를 적는다.

    redis.port=<포트번호>
    redis.host=<Redis가 설치된 주소>
    redis.password=<--requirepass 옵션으로 준 password값>

     

    그리고 Lettuce를 사용하여 연결 객체를 만들어내는 설정과 RedisTemplate 설정을 위한 RedisConfigure.java 파일을 만든다.

     

    그리고 아래와 같이 작성하자.

    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.data.redis.connection.RedisConnectionFactory;
    import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
    import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.data.redis.serializer.StringRedisSerializer;
    
    @Configuration // 설정파일 이므로 실행과 동시에 Springboot에서 설저앞일로 인식하도록
    public class RedisConfigure {
       @Value("${redis.host}") // application.properties에서 필요한것들 매핑
       private String host;
       @Value("${redis.port}")
       private int port;
       @Value("${redis.password}")
       private String password;
    	
        // 둘다 스프링 Bean 컨테이너에 등록하여 단일 객체를 주입받아서 사용하도록 설정한다.
       @Bean
        public RedisConnectionFactory redisConnectionFactory(){
           final RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
           redisStandaloneConfiguration.setHostName(host); // 가져와서 쓰기
           redisStandaloneConfiguration.setPort(port);
           redisStandaloneConfiguration.setPassword(password);
           return new LettuceConnectionFactory(redisStandaloneConfiguration); // Lettuce 타입으로 연결 객체생성
       }
    
       @Bean
        public RedisTemplate<String, Object> redisTemplate(){
        // Redis에 저장할 데이터형식을 제공한다.
        // 어떤 connection을 사용하는지 명시하고
        // key, value에 대한 어떤 직렬화를 사용할 것인지 명시한다.
           RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
           redisTemplate.setConnectionFactory(redisConnectionFactory());
           redisTemplate.setKeySerializer(new StringRedisSerializer());
           redisTemplate.setValueSerializer(new StringRedisSerializer());
           return redisTemplate;
       }
    
    }

     

    이제 Redis의 Sorted Set을 사용하여 데이터를 간단하게 넣고 빼고 조회하는것 까지 이해해보자.

    Redis - Sorted Set 실습해보기

    실습에 사용되는 추가적인 툴은 Post man이다.

     

    Redis의 Sorted Set은 기본적으로 key-value쌍인 Redis 자료구조에서

     

    Value에 Weight가 붙어 정렬된 상태를 유지하게 된다.

     

    이러한 Redis의 Sorted Set을 사용하여 유저 랭킹을 관리하는 웹서버를 만들어 보자.

     

     

    MyRedisController.java와 MyRedisService 인터페이스를 만든다.

     

    MyRedisService에는 필요한 함수만 대충 적어놓고 MyRedisServiceImpl을 만들어서 implements 해준다.

     

    그리고 컨트롤러단에는 redis에 데이터를 넣을 endpoint와 redis에서 정렬된 데이터 리스트를 뽑을 endpoint를 만들어준다.

     

    서비스단에서는 redisTemplate 객체를 들고와서 SortedSet 자료구조의 형태인 opsForZSet()을 호출 후 add() 메서드로 삽입한다.

     

    유저로부터 받은 UserDto 에서 Score와 name을 들고와서 Score -> score에 넣고 Name을 value로 넣는다.

     

    score기준으로 큰것부터 조회해야 하므로 reverseRangeWithScores() 함수를 호출한다.

     

    그걸 구현한 코드가 아래에 있다.

     

    package com.example.redis;
    
    import org.springframework.http.HttpStatus;
    import org.springframework.http.ResponseEntity;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RestController;
    
    import java.util.List;
    
    @RestController
    public class MyRedisController {
    
        private final MyRedisService service;
    
        public MyRedisController(MyRedisService service){
            this.service = service;
        }
    
        @GetMapping("/user")
        public ResponseEntity<?> getRank(){
            List<UserDto> userList = service.getRank();
            return new ResponseEntity<>(userList, HttpStatus.OK);
        }
    
        @PostMapping("/user")
        public ResponseEntity<?> addUser(@RequestBody UserDto userDto){
            service.insertUser(userDto);
            return new ResponseEntity<>(null, HttpStatus.OK);
        }
    
    }

     

    package com.example.redis;
    
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.data.redis.core.ZSetOperations;
    import org.springframework.stereotype.Service;
    
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Set;
    
    import static java.util.Arrays.stream;
    
    @Service
    public class MyRedisServiceImpl implements MyRedisService{
    
        private final RedisTemplate<String, Object> redisTemplate;
        // RedisConfigure에서 정의한 RedisTemplate Bean주입
    
        public MyRedisServiceImpl(RedisTemplate<String, Object> redisTemplate){
            this.redisTemplate = redisTemplate;
        }
    
        @Override
        public void insertUser(UserDto userDto) {
            redisTemplate.opsForZSet().add("ranking", userDto.getUserName(), userDto.getScore());
            // opsForZSet() : SortedSet 호출 
    		// add() 메서드로 SortedSet에 ranking이라는 키 값에 유저로부터 받은 이름과 스코어를 그대로 추가 
        }
    
        @Override
        public List<UserDto> getRank() {
           Set<ZSetOperations.TypedTuple<Object>> userRanking = redisTemplate.opsForZSet().reverseRangeWithScores("ranking", 0, -1);
           // redis에서 SortedSet을 꺼내고 정렬 역순으로 자바에 있는 자료구조로 꺼낸다.
           // 이 때 reverseRangeWithScores() 메서드를 사용하는데 인자로 각각, key, start, end 고 역 인덱싱을 지원하기에 지금 쓰여있는건 모두를 들고오는것
            List<ZSetOperations.TypedTuple<Object>> list = userRanking.stream().toList();
            // List 자료구조로 변경하고 TypedTuple타입은 Redis에서 getValue() 와 getScore()메서드를 호출 할 수 있음
            List<UserDto> result = new ArrayList<>();
           for(ZSetOperations.TypedTuple<Object> object : list){
               UserDto userDto = new UserDto();
               userDto.setUserName((String)object.getValue());
               userDto.setScore(object.getScore());
               result.add(userDto);
           }
            return result;
        }
    }

     

     

     

     

     

     

     

     

     

     

     

Designed by Tistory.