Spring MicroService 25 Spring MicroService 분산 캐싱
포스트
취소

Spring MicroService 25 Spring MicroService 분산 캐싱

우리는 지난시간에 비동기 메세지를 처리하면서 카프카와 주키퍼를 활용해서 메세지 생산자 , 메세지 소비자를 만들어 empClient 에서 empClient 의 생성 , 삭제 , 읽기 , 수정이 일어났을때 메세지 발행 , 그리고 그 메세지를 savingMoney 에서 소비하는 모습을 보였습이다 지난시간에는 메세지를 직접 발행 소비만 하는 과정을 보였지 이 과정이 왜 결합을 약하게 만들 수 있는지는 이번시간에 다루어볼려고 합니다

아키텍쳐

1

우리는 이제 EmpClient 의 데이터베이스를 캐싱할 DB 를 만들어둘것입니다 그리고 SavingMoney 는 EmpClient 에서 직접 데이터를 가져오지 않고 캐싱된 DB EmpClient 의 DB 의 데이터를 가져와서 로직을 처리합니다 그리고 EmpClient 에서 emp 데이터 변경시 메세지를 발행 , 그리고 SavingMoney 에서 해당 메세지를 통해서 캐싱 데이터를 무효화 하고 업데이트 하는 과정을 만들것입니다 이로 인해서 이전에는 EmpClient 와 강한결합이었던 SavingMoney 모듈이 이제 느슨하 결합으로 바뀌어 EmpClient 의 서버가 문제가 발생해도 영향이 덜가는 방향으로 만들어지게 됩니다

Redis

우리가 분산캐싱DB 를 사용할때 사용할 데이터베이스입니다 특징은 key-value 형태로 데이터가 저장되며 매우 빠른 읽기 쓰기 성능을 제공하는것이 장점인 경량 DB 의 일종입니다 그 외의 redis 는 분산 클러스트링 의 세션관리 등등 다양한 방면에서 사용되고 있지만 이번시간에는 제일 대표적으로 활용되고 있는 캐싱에 대해서 쓸려고 합니다

전체소스

https://gitlab.com/kimdongy1000/spring-cloud-project/-/tree/main-mini-project-messageQ-redis?ref_type=heads

Docker Redis 추가

1
2
3
4
5
6
7
8
9
10
 redis:
    image: redis:latest
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data      
    command: ["redis-server", "--requirepass", "1234567890", "--appendonly", "yes"]      
    container_name: redis    

우리는 위에서 말했다 싶히 캐싱 DB 로 redis 를 사용할것이기 때문에 이미지를 docker 에 만들어줍니다

config-server savingMoney-dev.yml

1
2
3
4
5
6
7
spring:
  redis:
    datasource:
      host: redis
      port: 6379
      password: 1234567890

config 서버에 redis 를 선언을 해주겠습니다

miniProjectSavingMoney pom.xml

1
2
3
4
5
6
7
8
9
10
11
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
<dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
</dependency>

의존성을 추가해줍니다 이때 gson 은 key-value 형태에서는 String 이 유리하기 때문에 이들을 변환해주는 역활을 하것입니다

miniProjectSavingMoney RedisConfig

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
35
36
37
38
39
@Configuration
public class RedisConfig {

    @Value("${spring.redis.datasource.host}")
    private String host;

    @Value("${spring.redis.datasource.port}")
    private int port;

    @Value("${spring.redis.datasource.password}")
    private String password;

    @Bean
    public RedisConnectionFactory redisConnectionFactory() {

        RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
        redisStandaloneConfiguration.setHostName(host);
        redisStandaloneConfiguration.setPort(port);
        redisStandaloneConfiguration.setPassword(password);

        LettuceConnectionFactory lettuceConnectionFactory = new LettuceConnectionFactory(redisStandaloneConfiguration);


        return lettuceConnectionFactory;
    }

    @Bean
    public RedisTemplate<String, Object> redisTemplate(){

        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory());
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new StringRedisSerializer());

        return redisTemplate;
    }
}


먼저 redis 와 통신할 Bean 을 생성하겠습니다

miniProjectSavingMoney RedisRepository

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
@Repository
public class RedisRepository {


    @Autowired
    private RedisTemplate<String , Object> redisTemplate;


    public void insert_redis_data(String key , Object value , long timeOut , TimeUnit timeUnit){
        redisTemplate.opsForValue().set(key , value , timeOut , timeUnit);
    }

    public Object select_redis_data(String key){
        return redisTemplate.opsForValue().get(key);
    }

    public boolean delete_redis_data(String key){
        return redisTemplate.delete(key);
    }

    public boolean expire_redis_data(String key , long timeOut , TimeUnit timeUnit){
        return redisTemplate.expire(key , timeOut , timeUnit);
    }

    public Set<String> select_redis_all_keys(){
        return redisTemplate.keys("*");
    }

}



redisTemplate 이 사용할 curd 메서드를 만들어주겠습니다

miniProjectSavingMoney EmpClientRedisTemplate

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
@Component
public class EmpClientRedisTemplate {

    private static final Logger log = LoggerFactory.getLogger(EmpClientRedisTemplate.class);

    @Autowired
    private RedisRepository redisRepository;

    @Autowired
    private SearchEurekaService searchEurekaService;

    @Autowired
    private Gson gson;

    public EmpDao checkEmpRedisData(String emp_id)
    {
        try{

            log.info("===============================================");
            log.info("checkEmpRedisData : {}" , emp_id);
            log.info("===============================================");

            String str_empDao = (String)redisRepository.select_redis_data(emp_id);
            return gson.fromJson(str_empDao, EmpDao.class);
        }catch(Exception e){
            log.error(e.getMessage());
            return null;
        }
    }

    public void saveEmpRedisData(EmpDao empDao)
    {
        try{

            log.info("===============================================");
            log.info("saveEmpRedisData : ");
            log.info("===============================================");

            String str_emp = gson.toJson(empDao);

            redisRepository.insert_redis_data(empDao.getEmp_id() , str_emp , 3600 , TimeUnit.SECONDS);
        }catch (Exception e){
            log.error(e.getMessage());

        }

    }

    public EmpDao getEmpRedisData(String emp_id)
    {
        try{

            log.info("===============================================");
            log.info("getEmpRedisData : {}" , emp_id);
            log.info("===============================================");

            EmpDao empDao = checkEmpRedisData(emp_id);

            if(empDao != null){
                return empDao;

            }else{

                String serviceUrl = searchEurekaService.getInstancesUri("miniProject-EmpClient");
                RestTemplate restTemplate = new RestTemplate();
                String requestUrl = String.format("%s/readEmp/%s", serviceUrl , emp_id);

                ResponseEntity<EmpDao> restExchange = restTemplate.exchange(
                        requestUrl,
                        HttpMethod.GET,
                        null ,
                        EmpDao.class
                );

                empDao = restExchange.getBody();
                saveEmpRedisData(empDao);
                return empDao;
            }

        }catch(Exception e){
            log.error(e.getMessage());
            return null;
        }

    }

    public void deleteEmpRedisData(String emp_id)
    {
        try{

            log.info("===============================================");
            log.info("deleteEmpRedisData : {}" , emp_id);
            log.info("===============================================");

            EmpDao empDao = checkEmpRedisData(emp_id);
            if(empDao != null){
                redisRepository.delete_redis_data(emp_id);
            }

        }catch(Exception e){
            log.error(e.getMessage());
        }
    }

    public void updateEmpRedisData(String emp_id , EmpDao empDao)
    {
        try{

            deleteEmpRedisData(emp_id);
            saveEmpRedisData(empDao);


        }catch(Exception e){
            log.error(e.getMessage());
        }

    }

    public Set<String> getAllEmp_id(){
        return redisRepository.select_redis_all_keys();
    }
}


이 부분이 핵심입니다 redisRepository 을 통해서 redis 에서 데이터를 체크하는 checkEmpRedisData 그리고 저장을 하는 saveEmpRedisData 그리고 업데이트 하는 updateEmpRedisData 삭제하는 deleteEmpRedisData 그리고 데이터를 가져오는 getEmpRedisData 들을 작성을 합니다 이 클래스를 통해서 empRedis 데이터를 관리할것입니다

miniProjectSavingMoney MiniProjectSavingMoneyApplication

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
35
36
@SpringBootApplication
public class MiniProjectSavingMoneyApplication {

	@Autowired
	private EmpClientRedisTemplate empClientRedisTemplate;

	private final static Logger log = LoggerFactory.getLogger(MiniProjectSavingMoneyApplication.class);

	public static void main(String[] args) {
		SpringApplication.run(MiniProjectSavingMoneyApplication.class, args);
	}

	@StreamListener("inputChannel")
	public void loggerSink(EmpChangeModel empChangeModel)
	{
		log.info("Received Message To EmpClient : {} , {}"  , empChangeModel.getStatus() , empChangeModel.getEmd_id());

		switch (empChangeModel.getStatus()) {
			case "CREATE":
				empClientRedisTemplate.saveEmpRedisData(empChangeModel.getEmpDao());
				break;
			case "READ":
				break;

			case "UPDATE":
				empClientRedisTemplate.updateEmpRedisData(empChangeModel.getEmd_id() , empChangeModel.getEmpDao());
				break;

			case "DELETE":
				empClientRedisTemplate.deleteEmpRedisData(empChangeModel.getEmd_id());
				break;
		}
	}
}


지난시간에는 단순 로그만 찍었다면 이제는 empChangeModel.getStatus() 를 통해서 캐싱작업을 진행을 하게 됩니다

miniProjectSavingMoney SavingMoneyController

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
@PostMapping("/createMoney")
public ResponseEntity<?> createMoney()
{
    try{


        Set<String> all_emp_id_set = empClientRedisTemplate.getAllEmp_id();
        Random rnd = new Random();
        int int_rnd = rnd.nextInt(all_emp_id_set.size());
        List<String> all_emp_id_list = new ArrayList<>(all_emp_id_set);
        String emp_id = all_emp_id_list.get(int_rnd);

        EmpDao empDao =  empClientRedisTemplate.getEmpRedisData(emp_id);


        SavingMoneyEntity savingMoneyEntity = new SavingMoneyEntity(empDao.getEmp_id() , new Random().nextInt(10000));
        int saving_id = savingMoneyRepository.save(savingMoneyEntity).getSavingMoneyId();

        Optional<SavingMoneyEntity> selectSavingMoneyEntity = savingMoneyRepository.findById(saving_id);
        if(selectSavingMoneyEntity.isPresent()){
            SavingMoneyEntity entity = selectSavingMoneyEntity.get();
            return new ResponseEntity<>(new SavingMoneyDao(entity.getSavingMoneyId() , entity.getEmp_id(), empDao.getEmp_name() , entity.getMoneyAmt()) , HttpStatus.OK);
        }

            return new ResponseEntity<>(null , null, HttpStatus.OK);
    }catch(Exception e){
        log.error(e.toString());
        return new ResponseEntity<>(null , null , HttpStatus.OK);
    }
}

이제 핵심코드 입니다 지난시간까지는 계속해서 empClient 와 지속적인 통신을 해서 emp 데이터를 가져와서 해당 로직을 진행을 했다면 이제는 자신이 가지고 있는 redisDB 의 캐싱을 통해서 진행을 하게 됩니다 이렇게 만들게 되면 SavingMoney 모듈은 empClient 와 강한 결합에서 느슨한 결합으로 바뀌게 됩니다 empClient 서버 상태의 유무에 상관 없이 SavingMoney 는 기존의 캐싱 DB 를 활용해서 지속적으로 서비스를 이어나갈 수 있게 되는 것입니다

우리는 지난시간에서 부터 이번시간까지 비동기 메세지를 이용해서 분산 캐싱에 대해서 알아보았습니다