ããã«ã¡ã¯ãHabrïŒ
æ°ããApacheãœãªã¥ãŒã·ã§ã³ã«åŒãç¶ãé¢å¿ããããŸãã 5æã«ã¯Holden Karauã®
ãHigh Performance Sparkã ïŒçµçã®æ¬ïŒã8æã«ã¯Nii Narhidã®
ãKafkaïŒThe Definitive Guideã ïŒãŸã 翻蚳äžïŒãšããæ¬ããªãªãŒã¹ããããšæã£ãŠããŸãã æ¬æ¥ã¯ãApache Igniteã«ã€ããŠã®ç°¡åãªçŽ¹ä»èšäºãæäŸãããããã¯ã«å¯Ÿããé¢å¿ã®èŠæš¡ãè©äŸ¡ããããšæããŸãã
çŽ æµãªèªæžãïŒ
Apache Igniteã¯æ¯èŒçæ°ãããœãªã¥ãŒã·ã§ã³ã§ããããã®äººæ°ã¯æ¥éã«é«ãŸã£ãŠããŸãã Igniteæ©èœã«ããããã€ãã®ããŒã«ã«äŒŒãŠãããããããŒã¿ããŒã¹ãšã³ãžã³ã®ç¹å®ã®äºçš®ã«èµ·å ããããšã¯å°é£ã§ãã ãã®ããŒã«ã®äž»ãªç®çã¯ãRAMã«åæ£ããŒã¿ãä¿åããããšãšãããŒãšå€ã®åœ¢åŒã§æ
å ±ãä¿åããããšã§ãã Igniteã«ã¯ãäžè¬çãªRDBMSæ©èœãç¹ã«SQLã¯ãšãªãšACIDãã©ã³ã¶ã¯ã·ã§ã³ã®ãµããŒãããããŸãã ããããããã¯ããã®ãœãªã¥ãŒã·ã§ã³ãSQLã§ãã©ã³ã¶ã¯ã·ã§ã³ãåŠçããããã®å
žåçãªããŒã¿ããŒã¹ã§ããããšãæå³ãããã®ã§ã¯ãããŸããã ããã§ã¯å€éšããŒã®å¶éã¯ãµããŒããããŠãããããã©ã³ã¶ã¯ã·ã§ã³ã¯ããŒãšå€ã®ã¬ãã«ã§ã®ã¿å©çšã§ããŸãã ãã ããApache Igniteã¯éåžžã«èå³æ·±ããœãªã¥ãŒã·ã§ã³ã®ããã§ãã
Apache Igniteã¯ãSpring Bootã¢ããªã±ãŒã·ã§ã³ã«çµã¿èŸŒãŸãããã¹ããšããŠç°¡åã«èµ·åã§ããŸãã ãããå®çŸããæãç°¡åãªæ¹æ³ã¯ãSpring Data Igniteã©ã€ãã©ãªã䜿çšããããšã§ãã Apache Igniteã¯ãçµ±åãããSpring Dataã€ã³ã¿ãŒãã§ã€ã¹ã䜿çšããŠApache Ignite SQLã°ãªãããžã®ã¢ã¯ã»ã¹ãæäŸããã ãã§ãªããåºæ¬çãªCRUDæäœããµããŒãããSpring Data
CrudRepository
å®è£
ããŠããŸãã SQLãµããŒããšACIDãã©ãã€ã ã䜿çšããŠãã£ã¹ã¯ã¹ãã¬ãŒãžã«ããŒã¿ã®æ°žç¶æ§ãæäŸããŸãããMySQLããŒã¿ããŒã¹ã«RAMãã£ãã·ã¥ãªããžã§ã¯ããä¿åããããã®ãœãªã¥ãŒã·ã§ã³ãéçºããŸããã ææ¡ããããœãªã¥ãŒã·ã§ã³ã®ã¢ãŒããã¯ãã£ã次ã®å³ã«ç€ºããŸã-ã芧ã®ãšãããéåžžã«åçŽã§ãã ã¢ããªã±ãŒã·ã§ã³ã¯ãApache Igniteã«é
眮ãããRAMãã£ãã·ã¥ã«ããŒã¿ãé
眮ããŸãã Apache Igniteã¯ãéåæããã¯ã°ã©ãŠã³ãã¿ã¹ã¯äžã«ãããã®å€æŽãããŒã¿ããŒã¹ãšèªåçã«åæããŸãã ãã®ã¢ããªã±ãŒã·ã§ã³ã§ããŒã¿ãèªã¿åãæ¹æ³ããé©ãããšã§ã¯ãããŸããã ãšã³ãã£ãã£ããã£ãã·ã¥ãããŠããªãå ŽåãããŒã¿ããŒã¹ããèªã¿åãããå°æ¥ã®ããã«ãã£ãã·ã¥ãããŸãã
ããã§ã¯ããã®çš®ã®ã¢ããªã±ãŒã·ã§ã³ã®éçºæ¹æ³ã«ã€ããŠè©³ãã説æããŸãã çµæã¯GitHubã«æçš¿ãããŸãã ã€ã³ã¿ãŒãããã§ããã«ããã€ãã®äŸãèŠã€ããŸããããåºæ¬ã ããã«ããŒãããŠããŸãã ãã£ãã·ã¥ããããŒã¿ããŒã¹ã«ãªããžã§ã¯ããæžã蟌ãããã«Apache Igniteãæ§æããæ¹æ³ãããã³è€æ°ã®ãã£ãã·ã¥ã䜿çšããŠããè€éãªããŒãžèŠæ±ãäœæããæ¹æ³ã瀺ããŸãã ããŒã¿ããŒã¹ãèµ·åããããšããå§ããŸãããã
1. MySQLããŒã¿ããŒã¹ãã»ããã¢ãããã
MySQLããŒã¿ããŒã¹ãããŒã«ã«ã§å®è¡ããã«ã¯ããã¡ããDockerã³ã³ããã䜿çšããã®ãæåã§ãã Docker for Windowsã®MySQLããŒã¿ããŒã¹ã¯çŸåš192.168.99.100:33306ã§å©çšå¯èœã§ãã
docker run -d
次ã«ãã¢ããªã±ãŒã·ã§ã³ãšã³ãã£ãã£ãããŒã¿ãä¿åããããã«äœ¿çšããããŒãã«
PERSON
ã
CONTACT
ãäœæããŸãã ãããã¯ãããŒãã«ã1 ... NãšããŠåç
§ããŸã
CONTACT
ããŒãã«ã«ã¯ã
PERSON id
æãå€éšããŒãå«ãŸããŸãã
CREATE TABLE `person` ( `id` int(11) NOT NULL, `first_name` varchar(45) DEFAULT NULL, `last_name` varchar(45) DEFAULT NULL, `gender` varchar(10) DEFAULT NULL, `country` varchar(10) DEFAULT NULL, `city` varchar(20) DEFAULT NULL, `address` varchar(45) DEFAULT NULL, `birth_date` date DEFAULT NULL, PRIMARY KEY (`id`) ); CREATE TABLE `contact` ( `id` int(11) NOT NULL, `location` varchar(45) DEFAULT NULL, `contact_type` varchar(10) DEFAULT NULL, `person_id` int(11) NOT NULL, PRIMARY KEY (`id`) ); ALTER TABLE `ignite`.`contact` ADD INDEX `person_fk_idx` (`person_id` ASC); ALTER TABLE `ignite`.`contact` ADD CONSTRAINT `person_fk` FOREIGN KEY (`person_id`) REFERENCES `ignite`.`person` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
2. Mavenãæ§æãã
Apache Igniteã®Spring DataãªããžããªãŒãéå§ããããã®æãç°¡åãªæ¹æ³ã¯ãã¢ããªã±ãŒã·ã§ã³ã®
pom.xml
ãã¡ã€ã«ã«æ¬¡ã®MavenäŸåé¢ä¿ãè¿œå ããããšã§ãã ä»ã®ãã¹ãŠã®IgniteäŸåé¢ä¿ã¯èªåçã«å«ãŸããŸãã ããŒã¿ããŒã¹æ¥ç¶ãæ§æããã«ã¯ãMySQL JDBCãã©ã€ããŒãšSpring JDBCäŸåé¢ä¿ãå¿
èŠã§ãã Apache Igniteãã¢ããªã±ãŒã·ã§ã³ã«åã蟌ã¿ããã£ãã·ã¥ãããŒã¿ããŒã¹ããŒãã«ãšåæã§ããããã«MySQLããŒã¿ããŒã¹ã«æ¥ç¶ããå¿
èŠããããããããããå¿
èŠã§ãã
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.apache.ignite</groupId> <artifactId>ignite-spring-data</artifactId> <version>${ignite.version}</version> </dependency>
3. IgniteããŒããæ§æãã
IgniteConfiguration
ã¯ã©ã¹ã䜿çšãããšãIgniteããŒãã§äœ¿çšå¯èœãªãã¹ãŠã®èšå®ãæ§æã§ããŸãã ãã®å Žåããã£ãã·ã¥æ§æïŒ1ïŒãæãéèŠã§ãã ãã¹ã¿ãŒããŒãšãšã³ãã£ãã£ã¯ã©ã¹ãã€ã³ããã¯ã¹ä»ãã¿ã€ããšããŠè¿œå ããå¿
èŠããããŸãïŒ2ïŒã 次ã«ããã£ãã·ã¥æŽæ°ã®ããŒã¿ããŒã¹ãžã®ãšã¯ã¹ããŒããæäŸãïŒ3ïŒããã£ãã·ã¥ã«ãªãæ
å ±ãããŒã¿ããŒã¹ããèªã¿åãå¿
èŠããããŸãïŒ4ïŒã IgniteããŒããšMySQLéã®çžäºäœçšã¯ã
CacheJdbcPojoStoreFactory
ïŒ5ïŒã¯ã©ã¹ã䜿çšããŠæ§æã§ããŸãã ããã§ã
DataSource @Bean
ïŒ6ïŒãæ¹èšïŒ7ïŒãããã³ãªããžã§ã¯ãã®ãã£ãŒã«ããšããŒãã«ã®åã®éã®å¯Ÿå¿ïŒ8ïŒãæž¡ãå¿
èŠããããŸãã
@Bean public Ignite igniteInstance() { IgniteConfiguration cfg = new IgniteConfiguration(); cfg.setIgniteInstanceName("ignite-1"); cfg.setPeerClassLoadingEnabled(true); CacheConfiguration<Long, Contact> ccfg2 = new CacheConfiguration<>("ContactCache");
Dockerã³ã³ãããšããŠã®MySQLã®SpringããŒã¿ãœãŒã¹èšå®ã¯æ¬¡ã®ãšããã§ãã
spring:
datasource:
name: mysqlds
url: jdbc:mysql://192.168.99.100:33306/ignite?useSSL=false
username: ignite
password: ignite123
ããã§ãApache Igniteã«ã¯ããã€ãã®æ¬ ç¹ããªãããã§ã¯ãªãããšã«æ³šæããŠãã ããã ããšãã°ã
Enum
ãæŽæ°ã«ããããããã®åºæ°å€ãåããŸãããVARCHARãJDCBã¿ã€ããšããŠæ§æããŸãã ãã®ãããªã·ãªãŒãºãããŒã¿ããŒã¹ããèªã¿åããããšããªããžã§ã¯ãã®Enumã«èª€ã£ãŠè¡šç€ºãã
null
ããã®å¿çãã£ãŒã«ãã§ã¯
null
ã«ãªã
null
ã
new JdbcTypeField(Types.VARCHAR, "contact_type", ContactType.class, "type")
4.ã¢ãã«ãªããžã§ã¯ã
åè¿°ã®ããã«ãããŒã¿ããŒã¹ã¹ããŒãã«ã¯2ã€ã®ããŒãã«ããããŸãã ãŸãã2ã€ã®ã¢ãã«ã¯ã©ã¹ãš2ã€ã®ãã£ãã·ã¥æ§æããããåã¢ãã«ã¯ã©ã¹ã«1ã€ãã€ãããŸãã 以äžã¯ãã¢ãã«ã¯ã©ã¹ã®å®è£
ã§ãã ããã§æ³šæãã¹ãæãèå³æ·±ãããšã®1ã€ã¯ã
AtomicLong
ã¯ã©ã¹ã䜿çšããŠIDãçæããããšã§ãã ããã¯ãã·ãŒã±ã³ã¹ãžã§ãã¬ãŒã¿ãŒãšããŠæ©èœããIgniteã®åºæ¬ã³ã³ããŒãã³ãã®1ã€ã§ãã ãŸããç¹å®ã®ã¢ãããŒã·ã§ã³
@QuerySqlField
ã確èª
@QuerySqlField
ãŸãã ãã£ãŒã«ãã«ä»éããå Žåããã®ãã£ãŒã«ãã¯SQLã§ã¯ãšãªãã©ã¡ãŒã¿ãšããŠäœ¿çšã§ããããšãæå³ããŸãã
@QueryGroupIndex.List( @QueryGroupIndex(name="idx1") ) public class Person implements Serializable { private static final long serialVersionUID = -1271194616130404625L; private static final AtomicLong ID_GEN = new AtomicLong(); @QuerySqlField(index = true) private Long id; @QuerySqlField(index = true) @QuerySqlField.Group(name = "idx1", order = 0) private String firstName; @QuerySqlField(index = true) @QuerySqlField.Group(name = "idx1", order = 1) private String lastName; private Gender gender; private Date birthDate; private String country; private String city; private String address; private List<Contact> contacts = new ArrayList<>(); public void init() { this.id = ID_GEN.incrementAndGet(); } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } public Gender getGender() { return gender; } public void setGender(Gender gender) { this.gender = gender; } public Date getBirthDate() { return birthDate; } public void setBirthDate(Date birthDate) { this.birthDate = birthDate; } public String getCountry() { return country; } public void setCountry(String country) { this.country = country; } public String getCity() { return city; } public void setCity(String city) { this.city = city; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } public List<Contact> getContacts() { return contacts; } public void setContacts(List<Contact> contacts) { this.contacts = contacts; } }
5.ãªããžããªã«ç¹ç«ãã
Spring Data JPAã§ãªããžããªãã©ã®ããã«äœæããããç¥ã£ãŠãããšæããŸãã ãªããžããªåŠçã¯ã
main
ãŸãã¯
@Configuration
ã§æäŸããå¿
èŠããããŸãã
@SpringBootApplication @EnableIgniteRepositories public class IgniteRestApplication { @Autowired DataSource datasource; public static void main(String[] args) { SpringApplication.run(IgniteRestApplication.class, args); }
次ã«ã
@Repository
ã€ã³ã¿ãŒãã§ãŒã¹ã
@Repository
ããŒã¹ã€ã³ã¿ãŒãã§ãŒã¹ã§æ¡åŒµããŸãã
id
ãã©ã¡ãŒã¿ãæã€ç¶æ¿ãããã¡ãœããã®ã¿ããµããŒãããŸãã
PersonRepository
ã®
PersonRepository
ã¹ãããã
PersonRepository
v Spring Dataããã³Igniteã¯ãšãªã§æ¡çšãããŠããåœåèŠåã䜿çšããŠãããã€ãã®æ€çŽ¢æ¹æ³ãå®çŸ©ããŸããã ãããã®äŸã¯ãå¿
èŠã«å¿ããŠãã¯ãšãªçµæã§å®å
šãªãªããžã§ã¯ããŸãã¯ãªããžã§ã¯ãããéžæãããã£ãŒã«ããè¿ãããšãã§ããããšã瀺ããŠããŸãã
@RepositoryConfig(cacheName = "PersonCache") public interface PersonRepository extends IgniteRepository<Person, Long> { List<Person> findByFirstNameAndLastName(String firstName, String lastName); @Query("SELECT c.* FROM Person p JOIN \"ContactCache\".Contact c ON p.id=c.personId WHERE p.firstName=? and p.lastName=?") List<Contact> selectContacts(String firstName, String lastName); @Query("SELECT p.id, p.firstName, p.lastName, c.id, c.type, c.location FROM Person p JOIN \"ContactCache\".Contact c ON p.id=c.personId WHERE p.firstName=? and p.lastName=?") List<List<?>> selectContacts2(String firstName, String lastName); }
6. APIãšãã¹ã
ããã§ãRESTã³ã³ãããŒã©ã¯ã©ã¹ã«ãªããžããªã³ã³ããŒãã³ããåã蟌ãããšãã§ããŸãã ãã®APIã¯ãæ°ãããªããžã§ã¯ãããã£ãã·ã¥ã«è¿œå ããããæ¢åã®ãªããžã§ã¯ããæŽæ°ãŸãã¯åé€ããããäž»ããŒããã®ä»ã®ããè€éãªã€ã³ããã¯ã¹ã§æ€çŽ¢ãããããããã®ã¡ãœãããæäŸããŸãã
@RestController @RequestMapping("/person") public class PersonController { private static final Logger LOGGER = LoggerFactory.getLogger(PersonController.class); @Autowired PersonRepository repository; @PostMapping public Person add(@RequestBody Person person) { person.init(); return repository.save(person.getId(), person); } @PutMapping public Person update(@RequestBody Person person) { return repository.save(person.getId(), person); } @DeleteMapping("/{id}") public void delete(Long id) { repository.delete(id); } @GetMapping("/{id}") public Person findById(@PathVariable("id") Long id) { return repository.findOne(id); } @GetMapping("/{firstName}/{lastName}") public List<Person> findByName(@PathVariable("firstName") String firstName, @PathVariable("lastName") String lastName) { return repository.findByFirstNameAndLastName(firstName, lastName); } @GetMapping("/contacts/{firstName}/{lastName}") public List<Person> findByNameWithContacts(@PathVariable("firstName") String firstName, @PathVariable("lastName") String lastName) { List<Person> persons = repository.findByFirstNameAndLastName(firstName, lastName); List<Contact> contacts = repository.selectContacts(firstName, lastName); persons.stream().forEach(it -> it.setContacts(contacts.stream().filter(c -> c.getPersonId().equals(it.getId())).collect(Collectors.toList()))); LOGGER.info("PersonController.findByIdWithContacts: {}", contacts); return persons; } @GetMapping("/contacts2/{firstName}/{lastName}") public List<Person> findByNameWithContacts2(@PathVariable("firstName") String firstName, @PathVariable("lastName") String lastName) { List<List<?>> result = repository.selectContacts2(firstName, lastName); List<Person> persons = new ArrayList<>(); for (List<?> l : result) { persons.add(mapPerson(l)); } LOGGER.info("PersonController.findByIdWithContacts: {}", result); return persons; } private Person mapPerson(List<?> l) { Person p = new Person(); Contact c = new Contact(); p.setId((Long) l.get(0)); p.setFirstName((String) l.get(1)); p.setLastName((String) l.get(2)); c.setId((Long) l.get(3)); c.setType((ContactType) l.get(4)); c.setLocation((String) l.get(4)); p.addContact(c); return p; } }
ãã¡ãããäœæããããœãªã¥ãŒã·ã§ã³ã®ããã©ãŒãã³ã¹ã確èªããããšã¯éèŠã§ããç¹ã«ãåæ£ããŒã¿ãRAMããã³ããŒã¿ããŒã¹ã«ä¿åããå Žåã¯éèŠã§ãã ãããè¡ãããã«ãå€æ°ã®ãªããžã§ã¯ãããã£ãã·ã¥ããŠããæ€çŽ¢ã¡ãœãããåŒã³åºãããã€ãã®junitãã¹ããäœæããŸããïŒã©ã³ãã ããŒã¿ãå
¥åã«äœ¿çšãããŸãïŒ-ãããã¯ãšãªã®ããã©ãŒãã³ã¹ã®ç¢ºèªæ¹æ³ã§ãã å€ãã®
Person
ããã³
Contact
ãªããžã§ã¯ããçæãããšã³ããã€ã³ãAPIã䜿çšããŠãã£ãã·ã¥ã«é
眮ããã¡ãœããã次ã«ç€ºããŸãã
@Test public void testAddPerson() throws InterruptedException { ExecutorService es = Executors.newCachedThreadPool(); for (int j = 0; j < 10; j++) { es.execute(() -> { TestRestTemplate restTemplateLocal = new TestRestTemplate(); Random r = new Random(); for (int i = 0; i < 1000000; i++) { Person p = restTemplateLocal.postForObject("http://localhost:8090/person", createTestPerson(), Person.class); int x = r.nextInt(6); for (int k = 0; k < x; k++) { restTemplateLocal.postForObject("http://localhost:8090/contact", createTestContact(p.getId()), Contact.class); } } }); } es.shutdown(); es.awaitTermination(60, TimeUnit.MINUTES); }
Spring Bootã¯ãAPIã®å¿çé床ãå€æããããã®åºæ¬çãªç¹æ§ãååŸããããã®ã¡ãœãããæäŸããŸãã ãã®æ©èœãæå¹ã«ããã«ã¯ã
Spring Actuator
äŸåé¢ä¿ãæå¹ã«ããå¿
èŠããããŸãã Metricsãšã³ããã€ã³ãã¯ã
localhost ïŒ8090 / metricsã§å©çšã§ããŸãã åAPIã¡ãœãããæ©èœããã®ã«ãããæéã瀺ãã ãã§ãªããã¢ã¯ãã£ããªã¹ã¬ããã®æ°ã空ãã¡ã¢ãªãªã©ã®ã€ã³ãžã±ãŒã¿ãŒã®çµ±èšã衚瀺ããŸãã
7.ã¢ããªã±ãŒã·ã§ã³ã®èµ·å
Apache IgniteããŒããçµ±åãããçµæã®ã¢ããªã±ãŒã·ã§ã³ãå®è¡ããŸãã Igniteã®ããã¥ã¡ã³ãã«å«ãŸããããã©ãŒãã³ã¹ã®ãã³ããèæ
®ãã以äžã«ç€ºãJVMæ§æã決å®ããŸããã
java -jar -Xms512m -Xmx1024m -XX:MaxDirectMemorySize=256m -XX:+DisableExplicitGC -XX:+UseG1GC target/ignite-rest-service-1.0-SNAPSHOT.jar
ããã§ã
JUnit IgniteRestControllerTest
ãã¹ãã¯ã©ã¹ãå®è¡ã§ããŸãã äžå®éã®ããŒã¿ããã£ãã·ã¥ããæ€çŽ¢ã¡ãœãããåŒã³åºããŸãã ãã©ã¡ãŒã¿ãŒã¯ã1Mã®
Person
ãªããžã§ã¯ããš2.5Mã®
Contact
ãªããžã§ã¯ãããã£ãã·ã¥ã§äœ¿çšããããã¹ãçšã«æäŸãããŸãã åæ€çŽ¢æ¹æ³ã¯ãå¹³åã§1ããªç§ã§å®è¡ãããŸãã
{ "mem": 624886, "mem.free": 389701, "processors": 4, "instance.uptime": 2446038, "uptime": 2466661, "systemload.average": -1, "heap.committed": 524288, "heap.init": 524288, "heap.used": 133756, "heap": 1048576, "threads.peak": 107, "threads.daemon": 25, "threads.totalStarted": 565, "threads": 80, ... "gauge.response.person.contacts.firstName.lastName": 1, "gauge.response.contact": 1, "gauge.response.person.firstName.lastName": 1, "gauge.response.contact.location.location": 1, "gauge.response.person.id": 1, "gauge.response.person": 0, "counter.status.200.person.id": 1000, "counter.status.200.person.contacts.firstName.lastName": 1000, "counter.status.200.person.firstName.lastName": 1000, "counter.status.200.contact": 2500806, "counter.status.200.person": 1000000, "counter.status.200.contact.location.location": 1000 }