채팅서버 cassandra 연동하기 - DevCamp2Flame/FlameTalk_Server GitHub Wiki

flame 팀 백엔드 다롬, 수연의 채팅 서버 페어 프로그래밍

목차

  1. docker 설치
  2. docker cassandra 컨테이너 실행 및 데이터 생성
  3. spring boot - cassandra 연동하기
  4. cassandra repository test

1. docker 설치

[Windows 10] Docker 설치 완벽 가이드(Home 포함)

2. docker cassandra 컨테이너 실행 및 데이터 생성

Docker Hub Cassandra 공식 도커 이미지로 컨테이너 띄우기(feat. cqlsh 실행)

// 이미지 pull
docker pull cassandra
// 컨테이너 실행
docker container run --name chat_cassandra -p 9042:9042 -d cassandra:latest
 
// cassandra 접속
docker exec -it chat_cassandra cqlsh

이후 실행, 중지는 docker GUI 에서 컨트롤 가능합니다.

Connection error: ('Unable to connect to any servers', {'127.0.0.1:9042': ConnectionRefusedError(111, "Tried connecting to [('127.0.0.1', 9042)]. Last error: Connection refused")})

이런 에러가 난다면,

> docker ps
CONTAINER ID   IMAGE              COMMAND                  CREATED              STATUS              PORTS                                                       NAMES
e4bcb2539bf7   cassandra:latest   "docker-entrypoint.s…"   About a minute ago   Up About a minute   7000-7001/tcp, 7199/tcp, 9160/tcp, 0.0.0.0:9042->9042/tcp   chat_cassandra
 
> docker exec -it e4bcb2539bf7 cqlsh
Connected to Test Cluster at 127.0.0.1:9042
[cqlsh 6.0.0 | Cassandra 4.0.1 | CQL spec 3.4.5 | Native protocol v5]
Use HELP for help.
cqlsh>

docker ps 명령어로 현재 실행하고 있는 container의 id 로 cqlsh 를 접속합니다.

2-1. Keyspace, table, data 만들기

keyspace 는 RDB의 스키마 같은 존재이고 table, data는 같은 개념입니다.

Keyspace 생성

// 기존 동일한 이름의 Keyspace 가 존재한다면 삭제
drop keyspace flametalk;
 
// Keyspace 생성
CREATE KEYSPACE IF NOT EXISTS flametalk WITH replication =
    {'class':'NetworkTopologyStrategy','datacenter1':1};
 
// Keyspace 접속
use flametalk;

Keyspace 이름을 flametalk 로 지정하고 NetworkTopology 전략을 선택하며 replica 1개로 설정합니다. 1개 이상 설정하면 오류로 실행이 되지 않습니다!

Cassandra Container DB NoNodeAvailableException

  • Docker로 Cassandra을 설치하였을 때, spring 에서 발생하는 node issue 문제 → NetworkTopologyStrategy, datacenter1 을 선택한 이유

table 생성

메시지 데이터 형식 : 채팅서버 구현정보 정리 의 채팅 메시지 데이터 참고

  1. message_id
  2. message_type
  3. sender_id
  4. nickname
  5. room_id
  6. contents
  7. file_url
  8. created_at
CREATE TABLE IF NOT EXISTS message (message_id text, message_type text, sender_id text, nickname text, room_id text, contents text, file_url blob, created_at timestamp, PRIMARY KEY (message_id));

Cassandra: text vs varchar

data 생성

insert into message (message_id, message_type, sender_id, nickname, room_id, contents, created_at) values ('1', 'TALK', '1', 'darom', '1', 'hi', '2015-05-03 13:30:54.234');
insert into message (message_id, message_type, sender_id, nickname, room_id, file_url, created_at) values ('1', 'TALK', '1', 'darom', '1', 'url', '2015-05-03 13:30:54.234');

3. spring boot - cassandra 연동하기

Spring docs

SpringBoot - cassandra CRUD API 예제

application.yml

spring:
    data:
    cassandra:
      port: 9042
      contact-points: 127.0.0.1
      schemaAction: CREATE_IF_NOT_EXISTS
      keyspace-name: flametalk
      local-datacenter: datacenter1

docker 로 띄웠기 때문에 contact-points 에 'localhost' 가 아니라 docker ip 주소를 작성해야 함 or 127.0.0.1

[윈도우 Docker IP 주소]

C:\Windows\System32\drivers\etc 폴더 - hosts 파일 (메모장으로 열기) - Add by Docker Desktop 찾기

CassandraConfig.java

@Configuration
@EnableCassandraRepositories(basePackages = { "com.devcamp.flametalk.domain" })
public class CassandraConfig {
 
  @Bean
  public CqlSessionFactoryBean session() {
 
    CqlSessionFactoryBean session = new CqlSessionFactoryBean();
    session.setContactPoints("127.0.0.1");
    session.setKeyspaceName("flametalk");
    session.setLocalDatacenter("datacenter1");
    session.setPort(9042);
 
    return session;
  }
 
  @Bean
  public SessionFactoryFactoryBean sessionFactory(CqlSession session, CassandraConverter converter) {
 
    SessionFactoryFactoryBean sessionFactory = new SessionFactoryFactoryBean();
    sessionFactory.setSession(session);
    sessionFactory.setConverter(converter);
    sessionFactory.setSchemaAction(SchemaAction.NONE);
 
    return sessionFactory;
  }
 
  @Bean
  public CassandraMappingContext mappingContext(CqlSession cqlSession) {
 
    CassandraMappingContext mappingContext = new CassandraMappingContext();
    mappingContext.setUserTypeResolver(new SimpleUserTypeResolver(cqlSession));
 
    return mappingContext;
  }
 
  @Bean
  public CassandraConverter converter(CassandraMappingContext mappingContext) {
    return new MappingCassandraConverter(mappingContext);
  }
 
  @Bean
  public CassandraOperations cassandraTemplate(SessionFactory sessionFactory, CassandraConverter converter) {
    return new CassandraTemplate(sessionFactory, converter);
  }
}

[주의] basePackages 에 repository 위치를 잘 작성해야 한다!

Message.java

@Builder
@Data
@AllArgsConstructor
@NoArgsConstructor
@Table("message")
public class Message {
 
  @Column
  @PrimaryKey
  private String message_id;
  
  @Column
  private String message_type;

  @Column
  private String sender_id;

  @Column
  private String nickname;

  @Column
  private String room_id;

  @Column
  private String contents;

  @Column
  private String file_url;

  @Column
  private LocalDateTime created_at;
}

MessageRepository.java

public interface MessageRepository extends CassandraRepository<Message, String> {
 
}

여기까지 완성하면, 연결은 성공! 아래서 테스트를 진행합니다.

4. cassandra repository test

MessageRepositoryTest.java

@SpringBootTest({"spring.data.cassandra.port=9042",
    "spring.data.cassandra.keyspace-name=flametalk"})
@ExtendWith(SpringExtension.class)
class MessageRepositoryTest {
 
  @Autowired
  private MessageRepository messageRepository;
 
  @Test
  void save() {
    // given
    String messageId = "1";
    String messageType = "ENTER";
    String senderId = "2";
    String nickname = "darom";
    String roomId = "3";
    String contents = "hihi";
    LocalDateTime date = LocalDateTime.now();

    Message message = Message.builder()
        .message_id(messageId)
        .message_type(messageType)
        .sender_id(senderId)
        .nickname(nickname)
        .room_id(roomId)
        .contents(contents)
        .created_at(date)
        .build();
 
    // when
    Message save = messageRepository.save(message);
 
    // then
    assertEquals(save.getMessage_id(), messageId);
  }
}

테스트를 실행하면 이미 넣어놨던 데이터가 아닌 새로운 데이터가 테이블에 추가됐음을 알 수 있다.

> select * from message;

마치며

docker도 nosql-cassandra도 test도 어느 하나 익숙한 것 없고 심지어 처음 사용해보는 것이 대부분인데, 어떻게든 해내겠다는 집념으로 테스트까지 성공했다는 것이 너무 감격스럽다 ㅜㅜ......

오늘 채팅 서버에 cassandra를 연동하기 위해 열어본 크롬창만 100개가 넘을 것 같다.😢

그래도 정말 다행인 점은 cassandra cqls가 sql 문과 거의 같다는 것이 한 줄기의 빛 같았다.

앞으로도 서로가 서로의 단점을 보완해주며 채팅 서버 완성까지 달려봅시다~!! 오늘도 수고 많으셨습니다.❤